From b711c5b2bd1524c87ba20aba4e1e302aa25b819a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Mon, 3 Oct 2022 07:14:47 +0200 Subject: [PATCH 1/2] feat: add a new cmd - group add --- cmd/dcbot/internal/run/run.go | 14 +- go.mod | 26 ++++ go.sum | 133 +++++++++++++++- internal/bundb/bundb_test.go | 147 ++++++++++++++++++ internal/bundb/group.go | 31 ++++ internal/bundb/group_test.go | 33 ++++ internal/bundb/internal/model/group.go | 25 +++ .../20221003042831_create_groups_table.go | 30 ++++ internal/bundb/migrations/migrations.go | 4 + internal/discord/bot.go | 34 +++- internal/discord/command.go | 9 ++ internal/discord/command_add_group.go | 85 ++++++++++ internal/discord/discord.go | 15 ++ internal/domain/error.go | 31 ++++ internal/domain/error_test.go | 20 +++ internal/domain/group.go | 27 ++++ internal/domain/group_test.go | 41 +++++ internal/service/group.go | 28 ++++ k8s/base/jobs.yml | 29 ++++ k8s/base/kustomization.yml | 1 + 20 files changed, 757 insertions(+), 6 deletions(-) create mode 100644 internal/bundb/bundb_test.go create mode 100644 internal/bundb/group.go create mode 100644 internal/bundb/group_test.go create mode 100644 internal/bundb/internal/model/group.go create mode 100644 internal/bundb/migrations/20221003042831_create_groups_table.go create mode 100644 internal/discord/command.go create mode 100644 internal/discord/command_add_group.go create mode 100644 internal/discord/discord.go create mode 100644 internal/domain/error.go create mode 100644 internal/domain/error_test.go create mode 100644 internal/domain/group.go create mode 100644 internal/domain/group_test.go create mode 100644 internal/service/group.go create mode 100644 k8s/base/jobs.yml diff --git a/cmd/dcbot/internal/run/run.go b/cmd/dcbot/internal/run/run.go index 3b3cd1b..3a893a9 100644 --- a/cmd/dcbot/internal/run/run.go +++ b/cmd/dcbot/internal/run/run.go @@ -7,7 +7,10 @@ import ( "os/signal" "syscall" + "gitea.dwysokinski.me/twhelp/dcbot/cmd/dcbot/internal" + "gitea.dwysokinski.me/twhelp/dcbot/internal/bundb" "gitea.dwysokinski.me/twhelp/dcbot/internal/discord" + "gitea.dwysokinski.me/twhelp/dcbot/internal/service" "github.com/kelseyhightower/envconfig" "github.com/urfave/cli/v2" "go.uber.org/zap" @@ -25,12 +28,21 @@ func New() *cli.Command { Action: func(c *cli.Context) error { logger := zap.L() + db, err := internal.NewBunDB() + if err != nil { + return fmt.Errorf("internal.NewBunDB: %w", err) + } + + groupRepo := bundb.NewGroup(db) + + groupSvc := service.NewGroup(groupRepo) + cfg, err := newBotConfig() if err != nil { return fmt.Errorf("newBotConfig: %w", err) } - bot, err := discord.NewBot(cfg.Token) + bot, err := discord.NewBot(cfg.Token, groupSvc) if err != nil { return fmt.Errorf("discord.NewBot: %w", err) } diff --git a/go.mod b/go.mod index 4118d75..f8990fe 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,10 @@ go 1.19 require ( github.com/bwmarrin/discordgo v0.26.1 + github.com/cenkalti/backoff/v4 v4.1.3 + github.com/google/uuid v1.3.0 github.com/kelseyhightower/envconfig v1.4.0 + github.com/ory/dockertest/v3 v3.9.1 github.com/stretchr/testify v1.8.0 github.com/uptrace/bun v1.1.8 github.com/uptrace/bun/dialect/pgdialect v1.1.8 @@ -15,18 +18,39 @@ require ( ) require ( + github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // 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/docker/cli v20.10.14+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/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/websocket v1.4.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // 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.2 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // 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/uptrace/opentelemetry-go-extra/otelsql v0.1.15 // 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 go.opentelemetry.io/otel v1.9.0 // indirect go.opentelemetry.io/otel/metric v0.31.0 // indirect @@ -34,7 +58,9 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mellium.im/sasl v0.3.0 // indirect ) diff --git a/go.sum b/go.sum index 9b61dbf..fb3524c 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,112 @@ +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.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +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/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE= github.com/bwmarrin/discordgo v0.26.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +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.14+incompatible h1:dSBKJOVesDgHo7rbxlYjYsXe7gPzrTT+/cKQgpDAazg= +github.com/docker/cli v20.10.14+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/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +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.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +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/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +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.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/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394= +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/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.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw= +github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +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.9.1 h1:v4dkG+dlu76goxMiTT2j8zV7s4oPPEppKT8K8p2f1kY= +github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= +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/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.20210429002308-3879420cc921/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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +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.8 h1:slxuaP4LYWFbPRUmTtQhfJN+6eX/6ar2HDKYTcI50SA= @@ -42,14 +119,25 @@ github.com/uptrace/bun/extra/bunotel v1.1.8 h1:Sg7XcLLI+KTalKg36aRpOJovYF9UUVuN5 github.com/uptrace/bun/extra/bunotel v1.1.8/go.mod h1:1chzcC5wkd7FP+/SRUbmmqF0DL1ed+NgdHlGE+GIkPs= github.com/uptrace/opentelemetry-go-extra/otelsql v0.1.15 h1:s6BZwhj/2oZ9GSkfcTH8YRHjxj3MGo1j2Pg83Pc1xjw= github.com/uptrace/opentelemetry-go-extra/otelsql v0.1.15/go.mod h1:aZXwJzbTHnhh8vpd1bPjK68iTuNEtfvpHcJq73FdmhQ= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.17.1 h1:UzjDEw2dJQUE3iRaiNQ1VrVFbyAtKGH3VdkMoHA58V0= github.com/urfave/cli/v2 v2.17.1/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= +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.opentelemetry.io/otel v1.9.0 h1:8WZNQFIB2a71LnANS9JeyidJKKGOOremcUtb/OtHISw= go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo= go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= @@ -63,20 +151,63 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +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.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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/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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/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/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.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/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/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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= mellium.im/sasl v0.3.0 h1:0qoaTCTo5Py7u/g0cBIQZcMOgG/5LM71nshbXwznBh8= mellium.im/sasl v0.3.0/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= diff --git a/internal/bundb/bundb_test.go b/internal/bundb/bundb_test.go new file mode 100644 index 0000000..62f0b79 --- /dev/null +++ b/internal/bundb/bundb_test.go @@ -0,0 +1,147 @@ +package bundb_test + +import ( + "context" + "database/sql" + "errors" + "fmt" + "net" + "net/url" + "os" + "strings" + "testing" + "time" + "unicode" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/bundb/migrations" + "github.com/cenkalti/backoff/v4" + "github.com/google/uuid" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/driver/pgdriver" +) + +func newDB(tb testing.TB) *bun.DB { + tb.Helper() + + if dsn, ok := os.LookupEnv("TESTS_DB_DSN"); ok { + return newDBWithDSN(tb, dsn) + } + + q := url.Values{} + q.Add("sslmode", "disable") + dsn := &url.URL{ + Scheme: "postgres", + User: url.UserPassword("postgres", "postgres"), + Path: "twhelp", + RawQuery: q.Encode(), + } + + pool, err := dockertest.NewPool("") + require.NoError(tb, err, "couldn't connect to docker") + + pool.MaxWait = 20 * time.Second + + pw, _ := dsn.User.Password() + + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: "14.5", + Env: []string{ + fmt.Sprintf("POSTGRES_USER=%s", dsn.User.Username()), + fmt.Sprintf("POSTGRES_PASSWORD=%s", pw), + fmt.Sprintf("POSTGRES_DB=%s", dsn.Path), + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{ + Name: "no", + } + }) + require.NoError(tb, err, "couldn't start resource") + tb.Cleanup(func() { + _ = pool.Purge(resource) + }) + assert.NoError(tb, resource.Expire(60)) + + dsn.Host = getHostPort(tb, resource, "5432/tcp") + + return newDBWithDSN(tb, dsn.String()) +} + +func newDBWithDSN(tb testing.TB, dsn string) *bun.DB { + tb.Helper() + + schema := generateSchema() + sqldb := sql.OpenDB( + pgdriver.NewConnector( + pgdriver.WithDSN(dsn), + pgdriver.WithConnParams(map[string]interface{}{ + "search_path": schema, + }), + ), + ) + + bunDB := bun.NewDB(sqldb, pgdialect.New()) + tb.Cleanup(func() { + _ = bunDB.Close() + }) + require.NoError(tb, retry(bunDB.Ping), "couldn't ping DB") + + _, err := bunDB.Exec("CREATE SCHEMA ?", bun.Safe(schema)) + require.NoError(tb, err, "couldn't create schema") + + runMigrations(tb, bunDB) + + return bunDB +} + +func getHostPort(tb testing.TB, resource *dockertest.Resource, id string) string { + tb.Helper() + + dockerURL := os.Getenv("DOCKER_HOST") + if dockerURL == "" { + return resource.GetHostPort(id) + } + + u, err := url.Parse(dockerURL) + require.NoError(tb, err) + + return net.JoinHostPort(u.Hostname(), resource.GetPort(id)) +} + +func runMigrations(tb testing.TB, db *bun.DB) { + tb.Helper() + + migrator := migrations.NewMigrator(db) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + require.NoError(tb, migrator.Init(ctx), "couldn't migrate (1)") + _, err := migrator.Migrate(ctx) + require.NoError(tb, err, "couldn't migrate (2)") +} + +func generateSchema() string { + return strings.TrimFunc(strings.ReplaceAll(uuid.NewString(), "-", "_"), unicode.IsNumber) +} + +func retry(op func() error) error { + bo := backoff.NewExponentialBackOff() + bo.MaxInterval = time.Second * 5 + bo.MaxElapsedTime = 30 * time.Second + + 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/bundb/group.go b/internal/bundb/group.go new file mode 100644 index 0000000..31ec6ed --- /dev/null +++ b/internal/bundb/group.go @@ -0,0 +1,31 @@ +package bundb + +import ( + "context" + "fmt" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/bundb/internal/model" + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" + "github.com/uptrace/bun" +) + +type Group struct { + db *bun.DB +} + +func NewGroup(db *bun.DB) *Group { + return &Group{db: db} +} + +func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (domain.Group, error) { + group := model.Group{ + ServerID: params.ServerID(), + } + if _, err := g.db.NewInsert(). + Model(&group). + Returning("*"). + Exec(ctx); err != nil { + return domain.Group{}, fmt.Errorf("something went wrong while inserting group into the db: %w", err) + } + return group.ToDomain(), nil +} diff --git a/internal/bundb/group_test.go b/internal/bundb/group_test.go new file mode 100644 index 0000000..66d8bea --- /dev/null +++ b/internal/bundb/group_test.go @@ -0,0 +1,33 @@ +package bundb_test + +import ( + "context" + "testing" + "time" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/bundb" + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGroup_Create(t *testing.T) { + t.Parallel() + + repo := bundb.NewGroup(newDB(t)) + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + params, err := domain.NewCreateGroupParams("592292203234328587") + require.NoError(t, err) + + group, err := repo.Create(context.Background(), params) + assert.NoError(t, err) + _, err = uuid.Parse(group.ID) + assert.NoError(t, err) + assert.Equal(t, params.ServerID(), group.ServerID) + assert.WithinDuration(t, time.Now(), group.CreatedAt, 1*time.Second) + }) +} diff --git a/internal/bundb/internal/model/group.go b/internal/bundb/internal/model/group.go new file mode 100644 index 0000000..1914cdb --- /dev/null +++ b/internal/bundb/internal/model/group.go @@ -0,0 +1,25 @@ +package model + +import ( + "time" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type Group struct { + bun.BaseModel `bun:"base_model,table:groups,alias:group"` + + ID uuid.UUID `bun:"id,type:uuid,pk,default:gen_random_uuid()"` + ServerID string `bun:"server_id,type:varchar(200)"` + CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"` +} + +func (g Group) ToDomain() domain.Group { + return domain.Group{ + ID: g.ID.String(), + ServerID: g.ServerID, + CreatedAt: g.CreatedAt, + } +} diff --git a/internal/bundb/migrations/20221003042831_create_groups_table.go b/internal/bundb/migrations/20221003042831_create_groups_table.go new file mode 100644 index 0000000..c38805c --- /dev/null +++ b/internal/bundb/migrations/20221003042831_create_groups_table.go @@ -0,0 +1,30 @@ +package migrations + +import ( + "context" + "fmt" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/bundb/internal/model" + "github.com/uptrace/bun" +) + +func init() { + Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + if _, err := db.NewCreateTable(). + Model(&model.Group{}). + Varchar(defaultVarcharLength). + Exec(ctx); err != nil { + return fmt.Errorf("couldn't create the 'groups' table: %w", err) + } + return nil + }, func(ctx context.Context, db *bun.DB) error { + if _, err := db.NewDropTable(). + Model(&model.Group{}). + IfExists(). + Cascade(). + Exec(ctx); err != nil { + return fmt.Errorf("couldn't drop the 'groups' table: %w", err) + } + return nil + }) +} diff --git a/internal/bundb/migrations/migrations.go b/internal/bundb/migrations/migrations.go index 8a48e55..1ab5418 100644 --- a/internal/bundb/migrations/migrations.go +++ b/internal/bundb/migrations/migrations.go @@ -5,6 +5,10 @@ import ( "github.com/uptrace/bun/migrate" ) +const ( + defaultVarcharLength = 200 +) + var Migrations = migrate.NewMigrations() func NewMigrator(db *bun.DB, opts ...migrate.MigratorOption) *migrate.Migrator { diff --git a/internal/discord/bot.go b/internal/discord/bot.go index 784c1fd..2b8f2b8 100644 --- a/internal/discord/bot.go +++ b/internal/discord/bot.go @@ -1,16 +1,23 @@ package discord import ( + "context" "fmt" + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" "github.com/bwmarrin/discordgo" ) -type Bot struct { - s *discordgo.Session +type GroupService interface { + Create(ctx context.Context, params domain.CreateGroupParams) (domain.Group, error) } -func NewBot(token string) (*Bot, error) { +type Bot struct { + s *discordgo.Session + groupSvc GroupService +} + +func NewBot(token string, groupSvc GroupService) (*Bot, error) { s, err := discordgo.New("Bot " + token) if err != nil { return nil, fmt.Errorf("discordgo.New: %w", err) @@ -18,7 +25,26 @@ func NewBot(token string) (*Bot, error) { if err = s.Open(); err != nil { return nil, fmt.Errorf("s.Open: %w", err) } - return &Bot{s: s}, nil + b := &Bot{s: s, groupSvc: groupSvc} + if err = b.registerCommands(); err != nil { + _ = s.Close() + return nil, fmt.Errorf("couldn't register commands: %w", err) + } + return b, nil +} + +func (b *Bot) registerCommands() error { + if err := b.registerCommand(&groupCommand{svc: b.groupSvc}); err != nil { + return err + } + return nil +} + +func (b *Bot) registerCommand(cmd command) error { + if err := cmd.register(b.s); err != nil { + return fmt.Errorf("couldn't register command '%s': %w", cmd.name(), err) + } + return nil } func (b *Bot) Close() error { diff --git a/internal/discord/command.go b/internal/discord/command.go new file mode 100644 index 0000000..c0e86ab --- /dev/null +++ b/internal/discord/command.go @@ -0,0 +1,9 @@ +package discord + +import "github.com/bwmarrin/discordgo" + +type command interface { + name() string + register(s *discordgo.Session) error + handle(s *discordgo.Session, i *discordgo.InteractionCreate) +} diff --git a/internal/discord/command_add_group.go b/internal/discord/command_add_group.go new file mode 100644 index 0000000..11d94ed --- /dev/null +++ b/internal/discord/command_add_group.go @@ -0,0 +1,85 @@ +package discord + +import ( + "context" + "fmt" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" + "github.com/bwmarrin/discordgo" +) + +type groupCommand struct { + svc GroupService +} + +func (c *groupCommand) name() string { + return "group" +} + +func (c *groupCommand) register(s *discordgo.Session) error { + var perm int64 = discordgo.PermissionAdministrator + _, err := s.ApplicationCommandCreate(s.State.User.ID, "", &discordgo.ApplicationCommand{ + Name: c.name(), + Description: "Manages monitor groups on this server", + DefaultMemberPermissions: &perm, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "add", + Description: "Adds a new monitor group", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + }, + }) + if err != nil { + return fmt.Errorf("s.ApplicationCommandCreate: %w", err) + } + s.AddHandler(c.handle) + return nil +} + +func (c *groupCommand) handle(s *discordgo.Session, i *discordgo.InteractionCreate) { + cmdData := i.ApplicationCommandData() + + if cmdData.Name != c.name() { + return + } + + switch cmdData.Options[0].Name { + case "add": + c.handleAdd(s, i) + return + } +} + +func (c *groupCommand) handleAdd(s *discordgo.Session, i *discordgo.InteractionCreate) { + ctx := context.Background() + + params, err := domain.NewCreateGroupParams(i.GuildID) + if err != nil { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: messageFromError(err), + }, + }) + return + } + + group, err := c.svc.Create(ctx, params) + if err != nil { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: messageFromError(err), + }, + }) + return + } + + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "group has been successfully added (id=" + group.ID + ")", + }, + }) +} diff --git a/internal/discord/discord.go b/internal/discord/discord.go new file mode 100644 index 0000000..0ca8979 --- /dev/null +++ b/internal/discord/discord.go @@ -0,0 +1,15 @@ +package discord + +import ( + "errors" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" +) + +func messageFromError(err error) string { + var userErr domain.UserError + if !errors.As(err, &userErr) { + return "something went wrong, please try again later" + } + return userErr.UserError() +} diff --git a/internal/domain/error.go b/internal/domain/error.go new file mode 100644 index 0000000..6254a36 --- /dev/null +++ b/internal/domain/error.go @@ -0,0 +1,31 @@ +package domain + +type ErrorCode uint8 + +const ( + ErrorCodeUnknown ErrorCode = iota + ErrorCodeEntityNotFound + ErrorCodeValidationError +) + +type UserError interface { + error + UserError() string + Code() ErrorCode +} + +type RequiredError struct { + Field string +} + +func (e RequiredError) Error() string { + return e.Field + ": cannot be blank" +} + +func (e RequiredError) UserError() string { + return e.Error() +} + +func (e RequiredError) Code() ErrorCode { + return ErrorCodeValidationError +} diff --git a/internal/domain/error_test.go b/internal/domain/error_test.go new file mode 100644 index 0000000..183ac98 --- /dev/null +++ b/internal/domain/error_test.go @@ -0,0 +1,20 @@ +package domain_test + +import ( + "testing" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" + "github.com/stretchr/testify/assert" +) + +func TestRequiredError(t *testing.T) { + t.Parallel() + + err := domain.RequiredError{ + Field: "123", + } + var _ domain.UserError = err + assert.Equal(t, err.Field+": cannot be blank", err.Error()) + assert.Equal(t, err.Error(), err.UserError()) + assert.Equal(t, domain.ErrorCodeValidationError, err.Code()) +} diff --git a/internal/domain/group.go b/internal/domain/group.go new file mode 100644 index 0000000..4070a62 --- /dev/null +++ b/internal/domain/group.go @@ -0,0 +1,27 @@ +package domain + +import "time" + +type Group struct { + ID string + ServerID string + CreatedAt time.Time +} + +type CreateGroupParams struct { + serverID string +} + +func NewCreateGroupParams(serverID string) (CreateGroupParams, error) { + if serverID == "" { + return CreateGroupParams{}, RequiredError{ + Field: "ServerID", + } + } + + return CreateGroupParams{serverID: serverID}, nil +} + +func (c CreateGroupParams) ServerID() string { + return c.serverID +} diff --git a/internal/domain/group_test.go b/internal/domain/group_test.go new file mode 100644 index 0000000..2517ce4 --- /dev/null +++ b/internal/domain/group_test.go @@ -0,0 +1,41 @@ +package domain_test + +import ( + "testing" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" + "github.com/stretchr/testify/assert" +) + +func TestNewCreateGroupParams(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + serverID string + err error + }{ + { + name: "OK", + serverID: "123441", + err: nil, + }, + { + name: "ERR: ServerID cannot be blank", + serverID: "", + err: domain.RequiredError{Field: "ServerID"}, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + res, err := domain.NewCreateGroupParams(tt.serverID) + assert.ErrorIs(t, err, tt.err) + assert.Equal(t, tt.serverID, res.ServerID()) + }) + } +} diff --git a/internal/service/group.go b/internal/service/group.go new file mode 100644 index 0000000..fa0e174 --- /dev/null +++ b/internal/service/group.go @@ -0,0 +1,28 @@ +package service + +import ( + "context" + "fmt" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" +) + +type GroupRepository interface { + Create(ctx context.Context, params domain.CreateGroupParams) (domain.Group, error) +} + +type Group struct { + repo GroupRepository +} + +func NewGroup(repo GroupRepository) *Group { + return &Group{repo: repo} +} + +func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (domain.Group, error) { + group, err := g.repo.Create(ctx, params) + if err != nil { + return domain.Group{}, fmt.Errorf("GroupRepository.Create: %w", err) + } + return group, nil +} diff --git a/k8s/base/jobs.yml b/k8s/base/jobs.yml new file mode 100644 index 0000000..b531632 --- /dev/null +++ b/k8s/base/jobs.yml @@ -0,0 +1,29 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: twhelp-dcbot-migrations-job +spec: + template: + spec: + containers: + - name: twhelp-dcbot-migrations + image: dcbot + args: ["db", "migrate"] + env: + - name: APP_MODE + value: development + - name: DB_MAX_OPEN_CONNECTIONS + value: "1" + - name: DB_DSN + valueFrom: + secretKeyRef: + name: twhelp-dcbot-secret + key: db-dsn + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 150m + memory: 128Mi + restartPolicy: Never diff --git a/k8s/base/kustomization.yml b/k8s/base/kustomization.yml index 079eab1..90d3ac2 100644 --- a/k8s/base/kustomization.yml +++ b/k8s/base/kustomization.yml @@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - bot.yml + - jobs.yml images: - name: dcbot newName: dcbot -- 2.45.1 From 7ae06351ce423be56ef96beaf5658284f14b7a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Mon, 3 Oct 2022 07:18:16 +0200 Subject: [PATCH 2/2] feat: add a new cmd - group add --- internal/discord/command_add_group.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/discord/command_add_group.go b/internal/discord/command_add_group.go index 11d94ed..67da7c1 100644 --- a/internal/discord/command_add_group.go +++ b/internal/discord/command_add_group.go @@ -48,6 +48,7 @@ func (c *groupCommand) handle(s *discordgo.Session, i *discordgo.InteractionCrea case "add": c.handleAdd(s, i) return + default: } } -- 2.45.1