From bd6092fdee053b62cfe9ec4e4158e0729434aa08 Mon Sep 17 00:00:00 2001 From: Tigor Hutasuhut Date: Thu, 25 Apr 2024 20:22:05 +0700 Subject: [PATCH] subreddits: add create api --- api/api.go | 7 ++- api/pubsub_download.go | 46 ++++++++++++++--- api/subreddits_create.go | 38 ++++++++++++++ db/db.go | 26 +++++----- flake.lock | 8 +-- flake.nix | 2 +- go.mod | 48 ++++++++--------- go.sum | 56 ++++++++++++++++++++ pkg/log/watermill.go | 2 +- pkg/telemetry/telemetry.go | 2 +- rest/subreddits/create.http | 10 ++++ rest/subreddits/start.http | 8 +++ server/routes/device_create.go | 1 + server/routes/routes.go | 3 ++ server/routes/subreddit_create.go | 80 +++++++++++++++++++++++++++++ server/routes/subreddit_download.go | 41 +++++++++++++++ 16 files changed, 326 insertions(+), 52 deletions(-) create mode 100644 api/subreddits_create.go create mode 100644 rest/subreddits/create.http create mode 100644 rest/subreddits/start.http create mode 100644 server/routes/subreddit_create.go create mode 100644 server/routes/subreddit_download.go diff --git a/api/api.go b/api/api.go index 8e9880a..0f6a0c5 100644 --- a/api/api.go +++ b/api/api.go @@ -3,6 +3,7 @@ package api import ( "context" "database/sql" + "encoding/json" "fmt" "os" "os/signal" @@ -56,6 +57,7 @@ var watermillLogger = &log.WatermillLogger{} func New(deps Dependencies) *API { ackDeadline := deps.Config.Duration("download.pubsub.ack.deadline") subscriber, err := watermillSql.NewSubscriber(deps.PubsubDB, watermillSql.SubscriberConfig{ + ConsumerGroup: "redmage", AckDeadline: &ackDeadline, SchemaAdapter: watermillSqlite.DefaultSQLiteSchema{}, OffsetsAdapter: watermillSqlite.DefaultSQLiteOffsetsAdapter{}, @@ -93,7 +95,7 @@ func New(deps Dependencies) *API { if err := api.StartScheduler(ctx); err != nil { panic(err) } - api.StartSubredditDownloadPubsub(ch) + go api.StartSubredditDownloadPubsub(ch) return api } @@ -119,7 +121,8 @@ func (api *API) StartScheduler(ctx context.Context) error { func (api *API) scheduleSubreddit(subreddit *models.Subreddit) error { id, err := api.scheduler.AddFunc(subreddit.Schedule, func() { - _ = api.publisher.Publish(downloadTopic, message.NewMessage(watermill.NewUUID(), []byte(subreddit.Name))) + payload, _ := json.Marshal(subreddit) + _ = api.publisher.Publish(downloadTopic, message.NewMessage(watermill.NewUUID(), payload)) }) if err != nil { return errs.Wrap(err) diff --git a/api/pubsub_download.go b/api/pubsub_download.go index c5abb42..1eefe8e 100644 --- a/api/pubsub_download.go +++ b/api/pubsub_download.go @@ -2,13 +2,18 @@ package api import ( "context" + "encoding/json" + "net/http" + "github.com/ThreeDotsLabs/watermill" "github.com/ThreeDotsLabs/watermill/message" "github.com/tigorlazuardi/redmage/api/reddit" "github.com/tigorlazuardi/redmage/models" + "github.com/tigorlazuardi/redmage/pkg/errs" "github.com/tigorlazuardi/redmage/pkg/log" "github.com/tigorlazuardi/redmage/pkg/telemetry" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) func (api *API) StartSubredditDownloadPubsub(messages <-chan *message.Message) { @@ -19,16 +24,17 @@ func (api *API) StartSubredditDownloadPubsub(messages <-chan *message.Message) { msg.Ack() <-api.subredditSemaphore }() - var err error + var ( + err error + subreddit *models.Subreddit + ) ctx, span := tracer.Start(context.Background(), "Download Subreddit Pubsub") defer func() { telemetry.EndWithStatus(span, err) }() span.AddEvent("pubsub." + downloadTopic) - subredditName := string(msg.Payload) - span.SetAttributes(attribute.String("subreddit", subredditName)) - subreddit, err := models.Subreddits.Query(ctx, api.db, models.SelectWhere.Subreddits.Name.EQ(subredditName)).One() + err = json.Unmarshal(msg.Payload, &subreddit) if err != nil { - log.New(ctx).Err(err).Error("failed to find subreddit", "subreddit", subredditName) + log.New(ctx).Err(err).Error("failed to unmarshal json for download pubsub", "topic", downloadTopic) return } @@ -38,15 +44,41 @@ func (api *API) StartSubredditDownloadPubsub(messages <-chan *message.Message) { return } - err = api.DownloadSubredditImages(ctx, subredditName, DownloadSubredditParams{ + err = api.DownloadSubredditImages(ctx, subreddit.Name, DownloadSubredditParams{ Countback: int(subreddit.Countback), Devices: devices, SubredditType: reddit.SubredditType(subreddit.Subtype), }) if err != nil { - log.New(ctx).Err(err).Error("failed to download subreddit images", "subreddit", subredditName) + log.New(ctx).Err(err).Error("failed to download subreddit images", "subreddit", subreddit) return } }(msg) } } + +type PubsubStartDownloadSubredditParams struct { + Subreddit string `json:"subreddit"` +} + +func (api *API) PubsubStartDownloadSubreddit(ctx context.Context, params PubsubStartDownloadSubredditParams) (err error) { + ctx, span := tracer.Start(ctx, "*API.PubsubStartDownloadSubreddit", trace.WithAttributes(attribute.String("subreddit", params.Subreddit))) + defer span.End() + + subreddit, err := models.Subreddits.Query(ctx, api.db, models.SelectWhere.Subreddits.Name.EQ(params.Subreddit)).One() + if err != nil { + if err.Error() == "sql: no rows in result set" { + return errs.Wrapw(err, "subreddit not found", "params", params).Code(http.StatusNotFound) + } + return errs.Wrapw(err, "failed to verify subreddit existence", "params", params) + } + + payload, _ := json.Marshal(subreddit) + + err = api.publisher.Publish(downloadTopic, message.NewMessage(watermill.NewUUID(), payload)) + if err != nil { + return errs.Wrapw(err, "failed to enqueue reddit download", "params", params) + } + + return nil +} diff --git a/api/subreddits_create.go b/api/subreddits_create.go new file mode 100644 index 0000000..ccf7061 --- /dev/null +++ b/api/subreddits_create.go @@ -0,0 +1,38 @@ +package api + +import ( + "context" + "errors" + "net/http" + + "github.com/aarondl/opt/omit" + "github.com/mattn/go-sqlite3" + "github.com/tigorlazuardi/redmage/models" + "github.com/tigorlazuardi/redmage/pkg/errs" +) + +func (api *API) SubredditsCreate(ctx context.Context, params *models.Subreddit) (subreddit *models.Subreddit, err error) { + ctx, span := tracer.Start(ctx, "*API.SubredditsCreate") + defer span.End() + + set := &models.SubredditSetter{ + Name: omit.From(params.Name), + Enable: omit.From(params.Enable), + Subtype: omit.From(params.Subtype), + Schedule: omit.From(params.Schedule), + Countback: omit.From(params.Countback), + } + + subreddit, err = models.Subreddits.Insert(ctx, api.db, set) + if err != nil { + var sqliteErr sqlite3.Error + if errors.As(err, &sqliteErr) { + if sqliteErr.Code == sqlite3.ErrConstraint { + return subreddit, errs.Wrapw(err, "subreddit with that name already exists", "set", set).Code(http.StatusConflict) + } + return subreddit, errs.Wrapw(err, "failed to create subreddit", "set", set) + } + } + + return subreddit, nil +} diff --git a/db/db.go b/db/db.go index 12d9602..b66e0b2 100644 --- a/db/db.go +++ b/db/db.go @@ -17,11 +17,25 @@ import ( var Migrations fs.FS func Open(cfg *config.Config) (*sql.DB, error) { + driver := cfg.String("db.driver") dsn := cfg.String("db.string") db, err := OpenSilent(cfg) if err != nil { return db, err } + if cfg.Bool("db.automigrate") { + goose.SetLogger(&gooseLogger{}) + goose.SetBaseFS(Migrations) + + if err := goose.SetDialect(driver); err != nil { + return db, errs.Wrapw(err, "failed to set goose dialect", "dialect", driver) + } + + if err := goose.Up(db, "db/migrations"); err != nil { + return db, errs.Wrapw(err, "failed to migrate database", "dialect", driver) + } + } + db = sqldblogger.OpenDriver(dsn, db.Driver(), sqlLogger{}, sqldblogger.WithSQLQueryAsMessage(true), ) @@ -38,17 +52,5 @@ func OpenSilent(cfg *config.Config) (*sql.DB, error) { return db, errs.Wrapw(err, "failed to open database", "driver", driver) } - if cfg.Bool("db.automigrate") { - goose.SetLogger(&gooseLogger{}) - goose.SetBaseFS(Migrations) - - if err := goose.SetDialect(driver); err != nil { - return db, errs.Wrapw(err, "failed to set goose dialect", "dialect", driver) - } - - if err := goose.Up(db, "db/migrations"); err != nil { - return db, errs.Wrapw(err, "failed to migrate database", "dialect", driver) - } - } return db, err } diff --git a/flake.lock b/flake.lock index 4d20bc6..975e82f 100644 --- a/flake.lock +++ b/flake.lock @@ -136,16 +136,16 @@ "xc": "xc" }, "locked": { - "lastModified": 1711308490, - "narHash": "sha256-9Co3yvfy8X69PIffPg2lDjVCVTjDhiFnSsJd4MQ6cf4=", + "lastModified": 1712689407, + "narHash": "sha256-TU8QG6OmUzSNDAX9W0Ntmz5cucLqVQeTskfnJbm/YM0=", "owner": "a-h", "repo": "templ", - "rev": "80bffc608e52244d441df77940a9f11a0c074536", + "rev": "0b30dc5aa29561d54f2f777723c7c7f956bb5de2", "type": "github" }, "original": { "owner": "a-h", - "ref": "v0.2.648", + "ref": "v0.2.663", "repo": "templ", "type": "github" } diff --git a/flake.nix b/flake.nix index c4a23ca..453afef 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - templ.url = "github:a-h/templ/v0.2.648"; + templ.url = "github:a-h/templ/v0.2.663"; nixpkgs.url = "nixpkgs/nixpkgs-unstable"; }; diff --git a/go.mod b/go.mod index 983fccf..3d5124a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.1 require ( github.com/ThreeDotsLabs/watermill v1.3.5 github.com/ThreeDotsLabs/watermill-sql/v3 v3.0.1 - github.com/a-h/templ v0.2.648 + github.com/a-h/templ v0.2.663 github.com/aarondl/opt v0.0.0-20240108180805-338d04d857dc github.com/adrg/xdg v0.4.0 github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 @@ -24,9 +24,9 @@ require ( github.com/knadh/koanf/v2 v2.1.1 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.20 - github.com/mattn/go-sqlite3 v1.14.16 - github.com/pressly/goose/v3 v3.19.2 - github.com/robfig/cron/v3 v3.0.0 + github.com/mattn/go-sqlite3 v1.14.22 + github.com/pressly/goose/v3 v3.20.0 + github.com/robfig/cron/v3 v3.0.1 github.com/simukti/sqldb-logger v0.0.0-20230108155151-646c1a075551 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 @@ -39,26 +39,26 @@ require ( github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/lithammer/shortuuid/v3 v3.0.7 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect - github.com/samber/lo v1.38.1 // indirect - github.com/stephenafamo/scan v0.4.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect - golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect - golang.org/x/net v0.23.0 // indirect + github.com/samber/lo v1.39.0 // indirect + github.com/stephenafamo/scan v0.5.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect + golang.org/x/image v0.15.0 // indirect + golang.org/x/net v0.24.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/grpc v1.63.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect + google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect ) require ( - github.com/XSAM/otelsql v0.29.0 + github.com/XSAM/otelsql v0.30.0 github.com/disintegration/imaging v1.6.2 github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -70,17 +70,17 @@ require ( github.com/mfridman/interpolate v0.0.2 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/riandyrn/otelchi v0.6.0 + github.com/riandyrn/otelchi v0.7.0 github.com/samber/slog-multi v1.0.2 github.com/sethvargo/go-retry v0.2.4 // indirect - go.opentelemetry.io/otel v1.25.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 - go.opentelemetry.io/otel/metric v1.25.0 // indirect - go.opentelemetry.io/otel/sdk v1.25.0 - go.opentelemetry.io/otel/trace v1.25.0 + go.opentelemetry.io/otel v1.26.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/otel/sdk v1.26.0 + go.opentelemetry.io/otel/trace v1.26.0 go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect - golang.org/x/sync v0.6.0 - golang.org/x/sys v0.18.0 // indirect + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 86296ea..8f88f8e 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,12 @@ github.com/ThreeDotsLabs/watermill-sql/v3 v3.0.1 h1:+uW9Db+7Ep4uon7enOq1cozCRua3 github.com/ThreeDotsLabs/watermill-sql/v3 v3.0.1/go.mod h1:iYZqlHt0tJPQIFwQSXoI6GnxDhTZhAzxVR1/EIS3DOw= github.com/XSAM/otelsql v0.29.0 h1:pEw9YXXs8ZrGRYfDc0cmArIz9lci5b42gmP5+tA1Huc= github.com/XSAM/otelsql v0.29.0/go.mod h1:d3/0xGIGC5RVEE+Ld7KotwaLy6zDeaF3fLJHOPpdN2w= +github.com/XSAM/otelsql v0.30.0 h1:yd6Ds3xQkKtP5+JztH2un5Hfy9uyo1eUgv34dGpSIK4= +github.com/XSAM/otelsql v0.30.0/go.mod h1:12ObuENPHhAcc2cU89u7Yr0uT60+FliFCTP9Sd4N68o= github.com/a-h/templ v0.2.648 h1:A1ggHGIE7AONOHrFaDTM8SrqgqHL6fWgWCijQ21Zy9I= github.com/a-h/templ v0.2.648/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8= +github.com/a-h/templ v0.2.663 h1:aa0WMm27InkYHGjimcM7us6hJ6BLhg98ZbfaiDPyjHE= +github.com/a-h/templ v0.2.663/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8= github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf h1:+edM69bH/X6JpYPmJYBRLanAMe1V5yRXYU3hHUovGcE= github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf/go.mod h1:FZqLhJSj2tg0ZN48GB1zvj00+ZYcHPqgsC7yzcgCq6k= github.com/aarondl/opt v0.0.0-20240108180805-338d04d857dc h1:LFVXRIKDdEZHRbhJ3PtEAbzeFh9DqizP7QzSHBUzFZw= @@ -80,6 +84,7 @@ 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.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -96,6 +101,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= 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= @@ -166,6 +173,7 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 h1:6PfEMwfInASh9hkN83aR0j4W/eKaAZt/AURtXAXlas0= github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475/go.mod h1:20nXSmcf0nAscrzqsXeC2/tA3KkV2eCiJqYuyAgl+ss= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM= 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-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -175,6 +183,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -209,6 +219,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pressly/goose/v3 v3.19.2 h1:z1yuD41jS4iaqLkyjkzGkKBz4rgyz/BYtCyMMGHlgzQ= github.com/pressly/goose/v3 v3.19.2/go.mod h1:BHkf3LzSBmO8E5FTMPupUYIpMTIh/ZuQVy+YTfhZLD4= +github.com/pressly/goose/v3 v3.20.0 h1:uPJdOxF/Ipj7ABVNOAMJXSxwFXZGwMGHNqjC8e61VA0= +github.com/pressly/goose/v3 v3.20.0/go.mod h1:BRfF2GcG4FTG12QfdBVy3q1yveaf4ckL9vWwEcIO3lA= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc= @@ -217,13 +229,19 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/riandyrn/otelchi v0.6.0 h1:kN6fkq5qLTjUuLfHpRUSKevkgr3pAMOyC4caPO9KnyY= github.com/riandyrn/otelchi v0.6.0/go.mod h1:BfwVxPKUNgJx12Z8XSrMGYT8/pTge+QNaojnhifsd4E= +github.com/riandyrn/otelchi v0.7.0 h1:Zr/WxFnQnVgfAgJuFUqKzepryVIDBq+F+Pv/rA7qL28= +github.com/riandyrn/otelchi v0.7.0/go.mod h1:ICb2XuXIInKsznOt2SJKcKnG1++LadHzVGoZHP8MXPo= github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/slog-multi v1.0.2 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ= github.com/samber/slog-multi v1.0.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= @@ -248,6 +266,8 @@ github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97 h1:XItoZNmhOih github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97/go.mod h1:bM3Vmw1IakoaXocHmMIGgJFYob0vuK+CFWiJHQvz0jQ= github.com/stephenafamo/scan v0.4.2 h1:mPmOjV84VCQdlYPPX1jZZHt5LYRHAPdEe/yuSaMi92Q= github.com/stephenafamo/scan v0.4.2/go.mod h1:FhIUJ8pLNyex36xGFiazDJJ5Xry0UkAi+RkWRrEcRMg= +github.com/stephenafamo/scan v0.5.0 h1:1zDlY1PFnLB8MErWVil/vywAoZxGIdQS/y8nDiGU+/c= +github.com/stephenafamo/scan v0.5.0/go.mod h1:FhIUJ8pLNyex36xGFiazDJJ5Xry0UkAi+RkWRrEcRMg= github.com/stephenafamo/sqlparser v0.0.0-20230326220333-c2adaf4c30e8 h1:HR6pFkWHsRRVm+VUSjltpaI56XFl2hdwxdGw7hHIPDk= github.com/stephenafamo/sqlparser v0.0.0-20230326220333-c2adaf4c30e8/go.mod h1:hOw+0TLAWETjE8UJG+q5bdj7EArbIombNBnEUoF7XwM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -265,6 +285,7 @@ github.com/teivah/broadcast v0.1.0 h1:UMs1tn8w20Xlnod+VbLbwH3dzEH2zfJy4lxdzZjQLL github.com/teivah/broadcast v0.1.0/go.mod h1:mXEgvXdYz2xUkQFARxI+jyX1MfCBwMDiGjIKSAsEq1g= github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898 h1:1MvEhzI5pvP27e9Dzz861mxk9WzXZLSJwzOU67cKTbU= github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898/go.mod h1:9bKuHS7eZh/0mJndbUOrCx8Ej3PlsRDszj4L7oVYMPQ= +github.com/tursodatabase/libsql-client-go v0.0.0-20240411070317-a1138d155304 h1:Y6cw8yjWCEJDy5Bll7HjTinkgTQU55AXiKSEe29SpgA= github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw= github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwvE8KsU= @@ -286,42 +307,68 @@ github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1/go.mod h1:udNPW8eupyH/EZocecFmaSNJ go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 h1:dT33yIHtmsqpixFsSQPwNeY5drM9wTcoL8h0FWF4oGM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 h1:Mbi5PKN7u322woPa85d7ebZ+SOvEoPvoiBu+ryHWgfA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0/go.mod h1:e7ciERRhZaOZXVjx5MiL8TK5+Xv7G5Gv5PA2ZDEJdL8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38= go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= +go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= @@ -333,15 +380,22 @@ google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUE google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -360,6 +414,7 @@ modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE= modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= @@ -368,3 +423,4 @@ mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= diff --git a/pkg/log/watermill.go b/pkg/log/watermill.go index 0e2c682..2358be4 100644 --- a/pkg/log/watermill.go +++ b/pkg/log/watermill.go @@ -16,7 +16,7 @@ func (wa *WatermillLogger) Error(msg string, err error, fields watermill.LogFiel } func (wa *WatermillLogger) Info(msg string, fields watermill.LogFields) { - New(context.Background()).With(wa.with...).With(intoAttrs(fields)...).Info(msg) + New(context.Background()).With(wa.with...).With(intoAttrs(fields)...).Debug(msg) } func (wa *WatermillLogger) Debug(msg string, fields watermill.LogFields) { diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index 7a49db5..2869082 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -39,7 +39,7 @@ func createProvider(ctx context.Context, cfg *config.Config) (*sdktrace.TracerPr sdktrace.WithSampler(sdktrace.TraceIDRatioBased(cfg.Float64("telemetry.trace.ratio"))), } - if cfg.Bool("telemetry.openobserve.trace.enable") { + if cfg.Bool("telemetry.openobserve.enable") && cfg.Bool("telemetry.openobserve.trace.enable") { url := cfg.String("telemetry.openobserve.trace.url") o2exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(url), diff --git a/rest/subreddits/create.http b/rest/subreddits/create.http new file mode 100644 index 0000000..ac8645c --- /dev/null +++ b/rest/subreddits/create.http @@ -0,0 +1,10 @@ +POST http://localhost:8080/api/v1/subreddits HTTP/1.1 +Host: localhost:8080 +Content-Length: 69 + +{ + "name": "awoo", + "enable": 1, + "schedule": "@daily", + "countback": 10 +} diff --git a/rest/subreddits/start.http b/rest/subreddits/start.http new file mode 100644 index 0000000..89b3e86 --- /dev/null +++ b/rest/subreddits/start.http @@ -0,0 +1,8 @@ +POST http://localhost:8080/api/v1/subreddits/start HTTP/1.1 +Host: localhost:8080 +Content-Type: application/json +Content-Length: 29 + +{ + "subreddit": "awoo" +} diff --git a/server/routes/device_create.go b/server/routes/device_create.go index 70000c3..9cc9f1b 100644 --- a/server/routes/device_create.go +++ b/server/routes/device_create.go @@ -48,6 +48,7 @@ func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) { WindowsWallpaperMode: omit.From(body.WindowsWallpaperMode), }) if err != nil { + log.New(ctx).Err(err).Error("failed to create device", "body", body) code, message := errs.HTTPMessage(err) rw.WriteHeader(code) _ = json.NewEncoder(rw).Encode(map[string]string{"error": message}) diff --git a/server/routes/routes.go b/server/routes/routes.go index ada2021..d3c07a1 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -36,7 +36,10 @@ func (routes *Routes) registerV1APIRoutes(router chi.Router) { router.Use(chimiddleware.RequestLogger(middleware.ChiLogger{})) router.Use(chimiddleware.SetHeader("Content-Type", "application/json")) + router.Post("/subreddits/start", routes.SubredditStartDownloadAPI) router.Get("/subreddits", routes.SubredditsListAPI) + router.Post("/subreddits", routes.SubredditsCreateAPI) + router.Get("/devices", routes.APIDeviceList) router.Post("/devices", routes.APIDeviceCreate) router.Patch("/devices/{id}", routes.APIDeviceUpdate) diff --git a/server/routes/subreddit_create.go b/server/routes/subreddit_create.go new file mode 100644 index 0000000..fcf3802 --- /dev/null +++ b/server/routes/subreddit_create.go @@ -0,0 +1,80 @@ +package routes + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/robfig/cron/v3" + "github.com/tigorlazuardi/redmage/models" + "github.com/tigorlazuardi/redmage/pkg/errs" + "github.com/tigorlazuardi/redmage/pkg/log" +) + +func (routes *Routes) SubredditsCreateAPI(rw http.ResponseWriter, req *http.Request) { + ctx, span := tracer.Start(req.Context(), "*Routes.SubredditsCreate") + defer span.End() + + var ( + body *models.Subreddit + enc = json.NewEncoder(rw) + ) + + if err := json.NewDecoder(req.Body).Decode(&body); err != nil { + rw.WriteHeader(http.StatusBadRequest) + _ = enc.Encode(map[string]string{"error": fmt.Sprintf("failed to decode json body: %s", err)}) + return + } + + if err := validateSubredditsCreate(body); err != nil { + rw.WriteHeader(http.StatusBadRequest) + _ = enc.Encode(map[string]string{"error": err.Error()}) + return + } + + // TODO: check if the subreddit actually exists on reddit + + sub, err := routes.API.SubredditsCreate(ctx, body) + if err != nil { + log.New(ctx).Err(err).Error("failed to create subreddit") + code, message := errs.HTTPMessage(err) + rw.WriteHeader(code) + _ = enc.Encode(map[string]string{"error": message}) + return + } + + rw.WriteHeader(http.StatusCreated) + if err := enc.Encode(sub); err != nil { + log.New(ctx).Err(err).Error("failed to encode subreddit into json") + } +} + +var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) + +func validateSubredditsCreate(body *models.Subreddit) error { + if body.Name == "" { + return errors.New("name is required") + } + if body.Enable > 1 { + body.Enable = 1 + } else if body.Enable < 0 { + body.Enable = 0 + } + if body.Subtype > 1 { + body.Subtype = 1 + } else if body.Subtype < 0 { + body.Subtype = 0 + } + if body.Schedule == "" { + return errors.New("schedule is required") + } + _, err := cronParser.Parse(body.Schedule) + if err != nil { + return fmt.Errorf("bad cron schedule: %w", err) + } + if body.Countback < 1 { + return errors.New("countback must be 1 or higher") + } + return nil +} diff --git a/server/routes/subreddit_download.go b/server/routes/subreddit_download.go new file mode 100644 index 0000000..4bf0b5f --- /dev/null +++ b/server/routes/subreddit_download.go @@ -0,0 +1,41 @@ +package routes + +import ( + "encoding/json" + "net/http" + + "github.com/tigorlazuardi/redmage/api" + "github.com/tigorlazuardi/redmage/pkg/errs" + "github.com/tigorlazuardi/redmage/pkg/log" +) + +func (r *Routes) SubredditStartDownloadAPI(rw http.ResponseWriter, req *http.Request) { + enc := json.NewEncoder(rw) + if r.Config.String("download.directory") == "" { + rw.WriteHeader(http.StatusBadRequest) + _ = enc.Encode(map[string]string{"error": "cannot download subreddits when download directory is not configured"}) + return + } + + ctx := req.Context() + + var body api.PubsubStartDownloadSubredditParams + + err := json.NewDecoder(req.Body).Decode(&body) + if err != nil { + rw.WriteHeader(http.StatusBadRequest) + _ = enc.Encode(map[string]string{"error": err.Error()}) + return + } + + err = r.API.PubsubStartDownloadSubreddit(ctx, body) + if err != nil { + log.New(ctx).Err(err).Error("failed to start subreddit download", "subreddit", body.Subreddit) + code, message := errs.HTTPMessage(err) + rw.WriteHeader(code) + _ = enc.Encode(map[string]string{"error": message}) + return + } + + _ = enc.Encode(map[string]string{"message": "subreddit enqueued"}) +}