Bascule sur l'ORM GORM
- On n'utilise plus la pattern CQRS trop lourde pour le système - Un système de models/repository "à la Symfony" est utilisé pour les requêtes
This commit is contained in:
parent
8b8f322630
commit
05dd505d6b
|
@ -4,4 +4,4 @@ OIDC_CLIENT_SECRET=daddycool
|
||||||
OIDC_POST_LOGOUT_REDIRECT_URL=http://localhost:8081/logout/redirect
|
OIDC_POST_LOGOUT_REDIRECT_URL=http://localhost:8081/logout/redirect
|
||||||
HTTP_COOKIE_AUTHENTICATION_KEY=cL87ucJJSGt7XSjRuQe7GDb2qna8ijfQ
|
HTTP_COOKIE_AUTHENTICATION_KEY=cL87ucJJSGt7XSjRuQe7GDb2qna8ijfQ
|
||||||
HTTP_COOKIE_ENCRYPTION_KEY=cL87ucJJSGt7XSjRuQe7GDb2qna8ijfQ
|
HTTP_COOKIE_ENCRYPTION_KEY=cL87ucJJSGt7XSjRuQe7GDb2qna8ijfQ
|
||||||
DATABASE_DSN="host=localhost user=daddy database=daddy password=daddy"
|
DATABASE_DSN="host=localhost user=daddy database=daddy password=daddy port=5432 sslmode=disable"
|
2
Makefile
2
Makefile
|
@ -6,7 +6,7 @@ build-docker:
|
||||||
docker-compose build
|
docker-compose build
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
cd internal && go run github.com/99designs/gqlgen generate
|
go generate ./...
|
||||||
|
|
||||||
build-server:
|
build-server:
|
||||||
CGO_ENABLED=0 go build -v -o ./bin/server ./cmd/server
|
CGO_ENABLED=0 go build -v -o ./bin/server ./cmd/server
|
||||||
|
|
|
@ -29,7 +29,7 @@ Les services suivants devraient être disponibles après démarrage de l'environ
|
||||||
|Service|Type|Accès|Description|
|
|Service|Type|Accès|Description|
|
||||||
|-------|----|-----|-----------|
|
|-------|----|-----|-----------|
|
||||||
|Application React|HTTP (UI)|http://localhost:8080/|Page d'accueil de l'application React (serveur Webpack)|
|
|Application React|HTTP (UI)|http://localhost:8080/|Page d'accueil de l'application React (serveur Webpack)|
|
||||||
|Interface Web GraphQL|HTTP (UI)|http://localhost:8081/api/v1/graphql (GET)|Interface Web de développement de l'API GraphQL (mode debug uniquement, nécessite d'être authentifié)|
|
|Interface Web GraphQL|HTTP (UI)|http://localhost:8081/api/v1/playground|Interface Web de développement de l'API GraphQL (mode debug uniquement, nécessite d'être authentifié)|
|
||||||
|Serveur GraphQL|HTTP (GraphQL)|http://localhost:8081/api/v1/graphql (POST)|Point d'entrée de l'API GraphQL|
|
|Serveur GraphQL|HTTP (GraphQL)|http://localhost:8081/api/v1/graphql (POST)|Point d'entrée de l'API GraphQL|
|
||||||
|Serveur Hydra|HTTP (ReST)|http://localhost:4444|Point d'entrée pour l'API OAuth2 d'[Hydra](https://www.ory.sh/hydra/docs/)|
|
|Serveur Hydra|HTTP (ReST)|http://localhost:4444|Point d'entrée pour l'API OAuth2 d'[Hydra](https://www.ory.sh/hydra/docs/)|
|
||||||
|Serveur Hydra Passwordless|HTTP|http://localhost:3000|Point d'entrée pour la ["Login/Consent App"](https://www.ory.sh/hydra/docs/implementing-consent) [hydra-passwordless](https://forge.cadoles.com/wpetit/hydra-passwordless)|
|
|Serveur Hydra Passwordless|HTTP|http://localhost:3000|Point d'entrée pour la ["Login/Consent App"](https://www.ory.sh/hydra/docs/implementing-consent) [hydra-passwordless](https://forge.cadoles.com/wpetit/hydra-passwordless)|
|
||||||
|
|
|
@ -4,10 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/migration"
|
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/database"
|
|
||||||
|
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
|
||||||
|
@ -84,21 +81,7 @@ func getServiceContainer(ctx context.Context, conf *config.Config) (*service.Con
|
||||||
oidc.WithScopes("email", "openid"),
|
oidc.WithScopes("email", "openid"),
|
||||||
))
|
))
|
||||||
|
|
||||||
ctn.Provide(database.ServiceName, database.ServiceProvider(conf.Database.DSN))
|
ctn.Provide(orm.ServiceName, orm.ServiceProvider("postgres", conf.Database.DSN, conf.Debug))
|
||||||
|
|
||||||
dbpool, err := database.From(ctn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not retrieve database service")
|
|
||||||
}
|
|
||||||
|
|
||||||
versionResolver := database.NewVersionResolver(dbpool)
|
|
||||||
if err := versionResolver.Init(ctx); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not initialize database version resolver")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctn.Provide(migration.ServiceName, migration.ServiceProvider(versionResolver))
|
|
||||||
|
|
||||||
ctn.Provide(cqrs.ServiceName, cqrs.ServiceProvider())
|
|
||||||
|
|
||||||
return ctn, nil
|
return ctn, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/command"
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/query"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
|
||||||
"gitlab.com/wpetit/goweb/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initCommands(ctn *service.Container) error {
|
|
||||||
dispatcher, err := cqrs.From(ctn)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatcher.RegisterCommand(
|
|
||||||
cqrs.MatchCommandRequest(&command.CreateUserCommandRequest{}),
|
|
||||||
cqrs.CommandHandlerFunc(command.HandleCreateUserCommand),
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initQueries(ctn *service.Container) error {
|
|
||||||
dispatcher, err := cqrs.From(ctn)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatcher.RegisterQuery(
|
|
||||||
cqrs.MatchQueryRequest(&query.FindUserQueryRequest{}),
|
|
||||||
cqrs.QueryHandlerFunc(query.HandleFindUserQuery),
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -153,23 +153,6 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init commands and queries
|
|
||||||
if err := initCommands(ctn); err != nil {
|
|
||||||
logger.Fatal(
|
|
||||||
ctx,
|
|
||||||
"could not init commands",
|
|
||||||
logger.E(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := initQueries(ctn); err != nil {
|
|
||||||
logger.Fatal(
|
|
||||||
ctx,
|
|
||||||
"could not init queries",
|
|
||||||
logger.E(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|
||||||
// Define base middlewares
|
// Define base middlewares
|
||||||
|
|
|
@ -3,9 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/database"
|
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/migration"
|
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
||||||
"github.com/jackc/pgx/v4"
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
"gitlab.com/wpetit/goweb/service"
|
"gitlab.com/wpetit/goweb/service"
|
||||||
|
@ -18,11 +18,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyMigration(ctx context.Context, ctn *service.Container) error {
|
func applyMigration(ctx context.Context, ctn *service.Container) error {
|
||||||
migr, err := migration.From(ctn)
|
orm, err := orm.From(ctn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migr := orm.Migration()
|
||||||
|
|
||||||
// Register available migrations
|
// Register available migrations
|
||||||
migr.Register(
|
migr.Register(
|
||||||
m000initialSchema(),
|
m000initialSchema(),
|
||||||
|
@ -74,29 +76,31 @@ func applyMigration(ctx context.Context, ctn *service.Container) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func m000initialSchema() migration.Migration {
|
// nolint: gochecknoglobals
|
||||||
return database.NewMigration(
|
var initialModels = []interface{}{
|
||||||
|
&model.User{},
|
||||||
|
}
|
||||||
|
|
||||||
|
func m000initialSchema() orm.Migration {
|
||||||
|
return orm.NewDBMigration(
|
||||||
"00_initial_schema",
|
"00_initial_schema",
|
||||||
func(ctx context.Context, tx pgx.Tx) error {
|
func(ctx context.Context, tx *gorm.DB) error {
|
||||||
_, err := tx.Exec(ctx, `
|
for _, m := range initialModels {
|
||||||
CREATE TABLE users (
|
if err := tx.AutoMigrate(m).Error; err != nil {
|
||||||
id SERIAL PRIMARY KEY,
|
return errors.WithStack(err)
|
||||||
name TEXT,
|
}
|
||||||
email TEXT NOT NULL,
|
}
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
||||||
connected_at TIMESTAMPTZ,
|
|
||||||
CONSTRAINT unique_email unique(email)
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
},
|
},
|
||||||
func(ctx context.Context, tx pgx.Tx) error {
|
func(ctx context.Context, tx *gorm.DB) error {
|
||||||
_, err := tx.Exec(ctx, `
|
for _, m := range initialModels {
|
||||||
DROP TABLE users;
|
if err := tx.DropTableIfExists(m).Error; err != nil {
|
||||||
`)
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -7,11 +7,13 @@ require (
|
||||||
github.com/99designs/gqlgen v0.11.3
|
github.com/99designs/gqlgen v0.11.3
|
||||||
github.com/caarlos0/env/v6 v6.2.2
|
github.com/caarlos0/env/v6 v6.2.2
|
||||||
github.com/cortesi/modd v0.0.0-20200630120222-8983974e5450 // indirect
|
github.com/cortesi/modd v0.0.0-20200630120222-8983974e5450 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/go-chi/chi v4.1.0+incompatible
|
github.com/go-chi/chi v4.1.0+incompatible
|
||||||
github.com/gorilla/sessions v1.2.0
|
github.com/gorilla/sessions v1.2.0
|
||||||
github.com/gorilla/websocket v1.2.0
|
github.com/gorilla/websocket v1.2.0
|
||||||
github.com/jackc/pgx v3.6.2+incompatible
|
github.com/jackc/pgx v3.6.2+incompatible
|
||||||
github.com/jackc/pgx/v4 v4.7.1
|
github.com/jackc/pgx/v4 v4.7.1
|
||||||
|
github.com/jinzhu/gorm v1.9.14
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
github.com/vektah/gqlparser/v2 v2.0.1
|
github.com/vektah/gqlparser/v2 v2.0.1
|
||||||
|
|
28
go.sum
28
go.sum
|
@ -22,6 +22,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||||
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||||
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
|
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
|
||||||
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
|
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
|
||||||
|
@ -42,6 +43,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||||
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||||
github.com/bmatcuk/doublestar v1.3.0/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
github.com/bmatcuk/doublestar v1.3.0/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||||
github.com/bmatcuk/doublestar v1.3.1 h1:rT8rxDPsavp9G+4ZULzqhhUSaI/OPsTZNG88Z3i0xvY=
|
github.com/bmatcuk/doublestar v1.3.1 h1:rT8rxDPsavp9G+4ZULzqhhUSaI/OPsTZNG88Z3i0xvY=
|
||||||
|
@ -50,6 +52,7 @@ github.com/caarlos0/env/v6 v6.2.2 h1:R0NIFXaB/LhwuGrjnsldzpnVNjFU/U+hTVHt+cq0yDY
|
||||||
github.com/caarlos0/env/v6 v6.2.2/go.mod h1:3LpmfcAYCG6gCiSgDLaFR5Km1FRpPwFvBbRcjHar6Sw=
|
github.com/caarlos0/env/v6 v6.2.2/go.mod h1:3LpmfcAYCG6gCiSgDLaFR5Km1FRpPwFvBbRcjHar6Sw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||||
|
@ -73,12 +76,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
|
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
|
||||||
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
|
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||||
|
@ -90,9 +97,14 @@ github.com/go-chi/chi v4.1.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxm
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
@ -188,6 +200,12 @@ github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
|
||||||
github.com/jackc/puddle v1.1.1 h1:PJAw7H/9hoWC4Kf3J8iNmL1SwA6E8vfsLqBiL+F6CtI=
|
github.com/jackc/puddle v1.1.1 h1:PJAw7H/9hoWC4Kf3J8iNmL1SwA6E8vfsLqBiL+F6CtI=
|
||||||
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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jinzhu/gorm v1.9.14 h1:Kg3ShyTPcM6nzVo148fRrcMO6MNKuqtOUwnzqMgVniM=
|
||||||
|
github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||||
|
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
@ -203,7 +221,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
|
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
|
||||||
|
@ -226,6 +246,8 @@ github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGe
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
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-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
@ -260,6 +282,7 @@ github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
|
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
|
||||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
|
@ -302,6 +325,7 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
|
||||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
@ -309,6 +333,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
@ -333,6 +358,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -347,6 +373,8 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
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=
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
/server.go
|
/server.go
|
||||||
/graph/generated
|
/graph/generated
|
||||||
|
/model/models_gen.go
|
|
@ -1,99 +0,0 @@
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/database"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
createConnectedUserStatement = `
|
|
||||||
INSERT INTO users (email, connected_at) VALUES ($1, now())
|
|
||||||
ON CONFLICT ON CONSTRAINT unique_email
|
|
||||||
DO UPDATE SET connected_at = now();
|
|
||||||
`
|
|
||||||
createUserStatement = `
|
|
||||||
INSERT INTO users (email) VALUES ($1)
|
|
||||||
ON CONFLICT ON CONSTRAINT unique_email
|
|
||||||
DO NOTHING;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
type CreateUserCommandRequest struct {
|
|
||||||
Email string
|
|
||||||
Connected bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleCreateUserCommand(ctx context.Context, cmd cqrs.Command) error {
|
|
||||||
req, ok := cmd.Request().(*CreateUserCommandRequest)
|
|
||||||
if !ok {
|
|
||||||
return errors.WithStack(cqrs.ErrUnexpectedRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctn, err := container.From(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := database.From(ctn)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := pool.Acquire(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer conn.Release()
|
|
||||||
|
|
||||||
if req.Connected {
|
|
||||||
if err := createConnectedUser(ctx, conn, req.Email); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := createUser(ctx, conn, req.Email); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createConnectedUser(ctx context.Context, conn *pgxpool.Conn, email string) error {
|
|
||||||
_, err := conn.Conn().Prepare(
|
|
||||||
ctx, "create_connected_user",
|
|
||||||
createConnectedUserStatement,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := conn.Exec(ctx, "create_connected_user", email); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createUser(ctx context.Context, conn *pgxpool.Conn, email string) error {
|
|
||||||
_, err := conn.Conn().Prepare(
|
|
||||||
ctx, "create_user",
|
|
||||||
createUserStatement,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := conn.Exec(ctx, "create_user", email); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v4"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MigrationFunc func(ctx context.Context, tx pgx.Tx) error
|
|
||||||
|
|
||||||
type Migration struct {
|
|
||||||
version string
|
|
||||||
up MigrationFunc
|
|
||||||
down MigrationFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Migration) Version() string {
|
|
||||||
return m.version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Migration) Up(ctx context.Context) error {
|
|
||||||
pool, err := m.getDatabaseService(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = WithTx(ctx, pool, func(ctx context.Context, tx pgx.Tx) error {
|
|
||||||
return m.up(ctx, tx)
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not apply up migration")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Migration) Down(ctx context.Context) error {
|
|
||||||
pool, err := m.getDatabaseService(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = WithTx(ctx, pool, func(ctx context.Context, tx pgx.Tx) error {
|
|
||||||
return m.down(ctx, tx)
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not apply down migration")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Migration) getDatabaseService(ctx context.Context) (*pgxpool.Pool, error) {
|
|
||||||
ctn, err := container.From(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not retrieve service container")
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := From(ctn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not retrieve database service")
|
|
||||||
}
|
|
||||||
|
|
||||||
return pool, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMigration(version string, up, down MigrationFunc) *Migration {
|
|
||||||
return &Migration{
|
|
||||||
version: version,
|
|
||||||
up: up,
|
|
||||||
down: down,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ServiceProvider(dsn string) service.Provider {
|
|
||||||
pool, err := pgxpool.Connect(context.Background(), dsn)
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Wrap(err, "could not connect to database")
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(ctn *service.Container) (interface{}, error) {
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pool, nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ServiceName service.Name = "database"
|
|
||||||
|
|
||||||
// From retrieves the database pool service in the given container.
|
|
||||||
func From(container *service.Container) (*pgxpool.Pool, error) {
|
|
||||||
service, err := container.Service(ServiceName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "error while retrieving '%s' service", ServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv, ok := service.(*pgxpool.Pool)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("retrieved service is not a valid '%s' service", ServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return srv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must retrieves the database pool service in the given container or panic otherwise.
|
|
||||||
func Must(container *service.Container) *pgxpool.Pool {
|
|
||||||
srv, err := From(container)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return srv
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v4"
|
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func WithTx(ctx context.Context, pool *pgxpool.Pool, fn func(context.Context, pgx.Tx) error) error {
|
|
||||||
tx, err := pool.BeginTx(ctx, pgx.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not begin transaction")
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err := tx.Rollback(ctx); err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
|
||||||
panic(errors.Wrap(err, "could not rollback transaction"))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := fn(ctx, tx); err != nil {
|
|
||||||
err := errors.Wrap(err, "could not apply down migration")
|
|
||||||
|
|
||||||
if rollbackErr := tx.Rollback(ctx); rollbackErr != nil {
|
|
||||||
return errors.Wrap(err, rollbackErr.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(ctx); err != nil {
|
|
||||||
return errors.Wrap(err, "could not commit transaction")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v4"
|
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VersionResolver struct {
|
|
||||||
pool *pgxpool.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VersionResolver) Current(ctx context.Context) (string, error) {
|
|
||||||
var version string
|
|
||||||
|
|
||||||
err := WithTx(ctx, r.pool, func(ctx context.Context, tx pgx.Tx) error {
|
|
||||||
err := tx.QueryRow(ctx, `SELECT version FROM database_schema WHERE is_current = true;`).
|
|
||||||
Scan(&version)
|
|
||||||
|
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "could execute version resolver init transaction")
|
|
||||||
}
|
|
||||||
|
|
||||||
return version, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VersionResolver) Set(ctx context.Context, version string) error {
|
|
||||||
err := WithTx(ctx, r.pool, func(ctx context.Context, tx pgx.Tx) error {
|
|
||||||
if version != "" {
|
|
||||||
_, err := tx.Exec(ctx, `
|
|
||||||
INSERT INTO database_schema (version, is_current, migrated_at)
|
|
||||||
VALUES
|
|
||||||
(
|
|
||||||
$1,
|
|
||||||
true,
|
|
||||||
now()
|
|
||||||
)
|
|
||||||
ON CONFLICT ON CONSTRAINT unique_version
|
|
||||||
DO UPDATE SET migrated_at = now(), is_current = true;
|
|
||||||
`, version)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := tx.Exec(ctx, `
|
|
||||||
UPDATE database_schema SET is_current = false, migrated_at = null WHERE version <> $1;
|
|
||||||
`, version)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not update schema version")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VersionResolver) Init(ctx context.Context) error {
|
|
||||||
err := WithTx(ctx, r.pool, func(ctx context.Context, tx pgx.Tx) error {
|
|
||||||
_, err := tx.Exec(ctx, `
|
|
||||||
CREATE TABLE IF NOT EXISTS database_schema(
|
|
||||||
version TEXT NOT NULL,
|
|
||||||
migrated_at TIME,
|
|
||||||
is_current BOOLEAN,
|
|
||||||
CONSTRAINT unique_version UNIQUE(version)
|
|
||||||
);`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could execute version resolver init transaction")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVersionResolver(pool *pgxpool.Pool) *VersionResolver {
|
|
||||||
return &VersionResolver{
|
|
||||||
pool: pool,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
|
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
|
||||||
schema:
|
schema:
|
||||||
- graph/*.graphqls
|
- graph/*.graphql
|
||||||
|
|
||||||
# Where should the generated server code go?
|
# Where should the generated server code go?
|
||||||
exec:
|
exec:
|
||||||
|
@ -14,7 +14,7 @@ exec:
|
||||||
|
|
||||||
# Where should any generated models go?
|
# Where should any generated models go?
|
||||||
model:
|
model:
|
||||||
filename: graph/model/models_gen.go
|
filename: model/models_gen.go
|
||||||
package: model
|
package: model
|
||||||
|
|
||||||
# Where should the resolver implementations go?
|
# Where should the resolver implementations go?
|
||||||
|
@ -35,7 +35,7 @@ resolver:
|
||||||
# gqlgen will search for any type names in the schema in these go packages
|
# gqlgen will search for any type names in the schema in these go packages
|
||||||
# if they match it will use them, otherwise it will generate them.
|
# if they match it will use them, otherwise it will generate them.
|
||||||
autobind:
|
autobind:
|
||||||
- "forge.cadoles.com/Cadoles/daddy/internal/graph/model"
|
- "forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
|
|
||||||
# This section declares type mapping between the GraphQL and go type systems
|
# This section declares type mapping between the GraphQL and go type systems
|
||||||
#
|
#
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDB(ctx context.Context) (*gorm.DB, error) {
|
||||||
|
ctn, err := container.From(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
orm, err := orm.From(ctn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return orm.DB(), nil
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Name *string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
ConnectedAt time.Time `json:"connectedAt"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
}
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
input ProfileChanges {
|
||||||
|
name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
updateProfile(changes: ProfileChanges!): User!
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package graph
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/daddy/internal/graph/generated"
|
||||||
|
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *mutationResolver) UpdateProfile(ctx context.Context, changes model.ProfileChanges) (*model.User, error) {
|
||||||
|
return handleUpdateUserProfile(ctx, changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns generated.MutationResolver implementation.
|
||||||
|
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
|
||||||
|
|
||||||
|
type mutationResolver struct{ *Resolver }
|
|
@ -1,7 +1,3 @@
|
||||||
# GraphQL schema example
|
|
||||||
#
|
|
||||||
# https://gqlgen.com/getting-started/
|
|
||||||
|
|
||||||
scalar Time
|
scalar Time
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
|
@ -13,4 +9,4 @@ type User {
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
userProfile: User
|
userProfile: User
|
||||||
}
|
}
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/graph/generated"
|
"forge.cadoles.com/Cadoles/daddy/internal/graph/generated"
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/graph/model"
|
model1 "forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *queryResolver) UserProfile(ctx context.Context) (*model.User, error) {
|
func (r *queryResolver) UserProfile(ctx context.Context) (*model1.User, error) {
|
||||||
return handleUserProfile(ctx)
|
return handleUserProfile(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,6 @@ package graph
|
||||||
//
|
//
|
||||||
// It serves as dependency injection for your app, add any dependencies you require here.
|
// It serves as dependency injection for your app, add any dependencies you require here.
|
||||||
|
|
||||||
|
//go:generate go run github.com/99designs/gqlgen
|
||||||
|
|
||||||
type Resolver struct{}
|
type Resolver struct{}
|
||||||
|
|
|
@ -3,43 +3,33 @@ package graph
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/graph/model"
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/query"
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/session"
|
"forge.cadoles.com/Cadoles/daddy/internal/session"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleUserProfile(ctx context.Context) (*model.User, error) {
|
func handleUserProfile(ctx context.Context) (*model.User, error) {
|
||||||
|
db, err := getDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
userEmail, err := session.UserEmail(ctx)
|
userEmail, err := session.UserEmail(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctn, err := container.From(ctx)
|
repo := model.NewUserRepository(db)
|
||||||
|
|
||||||
|
user, err := repo.FindUserByEmail(ctx, userEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatcher, err := cqrs.From(ctn)
|
return user, nil
|
||||||
if err != nil {
|
}
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
func handleUpdateUserProfile(ctx context.Context, changes model.ProfileChanges) (*model.User, error) {
|
||||||
|
return nil, nil
|
||||||
qry := &query.FindUserQueryRequest{
|
|
||||||
Email: userEmail,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := dispatcher.Query(ctx, qry)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
findUserData, ok := result.Data().(*query.FindUserData)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.WithStack(cqrs.ErrUnexpectedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
return findUserData.User, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package migration
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type Migration interface {
|
|
||||||
Version() string
|
|
||||||
Up(context.Context) error
|
|
||||||
Down(context.Context) error
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package migration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gitlab.com/wpetit/goweb/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ServiceProvider(resolver VersionResolver) service.Provider {
|
|
||||||
manager := NewManager(resolver)
|
|
||||||
|
|
||||||
return func(ctn *service.Container) (interface{}, error) {
|
|
||||||
return manager, nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package migration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ServiceName service.Name = "migration"
|
|
||||||
|
|
||||||
// From retrieves the migration service in the given container.
|
|
||||||
func From(container *service.Container) (*Manager, error) {
|
|
||||||
service, err := container.Service(ServiceName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "error while retrieving '%s' service", ServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv, ok := service.(*Manager)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("retrieved service is not a valid '%s' service", ServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return srv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must retrieves the migration service in the given container or panic otherwise.
|
|
||||||
func Must(container *service.Container) *Manager {
|
|
||||||
srv, err := From(container)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return srv
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package migration
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type VersionResolver interface {
|
|
||||||
Current(context.Context) (string, error)
|
|
||||||
Set(context.Context, string) error
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID *uint `gorm:"primary_key"`
|
||||||
|
Name *string `json:"name"`
|
||||||
|
Email string `json:"email" gorm:"unique;not null"`
|
||||||
|
ConnectedAt time.Time `json:"connectedAt"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileChanges struct {
|
||||||
|
Name *string `json:"name"`
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) CreateOrConnectUser(ctx context.Context, email string) (*User, error) {
|
||||||
|
user := &User{
|
||||||
|
Email: email,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := orm.WithTx(ctx, r.db, func(ctx context.Context, tx *gorm.DB) error {
|
||||||
|
err := tx.Where("email = ?", email).FirstOrCreate(user).Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Model(user).UpdateColumn("connected_at", time.Now()).Error; err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not create user")
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) FindUserByEmail(ctx context.Context, email string) (*User, error) {
|
||||||
|
user := &User{
|
||||||
|
Email: email,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.db.First(user, "email = ?", email).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not find user")
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserRepository(db *gorm.DB) *UserRepository {
|
||||||
|
return &UserRepository{db}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MigrationFunc func(ctx context.Context, tx *gorm.DB) error
|
||||||
|
|
||||||
|
type Migration interface {
|
||||||
|
Version() string
|
||||||
|
Up(context.Context) error
|
||||||
|
Down(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBMigration struct {
|
||||||
|
version string
|
||||||
|
up MigrationFunc
|
||||||
|
down MigrationFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DBMigration) Version() string {
|
||||||
|
return m.version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DBMigration) Up(ctx context.Context) error {
|
||||||
|
db, err := m.getDatabase(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = WithTx(ctx, db, func(ctx context.Context, tx *gorm.DB) error {
|
||||||
|
return m.up(ctx, tx)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not apply up migration")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DBMigration) Down(ctx context.Context) error {
|
||||||
|
db, err := m.getDatabase(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = WithTx(ctx, db, func(ctx context.Context, tx *gorm.DB) error {
|
||||||
|
return m.down(ctx, tx)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not apply down migration")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DBMigration) getDatabase(ctx context.Context) (*gorm.DB, error) {
|
||||||
|
ctn, err := container.From(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not retrieve service container")
|
||||||
|
}
|
||||||
|
|
||||||
|
orm, err := From(ctn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not retrieve orm service")
|
||||||
|
}
|
||||||
|
|
||||||
|
return orm.DB(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDBMigration(version string, up, down MigrationFunc) *DBMigration {
|
||||||
|
return &DBMigration{
|
||||||
|
version: version,
|
||||||
|
up: up,
|
||||||
|
down: down,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package migration
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -11,12 +11,12 @@ var (
|
||||||
ErrMigrationNotFound = errors.New("migration not found")
|
ErrMigrationNotFound = errors.New("migration not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type MigrationManager struct {
|
||||||
migrations []Migration
|
migrations []Migration
|
||||||
resolver VersionResolver
|
resolver VersionResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Up(ctx context.Context) error {
|
func (m *MigrationManager) Up(ctx context.Context) error {
|
||||||
currentVersion, err := m.resolver.Current(ctx)
|
currentVersion, err := m.resolver.Current(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not retrieve current version")
|
return errors.Wrap(err, "could not retrieve current version")
|
||||||
|
@ -58,7 +58,7 @@ func (m *Manager) Up(ctx context.Context) error {
|
||||||
return errors.WithStack(ErrMigrationNotFound)
|
return errors.WithStack(ErrMigrationNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Down(ctx context.Context) error {
|
func (m *MigrationManager) Down(ctx context.Context) error {
|
||||||
currentVersion, err := m.resolver.Current(ctx)
|
currentVersion, err := m.resolver.Current(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not retrieve current version")
|
return errors.Wrap(err, "could not retrieve current version")
|
||||||
|
@ -91,7 +91,7 @@ func (m *Manager) Down(ctx context.Context) error {
|
||||||
return errors.WithStack(ErrMigrationNotFound)
|
return errors.WithStack(ErrMigrationNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Latest(ctx context.Context) error {
|
func (m *MigrationManager) Latest(ctx context.Context) error {
|
||||||
for {
|
for {
|
||||||
isLatest, err := m.IsLatest(ctx)
|
isLatest, err := m.IsLatest(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -108,15 +108,15 @@ func (m *Manager) Latest(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Register(migrations ...Migration) {
|
func (m *MigrationManager) Register(migrations ...Migration) {
|
||||||
m.migrations = migrations
|
m.migrations = migrations
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) CurrentVersion(ctx context.Context) (string, error) {
|
func (m *MigrationManager) CurrentVersion(ctx context.Context) (string, error) {
|
||||||
return m.resolver.Current(ctx)
|
return m.resolver.Current(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) LatestVersion() (string, error) {
|
func (m *MigrationManager) LatestVersion() (string, error) {
|
||||||
if len(m.migrations) == 0 {
|
if len(m.migrations) == 0 {
|
||||||
return "", errors.WithStack(ErrNoAvailableMigration)
|
return "", errors.WithStack(ErrNoAvailableMigration)
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ func (m *Manager) LatestVersion() (string, error) {
|
||||||
return m.migrations[len(m.migrations)-1].Version(), nil
|
return m.migrations[len(m.migrations)-1].Version(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) IsLatest(ctx context.Context) (bool, error) {
|
func (m *MigrationManager) IsLatest(ctx context.Context) (bool, error) {
|
||||||
currentVersion, err := m.resolver.Current(ctx)
|
currentVersion, err := m.resolver.Current(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "could not retrieve current version")
|
return false, errors.Wrap(err, "could not retrieve current version")
|
||||||
|
@ -138,8 +138,8 @@ func (m *Manager) IsLatest(ctx context.Context) (bool, error) {
|
||||||
return currentVersion == latestVersion, nil
|
return currentVersion == latestVersion, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(resolver VersionResolver) *Manager {
|
func NewMigrationManager(resolver VersionResolver) *MigrationManager {
|
||||||
return &Manager{
|
return &MigrationManager{
|
||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
migrations: make([]Migration, 0),
|
migrations: make([]Migration, 0),
|
||||||
}
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/service"
|
||||||
|
|
||||||
|
// Import postgres dialect
|
||||||
|
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ServiceProvider(dialect, dsn string, debug bool) service.Provider {
|
||||||
|
db, err := gorm.Open(dialect, dsn)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "could not connect to database")
|
||||||
|
}
|
||||||
|
|
||||||
|
var srv *Service
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
db = db.LogMode(debug)
|
||||||
|
|
||||||
|
versionResolver := NewDBVersionResolver(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
err := versionResolver.Init(ctx)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "could not initialize version resolver")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
srv = &Service{
|
||||||
|
db: db,
|
||||||
|
migration: NewMigrationManager(versionResolver),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctn *service.Container) (interface{}, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ServiceName service.Name = "orm"
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
db *gorm.DB
|
||||||
|
migration *MigrationManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DB() *gorm.DB {
|
||||||
|
return s.db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Migration() *MigrationManager {
|
||||||
|
return s.migration
|
||||||
|
}
|
||||||
|
|
||||||
|
// From retrieves the orm service in the given container.
|
||||||
|
func From(container *service.Container) (*Service, error) {
|
||||||
|
service, err := container.Service(ServiceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error while retrieving '%s' service", ServiceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv, ok := service.(*Service)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("retrieved service is not a valid '%s' service", ServiceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must retrieves the orm pool service in the given container or panic otherwise.
|
||||||
|
func Must(container *service.Container) *Service {
|
||||||
|
srv, err := From(container)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithTx(ctx context.Context, db *gorm.DB, fn func(context.Context, *gorm.DB) error) error {
|
||||||
|
tx := db.BeginTx(ctx, &sql.TxOptions{})
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := tx.Rollback().Error; err != nil && !isGormError(err, gorm.ErrInvalidTransaction) {
|
||||||
|
panic(errors.Wrap(err, "could not rollback transaction"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := fn(ctx, tx); err != nil {
|
||||||
|
err := errors.Wrap(err, "could not apply down migration")
|
||||||
|
|
||||||
|
if rollbackErr := tx.Rollback().Error; rollbackErr != nil {
|
||||||
|
return errors.Wrap(err, rollbackErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
return errors.Wrap(err, "could not commit transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGormError(err error, compErr error) bool {
|
||||||
|
if errs, ok := err.(gorm.Errors); ok {
|
||||||
|
for _, err := range errs {
|
||||||
|
if errors.Is(err, compErr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Is(err, compErr)
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VersionResolver interface {
|
||||||
|
Current(context.Context) (string, error)
|
||||||
|
Set(context.Context, string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBVersionResolver struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseVersion struct {
|
||||||
|
ID uint `gorm:"primary_key"`
|
||||||
|
Version string `gorm:"unique; not null"`
|
||||||
|
MigratedAt time.Time
|
||||||
|
IsCurrent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DBVersionResolver) Current(ctx context.Context) (string, error) {
|
||||||
|
var version string
|
||||||
|
|
||||||
|
err := WithTx(ctx, r.db, func(ctx context.Context, tx *gorm.DB) error {
|
||||||
|
dbVersion := &DatabaseVersion{}
|
||||||
|
err := tx.Where("is_current = ?", true).First(dbVersion).Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
version = dbVersion.Version
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "could execute version resolver init transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DBVersionResolver) Set(ctx context.Context, version string) error {
|
||||||
|
err := WithTx(ctx, r.db, func(ctx context.Context, tx *gorm.DB) error {
|
||||||
|
dbVersion := &DatabaseVersion{
|
||||||
|
Version: version,
|
||||||
|
MigratedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if version != "" {
|
||||||
|
if err := tx.FirstOrCreate(dbVersion).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tx.Model(dbVersion).
|
||||||
|
UpdateColumn("is_current", true).Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tx.Model(&DatabaseVersion{}).
|
||||||
|
Where("version <> ?", version).
|
||||||
|
UpdateColumn("is_current", false).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not update schema version")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DBVersionResolver) Init(ctx context.Context) error {
|
||||||
|
err := WithTx(ctx, r.db, func(ctx context.Context, tx *gorm.DB) error {
|
||||||
|
if err := tx.AutoMigrate(&DatabaseVersion{}).Error; err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if err := tx.Model(&DatabaseVersion{}).AddUniqueIndex("idx_unique_version", "version").Error; err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could execute version resolver init transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDBVersionResolver(db *gorm.DB) *DBVersionResolver {
|
||||||
|
return &DBVersionResolver{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,71 +0,0 @@
|
||||||
package query
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/graph/model"
|
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/database"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
findUserStatement = `SELECT email, connected_at, created_at FROM users WHERE email = $1`
|
|
||||||
)
|
|
||||||
|
|
||||||
type FindUserQueryRequest struct {
|
|
||||||
Email string
|
|
||||||
}
|
|
||||||
|
|
||||||
type FindUserData struct {
|
|
||||||
User *model.User
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleFindUserQuery(ctx context.Context, qry cqrs.Query) (interface{}, error) {
|
|
||||||
req, ok := qry.Request().(*FindUserQueryRequest)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.WithStack(cqrs.ErrUnexpectedRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctn, err := container.From(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := database.From(ctn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := pool.Acquire(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer conn.Release()
|
|
||||||
|
|
||||||
_, err = conn.Conn().Prepare(
|
|
||||||
ctx, "find_user",
|
|
||||||
findUserStatement,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &model.User{}
|
|
||||||
|
|
||||||
err = conn.QueryRow(ctx, "find_user", req.Email).
|
|
||||||
Scan(&user.Email, &user.ConnectedAt, &user.CreatedAt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data := &FindUserData{
|
|
||||||
User: user,
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
|
@ -3,8 +3,8 @@ package route
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/command"
|
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/session"
|
"forge.cadoles.com/Cadoles/daddy/internal/session"
|
||||||
|
|
||||||
|
@ -62,15 +62,11 @@ func handleLoginCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatcher := cqrs.Must(ctn)
|
db := orm.Must(ctn).DB()
|
||||||
|
repo := model.NewUserRepository(db)
|
||||||
|
|
||||||
cmd := &command.CreateUserCommandRequest{
|
if _, err := repo.CreateOrConnectUser(ctx, claims.Email); err != nil {
|
||||||
Email: claims.Email,
|
panic(errors.Wrap(err, "could not upsert user"))
|
||||||
Connected: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := dispatcher.Exec(ctx, cmd); err != nil {
|
|
||||||
panic(errors.WithStack(err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := session.SaveUserEmail(w, r, claims.Email); err != nil {
|
if err := session.SaveUserEmail(w, r, claims.Email); err != nil {
|
||||||
|
|
Loading…
Reference in New Issue