mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
1c0477f861 | |||
28a3418ad5 | |||
c5db2c235f | |||
9cbddaeedf | |||
485dfe2888 | |||
78a26dbf3c | |||
b2c72c6420 | |||
6852494d36 | |||
305166ce68 | |||
400f25c23e | |||
3aacd3461d | |||
7e2460e1f0 |
35
.github/workflows/docker.yml
vendored
35
.github/workflows/docker.yml
vendored
@ -20,39 +20,42 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push to Docker Hub
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: |
|
||||
n1try/wakapi:${{ env.GIT_TAG }}
|
||||
n1try/wakapi:latest
|
||||
n1try/wakapi:${{ env.GIT_TAG }}
|
||||
ghcr.io/${{ github.repository }}:latest
|
||||
ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
cache-from: type=registry,ref=n1try/wakapi:buildcache
|
||||
cache-to: type=registry,ref=n1try/wakapi:buildcache,mode=max
|
||||
|
||||
- name: Build and push to Docker Hub (Alpine)
|
||||
- name: Build and push (Alpine)
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: Dockerfile.alpine
|
||||
push: true
|
||||
tags: |
|
||||
n1try/wakapi:${{ env.GIT_TAG }}-alpine
|
||||
n1try/wakapi:latest-alpine
|
||||
n1try/wakapi:${{ env.GIT_TAG }}-alpine
|
||||
ghcr.io/${{ github.repository }}:latest-alpine
|
||||
ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }}-alpine
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
cache-from: type=registry,ref=n1try/wakapi:buildcache-alpine
|
||||
cache-to: type=registry,ref=n1try/wakapi:buildcache-alpine,mode=max
|
||||
|
23
README.md
23
README.md
@ -32,21 +32,7 @@
|
||||
<img src="static/assets/images/screenshot.png" width="500px">
|
||||
</p>
|
||||
|
||||
## Table of Contents
|
||||
* [User Survey](#-user-survey)
|
||||
* [Features](#-features)
|
||||
* [Roadmap](#-roadmap)
|
||||
* [How to use](#%EF%B8%8F-how-to-use)
|
||||
* [Configuration Options](#-configuration-options)
|
||||
* [API Endpoints](#-api-endpoints)
|
||||
* [Integrations](#-integrations)
|
||||
* [Best Practices](#-best-practices)
|
||||
* [Tests](#-tests)
|
||||
* [Developer Notes](#-developer-notes)
|
||||
* [Support](#-support)
|
||||
* [FAQs](#-faqs)
|
||||
|
||||
Further instructions can be found in the [Wiki](https://github.com/muety/wakapi/wiki).
|
||||
Installation instructions can be found below and in the [Wiki](https://github.com/muety/wakapi/wiki).
|
||||
|
||||
## 📬 **User Survey**
|
||||
I'd love to get some community feedback from active Wakapi users. If you want, please participate in the recent [user survey](https://github.com/muety/wakapi/issues/82). Thanks a lot!
|
||||
@ -71,7 +57,7 @@ Plans for the near future mainly include, besides usual improvements and bug fix
|
||||
There are different options for how to use Wakapi, ranging from our hosted cloud service to self-hosting it. Regardless of which option choose, you will always have to do the [client setup](#-client-setup) in addition.
|
||||
|
||||
### ☁️ Option 1: Use [wakapi.dev](https://wakapi.dev)
|
||||
If you want to you out free, hosted cloud service, all you need to do is create an account and the set up your client-side tooling (see below).
|
||||
If you want to try out free, hosted cloud service, all you need to do is create an account and the set up your client-side tooling (see below).
|
||||
|
||||
However, we do not guarantee data persistence, so you might potentially lose your data if the service is taken down some day ❕
|
||||
|
||||
@ -187,9 +173,6 @@ Wakapi uses [GORM](https://gorm.io) as an ORM. As a consequence, a set of differ
|
||||
* [Postgres](https://hub.docker.com/_/postgres) (_open-source as well_)
|
||||
* [CockroachDB](https://www.cockroachlabs.com/docs/stable/install-cockroachdb-linux.html) (_cloud-native, distributed, Postgres-compatible API_)
|
||||
|
||||
### Client-side proxy (`optional`)
|
||||
See the [advanced setup instructions](docs/advanced_setup.md).
|
||||
|
||||
## 🔧 API Endpoints
|
||||
See our [Swagger API Documentation](https://wakapi.dev/swagger-ui).
|
||||
|
||||
@ -359,7 +342,7 @@ All data is cached locally on your machine and sent in batches once you're onlin
|
||||
<details>
|
||||
<summary><b>How did Wakapi come about?</b></summary>
|
||||
|
||||
Wakapi was started when I was a student, who wanted to track detailed statistics about my coding time. Although I'm a big fan of WakaTime I didn't want to pay <a href="https://wakatime.com/pricing)">9 $ a month</a> back then. Luckily, most parts of WakaTime are open source!
|
||||
Wakapi was started when I was a student, who wanted to track detailed statistics about my coding time. Although I'm a big fan of WakaTime I didn't want to pay <a href="https://wakatime.com/pricing">$9 a month</a> back then. Luckily, most parts of WakaTime are open source!
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
@ -239,6 +239,10 @@ func (c *appConfig) GetWeeklyReportTime() string {
|
||||
return strings.Split(c.ReportTimeWeekly, ",")[1]
|
||||
}
|
||||
|
||||
func (c *dbConfig) IsSQLite() bool {
|
||||
return c.Dialect == "sqlite3"
|
||||
}
|
||||
|
||||
func (c *serverConfig) GetPublicUrl() string {
|
||||
return strings.TrimSuffix(c.PublicUrl, "/")
|
||||
}
|
||||
@ -365,6 +369,10 @@ func Load(version string) *Config {
|
||||
if config.Db.MaxConn <= 0 {
|
||||
logbuch.Fatal("you must allow at least one database connection")
|
||||
}
|
||||
if config.Db.MaxConn > 1 && config.Db.IsSQLite() {
|
||||
logbuch.Warn("with sqlite, only a single connection is supported") // otherwise 'PRAGMA foreign_keys=ON' would somehow have to be set for every connection in the pool
|
||||
config.Db.MaxConn = 1
|
||||
}
|
||||
if config.Mail.Provider != "" && findString(config.Mail.Provider, emailProviders, "") == "" {
|
||||
logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider)
|
||||
}
|
||||
|
9
main.go
9
main.go
@ -118,9 +118,12 @@ func main() {
|
||||
// Connect to database
|
||||
var err error
|
||||
db, err = gorm.Open(config.Db.GetDialector(), &gorm.Config{Logger: gormLogger})
|
||||
if config.Db.Dialect == "sqlite3" {
|
||||
db.Raw("PRAGMA foreign_keys = ON;")
|
||||
db.DisableForeignKeyConstraintWhenMigrating = true
|
||||
if config.Db.IsSQLite() {
|
||||
db.Exec("PRAGMA foreign_keys = ON;")
|
||||
if !utils.IsCleanDB(db) && !utils.HasConstraints(db) {
|
||||
db.DisableForeignKeyConstraintWhenMigrating = true
|
||||
logbuch.Warn("using existing sqlite database without foreign key constraints and no ability to migrate, functionality may be limited")
|
||||
}
|
||||
}
|
||||
|
||||
if config.IsDev() {
|
||||
|
@ -9,11 +9,12 @@ import (
|
||||
func TestUser_TZ(t *testing.T) {
|
||||
sut1, sut2 := &User{Location: ""}, &User{Location: "America/Los_Angeles"}
|
||||
pst, _ := time.LoadLocation("America/Los_Angeles")
|
||||
_, offset := time.Now().Zone()
|
||||
_, offset1 := time.Now().Zone()
|
||||
_, offset2 := time.Now().In(pst).Zone()
|
||||
|
||||
assert.Equal(t, time.Local, sut1.TZ())
|
||||
assert.Equal(t, pst, sut2.TZ())
|
||||
|
||||
assert.InDelta(t, time.Duration(offset*int(time.Second)), sut1.TZOffset(), float64(1*time.Second))
|
||||
assert.InDelta(t, time.Duration(-7*int(time.Hour)), sut2.TZOffset(), float64(1*time.Second))
|
||||
assert.InDelta(t, time.Duration(offset1*int(time.Second)), sut1.TZOffset(), float64(1*time.Second))
|
||||
assert.InDelta(t, time.Duration(offset2*int(time.Second)), sut2.TZOffset(), float64(1*time.Second))
|
||||
}
|
||||
|
32
utils/db.go
Normal file
32
utils/db.go
Normal file
@ -0,0 +1,32 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/emvi/logbuch"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func IsCleanDB(db *gorm.DB) bool {
|
||||
if db.Dialector.Name() == "sqlite" {
|
||||
var count int64
|
||||
if err := db.Raw("SELECT count(*) from sqlite_master WHERE type = 'table'").Scan(&count).Error; err != nil {
|
||||
logbuch.Error("failed to check if database is clean - '%v'", err)
|
||||
return false
|
||||
}
|
||||
return count == 0
|
||||
}
|
||||
logbuch.Warn("IsCleanDB is not yet implemented for dialect '%s'", db.Dialector.Name())
|
||||
return false
|
||||
}
|
||||
|
||||
func HasConstraints(db *gorm.DB) bool {
|
||||
if db.Dialector.Name() == "sqlite" {
|
||||
var count int64
|
||||
if err := db.Raw("SELECT count(*) from sqlite_master WHERE sql LIKE '%CONSTRAINT%'").Scan(&count).Error; err != nil {
|
||||
logbuch.Error("failed to check if database has constraints - '%v'", err)
|
||||
return false
|
||||
}
|
||||
return count != 0
|
||||
}
|
||||
logbuch.Warn("HasForeignKeyConstraints is not yet implemented for dialect '%s'", db.Dialector.Name())
|
||||
return false
|
||||
}
|
@ -1 +1 @@
|
||||
1.30.1
|
||||
1.30.2
|
||||
|
Reference in New Issue
Block a user