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
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
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
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
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
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
n1try/wakapi:${{ env.GIT_TAG }}
|
|
||||||
n1try/wakapi:latest
|
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
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=registry,ref=n1try/wakapi:buildcache
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
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
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
file: Dockerfile.alpine
|
file: Dockerfile.alpine
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
n1try/wakapi:${{ env.GIT_TAG }}-alpine
|
|
||||||
n1try/wakapi:latest-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
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=registry,ref=n1try/wakapi:buildcache-alpine
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
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">
|
<img src="static/assets/images/screenshot.png" width="500px">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Table of Contents
|
Installation instructions can be found below and in the [Wiki](https://github.com/muety/wakapi/wiki).
|
||||||
* [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).
|
|
||||||
|
|
||||||
## 📬 **User Survey**
|
## 📬 **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!
|
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.
|
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)
|
### ☁️ 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 ❕
|
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_)
|
* [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_)
|
* [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
|
## 🔧 API Endpoints
|
||||||
See our [Swagger API Documentation](https://wakapi.dev/swagger-ui).
|
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>
|
<details>
|
||||||
<summary><b>How did Wakapi come about?</b></summary>
|
<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>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@@ -239,6 +239,10 @@ func (c *appConfig) GetWeeklyReportTime() string {
|
|||||||
return strings.Split(c.ReportTimeWeekly, ",")[1]
|
return strings.Split(c.ReportTimeWeekly, ",")[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *dbConfig) IsSQLite() bool {
|
||||||
|
return c.Dialect == "sqlite3"
|
||||||
|
}
|
||||||
|
|
||||||
func (c *serverConfig) GetPublicUrl() string {
|
func (c *serverConfig) GetPublicUrl() string {
|
||||||
return strings.TrimSuffix(c.PublicUrl, "/")
|
return strings.TrimSuffix(c.PublicUrl, "/")
|
||||||
}
|
}
|
||||||
@@ -365,6 +369,10 @@ func Load(version string) *Config {
|
|||||||
if config.Db.MaxConn <= 0 {
|
if config.Db.MaxConn <= 0 {
|
||||||
logbuch.Fatal("you must allow at least one database connection")
|
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, "") == "" {
|
if config.Mail.Provider != "" && findString(config.Mail.Provider, emailProviders, "") == "" {
|
||||||
logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider)
|
logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider)
|
||||||
}
|
}
|
||||||
|
7
main.go
7
main.go
@@ -118,9 +118,12 @@ func main() {
|
|||||||
// Connect to database
|
// Connect to database
|
||||||
var err error
|
var err error
|
||||||
db, err = gorm.Open(config.Db.GetDialector(), &gorm.Config{Logger: gormLogger})
|
db, err = gorm.Open(config.Db.GetDialector(), &gorm.Config{Logger: gormLogger})
|
||||||
if config.Db.Dialect == "sqlite3" {
|
if config.Db.IsSQLite() {
|
||||||
db.Raw("PRAGMA foreign_keys = ON;")
|
db.Exec("PRAGMA foreign_keys = ON;")
|
||||||
|
if !utils.IsCleanDB(db) && !utils.HasConstraints(db) {
|
||||||
db.DisableForeignKeyConstraintWhenMigrating = true
|
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() {
|
if config.IsDev() {
|
||||||
|
@@ -9,11 +9,12 @@ import (
|
|||||||
func TestUser_TZ(t *testing.T) {
|
func TestUser_TZ(t *testing.T) {
|
||||||
sut1, sut2 := &User{Location: ""}, &User{Location: "America/Los_Angeles"}
|
sut1, sut2 := &User{Location: ""}, &User{Location: "America/Los_Angeles"}
|
||||||
pst, _ := time.LoadLocation("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, time.Local, sut1.TZ())
|
||||||
assert.Equal(t, pst, sut2.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(offset1*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(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