From 4f0e5ff8d5faf31c4e100a7e638fb23709c622be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Wed, 20 Dec 2023 07:03:03 +0000 Subject: [PATCH] feat: add version repo (#4) Reviewed-on: https://gitea.dwysokinski.me/twhelp/corev3/pulls/4 --- .golangci.yml | 5 + .woodpecker/test.yml | 1 - cmd/twhelp/bun.go | 10 +- go.mod | 46 ++++- go.sum | 186 ++++++++++++++++++ internal/adapter/adapter_test.go | 43 ++++ internal/adapter/adaptertest/postgres.go | 183 +++++++++++++++++ internal/adapter/adaptertest/sqlite.go | 27 +++ internal/adapter/adaptertest/testing_t.go | 9 + internal/adapter/adaptertest/utils.go | 58 ++++++ internal/adapter/internal/bunmodel/version.go | 43 ++++ internal/adapter/repository_bun_version.go | 64 ++++++ .../adapter/repository_bun_version_test.go | 30 +++ internal/adapter/repository_version_test.go | 105 ++++++++++ internal/app/service_version.go | 23 +++ internal/chislog/chislog.go | 1 + internal/domain/error.go | 77 ++++++++ internal/domain/validators.go | 45 +++++ internal/domain/version.go | 109 ++++++++++ internal/domain/version_test.go | 79 ++++++++ internal/health/health.go | 6 +- ...31216063603_create_versions_table.down.sql | 1 - ...231216063731_create_servers_table.down.sql | 1 - ...0231216063825_create_tribes_table.down.sql | 1 - ...231216064247_create_players_table.down.sql | 1 - ...6064325_create_ennoblements_table.down.sql | 1 - ...433_create_player_snapshots_table.down.sql | 1 - ...4559_create_tribe_snapshots_table.down.sql | 1 - ...064617_create_tribe_changes_table.down.sql | 1 - ...> 20231220050527_create_versions_table.go} | 20 +- ...=> 20231220050708_create_servers_table.go} | 20 +- ... => 20231220050904_create_tribes_table.go} | 19 ++ ...=> 20231220051107_create_players_table.go} | 18 ++ .../20231220051221_create_villages_table.go | 42 ++++ ...231220051322_create_ennoblements_table.go} | 25 ++- ...20051411_create_player_snapshots_table.go} | 20 +- ...220052323_create_tribe_snapshots_table.go} | 21 +- ...31220052428_create_tribe_changes_table.go} | 26 ++- ...2526_create_index_ennoblements_hash_key.go | 34 ++++ ...547_create_index_tribe_changes_hash_key.go | 34 ++++ internal/migrations/migrations.go | 11 -- internal/migrations/sql_utils.go | 17 ++ 42 files changed, 1418 insertions(+), 47 deletions(-) create mode 100644 internal/adapter/adapter_test.go create mode 100644 internal/adapter/adaptertest/postgres.go create mode 100644 internal/adapter/adaptertest/sqlite.go create mode 100644 internal/adapter/adaptertest/testing_t.go create mode 100644 internal/adapter/adaptertest/utils.go create mode 100644 internal/adapter/internal/bunmodel/version.go create mode 100644 internal/adapter/repository_bun_version.go create mode 100644 internal/adapter/repository_bun_version_test.go create mode 100644 internal/adapter/repository_version_test.go create mode 100644 internal/app/service_version.go create mode 100644 internal/domain/error.go create mode 100644 internal/domain/validators.go create mode 100644 internal/domain/version.go create mode 100644 internal/domain/version_test.go delete mode 100644 internal/migrations/20231216063603_create_versions_table.down.sql delete mode 100644 internal/migrations/20231216063731_create_servers_table.down.sql delete mode 100644 internal/migrations/20231216063825_create_tribes_table.down.sql delete mode 100644 internal/migrations/20231216064247_create_players_table.down.sql delete mode 100644 internal/migrations/20231216064325_create_ennoblements_table.down.sql delete mode 100644 internal/migrations/20231216064433_create_player_snapshots_table.down.sql delete mode 100644 internal/migrations/20231216064559_create_tribe_snapshots_table.down.sql delete mode 100644 internal/migrations/20231216064617_create_tribe_changes_table.down.sql rename internal/migrations/{20231216063603_create_versions_table.up.sql => 20231220050527_create_versions_table.go} (80%) rename internal/migrations/{20231216063731_create_servers_table.up.sql => 20231220050708_create_servers_table.go} (99%) rename internal/migrations/{20231216063825_create_tribes_table.up.sql => 20231220050904_create_tribes_table.go} (84%) rename internal/migrations/{20231216064247_create_players_table.up.sql => 20231220051107_create_players_table.go} (83%) create mode 100644 internal/migrations/20231220051221_create_villages_table.go rename internal/migrations/{20231216064325_create_ennoblements_table.up.sql => 20231220051322_create_ennoblements_table.go} (74%) rename internal/migrations/{20231216064433_create_player_snapshots_table.up.sql => 20231220051411_create_player_snapshots_table.go} (73%) rename internal/migrations/{20231216064559_create_tribe_snapshots_table.up.sql => 20231220052323_create_tribe_snapshots_table.go} (74%) rename internal/migrations/{20231216064617_create_tribe_changes_table.up.sql => 20231220052428_create_tribe_changes_table.go} (64%) create mode 100644 internal/migrations/20231220052526_create_index_ennoblements_hash_key.go create mode 100644 internal/migrations/20231220052547_create_index_tribe_changes_hash_key.go create mode 100644 internal/migrations/sql_utils.go diff --git a/.golangci.yml b/.golangci.yml index ce47782..9e789fa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -53,6 +53,7 @@ linters: - inamedparam - sloglint - revive + - gomnd linters-settings: gocyclo: @@ -105,6 +106,10 @@ linters-settings: enable-all: true sloglint: attr-only: true + gomnd: + ignored-functions: + - strconv.FormatInt + - strconv.ParseInt revive: rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml index e889e0c..3b28c02 100644 --- a/.woodpecker/test.yml +++ b/.woodpecker/test.yml @@ -20,7 +20,6 @@ steps: image: *go_image pull: true commands: - - go mod download - make generate test: diff --git a/cmd/twhelp/bun.go b/cmd/twhelp/bun.go index 540f7bd..87c3ea2 100644 --- a/cmd/twhelp/bun.go +++ b/cmd/twhelp/bun.go @@ -22,30 +22,30 @@ var ( } dbFlagMaxIdleConns = &cli.IntFlag{ Name: "db.maxIdleConns", - Value: 2, + Value: 2, //nolint:gomnd EnvVars: []string{"DB_MAX_IDLE_CONNS"}, Usage: "https://pkg.go.dev/database/sql#DB.SetMaxIdleConns", } dbFlagMaxOpenConns = &cli.IntFlag{ Name: "db.maxOpenConns", - Value: runtime.NumCPU() * 4, + Value: runtime.NumCPU() * 4, //nolint:gomnd EnvVars: []string{"DB_MAX_OPEN_CONNS"}, Usage: "https://pkg.go.dev/database/sql#DB.SetMaxOpenConns", } dbFlagConnMaxLifetime = &cli.DurationFlag{ Name: "db.connMaxLifetime", - Value: 30 * time.Minute, + Value: 30 * time.Minute, //nolint:gomnd EnvVars: []string{"DB_CONN_MAX_LIFETIME"}, Usage: "https://pkg.go.dev/database/sql#DB.SetConnMaxLifetime", } dbFlagReadTimeout = &cli.DurationFlag{ Name: "db.readTimeout", - Value: 10 * time.Second, + Value: 10 * time.Second, //nolint:gomnd EnvVars: []string{"DB_READ_TIMEOUT"}, } dbFlagWriteTimeout = &cli.DurationFlag{ Name: "db.writeTimeout", - Value: 5 * time.Second, + Value: 5 * time.Second, //nolint:gomnd EnvVars: []string{"DB_WRITE_TIMEOUT"}, } dbFlags = []cli.Flag{ diff --git a/go.mod b/go.mod index be84127..1666120 100644 --- a/go.mod +++ b/go.mod @@ -5,36 +5,80 @@ go 1.21 require ( github.com/ThreeDotsLabs/watermill v1.3.5 github.com/ThreeDotsLabs/watermill-amqp/v2 v2.1.1 + github.com/cenkalti/backoff/v4 v4.2.1 github.com/elliotchance/phpserialize v1.3.3 github.com/go-chi/chi/v5 v5.0.10 github.com/google/go-cmp v0.6.0 + github.com/google/uuid v1.3.1 + github.com/gosimple/slug v1.13.1 + github.com/ory/dockertest/v3 v3.10.0 github.com/stretchr/testify v1.8.4 github.com/uptrace/bun v1.1.16 github.com/uptrace/bun/dialect/pgdialect v1.1.16 + github.com/uptrace/bun/dialect/sqlitedialect v1.1.16 github.com/uptrace/bun/driver/pgdriver v1.1.16 + github.com/uptrace/bun/driver/sqliteshim v1.1.16 github.com/urfave/cli/v2 v2.26.0 ) require ( + github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect + github.com/containerd/continuity v0.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/google/uuid v1.3.1 // indirect + github.com/docker/cli v20.10.17+incompatible // indirect + github.com/docker/docker v20.10.7+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/gosimple/unidecode v1.0.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/lithammer/shortuuid/v3 v3.0.7 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/runc v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rabbitmq/amqp091-go v1.9.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/crypto v0.13.0 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/sys v0.12.0 // indirect + golang.org/x/tools v0.13.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/uint128 v1.3.0 // indirect mellium.im/sasl v0.3.1 // indirect + modernc.org/cc/v3 v3.41.0 // indirect + modernc.org/ccgo/v3 v3.16.15 // indirect + modernc.org/libc v1.24.1 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.1 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/sqlite v1.25.0 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index 4c7c001..54504da 100644 --- a/go.sum +++ b/go.sum @@ -1,84 +1,270 @@ +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK2WofkbXg= github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY= github.com/ThreeDotsLabs/watermill-amqp/v2 v2.1.1 h1:OxvMB2/3YtcQuC7quC+CGmFpGz9oaxP2ef5wkp+R2oM= github.com/ThreeDotsLabs/watermill-amqp/v2 v2.1.1/go.mod h1:MCNoh0HUg4w0bY64on9BnhUodHeimz8+vMfXrzyuWN8= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= +github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elliotchance/phpserialize v1.3.3 h1:hV4QVmGdCiYgoBbw+ADt6fNgyZ2mYX0OgpnON1adTCM= github.com/elliotchance/phpserialize v1.3.3/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= +github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= +github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A= github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8= github.com/uptrace/bun/dialect/pgdialect v1.1.16 h1:eUPZ+YCJ69BA+W1X1ZmpOJSkv1oYtinr0zCXf7zCo5g= github.com/uptrace/bun/dialect/pgdialect v1.1.16/go.mod h1:KQjfx/r6JM0OXfbv0rFrxAbdkPD7idK8VitnjIV9fZI= +github.com/uptrace/bun/dialect/sqlitedialect v1.1.16 h1:gbc9BP/e4sNOB9VBj+Si46dpOz2oktmZPidkda92GYY= +github.com/uptrace/bun/dialect/sqlitedialect v1.1.16/go.mod h1:YNezpK7fIn5Wa2WGmTCZ/nEyiswcXmuT4iNWADeL1x4= github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7QiNOCPLqPFJjK4= github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= +github.com/uptrace/bun/driver/sqliteshim v1.1.16 h1:dhXrdXQegGSM+Jk07s9p1Y575DMhCKl7wBkss8kgM+8= +github.com/uptrace/bun/driver/sqliteshim v1.1.16/go.mod h1:7fJaPqaiSvnWytQcMwVv1ZcjUJh7tGDonO/zq3O6RRI= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI= github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= +modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= +modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= +modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= +modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= +modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.1 h1:9J+2/GKTlV503mk3yv8QJ6oEpRCUrRy0ad8TXEPoV8M= +modernc.org/memory v1.7.1/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= +modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= +modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= +modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= diff --git a/internal/adapter/adapter_test.go b/internal/adapter/adapter_test.go new file mode 100644 index 0000000..20f66c0 --- /dev/null +++ b/internal/adapter/adapter_test.go @@ -0,0 +1,43 @@ +package adapter_test + +import ( + "flag" + "log" + "os" + "testing" + + "gitea.dwysokinski.me/twhelp/corev3/internal/adapter/adaptertest" + "github.com/ory/dockertest/v3" +) + +var postgres *adaptertest.Postgres + +func TestMain(m *testing.M) { + os.Exit(testMainWrapper(m)) +} + +func testMainWrapper(m *testing.M) int { + // https://github.com/golang/go/blob/7cfa7d69259590319524c3715df4a39b39924bc3/src/testing/testing.go#L224 + flag.Parse() + + if testing.Short() { + return m.Run() + } + + pool, err := dockertest.NewPool("") + if err != nil { + log.Println("couldn't construct dockertest.Pool:", err) + return 1 + } + + postgres, err = adaptertest.NewPostgres(pool) + if err != nil { + log.Println("couldn't construct adaptertest.Postgres:", err) + return 1 + } + defer func() { + _ = postgres.Close() + }() + + return m.Run() +} diff --git a/internal/adapter/adaptertest/postgres.go b/internal/adapter/adaptertest/postgres.go new file mode 100644 index 0000000..ce1ebd9 --- /dev/null +++ b/internal/adapter/adaptertest/postgres.go @@ -0,0 +1,183 @@ +package adaptertest + +import ( + "database/sql" + "fmt" + "net/url" + "os" + "strings" + "time" + "unicode" + + "github.com/cenkalti/backoff/v4" + "github.com/google/uuid" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "github.com/stretchr/testify/require" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/driver/pgdriver" +) + +type Postgres struct { + connectionString *url.URL + resource *dockertest.Resource +} + +type postgresConfig struct { + repo string + tag string + ttl uint +} + +type PostgresOption func(cfg *postgresConfig) + +const postgresDefaultTTL = 60 + +func newPostgresConfig(opts ...PostgresOption) *postgresConfig { + cfg := &postgresConfig{ + repo: "postgres", + tag: "14", + ttl: postgresDefaultTTL, + } + + for _, opt := range opts { + opt(cfg) + } + + return cfg +} + +func WithTTL(ttlSeconds uint) PostgresOption { + return func(cfg *postgresConfig) { + cfg.ttl = ttlSeconds + } +} + +func WithImage(image string) PostgresOption { + return func(cfg *postgresConfig) { + cfg.repo, cfg.tag = docker.ParseRepositoryTag(image) + } +} + +// NewPostgres constructs a new Postgres resource. If the env variable 'TESTS_POSTGRES_CONNECTION_STRING' is set, +// this function doesn't run a new Docker container and uses the value of this variable as a connection string, +// otherwise this function runs a new PostgresSQL database in a Docker container. +// This function is intended for use in TestMain. +func NewPostgres(pool *dockertest.Pool, opts ...PostgresOption) (*Postgres, error) { + cfg := newPostgresConfig(opts...) + + if connString := os.Getenv("TESTS_POSTGRES_CONNECTION_STRING"); connString != "" { + u, err := url.ParseRequestURI(connString) + if err != nil { + return nil, err + } + + return &Postgres{ + connectionString: u, + }, nil + } + + u := &url.URL{ + Scheme: "postgres", + User: url.UserPassword("postgres", "postgres"), + Path: "twhelpdb", + RawQuery: url.Values{ + "sslmode": []string{"disable"}, + }.Encode(), + } + + pw, _ := u.User.Password() + + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: cfg.repo, + Tag: cfg.tag, + Env: []string{ + fmt.Sprintf("POSTGRES_USER=%s", u.User.Username()), + fmt.Sprintf("POSTGRES_PASSWORD=%s", pw), + fmt.Sprintf("POSTGRES_DB=%s", u.Path), + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{ + Name: "no", + } + }) + if err != nil { + return nil, fmt.Errorf("couldn't run postgres: %w", err) + } + + if err = resource.Expire(cfg.ttl); err != nil { + return nil, err + } + + u.Host, err = getHostPort(resource, "5432/tcp") + if err != nil { + return nil, err + } + + return &Postgres{ + connectionString: u, + resource: resource, + }, nil +} + +const ( + postgresPingBackOffMaxInterval = 5 * time.Second + postgresPingBackOffMaxElapsedTime = 30 * time.Second +) + +// NewBunDB initializes a new instance of *bun.DB, which is ready for use (all required migrations are applied). +// This method guarantees data separation through PostgresSQL schemas +// (https://www.postgresql.org/docs/current/ddl-schemas.html) +// and it is safe to call Postgres.NewBunDB multiple times. +// +// It fails if Postgres hasn't been properly initialized (via NewPostgres). +func (p *Postgres) NewBunDB(tb TestingTB) *bun.DB { + tb.Helper() + + require.NotNil(tb, p, "postgres resource not property initialized") + require.NotNil(tb, p.connectionString, "postgres resource not properly initialized") + + schema := generatePostgresSchema() + + sqldb := sql.OpenDB( + pgdriver.NewConnector( + pgdriver.WithDSN(p.connectionString.String()), + pgdriver.WithConnParams(map[string]any{ + "search_path": schema, + }), + ), + ) + + bunDB := bun.NewDB(sqldb, pgdialect.New()) + tb.Cleanup(func() { + _ = bunDB.Close() + }) + + bo := backoff.NewExponentialBackOff() + bo.MaxInterval = postgresPingBackOffMaxInterval + bo.MaxElapsedTime = postgresPingBackOffMaxElapsedTime + require.NoError(tb, retry(bo, bunDB.Ping), "couldn't ping DB") + + _, err := bunDB.Exec("CREATE SCHEMA ?", bun.Safe(schema)) + require.NoError(tb, err, "couldn't create postgres schema") + + runMigrations(tb, bunDB) + + return bunDB +} + +func (p *Postgres) Close() error { + if p.resource != nil { + if err := p.resource.Close(); err != nil { + return err + } + } + + return nil +} + +func generatePostgresSchema() string { + return strings.TrimFunc(strings.ReplaceAll(uuid.NewString(), "-", "_"), unicode.IsNumber) +} diff --git a/internal/adapter/adaptertest/sqlite.go b/internal/adapter/adaptertest/sqlite.go new file mode 100644 index 0000000..754f255 --- /dev/null +++ b/internal/adapter/adaptertest/sqlite.go @@ -0,0 +1,27 @@ +package adaptertest + +import ( + "database/sql" + + "github.com/stretchr/testify/require" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/sqlitedialect" + "github.com/uptrace/bun/driver/sqliteshim" +) + +const sqliteMaxIdleConns = 1000 + +// NewBunDBSQLite initializes a new instance of *bun.DB, which is ready for use (all required migrations are applied). +// Data is stored in memory (https://www.sqlite.org/inmemorydb.html). +func NewBunDBSQLite(tb TestingTB) *bun.DB { + sqldb, err := sql.Open(sqliteshim.ShimName, "file::memory:?cache=shared") + require.NoError(tb, err) + sqldb.SetMaxIdleConns(sqliteMaxIdleConns) + sqldb.SetConnMaxLifetime(0) + + db := bun.NewDB(sqldb, sqlitedialect.New()) + + runMigrations(tb, db) + + return db +} diff --git a/internal/adapter/adaptertest/testing_t.go b/internal/adapter/adaptertest/testing_t.go new file mode 100644 index 0000000..d53ef9e --- /dev/null +++ b/internal/adapter/adaptertest/testing_t.go @@ -0,0 +1,9 @@ +package adaptertest + +// TestingTB is a subset of the API provided by both *testing.T and *testing.B. +type TestingTB interface { + Helper() + Errorf(format string, args ...interface{}) + FailNow() + Cleanup(f func()) +} diff --git a/internal/adapter/adaptertest/utils.go b/internal/adapter/adaptertest/utils.go new file mode 100644 index 0000000..d9b2fdc --- /dev/null +++ b/internal/adapter/adaptertest/utils.go @@ -0,0 +1,58 @@ +package adaptertest + +import ( + "context" + "errors" + "fmt" + "net" + "net/url" + "os" + "time" + + "gitea.dwysokinski.me/twhelp/corev3/internal/migrations" + "github.com/cenkalti/backoff/v4" + "github.com/ory/dockertest/v3" + "github.com/stretchr/testify/require" + "github.com/uptrace/bun" +) + +const migrationTimeout = 10 * time.Second + +func runMigrations(tb TestingTB, db *bun.DB) { + tb.Helper() + + migrator := migrations.NewMigrator(db) + ctx, cancel := context.WithTimeout(context.Background(), migrationTimeout) + defer cancel() + + require.NoError(tb, migrator.Init(ctx), "couldn't init migrator") + _, err := migrator.Migrate(ctx) + fmt.Println(err) + require.NoError(tb, err, "couldn't apply migrations") +} + +func getHostPort(resource *dockertest.Resource, id string) (string, error) { + dockerURL := os.Getenv("DOCKER_HOST") + if dockerURL == "" { + return resource.GetHostPort(id), nil + } + + u, err := url.Parse(dockerURL) + if err != nil { + return "", err + } + + return net.JoinHostPort(u.Hostname(), resource.GetPort(id)), nil +} + +func retry(bo backoff.BackOff, op backoff.Operation) error { + if err := backoff.Retry(op, bo); err != nil { + if bo.NextBackOff() == backoff.Stop { + return errors.New("reached retry deadline") + } + + return err + } + + return nil +} diff --git a/internal/adapter/internal/bunmodel/version.go b/internal/adapter/internal/bunmodel/version.go new file mode 100644 index 0000000..f6bdd47 --- /dev/null +++ b/internal/adapter/internal/bunmodel/version.go @@ -0,0 +1,43 @@ +package bunmodel + +import ( + "fmt" + + "gitea.dwysokinski.me/twhelp/corev3/internal/domain" + "github.com/uptrace/bun" +) + +type Version struct { + bun.BaseModel `bun:"table:versions,alias:version"` + + Code string `bun:"code,pk"` + Name string `bun:"name"` + Host string `bun:"host"` + Timezone string `bun:"timezone"` +} + +func (v Version) ToDomain() (domain.Version, error) { + return domain.UnmarshalVersionFromDatabase( + v.Code, + v.Name, + v.Host, + v.Timezone, + ) +} + +type Versions []Version + +func (vs Versions) ToDomain() (domain.Versions, error) { + res := make(domain.Versions, 0, len(vs)) + + for _, v := range vs { + converted, err := v.ToDomain() + if err != nil { + return nil, fmt.Errorf("couldn't construct domain.Version (version=%s): %w", v.Code, err) + } + + res = append(res, converted) + } + + return res, nil +} diff --git a/internal/adapter/repository_bun_version.go b/internal/adapter/repository_bun_version.go new file mode 100644 index 0000000..ed004cb --- /dev/null +++ b/internal/adapter/repository_bun_version.go @@ -0,0 +1,64 @@ +package adapter + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "gitea.dwysokinski.me/twhelp/corev3/internal/adapter/internal/bunmodel" + "gitea.dwysokinski.me/twhelp/corev3/internal/domain" + "github.com/uptrace/bun" +) + +type VersionBunRepository struct { + db bun.IDB +} + +func NewVersionBunRepository(db bun.IDB) *VersionBunRepository { + return &VersionBunRepository{db: db} +} + +func (repo *VersionBunRepository) List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error) { + var versions bunmodel.Versions + + if err := repo.db.NewSelect(). + Model(&versions). + Apply(listVersionsParamsApplier{params: params}.apply). + Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("couldn't select versions from the database: %w", err) + } + + return versions.ToDomain() +} + +func (repo *VersionBunRepository) ListCount( + ctx context.Context, + params domain.ListVersionsParams, +) (domain.Versions, int, error) { + versions, err := repo.List(ctx, params) + if err != nil { + return nil, 0, err + } + + return versions, len(versions), nil +} + +type listVersionsParamsApplier struct { + params domain.ListVersionsParams +} + +func (a listVersionsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery { + for _, s := range a.params.Sort() { + switch s { + case domain.VersionSortCodeASC: + q = q.Order("version.code ASC") + case domain.VersionSortCodeDESC: + q = q.Order("version.code DESC") + default: + return q.Err(errors.New("unsupported sort value")) + } + } + + return q +} diff --git a/internal/adapter/repository_bun_version_test.go b/internal/adapter/repository_bun_version_test.go new file mode 100644 index 0000000..053bca6 --- /dev/null +++ b/internal/adapter/repository_bun_version_test.go @@ -0,0 +1,30 @@ +package adapter_test + +import ( + "testing" + + "gitea.dwysokinski.me/twhelp/corev3/internal/adapter" + "gitea.dwysokinski.me/twhelp/corev3/internal/adapter/adaptertest" +) + +func TestVersionBunRepo_Postgres(t *testing.T) { + t.Parallel() + + if testing.Short() { + t.Skip("skipping long-running test") + } + + testVersionRepo(t, func(t *testing.T) versionRepository { + t.Helper() + return adapter.NewVersionBunRepository(postgres.NewBunDB(t)) + }) +} + +func TestVersionBunRepo_SQLite(t *testing.T) { + t.Parallel() + + testVersionRepo(t, func(t *testing.T) versionRepository { + t.Helper() + return adapter.NewVersionBunRepository(adaptertest.NewBunDBSQLite(t)) + }) +} diff --git a/internal/adapter/repository_version_test.go b/internal/adapter/repository_version_test.go new file mode 100644 index 0000000..a4cf60c --- /dev/null +++ b/internal/adapter/repository_version_test.go @@ -0,0 +1,105 @@ +package adapter_test + +import ( + "cmp" + "context" + "slices" + "testing" + + "gitea.dwysokinski.me/twhelp/corev3/internal/domain" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type versionRepository interface { + List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error) + ListCount( + ctx context.Context, + params domain.ListVersionsParams, + ) (domain.Versions, int, error) +} + +func testVersionRepo(t *testing.T, newRepo func(t *testing.T) versionRepository) { + t.Helper() + + t.Run("List & ListCount", func(t *testing.T) { + t.Parallel() + + repo := newRepo(t) + + tests := []struct { + name string + params func(t *testing.T) domain.ListVersionsParams + assertVersions func(t *testing.T, versions domain.Versions) + assertError func(t *testing.T, err error) + assertTotal func(t *testing.T, total int) + }{ + { + name: "OK: default params", + params: func(t *testing.T) domain.ListVersionsParams { + t.Helper() + return domain.NewListVersionsParams() + }, + assertVersions: func(t *testing.T, versions domain.Versions) { + t.Helper() + assert.NotEmpty(t, len(versions)) + assert.True(t, slices.IsSortedFunc(versions, func(a, b domain.Version) int { + return cmp.Compare(a.Code(), b.Code()) + })) + }, + assertError: func(t *testing.T, err error) { + t.Helper() + require.NoError(t, err) + }, + assertTotal: func(t *testing.T, total int) { + t.Helper() + assert.NotEmpty(t, total, 0) + }, + }, + { + name: "OK: Sort=[Code DESC]", + params: func(t *testing.T) domain.ListVersionsParams { + t.Helper() + params := domain.NewListVersionsParams() + require.NoError(t, params.SetSort([]domain.VersionSort{domain.VersionSortCodeDESC})) + return params + }, + assertVersions: func(t *testing.T, versions domain.Versions) { + t.Helper() + assert.NotEmpty(t, len(versions)) + assert.True(t, slices.IsSortedFunc(versions, func(a, b domain.Version) int { + return cmp.Compare(a.Code(), b.Code()) * -1 + })) + }, + assertError: func(t *testing.T, err error) { + t.Helper() + require.NoError(t, err) + }, + assertTotal: func(t *testing.T, total int) { + t.Helper() + assert.NotEmpty(t, total) + }, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + params := tt.params(t) + + res, err := repo.List(ctx, params) + tt.assertError(t, err) + tt.assertVersions(t, res) + + res, count, err := repo.ListCount(ctx, params) + tt.assertError(t, err) + tt.assertVersions(t, res) + tt.assertTotal(t, count) + }) + } + }) +} diff --git a/internal/app/service_version.go b/internal/app/service_version.go new file mode 100644 index 0000000..b337090 --- /dev/null +++ b/internal/app/service_version.go @@ -0,0 +1,23 @@ +package app + +import ( + "context" + + "gitea.dwysokinski.me/twhelp/corev3/internal/domain" +) + +type VersionRepository interface { + List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error) +} + +type VersionService struct { + repo VersionRepository +} + +func NewVersionService(repo VersionRepository) *VersionService { + return &VersionService{repo: repo} +} + +func (svc *VersionService) List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error) { + return svc.repo.List(ctx, params) +} diff --git a/internal/chislog/chislog.go b/internal/chislog/chislog.go index 6c35387..961be5a 100644 --- a/internal/chislog/chislog.go +++ b/internal/chislog/chislog.go @@ -47,6 +47,7 @@ func Logger(logger Slogger, opts ...Option) func(next http.Handler) http.Handler slog.String("httpRequest.ip", cfg.ipExtractor(r)), slog.Int("httpResponse.status", status), slog.Int("httpResponse.bytes", ww.BytesWritten()), + //nolint:gomnd slog.Float64("httpResponse.duration", float64(end.Sub(start).Nanoseconds())/1000000.0), // in milliseconds ) }) diff --git a/internal/domain/error.go b/internal/domain/error.go new file mode 100644 index 0000000..3cc8830 --- /dev/null +++ b/internal/domain/error.go @@ -0,0 +1,77 @@ +package domain + +import ( + "errors" + "fmt" + + "github.com/gosimple/slug" +) + +type ErrorCode uint8 + +const ( + ErrorCodeUnknown ErrorCode = iota + ErrorCodeEntityNotFound + ErrorCodeIncorrectInput +) + +func (e ErrorCode) String() string { + switch e { + case ErrorCodeEntityNotFound: + return "entity-not-found" + case ErrorCodeIncorrectInput: + return "incorrect-input" + case ErrorCodeUnknown: + fallthrough + default: + return "unknown-error" + } +} + +type Error interface { + error + Code() ErrorCode + Slug() string +} + +type ErrorWithParams interface { + Error + Params() map[string]any +} + +type ValidationError struct { + Field string + Err error +} + +var _ ErrorWithParams = ValidationError{} + +func (e ValidationError) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.Err) +} + +func (e ValidationError) Code() ErrorCode { + var domainErr Error + if errors.As(e.Err, &domainErr) { + return domainErr.Code() + } + return ErrorCodeIncorrectInput +} + +func (e ValidationError) Slug() string { + s := "validation" + var domainErr Error + if errors.As(e.Err, &domainErr) { + s = domainErr.Slug() + } + return fmt.Sprintf("%s-%s", slug.Make(e.Field), s) +} + +func (e ValidationError) Params() map[string]any { + var withParams ErrorWithParams + ok := errors.As(e.Err, &withParams) + if !ok { + return nil + } + return withParams.Params() +} diff --git a/internal/domain/validators.go b/internal/domain/validators.go new file mode 100644 index 0000000..34cde52 --- /dev/null +++ b/internal/domain/validators.go @@ -0,0 +1,45 @@ +package domain + +import ( + "fmt" +) + +type LenError struct { + Min int + Max int + Current int +} + +var _ ErrorWithParams = LenError{} + +func (e LenError) Error() string { + return fmt.Sprintf("sort length must be between %d and %d (current length: %d)", e.Min, e.Max, e.Current) +} + +func (e LenError) Code() ErrorCode { + return ErrorCodeIncorrectInput +} + +func (e LenError) Slug() string { + return "length" +} + +func (e LenError) Params() map[string]any { + return map[string]any{ + "Min": e.Min, + "Max": e.Max, + "Current": e.Current, + } +} + +func validateSliceLen[S ~[]E, E any](s S, min, max int) error { + if l := len(s); l > max || l < min { + return LenError{ + Min: min, + Max: max, + Current: l, + } + } + + return nil +} diff --git a/internal/domain/version.go b/internal/domain/version.go new file mode 100644 index 0000000..308dfef --- /dev/null +++ b/internal/domain/version.go @@ -0,0 +1,109 @@ +package domain + +import ( + "errors" + "net/url" +) + +type Version struct { + code string + name string + host string + timezone string +} + +// UnmarshalVersionFromDatabase unmarshals Version from the database. +// +// It should be used only for unmarshalling from the database! +// You can't use UnmarshalVersionFromDatabase as constructor - It may put domain into the invalid state! +func UnmarshalVersionFromDatabase( + code string, + name string, + host string, + timezone string, +) (Version, error) { + if code == "" { + return Version{}, errors.New("code can't be blank") + } + + if name == "" { + return Version{}, errors.New("name can't be blank") + } + + if host == "" { + return Version{}, errors.New("host can't be blank") + } + + if timezone == "" { + return Version{}, errors.New("timezone can't be blank") + } + + return Version{ + code: code, + name: name, + host: host, + timezone: timezone, + }, nil +} + +func (v Version) Code() string { + return v.code +} + +func (v Version) Name() string { + return v.name +} + +func (v Version) Host() string { + return v.host +} + +func (v Version) Timezone() string { + return v.timezone +} + +func (v Version) URL() *url.URL { + return &url.URL{ + Scheme: "https", + Host: v.host, + } +} + +type Versions []Version + +type VersionSort uint8 + +const ( + VersionSortCodeASC VersionSort = iota + 1 + VersionSortCodeDESC +) + +type ListVersionsParams struct { + sort []VersionSort +} + +func NewListVersionsParams() ListVersionsParams { + return ListVersionsParams{sort: []VersionSort{VersionSortCodeASC}} +} + +const ( + versionSortMinLength = 1 + versionSortMaxLength = 1 +) + +func (params *ListVersionsParams) SetSort(sort []VersionSort) error { + if err := validateSliceLen(sort, versionSortMinLength, versionSortMaxLength); err != nil { + return ValidationError{ + Field: "sort", + Err: err, + } + } + + params.sort = sort + + return nil +} + +func (params *ListVersionsParams) Sort() []VersionSort { + return params.sort +} diff --git a/internal/domain/version_test.go b/internal/domain/version_test.go new file mode 100644 index 0000000..6a19a58 --- /dev/null +++ b/internal/domain/version_test.go @@ -0,0 +1,79 @@ +package domain_test + +import ( + "testing" + + "gitea.dwysokinski.me/twhelp/corev3/internal/domain" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestListVersionsParams_SetSort(t *testing.T) { + t.Parallel() + + type args struct { + sort []domain.VersionSort + } + + tests := []struct { + name string + args args + expectedErr error + }{ + { + name: "OK", + args: args{ + sort: []domain.VersionSort{ + domain.VersionSortCodeASC, + }, + }, + }, + { + name: "ERR: len(sort) < 1", + args: args{ + sort: nil, + }, + expectedErr: domain.ValidationError{ + Field: "sort", + Err: domain.LenError{ + Min: 1, + Max: 1, + Current: 0, + }, + }, + }, + { + name: "ERR: len(sort) > 1", + args: args{ + sort: []domain.VersionSort{ + domain.VersionSortCodeASC, + domain.VersionSortCodeDESC, + }, + }, + expectedErr: domain.ValidationError{ + Field: "sort", + Err: domain.LenError{ + Min: 1, + Max: 1, + Current: 2, + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + params := domain.NewListVersionsParams() + + require.ErrorIs(t, params.SetSort(tt.args.sort), tt.expectedErr) + if tt.expectedErr != nil { + return + } + assert.Equal(t, tt.args.sort, params.Sort()) + }) + } +} diff --git a/internal/health/health.go b/internal/health/health.go index 1e6834a..b09df26 100644 --- a/internal/health/health.go +++ b/internal/health/health.go @@ -52,7 +52,7 @@ func (h *Health) CheckReady(ctx context.Context) Result { } type singleCheckResultWithName struct { - Name string + name string SingleCheckResult } @@ -83,7 +83,7 @@ func (h *Health) check(ctx context.Context, checks []Checker) Result { } checkCh <- singleCheckResultWithName{ - Name: ch.Name(), + name: ch.Name(), SingleCheckResult: check, } }(ch) @@ -105,7 +105,7 @@ func (h *Health) check(ctx context.Context, checks []Checker) Result { res.Status = StatusFail } - res.Checks[check.Name] = append(res.Checks[check.Name], check.SingleCheckResult) + res.Checks[check.name] = append(res.Checks[check.name], check.SingleCheckResult) } return res diff --git a/internal/migrations/20231216063603_create_versions_table.down.sql b/internal/migrations/20231216063603_create_versions_table.down.sql deleted file mode 100644 index c31dbbc..0000000 --- a/internal/migrations/20231216063603_create_versions_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table if exists versions CASCADE; diff --git a/internal/migrations/20231216063731_create_servers_table.down.sql b/internal/migrations/20231216063731_create_servers_table.down.sql deleted file mode 100644 index ae6f784..0000000 --- a/internal/migrations/20231216063731_create_servers_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table if exists servers cascade; diff --git a/internal/migrations/20231216063825_create_tribes_table.down.sql b/internal/migrations/20231216063825_create_tribes_table.down.sql deleted file mode 100644 index 5141f04..0000000 --- a/internal/migrations/20231216063825_create_tribes_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table if exists tribes cascade; diff --git a/internal/migrations/20231216064247_create_players_table.down.sql b/internal/migrations/20231216064247_create_players_table.down.sql deleted file mode 100644 index 676aefd..0000000 --- a/internal/migrations/20231216064247_create_players_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table if exists players cascade; diff --git a/internal/migrations/20231216064325_create_ennoblements_table.down.sql b/internal/migrations/20231216064325_create_ennoblements_table.down.sql deleted file mode 100644 index e5908d8..0000000 --- a/internal/migrations/20231216064325_create_ennoblements_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table if exists ennoblements cascade; diff --git a/internal/migrations/20231216064433_create_player_snapshots_table.down.sql b/internal/migrations/20231216064433_create_player_snapshots_table.down.sql deleted file mode 100644 index ad97025..0000000 --- a/internal/migrations/20231216064433_create_player_snapshots_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table if exists player_snapshots cascade; diff --git a/internal/migrations/20231216064559_create_tribe_snapshots_table.down.sql b/internal/migrations/20231216064559_create_tribe_snapshots_table.down.sql deleted file mode 100644 index 7161901..0000000 --- a/internal/migrations/20231216064559_create_tribe_snapshots_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table if exists tribe_snapshots cascade; diff --git a/internal/migrations/20231216064617_create_tribe_changes_table.down.sql b/internal/migrations/20231216064617_create_tribe_changes_table.down.sql deleted file mode 100644 index 09d00bd..0000000 --- a/internal/migrations/20231216064617_create_tribe_changes_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table if exists tribe_changes cascade; diff --git a/internal/migrations/20231216063603_create_versions_table.up.sql b/internal/migrations/20231220050527_create_versions_table.go similarity index 80% rename from internal/migrations/20231216063603_create_versions_table.up.sql rename to internal/migrations/20231220050527_create_versions_table.go index a26a1b4..8eeae53 100644 --- a/internal/migrations/20231216063603_create_versions_table.up.sql +++ b/internal/migrations/20231220050527_create_versions_table.go @@ -1,3 +1,14 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` create table if not exists versions ( code varchar(6) not null @@ -28,4 +39,11 @@ values ('pl', 'Poland', 'www.plemiona.pl', 'Europe/Warsaw'), ('de', 'Germany', 'www.die-staemme.de', 'Europe/Berlin'), ('sk', 'Slovakia', 'www.divoke-kmene.sk', 'Europe/Bratislava'), ('ru', 'Russia', 'www.voynaplemyon.com', 'Europe/Moscow') -ON CONFLICT DO NOTHING; +ON CONFLICT DO NOTHING;`) + + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "drop table if exists versions CASCADE;") + return err + }) +} diff --git a/internal/migrations/20231216063731_create_servers_table.up.sql b/internal/migrations/20231220050708_create_servers_table.go similarity index 99% rename from internal/migrations/20231216063731_create_servers_table.up.sql rename to internal/migrations/20231220050708_create_servers_table.go index fc4cef0..79c8d00 100644 --- a/internal/migrations/20231216063731_create_servers_table.up.sql +++ b/internal/migrations/20231220050708_create_servers_table.go @@ -1,3 +1,15 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +//nolint:lll +func init() { + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` create table if not exists servers ( key varchar(100) not null @@ -46,4 +58,10 @@ values ('pls1', 'https://pls1.plemiona.pl', false, true, 0, 0, 0, '{"Win": {"Ch ('sks1', 'https://sks1.divoke-kmene.sk', false, true, 0, 0, 0, '{"Win": {"Check": 0}, "Ally": {"Limit": 0, "Levels": 0, "NoHarm": 0, "NoJoin": 0, "NoLeave": 0, "FixedAllies": 0, "NoOtherSupport": 0, "XpRequirements": "", "AllytimeSupport": 0, "PointsMemberCount": 0, "NoOtherSupportType": 0, "WarsAutoacceptDays": 0, "WarsMemberRequirement": 0, "WarsPointsRequirement": 0}, "Game": {"Tech": 0, "Event": 0, "Hauls": 0, "Archer": 0, "Church": 0, "Knight": 0, "HaulsMax": 0, "FakeLimit": 0, "FarmLimit": 0, "HaulsBase": 0, "Stronghold": 0, "Watchtower": 0, "BarbarianRise": 0, "BaseProduction": 0, "KnightNewItems": 0, "SuppressEvents": 0, "BarbarianShrink": 0, "BuildtimeFormula": 0, "BarbarianMaxPoints": 0}, "Misc": {"Tutorial": 0, "KillRanking": 0, "TradeCancelTime": 0}, "Snob": {"Gold": 0, "Rise": 0, "Factor": 0, "MaxDist": 0, "CoinIron": 0, "CoinWood": 0, "CoinStone": 0, "CheapRebuild": 0, "NoBarbConquer": 0}, "Build": {"Destroy": 0}, "Coord": {"Func": 0, "Inner": 0, "MapSize": 0, "BonusNew": 0, "SelectStart": 0, "NobleRestart": 0, "BonusVillages": 0, "EmptyVillages": 0, "StartVillages": 0, "VillageMoveWait": 0}, "Moral": 0, "Night": {"Active": 0, "EndHour": 0, "Duration": 0, "DefFactor": 0, "StartHour": 0}, "Sleep": {"Max": 0, "Min": 0, "Delay": 0, "Active": 0, "MaxAwake": 0, "MinAwake": 0, "WarnTime": 0}, "Speed": 0, "Newbie": {"Days": 0, "Ratio": 0, "RatioDays": 0, "RemoveNewbieVillages": 0}, "Sitter": {"Allow": 0}, "Commands": {"MillisArrival": 0, "CommandCancelTime": 0}, "Buildings": {"CustomFarm": 0, "CustomHide": 0, "CustomIron": 0, "CustomMain": 0, "CustomSnob": 0, "CustomWall": 0, "CustomWood": 0, "CustomPlace": 0, "CustomSmith": 0, "CustomStone": 0, "CustomChurch": 0, "CustomGarage": 0, "CustomMarket": 0, "CustomStable": 0, "CustomStatue": 0, "CustomStorage": 0, "CustomBarracks": 0, "CustomWatchtower": 0}, "UnitSpeed": 0}', '{"Farm": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Hide": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Iron": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Main": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Snob": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Wall": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Wood": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Place": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Smith": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Stone": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Garage": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Market": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Stable": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Statue": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Storage": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Barracks": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Watchtower": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}}', '{"Axe": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Ram": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Spy": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Snob": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Heavy": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Light": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Spear": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Sword": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Archer": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Knight": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Marcher": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Militia": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Catapult": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}}', CURRENT_TIMESTAMP, null, null, null, 'sk', null, null, null, 0, 0, 0), ('master', 'https://www.tribalwars.co.uk', false, true, 0, 0, 0, '{"Win": {"Check": 0}, "Ally": {"Limit": 0, "Levels": 0, "NoHarm": 0, "NoJoin": 0, "NoLeave": 0, "FixedAllies": 0, "NoOtherSupport": 0, "XpRequirements": "", "AllytimeSupport": 0, "PointsMemberCount": 0, "NoOtherSupportType": 0, "WarsAutoacceptDays": 0, "WarsMemberRequirement": 0, "WarsPointsRequirement": 0}, "Game": {"Tech": 0, "Event": 0, "Hauls": 0, "Archer": 0, "Church": 0, "Knight": 0, "HaulsMax": 0, "FakeLimit": 0, "FarmLimit": 0, "HaulsBase": 0, "Stronghold": 0, "Watchtower": 0, "BarbarianRise": 0, "BaseProduction": 0, "KnightNewItems": 0, "SuppressEvents": 0, "BarbarianShrink": 0, "BuildtimeFormula": 0, "BarbarianMaxPoints": 0}, "Misc": {"Tutorial": 0, "KillRanking": 0, "TradeCancelTime": 0}, "Snob": {"Gold": 0, "Rise": 0, "Factor": 0, "MaxDist": 0, "CoinIron": 0, "CoinWood": 0, "CoinStone": 0, "CheapRebuild": 0, "NoBarbConquer": 0}, "Build": {"Destroy": 0}, "Coord": {"Func": 0, "Inner": 0, "MapSize": 0, "BonusNew": 0, "SelectStart": 0, "NobleRestart": 0, "BonusVillages": 0, "EmptyVillages": 0, "StartVillages": 0, "VillageMoveWait": 0}, "Moral": 0, "Night": {"Active": 0, "EndHour": 0, "Duration": 0, "DefFactor": 0, "StartHour": 0}, "Sleep": {"Max": 0, "Min": 0, "Delay": 0, "Active": 0, "MaxAwake": 0, "MinAwake": 0, "WarnTime": 0}, "Speed": 0, "Newbie": {"Days": 0, "Ratio": 0, "RatioDays": 0, "RemoveNewbieVillages": 0}, "Sitter": {"Allow": 0}, "Commands": {"MillisArrival": 0, "CommandCancelTime": 0}, "Buildings": {"CustomFarm": 0, "CustomHide": 0, "CustomIron": 0, "CustomMain": 0, "CustomSnob": 0, "CustomWall": 0, "CustomWood": 0, "CustomPlace": 0, "CustomSmith": 0, "CustomStone": 0, "CustomChurch": 0, "CustomGarage": 0, "CustomMarket": 0, "CustomStable": 0, "CustomStatue": 0, "CustomStorage": 0, "CustomBarracks": 0, "CustomWatchtower": 0}, "UnitSpeed": 0}', '{"Farm": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Hide": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Iron": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Main": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Snob": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Wall": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Wood": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Place": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Smith": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Stone": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Garage": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Market": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Stable": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Statue": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Storage": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Barracks": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Watchtower": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}}', '{"Axe": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Ram": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Spy": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Snob": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Heavy": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Light": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Spear": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Sword": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Archer": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Knight": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Marcher": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Militia": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Catapult": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}}', CURRENT_TIMESTAMP, null, null, null, 'uk', null, null, null, 0, 0, 0), ('rus1', 'https://rus1.voynaplemyon.com', false, true, 0, 0, 0, '{"Win": {"Check": 0}, "Ally": {"Limit": 0, "Levels": 0, "NoHarm": 0, "NoJoin": 0, "NoLeave": 0, "FixedAllies": 0, "NoOtherSupport": 0, "XpRequirements": "", "AllytimeSupport": 0, "PointsMemberCount": 0, "NoOtherSupportType": 0, "WarsAutoacceptDays": 0, "WarsMemberRequirement": 0, "WarsPointsRequirement": 0}, "Game": {"Tech": 0, "Event": 0, "Hauls": 0, "Archer": 0, "Church": 0, "Knight": 0, "HaulsMax": 0, "FakeLimit": 0, "FarmLimit": 0, "HaulsBase": 0, "Stronghold": 0, "Watchtower": 0, "BarbarianRise": 0, "BaseProduction": 0, "KnightNewItems": 0, "SuppressEvents": 0, "BarbarianShrink": 0, "BuildtimeFormula": 0, "BarbarianMaxPoints": 0}, "Misc": {"Tutorial": 0, "KillRanking": 0, "TradeCancelTime": 0}, "Snob": {"Gold": 0, "Rise": 0, "Factor": 0, "MaxDist": 0, "CoinIron": 0, "CoinWood": 0, "CoinStone": 0, "CheapRebuild": 0, "NoBarbConquer": 0}, "Build": {"Destroy": 0}, "Coord": {"Func": 0, "Inner": 0, "MapSize": 0, "BonusNew": 0, "SelectStart": 0, "NobleRestart": 0, "BonusVillages": 0, "EmptyVillages": 0, "StartVillages": 0, "VillageMoveWait": 0}, "Moral": 0, "Night": {"Active": 0, "EndHour": 0, "Duration": 0, "DefFactor": 0, "StartHour": 0}, "Sleep": {"Max": 0, "Min": 0, "Delay": 0, "Active": 0, "MaxAwake": 0, "MinAwake": 0, "WarnTime": 0}, "Speed": 0, "Newbie": {"Days": 0, "Ratio": 0, "RatioDays": 0, "RemoveNewbieVillages": 0}, "Sitter": {"Allow": 0}, "Commands": {"MillisArrival": 0, "CommandCancelTime": 0}, "Buildings": {"CustomFarm": 0, "CustomHide": 0, "CustomIron": 0, "CustomMain": 0, "CustomSnob": 0, "CustomWall": 0, "CustomWood": 0, "CustomPlace": 0, "CustomSmith": 0, "CustomStone": 0, "CustomChurch": 0, "CustomGarage": 0, "CustomMarket": 0, "CustomStable": 0, "CustomStatue": 0, "CustomStorage": 0, "CustomBarracks": 0, "CustomWatchtower": 0}, "UnitSpeed": 0}', '{"Farm": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Hide": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Iron": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Main": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Snob": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Wall": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Wood": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Place": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Smith": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Stone": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Garage": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Market": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Stable": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Statue": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Storage": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Barracks": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}, "Watchtower": {"Pop": 0, "Iron": 0, "Wood": 0, "Stone": 0, "MaxLevel": 0, "MinLevel": 0, "BuildTime": 0, "PopFactor": 0, "IronFactor": 0, "WoodFactor": 0, "StoneFactor": 0, "BuildTimeFactor": 0}}', '{"Axe": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Ram": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Spy": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Snob": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Heavy": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Light": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Spear": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Sword": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Archer": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Knight": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Marcher": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Militia": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}, "Catapult": {"Pop": 0, "Carry": 0, "Speed": 0, "Attack": 0, "Defense": 0, "BuildTime": 0, "DefenseArcher": 0, "DefenseCavalry": 0}}', CURRENT_TIMESTAMP, null, null, null, 'ru', null, null, null, 0, 0, 0) -ON CONFLICT DO NOTHING; +ON CONFLICT DO NOTHING;`) + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "drop table if exists servers cascade;") + return err + }) +} diff --git a/internal/migrations/20231216063825_create_tribes_table.up.sql b/internal/migrations/20231220050904_create_tribes_table.go similarity index 84% rename from internal/migrations/20231216063825_create_tribes_table.up.sql rename to internal/migrations/20231220050904_create_tribes_table.go index 2118bc0..415d64a 100644 --- a/internal/migrations/20231216063825_create_tribes_table.up.sql +++ b/internal/migrations/20231220050904_create_tribes_table.go @@ -1,3 +1,14 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` create table if not exists tribes ( id bigint not null, @@ -33,3 +44,11 @@ create table if not exists tribes create index if not exists tribes_server_key_idx on tribes (server_key); +`) + + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "drop table if exists tribes cascade;") + return err + }) +} diff --git a/internal/migrations/20231216064247_create_players_table.up.sql b/internal/migrations/20231220051107_create_players_table.go similarity index 83% rename from internal/migrations/20231216064247_create_players_table.up.sql rename to internal/migrations/20231220051107_create_players_table.go index abe0a9b..d6db581 100644 --- a/internal/migrations/20231216064247_create_players_table.up.sql +++ b/internal/migrations/20231220051107_create_players_table.go @@ -1,3 +1,14 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` create table if not exists players ( id bigint not null, @@ -31,3 +42,10 @@ create table if not exists players create index if not exists players_server_key_idx on players (server_key); +`) + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "drop table if exists players cascade;") + return err + }) +} diff --git a/internal/migrations/20231220051221_create_villages_table.go b/internal/migrations/20231220051221_create_villages_table.go new file mode 100644 index 0000000..5f4ebec --- /dev/null +++ b/internal/migrations/20231220051221_create_villages_table.go @@ -0,0 +1,42 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` +create table if not exists villages +( + id bigint not null, + server_key varchar(100) not null + references servers, + name varchar(150) not null, + points bigint default 0, + x bigint default 0, + y bigint default 0, + continent varchar(5), + bonus bigint default 0, + player_id bigint, + created_at timestamp with time zone default CURRENT_TIMESTAMP not null, + profile_url varchar(150), + primary key (id, server_key) +); + +create index if not exists villages_server_key_idx + on villages (server_key); + +create index if not exists villages_server_key_x_y_idx + on villages (server_key, x, y); + +create index if not exists villages_server_key_player_id_idx + on villages (server_key, player_id);`) + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "drop table if exists villages cascade;") + return err + }) +} diff --git a/internal/migrations/20231216064325_create_ennoblements_table.up.sql b/internal/migrations/20231220051322_create_ennoblements_table.go similarity index 74% rename from internal/migrations/20231216064325_create_ennoblements_table.up.sql rename to internal/migrations/20231220051322_create_ennoblements_table.go index 31170f7..9f61afc 100644 --- a/internal/migrations/20231216064325_create_ennoblements_table.up.sql +++ b/internal/migrations/20231220051322_create_ennoblements_table.go @@ -1,6 +1,17 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` create table if not exists ennoblements ( - id bigint generated by default as identity + id bigint ? primary key, server_key varchar(100) not null references servers, @@ -13,11 +24,6 @@ create table if not exists ennoblements created_at timestamp with time zone default CURRENT_TIMESTAMP not null ); -create unique index if not exists ennoblements_hash_key - on ennoblements (hash_record_extended( - ROW (server_key, village_id, new_owner_id, new_tribe_id, old_owner_id, old_tribe_id, points, created_at), - 0::bigint)); - create index if not exists ennoblements_server_key_created_at_idx on ennoblements (server_key, created_at); @@ -35,3 +41,10 @@ create index if not exists ennoblements_server_key_new_tribe_id_idx create index if not exists ennoblements_server_key_old_tribe_id_idx on ennoblements (server_key, old_tribe_id); +`, bun.Safe(autoIncrement(db))) + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "drop table if exists ennoblements cascade;") + return err + }) +} diff --git a/internal/migrations/20231216064433_create_player_snapshots_table.up.sql b/internal/migrations/20231220051411_create_player_snapshots_table.go similarity index 73% rename from internal/migrations/20231216064433_create_player_snapshots_table.up.sql rename to internal/migrations/20231220051411_create_player_snapshots_table.go index 32694a7..95622eb 100644 --- a/internal/migrations/20231216064433_create_player_snapshots_table.up.sql +++ b/internal/migrations/20231220051411_create_player_snapshots_table.go @@ -1,6 +1,17 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` create table if not exists player_snapshots ( - id bigint generated by default as identity + id bigint ? primary key, player_id bigint not null, num_villages bigint default 0, @@ -22,3 +33,10 @@ create table if not exists player_snapshots unique (player_id, server_key, date), foreign key (player_id, server_key) references players ); +`, bun.Safe(autoIncrement(db))) + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "drop table if exists player_snapshots cascade;") + return err + }) +} diff --git a/internal/migrations/20231216064559_create_tribe_snapshots_table.up.sql b/internal/migrations/20231220052323_create_tribe_snapshots_table.go similarity index 74% rename from internal/migrations/20231216064559_create_tribe_snapshots_table.up.sql rename to internal/migrations/20231220052323_create_tribe_snapshots_table.go index 50b0b37..545a708 100644 --- a/internal/migrations/20231216064559_create_tribe_snapshots_table.up.sql +++ b/internal/migrations/20231220052323_create_tribe_snapshots_table.go @@ -1,7 +1,17 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` create table if not exists tribe_snapshots ( - id bigint generated by default as identity - primary key, + id bigint ? primary key, tribe_id bigint not null, server_key varchar(100) not null references servers, @@ -24,3 +34,10 @@ create table if not exists tribe_snapshots unique (tribe_id, server_key, date), foreign key (tribe_id, server_key) references tribes ); +`, bun.Safe(autoIncrement(db))) + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "drop table if exists tribe_snapshots cascade;") + return err + }) +} diff --git a/internal/migrations/20231216064617_create_tribe_changes_table.up.sql b/internal/migrations/20231220052428_create_tribe_changes_table.go similarity index 64% rename from internal/migrations/20231216064617_create_tribe_changes_table.up.sql rename to internal/migrations/20231220052428_create_tribe_changes_table.go index 9689880..b53bf60 100644 --- a/internal/migrations/20231216064617_create_tribe_changes_table.up.sql +++ b/internal/migrations/20231220052428_create_tribe_changes_table.go @@ -1,7 +1,17 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +func init() { + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, ` create table if not exists tribe_changes ( - id bigint generated by default as identity - primary key, + id bigint ? primary key, player_id bigint not null, new_tribe_id bigint, old_tribe_id bigint, @@ -14,13 +24,15 @@ create table if not exists tribe_changes create index if not exists tribe_changes_server_key_player_id_idx on tribe_changes (server_key, player_id); -create unique index if not exists tribe_changes_hash_key - on tribe_changes (hash_record_extended( - ROW (player_id, new_tribe_id, old_tribe_id, server_key, date_trunc('hours'::text, (created_at AT TIME ZONE 'UTC'::text))), - 0::bigint)); - create index if not exists tribe_changes_server_key_new_tribe_id_idx on tribe_changes (server_key, new_tribe_id); create index if not exists tribe_changes_server_key_old_tribe_id_idx on tribe_changes (server_key, old_tribe_id); +`, bun.Safe(autoIncrement(db))) + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "drop table if exists tribe_changes cascade;") + return err + }) +} diff --git a/internal/migrations/20231220052526_create_index_ennoblements_hash_key.go b/internal/migrations/20231220052526_create_index_ennoblements_hash_key.go new file mode 100644 index 0000000..490d936 --- /dev/null +++ b/internal/migrations/20231220052526_create_index_ennoblements_hash_key.go @@ -0,0 +1,34 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect" +) + +//nolint:lll +func init() { + // this index is for Postgres only + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + if db.Dialect().Name() != dialect.PG { + return nil + } + + _, err := db.ExecContext( + ctx, + `create unique index concurrently if not exists ennoblements_hash_key + on ennoblements (hash_record_extended( + ROW (server_key, village_id, new_owner_id, new_tribe_id, old_owner_id, old_tribe_id, points, created_at), + 0::bigint));`, + ) + return err + }, func(ctx context.Context, db *bun.DB) error { + if db.Dialect().Name() != dialect.PG { + return nil + } + + _, err := db.ExecContext(ctx, "DROP INDEX CONCURRENTLY ennoblements_hash_key") + return err + }) +} diff --git a/internal/migrations/20231220052547_create_index_tribe_changes_hash_key.go b/internal/migrations/20231220052547_create_index_tribe_changes_hash_key.go new file mode 100644 index 0000000..ef224bc --- /dev/null +++ b/internal/migrations/20231220052547_create_index_tribe_changes_hash_key.go @@ -0,0 +1,34 @@ +package migrations + +import ( + "context" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect" +) + +//nolint:lll +func init() { + // this index is for Postgres only + migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + if db.Dialect().Name() != dialect.PG { + return nil + } + + _, err := db.ExecContext( + ctx, + `create unique index concurrently if not exists tribe_changes_hash_key + on tribe_changes (hash_record_extended( + ROW (player_id, new_tribe_id, old_tribe_id, server_key, date_trunc('hours'::text, (created_at AT TIME ZONE 'UTC'::text))), + 0::bigint));`, + ) + return err + }, func(ctx context.Context, db *bun.DB) error { + if db.Dialect().Name() != dialect.PG { + return nil + } + + _, err := db.ExecContext(ctx, "DROP INDEX CONCURRENTLY tribe_changes_hash_key") + return err + }) +} diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go index ce19676..281611a 100644 --- a/internal/migrations/migrations.go +++ b/internal/migrations/migrations.go @@ -1,23 +1,12 @@ package migrations import ( - "embed" - "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" ) var migrations = migrate.NewMigrations() -//go:embed *.sql -var sqlMigrations embed.FS - -func init() { - if err := migrations.Discover(sqlMigrations); err != nil { - panic(err) - } -} - func NewMigrator(db *bun.DB, opts ...migrate.MigratorOption) *migrate.Migrator { return migrate.NewMigrator(db, migrations, opts...) } diff --git a/internal/migrations/sql_utils.go b/internal/migrations/sql_utils.go new file mode 100644 index 0000000..0a1c461 --- /dev/null +++ b/internal/migrations/sql_utils.go @@ -0,0 +1,17 @@ +package migrations + +import "github.com/uptrace/bun/dialect/feature" + +type hasFeaturer interface { + HasFeature(feat feature.Feature) bool +} + +func autoIncrement(f hasFeaturer) string { + if f.HasFeature(feature.GeneratedIdentity) { + return "GENERATED BY DEFAULT AS IDENTITY" + } + if f.HasFeature(feature.AutoIncrement) { + return "AUTO_INCREMENT" + } + return "" +}