1
0
mirror of https://github.com/muety/wakapi.git synced 2023-08-10 21:12:56 +03:00

Compare commits

...

18 Commits

Author SHA1 Message Date
7dd0967451 chore: drop debian based docker image and use alpine by default 2021-12-15 15:37:14 +01:00
d6aa2c4405 chore: bump version 2021-12-15 15:16:31 +01:00
821ae94c1e fix: auto increment in bigint migration 2021-12-15 13:17:07 +01:00
adcd7b35ae fix: adapt tests 2021-12-15 12:52:24 +01:00
b0bd26f0ec chore: upgrade dependencies (fix #280) 2021-12-15 12:51:44 +01:00
259f711f2d fix: migrate id column type to bigint (resolve #281) 2021-12-15 10:50:16 +01:00
1c0477f861 Merge pull request #279 from muety/docker
fix: anticipated docker push issue
2021-12-14 15:31:06 +01:00
28a3418ad5 fix: limit sqlite connection pool to one 2021-12-14 02:17:59 +01:00
c5db2c235f chore: enable foreign key constraints for new sqlite databases 2021-12-14 00:47:04 +01:00
9cbddaeedf fix: anticipated docker push issue 2021-12-02 23:06:19 +11:00
485dfe2888 fix: user time zone test (fix #275) [ci skip] 2021-11-28 12:40:46 +01:00
Kid
78a26dbf3c Another typo fix 2021-11-26 22:55:43 +11:00
b2c72c6420 Merge pull request #272 from kidonng/patch-1 [ci skip]
Fix a typo
2021-11-25 15:31:05 +01:00
Kid
6852494d36 Fix a typo 2021-11-25 22:28:20 +08:00
305166ce68 Remove Table of Contents from README [ci skip]
Github has menu, and the links don't seem to work on Docker Hub
2021-11-25 20:31:41 +11:00
400f25c23e fix: remove dead client-side proxy link in README [ci skip]
This link is present in Client Setup section
2021-11-25 20:28:43 +11:00
3aacd3461d Merge pull request #265 from muety/ghcr
ci: add ghcr for Docker deploy, change cache
2021-10-21 13:42:29 +02:00
7e2460e1f0 ci: add ghcr for Docker deploy, change cache 2021-10-21 20:54:13 +11:00
26 changed files with 910 additions and 944 deletions

View File

@ -20,39 +20,31 @@ 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/build-push-action@v2 uses: docker/login-action@v1
with: with:
push: true registry: ghcr.io
tags: | username: ${{ github.actor }}
n1try/wakapi:${{ env.GIT_TAG }} password: ${{ secrets.GITHUB_TOKEN }}
n1try/wakapi:latest
platforms: linux/amd64,linux/arm64
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
- name: Build and push to Docker Hub (Alpine) - name: Build and push
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
file: Dockerfile.alpine file: Dockerfile
push: true push: true
tags: | tags: |
n1try/wakapi:${{ env.GIT_TAG }}-alpine n1try/wakapi:latest
n1try/wakapi:latest-alpine n1try/wakapi:alpine
n1try/wakapi:${{ env.GIT_TAG }}
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:alpine
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-alpine
cache-to: type=local,dest=/tmp/.buildx-cache cache-to: type=registry,ref=n1try/wakapi:buildcache-alpine,mode=max

View File

@ -1,12 +1,15 @@
# Build Stage # Build Stage
FROM golang:1.16 AS build-env FROM golang:1.16-alpine AS build-env
WORKDIR /src WORKDIR /src
# Required for go-sqlite3
RUN apk add gcc musl-dev
ADD ./go.mod . ADD ./go.mod .
RUN go mod download RUN go mod download
RUN curl "https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh" -o wait-for-it.sh && \ RUN wget "https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh" -O wait-for-it.sh && \
chmod +x wait-for-it.sh chmod +x wait-for-it.sh
ADD . . ADD . .
@ -25,12 +28,10 @@ RUN cp /src/wakapi . && \
# to override config values using `-e` syntax. # to override config values using `-e` syntax.
# Available options can be found in [README.md#-configuration](README.md#-configuration) # Available options can be found in [README.md#-configuration](README.md#-configuration)
FROM debian FROM alpine:3
WORKDIR /app WORKDIR /app
RUN apt update && \ RUN apk update && apk add bash ca-certificates tzdata && rm -rf /var/cache/apk
apt install -y ca-certificates && \
rm -rf /var/lib/apt/lists/*
# See README.md and config.default.yml for all config options # See README.md and config.default.yml for all config options
ENV ENVIRONMENT prod ENV ENVIRONMENT prod
@ -48,4 +49,4 @@ COPY --from=build-env /app .
VOLUME /data VOLUME /data
ENTRYPOINT ./entrypoint.sh ENTRYPOINT /app/entrypoint.sh

View File

@ -1,52 +0,0 @@
# Build Stage
FROM golang:1.16-alpine AS build-env
WORKDIR /src
# Required for go-sqlite3
RUN apk add gcc musl-dev
ADD ./go.mod .
RUN go mod download
RUN wget "https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh" -O wait-for-it.sh && \
chmod +x wait-for-it.sh
ADD . .
RUN go build -o wakapi
WORKDIR /app
RUN cp /src/wakapi . && \
cp /src/config.default.yml config.yml && \
sed -i 's/listen_ipv6: ::1/listen_ipv6: /g' config.yml && \
cp /src/wait-for-it.sh . && \
cp /src/entrypoint.sh .
# Run Stage
# When running the application using `docker run`, you can pass environment variables
# to override config values using `-e` syntax.
# Available options can be found in [README.md#-configuration](README.md#-configuration)
FROM alpine:3
WORKDIR /app
RUN apk update && apk add bash ca-certificates tzdata && rm -rf /var/cache/apk
# See README.md and config.default.yml for all config options
ENV ENVIRONMENT prod
ENV WAKAPI_DB_TYPE sqlite3
ENV WAKAPI_DB_USER ''
ENV WAKAPI_DB_PASSWORD ''
ENV WAKAPI_DB_HOST ''
ENV WAKAPI_DB_NAME=/data/wakapi.db
ENV WAKAPI_PASSWORD_SALT ''
ENV WAKAPI_LISTEN_IPV4 '0.0.0.0'
ENV WAKAPI_INSECURE_COOKIES 'true'
ENV WAKAPI_ALLOW_SIGNUP 'true'
COPY --from=build-env /app .
VOLUME /data
ENTRYPOINT /app/entrypoint.sh

View File

@ -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>

View File

@ -239,6 +239,18 @@ 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 *dbConfig) IsMySQL() bool {
return c.Dialect == "mysql"
}
func (c *dbConfig) IsPostgres() bool {
return c.Dialect == "postgres"
}
func (c *serverConfig) GetPublicUrl() string { func (c *serverConfig) GetPublicUrl() string {
return strings.TrimSuffix(c.PublicUrl, "/") return strings.TrimSuffix(c.PublicUrl, "/")
} }
@ -285,6 +297,12 @@ func resolveDbDialect(dbType string) string {
if dbType == "cockroach" { if dbType == "cockroach" {
return "postgres" return "postgres"
} }
if dbType == "sqlite" {
return "sqlite3"
}
if dbType == "mariadb" {
return "mysql"
}
return dbType return dbType
} }
@ -365,6 +383,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)
} }

File diff suppressed because it is too large Load Diff

19
go.mod
View File

@ -4,20 +4,23 @@ go 1.16
require ( require (
github.com/BurntSushi/toml v0.4.1 // indirect github.com/BurntSushi/toml v0.4.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac
github.com/emersion/go-smtp v0.15.0 github.com/emersion/go-smtp v0.15.0
github.com/emvi/logbuch v1.2.0 github.com/emvi/logbuch v1.2.0
github.com/felixge/httpsnoop v1.0.2 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/getsentry/sentry-go v0.11.0 github.com/getsentry/sentry-go v0.11.0
github.com/go-co-op/gocron v1.6.2 github.com/go-co-op/gocron v1.11.0
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-openapi/spec v0.20.2 // indirect github.com/go-openapi/spec v0.20.2 // indirect
github.com/gorilla/handlers v1.5.1 github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0 github.com/gorilla/schema v1.2.0
github.com/gorilla/securecookie v1.1.1 github.com/gorilla/securecookie v1.1.1
github.com/jackc/pgx/v4 v4.13.0 // indirect github.com/jackc/pgx/v4 v4.14.1 // indirect
github.com/jinzhu/configor v1.2.1 github.com/jinzhu/configor v1.2.1
github.com/jinzhu/now v1.1.4 // indirect
github.com/leandro-lugaresi/hub v1.1.1 github.com/leandro-lugaresi/hub v1.1.1
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
@ -27,11 +30,11 @@ require (
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/swaggo/swag v1.7.0 github.com/swaggo/swag v1.7.0
go.uber.org/atomic v1.9.0 go.uber.org/atomic v1.9.0
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/tools v0.1.0 // indirect golang.org/x/tools v0.1.0 // indirect
gorm.io/driver/mysql v1.1.1 gorm.io/driver/mysql v1.2.1
gorm.io/driver/postgres v1.1.0 gorm.io/driver/postgres v1.2.3
gorm.io/driver/sqlite v1.1.4 gorm.io/driver/sqlite v1.2.6
gorm.io/gorm v1.21.12 gorm.io/gorm v1.22.4
) )

36
go.sum
View File

@ -79,6 +79,8 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac h1:tn/OQ2PmwQ0XFVgAHfjlLyqMewry25Rz7jWnVoh4Ggs=
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8= github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8=
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/emvi/logbuch v1.2.0 h1:Bw0jQH1Dbs+oIygZBNx/2Ub1igXRFtKQrIMRrZdVFJM= github.com/emvi/logbuch v1.2.0 h1:Bw0jQH1Dbs+oIygZBNx/2Ub1igXRFtKQrIMRrZdVFJM=
@ -105,6 +107,8 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-co-op/gocron v1.6.2 h1:x5g1tWnWcXIZesdosJJcbziRi4XG6tKB92yKLUpoBkU= github.com/go-co-op/gocron v1.6.2 h1:x5g1tWnWcXIZesdosJJcbziRi4XG6tKB92yKLUpoBkU=
github.com/go-co-op/gocron v1.6.2/go.mod h1:DbJm9kdgr1sEvWpHCA7dFFs/PGHPMil9/97EXCRPr4k= github.com/go-co-op/gocron v1.6.2/go.mod h1:DbJm9kdgr1sEvWpHCA7dFFs/PGHPMil9/97EXCRPr4k=
github.com/go-co-op/gocron v1.11.0 h1:ujOMubCpGcTxnnR/9vJIPIEpgwuAjbueAYqJRNr+nHg=
github.com/go-co-op/gocron v1.11.0/go.mod h1:qtlsoMpHlSdIZ3E/xuZzrrAbeX3u5JtPvWf2TcdutU0=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -227,6 +231,8 @@ github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU= github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8=
github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
@ -245,6 +251,8 @@ github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
@ -258,6 +266,9 @@ github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs= github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0=
github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
@ -268,11 +279,15 @@ github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CI
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570= github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8=
github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU=
github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko= github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc= github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@ -280,6 +295,9 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@ -342,6 +360,7 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
@ -539,6 +558,9 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -574,6 +596,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -611,6 +635,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@ -622,6 +647,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -702,14 +729,23 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.1.1 h1:yr1bpyqiwuSPJ4aGGUX9nu46RHXlF8RASQVb1QQNcvo= gorm.io/driver/mysql v1.1.1 h1:yr1bpyqiwuSPJ4aGGUX9nu46RHXlF8RASQVb1QQNcvo=
gorm.io/driver/mysql v1.1.1/go.mod h1:KdrTanmfLPPyAOeYGyG+UpDys7/7eeWT1zCq+oekYnU= gorm.io/driver/mysql v1.1.1/go.mod h1:KdrTanmfLPPyAOeYGyG+UpDys7/7eeWT1zCq+oekYnU=
gorm.io/driver/mysql v1.2.1 h1:h+3f1l9Ng2C072Y2tIiLgPpWN78r1KXL7bHJ0nTjlhU=
gorm.io/driver/mysql v1.2.1/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
gorm.io/driver/postgres v1.1.0 h1:afBljg7PtJ5lA6YUWluV2+xovIPhS+YiInuL3kUjrbk= gorm.io/driver/postgres v1.1.0 h1:afBljg7PtJ5lA6YUWluV2+xovIPhS+YiInuL3kUjrbk=
gorm.io/driver/postgres v1.1.0/go.mod h1:hXQIwafeRjJvUm+OMxcFWyswJ/vevcpPLlGocwAwuqw= gorm.io/driver/postgres v1.1.0/go.mod h1:hXQIwafeRjJvUm+OMxcFWyswJ/vevcpPLlGocwAwuqw=
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM= gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=
gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.21.9/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.21.9/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.21.12 h1:3fQM0Eiz7jcJEhPggHEpoYnsGZqynMzverL77DV40RM= gorm.io/gorm v1.21.12 h1:3fQM0Eiz7jcJEhPggHEpoYnsGZqynMzverL77DV40RM=
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM=
gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -118,9 +118,8 @@ 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;")
db.DisableForeignKeyConstraintWhenMigrating = true
} }
if config.IsDev() { if config.IsDev() {

View File

@ -31,13 +31,7 @@ func init() {
return nil return nil
} }
condition := "key = ?" if hasRun(name, db) {
if cfg.Db.Dialect == config.SQLDialectMysql {
condition = "`key` = ?"
}
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
logbuch.Info("no need to migrate '%s'", name)
return nil return nil
} }
@ -64,13 +58,7 @@ func init() {
} }
} }
if err := db.Create(&models.KeyStringValue{ setHasRun(name, db)
Key: name,
Value: "done",
}).Error; err != nil {
return err
}
return nil return nil
}, },
} }

View File

@ -26,13 +26,7 @@ func init() {
return nil return nil
} }
condition := "key = ?" if hasRun(name, db) {
if cfg.Db.Dialect == config.SQLDialectMysql {
condition = "`key` = ?"
}
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
logbuch.Info("no need to migrate '%s'", name)
return nil return nil
} }
@ -43,13 +37,7 @@ func init() {
} }
} }
if err := db.Create(&models.KeyStringValue{ setHasRun(name, db)
Key: name,
Value: "done",
}).Error; err != nil {
return err
}
return nil return nil
}, },
} }

View File

@ -1,9 +1,7 @@
package migrations package migrations
import ( import (
"github.com/emvi/logbuch"
"github.com/muety/wakapi/config" "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -12,13 +10,7 @@ func init() {
f := migrationFunc{ f := migrationFunc{
name: name, name: name,
f: func(db *gorm.DB, cfg *config.Config) error { f: func(db *gorm.DB, cfg *config.Config) error {
condition := "key = ?" if hasRun(name, db) {
if cfg.Db.Dialect == config.SQLDialectMysql {
condition = "`key` = ?"
}
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
logbuch.Info("no need to migrate '%s'", name)
return nil return nil
} }
@ -26,13 +18,7 @@ func init() {
return err return err
} }
if err := db.Create(&models.KeyStringValue{ setHasRun(name, db)
Key: name,
Value: "done",
}).Error; err != nil {
return err
}
return nil return nil
}, },
} }

View File

@ -1,9 +1,7 @@
package migrations package migrations
import ( import (
"github.com/emvi/logbuch"
"github.com/muety/wakapi/config" "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -12,13 +10,7 @@ func init() {
f := migrationFunc{ f := migrationFunc{
name: name, name: name,
f: func(db *gorm.DB, cfg *config.Config) error { f: func(db *gorm.DB, cfg *config.Config) error {
condition := "key = ?" if hasRun(name, db) {
if cfg.Db.Dialect == config.SQLDialectMysql {
condition = "`key` = ?"
}
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
logbuch.Info("no need to migrate '%s'", name)
return nil return nil
} }
@ -26,13 +18,7 @@ func init() {
return err return err
} }
if err := db.Create(&models.KeyStringValue{ setHasRun(name, db)
Key: name,
Value: "done",
}).Error; err != nil {
return err
}
return nil return nil
}, },
} }

View File

@ -1,7 +1,6 @@
package migrations package migrations
import ( import (
"github.com/emvi/logbuch"
"github.com/muety/wakapi/config" "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models" "github.com/muety/wakapi/models"
"gorm.io/gorm" "gorm.io/gorm"
@ -13,15 +12,14 @@ func init() {
f := migrationFunc{ f := migrationFunc{
name: name, name: name,
f: func(db *gorm.DB, cfg *config.Config) error { f: func(db *gorm.DB, cfg *config.Config) error {
if hasRun(name, db) {
return nil
}
condition := "key = ?" condition := "key = ?"
if cfg.Db.Dialect == config.SQLDialectMysql { if cfg.Db.Dialect == config.SQLDialectMysql {
condition = "`key` = ?" condition = "`key` = ?"
} }
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
logbuch.Info("no need to migrate '%s'", name)
return nil
}
imprintKv := &models.KeyStringValue{Key: "imprint", Value: "no content here"} imprintKv := &models.KeyStringValue{Key: "imprint", Value: "no content here"}
if err := db. if err := db.
@ -32,13 +30,7 @@ func init() {
return err return err
} }
if err := db.Create(&models.KeyStringValue{ setHasRun(name, db)
Key: name,
Value: "done",
}).Error; err != nil {
return err
}
return nil return nil
}, },
} }

View File

@ -13,14 +13,7 @@ func init() {
f := migrationFunc{ f := migrationFunc{
name: name, name: name,
f: func(db *gorm.DB, cfg *config.Config) error { f: func(db *gorm.DB, cfg *config.Config) error {
condition := "key = ?" if hasRun(name, db) {
if cfg.Db.Dialect == config.SQLDialectMysql {
condition = "`key` = ?"
}
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
logbuch.Info("no need to migrate '%s'", name)
return nil return nil
} }
@ -35,13 +28,7 @@ func init() {
} }
logbuch.Info("successfully deleted project label summary items") logbuch.Info("successfully deleted project label summary items")
if err := db.Create(&models.KeyStringValue{ setHasRun(name, db)
Key: name,
Value: "done",
}).Error; err != nil {
return err
}
return nil return nil
}, },
} }

View File

@ -0,0 +1,50 @@
package migrations
import (
"github.com/emvi/logbuch"
"github.com/muety/wakapi/config"
"gorm.io/gorm"
)
func init() {
const name = "20211215-migrate_id_to_bigint-add_has_data_field"
f := migrationFunc{
name: name,
f: func(db *gorm.DB, cfg *config.Config) error {
if hasRun(name, db) {
return nil
}
if cfg.Db.IsMySQL() {
tx := db.Begin()
if err := tx.Exec("ALTER TABLE heartbeats MODIFY COLUMN id BIGINT UNSIGNED AUTO_INCREMENT").Error; err != nil {
return err
}
if err := tx.Exec("ALTER TABLE summary_items MODIFY COLUMN id BIGINT UNSIGNED AUTO_INCREMENT").Error; err != nil {
return err
}
tx.Commit()
} else if cfg.Db.IsPostgres() {
// postgres does not have unsigned data types
// https://www.postgresql.org/docs/10/datatype-numeric.html
tx := db.Begin()
if err := tx.Exec("ALTER TABLE heartbeats ALTER COLUMN id TYPE BIGINT").Error; err != nil {
return err
}
if err := tx.Exec("ALTER TABLE summary_items ALTER COLUMN id TYPE BIGINT").Error; err != nil {
return err
}
tx.Commit()
} else {
// sqlite doesn't allow for changing column type easily
// https://stackoverflow.com/a/2083562/3112139
logbuch.Warn("unable to migrate id columns to bigint on %s", cfg.Db.Dialect)
}
setHasRun(name, db)
return nil
},
}
registerPostMigration(f)
}

30
migrations/shared.go Normal file
View File

@ -0,0 +1,30 @@
package migrations
import (
"github.com/emvi/logbuch"
"github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"gorm.io/gorm"
)
func hasRun(name string, db *gorm.DB) bool {
condition := "key = ?"
if config.Get().Db.Dialect == config.SQLDialectMysql {
condition = "`key` = ?"
}
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
logbuch.Info("no need to migrate '%s'", name)
return true
}
return false
}
func setHasRun(name string, db *gorm.DB) {
if err := db.Create(&models.KeyStringValue{
Key: name,
Value: "done",
}).Error; err != nil {
logbuch.Error("failed to mark migration %s as run - %v", name, err)
}
}

View File

@ -9,7 +9,7 @@ import (
) )
type Heartbeat struct { type Heartbeat struct {
ID uint `gorm:"primary_key" hash:"ignore"` ID uint64 `gorm:"primary_key" hash:"ignore"`
User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" hash:"ignore"` User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" hash:"ignore"`
UserID string `json:"-" gorm:"not null; index:idx_time_user"` UserID string `json:"-" gorm:"not null; index:idx_time_user"`
Entity string `json:"entity" gorm:"not null; index:idx_entity"` Entity string `json:"entity" gorm:"not null; index:idx_entity"`

View File

@ -36,7 +36,7 @@ type Summary struct {
type SummaryItems []*SummaryItem type SummaryItems []*SummaryItem
type SummaryItem struct { type SummaryItem struct {
ID uint `json:"-" gorm:"primary_key"` ID uint64 `json:"-" gorm:"primary_key"`
Summary *Summary `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` Summary *Summary `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
SummaryID uint `json:"-"` SummaryID uint `json:"-"`
Type uint8 `json:"-" gorm:"index:idx_type"` Type uint8 `json:"-" gorm:"index:idx_type"`

View File

@ -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))
} }

View File

@ -126,16 +126,16 @@ func (r *UserRepository) Count() (int64, error) {
} }
func (r *UserRepository) InsertOrGet(user *models.User) (*models.User, bool, error) { func (r *UserRepository) InsertOrGet(user *models.User) (*models.User, bool, error) {
result := r.db.FirstOrCreate(user, &models.User{ID: user.ID}) if u, err := r.GetById(user.ID); err == nil && u != nil && u.ID != "" {
return u, false, nil
}
result := r.db.Create(user)
if err := result.Error; err != nil { if err := result.Error; err != nil {
return nil, false, err return nil, false, err
} }
if result.RowsAffected == 1 { return user, true, nil
return user, true, nil
}
return user, false, nil
} }
func (r *UserRepository) Update(user *models.User) (*models.User, error) { func (r *UserRepository) Update(user *models.User) (*models.User, error) {

View File

@ -50,7 +50,7 @@ func (suite *SummaryServiceTestSuite) SetupSuite() {
suite.TestStartTime = time.Unix(0, MinUnixTime1) suite.TestStartTime = time.Unix(0, MinUnixTime1)
suite.TestHeartbeats = []*models.Heartbeat{ suite.TestHeartbeats = []*models.Heartbeat{
{ {
ID: uint(rand.Uint32()), ID: rand.Uint64(),
UserID: TestUserId, UserID: TestUserId,
Project: TestProject1, Project: TestProject1,
Language: TestLanguageGo, Language: TestLanguageGo,
@ -60,7 +60,7 @@ func (suite *SummaryServiceTestSuite) SetupSuite() {
Time: models.CustomTime(suite.TestStartTime), Time: models.CustomTime(suite.TestStartTime),
}, },
{ {
ID: uint(rand.Uint32()), ID: rand.Uint64(),
UserID: TestUserId, UserID: TestUserId,
Project: TestProject1, Project: TestProject1,
Language: TestLanguageGo, Language: TestLanguageGo,
@ -70,7 +70,7 @@ func (suite *SummaryServiceTestSuite) SetupSuite() {
Time: models.CustomTime(suite.TestStartTime.Add(30 * time.Second)), Time: models.CustomTime(suite.TestStartTime.Add(30 * time.Second)),
}, },
{ {
ID: uint(rand.Uint32()), ID: rand.Uint64(),
UserID: TestUserId, UserID: TestUserId,
Project: TestProject1, Project: TestProject1,
Language: TestLanguageGo, Language: TestLanguageGo,
@ -375,7 +375,7 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Aliased() {
heartbeats := filter(from, to, suite.TestHeartbeats) heartbeats := filter(from, to, suite.TestHeartbeats)
heartbeats = append(heartbeats, &models.Heartbeat{ heartbeats = append(heartbeats, &models.Heartbeat{
ID: uint(rand.Uint32()), ID: rand.Uint64(),
UserID: TestUserId, UserID: TestUserId,
Project: TestProject2, Project: TestProject2,
Language: TestLanguageGo, Language: TestLanguageGo,
@ -414,7 +414,7 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Aliased_ProjectLabels()
heartbeats := filter(from, to, suite.TestHeartbeats) heartbeats := filter(from, to, suite.TestHeartbeats)
heartbeats = append(heartbeats, &models.Heartbeat{ heartbeats = append(heartbeats, &models.Heartbeat{
ID: uint(rand.Uint32()), ID: rand.Uint64(),
UserID: TestUserId, UserID: TestUserId,
Project: TestProject2, Project: TestProject2,
Language: TestLanguageGo, Language: TestLanguageGo,

View File

@ -1,3 +1,15 @@
BEGIN TRANSACTION;
INSERT INTO "key_string_values" VALUES ('20210213-add_has_data_field','done');
INSERT INTO "key_string_values" VALUES ('20210221-add_created_date_column','done');
INSERT INTO "key_string_values" VALUES ('imprint','no content here');
INSERT INTO "key_string_values" VALUES ('20210411-add_imprint_content','done');
INSERT INTO "key_string_values" VALUES ('20210806-remove_persisted_project_labels','done');
INSERT INTO "key_string_values" VALUES ('20211215-migrate_id_to_bigint-add_has_data_field','done');
INSERT INTO "key_string_values" VALUES ('latest_total_time','0s');
INSERT INTO "key_string_values" VALUES ('latest_total_users','0');
COMMIT;
BEGIN TRANSACTION; BEGIN TRANSACTION;
INSERT INTO "users" ("id", "api_key", "email", "location", "password", "created_at", "last_logged_in_at", INSERT INTO "users" ("id", "api_key", "email", "location", "password", "created_at", "last_logged_in_at",
"share_data_max_days", "share_editors", "share_languages", "share_projects", "share_oss", "share_data_max_days", "share_editors", "share_languages", "share_projects", "share_oss",
@ -11,4 +23,4 @@ INSERT INTO "users" ("id", "api_key", "email", "location", "password", "created_
VALUES ('writeuser', 'f7aa255c-8647-4d0b-b90f-621c58fd580f', '', 'Europe/Berlin', VALUES ('writeuser', 'f7aa255c-8647-4d0b-b90f-621c58fd580f', '', 'Europe/Berlin',
'$2a$10$93CAptdjLGRtc1D3xrZJcu8B/YBAPSjCZOHZRId.xpyrsLAeHOoA.', '2021-05-28 12:34:56', '$2a$10$93CAptdjLGRtc1D3xrZJcu8B/YBAPSjCZOHZRId.xpyrsLAeHOoA.', '2021-05-28 12:34:56',
'2021-05-28 14:35:05.118+02:00', 7, 0, 0, 1, 0, 0, 0, 1, '', '', 0); '2021-05-28 14:35:05.118+02:00', 7, 0, 0, 1, 0, 0, 0, 1, '', '', 0);
COMMIT; COMMIT;

View File

@ -1,147 +1,53 @@
-- Created with SQLite DB Browser through:
-- File -> Export -> to SQL file
-- with options:
-- Overwrite, Keep original CREATE
BEGIN TRANSACTION; BEGIN TRANSACTION;
DROP TABLE IF EXISTS "users";
CREATE TABLE IF NOT EXISTS "users" (
"id" text,
"api_key" text UNIQUE,
"email" text,
"password" text,
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP,
"last_logged_in_at" timestamp DEFAULT CURRENT_TIMESTAMP,
"share_data_max_days" integer DEFAULT 0,
"share_editors" numeric DEFAULT false,
"share_languages" numeric DEFAULT false,
"share_projects" numeric DEFAULT false,
"share_oss" numeric DEFAULT false,
"share_machines" numeric DEFAULT false,
"is_admin" numeric DEFAULT false,
"has_data" numeric DEFAULT false,
"wakatime_api_key" text,
"reset_token" text,
"location" text,
"reports_weekly" numeric DEFAULT false,
PRIMARY KEY("id")
);
DROP TABLE IF EXISTS "key_string_values";
CREATE TABLE IF NOT EXISTS "key_string_values" (
"key" text,
"value" text,
PRIMARY KEY("key")
);
DROP TABLE IF EXISTS "summary_items";
CREATE TABLE IF NOT EXISTS "summary_items" (
"id" integer,
"summary_id" integer,
"type" integer,
"key" text,
"total" integer,
CONSTRAINT "fk_summaries_languages" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "fk_summary_items_summary" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "fk_summaries_machines" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "fk_summaries_projects" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "fk_summaries_operating_systems" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "fk_summaries_editors" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY("id")
);
DROP TABLE IF EXISTS "aliases"; DROP TABLE IF EXISTS "aliases";
CREATE TABLE IF NOT EXISTS "aliases" ( CREATE TABLE `aliases` (`id` integer,`type` integer NOT NULL,`user_id` text NOT NULL,`key` text NOT NULL,`value` text NOT NULL,PRIMARY KEY (`id`),CONSTRAINT `fk_aliases_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE);
"id" integer, DROP TABLE IF EXISTS "diagnostics";
"type" integer NOT NULL, CREATE TABLE `diagnostics` (`id` integer,`user_id` text NOT NULL,`platform` text,`architecture` text,`plugin` text,`cli_version` text,`logs` text,`stack_trace` text,PRIMARY KEY (`id`),CONSTRAINT `fk_diagnostics_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE);
"user_id" text NOT NULL,
"key" text NOT NULL,
"value" text NOT NULL,
CONSTRAINT "fk_aliases_user" FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY("id")
);
DROP TABLE IF EXISTS "heartbeats"; DROP TABLE IF EXISTS "heartbeats";
CREATE TABLE IF NOT EXISTS "heartbeats" ( CREATE TABLE `heartbeats` (`id` integer,`user_id` text NOT NULL,`entity` text NOT NULL,`type` text,`category` text,`project` text,`branch` text,`language` text,`is_write` numeric,`editor` text,`operating_system` text,`machine` text,`user_agent` text,`time` timestamp,`hash` varchar(17),`origin` text,`origin_id` text,`created_at` timestamp,PRIMARY KEY (`id`),CONSTRAINT `fk_heartbeats_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE);
"id" integer, DROP TABLE IF EXISTS "key_string_values";
"user_id" text NOT NULL, CREATE TABLE `key_string_values` (`key` text,`value` text,PRIMARY KEY (`key`));
"entity" text NOT NULL,
"type" text,
"category" text,
"project" text,
"branch" text,
"language" text,
"is_write" numeric,
"editor" text,
"operating_system" text,
"machine" text,
"time" timestamp,
"hash" varchar(17),
"origin" text,
"origin_id" text,
"created_at" timestamp,
CONSTRAINT "fk_heartbeats_user" FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY("id")
);
DROP TABLE IF EXISTS "summaries";
CREATE TABLE IF NOT EXISTS "summaries" (
"id" integer,
"user_id" text NOT NULL,
"from_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"to_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "fk_summaries_user" FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY("id")
);
DROP TABLE IF EXISTS "language_mappings"; DROP TABLE IF EXISTS "language_mappings";
CREATE TABLE IF NOT EXISTS "language_mappings" ( CREATE TABLE `language_mappings` (`id` integer,`user_id` text NOT NULL,`extension` varchar(16),`language` varchar(64),PRIMARY KEY (`id`),CONSTRAINT `fk_language_mappings_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE);
"id" integer, DROP TABLE IF EXISTS "project_labels";
"user_id" text NOT NULL, CREATE TABLE `project_labels` (`id` integer,`user_id` text NOT NULL,`project_key` text,`label` varchar(64),PRIMARY KEY (`id`),CONSTRAINT `fk_project_labels_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE);
"extension" varchar(16), DROP TABLE IF EXISTS "summaries";
"language" varchar(64), CREATE TABLE "summaries" (`id` integer,`user_id` text NOT NULL,`from_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,`to_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),CONSTRAINT `fk_summaries_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE);
CONSTRAINT "fk_language_mappings_user" FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE, DROP TABLE IF EXISTS "summary_items";
PRIMARY KEY("id") CREATE TABLE `summary_items` (`id` integer,`summary_id` integer,`type` integer,`key` text,`total` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_summaries_editors` FOREIGN KEY (`summary_id`) REFERENCES `summaries`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT `fk_summaries_operating_systems` FOREIGN KEY (`summary_id`) REFERENCES `summaries`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT `fk_summaries_machines` FOREIGN KEY (`summary_id`) REFERENCES `summaries`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT `fk_summaries_projects` FOREIGN KEY (`summary_id`) REFERENCES `summaries`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT `fk_summaries_languages` FOREIGN KEY (`summary_id`) REFERENCES `summaries`(`id`) ON DELETE CASCADE ON UPDATE CASCADE);
); DROP TABLE IF EXISTS "users";
DROP INDEX IF EXISTS "idx_user_email"; CREATE TABLE `users` (`id` text,`api_key` text UNIQUE,`email` text,`location` text,`password` text,`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,`last_logged_in_at` timestamp DEFAULT CURRENT_TIMESTAMP,`share_data_max_days` integer DEFAULT 0,`share_editors` numeric DEFAULT false,`share_languages` numeric DEFAULT false,`share_projects` numeric DEFAULT false,`share_oss` numeric DEFAULT false,`share_machines` numeric DEFAULT false,`share_labels` numeric DEFAULT false,`is_admin` numeric DEFAULT false,`has_data` numeric DEFAULT false,`wakatime_api_key` text,`reset_token` text,`reports_weekly` numeric DEFAULT false,PRIMARY KEY (`id`));
CREATE INDEX IF NOT EXISTS "idx_user_email" ON "users" (
"email"
);
DROP INDEX IF EXISTS "idx_type";
CREATE INDEX IF NOT EXISTS "idx_type" ON "summary_items" (
"type"
);
DROP INDEX IF EXISTS "idx_alias_type_key"; DROP INDEX IF EXISTS "idx_alias_type_key";
CREATE INDEX IF NOT EXISTS "idx_alias_type_key" ON "aliases" ( CREATE INDEX `idx_alias_type_key` ON `aliases`(`type`,`key`);
"type",
"key"
);
DROP INDEX IF EXISTS "idx_alias_user"; DROP INDEX IF EXISTS "idx_alias_user";
CREATE INDEX IF NOT EXISTS "idx_alias_user" ON "aliases" ( CREATE INDEX `idx_alias_user` ON `aliases`(`user_id`);
"user_id" DROP INDEX IF EXISTS "idx_diagnostics_user";
); CREATE INDEX `idx_diagnostics_user` ON `diagnostics`(`user_id`);
DROP INDEX IF EXISTS "idx_time";
CREATE INDEX IF NOT EXISTS "idx_time" ON "heartbeats" (
"time"
);
DROP INDEX IF EXISTS "idx_heartbeats_hash";
CREATE UNIQUE INDEX IF NOT EXISTS "idx_heartbeats_hash" ON "heartbeats" (
"hash"
);
DROP INDEX IF EXISTS "idx_time_user";
CREATE INDEX IF NOT EXISTS "idx_time_user" ON "heartbeats" (
"user_id"
);
DROP INDEX IF EXISTS "idx_entity"; DROP INDEX IF EXISTS "idx_entity";
CREATE INDEX IF NOT EXISTS "idx_entity" ON "heartbeats" ( CREATE INDEX `idx_entity` ON `heartbeats`(`entity`);
"entity" DROP INDEX IF EXISTS "idx_heartbeats_hash";
); CREATE UNIQUE INDEX `idx_heartbeats_hash` ON `heartbeats`(`hash`);
DROP INDEX IF EXISTS "idx_language"; DROP INDEX IF EXISTS "idx_language";
CREATE INDEX IF NOT EXISTS "idx_language" ON "heartbeats" ( CREATE INDEX `idx_language` ON `heartbeats`(`language`);
"language"
);
DROP INDEX IF EXISTS "idx_time_summary_user";
CREATE INDEX IF NOT EXISTS "idx_time_summary_user" ON "summaries" (
"user_id",
"from_time",
"to_time"
);
DROP INDEX IF EXISTS "idx_language_mapping_composite"; DROP INDEX IF EXISTS "idx_language_mapping_composite";
CREATE UNIQUE INDEX IF NOT EXISTS "idx_language_mapping_composite" ON "language_mappings" ( CREATE UNIQUE INDEX `idx_language_mapping_composite` ON `language_mappings`(`user_id`,`extension`);
"user_id",
"extension"
);
DROP INDEX IF EXISTS "idx_language_mapping_user"; DROP INDEX IF EXISTS "idx_language_mapping_user";
CREATE INDEX IF NOT EXISTS "idx_language_mapping_user" ON "language_mappings" ( CREATE INDEX `idx_language_mapping_user` ON `language_mappings`(`user_id`);
"user_id" DROP INDEX IF EXISTS "idx_project_label_user";
); CREATE INDEX `idx_project_label_user` ON `project_labels`(`user_id`);
DROP INDEX IF EXISTS "idx_time";
CREATE INDEX `idx_time` ON `heartbeats`(`time`);
DROP INDEX IF EXISTS "idx_time_summary_user";
CREATE INDEX `idx_time_summary_user` ON `summaries`(`user_id`,`from_time`,`to_time`);
DROP INDEX IF EXISTS "idx_time_user";
CREATE INDEX `idx_time_user` ON `heartbeats`(`user_id`);
DROP INDEX IF EXISTS "idx_type";
CREATE INDEX `idx_type` ON `summary_items`(`type`);
DROP INDEX IF EXISTS "idx_user_email";
CREATE INDEX `idx_user_email` ON `users`(`email`);
COMMIT; COMMIT;

32
utils/db.go Normal file
View 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
}

View File

@ -1 +1 @@
1.30.1 1.30.3