From bcf7cd4431a07c7f7bda78b38abc9eab1ae295c0 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 16:24:51 -0400 Subject: [PATCH 001/341] added Dockerfile --- Dockerfile | 41 +++++++++++++++++++++++++++++++++++++++++ docker-entrypoint.sh | 1 + 2 files changed, 42 insertions(+) create mode 100644 Dockerfile create mode 100755 docker-entrypoint.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f39e9790 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +FROM golang:1.22.3-alpine as builder + +WORKDIR /workspace + +# add go modules lockfiles +COPY go.mod go.sum ./ +RUN go mod download + +# prefetch the binaries, so that they will be cached and not downloaded on each change +RUN go run github.com/steebchen/prisma-client-go prefetch + +COPY ./ ./ +# generate the Prisma Client Go client +RUN go generate ./... + +# build a fully standalone binary with zero dependencies +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o app . + + +# railway.app does not really have a way to run a migration container on the volume, so we have to improvise here +# in order to run migrations, we need to have go and prisma installed +FROM golang:1.22.3-alpine + +# set timezone +ENV TZ=Europe/Berlin + +# copy final binary +COPY --from=builder /workspace/app /app + +# copy migrations and schema +COPY --from=builder /workspace/internal/database/prisma/migrations /prisma/migrations +COPY --from=builder /workspace/internal/database/prisma/schema.prisma /prisma/schema.prisma +COPY --from=builder /workspace/docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=builder /workspace/go.mod /go.mod +COPY --from=builder /workspace/go.sum /go.sum + +# install prisma and prefetch binaries +RUN go install github.com/steebchen/prisma-client-go +RUN go run github.com/steebchen/prisma-client-go prefetch + +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 00000000..7c11860c --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1 @@ +go run github.com/steebchen/prisma-client-go migrate deploy --schema /prisma/schema.prisma && /app From 4d6cafec1664b0c383f4c030c9ab2462f5264479 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 16:46:43 -0400 Subject: [PATCH 002/341] added fly.io config and workflow --- .github/workflows/canary_fly.yml | 18 ++++++++++++++ fly.canary.toml | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .github/workflows/canary_fly.yml create mode 100644 fly.canary.toml diff --git a/.github/workflows/canary_fly.yml b/.github/workflows/canary_fly.yml new file mode 100644 index 00000000..1f0ce2ed --- /dev/null +++ b/.github/workflows/canary_fly.yml @@ -0,0 +1,18 @@ +name: Fly Deploy to Canary + +on: + push: + branches: + - canary + workflow_dispatch: {} + +jobs: + deploy: + name: Deploy app to canary + runs-on: ubuntu-latest + concurrency: deploy-group + steps: + - uses: actions/checkout@v4 + - run: flyctl deploy --remote-only -c fly.canary.toml + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_CANARY }} diff --git a/fly.canary.toml b/fly.canary.toml new file mode 100644 index 00000000..72616c2a --- /dev/null +++ b/fly.canary.toml @@ -0,0 +1,41 @@ +app = 'aftermath-canary' +primary_region = 'lhr' +kill_signal = 'SIGINT' +kill_timeout = '5s' + + +[http_service] +internal_port = 8080 +force_https = true +auto_stop_machines = false +auto_start_machines = false +processes = ['app'] + +[env] +# Database configuration +DATABASE_URL = "file:/data/db/canary.db" +# Wargaming API +WG_PRIMARY_APP_RPS = "10" +WG_REQUEST_TIMEOUT_SEC = "5" +# External services +BLITZ_STARS_API_URL = "https://www.blitzstars.com/api" +WOT_BLITZ_PUBLIC_API_URL_FMT = "https://%s.wotblitz.com/en/api" +WOT_INSPECTOR_REPLAYS_URL = 'https://api.wotinspector.com/v2/blitz/replays/' +WOT_INSPECTOR_TANK_DB_URL = "https://armor.wotinspector.com/static/armorinspector/tank_db_blitz.js" +# Discord +DISCORD_PRIMARY_GUILD_ID = "1090331056449781793" +# Misc configuration +LOG_LEVEL = "info" +NETWORK = "tcp" +PORT = "8080" + +[[mounts]] +source = 'aftermath_canary_data' +destination = '/data/db' +initial_size = '1GB' +auto_extend_size_threshold = 80 +auto_extend_size_increment = '1GB' +auto_extend_size_limit = '100GB' + +[[vm]] +size = 'shared-cpu-2x' From 3364a834e728dded9efaa464997b78a812e3f46a Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 16:47:49 -0400 Subject: [PATCH 003/341] added missing action --- .github/workflows/canary_fly.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/canary_fly.yml b/.github/workflows/canary_fly.yml index 1f0ce2ed..1676b04d 100644 --- a/.github/workflows/canary_fly.yml +++ b/.github/workflows/canary_fly.yml @@ -13,6 +13,7 @@ jobs: concurrency: deploy-group steps: - uses: actions/checkout@v4 + - uses: superfly/flyctl-actions/setup-flyctl@master - run: flyctl deploy --remote-only -c fly.canary.toml env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_CANARY }} From 598e05666ff964fce92282a1aa97bd6056a0780a Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 16:51:53 -0400 Subject: [PATCH 004/341] added env with secrets --- .github/workflows/canary_fly.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/canary_fly.yml b/.github/workflows/canary_fly.yml index 1676b04d..02b8f6d4 100644 --- a/.github/workflows/canary_fly.yml +++ b/.github/workflows/canary_fly.yml @@ -9,6 +9,7 @@ on: jobs: deploy: name: Deploy app to canary + environment: Fly Canary runs-on: ubuntu-latest concurrency: deploy-group steps: @@ -16,4 +17,4 @@ jobs: - uses: superfly/flyctl-actions/setup-flyctl@master - run: flyctl deploy --remote-only -c fly.canary.toml env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_CANARY }} + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} From e1a6d5a1ebcd512a00dc81d15dce0d6532b8aea5 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 16:54:12 -0400 Subject: [PATCH 005/341] added initial migration --- .../20240606205337_init/migration.sql | 299 ++++++++++++++++++ .../prisma/migrations/migration_lock.toml | 3 + 2 files changed, 302 insertions(+) create mode 100644 internal/database/prisma/migrations/20240606205337_init/migration.sql create mode 100644 internal/database/prisma/migrations/migration_lock.toml diff --git a/internal/database/prisma/migrations/20240606205337_init/migration.sql b/internal/database/prisma/migrations/20240606205337_init/migration.sql new file mode 100644 index 00000000..d8843022 --- /dev/null +++ b/internal/database/prisma/migrations/20240606205337_init/migration.sql @@ -0,0 +1,299 @@ +-- CreateTable +CREATE TABLE "app_configurations" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "valueEncoded" TEXT NOT NULL, + "metadataEncoded" TEXT NOT NULL DEFAULT '' +); + +-- CreateTable +CREATE TABLE "auth_nonces" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "expiresAt" DATETIME NOT NULL, + "referenceId" TEXT NOT NULL, + "metadataEncoded" TEXT NOT NULL DEFAULT '' +); + +-- CreateTable +CREATE TABLE "users" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "permissions" TEXT NOT NULL, + "featureFlagsEncoded" TEXT NOT NULL DEFAULT '' +); + +-- CreateTable +CREATE TABLE "user_subscriptions" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "expiresAt" DATETIME NOT NULL, + "referenceId" TEXT NOT NULL, + "permissions" TEXT NOT NULL, + CONSTRAINT "user_subscriptions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "user_connections" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "permissions" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "metadataEncoded" TEXT NOT NULL DEFAULT '', + CONSTRAINT "user_connections_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "user_content" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "valueEncoded" TEXT NOT NULL, + "metadataEncoded" TEXT NOT NULL DEFAULT '', + CONSTRAINT "user_content_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "accounts" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "lastBattleTime" DATETIME NOT NULL, + "accountCreatedAt" DATETIME NOT NULL, + "realm" TEXT NOT NULL, + "nickname" TEXT NOT NULL, + "private" BOOLEAN NOT NULL DEFAULT false, + "clanId" TEXT, + CONSTRAINT "accounts_clanId_fkey" FOREIGN KEY ("clanId") REFERENCES "account_clans" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "account_snapshots" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL, + "updatedAt" DATETIME NOT NULL, + "type" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "lastBattleTime" DATETIME NOT NULL, + "dataEncoded" TEXT NOT NULL +); + +-- CreateTable +CREATE TABLE "account_rating_season_snapshots" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL, + "updatedAt" DATETIME NOT NULL, + "seasonId" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "lastBattleTime" DATETIME NOT NULL, + "dataEncoded" TEXT NOT NULL +); + +-- CreateTable +CREATE TABLE "account_clans" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL, + "updatedAt" DATETIME NOT NULL, + "tag" TEXT NOT NULL, + "name" TEXT NOT NULL, + "emblemId" TEXT NOT NULL DEFAULT '', + "membersString" TEXT NOT NULL, + "recordUpdatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "glossary_averages" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "dataEncoded" TEXT NOT NULL +); + +-- CreateTable +CREATE TABLE "glossary_vehicles" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "tier" INTEGER NOT NULL, + "type" TEXT NOT NULL, + "class" TEXT NOT NULL, + "nation" TEXT NOT NULL, + "localizedNamesEncoded" TEXT NOT NULL +); + +-- CreateTable +CREATE TABLE "glossary_achievements" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "section" TEXT NOT NULL, + "dataEncoded" TEXT NOT NULL +); + +-- CreateTable +CREATE TABLE "user_interactions" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "type" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "dataEncoded" TEXT NOT NULL, + "metadataEncoded" TEXT NOT NULL DEFAULT '' +); + +-- CreateTable +CREATE TABLE "discord_live_sessions" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "locale" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "guildId" TEXT, + "channelId" TEXT NOT NULL, + "messageId" TEXT NOT NULL, + "lastUpdate" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastBattleTime" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "optionsEncoded" TEXT NOT NULL +); + +-- CreateTable +CREATE TABLE "stats_request_options" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "type" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "dataEncoded" TEXT NOT NULL +); + +-- CreateIndex +CREATE INDEX "auth_nonces_createdAt_idx" ON "auth_nonces"("createdAt"); + +-- CreateIndex +CREATE INDEX "auth_nonces_referenceId_idx" ON "auth_nonces"("referenceId"); + +-- CreateIndex +CREATE INDEX "auth_nonces_referenceId_expiresAt_idx" ON "auth_nonces"("referenceId", "expiresAt"); + +-- CreateIndex +CREATE INDEX "user_subscriptions_userId_idx" ON "user_subscriptions"("userId"); + +-- CreateIndex +CREATE INDEX "user_subscriptions_expiresAt_idx" ON "user_subscriptions"("expiresAt"); + +-- CreateIndex +CREATE INDEX "user_subscriptions_type_userId_expiresAt_idx" ON "user_subscriptions"("type", "userId", "expiresAt"); + +-- CreateIndex +CREATE INDEX "user_subscriptions_type_userId_idx" ON "user_subscriptions"("type", "userId"); + +-- CreateIndex +CREATE INDEX "user_subscriptions_referenceId_idx" ON "user_subscriptions"("referenceId"); + +-- CreateIndex +CREATE INDEX "user_subscriptions_type_referenceId_idx" ON "user_subscriptions"("type", "referenceId"); + +-- CreateIndex +CREATE INDEX "user_connections_userId_idx" ON "user_connections"("userId"); + +-- CreateIndex +CREATE INDEX "user_connections_type_userId_idx" ON "user_connections"("type", "userId"); + +-- CreateIndex +CREATE INDEX "user_connections_referenceId_idx" ON "user_connections"("referenceId"); + +-- CreateIndex +CREATE INDEX "user_connections_type_referenceId_idx" ON "user_connections"("type", "referenceId"); + +-- CreateIndex +CREATE INDEX "user_content_userId_idx" ON "user_content"("userId"); + +-- CreateIndex +CREATE INDEX "user_content_type_userId_idx" ON "user_content"("type", "userId"); + +-- CreateIndex +CREATE INDEX "user_content_referenceId_idx" ON "user_content"("referenceId"); + +-- CreateIndex +CREATE INDEX "user_content_type_referenceId_idx" ON "user_content"("type", "referenceId"); + +-- CreateIndex +CREATE INDEX "accounts_realm_idx" ON "accounts"("realm"); + +-- CreateIndex +CREATE INDEX "accounts_clanId_idx" ON "accounts"("clanId"); + +-- CreateIndex +CREATE INDEX "accounts_nickname_idx" ON "accounts"("nickname"); + +-- CreateIndex +CREATE INDEX "accounts_lastBattleTime_idx" ON "accounts"("lastBattleTime"); + +-- CreateIndex +CREATE INDEX "account_snapshots_createdAt_idx" ON "account_snapshots"("createdAt"); + +-- CreateIndex +CREATE INDEX "account_snapshots_type_accountId_idx" ON "account_snapshots"("type", "accountId"); + +-- CreateIndex +CREATE INDEX "account_snapshots_type_accountId_createdAt_idx" ON "account_snapshots"("type", "accountId", "createdAt"); + +-- CreateIndex +CREATE INDEX "account_snapshots_type_accountId_lastBattleTime_idx" ON "account_snapshots"("type", "accountId", "lastBattleTime"); + +-- CreateIndex +CREATE INDEX "account_snapshots_type_referenceId_idx" ON "account_snapshots"("type", "referenceId"); + +-- CreateIndex +CREATE INDEX "account_snapshots_type_referenceId_createdAt_idx" ON "account_snapshots"("type", "referenceId", "createdAt"); + +-- CreateIndex +CREATE INDEX "account_rating_season_snapshots_createdAt_idx" ON "account_rating_season_snapshots"("createdAt"); + +-- CreateIndex +CREATE INDEX "account_rating_season_snapshots_seasonId_accountId_idx" ON "account_rating_season_snapshots"("seasonId", "accountId"); + +-- CreateIndex +CREATE INDEX "account_rating_season_snapshots_seasonId_referenceId_idx" ON "account_rating_season_snapshots"("seasonId", "referenceId"); + +-- CreateIndex +CREATE INDEX "account_clans_tag_idx" ON "account_clans"("tag"); + +-- CreateIndex +CREATE INDEX "user_interactions_type_idx" ON "user_interactions"("type"); + +-- CreateIndex +CREATE INDEX "user_interactions_userId_idx" ON "user_interactions"("userId"); + +-- CreateIndex +CREATE INDEX "user_interactions_userId_type_idx" ON "user_interactions"("userId", "type"); + +-- CreateIndex +CREATE INDEX "user_interactions_referenceId_idx" ON "user_interactions"("referenceId"); + +-- CreateIndex +CREATE INDEX "user_interactions_referenceId_type_idx" ON "user_interactions"("referenceId", "type"); + +-- CreateIndex +CREATE INDEX "discord_live_sessions_referenceId_idx" ON "discord_live_sessions"("referenceId"); + +-- CreateIndex +CREATE INDEX "stats_request_options_referenceId_idx" ON "stats_request_options"("referenceId"); diff --git a/internal/database/prisma/migrations/migration_lock.toml b/internal/database/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..e5e5c470 --- /dev/null +++ b/internal/database/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" \ No newline at end of file From 5b3464a6576dce538b5d1b8a118d7823196e89ac Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 16:57:35 -0400 Subject: [PATCH 006/341] added header --- docker-entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 7c11860c..6f30e8bf 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1 +1,2 @@ +#!/bin/bash go run github.com/steebchen/prisma-client-go migrate deploy --schema /prisma/schema.prisma && /app From acb30bebe3a8221dc3744104a5aa02bac2fc09ac Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 17:03:07 -0400 Subject: [PATCH 007/341] updated dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f39e9790..993f4f5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ COPY ./ ./ RUN go generate ./... # build a fully standalone binary with zero dependencies -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o app . +RUN CGO_ENABLED=0 GOOS=linux go build -o app . # railway.app does not really have a way to run a migration container on the volume, so we have to improvise here @@ -37,5 +37,6 @@ COPY --from=builder /workspace/go.sum /go.sum # install prisma and prefetch binaries RUN go install github.com/steebchen/prisma-client-go RUN go run github.com/steebchen/prisma-client-go prefetch +RUN chmod +x /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] From 9ef67b94ac538a6e1e941f853229a045a12e61b4 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 17:21:43 -0400 Subject: [PATCH 008/341] changed shell, added timeout --- .github/workflows/canary_fly.yml | 3 ++- docker-entrypoint.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/canary_fly.yml b/.github/workflows/canary_fly.yml index 02b8f6d4..41ce70af 100644 --- a/.github/workflows/canary_fly.yml +++ b/.github/workflows/canary_fly.yml @@ -9,9 +9,10 @@ on: jobs: deploy: name: Deploy app to canary - environment: Fly Canary runs-on: ubuntu-latest + environment: Fly Canary concurrency: deploy-group + timeout-minutes: 3 steps: - uses: actions/checkout@v4 - uses: superfly/flyctl-actions/setup-flyctl@master diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 6f30e8bf..e03049b6 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,2 +1,2 @@ -#!/bin/bash +#!/bin/sh go run github.com/steebchen/prisma-client-go migrate deploy --schema /prisma/schema.prisma && /app From f7bd45139c39f43e92f72a9f4d8bfc36d527dc99 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 17:25:48 -0400 Subject: [PATCH 009/341] pulling port from env --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 842f96af..054869a1 100644 --- a/main.go +++ b/main.go @@ -47,7 +47,7 @@ func main() { } http.Handle("/discord/callback", discordHandler) - panic(http.ListenAndServe(":9092", nil)) + panic(http.ListenAndServe(":"+os.Getenv("PORT"), nil)) // test // localePrinter := func(s string) string { return "localized:" + s } From 38e09a559a3f72b6300b24579fa383c8e7955693 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 17:26:18 -0400 Subject: [PATCH 010/341] cleaned up main --- main.go | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/main.go b/main.go index 054869a1..c3312e0b 100644 --- a/main.go +++ b/main.go @@ -48,34 +48,6 @@ func main() { http.Handle("/discord/callback", discordHandler) panic(http.ListenAndServe(":"+os.Getenv("PORT"), nil)) - - // test - // localePrinter := func(s string) string { return "localized:" + s } - - // renderer := stats.NewRenderer(statsClient, language.English) - - // var days time.Duration = 60 - // img, meta, err := renderer.Period(context.Background(), "579553160", time.Now().Add(time.Hour*24*days*-1)) - // if err != nil { - // panic(err) - // } - - // fmt.Printf("rendered in %v\n", meta.TotalTime()) - - // bgImage, _ := assets.GetLoadedImage("bg-light") - // finalImage := render.AddBackground(img, bgImage, render.Style{Blur: 10, BorderRadius: 30}) - - // f, err := os.Create("tmp/test-image.png") - // if err != nil { - // panic(err) - // } - // defer f.Close() - - // err = png.Encode(f, finalImage) - // if err != nil { - // panic(err) - // } - } func loadStaticAssets(static fs.FS) { From 365684347bca20c73f789b26f0f0938918df1aa4 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 19:40:47 -0400 Subject: [PATCH 011/341] averages --- cmds/core/client.go | 31 +++++++--- cmds/core/scheduler/cron.go | 44 +++++++++++++ cmds/core/scheduler/glossary.go | 48 +++++++++++++++ cmds/core/scheduler/workers.go | 68 +++++++++++++++++++++ cmds/discord/commands/link.go | 4 +- cmds/discord/commands/stats.go | 4 +- cmds/discord/common/context.go | 2 +- go.mod | 4 ++ go.sum | 33 +++++++++- internal/database/client.go | 6 +- internal/database/external.go | 33 ++++++++-- internal/database/prisma/schema.prisma | 2 +- internal/database/users.go | 8 +-- internal/external/blitzstars/averages.go | 78 ++++++++++++++++++++++++ internal/external/blitzstars/client.go | 7 ++- internal/stats/fetch/client.go | 36 ++++++++++- internal/stats/fetch/multisource.go | 64 +++++++++++++++++-- internal/stats/frame/frame.go | 27 ++++---- internal/stats/frame/value.go | 2 +- internal/stats/period.go | 2 +- internal/stats/prepare/common/card.go | 16 ++--- internal/stats/prepare/period/preset.go | 3 +- main.go | 52 ++++++++-------- 23 files changed, 488 insertions(+), 86 deletions(-) create mode 100644 cmds/core/scheduler/cron.go create mode 100644 cmds/core/scheduler/glossary.go create mode 100644 cmds/core/scheduler/workers.go create mode 100644 internal/external/blitzstars/averages.go diff --git a/cmds/core/client.go b/cmds/core/client.go index a5512bc4..cd1e9381 100644 --- a/cmds/core/client.go +++ b/cmds/core/client.go @@ -7,15 +7,32 @@ import ( "golang.org/x/text/language" ) -type Client struct { - Fetch fetch.Client - DB database.Client +var _ Client = &client{} + +type Client interface { + Render(locale language.Tag) stats.Renderer + + Database() database.Client + Fetch() fetch.Client +} + +type client struct { + fetch fetch.Client + db database.Client +} + +func (c *client) Database() database.Client { + return c.db +} + +func (c *client) Fetch() fetch.Client { + return c.fetch } -func (c *Client) Render(locale language.Tag) stats.Renderer { - return stats.NewRenderer(c.Fetch, locale) +func (c *client) Render(locale language.Tag) stats.Renderer { + return stats.NewRenderer(c.fetch, locale) } -func NewClient(fetch fetch.Client, database database.Client) Client { - return Client{Fetch: fetch, DB: database} +func NewClient(fetch fetch.Client, database database.Client) *client { + return &client{fetch: fetch, db: database} } diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go new file mode 100644 index 00000000..23ea3de7 --- /dev/null +++ b/cmds/core/scheduler/cron.go @@ -0,0 +1,44 @@ +package scheduler + +import ( + "time" + + "github.com/cufee/aftermath/cmds/core" + "github.com/go-co-op/gocron" + "github.com/rs/zerolog/log" +) + +func StartCronJobs(client core.Client) { + log.Info().Msg("starting cron jobs") + + c := gocron.NewScheduler(time.UTC) + // Tasks + c.Cron("* * * * *").Do(runTasksWorker(client)) + c.Cron("0 * * * *").Do(restartTasksWorker(client)) + + // Glossary - Do it around the same time WG releases game updates + c.Cron("0 10 * * *").Do(UpdateGlossaryWorker(client)) + c.Cron("0 12 * * *").Do(UpdateGlossaryWorker(client)) + // c.AddFunc("40 9 * * 0", updateAchievementsWorker) + + // Averages - Update averages shortly after session refreshes + c.Cron("0 10 * * *").Do(UpdateAveragesWorker(client)) + c.Cron("0 2 * * *").Do(UpdateAveragesWorker(client)) + c.Cron("0 19 * * *").Do(UpdateAveragesWorker(client)) + + // Sessions + c.Cron("0 9 * * *").Do(createSessionTasksWorker(client, "NA")) // NA + c.Cron("0 1 * * *").Do(createSessionTasksWorker(client, "EU")) // EU + c.Cron("0 18 * * *").Do(createSessionTasksWorker(client, "AS")) // Asia + + // Refresh WN8 + // "45 9 * * *" // NA + // "45 1 * * *" // EU + // "45 18 * * *" // Asia + + // Configurations + c.Cron("0 0 */7 * *").Do(rotateBackgroundPresetsWorker(client)) + + // Start the Cron job scheduler + c.StartAsync() +} diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go new file mode 100644 index 00000000..a81cd930 --- /dev/null +++ b/cmds/core/scheduler/glossary.go @@ -0,0 +1,48 @@ +package scheduler + +import ( + "context" + "time" + + "github.com/cufee/aftermath/cmds/core" + "github.com/rs/zerolog/log" +) + +// CurrentTankAverages + +func UpdateAveragesWorker(client core.Client) func() { + return func() { + log.Info().Msg("updating tank averages cache") + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) + defer cancel() + + // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails + averages, err := client.Fetch().CurrentTankAverages(ctx) + if err != nil { + log.Err(err).Msg("failed to update averages cache") + return + } + + err = client.Database().UpsertVehicleAverages(ctx, averages) + if err != nil { + log.Err(err).Msg("failed to update averages cache") + return + } + + log.Info().Msg("averages cache updated") + } +} + +func UpdateGlossaryWorker(client core.Client) func() { + return func() { + // // We just run the logic directly as it's not a heavy task and it doesn't matter if it fails due to the app failing + // log.Info().Msg("updating glossary cache") + // err := cache.UpdateGlossaryCache() + // if err != nil { + // log.Err(err).Msg("failed to update glossary cache") + // } else { + // log.Info().Msg("glossary cache updated") + // } + } +} diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go new file mode 100644 index 00000000..d5037340 --- /dev/null +++ b/cmds/core/scheduler/workers.go @@ -0,0 +1,68 @@ +package scheduler + +import ( + "github.com/cufee/aftermath/cmds/core" +) + +func rotateBackgroundPresetsWorker(client core.Client) func() { + return func() { + // // We just run the logic directly as it's not a heavy task and it doesn't matter if it fails due to the app failing + // log.Info().Msg("rotating background presets") + // images, err := content.PickRandomBackgroundImages(3) + // if err != nil { + // log.Err(err).Msg("failed to pick random background images") + // return + // } + // err = database.UpdateAppConfiguration[[]string]("backgroundImagesSelection", images, nil, true) + // if err != nil { + // log.Err(err).Msg("failed to update background images selection") + // } + } +} + +func createSessionTasksWorker(client core.Client, realm string) func() { + return func() { + // err := tasks.CreateSessionUpdateTasks(realm) + // if err != nil { + // log.Err(err).Msg("failed to create session update tasks") + // } + } +} + +func runTasksWorker(client core.Client) func() { + return func() { + // if tasks.DefaultQueue.ActiveWorkers() > 0 { + // return + // } + + // activeTasks, err := tasks.StartScheduledTasks(nil, 50) + // if err != nil { + // log.Err(err).Msg("failed to start scheduled tasks") + // return + // } + // if len(activeTasks) == 0 { + // return + // } + + // tasks.DefaultQueue.Process(func(err error) { + // if err != nil { + // log.Err(err).Msg("failed to process tasks") + // return + // } + + // // If the queue is now empty, we can run the next batch of tasks right away + // runTasksWorker() + + // }, activeTasks...) + } +} + +func restartTasksWorker(client core.Client) func() { + return func() { + // _, err := tasks.RestartAbandonedTasks(nil) + // if err != nil { + // log.Err(err).Msg("failed to start scheduled tasks") + // return + // } + } +} diff --git a/cmds/discord/commands/link.go b/cmds/discord/commands/link.go index 7d735039..0eb0c5d2 100644 --- a/cmds/discord/commands/link.go +++ b/cmds/discord/commands/link.go @@ -41,7 +41,7 @@ func init() { return ctx.Reply(message) } - account, err := ctx.Core.Fetch.Search(ctx.Context, options.Nickname, options.Server) + account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { return ctx.ReplyFmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)) @@ -59,7 +59,7 @@ func init() { currentConnection.Metadata["verified"] = false currentConnection.ReferenceID = fmt.Sprint(account.ID) - _, err = ctx.Core.DB.UpsertConnection(ctx.Context, currentConnection) + _, err = ctx.Core.Database().UpsertConnection(ctx.Context, currentConnection) if err != nil { return ctx.Err(err) } diff --git a/cmds/discord/commands/stats.go b/cmds/discord/commands/stats.go index 94b5f42d..9af35d25 100644 --- a/cmds/discord/commands/stats.go +++ b/cmds/discord/commands/stats.go @@ -30,7 +30,7 @@ func init() { switch { case options.UserID != "": // mentioned another user, check if the user has an account linked - mentionedUser, _ := ctx.Core.DB.GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithContent()) + mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithContent()) defaultAccount, hasDefaultAccount := mentionedUser.Connection(database.ConnectionTypeWargaming) if !hasDefaultAccount { return ctx.Reply("stats_error_connection_not_found_vague") @@ -40,7 +40,7 @@ func init() { case options.Nickname != "" && options.Server != "": // nickname provided and server selected - lookup the account - account, err := ctx.Core.Fetch.Search(ctx.Context, options.Nickname, options.Server) + account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { return ctx.ReplyFmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)) diff --git a/cmds/discord/common/context.go b/cmds/discord/common/context.go index bdfa25fd..96018022 100644 --- a/cmds/discord/common/context.go +++ b/cmds/discord/common/context.go @@ -54,7 +54,7 @@ func NewContext(ctx context.Context, interaction discordgo.Interaction, respondC return nil, errors.New("failed to get a valid discord user id") } - user, err := client.DB.GetOrCreateUserByID(ctx, c.Member.ID, database.WithConnections(), database.WithSubscriptions()) + user, err := client.Database().GetOrCreateUserByID(ctx, c.Member.ID, database.WithConnections(), database.WithSubscriptions()) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index bdc7e794..e4c1c034 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/cufee/am-wg-proxy-next/v2 v2.1.2 github.com/disintegration/imaging v1.6.2 github.com/fogleman/gg v1.3.0 + github.com/go-co-op/gocron v1.37.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/joho/godotenv v1.5.1 github.com/rs/zerolog v1.33.0 @@ -22,11 +23,14 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.19.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect diff --git a/go.sum b/go.sum index 1a4a8bcb..fa0e39ca 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cufee/am-wg-proxy-next/v2 v2.1.2 h1:j45xbLPs5FqSsfEccls8AWXK1DIWjnIFEKoiB7M/TGY= github.com/cufee/am-wg-proxy-next/v2 v2.1.2/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -10,24 +11,43 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1 github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= +github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +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/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -36,7 +56,12 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D github.com/steebchen/prisma-client-go v0.37.0 h1:CYfRxUnIsJRlCvPM4Yw2fElB7Y9rC4f2/PPmHliqyTc= github.com/steebchen/prisma-client-go v0.37.0/go.mod h1:wp2xU9HO5WIefc65vcl1HOiFUzaHKyOhHw5atrzs8hc= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= @@ -48,6 +73,8 @@ go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRL go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= @@ -73,7 +100,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -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/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +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= diff --git a/internal/database/client.go b/internal/database/client.go index 575b6fd6..d4749a3f 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -8,8 +8,8 @@ import ( ) type Client interface { - SetVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) + UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error GetUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) @@ -20,7 +20,7 @@ type Client interface { // var _ Client = &client{} // just a marker to see if it is implemented correctly type client struct { - prisma *db.PrismaClient + Raw *db.PrismaClient } func NewClient() (*client, error) { @@ -30,5 +30,5 @@ func NewClient() (*client, error) { return nil, err } - return &client{prisma: prisma}, nil + return &client{Raw: prisma}, nil } diff --git a/internal/database/external.go b/internal/database/external.go index f644c3f3..87117a4a 100644 --- a/internal/database/external.go +++ b/internal/database/external.go @@ -8,6 +8,7 @@ import ( "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/stats/frame" "github.com/rs/zerolog/log" + "github.com/steebchen/prisma-client-go/runtime/transaction" ) type GlossaryVehicle struct { @@ -18,12 +19,32 @@ func (v GlossaryVehicle) Name(printer localization.Printer) string { return printer("name") } -func (c *client) SetVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { +func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { if len(averages) < 1 { return nil } - return nil + var transactions []transaction.Transaction + for id, data := range averages { + encoded, err := data.Encode() + if err != nil { + log.Err(err).Str("id", id).Msg("failed to encode a stats frame for vehicle averages") + continue + } + + transactions = append(transactions, c.Raw.VehicleAverage. + UpsertOne(db.VehicleAverage.ID.Equals(id)). + Create( + db.VehicleAverage.ID.Set(id), + db.VehicleAverage.DataEncoded.Set(encoded), + ). + Update( + db.VehicleAverage.DataEncoded.Set(encoded), + ).Tx(), + ) + } + + return c.Raw.Prisma.Transaction(transactions...).Exec(ctx) } func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { @@ -33,7 +54,7 @@ func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[stri qCtx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() - records, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(ids)).Exec(qCtx) + records, err := c.Raw.VehicleAverage.FindMany(db.VehicleAverage.ID.In(ids)).Exec(qCtx) if err != nil { return nil, err } @@ -44,9 +65,9 @@ func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[stri for _, record := range records { parsed, err := frame.DecodeStatsFrame(record.DataEncoded) - if err != nil && !db.IsErrNotFound(err) { + lastErr = err + if err != nil { badRecords = append(badRecords, record.ID) - lastErr = err continue } averages[record.ID] = parsed @@ -61,7 +82,7 @@ func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[stri // we can just delete the record and move on ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(badRecords)).Delete().Exec(ctx) + _, err := c.Raw.VehicleAverage.FindMany(db.VehicleAverage.ID.In(badRecords)).Delete().Exec(ctx) if err != nil { log.Err(err).Strs("ids", badRecords).Msg("failed to delete a bad vehicle average records") } diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index 1b54ac15..f4a0c95d 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -209,7 +209,7 @@ model Clan { } model VehicleAverage { - id String @id @default(uuid()) + id String @id createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/internal/database/users.go b/internal/database/users.go index c9458a51..eee8e046 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -176,7 +176,7 @@ func (c *client) GetOrCreateUserByID(ctx context.Context, id string, opts ...use user, err := c.GetUserByID(ctx, id, opts...) if err != nil { if db.IsErrNotFound(err) { - model, err := c.prisma.User.CreateOne(db.User.ID.Set(id), db.User.Permissions.Set(permissions.User.Encode())).Exec(ctx) + model, err := c.Raw.User.CreateOne(db.User.ID.Set(id), db.User.Permissions.Set(permissions.User.Encode())).Exec(ctx) if err != nil { return User{}, err } @@ -210,7 +210,7 @@ func (c *client) GetUserByID(ctx context.Context, id string, opts ...userGetOpti fields = append(fields, db.User.Content.Fetch()) } - model, err := c.prisma.User.FindUnique(db.User.ID.Equals(id)).With(fields...).Exec(ctx) + model, err := c.Raw.User.FindUnique(db.User.ID.Equals(id)).With(fields...).Exec(ctx) if err != nil { return User{}, err } @@ -238,7 +238,7 @@ func (c *client) UpdateConnection(ctx context.Context, connection UserConnection return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) } - model, err := c.prisma.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( + model, err := c.Raw.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( db.UserConnection.ReferenceID.Set(connection.ReferenceID), db.UserConnection.Permissions.Set(connection.Permissions.Encode()), db.UserConnection.MetadataEncoded.Set(string(encoded)), @@ -269,7 +269,7 @@ func (c *client) UpsertConnection(ctx context.Context, connection UserConnection return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) } - model, err := c.prisma.UserConnection.CreateOne( + model, err := c.Raw.UserConnection.CreateOne( db.UserConnection.User.Link(db.User.ID.Equals(connection.UserID)), db.UserConnection.Type.Set(string(connection.Type)), db.UserConnection.Permissions.Set(connection.Permissions.Encode()), diff --git a/internal/external/blitzstars/averages.go b/internal/external/blitzstars/averages.go new file mode 100644 index 00000000..b7ddddb4 --- /dev/null +++ b/internal/external/blitzstars/averages.go @@ -0,0 +1,78 @@ +package blitzstars + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/cufee/aftermath/internal/stats/frame" +) + +// Response from https://www.blitzstars.com/ average stats endpoint +type VehicleAverages struct { + TankID int `json:"tank_id"` + Players int `json:"number_of_players"` + All struct { + AvgBattles float32 `json:"battles,omitempty"` + AvgDroppedCapturePoints float32 `json:"dropped_capture_points,omitempty"` + } `json:",omitempty"` + Special struct { + Winrate float32 `json:"winrate,omitempty"` + DamageRatio float32 `json:"damageRatio,omitempty"` + Kdr float32 `json:"kdr,omitempty"` + DamagePerBattle float32 `json:"damagePerBattle,omitempty"` + KillsPerBattle float32 `json:"killsPerBattle,omitempty"` + HitsPerBattle float32 `json:"hitsPerBattle,omitempty"` + SpotsPerBattle float32 `json:"spotsPerBattle,omitempty"` + Wpm float32 `json:"wpm,omitempty"` + Dpm float32 `json:"dpm,omitempty"` + Kpm float32 `json:"kpm,omitempty"` + HitRate float32 `json:"hitRate,omitempty"` + SurvivalRate float32 `json:"survivalRate,omitempty"` + } `json:"special,omitempty"` +} + +func (c *client) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) { + req, err := http.NewRequest("GET", c.apiURL+"/tankaverages.json", nil) + if err != nil { + return nil, err + } + + res, err := c.http.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("bad status code: %d", res.StatusCode) + } + + var averages []VehicleAverages + err = json.NewDecoder(res.Body).Decode(&averages) + if err != nil { + return nil, err + } + + averagesMap := make(map[string]frame.StatsFrame) + for _, average := range averages { + battles := average.All.AvgBattles * float32(average.Players) + + averagesMap[fmt.Sprint(average.TankID)] = frame.StatsFrame{ + Battles: frame.ValueInt(battles), + BattlesWon: frame.ValueInt(average.Special.Winrate * battles / 100), + DamageDealt: frame.ValueInt(average.Special.DamagePerBattle * battles), + + ShotsHit: frame.ValueInt(average.Special.HitsPerBattle * battles), + ShotsFired: frame.ValueInt((average.Special.HitsPerBattle * battles) / (average.Special.HitRate / 100)), + + Frags: frame.ValueInt(average.Special.KillsPerBattle * battles), + EnemiesSpotted: frame.ValueInt(average.Special.SpotsPerBattle * battles), + DroppedCapturePoints: frame.ValueInt(average.All.AvgDroppedCapturePoints * battles), + } + + } + + return averagesMap, nil +} diff --git a/internal/external/blitzstars/client.go b/internal/external/blitzstars/client.go index 5c49b4e6..2015050e 100644 --- a/internal/external/blitzstars/client.go +++ b/internal/external/blitzstars/client.go @@ -4,9 +4,12 @@ import ( "context" "net/http" "time" + + "github.com/cufee/aftermath/internal/stats/frame" ) type Client interface { + CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) AccountTankHistories(ctx context.Context, accountId string) (map[int][]TankHistoryEntry, error) } @@ -18,10 +21,10 @@ type client struct { requestTimeout time.Duration } -func NewClient(apiURL string, requestTimeout time.Duration) *client { +func NewClient(apiURL string, requestTimeout time.Duration) (*client, error) { return &client{ apiURL: apiURL, requestTimeout: requestTimeout, http: http.Client{Timeout: requestTimeout}, - } + }, nil } diff --git a/internal/stats/fetch/client.go b/internal/stats/fetch/client.go index 18c82a8f..8c273edb 100644 --- a/internal/stats/fetch/client.go +++ b/internal/stats/fetch/client.go @@ -23,6 +23,24 @@ type AccountStatsOverPeriod struct { LastBattleTime time.Time `json:"lastBattleTime"` } +func (stats *AccountStatsOverPeriod) AddWN8(averages map[string]frame.StatsFrame) { + var weightedTotal, battlesTotal float32 + for id, data := range stats.RegularBattles.Vehicles { + tankAverages, ok := averages[id] + if !ok || data.Battles < 1 { + continue + } + weightedTotal += data.Battles.Float() * data.WN8(tankAverages).Float() + battlesTotal += data.Battles.Float() + } + if battlesTotal < 1 { + return + } + + wn8 := int(weightedTotal) / int(battlesTotal) + stats.RegularBattles.SetWN8(wn8) +} + type StatsWithVehicles struct { frame.StatsFrame Vehicles map[string]frame.VehicleStatsFrame @@ -30,6 +48,20 @@ type StatsWithVehicles struct { type Client interface { Search(ctx context.Context, nickname, realm string) (types.Account, error) - CurrentStats(ctx context.Context, id string) (AccountStatsOverPeriod, error) - PeriodStats(ctx context.Context, id string, from time.Time) (AccountStatsOverPeriod, error) + CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) + PeriodStats(ctx context.Context, id string, from time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) + + CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) +} + +type statsOptions struct { + withWN8 bool +} + +type statsOption func(*statsOptions) + +func WithWN8() statsOption { + return func(so *statsOptions) { + so.withWN8 = true + } } diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 7a48dcf1..134c933f 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -3,12 +3,14 @@ package fetch import ( "context" "errors" + "fmt" "sync" "time" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" + "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/am-wg-proxy-next/v2/types" ) @@ -51,7 +53,12 @@ Get live account stats from wargaming - Each request will be retried c.retriesPerRequest times - This function will assume the player ID is valid and optimistically run all request concurrently, returning the first error once all request finish */ -func (c *multiSourceClient) CurrentStats(ctx context.Context, id string) (AccountStatsOverPeriod, error) { +func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) { + var options statsOptions + for _, apply := range opts { + apply(&options) + } + realm := c.wargaming.RealmFromAccountID(id) var group sync.WaitGroup @@ -60,6 +67,7 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string) (Accoun var clan types.ClanMember var account DataWithErr[types.ExtendedAccount] var vehicles DataWithErr[[]types.VehicleStatsFrame] + var averages DataWithErr[map[string]frame.StatsFrame] go func() { defer group.Done() @@ -79,6 +87,17 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string) (Accoun vehicles = withRetry(func() ([]types.VehicleStatsFrame, error) { return c.wargaming.AccountVehicles(ctx, realm, id) }, c.retriesPerRequest, c.retrySleepInterval) + + if vehicles.Err != nil || len(vehicles.Data) < 1 || !options.withWN8 { + return + } + + var ids []string + for _, v := range vehicles.Data { + ids = append(ids, fmt.Sprint(v.TankID)) + } + a, err := c.database.GetVehicleAverages(ctx, ids) + averages = DataWithErr[map[string]frame.StatsFrame]{a, err} }() group.Wait() @@ -90,11 +109,22 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string) (Accoun return AccountStatsOverPeriod{}, vehicles.Err } - return wargamingToStats(realm, account.Data, clan, vehicles.Data), nil + stats := wargamingToStats(realm, account.Data, clan, vehicles.Data) + if options.withWN8 { + stats.AddWN8(averages.Data) + } + + return stats, nil } -func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodStart time.Time) (AccountStatsOverPeriod, error) { +func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) { + var options statsOptions + for _, apply := range opts { + apply(&options) + } + var histories DataWithErr[map[int][]blitzstars.TankHistoryEntry] + var averages DataWithErr[map[string]frame.StatsFrame] var current DataWithErr[AccountStatsOverPeriod] var group sync.WaitGroup @@ -104,6 +134,17 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt stats, err := c.CurrentStats(ctx, id) current = DataWithErr[AccountStatsOverPeriod]{stats, err} + + if err != nil || stats.RegularBattles.Battles < 1 || !options.withWN8 { + return + } + + var ids []string + for id := range stats.RegularBattles.Vehicles { + ids = append(ids, id) + } + a, err := c.database.GetVehicleAverages(ctx, ids) + averages = DataWithErr[map[string]frame.StatsFrame]{a, err} }() // TODO: lookup a session from the database first @@ -116,7 +157,11 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt return AccountStatsOverPeriod{}, current.Err } - return current.Data, nil + stats := current.Data + if options.withWN8 { + stats.AddWN8(averages.Data) + } + return stats, nil } group.Add(1) @@ -139,5 +184,14 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt current.Data.PeriodStart = periodStart current.Data.RatingBattles = StatsWithVehicles{} // blitzstars do not provide rating battles stats current.Data.RegularBattles = blitzstarsToStats(current.Data.RegularBattles.Vehicles, histories.Data, periodStart) - return current.Data, nil + + stats := current.Data + if options.withWN8 { + stats.AddWN8(averages.Data) + } + return stats, nil +} + +func (c *multiSourceClient) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) { + return c.blitzstars.CurrentTankAverages(ctx) } diff --git a/internal/stats/frame/frame.go b/internal/stats/frame/frame.go index 2a64c61c..570f97bd 100644 --- a/internal/stats/frame/frame.go +++ b/internal/stats/frame/frame.go @@ -1,10 +1,9 @@ package frame import ( + "encoding/json" "math" "time" - - "go.dedis.ch/protobuf" ) /* @@ -42,7 +41,7 @@ type StatsFrame struct { func DecodeStatsFrame(encoded string) (StatsFrame, error) { var data StatsFrame - err := protobuf.Decode([]byte(encoded), &data) + err := json.Unmarshal([]byte(encoded), &data) if err != nil { return StatsFrame{}, err } @@ -53,7 +52,7 @@ func DecodeStatsFrame(encoded string) (StatsFrame, error) { Encode StatsFrame to string */ func (r *StatsFrame) Encode() (string, error) { - data, err := protobuf.Encode(r) + data, err := json.Marshal(r) if err != nil { return "", err } @@ -63,7 +62,7 @@ func (r *StatsFrame) Encode() (string, error) { /* Calculate and return average damage per battle */ -func (r *StatsFrame) AvgDamage() Value { +func (r *StatsFrame) AvgDamage(_ ...any) Value { if r.Battles == 0 { return InvalidValue } @@ -76,7 +75,7 @@ func (r *StatsFrame) AvgDamage() Value { /* Calculate and return damage dealt vs damage received ration */ -func (r *StatsFrame) DamageRatio() Value { +func (r *StatsFrame) DamageRatio(_ ...any) Value { if r.Battles == 0 || r.DamageReceived == 0 { return InvalidValue } @@ -89,7 +88,7 @@ func (r *StatsFrame) DamageRatio() Value { /* Calculate and return survival battles vs total battles ratio */ -func (r *StatsFrame) SurvivalRatio() Value { +func (r *StatsFrame) SurvivalRatio(_ ...any) Value { if r.Battles == 0 { return InvalidValue } @@ -102,7 +101,7 @@ func (r *StatsFrame) SurvivalRatio() Value { /* Calculate and return survival percentage */ -func (r *StatsFrame) Survival() Value { +func (r *StatsFrame) Survival(_ ...any) Value { if r.Battles == 0 { return InvalidValue } @@ -115,7 +114,7 @@ func (r *StatsFrame) Survival() Value { /* Calculate and return win percentage */ -func (r *StatsFrame) WinRate() Value { +func (r *StatsFrame) WinRate(_ ...any) Value { if r.Battles == 0 { return InvalidValue } @@ -128,7 +127,7 @@ func (r *StatsFrame) WinRate() Value { /* Calculate and return accuracy */ -func (r *StatsFrame) Accuracy() Value { +func (r *StatsFrame) Accuracy(_ ...any) Value { if r.Battles == 0 || r.ShotsFired == 0 { return InvalidValue } @@ -149,17 +148,17 @@ func (r *StatsFrame) SetWN8(wn8 int) { Calculate WN8 Rating for a tank using the following formula: (980*rDAMAGEc + 210*rDAMAGEc*rFRAGc + 155*rFRAGc*rSPOTc + 75*rDEFc*rFRAGc + 145*MIN(1.8,rWINc))/EXPc */ -func (r *StatsFrame) WN8(vehicleAverages ...StatsFrame) Value { +func (r *StatsFrame) WN8(averages ...any) Value { if r.wn8 > 0 { return r.wn8 } - if r.Battles == 0 || len(vehicleAverages) < 1 { + if r.Battles == 0 || len(averages) < 1 { return InvalidValue } - average := vehicleAverages[0] - if average.Battles == 0 { + average, ok := averages[0].(StatsFrame) + if !ok || average.Battles == 0 { return InvalidValue } diff --git a/internal/stats/frame/value.go b/internal/stats/frame/value.go index 86a7b9ee..6b15afbe 100644 --- a/internal/stats/frame/value.go +++ b/internal/stats/frame/value.go @@ -2,7 +2,7 @@ package frame import "fmt" -type ValueInt uint32 +type ValueInt int func (value ValueInt) String() string { return fmt.Sprintf("%d", value) diff --git a/internal/stats/period.go b/internal/stats/period.go index c36e3b99..3e61b421 100644 --- a/internal/stats/period.go +++ b/internal/stats/period.go @@ -26,7 +26,7 @@ func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, } stop := meta.Timer("fetchClient#PeriodStats") - stats, err := r.fetchClient.PeriodStats(ctx, accountId, from) + stats, err := r.fetchClient.PeriodStats(ctx, accountId, from, fetch.WithWN8()) stop() if err != nil { return nil, meta, err diff --git a/internal/stats/prepare/common/card.go b/internal/stats/prepare/common/card.go index f3af6977..56e370ce 100644 --- a/internal/stats/prepare/common/card.go +++ b/internal/stats/prepare/common/card.go @@ -38,28 +38,28 @@ func (block *StatsBlock[D]) Localize(printer func(string) string) { block.Label = printer(block.Label) } -func (block *StatsBlock[D]) FillValue(stats frame.StatsFrame) error { +func (block *StatsBlock[D]) FillValue(stats frame.StatsFrame, args ...any) error { switch block.Tag { case TagWN8: - block.Value = stats.WN8() + block.Value = stats.WN8(args...) case TagFrags: block.Value = stats.Frags case TagBattles: block.Value = stats.Battles case TagWinrate: - block.Value = stats.WinRate() + block.Value = stats.WinRate(args...) case TagAccuracy: - block.Value = stats.Accuracy() + block.Value = stats.Accuracy(args...) case TagRankedRating: block.Value = stats.Rating case TagAvgDamage: - block.Value = stats.AvgDamage() + block.Value = stats.AvgDamage(args...) case TagDamageRatio: - block.Value = stats.DamageRatio() + block.Value = stats.DamageRatio(args...) case TagSurvivalRatio: - block.Value = stats.SurvivalRatio() + block.Value = stats.SurvivalRatio(args...) case TagSurvivalPercent: - block.Value = stats.Survival() + block.Value = stats.Survival(args...) case TagDamageDealt: block.Value = stats.DamageDealt case TagDamageTaken: diff --git a/internal/stats/prepare/period/preset.go b/internal/stats/prepare/period/preset.go index 253e03b6..781aaaec 100644 --- a/internal/stats/prepare/period/preset.go +++ b/internal/stats/prepare/period/preset.go @@ -7,8 +7,8 @@ import ( func presetToBlock(preset common.Tag, stats frame.StatsFrame) common.StatsBlock[BlockData] { block := common.StatsBlock[BlockData](common.NewBlock(preset, BlockData{})) - block.FillValue(stats) + var args []any switch preset { case common.TagWN8: block.Data.Flavor = BlockFlavorSpecial @@ -32,5 +32,6 @@ func presetToBlock(preset common.Tag, stats frame.StatsFrame) common.StatsBlock[ block.Data.Flavor = BlockFlavorDefault } + block.FillValue(stats, args...) return block } diff --git a/main.go b/main.go index c3312e0b..2f8f7850 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler" "github.com/cufee/aftermath/cmds/discord" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" @@ -35,11 +36,9 @@ func main() { loadStaticAssets(static) - dbClient, err := database.NewClient() - if err != nil { - panic(err) - } - coreClient := core.NewClient(fetchClientFromEnv(), dbClient) + coreClient := coreClientFromEnv() + + scheduler.UpdateAveragesWorker(coreClient)() discordHandler, err := discord.NewRouterHandler(coreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) if err != nil { @@ -50,24 +49,7 @@ func main() { panic(http.ListenAndServe(":"+os.Getenv("PORT"), nil)) } -func loadStaticAssets(static fs.FS) { - // Assets for rendering - err := assets.LoadAssets(static, "static") - if err != nil { - log.Fatalf("assets#LoadAssets failed to load assets from static/ embed.FS %s", err) - } - err = render.InitLoadedAssets() - if err != nil { - log.Fatalf("render#InitLoadedAssets failed %s", err) - } - err = localization.LoadAssets(static, "static/localization") - if err != nil { - log.Fatalf("localization#LoadAssets failed %s", err) - } - -} - -func fetchClientFromEnv() fetch.Client { +func coreClientFromEnv() core.Client { // Dependencies wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_PRIMARY_APP_ID"), os.Getenv("WG_PRIMARY_APP_RPS"), os.Getenv("WG_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_PROXY_HOST_LIST")) if err != nil { @@ -77,7 +59,10 @@ func fetchClientFromEnv() fetch.Client { if err != nil { log.Fatalf("database#NewClient failed %s", err) } - bsClient := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*3) + bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) + if err != nil { + log.Fatalf("failed to init a blitzstars client %s", err) + } // Fetch client client, err := fetch.NewMultiSourceClient(wgClient, bsClient, dbClient) @@ -85,5 +70,22 @@ func fetchClientFromEnv() fetch.Client { log.Fatalf("fetch#NewMultiSourceClient failed %s", err) } - return client + return core.NewClient(client, dbClient) +} + +func loadStaticAssets(static fs.FS) { + // Assets for rendering + err := assets.LoadAssets(static, "static") + if err != nil { + log.Fatalf("assets#LoadAssets failed to load assets from static/ embed.FS %s", err) + } + err = render.InitLoadedAssets() + if err != nil { + log.Fatalf("render#InitLoadedAssets failed %s", err) + } + err = localization.LoadAssets(static, "static/localization") + if err != nil { + log.Fatalf("localization#LoadAssets failed %s", err) + } + } From 7bd15ddda678f72ac01e560b8988330b24011deb Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 19:41:48 -0400 Subject: [PATCH 012/341] disabled averages worked by default --- main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main.go b/main.go index 2f8f7850..5fbdc16a 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,6 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/scheduler" "github.com/cufee/aftermath/cmds/discord" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" @@ -38,7 +37,7 @@ func main() { coreClient := coreClientFromEnv() - scheduler.UpdateAveragesWorker(coreClient)() + // scheduler.UpdateAveragesWorker(coreClient)() discordHandler, err := discord.NewRouterHandler(coreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) if err != nil { From c9fe767aaf907d6387d9ced60f6cc2646c611f78 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 21:07:39 -0400 Subject: [PATCH 013/341] minor style tweaks --- internal/stats/image.go | 6 +++--- internal/stats/render/common/badges.go | 14 +++++++------- internal/stats/render/common/constants.go | 3 ++- internal/stats/render/common/footer.go | 2 +- internal/stats/render/period/constants.go | 4 ++-- internal/stats/render/segments.go | 2 +- static/images/backgrounds/bg-default.jpg | Bin 7541 -> 36041 bytes static/localization/en/stats.yaml | 2 +- 8 files changed, 17 insertions(+), 16 deletions(-) diff --git a/internal/stats/image.go b/internal/stats/image.go index 7c06a7a3..9e73843b 100644 --- a/internal/stats/image.go +++ b/internal/stats/image.go @@ -10,7 +10,7 @@ import ( ) type Image interface { - AddBackground(image.Image) error + AddBackground(image.Image, common.Style) error PNG(io.Writer) error } @@ -18,11 +18,11 @@ type imageImp struct { image.Image } -func (i *imageImp) AddBackground(bg image.Image) error { +func (i *imageImp) AddBackground(bg image.Image, style common.Style) error { if bg == nil { return errors.New("background cannot be nil") } - i.Image = common.AddBackground(i, bg, common.Style{Blur: 10, BorderRadius: 30}) + i.Image = common.AddBackground(i, bg, style) return nil } diff --git a/internal/stats/render/common/badges.go b/internal/stats/render/common/badges.go index eaab0ec9..12e6e1dd 100644 --- a/internal/stats/render/common/badges.go +++ b/internal/stats/render/common/badges.go @@ -54,7 +54,7 @@ var ( Name: "Supporter", Icon: "images/icons/fire", Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColor, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Height: 32}, + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Height: 32}, Icon: Style{Width: 16, Height: 16, BackgroundColor: TextSubscriptionPlus}, Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 5}, }, @@ -63,7 +63,7 @@ var ( Name: "Aftermath+", Icon: "images/icons/star", Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColor, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, Icon: Style{Width: 24, Height: 24, BackgroundColor: TextSubscriptionPlus}, Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 5}, }, @@ -72,7 +72,7 @@ var ( Name: "Aftermath Pro", Icon: "images/icons/star", Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColor, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, Icon: Style{Width: 24, Height: 24, BackgroundColor: TextSubscriptionPremium}, Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 5}, }, @@ -107,7 +107,7 @@ var ( Name: "Community Moderator", Icon: "images/icons/logo-128", Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColor, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, Icon: Style{Width: 20, Height: 20}, Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 2}, }, @@ -116,7 +116,7 @@ var ( Name: "Moderator", Icon: "images/icons/logo-128", Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColor, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, Icon: Style{Width: 20, Height: 20}, Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 2}, }, @@ -125,7 +125,7 @@ var ( Name: "Booster", Icon: "images/icons/discord-booster", Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColor, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, Icon: Style{Width: 20, Height: 20}, Text: Style{Font: &FontSmall, FontColor: TextSecondary}, }, @@ -134,7 +134,7 @@ var ( Name: "Translator", Icon: "images/icons/translator", Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColor, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, Icon: Style{Width: 20, Height: 20, BackgroundColor: TextPrimary}, Text: Style{Font: &FontSmall, FontColor: TextSecondary}, }, diff --git a/internal/stats/render/common/constants.go b/internal/stats/render/common/constants.go index cbbe28b4..dcdcb341 100644 --- a/internal/stats/render/common/constants.go +++ b/internal/stats/render/common/constants.go @@ -24,7 +24,8 @@ var ( TextSubscriptionPlus = color.RGBA{72, 167, 250, 255} TextSubscriptionPremium = color.RGBA{255, 223, 0, 255} - DefaultCardColor = color.RGBA{10, 10, 10, 180} + DefaultCardColor = color.RGBA{10, 10, 10, 180} + DefaultCardColorNoAlpha = color.RGBA{10, 10, 10, 255} ColorAftermathRed = color.RGBA{255, 0, 120, 255} ColorAftermathBlue = color.RGBA{90, 90, 255, 255} diff --git a/internal/stats/render/common/footer.go b/internal/stats/render/common/footer.go index d5c26294..53055e89 100644 --- a/internal/stats/render/common/footer.go +++ b/internal/stats/render/common/footer.go @@ -1,7 +1,7 @@ package common func NewFooterCard(text string) Block { - backgroundColor := DefaultCardColor + backgroundColor := DefaultCardColorNoAlpha backgroundColor.A = 120 return NewBlocksContent(Style{ JustifyContent: JustifyContentCenter, diff --git a/internal/stats/render/period/constants.go b/internal/stats/render/period/constants.go index b339fc78..bbb2b7a3 100644 --- a/internal/stats/render/period/constants.go +++ b/internal/stats/render/period/constants.go @@ -35,7 +35,7 @@ func getOverviewStyle(columnWidth float64) overviewStyle { Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, JustifyContent: common.JustifyContentCenter, - PaddingX: 15, + PaddingX: 10, PaddingY: 0, Gap: 10, Width: columnWidth, @@ -52,7 +52,7 @@ func defaultCardStyle(width float64) common.Style { AlignItems: common.AlignItemsCenter, Direction: common.DirectionVertical, BackgroundColor: common.DefaultCardColor, - BorderRadius: 20, + BorderRadius: 25, PaddingY: 10, PaddingX: 20, Gap: 20, diff --git a/internal/stats/render/segments.go b/internal/stats/render/segments.go index 87bf19a4..561e915c 100644 --- a/internal/stats/render/segments.go +++ b/internal/stats/render/segments.go @@ -49,7 +49,7 @@ func (s *Segments) Render(opts ...Option) (image.Image, error) { if err != nil { return nil, err } - mainSegmentImg = common.AddBackground(mainSegmentImg, options.Background, common.Style{Blur: 10, BorderRadius: 30}) + mainSegmentImg = common.AddBackground(mainSegmentImg, options.Background, common.Style{Blur: 10, BorderRadius: 42.5}) frameParts = append(frameParts, common.NewImageContent(common.Style{}, mainSegmentImg)) if len(s.header) > 0 { diff --git a/static/images/backgrounds/bg-default.jpg b/static/images/backgrounds/bg-default.jpg index abe35ea3e36d4af3f1f8317ee32e9141907a66cc..d752d09c77a0818bb8bd2f54bb9fe3233942504a 100644 GIT binary patch literal 36041 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 literal 7541 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 diff --git a/static/localization/en/stats.yaml b/static/localization/en/stats.yaml index 3ac23f88..49f3a7fd 100755 --- a/static/localization/en/stats.yaml +++ b/static/localization/en/stats.yaml @@ -14,7 +14,7 @@ - key: label_frags value: Kills - key: label_damage_ratio - value: Damage Ratio + value: Dmg. Ratio - key: label_damage_blocked value: Blocked - key: label_avg_tier From 50072830582cfd53906fed58066e2e274fdf0967 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 21:53:13 -0400 Subject: [PATCH 014/341] added glossary --- cmds/core/client.go | 17 +++- cmds/core/scheduler/glossary.go | 41 ++++++-- go.mod | 1 - go.sum | 11 --- .../database/{external.go => averages.go} | 9 -- internal/database/client.go | 2 + internal/database/vehicles.go | 93 +++++++++++++++++++ internal/stats/period.go | 22 ++++- internal/stats/prepare/common/tags.go | 3 + internal/stats/prepare/period/card.go | 13 ++- internal/stats/prepare/period/options.go | 9 +- internal/stats/prepare/period/tier.go | 36 ++++--- .../stats/render/common/tier-percentage.go | 2 +- internal/stats/renderer.go | 5 +- main.go | 4 +- 15 files changed, 206 insertions(+), 62 deletions(-) rename internal/database/{external.go => averages.go} (91%) create mode 100644 internal/database/vehicles.go diff --git a/cmds/core/client.go b/cmds/core/client.go index cd1e9381..db5ca9b7 100644 --- a/cmds/core/client.go +++ b/cmds/core/client.go @@ -2,6 +2,7 @@ package core import ( "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/stats" "github.com/cufee/aftermath/internal/stats/fetch" "golang.org/x/text/language" @@ -12,13 +13,19 @@ var _ Client = &client{} type Client interface { Render(locale language.Tag) stats.Renderer + Wargaming() wargaming.Client Database() database.Client Fetch() fetch.Client } type client struct { - fetch fetch.Client - db database.Client + wargaming wargaming.Client + fetch fetch.Client + db database.Client +} + +func (c *client) Wargaming() wargaming.Client { + return c.wargaming } func (c *client) Database() database.Client { @@ -30,9 +37,9 @@ func (c *client) Fetch() fetch.Client { } func (c *client) Render(locale language.Tag) stats.Renderer { - return stats.NewRenderer(c.fetch, locale) + return stats.NewRenderer(c.fetch, c.db, locale) } -func NewClient(fetch fetch.Client, database database.Client) *client { - return &client{fetch: fetch, db: database} +func NewClient(fetch fetch.Client, wargaming wargaming.Client, database database.Client) *client { + return &client{fetch: fetch, db: database, wargaming: wargaming} } diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go index a81cd930..587d651b 100644 --- a/cmds/core/scheduler/glossary.go +++ b/cmds/core/scheduler/glossary.go @@ -5,7 +5,9 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/internal/database" "github.com/rs/zerolog/log" + "golang.org/x/text/language" ) // CurrentTankAverages @@ -36,13 +38,36 @@ func UpdateAveragesWorker(client core.Client) func() { func UpdateGlossaryWorker(client core.Client) func() { return func() { - // // We just run the logic directly as it's not a heavy task and it doesn't matter if it fails due to the app failing - // log.Info().Msg("updating glossary cache") - // err := cache.UpdateGlossaryCache() - // if err != nil { - // log.Err(err).Msg("failed to update glossary cache") - // } else { - // log.Info().Msg("glossary cache updated") - // } + // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails + log.Info().Msg("updating glossary cache") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + glossary, err := client.Wargaming().CompleteVehicleGlossary(ctx, "eu", "en") + if err != nil { + log.Err(err).Msg("failed to get vehicle glossary") + return + } + + vehicles := make(map[string]database.Vehicle) + for id, data := range glossary { + vehicles[id] = database.Vehicle{ + ID: id, + Tier: data.Tier, + Type: data.Type, + // Class: , + Nation: data.Nation, + LocalizedNames: map[language.Tag]string{language.English: data.Name}, + } + } + + err = client.Database().UpsertVehicles(ctx, vehicles) + if err != nil { + log.Err(err).Msg("failed to save vehicle glossary") + return + } + + log.Info().Msg("glossary cache updated") } } diff --git a/go.mod b/go.mod index e4c1c034..08be4bfe 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/steebchen/prisma-client-go v0.37.0 github.com/stretchr/testify v1.9.0 - go.dedis.ch/protobuf v1.0.11 golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 golang.org/x/image v0.16.0 golang.org/x/text v0.15.0 diff --git a/go.sum b/go.sum index fa0e39ca..d1ebe577 100644 --- a/go.sum +++ b/go.sum @@ -64,18 +64,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= -go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= -go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= -go.dedis.ch/kyber/v3 v3.0.9 h1:i0ZbOQocHUjfFasBiUql5zVeC7u/vahFd96DFA8UOWk= -go.dedis.ch/kyber/v3 v3.0.9/go.mod h1:rhNjUUg6ahf8HEg5HUvVBYoWY4boAafX8tYxX+PS+qg= -go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= -go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= -go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= -go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= @@ -87,7 +77,6 @@ golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= diff --git a/internal/database/external.go b/internal/database/averages.go similarity index 91% rename from internal/database/external.go rename to internal/database/averages.go index 87117a4a..5a27a3f4 100644 --- a/internal/database/external.go +++ b/internal/database/averages.go @@ -5,20 +5,11 @@ import ( "time" "github.com/cufee/aftermath/internal/database/prisma/db" - "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/stats/frame" "github.com/rs/zerolog/log" "github.com/steebchen/prisma-client-go/runtime/transaction" ) -type GlossaryVehicle struct { - db.VehicleModel -} - -func (v GlossaryVehicle) Name(printer localization.Printer) string { - return printer("name") -} - func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { if len(averages) < 1 { return nil diff --git a/internal/database/client.go b/internal/database/client.go index d4749a3f..de950fa6 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -10,6 +10,8 @@ import ( type Client interface { GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error + GetVehicles(ctx context.Context, ids []string) (map[string]Vehicle, error) + UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle) error GetUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go new file mode 100644 index 00000000..95def7d4 --- /dev/null +++ b/internal/database/vehicles.go @@ -0,0 +1,93 @@ +package database + +import ( + "context" + "encoding/json" + + "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/rs/zerolog/log" + "github.com/steebchen/prisma-client-go/runtime/transaction" + "golang.org/x/text/language" +) + +type Vehicle struct { + ID string + Tier int + Type string + Class string + Nation string + + LocalizedNames map[language.Tag]string +} + +func (v Vehicle) FromModel(model db.VehicleModel) Vehicle { + v.ID = model.ID + v.Tier = model.Tier + v.Type = model.Type + v.Nation = model.Nation + v.LocalizedNames = make(map[language.Tag]string) + if model.LocalizedNamesEncoded != "" { + _ = json.Unmarshal([]byte(model.LocalizedNamesEncoded), &v.LocalizedNames) + } + return v +} + +func (v Vehicle) Name(locale language.Tag) string { + if n := v.LocalizedNames[locale]; n != "" { + return n + } + if n := v.LocalizedNames[language.English]; n != "" { + return n + } + return "Secret Tank" +} + +func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle) error { + if len(vehicles) < 1 { + return nil + } + + var transactions []transaction.Transaction + for id, data := range vehicles { + encoded, err := json.Marshal(data.LocalizedNames) + if err != nil { + log.Err(err).Str("id", id).Msg("failed to encode a stats frame for vehicle averages") + continue + } + + transactions = append(transactions, c.Raw.Vehicle. + UpsertOne(db.Vehicle.ID.Equals(id)). + Create( + db.Vehicle.ID.Set(id), + db.Vehicle.Tier.Set(data.Tier), + db.Vehicle.Type.Set(data.Type), + db.Vehicle.Class.Set(data.Class), + db.Vehicle.Nation.Set(data.Nation), + db.Vehicle.LocalizedNamesEncoded.Set(string(encoded)), + ). + Update( + db.Vehicle.Tier.Set(data.Tier), + db.Vehicle.Type.Set(data.Type), + db.Vehicle.Class.Set(data.Class), + db.Vehicle.Nation.Set(data.Nation), + db.Vehicle.LocalizedNamesEncoded.Set(string(encoded)), + ).Tx(), + ) + } + + return c.Raw.Prisma.Transaction(transactions...).Exec(ctx) +} + +func (c *client) GetVehicles(ctx context.Context, ids []string) (map[string]Vehicle, error) { + models, err := c.Raw.Vehicle.FindMany(db.Vehicle.ID.In(ids)).Exec(ctx) + if err != nil { + return nil, err + } + + vehicles := make(map[string]Vehicle) + for _, model := range models { + vehicles[model.ID] = vehicles[model.ID].FromModel(model) + } + + return vehicles, nil +} diff --git a/internal/stats/period.go b/internal/stats/period.go index 3e61b421..aeb41fdb 100644 --- a/internal/stats/period.go +++ b/internal/stats/period.go @@ -2,8 +2,10 @@ package stats import ( "context" + "slices" "time" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/stats/fetch" prepare "github.com/cufee/aftermath/internal/stats/prepare/period" @@ -14,6 +16,7 @@ import ( type renderer struct { fetchClient fetch.Client + database database.Client locale language.Tag } @@ -33,8 +36,25 @@ func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, } meta.Stats = stats + stop = meta.Timer("prepare#GetVehicles") + var vehicles []string + for id := range stats.RegularBattles.Vehicles { + vehicles = append(vehicles, id) + } + for id := range stats.RatingBattles.Vehicles { + if !slices.Contains(vehicles, id) { + vehicles = append(vehicles, id) + } + } + + glossary, err := r.database.GetVehicles(ctx, vehicles) + if err != nil { + return nil, meta, err + } + stop() + stop = meta.Timer("prepare#NewCards") - cards, err := prepare.NewCards(stats, nil, prepare.WithPrinter(printer)) + cards, err := prepare.NewCards(stats, glossary, prepare.WithPrinter(printer, r.locale)) stop() if err != nil { return nil, meta, err diff --git a/internal/stats/prepare/common/tags.go b/internal/stats/prepare/common/tags.go index cba8b267..34301e15 100644 --- a/internal/stats/prepare/common/tags.go +++ b/internal/stats/prepare/common/tags.go @@ -24,6 +24,9 @@ const ( TagSurvivalPercent Tag = "survival_percent" TagDamageDealt Tag = "damage_dealt" TagDamageTaken Tag = "damage_taken" + + // Module Specific + TagAvgTier Tag = "avg_tier" ) func ParseTags(tags ...string) ([]Tag, error) { diff --git a/internal/stats/prepare/period/card.go b/internal/stats/prepare/period/card.go index c6f068df..5931d109 100644 --- a/internal/stats/prepare/period/card.go +++ b/internal/stats/prepare/period/card.go @@ -8,13 +8,13 @@ import ( "github.com/cufee/aftermath/internal/stats/prepare/common" ) -func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.GlossaryVehicle, opts ...Option) (Cards, error) { +func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.Vehicle, opts ...Option) (Cards, error) { options := defaultOptions for _, apply := range opts { apply(&options) } if glossary == nil { - glossary = make(map[string]database.GlossaryVehicle) + glossary = make(map[string]database.Vehicle) } var cards Cards @@ -23,14 +23,13 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.G for _, column := range selectedBlocks { var columnBlocks []common.StatsBlock[BlockData] for _, preset := range column { + block := presetToBlock(preset, stats.RegularBattles.StatsFrame) if preset == TagAvgTier { - // value := calculateAvgTier(input.Stats.Vehicles, input.VehicleGlossary) - continue + block = avgTierBlock(stats.RegularBattles.Vehicles, glossary) } - block := presetToBlock(preset, stats.RegularBattles.StatsFrame) + block.Localize(options.localePrinter) columnBlocks = append(columnBlocks, block) - } cards.Overview.Type = common.CardTypeOverview @@ -70,7 +69,7 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.G glossary.ID = data.vehicle.VehicleID cards.Highlights = append(cards.Highlights, VehicleCard{ - Title: fmt.Sprintf("%s %s", common.IntToRoman(glossary.Tier), glossary.Name(options.localePrinter)), + Title: fmt.Sprintf("%s %s", common.IntToRoman(glossary.Tier), glossary.Name(options.locale)), Type: common.CardTypeVehicle, Blocks: vehicleBlocks, Meta: options.localePrinter(data.highlight.label), diff --git a/internal/stats/prepare/period/options.go b/internal/stats/prepare/period/options.go index 9f6a9fa9..a7a3f1d8 100644 --- a/internal/stats/prepare/period/options.go +++ b/internal/stats/prepare/period/options.go @@ -1,13 +1,16 @@ package period -var defaultOptions = options{localePrinter: func(s string) string { return s }} +import "golang.org/x/text/language" + +var defaultOptions = options{localePrinter: func(s string) string { return s }, locale: language.English} type options struct { localePrinter func(string) string + locale language.Tag } type Option func(*options) -func WithPrinter(printer func(string) string) func(*options) { - return func(o *options) { o.localePrinter = printer } +func WithPrinter(printer func(string) string, locale language.Tag) func(*options) { + return func(o *options) { o.localePrinter = printer; o.locale = locale } } diff --git a/internal/stats/prepare/period/tier.go b/internal/stats/prepare/period/tier.go index 2096e4aa..5b085788 100644 --- a/internal/stats/prepare/period/tier.go +++ b/internal/stats/prepare/period/tier.go @@ -1,17 +1,27 @@ package period -// func calculateAvgTier(vehicles map[string]fetch.VehicleStatsFrame, glossary map[string]struct{}) fetch.Value { -// var weightedTierTotal float32 +import ( + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/stats/frame" + "github.com/cufee/aftermath/internal/stats/prepare/common" +) -// for _, vehicle := range vehicles { -// if data, ok := glossary[vehicle.VehicleID]; ok && data.Tier > 0 { -// battlesTotal += vehicle.Battles -// weightedTierTotal += float32(vehicle.Battles * data.Tier) -// } -// } -// if battlesTotal == 0 { -// return fetch.InvalidValue -// } +func avgTierBlock(vehicles map[string]frame.VehicleStatsFrame, glossary map[string]database.Vehicle) common.StatsBlock[BlockData] { + block := common.StatsBlock[BlockData](common.NewBlock(common.TagAvgTier, BlockData{Flavor: BlockFlavorSecondary})) -// return weightedTierTotal / float32(battlesTotal) -// } + var weightedTotal, battlesTotal float32 + + for _, vehicle := range vehicles { + if data, ok := glossary[vehicle.VehicleID]; ok && data.Tier > 0 { + battlesTotal += vehicle.Battles.Float() + weightedTotal += vehicle.Battles.Float() * float32(data.Tier) + } + } + if battlesTotal == 0 { + block.Value = frame.InvalidValue + return block + } + + block.Value = frame.ValueFloatDecimal(weightedTotal / battlesTotal) + return block +} diff --git a/internal/stats/render/common/tier-percentage.go b/internal/stats/render/common/tier-percentage.go index 92cced6e..dd96c2d4 100644 --- a/internal/stats/render/common/tier-percentage.go +++ b/internal/stats/render/common/tier-percentage.go @@ -8,7 +8,7 @@ import ( "github.com/cufee/aftermath/internal/stats/frame" ) -func NewTierPercentageCard(style Style, vehicles map[string]frame.VehicleStatsFrame, glossary map[int]database.GlossaryVehicle) Block { +func NewTierPercentageCard(style Style, vehicles map[string]frame.VehicleStatsFrame, glossary map[int]database.Vehicle) Block { var blocks []Block var elements int = 10 diff --git a/internal/stats/renderer.go b/internal/stats/renderer.go index fb382966..3bd2e7ad 100644 --- a/internal/stats/renderer.go +++ b/internal/stats/renderer.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/render" "golang.org/x/text/language" @@ -18,6 +19,6 @@ type Renderer interface { // Session(accountId string, from time.Time) (image.Image, error) } -func NewRenderer(fetch fetch.Client, locale language.Tag) *renderer { - return &renderer{fetch, locale} +func NewRenderer(fetch fetch.Client, database database.Client, locale language.Tag) *renderer { + return &renderer{fetch, database, locale} } diff --git a/main.go b/main.go index 5fbdc16a..a57a607c 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler" "github.com/cufee/aftermath/cmds/discord" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" @@ -38,6 +39,7 @@ func main() { coreClient := coreClientFromEnv() // scheduler.UpdateAveragesWorker(coreClient)() + scheduler.UpdateGlossaryWorker(coreClient)() discordHandler, err := discord.NewRouterHandler(coreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) if err != nil { @@ -69,7 +71,7 @@ func coreClientFromEnv() core.Client { log.Fatalf("fetch#NewMultiSourceClient failed %s", err) } - return core.NewClient(client, dbClient) + return core.NewClient(client, wgClient, dbClient) } func loadStaticAssets(static fs.FS) { From 112dcf5100f92ad2949b7040498fe6882bfd011c Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 22:07:08 -0400 Subject: [PATCH 015/341] added retry to all discord calls --- cmds/discord/rest/client.go | 49 ++++++++++++++---------- cmds/discord/router/handler.go | 3 -- internal/{stats/fetch => retry}/retry.go | 6 +-- internal/stats/fetch/multisource.go | 25 ++++++------ main.go | 4 +- 5 files changed, 45 insertions(+), 42 deletions(-) rename internal/{stats/fetch => retry}/retry.go (67%) diff --git a/cmds/discord/rest/client.go b/cmds/discord/rest/client.go index 7c85e32c..bbc26f9f 100644 --- a/cmds/discord/rest/client.go +++ b/cmds/discord/rest/client.go @@ -13,6 +13,7 @@ import ( "time" "github.com/bwmarrin/discordgo" + "github.com/cufee/aftermath/internal/retry" ) type Client struct { @@ -25,7 +26,7 @@ type Client struct { func NewClient(token string) (*Client, error) { client := &Client{ token: token, - http: http.Client{Timeout: time.Millisecond * 3000}, + http: http.Client{Timeout: time.Millisecond * 1000}, } _, err := client.lookupApplicationID() @@ -109,29 +110,35 @@ func partHeader(contentDisposition string, contentType string) textproto.MIMEHea } func (c *Client) do(req *http.Request, target any) error { - res, err := c.http.Do(req) - if err != nil { - return err - } - defer res.Body.Close() - - if res.StatusCode > 299 { - var body discordgo.APIErrorMessage - _ = json.NewDecoder(res.Body).Decode(&body) - message := body.Message - if message == "" { - message = res.Status + result := retry.Retry(func() (any, error) { + res, err := c.http.Do(req) + if err != nil { + return nil, err } - return errors.New("discord: " + strings.ToLower(message)) - } + defer res.Body.Close() - if target != nil { - err = json.NewDecoder(res.Body).Decode(target) - if err != nil { - return fmt.Errorf("failed to decode response body :%w", err) + if res.StatusCode > 299 { + var body discordgo.APIErrorMessage + _ = json.NewDecoder(res.Body).Decode(&body) + message := body.Message + if message == "" { + message = res.Status + } + + return nil, errors.New("discord: " + strings.ToLower(message)) } - } - return nil + + if target != nil { + err = json.NewDecoder(res.Body).Decode(target) + if err != nil { + return nil, fmt.Errorf("failed to decode response body :%w", err) + } + } + + return nil, nil + }, 3, time.Millisecond*150) + + return result.Err } func (c *Client) lookupApplicationID() (string, error) { diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 73fb9a96..685030b4 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -127,9 +127,6 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { go func() { // unlock once this context is done and the ack is delivered <-r.Context().Done() - // give discord time to register the reply - // in very rare cases, out reply comes right after the unlock and discord returns an error - time.Sleep(time.Millisecond * 50) state.mx.Unlock() }() diff --git a/internal/stats/fetch/retry.go b/internal/retry/retry.go similarity index 67% rename from internal/stats/fetch/retry.go rename to internal/retry/retry.go index 26d5c08d..f45c4ad9 100644 --- a/internal/stats/fetch/retry.go +++ b/internal/retry/retry.go @@ -1,4 +1,4 @@ -package fetch +package retry import ( "errors" @@ -10,7 +10,7 @@ type DataWithErr[T any] struct { Err error } -func withRetry[T any](fn func() (T, error), tries int, sleepOnFail time.Duration) DataWithErr[T] { +func Retry[T any](fn func() (T, error), tries int, sleepOnFail time.Duration) DataWithErr[T] { if tries < 1 { return DataWithErr[T]{Err: errors.New("invalid number of tries provided")} } @@ -22,5 +22,5 @@ func withRetry[T any](fn func() (T, error), tries int, sleepOnFail time.Duration } time.Sleep(sleepOnFail) - return withRetry(fn, tries, sleepOnFail) + return Retry(fn, tries, sleepOnFail) } diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 134c933f..ddd08882 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -10,6 +10,7 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" + "github.com/cufee/aftermath/internal/retry" "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/am-wg-proxy-next/v2/types" ) @@ -65,14 +66,14 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. group.Add(3) var clan types.ClanMember - var account DataWithErr[types.ExtendedAccount] - var vehicles DataWithErr[[]types.VehicleStatsFrame] - var averages DataWithErr[map[string]frame.StatsFrame] + var account retry.DataWithErr[types.ExtendedAccount] + var vehicles retry.DataWithErr[[]types.VehicleStatsFrame] + var averages retry.DataWithErr[map[string]frame.StatsFrame] go func() { defer group.Done() - account = withRetry(func() (types.ExtendedAccount, error) { + account = retry.Retry(func() (types.ExtendedAccount, error) { return c.wargaming.AccountByID(ctx, realm, id) }, c.retriesPerRequest, c.retrySleepInterval) }() @@ -84,7 +85,7 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. go func() { defer group.Done() - vehicles = withRetry(func() ([]types.VehicleStatsFrame, error) { + vehicles = retry.Retry(func() ([]types.VehicleStatsFrame, error) { return c.wargaming.AccountVehicles(ctx, realm, id) }, c.retriesPerRequest, c.retrySleepInterval) @@ -97,7 +98,7 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. ids = append(ids, fmt.Sprint(v.TankID)) } a, err := c.database.GetVehicleAverages(ctx, ids) - averages = DataWithErr[map[string]frame.StatsFrame]{a, err} + averages = retry.DataWithErr[map[string]frame.StatsFrame]{Data: a, Err: err} }() group.Wait() @@ -123,9 +124,9 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt apply(&options) } - var histories DataWithErr[map[int][]blitzstars.TankHistoryEntry] - var averages DataWithErr[map[string]frame.StatsFrame] - var current DataWithErr[AccountStatsOverPeriod] + var histories retry.DataWithErr[map[int][]blitzstars.TankHistoryEntry] + var averages retry.DataWithErr[map[string]frame.StatsFrame] + var current retry.DataWithErr[AccountStatsOverPeriod] var group sync.WaitGroup group.Add(1) @@ -133,7 +134,7 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt defer group.Done() stats, err := c.CurrentStats(ctx, id) - current = DataWithErr[AccountStatsOverPeriod]{stats, err} + current = retry.DataWithErr[AccountStatsOverPeriod]{Data: stats, Err: err} if err != nil || stats.RegularBattles.Battles < 1 || !options.withWN8 { return @@ -144,7 +145,7 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt ids = append(ids, id) } a, err := c.database.GetVehicleAverages(ctx, ids) - averages = DataWithErr[map[string]frame.StatsFrame]{a, err} + averages = retry.DataWithErr[map[string]frame.StatsFrame]{Data: a, Err: err} }() // TODO: lookup a session from the database first @@ -167,7 +168,7 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt group.Add(1) go func() { defer group.Done() - histories = withRetry(func() (map[int][]blitzstars.TankHistoryEntry, error) { + histories = retry.Retry(func() (map[int][]blitzstars.TankHistoryEntry, error) { return c.blitzstars.AccountTankHistories(ctx, id) }, c.retriesPerRequest, c.retrySleepInterval) }() diff --git a/main.go b/main.go index a57a607c..4351c111 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,6 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/scheduler" "github.com/cufee/aftermath/cmds/discord" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" @@ -39,7 +38,7 @@ func main() { coreClient := coreClientFromEnv() // scheduler.UpdateAveragesWorker(coreClient)() - scheduler.UpdateGlossaryWorker(coreClient)() + // scheduler.UpdateGlossaryWorker(coreClient)() discordHandler, err := discord.NewRouterHandler(coreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) if err != nil { @@ -88,5 +87,4 @@ func loadStaticAssets(static fs.FS) { if err != nil { log.Fatalf("localization#LoadAssets failed %s", err) } - } From e3f4842e0e00f39d20f6e4ae933853336f22d0c5 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 6 Jun 2024 22:11:53 -0400 Subject: [PATCH 016/341] handling 0 length request slice --- internal/database/vehicles.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index 95def7d4..d231f256 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -79,6 +79,10 @@ func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle } func (c *client) GetVehicles(ctx context.Context, ids []string) (map[string]Vehicle, error) { + if len(ids) < 1 { + return nil, nil + } + models, err := c.Raw.Vehicle.FindMany(db.Vehicle.ID.In(ids)).Exec(ctx) if err != nil { return nil, err From 2ea3d4409b3849b4e609572d20ea78dc10f1f57c Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 7 Jun 2024 16:52:51 -0400 Subject: [PATCH 017/341] scheduler queue, db accounts --- cmds/core/scheduler/cron.go | 9 +- cmds/core/scheduler/tasks/handler.go | 17 ++ cmds/core/scheduler/tasks/queue.go | 125 +++++++++ cmds/core/scheduler/tasks/sessions.go | 88 +++++++ cmds/core/scheduler/tasks/split.go | 24 ++ cmds/core/scheduler/workers.go | 5 +- cmds/discord/commands/ping.go | 16 ++ cmds/discord/router/handler.go | 13 +- go.mod | 3 + go.sum | 15 ++ internal/database/accounts.go | 132 ++++++++++ internal/database/averages.go | 8 +- internal/database/client.go | 23 +- internal/database/prisma/schema.prisma | 32 ++- internal/database/tasks.go | 247 ++++++++++++++++++ internal/database/users.go | 22 +- internal/database/vehicles.go | 6 +- internal/permissions/actions.go | 22 +- internal/permissions/roles.go | 3 +- internal/permissions/type.go | 2 +- internal/stats/fetch/client.go | 4 +- internal/stats/fetch/convert.go | 21 +- internal/stats/fetch/replay/errors.go | 7 + internal/stats/fetch/replay/meta.go | 17 ++ internal/stats/fetch/replay/replay.go | 228 ++++++++++++++++ internal/stats/fetch/replay/results.go | 181 +++++++++++++ internal/stats/fetch/replay/unpack.go | 102 ++++++++ internal/stats/prepare/replay/replay.go.todo | 224 ---------------- internal/stats/render/period/cards.go | 4 +- internal/stats/render/replay/blocks.go.todo | 108 ++++++++ internal/stats/render/replay/cards.go.todo | 90 +++++++ .../stats/render/replay/constants.go.todo | 27 ++ internal/stats/render/replay/elements.go.todo | 48 ++++ internal/stats/render/replay/preset.go.todo | 18 ++ main.go | 7 +- 35 files changed, 1609 insertions(+), 289 deletions(-) create mode 100644 cmds/core/scheduler/tasks/handler.go create mode 100644 cmds/core/scheduler/tasks/queue.go create mode 100644 cmds/core/scheduler/tasks/sessions.go create mode 100644 cmds/core/scheduler/tasks/split.go create mode 100644 cmds/discord/commands/ping.go create mode 100644 internal/database/accounts.go create mode 100644 internal/database/tasks.go create mode 100644 internal/stats/fetch/replay/errors.go create mode 100644 internal/stats/fetch/replay/meta.go create mode 100644 internal/stats/fetch/replay/replay.go create mode 100644 internal/stats/fetch/replay/results.go create mode 100644 internal/stats/fetch/replay/unpack.go delete mode 100644 internal/stats/prepare/replay/replay.go.todo create mode 100644 internal/stats/render/replay/blocks.go.todo create mode 100644 internal/stats/render/replay/cards.go.todo create mode 100644 internal/stats/render/replay/constants.go.todo create mode 100644 internal/stats/render/replay/elements.go.todo create mode 100644 internal/stats/render/replay/preset.go.todo diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go index 23ea3de7..d78ad736 100644 --- a/cmds/core/scheduler/cron.go +++ b/cmds/core/scheduler/cron.go @@ -4,17 +4,18 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler/tasks" "github.com/go-co-op/gocron" "github.com/rs/zerolog/log" ) -func StartCronJobs(client core.Client) { - log.Info().Msg("starting cron jobs") +func StartCronJobs(client core.Client, queue *tasks.Queue) { + defer log.Info().Msg("started cron scheduler") c := gocron.NewScheduler(time.UTC) // Tasks - c.Cron("* * * * *").Do(runTasksWorker(client)) - c.Cron("0 * * * *").Do(restartTasksWorker(client)) + c.Cron("* * * * *").Do(runTasksWorker(queue)) + c.Cron("0 * * * *").Do(restartTasksWorker(queue)) // Glossary - Do it around the same time WG releases game updates c.Cron("0 10 * * *").Do(UpdateGlossaryWorker(client)) diff --git a/cmds/core/scheduler/tasks/handler.go b/cmds/core/scheduler/tasks/handler.go new file mode 100644 index 00000000..4c99d1ff --- /dev/null +++ b/cmds/core/scheduler/tasks/handler.go @@ -0,0 +1,17 @@ +package tasks + +import ( + "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/internal/database" +) + +type TaskHandler struct { + process func(client core.Client, task database.Task) (string, error) + shouldRetry func(task *database.Task) bool +} + +var defaultHandlers = make(map[database.TaskType]TaskHandler) + +func DefaultHandlers() map[database.TaskType]TaskHandler { + return defaultHandlers +} diff --git a/cmds/core/scheduler/tasks/queue.go b/cmds/core/scheduler/tasks/queue.go new file mode 100644 index 00000000..9a5dc94d --- /dev/null +++ b/cmds/core/scheduler/tasks/queue.go @@ -0,0 +1,125 @@ +package tasks + +import ( + "context" + "sync" + "time" + + "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/internal/database" + "github.com/rs/zerolog/log" +) + +type Queue struct { + limiter chan struct{} + concurrencyLimit int + lastTaskRun time.Time + + handlers map[database.TaskType]TaskHandler + core core.Client +} + +func (q *Queue) ConcurrencyLimit() int { + return q.concurrencyLimit +} + +func (q *Queue) ActiveWorkers() int { + return len(q.limiter) +} + +func (q *Queue) LastTaskRun() time.Time { + return q.lastTaskRun +} + +func NewQueue(client core.Client, handlers map[database.TaskType]TaskHandler, concurrencyLimit int) *Queue { + return &Queue{ + core: client, + handlers: handlers, + concurrencyLimit: concurrencyLimit, + limiter: make(chan struct{}, concurrencyLimit), + } +} + +func (q *Queue) Process(callback func(error), tasks ...database.Task) { + var err error + if callback != nil { + defer callback(err) + } + if len(tasks) == 0 { + log.Debug().Msg("no tasks to process") + return + } + + log.Debug().Msgf("processing %d tasks", len(tasks)) + + var wg sync.WaitGroup + q.lastTaskRun = time.Now() + processedTasks := make(chan database.Task, len(tasks)) + for _, task := range tasks { + wg.Add(1) + go func(t database.Task) { + q.limiter <- struct{}{} + defer func() { + processedTasks <- t + wg.Done() + <-q.limiter + log.Debug().Msgf("finished processing task %s", t.ID) + }() + log.Debug().Msgf("processing task %s", t.ID) + + handler, ok := q.handlers[t.Type] + if !ok { + t.Status = database.TaskStatusFailed + t.LogAttempt(database.TaskLog{ + Targets: t.Targets, + Timestamp: time.Now(), + Error: "missing task type handler", + }) + return + } + + attempt := database.TaskLog{ + Targets: t.Targets, + Timestamp: time.Now(), + } + + message, err := handler.process(nil, t) + attempt.Comment = message + if err != nil { + attempt.Error = err.Error() + t.Status = database.TaskStatusFailed + } else { + t.Status = database.TaskStatusComplete + } + t.LogAttempt(attempt) + }(task) + } + + wg.Wait() + close(processedTasks) + + rescheduledCount := 0 + processedSlice := make([]database.Task, 0, len(processedTasks)) + for task := range processedTasks { + handler, ok := q.handlers[task.Type] + if !ok { + continue + } + + if task.Status == database.TaskStatusFailed && handler.shouldRetry(&task) { + rescheduledCount++ + task.Status = database.TaskStatusScheduled + } + processedSlice = append(processedSlice, task) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + err = q.core.Database().UpdateTasks(ctx, processedSlice...) + if err != nil { + return + } + + log.Debug().Msgf("processed %d tasks, %d rescheduled", len(processedSlice), rescheduledCount) +} diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go new file mode 100644 index 00000000..45c03af2 --- /dev/null +++ b/cmds/core/scheduler/tasks/sessions.go @@ -0,0 +1,88 @@ +package tasks + +import ( + "context" + "errors" + "strings" + "time" + + "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/internal/database" +) + +func init() { + defaultHandlers[database.TaskTypeRecordSessions] = TaskHandler{ + process: func(client core.Client, task database.Task) (string, error) { + if task.Data == nil { + return "no data provided", errors.New("no data provided") + } + realm, ok := task.Data["realm"].(string) + if !ok { + return "invalid realm", errors.New("invalid realm") + } + + return "did nothing for a task on realm " + realm, nil + + // accountErrs, err := cache.RefreshSessionsAndAccounts(models.SessionTypeDaily, nil, realm, task.Targets...) + // if err != nil { + // return "failed to refresh sessions on all account", err + // } + + // var failedAccounts []int + // for accountId, err := range accountErrs { + // if err != nil && accountId != 0 { + // failedAccounts = append(failedAccounts, accountId) + // } + // } + // if len(failedAccounts) == 0 { + // return "finished session update on all accounts", nil + // } + + // // Retry failed accounts + // task.Targets = failedAccounts + // return "retrying failed accounts", errors.New("some accounts failed") + }, + shouldRetry: func(task *database.Task) bool { + triesLeft, ok := task.Data["triesLeft"].(int32) + if !ok { + return false + } + if triesLeft <= 0 { + return false + } + + triesLeft -= 1 + task.Data["triesLeft"] = triesLeft + task.ScheduledAfter = time.Now().Add(5 * time.Minute) // Backoff for 5 minutes to avoid spamming + return true + }, + } +} + +func CreateSessionUpdateTasks(client core.Client) func(realm string) error { + return func(realm string) error { + realm = strings.ToUpper(realm) + task := database.Task{ + Type: database.TaskTypeRecordSessions, + ReferenceID: "realm_" + realm, + Data: map[string]any{ + "realm": realm, + "triesLeft": int32(3), + }, + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + accounts, err := client.Database().GetRealmAccounts(ctx, realm) + if err != nil { + return err + } + if len(accounts) < 1 { + return nil + } + + // This update requires (2 + n) requests per n players + return client.Database().CreateTasks(ctx, splitTaskByTargets(task, 50)...) + } +} diff --git a/cmds/core/scheduler/tasks/split.go b/cmds/core/scheduler/tasks/split.go new file mode 100644 index 00000000..38a9df21 --- /dev/null +++ b/cmds/core/scheduler/tasks/split.go @@ -0,0 +1,24 @@ +package tasks + +import "github.com/cufee/aftermath/internal/database" + +func splitTaskByTargets(task database.Task, batchSize int) []database.Task { + if len(task.Targets) <= batchSize { + return []database.Task{task} + } + + var tasks []database.Task + subTasks := len(task.Targets) / batchSize + + for i := 0; i <= subTasks; i++ { + subTask := task + if len(task.Targets) > batchSize*(i+1) { + subTask.Targets = (task.Targets[batchSize*i : batchSize*(i+1)]) + } else { + subTask.Targets = (task.Targets[batchSize*i:]) + } + tasks = append(tasks, subTask) + } + + return tasks +} diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index d5037340..06c8eff7 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -2,6 +2,7 @@ package scheduler import ( "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler/tasks" ) func rotateBackgroundPresetsWorker(client core.Client) func() { @@ -29,7 +30,7 @@ func createSessionTasksWorker(client core.Client, realm string) func() { } } -func runTasksWorker(client core.Client) func() { +func runTasksWorker(queue *tasks.Queue) func() { return func() { // if tasks.DefaultQueue.ActiveWorkers() > 0 { // return @@ -57,7 +58,7 @@ func runTasksWorker(client core.Client) func() { } } -func restartTasksWorker(client core.Client) func() { +func restartTasksWorker(queue *tasks.Queue) func() { return func() { // _, err := tasks.RestartAbandonedTasks(nil) // if err != nil { diff --git a/cmds/discord/commands/ping.go b/cmds/discord/commands/ping.go new file mode 100644 index 00000000..7e062030 --- /dev/null +++ b/cmds/discord/commands/ping.go @@ -0,0 +1,16 @@ +package commands + +import ( + "github.com/cufee/aftermath/cmds/discord/commands/builder" + "github.com/cufee/aftermath/cmds/discord/common" +) + +func init() { + Loaded.add( + builder.NewCommand("ping"). + Params(builder.SetDescKey("Pong!")). + Handler(func(ctx *common.Context) error { + return ctx.Reply("Pong!") + }), + ) +} diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 685030b4..ff110617 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -109,7 +109,7 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { command, err := router.routeInteraction(data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - log.Err(err).Msg("failed to route interaction") + log.Err(err).Str("id", data.ID).Msg("failed to route interaction") return } @@ -117,16 +117,19 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { err = writeDeferredInteractionResponseAck(w, data.Type, command.Ephemeral) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - log.Err(err).Msg("failed to ack an interaction") + log.Err(err).Str("id", data.ID).Msg("failed to ack an interaction") return } // route the interaction to a proper handler for a later reply - state := &interactionState{mx: &sync.Mutex{}, acked: true} + state := &interactionState{mx: &sync.Mutex{}} state.mx.Lock() go func() { // unlock once this context is done and the ack is delivered <-r.Context().Done() + log.Debug().Str("id", data.ID).Msg("sent an interaction ack") + + state.acked = true state.mx.Unlock() }() @@ -190,7 +193,7 @@ func sendPingReply(w http.ResponseWriter) { } func (router *Router) startInteractionHandlerAsync(interaction discordgo.Interaction, state *interactionState, command *builder.Command) { - log.Debug().Str("id", interaction.ID).Msg("started handling an interaction") + log.Info().Str("id", interaction.ID).Msg("started handling an interaction") // create a timer for the interaction response responseTimer := time.NewTimer(interactionTimeout) @@ -220,7 +223,7 @@ func (router *Router) startInteractionHandlerAsync(interaction discordgo.Interac // we are done, there is nothing else we should do here // lock in case responseCh is still busy sending some data over defer state.mx.Unlock() - log.Debug().Str("id", interaction.ID).Msg("finished handling an interaction") + log.Info().Str("id", interaction.ID).Msg("finished handling an interaction") return case data := <-responseCh: diff --git a/go.mod b/go.mod index 08be4bfe..56825584 100644 --- a/go.mod +++ b/go.mod @@ -10,12 +10,15 @@ require ( github.com/go-co-op/gocron v1.37.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/joho/godotenv v1.5.1 + github.com/nlpodyssey/gopickle v0.3.0 github.com/rs/zerolog v1.33.0 github.com/shopspring/decimal v1.4.0 github.com/steebchen/prisma-client-go v0.37.0 github.com/stretchr/testify v1.9.0 + go.dedis.ch/protobuf v1.0.11 golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 golang.org/x/image v0.16.0 + golang.org/x/sync v0.7.0 golang.org/x/text v0.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index d1ebe577..4b9b9c9d 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= +github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -64,8 +66,18 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= +go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= +go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= +go.dedis.ch/kyber/v3 v3.0.9 h1:i0ZbOQocHUjfFasBiUql5zVeC7u/vahFd96DFA8UOWk= +go.dedis.ch/kyber/v3 v3.0.9/go.mod h1:rhNjUUg6ahf8HEg5HUvVBYoWY4boAafX8tYxX+PS+qg= +go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= +go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= +go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= +go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= @@ -77,6 +89,9 @@ golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +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-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= diff --git a/internal/database/accounts.go b/internal/database/accounts.go new file mode 100644 index 00000000..14330bcf --- /dev/null +++ b/internal/database/accounts.go @@ -0,0 +1,132 @@ +package database + +import ( + "context" + "sync" + "time" + + "github.com/cufee/aftermath/internal/database/prisma/db" +) + +type Account struct { + ID string + Realm string + Nickname string + + Private bool + CreatedAt time.Time + LastBattleTime time.Time + + ClanID string + ClanTag string +} + +func (a Account) FromModel(model db.AccountModel) Account { + a.ID = model.ID + a.Realm = model.Realm + a.Nickname = model.Nickname + a.Private = model.Private + a.CreatedAt = model.CreatedAt + a.LastBattleTime = model.LastBattleTime + return a +} + +func (a *Account) AddClan(model *db.ClanModel) { + a.ClanID = model.ID + a.ClanTag = model.Tag +} + +func (c *client) GetRealmAccounts(ctx context.Context, realm string) ([]Account, error) { + models, err := c.prisma.Account.FindMany(db.Account.Realm.Equals(realm)).With(db.Account.Clan.Fetch()).Exec(ctx) + if err != nil { + return nil, err + } + + var accounts []Account + for _, model := range models { + account := Account{}.FromModel(model) + if clan, ok := model.Clan(); ok { + account.AddClan(clan) + } + accounts = append(accounts, account) + } + + return accounts, nil +} + +func (c *client) GetAccountByID(ctx context.Context, id string) (Account, error) { + model, err := c.prisma.Account.FindUnique(db.Account.ID.Equals(id)).With(db.Account.Clan.Fetch()).Exec(ctx) + if err != nil { + return Account{}, err + } + + account := Account{}.FromModel(*model) + if clan, ok := model.Clan(); ok { + account.AddClan(clan) + } + + return account, nil +} + +func (c *client) GetAccounts(ctx context.Context, ids []string) ([]Account, error) { + if len(ids) < 1 { + return nil, nil + } + + models, err := c.prisma.Account.FindMany(db.Account.ID.In(ids)).With(db.Account.Clan.Fetch()).Exec(ctx) + if err != nil { + return nil, err + } + + var accounts []Account + for _, model := range models { + account := Account{}.FromModel(model) + if clan, ok := model.Clan(); ok { + account.AddClan(clan) + } + accounts = append(accounts, account) + } + + return accounts, nil +} + +func (c *client) UpsertAccounts(ctx context.Context, accounts []Account) map[string]error { + if len(accounts) < 1 { + return nil + } + + var mx sync.Mutex + var wg sync.WaitGroup + errors := make(map[string]error, len(accounts)) + + // we don't really want to exit if one fails + for _, account := range accounts { + wg.Add(1) + go func(account Account) { + defer wg.Done() + optional := []db.AccountSetParam{db.Account.Private.Set(account.Private)} + if account.ClanID != "" { + optional = append(optional, db.Account.Clan.Link(db.Clan.ID.Equals(account.ClanID))) + } + + _, err := c.prisma.Account. + UpsertOne(db.Account.ID.Equals(account.ID)). + Create(db.Account.ID.Set(account.ID), + db.Account.LastBattleTime.Set(account.LastBattleTime), + db.Account.AccountCreatedAt.Set(account.CreatedAt), + db.Account.Realm.Set(account.Realm), + db.Account.Nickname.Set(account.Nickname), + optional..., + ). + Exec(ctx) + if err != nil { + mx.Lock() + errors[account.ID] = err + mx.Unlock() + } + }(account) + } + wg.Wait() + + return errors +} diff --git a/internal/database/averages.go b/internal/database/averages.go index 5a27a3f4..a2e59a64 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -23,7 +23,7 @@ func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string] continue } - transactions = append(transactions, c.Raw.VehicleAverage. + transactions = append(transactions, c.prisma.VehicleAverage. UpsertOne(db.VehicleAverage.ID.Equals(id)). Create( db.VehicleAverage.ID.Set(id), @@ -35,7 +35,7 @@ func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string] ) } - return c.Raw.Prisma.Transaction(transactions...).Exec(ctx) + return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) } func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { @@ -45,7 +45,7 @@ func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[stri qCtx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() - records, err := c.Raw.VehicleAverage.FindMany(db.VehicleAverage.ID.In(ids)).Exec(qCtx) + records, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(ids)).Exec(qCtx) if err != nil { return nil, err } @@ -73,7 +73,7 @@ func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[stri // we can just delete the record and move on ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _, err := c.Raw.VehicleAverage.FindMany(db.VehicleAverage.ID.In(badRecords)).Delete().Exec(ctx) + _, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(badRecords)).Delete().Exec(ctx) if err != nil { log.Err(err).Strs("ids", badRecords).Msg("failed to delete a bad vehicle average records") } diff --git a/internal/database/client.go b/internal/database/client.go index de950fa6..d9225317 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -5,9 +5,15 @@ import ( "github.com/cufee/aftermath/internal/database/prisma/db" "github.com/cufee/aftermath/internal/stats/frame" + "golang.org/x/sync/semaphore" ) type Client interface { + GetAccountByID(ctx context.Context, id string) (Account, error) + GetAccounts(ctx context.Context, ids []string) ([]Account, error) + GetRealmAccounts(ctx context.Context, realm string) ([]Account, error) + UpsertAccounts(ctx context.Context, accounts []Account) map[string]error + GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error GetVehicles(ctx context.Context, ids []string) (map[string]Vehicle, error) @@ -17,12 +23,23 @@ type Client interface { GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) UpdateConnection(ctx context.Context, connection UserConnection) (UserConnection, error) UpsertConnection(ctx context.Context, connection UserConnection) (UserConnection, error) + + CreateTasks(ctx context.Context, tasks ...Task) error + UpdateTasks(ctx context.Context, tasks ...Task) error + DeleteTasks(ctx context.Context, ids ...string) error } -// var _ Client = &client{} // just a marker to see if it is implemented correctly +var _ Client = &client{} type client struct { - Raw *db.PrismaClient + prisma *db.PrismaClient + // Prisma does not currently support updateManyAndReturn + // in order to avoid a case where we + tasksUpdateSem *semaphore.Weighted +} + +func (c *client) Prisma() *db.PrismaClient { + return c.prisma } func NewClient() (*client, error) { @@ -32,5 +49,5 @@ func NewClient() (*client, error) { return nil, err } - return &client{Raw: prisma}, nil + return &client{prisma: prisma, tasksUpdateSem: semaphore.NewWeighted(1)}, nil } diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index f4a0c95d..beff86c1 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -23,8 +23,8 @@ model AppConfiguration { } // -// === Authentification === -// +// === Authentification === +// model AuthNonce { id String @id @default(cuid()) @@ -245,9 +245,9 @@ model Achievement { @@map("glossary_achievements") } -// -// === App Data === -// +// +// === App Data === +// model UserInteraction { id String @id @default(cuid()) @@ -307,3 +307,25 @@ model StatsRequestsOptions { @@index([referenceId]) @@map("stats_request_options") } + +model CronTask { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + type String + referenceId String + targetsEncoded String + + status String + lastRun DateTime + scheduledAfter DateTime + + logsEncoded String @default("") + dataEncoded String @default("") + + @@index([referenceId]) + @@index([status, referenceId, scheduledAfter]) + @@index([lastRun, status]) + @@map("cron_tasks") +} diff --git a/internal/database/tasks.go b/internal/database/tasks.go new file mode 100644 index 00000000..1890cff3 --- /dev/null +++ b/internal/database/tasks.go @@ -0,0 +1,247 @@ +package database + +import ( + "context" + "encoding/json" + "strings" + "time" + + "github.com/cufee/aftermath/internal/database/prisma/db" +) + +type TaskType string + +const ( + TaskTypeUpdateClans TaskType = "UPDATE_CLANS" + TaskTypeRecordSessions TaskType = "RECORD_ACCOUNT_SESSIONS" + TaskTypeUpdateAccountWN8 TaskType = "UPDATE_ACCOUNT_WN8" + TaskTypeRecordPlayerAchievements TaskType = "UPDATE_ACCOUNT_ACHIEVEMENTS" +) + +// Task statuses +type taskStatus string + +const ( + TaskStatusScheduled taskStatus = "TASK_SCHEDULED" + TaskStatusInProgress taskStatus = "TASK_IN_PROGRESS" + TaskStatusComplete taskStatus = "TASK_COMPLETE" + TaskStatusFailed taskStatus = "TASK_FAILED" +) + +type Task struct { + ID string `json:"id"` + Type TaskType `json:"kind"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + ReferenceID string `json:"referenceId"` + Targets []string `json:"targets"` + + Logs []TaskLog `json:"logs"` + + Status taskStatus `json:"status"` + ScheduledAfter time.Time `json:"scheduledAfter"` + LastRun time.Time `json:"lastRun"` + + Data map[string]any `json:"data"` +} + +func (t Task) FromModel(model db.CronTaskModel) Task { + t.ID = model.ID + t.Type = TaskType(model.Type) + + t.Status = taskStatus(model.Status) + t.ReferenceID = model.ReferenceID + + t.LastRun = model.LastRun + t.CreatedAt = model.CreatedAt + t.UpdatedAt = model.UpdatedAt + t.ScheduledAfter = model.ScheduledAfter + + t.decodeData(model.DataEncoded) + t.decodeLogs(model.LogsEncoded) + t.decodeTargets(model.TargetsEncoded) + return t +} + +func (t *Task) LogAttempt(log TaskLog) { + t.Logs = append(t.Logs, log) +} + +func (t *Task) OnCreated() { + t.LastRun = time.Now() + t.CreatedAt = time.Now() + t.UpdatedAt = time.Now() +} +func (t *Task) OnUpdated() { + t.UpdatedAt = time.Now() +} + +func (t *Task) encodeTargets() string { + return strings.Join(t.Targets, ";") +} +func (t *Task) decodeTargets(targets string) { + t.Targets = strings.Split(targets, ";") +} + +func (t *Task) encodeLogs() string { + data, _ := json.Marshal(t.Logs) + return string(data) +} +func (t *Task) decodeLogs(logs string) { + _ = json.Unmarshal([]byte(logs), &t.Logs) +} + +func (t *Task) encodeData() string { + if t.Data == nil { + return "" + } + data, _ := json.Marshal(t.Data) + return string(data) +} +func (t *Task) decodeData(data string) { + t.Data = make(map[string]any) + _ = json.Unmarshal([]byte(data), &t.Data) +} + +type TaskLog struct { + Targets []string `json:"targets" bson:"targets"` + Timestamp time.Time `json:"timestamp" bson:"timestamp"` + Comment string `json:"result" bson:"result"` + Error string `json:"error" bson:"error"` +} + +func NewAttemptLog(task Task, comment, err string) TaskLog { + return TaskLog{ + Targets: task.Targets, + Timestamp: time.Now(), + Comment: comment, + Error: err, + } +} + +/* +GetAndStartTasks retrieves up to limit number of tasks matching the referenceId and updates their status to in progress + - this func will block until all other calls to task update funcs are done +*/ +func (c *client) GetAndStartTasks(ctx context.Context, referenceId string, limit int) ([]Task, error) { + if limit < 1 { + return nil, nil + } + if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { + return nil, err + } + + models, err := c.prisma.CronTask. + FindMany( + db.CronTask.Status.Equals(string(TaskStatusScheduled)), + db.CronTask.ReferenceID.Equals(referenceId), + db.CronTask.ScheduledAfter.Gte(time.Now()), + ). + Take(limit). + Exec(ctx) + if err != nil { + return nil, err + } + + var ids []string + var tasks []Task + for _, model := range models { + tasks = append(tasks, Task{}.FromModel(model)) + ids = append(ids, model.ID) + } + + _, err = c.prisma.CronTask. + FindMany(db.CronTask.ID.In(ids)). + Update(db.CronTask.Status.Set(string(TaskStatusInProgress))). + Exec(ctx) + if err != nil { + return nil, err + } + + return tasks, nil +} + +func (c *client) CreateTasks(ctx context.Context, tasks ...Task) error { + if len(tasks) < 1 { + return nil + } + // we do not block using c.tasksUpdateSem here because the order of ops in GetAndStartTasks makes this safe + + var txns []db.PrismaTransaction + for _, task := range tasks { + task.OnCreated() + txns = append(txns, c.prisma.CronTask.CreateOne( + db.CronTask.Type.Set(string(task.Type)), + db.CronTask.ReferenceID.Set(task.ReferenceID), + db.CronTask.TargetsEncoded.Set(task.encodeTargets()), + db.CronTask.Status.Set(string(task.Status)), + db.CronTask.LastRun.Set(task.LastRun), + db.CronTask.ScheduledAfter.Set(task.ScheduledAfter), + db.CronTask.LogsEncoded.Set(task.encodeLogs()), + db.CronTask.DataEncoded.Set(task.encodeData()), + ).Tx()) + } + + err := c.prisma.Prisma.Transaction(txns...).Exec(ctx) + if err != nil { + return err + } + return nil +} + +/* +UpdateTasks will update all tasks passed in + - the following fields will be replaced: targets, status, leastRun, scheduleAfterm logs, data + - this func will block until all other calls to task update funcs are done +*/ +func (c *client) UpdateTasks(ctx context.Context, tasks ...Task) error { + if len(tasks) < 1 { + return nil + } + + if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { + return err + } + + var txns []db.PrismaTransaction + for _, task := range tasks { + task.OnUpdated() + txns = append(txns, c.prisma.CronTask. + FindUnique(db.CronTask.ID.Equals(task.ID)). + Update( + db.CronTask.TargetsEncoded.Set(task.encodeTargets()), + db.CronTask.Status.Set(string(task.Status)), + db.CronTask.LastRun.Set(task.LastRun), + db.CronTask.ScheduledAfter.Set(task.ScheduledAfter), + db.CronTask.LogsEncoded.Set(task.encodeLogs()), + db.CronTask.DataEncoded.Set(task.encodeData()), + ).Tx()) + } + + err := c.prisma.Prisma.Transaction(txns...).Exec(ctx) + if err != nil { + return err + } + return nil +} + +/* +DeleteTasks will delete all tasks matching by ids + - this func will block until all other calls to task update funcs are done +*/ +func (c *client) DeleteTasks(ctx context.Context, ids ...string) error { + if len(ids) < 1 { + return nil + } + + if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { + return nil + } + + _, err := c.prisma.CronTask.FindMany(db.CronTask.ID.In(ids)).Delete().Exec(ctx) + if err != nil { + return err + } + return nil +} diff --git a/internal/database/users.go b/internal/database/users.go index eee8e046..270e7ab1 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -128,20 +128,6 @@ type UserSubscription struct { ExpiresAt time.Time ReferenceID string Permissions permissions.Permissions - - // id String @id @default(cuid()) - // createdAt DateTime @default(now()) - // updatedAt DateTime @updatedAt - - // user User @relation(fields: [userId], references: [id]) - // userId String - - // type String - // expiresAt DateTime - // referenceId String - // permissions String? - - db.UserSubscriptionModel } type userGetOpts struct { @@ -176,7 +162,7 @@ func (c *client) GetOrCreateUserByID(ctx context.Context, id string, opts ...use user, err := c.GetUserByID(ctx, id, opts...) if err != nil { if db.IsErrNotFound(err) { - model, err := c.Raw.User.CreateOne(db.User.ID.Set(id), db.User.Permissions.Set(permissions.User.Encode())).Exec(ctx) + model, err := c.prisma.User.CreateOne(db.User.ID.Set(id), db.User.Permissions.Set(permissions.User.Encode())).Exec(ctx) if err != nil { return User{}, err } @@ -210,7 +196,7 @@ func (c *client) GetUserByID(ctx context.Context, id string, opts ...userGetOpti fields = append(fields, db.User.Content.Fetch()) } - model, err := c.Raw.User.FindUnique(db.User.ID.Equals(id)).With(fields...).Exec(ctx) + model, err := c.prisma.User.FindUnique(db.User.ID.Equals(id)).With(fields...).Exec(ctx) if err != nil { return User{}, err } @@ -238,7 +224,7 @@ func (c *client) UpdateConnection(ctx context.Context, connection UserConnection return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) } - model, err := c.Raw.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( + model, err := c.prisma.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( db.UserConnection.ReferenceID.Set(connection.ReferenceID), db.UserConnection.Permissions.Set(connection.Permissions.Encode()), db.UserConnection.MetadataEncoded.Set(string(encoded)), @@ -269,7 +255,7 @@ func (c *client) UpsertConnection(ctx context.Context, connection UserConnection return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) } - model, err := c.Raw.UserConnection.CreateOne( + model, err := c.prisma.UserConnection.CreateOne( db.UserConnection.User.Link(db.User.ID.Equals(connection.UserID)), db.UserConnection.Type.Set(string(connection.Type)), db.UserConnection.Permissions.Set(connection.Permissions.Encode()), diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index d231f256..eb16c885 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -55,7 +55,7 @@ func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle continue } - transactions = append(transactions, c.Raw.Vehicle. + transactions = append(transactions, c.prisma.Vehicle. UpsertOne(db.Vehicle.ID.Equals(id)). Create( db.Vehicle.ID.Set(id), @@ -75,7 +75,7 @@ func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle ) } - return c.Raw.Prisma.Transaction(transactions...).Exec(ctx) + return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) } func (c *client) GetVehicles(ctx context.Context, ids []string) (map[string]Vehicle, error) { @@ -83,7 +83,7 @@ func (c *client) GetVehicles(ctx context.Context, ids []string) (map[string]Vehi return nil, nil } - models, err := c.Raw.Vehicle.FindMany(db.Vehicle.ID.In(ids)).Exec(ctx) + models, err := c.prisma.Vehicle.FindMany(db.Vehicle.ID.In(ids)).Exec(ctx) if err != nil { return nil, err } diff --git a/internal/permissions/actions.go b/internal/permissions/actions.go index 55bcfaec..9c5b3b23 100644 --- a/internal/permissions/actions.go +++ b/internal/permissions/actions.go @@ -35,16 +35,16 @@ var ( RemoveUserPersonalContent = fromLsh(32) // Subscriptions - ViewUserSubscriptions Permissions = fromLsh(33) - CreateUserSubscription = fromLsh(34) - ExtendUserSubscription = fromLsh(35) - TerminateUserSubscription = fromLsh(36) + ViewUserSubscriptions Permissions = fromLsh(35) + CreateUserSubscription = fromLsh(36) + ExtendUserSubscription = fromLsh(37) + TerminateUserSubscription = fromLsh(38) // Connections - ViewUserConnections Permissions = fromLsh(37) - CreateUserConnection = fromLsh(38) - UpdateUserConnection = fromLsh(39) - RemoveUserConnection = fromLsh(30) + ViewUserConnections Permissions = fromLsh(40) + CreateUserConnection = fromLsh(41) + UpdateUserConnection = fromLsh(42) + RemoveUserConnection = fromLsh(43) // Restrictions ViewUserRestrictions Permissions = fromLsh(45) @@ -56,7 +56,7 @@ var ( RemoveHardRestriction = fromLsh(51) ) -const ( -// Admin -// _ Permissions = = fromLsh(60) +var ( + // Admin + ViewLogs Permissions = fromLsh(60) ) diff --git a/internal/permissions/roles.go b/internal/permissions/roles.go index 5ffe58ea..364b861d 100644 --- a/internal/permissions/roles.go +++ b/internal/permissions/roles.go @@ -50,5 +50,6 @@ var ( Add(UpdateSoftRestriction). Add(UpdateHardRestriction). Add(RemoveSoftRestriction). - Add(RemoveHardRestriction) + Add(RemoveHardRestriction). + Add(ViewLogs) ) diff --git a/internal/permissions/type.go b/internal/permissions/type.go index 42efc11d..7723547c 100644 --- a/internal/permissions/type.go +++ b/internal/permissions/type.go @@ -6,7 +6,7 @@ import ( "strings" ) -const version = "v3" +const version = "v4" type Permissions struct { value *big.Int diff --git a/internal/stats/fetch/client.go b/internal/stats/fetch/client.go index 8c273edb..b0dd727e 100644 --- a/internal/stats/fetch/client.go +++ b/internal/stats/fetch/client.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/am-wg-proxy-next/v2/types" ) @@ -11,8 +12,7 @@ import ( type AccountStatsOverPeriod struct { Realm string `json:"realm"` - Account types.Account `json:"account"` - Clan types.Clan `json:"clan"` + Account database.Account `json:"account"` PeriodStart time.Time `json:"start"` PeriodEnd time.Time `json:"end"` diff --git a/internal/stats/fetch/convert.go b/internal/stats/fetch/convert.go index e9c11d61..7db9f1c4 100644 --- a/internal/stats/fetch/convert.go +++ b/internal/stats/fetch/convert.go @@ -5,6 +5,7 @@ import ( "strconv" "time" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/am-wg-proxy-next/v2/types" @@ -14,11 +15,25 @@ func timestampToTime(timestamp int) time.Time { return time.Unix(int64(timestamp), 0) } +func wargamingToAccount(realm string, account types.ExtendedAccount, clan types.ClanMember, private bool) database.Account { + return database.Account{ + ID: strconv.Itoa(account.ID), + Realm: realm, + Nickname: account.Nickname, + ClanTag: clan.Clan.Tag, + ClanID: strconv.Itoa(clan.ClanID), + + Private: private, + CreatedAt: timestampToTime(account.CreatedAt), + LastBattleTime: timestampToTime(account.LastBattleTime), + } +} + func wargamingToStats(realm string, accountData types.ExtendedAccount, clanMember types.ClanMember, vehicleData []types.VehicleStatsFrame) AccountStatsOverPeriod { stats := AccountStatsOverPeriod{ - Realm: realm, - Account: accountData.Account, - Clan: clanMember.Clan, + Realm: realm, + // we got the stats, so the account is obv not private at this point + Account: wargamingToAccount(realm, accountData, clanMember, false), RegularBattles: StatsWithVehicles{ StatsFrame: wargamingToFrame(accountData.Statistics.All), Vehicles: wargamingVehiclesToFrame(vehicleData), diff --git a/internal/stats/fetch/replay/errors.go b/internal/stats/fetch/replay/errors.go new file mode 100644 index 00000000..03d97a99 --- /dev/null +++ b/internal/stats/fetch/replay/errors.go @@ -0,0 +1,7 @@ +package replay + +import "errors" + +var ( + ErrInvalidReplayFile = errors.New("invalid replay file") +) diff --git a/internal/stats/fetch/replay/meta.go b/internal/stats/fetch/replay/meta.go new file mode 100644 index 00000000..faa7e7bc --- /dev/null +++ b/internal/stats/fetch/replay/meta.go @@ -0,0 +1,17 @@ +package replay + +type replayMeta struct { + Version string `json:"version"` + Title string `json:"title"` + Dbid string `json:"dbid"` + PlayerName string `json:"playerName"` + BattleStartTime string `json:"battleStartTime"` + PlayerVehicleName string `json:"playerVehicleName"` + MapName string `json:"mapName"` + ArenaUniqueID string `json:"arenaUniqueId"` + BattleDuration float64 `json:"battleDuration"` + VehicleCompDescriptor int `json:"vehicleCompDescriptor"` + CamouflageID int `json:"camouflageId"` + MapID int `json:"mapId"` + ArenaBonusType int `json:"arenaBonusType"` +} diff --git a/internal/stats/fetch/replay/replay.go b/internal/stats/fetch/replay/replay.go new file mode 100644 index 00000000..41236fa5 --- /dev/null +++ b/internal/stats/fetch/replay/replay.go @@ -0,0 +1,228 @@ +package replay + +import ( + "fmt" + "strconv" + "time" + + "github.com/cufee/aftermath/internal/stats/frame" +) + +type battleType struct { + ID int `json:"id" protobuf:"10"` + Tag string `json:"tag" protobuf:"11"` +} + +func (bt battleType) String() string { + return bt.Tag +} + +var ( + BattleTypeUnknown = battleType{-1, "battle_type_unknown"} + BattleTypeRandom = battleType{0, "battle_type_regular"} + BattleTypeSupremacy = battleType{1, "battle_type_supremacy"} +) + +var battleTypes = map[int]battleType{ + 0: BattleTypeRandom, + 1: BattleTypeSupremacy, +} + +type gameMode struct { + ID int `json:"id" protobuf:"15"` + Name string `json:"name" protobuf:"16"` + Special bool `json:"special" protobuf:"17"` // Signifies if WN8 should be calculated +} + +func (gm gameMode) String() string { + return gm.Name +} + +var ( + GameModeUnknown = gameMode{-1, "game_mode_unknown", false} + GameModeRegular = gameMode{1, "game_mode_regular", false} + GameModeTraining = gameMode{2, "game_mode_training", true} + GameModeTournament = gameMode{4, "game_mode_tournament", true} + GameModeQuickTournament = gameMode{5, "game_mode_quick_tournament", true} + GameModeRating = gameMode{7, "game_mode_rating", false} + GameModeMadGames = gameMode{8, "game_mode_mad_games", true} + GameModeRealistic = gameMode{22, "game_mode_realistic", false} + GameModeUprising = gameMode{23, "game_mode_uprising", true} + GameModeGravity = gameMode{24, "game_mode_gravity", true} + GameModeSkirmish = gameMode{25, "game_mode_skirmish", false} + GameModeBurningGames = gameMode{26, "game_mode_burning_games", true} +) + +var gameModes = map[int]gameMode{ + 1: GameModeRegular, + 2: GameModeTraining, + 4: GameModeTournament, + 5: GameModeQuickTournament, + 7: GameModeRating, + 8: GameModeMadGames, + 22: GameModeRealistic, + 23: GameModeUprising, + 24: GameModeGravity, + 25: GameModeSkirmish, + 26: GameModeBurningGames, +} + +type Replay struct { + MapID int `json:"mapId" protobuf:"10"` + GameMode gameMode `json:"gameMode" protobuf:"11"` + BattleType battleType `json:"battleType" protobuf:"12"` + + Victory bool `json:"victory" protobuf:"15"` + BattleTime time.Time `json:"battleTime" protobuf:"16"` + BattleDuration int `json:"battleDuration" protobuf:"17"` + + Spoils Spoils `json:"spoils" protobuf:"20"` + Protagonist Player `json:"protagonist" protobuf:"21"` + + Teams Teams `json:"teams" protobuf:"22"` +} + +func Prettify(battle battleResults, meta replayMeta) *Replay { + var replay Replay + + replay.GameMode = GameModeUnknown + if gm, ok := gameModes[int(battle.RoomType)]; ok { + replay.GameMode = gm + } + + // ModeAndMap + replay.BattleType = BattleTypeUnknown + if bt, ok := battleTypes[battle.GameMode()]; ok { + replay.BattleType = bt + } + + replay.MapID = battle.MapID() + ts, _ := strconv.ParseInt(meta.BattleStartTime, 10, 64) + replay.BattleTime = time.Unix(ts, 0) + replay.BattleDuration = int(meta.BattleDuration) + + replay.Spoils = Spoils{ + Exp: frame.ValueInt(battle.Author.TotalXP), + Credits: frame.ValueInt(battle.Author.TotalCredits), + // TODO: Find where mastery is set + // MasteryBadge: data.MasteryBadge, + } + + var allyTeam uint32 + players := make(map[int]playerInfo) + for _, p := range battle.Players { + players[int(p.AccountID)] = p.Info + if p.AccountID == battle.Author.AccountID { + allyTeam = p.Info.Team + } + } + for _, result := range battle.PlayerResults { + info, ok := players[int(result.Info.AccountID)] + if !ok { + continue + } + player := playerFromData(battle, info, result.Info) + if player.ID == fmt.Sprint(battle.Author.AccountID) { + replay.Protagonist = player + } + if info.Team == allyTeam { + replay.Teams.Allies = append(replay.Teams.Allies, player) + } else { + replay.Teams.Enemies = append(replay.Teams.Enemies, player) + } + } + + replay.Victory = allyTeam == battle.WinnerTeam + return &replay +} + +type Teams struct { + Allies []Player `json:"allies" protobuf:"10,repeated"` + Enemies []Player `json:"enemies" protobuf:"11,repeated"` +} + +type Player struct { + ID string `json:"id" protobuf:"10"` + ClanID string `json:"clanId" protobuf:"11"` + ClanTag string `json:"clanTag" protobuf:"12"` + Nickname string `json:"nickname" protobuf:"13"` + + VehicleID string `json:"vehicleId" protobuf:"15"` + PlatoonID *int `json:"platoonId" protobuf:"16"` + TimeAlive frame.ValueInt `json:"timeAlive" protobuf:"17"` + HPLeft frame.ValueInt `json:"hpLeft" protobuf:"18"` + + Performance Performance `json:"performance" protobuf:"20"` + Achievements map[string]int `json:"achievements" protobuf:"21"` +} + +func playerFromData(battle battleResults, info playerInfo, result playerResultsInfo) Player { + var player Player + player.ID = fmt.Sprint(result.AccountID) + player.Nickname = info.Nickname + player.VehicleID = fmt.Sprint(result.TankID) + if info.ClanTag != nil && info.ClanID != nil { + player.ClanTag = *info.ClanTag + player.ClanID = fmt.Sprint(*info.ClanID) + } + + if info.PlatoonID != nil { + id := int(*info.PlatoonID) + player.PlatoonID = &id + } + + var stats frame.StatsFrame + stats.Battles = 1 + if info.Team == battle.WinnerTeam { + stats.BattlesWon = 1 + } + + if result.HitpointsLeft != nil { + player.HPLeft = frame.ValueInt(*result.HitpointsLeft) + } + if player.HPLeft > 0 { + stats.BattlesSurvived = 1 + } + + stats.DamageDealt = frame.ValueInt(result.DamageDealt) + stats.DamageReceived = frame.ValueInt(result.DamageReceived) + stats.ShotsHit = frame.ValueInt(result.ShotsHit) + stats.ShotsFired = frame.ValueInt(result.ShotsFired) + stats.Frags = frame.ValueInt(result.EnemiesDestroyed) + stats.MaxFrags = frame.ValueInt(stats.Frags) + stats.EnemiesSpotted = frame.ValueInt(result.DamageAssisted) + // TODO: Parse this from replays, it seems that those fields are only present when a battle was won by cap + // frame.CapturePoints = + // frame.DroppedCapturePoints = + player.Performance = Performance{ + DamageBlocked: frame.ValueInt(result.DamageBlocked), + DamageReceived: frame.ValueInt(result.DamageReceived), + DamageAssisted: frame.ValueInt(result.DamageAssisted + result.DamageAssistedTrack), + DistanceTraveled: frame.ValueInt(result.DistanceTraveled), + StatsFrame: stats, + } + + // player.Achievements = make(map[int]int) + // for _, a := range append(result.Achievements, result.AchievementsOther...) { + // player.Achievements[int(a.Tag)] = int(a.Value) + // } + + return player +} + +type Performance struct { + DamageBlocked frame.ValueInt `json:"damageBlocked" protobuf:"10"` + DamageReceived frame.ValueInt `json:"damageReceived" protobuf:"11"` + DamageAssisted frame.ValueInt `json:"damageAssisted" protobuf:"12"` + DistanceTraveled frame.ValueInt `json:"distanceTraveled" protobuf:"13"` + SupremacyPointsEarned frame.ValueInt `json:"supremacyPointsEarned" protobuf:"14"` + SupremacyPointsStolen frame.ValueInt `json:"supremacyPointsStolen" protobuf:"15"` + + frame.StatsFrame `json:",inline" protobuf:"20"` +} + +type Spoils struct { + Exp frame.ValueInt `json:"exp" protobuf:"10"` + Credits frame.ValueInt `json:"credits" protobuf:"11"` + MasteryBadge int `json:"masteryBadge" protobuf:"12"` +} diff --git a/internal/stats/fetch/replay/results.go b/internal/stats/fetch/replay/results.go new file mode 100644 index 00000000..92d963fa --- /dev/null +++ b/internal/stats/fetch/replay/results.go @@ -0,0 +1,181 @@ +package replay + +import ( + "io" + + "github.com/nlpodyssey/gopickle/pickle" + "github.com/nlpodyssey/gopickle/types" + "go.dedis.ch/protobuf" +) + +type battleResults struct { + ModeAndMap uint32 `protobuf:"1" json:"modeAndMap"` + Timestamp uint64 `protobuf:"2" json:"timestamp"` + WinnerTeam uint32 `protobuf:"3" json:"winnerTeam"` + + EnemiesDestroyed uint32 `protobuf:"4" json:"enemiesKilled"` + TimeAlive uint32 `protobuf:"5" json:"timeAlive"` + + Author author `protobuf:"8,required" json:"protagonist"` + RoomType uint32 `protobuf:"9" json:"roomType"` + + RepairCost uint32 `protobuf:"136" json:"repairCost"` + FreeXP uint32 `protobuf:"137" json:"freeXp"` + + TotalXP uint32 `protobuf:"181" json:"totalXp"` + BaseFreeXP uint32 `protobuf:"182" json:"freeXpBase"` + CreditsBase uint32 `protobuf:"183" json:"creditsBase"` + + // 185 { + // 1: "None" + // 2 { + // 27 { + // 1: 2020 + // 2: 63 + // } + // } + // } + // 185 { + // 1: "booster" + // 2 { + // 1: 21959 + // 2: 10 + // } + // } + + Players []player `protobuf:"201,repeated" json:"players"` + PlayerResults []playerResults `protobuf:"301,repeated" json:"playerResults"` +} + +func (result *battleResults) MapID() int { + return int(result.ModeAndMap & 0xFFFF) +} + +func (result *battleResults) GameMode() int { + /// - `0x0xxxx` is «encounter» + /// - `0x1xxxx` flag is «supremacy» + return int((result.ModeAndMap >> 16) & 0xFF) +} + +type player struct { + AccountID uint32 `protobuf:"1" json:"accountId"` + Info playerInfo `protobuf:"2,required" json:"info"` +} + +type playerInfo struct { + Nickname string `protobuf:"1" json:"nickname"` + PlatoonID *uint32 `protobuf:"2,optional" json:"platoon"` + Team uint32 `protobuf:"3" json:"team"` + ClanID *uint32 `protobuf:"4,optional" json:"clanId"` + ClanTag *string `protobuf:"5,optional" json:"clanTag"` + // 6: "\000\000" + Avatar avatar `protobuf:"7,required" json:"avatar"` + // 8: 463102 + Rank *uint32 `protobuf:"9,optional" json:"rank"` +} + +type playerResults struct { + ResultID uint32 `protobuf:"1" json:"id"` + Info playerResultsInfo `protobuf:"2,required" json:"result"` +} + +type playerResultsInfo struct { + HitpointsLeft *uint64 `protobuf:"1,optional" json:"hitpointsLeft"` + CreditsEarned uint32 `protobuf:"2" json:"creditsEarned"` + BaseXP uint32 `protobuf:"3" json:"baseXp"` + ShotsFired uint32 `protobuf:"4" json:"shotsFired"` + ShotsHit uint32 `protobuf:"5" json:"shotsHit"` + ShotsPenetrated uint32 `protobuf:"7" json:"shotsPenetrated"` + DamageDealt uint32 `protobuf:"8" json:"damageDealt"` + DamageAssisted uint32 `protobuf:"9" json:"damageAssisted"` + DamageAssistedTrack uint32 `protobuf:"10" json:"damageAssistedTrack"` + DamageReceived uint32 `protobuf:"11" json:"damageReceived"` + HitsReceived uint32 `protobuf:"12" json:"hitsReceived"` + HitsBlocked uint32 `protobuf:"13" json:"hitsBlocked"` + HitsPenetrated uint32 `protobuf:"15" json:"hitsPenetrated"` + EnemiesSpotted uint32 `protobuf:"16" json:"enemiesSpotted"` + EnemiesDamaged uint32 `protobuf:"17" json:"enemiesDamaged"` + EnemiesDestroyed uint32 `protobuf:"18" json:"enemiesDestroyed"` + DistanceTraveled uint32 `protobuf:"23" json:"distanceTraveled"` + TimeAlive uint32 `protobuf:"24" json:"timeAlive"` + KilledByAccountID uint32 `protobuf:"25" json:"killedBy"` + // 26: "\323\003" + Achievements []Achievement `protobuf:"27,repeated" json:"achievements"` + AchievementsOther []Achievement `protobuf:"28,repeated" json:"achievementsOther"` + DamageXP uint32 `protobuf:"29" json:"damageXp"` + AssistXP uint32 `protobuf:"30" json:"assistXp"` + TeamBonusXP uint32 `protobuf:"31" json:"teamBonusXp"` + SupremacyPointsEarned uint32 `protobuf:"32" json:"supremacyPointsEarned"` + SupremacyPointsStolen uint32 `protobuf:"33" json:"supremacyPointsStolen"` + + AccountID uint32 `protobuf:"101" json:"accountId"` + Team uint32 `protobuf:"102" json:"team"` + TankID uint32 `protobuf:"103" json:"tankId"` + // 105: 18446744073709551615 + // 106: 80120 + MMRating float32 `protobuf:"107" json:"mmRating"` + DamageBlocked uint32 `protobuf:"117" json:"damageBlocked"` +} + +func (results *playerResultsInfo) DisplayRating() float32 { + return results.MMRating*10 + 3000 +} + +type Achievement struct { + Tag uint32 `protobuf:"1" json:"tag"` + Value uint32 `protobuf:"2" json:"value"` +} + +const PlayerAfkHitpointsLeft = -2 + +type author struct { + // The field is PlayerAfkHitpointsLeft if the player is auto-destroyed by inactivity. + HitpointsLeft int32 `protobuf:"1" json:"hitpointsLeft"` + TotalCredits uint32 `protobuf:"2" json:"creditsEarned"` + TotalXP uint32 `protobuf:"3" json:"xpEarned"` + ShotsFired uint32 `protobuf:"4" json:"shotsFired"` + ShotsHit uint32 `protobuf:"5" json:"shotsHit"` + ShotsSplashHit uint32 `protobuf:"6" json:"shotsSplashHit"` + ShotsPenetrated uint32 `protobuf:"7" json:"shotsPenetrated"` + DamageDealt uint32 `protobuf:"8" json:"damageDealt"` + + AccountID uint32 `protobuf:"101" json:"accountId"` + Team uint32 `protobuf:"102" json:"team"` +} + +type avatar struct { + // 1: 312062 id? + Info avatarInfo `protobuf:"2,required" json:"data"` +} + +type avatarInfo struct { + // 1: 312062 id? + GfxURL string `protobuf:"2" json:"gfxUrl"` + Gfx2URL string `protobuf:"3" json:"gfx2Url"` + Kind string `protobuf:"4" json:"kind"` +} + +// Un-pickled `battle_results.dat`. +// +// # `battle_results.dat` structure +// +// Entire file is a [pickled](https://docs.python.org/3/library/pickle.html) 2-tuple: +// +// - Arena unique ID +// - Battle results serialized with [Protocol Buffers](https://developers.google.com/protocol-buffers) +func decodeBattleResults(reader io.Reader) (*battleResults, error) { + unpickler := pickle.NewUnpickler(reader) + data, err := unpickler.Load() + if err != nil { + return nil, err + } + + if tuple, ok := data.(*types.Tuple); ok && tuple.Len() == 2 { + if data, ok := tuple.Get(1).(string); ok { + var result battleResults + return &result, protobuf.Decode([]byte(data), &result) + } + return nil, ErrInvalidReplayFile + } + return nil, ErrInvalidReplayFile +} diff --git a/internal/stats/fetch/replay/unpack.go b/internal/stats/fetch/replay/unpack.go new file mode 100644 index 00000000..65903a6d --- /dev/null +++ b/internal/stats/fetch/replay/unpack.go @@ -0,0 +1,102 @@ +package replay + +import ( + "archive/zip" + "bytes" + "encoding/json" + "io" + "net/http" + "strconv" + + "github.com/rs/zerolog/log" +) + +func UnpackRemote(link string) (*UnpackedReplay, error) { + resp, err := http.DefaultClient.Get(link) + if err != nil { + return nil, ErrInvalidReplayFile + } + defer resp.Body.Close() + + // Convert 10 MB to bytes + const maxFileSize = 10 * 1024 * 1024 + + // Check the Content-Length header + contentLengthStr := resp.Header.Get("Content-Length") + if contentLengthStr == "" { + log.Warn().Msg("Content-Length header is missing on remote replay file") + return nil, ErrInvalidReplayFile + } + + contentLength, err := strconv.ParseInt(contentLengthStr, 10, 64) + if err != nil { + return nil, err + } + if contentLength > maxFileSize { + return nil, ErrInvalidReplayFile + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return Unpack(bytes.NewReader(data), contentLength) +} + +// World of Tanks Blitz replay. +// +// # Replay structure +// +// `*.wotbreplay` is a ZIP-archive containing: +// +// - `battle_results.dat` +// - `meta.json` +// - `data.wotreplay` + +type UnpackedReplay struct { + BattleResult battleResults `json:"results"` + Meta replayMeta `json:"meta"` +} + +func Unpack(file io.ReaderAt, size int64) (*UnpackedReplay, error) { + archive, err := newZipFromReader(file, size) + if err != nil { + return nil, err + } + if len(archive.File) < 3 { + return nil, ErrInvalidReplayFile + } + + var data UnpackedReplay + + resultsDat, err := archive.Open("battle_results.dat") + if err != nil { + return nil, ErrInvalidReplayFile + } + result, err := decodeBattleResults(resultsDat) + if err != nil { + return nil, ErrInvalidReplayFile + } + data.BattleResult = *result + + meta, err := archive.Open("meta.json") + if err != nil { + return nil, ErrInvalidReplayFile + } + metaBytes, err := io.ReadAll(meta) + if err != nil { + return nil, err + } + + return &data, json.Unmarshal(metaBytes, &data.Meta) +} + +func newZipFromReader(file io.ReaderAt, size int64) (*zip.Reader, error) { + reader, err := zip.NewReader(file, size) + if err != nil { + return nil, err + } + + return reader, nil +} diff --git a/internal/stats/prepare/replay/replay.go.todo b/internal/stats/prepare/replay/replay.go.todo deleted file mode 100644 index 3f255378..00000000 --- a/internal/stats/prepare/replay/replay.go.todo +++ /dev/null @@ -1,224 +0,0 @@ -package prepare - -import ( - "time" -) - -type battleType struct { - ID int `json:"id" protobuf:"10"` - Tag string `json:"tag" protobuf:"11"` -} - -func (bt battleType) String() string { - return bt.Tag -} - -var ( - BattleTypeUnknown = battleType{-1, "battle_type_unknown"} - BattleTypeRandom = battleType{0, "battle_type_regular"} - BattleTypeSupremacy = battleType{1, "battle_type_supremacy"} -) - -var battleTypes = map[int]battleType{ - 0: BattleTypeRandom, - 1: BattleTypeSupremacy, -} - -type gameMode struct { - ID int `json:"id" protobuf:"15"` - Name string `json:"name" protobuf:"16"` - Special bool `json:"special" protobuf:"17"` // Signifies if WN8 should be calculated -} - -func (gm gameMode) String() string { - return gm.Name -} - -var ( - GameModeUnknown = gameMode{-1, "game_mode_unknown", false} - GameModeRegular = gameMode{1, "game_mode_regular", false} - GameModeTraining = gameMode{2, "game_mode_training", true} - GameModeTournament = gameMode{4, "game_mode_tournament", true} - GameModeQuickTournament = gameMode{5, "game_mode_quick_tournament", true} - GameModeRating = gameMode{7, "game_mode_rating", false} - GameModeMadGames = gameMode{8, "game_mode_mad_games", true} - GameModeRealistic = gameMode{22, "game_mode_realistic", false} - GameModeUprising = gameMode{23, "game_mode_uprising", true} - GameModeGravity = gameMode{24, "game_mode_gravity", true} - GameModeSkirmish = gameMode{25, "game_mode_skirmish", false} - GameModeBurningGames = gameMode{26, "game_mode_burning_games", true} -) - -var gameModes = map[int]gameMode{ - 1: GameModeRegular, - 2: GameModeTraining, - 4: GameModeTournament, - 5: GameModeQuickTournament, - 7: GameModeRating, - 8: GameModeMadGames, - 22: GameModeRealistic, - 23: GameModeUprising, - 24: GameModeGravity, - 25: GameModeSkirmish, - 26: GameModeBurningGames, -} - -type Replay struct { - MapID int `json:"mapId" protobuf:"10"` - GameMode gameMode `json:"gameMode" protobuf:"11"` - BattleType battleType `json:"battleType" protobuf:"12"` - - Victory bool `json:"victory" protobuf:"15"` - BattleTime time.Time `json:"battleTime" protobuf:"16"` - BattleDuration int `json:"battleDuration" protobuf:"17"` - - Spoils Spoils `json:"spoils" protobuf:"20"` - Protagonist Player `json:"protagonist" protobuf:"21"` - - Teams Teams `json:"teams" protobuf:"22"` -} - -// func Prettify(battle battleResults, meta replayMeta) *Replay { -// var replay Replay - -// replay.GameMode = GameModeUnknown -// if gm, ok := gameModes[int(battle.RoomType)]; ok { -// replay.GameMode = gm -// } - -// // ModeAndMap -// replay.BattleType = BattleTypeUnknown -// if bt, ok := battleTypes[battle.GameMode()]; ok { -// replay.BattleType = bt -// } - -// replay.MapID = battle.MapID() -// ts, _ := strconv.ParseInt(meta.BattleStartTime, 10, 64) -// replay.BattleTime = time.Unix(ts, 0) -// replay.BattleDuration = int(meta.BattleDuration) - -// replay.Spoils = Spoils{ -// Exp: int(battle.Author.TotalXP), -// Credits: int(battle.Author.TotalCredits), -// // TODO: Find where mastery is set -// // MasteryBadge: data.MasteryBadge, -// } - -// var allyTeam uint32 -// players := make(map[int]playerInfo) -// for _, p := range battle.Players { -// players[int(p.AccountID)] = p.Info -// if p.AccountID == battle.Author.AccountID { -// allyTeam = p.Info.Team -// } -// } -// for _, result := range battle.PlayerResults { -// info, ok := players[int(result.Info.AccountID)] -// if !ok { -// continue -// } -// player := playerFromData(battle, info, result.Info) -// if player.ID == int(battle.Author.AccountID) { -// replay.Protagonist = player -// } -// if info.Team == allyTeam { -// replay.Teams.Allies = append(replay.Teams.Allies, player) -// } else { -// replay.Teams.Enemies = append(replay.Teams.Enemies, player) -// } -// } - -// replay.Victory = allyTeam == battle.WinnerTeam -// return &replay -// } - -type Teams struct { - Allies []Player `json:"allies" protobuf:"10,repeated"` - Enemies []Player `json:"enemies" protobuf:"11,repeated"` -} - -type Player struct { - ID int `json:"id" protobuf:"10"` - ClanID int `json:"clanId" protobuf:"11"` - ClanTag string `json:"clanTag" protobuf:"12"` - Nickname string `json:"nickname" protobuf:"13"` - - VehicleID int `json:"vehicleId" protobuf:"15"` - PlatoonID *int `json:"platoonId" protobuf:"16"` - TimeAlive ValueInt `json:"timeAlive" protobuf:"17"` - HPLeft ValueInt `json:"hpLeft" protobuf:"18"` - - Performance Performance `json:"performance" protobuf:"20"` - // Achievements map[int]int `json:"achievements" protobuf:"21"` -} - -// func playerFromData(battle battleResults, info playerInfo, result playerResultsInfo) Player { -// var player Player -// player.ID = int(result.AccountID) -// player.Nickname = info.Nickname -// player.VehicleID = int(result.TankID) -// if info.ClanTag != nil && info.ClanID != nil { -// player.ClanTag = *info.ClanTag -// player.ClanID = int(*info.ClanID) -// } - -// if info.PlatoonID != nil { -// id := int(*info.PlatoonID) -// player.PlatoonID = &id -// } - -// var frame stats.ReducedStatsFrame -// frame.Battles = 1 -// if info.Team == battle.WinnerTeam { -// frame.BattlesWon = 1 -// } - -// if result.HitpointsLeft != nil { -// player.HPLeft = int(*result.HitpointsLeft) -// } -// if player.HPLeft > 0 { -// frame.BattlesSurvived = 1 -// } - -// frame.DamageDealt = int(result.DamageDealt) -// frame.DamageReceived = int(result.DamageReceived) -// frame.ShotsHit = int(result.ShotsHit) -// frame.ShotsFired = int(result.ShotsFired) -// frame.Frags = int(result.EnemiesDestroyed) -// frame.MaxFrags = frame.Frags -// frame.EnemiesSpotted = int(result.DamageAssisted) -// // TODO: Parse this from replays, it seems that those fields are only present when a battle was won by cap -// // frame.CapturePoints = -// // frame.DroppedCapturePoints = -// player.Performance = Performance{ -// DamageBlocked: int(result.DamageBlocked), -// DamageReceived: int(result.DamageReceived), -// DamageAssisted: int(result.DamageAssisted + result.DamageAssistedTrack), -// DistanceTraveled: int(result.DistanceTraveled), -// ReducedStatsFrame: frame, -// } - -// player.Achievements = make(map[int]int) -// for _, a := range append(result.Achievements, result.AchievementsOther...) { -// player.Achievements[int(a.Tag)] = int(a.Value) -// } - -// return player -// } - -type Performance struct { - DamageBlocked ValueInt `json:"damageBlocked" protobuf:"10"` - DamageReceived ValueInt `json:"damageReceived" protobuf:"11"` - DamageAssisted ValueInt `json:"damageAssisted" protobuf:"12"` - DistanceTraveled ValueInt `json:"distanceTraveled" protobuf:"13"` - SupremacyPointsEarned ValueInt `json:"supremacyPointsEarned" protobuf:"14"` - SupremacyPointsStolen ValueInt `json:"supremacyPointsStolen" protobuf:"15"` - - StatsFrame `json:",inline" protobuf:"20"` -} - -type Spoils struct { - Exp ValueInt `json:"exp" protobuf:"10"` - Credits ValueInt `json:"credits" protobuf:"11"` - MasteryBadge int `json:"masteryBadge" protobuf:"12"` -} diff --git a/internal/stats/render/period/cards.go b/internal/stats/render/period/cards.go index 787a7817..7c23d2f1 100644 --- a/internal/stats/render/period/cards.go +++ b/internal/stats/render/period/cards.go @@ -28,7 +28,7 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs { { titleStyle := common.DefaultPlayerTitleStyle(titleCardStyle(cardWidth)) - clanSize := common.MeasureString(stats.Clan.Tag, *titleStyle.ClanTag.Font) + clanSize := common.MeasureString(stats.Account.ClanTag, *titleStyle.ClanTag.Font) nameSize := common.MeasureString(stats.Account.Nickname, *titleStyle.Nickname.Font) cardWidth = common.Max(cardWidth, titleStyle.TotalPaddingAndGaps()+nameSize.TotalWidth+clanSize.TotalWidth*2) } @@ -118,7 +118,7 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs } // Player Title card - segments.AddContent(common.NewPlayerTitleCard(common.DefaultPlayerTitleStyle(titleCardStyle(cardWidth)), stats.Account.Nickname, stats.Clan.Tag, subs)) + segments.AddContent(common.NewPlayerTitleCard(common.DefaultPlayerTitleStyle(titleCardStyle(cardWidth)), stats.Account.Nickname, stats.Account.ClanTag, subs)) // Overview Card { diff --git a/internal/stats/render/replay/blocks.go.todo b/internal/stats/render/replay/blocks.go.todo new file mode 100644 index 00000000..ab08e47b --- /dev/null +++ b/internal/stats/render/replay/blocks.go.todo @@ -0,0 +1,108 @@ +package replay + +import ( + "fmt" + "math" + + "github.com/cufee/aftermath-core/dataprep" + "github.com/cufee/aftermath-core/dataprep/replay" + prepare "github.com/cufee/aftermath/internal/stats/prepare/replay" + "github.com/cufee/aftermath/internal/stats/render/common" +) + +func newTitleBlock(replay *prepare.Replay, width float64, printer func(string) string) common.Block { + var titleBlocks []common.Block + if replay.Victory { + titleBlocks = append(titleBlocks, common.NewTextContent(common.Style{ + Font: &common.FontLarge, + FontColor: common.TextPrimary, + }, printer("label_victory"))) + } else { + titleBlocks = append(titleBlocks, common.NewTextContent(common.Style{ + Font: &common.FontLarge, + FontColor: common.TextPrimary, + }, printer("label_defeat"))) + } + + titleBlocks = append(titleBlocks, common.NewTextContent(common.Style{ + Font: &common.FontLarge, + FontColor: common.TextSecondary, + }, fmt.Sprintf(" - %s", printer("label_"+replay.BattleType.String())))) + + style := defaultCardStyle(width, 75) + style.JustifyContent = common.JustifyContentCenter + style.Direction = common.DirectionHorizontal + style.AlignItems = common.AlignItemsCenter + + return common.NewBlocksContent(style, titleBlocks...) +} + +func newPlayerCard(style common.Style, sizes map[dataprep.Tag]float64, card replay.Card, player parse.Player, ally, protagonist bool) common.Block { + hpBarValue := float64(player.HPLeft) / float64((player.Performance.DamageReceived + player.HPLeft)) + if hpBarValue > 0 { + hpBarValue = math.Max(hpBarValue, 0.2) + } + + var hpBar common.Block + if ally { + hpBar = newProgressBar(60, int(hpBarValue*100), progressDirectionVertical, hpBarColorAllies) + } else { + hpBar = newProgressBar(60, int(hpBarValue*100), progressDirectionVertical, hpBarColorEnemies) + } + + vehicleColor := common.TextPrimary + if player.HPLeft <= 0 { + vehicleColor = common.TextSecondary + } + + leftBlock := common.NewBlocksContent(common.Style{ + Direction: common.DirectionHorizontal, + AlignItems: common.AlignItemsCenter, + Gap: 10, + Height: 80, + // Debug: true, + }, hpBar, common.NewBlocksContent(common.Style{Direction: common.DirectionVertical}, + common.NewTextContent(common.Style{Font: &common.FontLarge, FontColor: vehicleColor}, card.Title), + playerNameBlock(player, protagonist), + )) + + var rightBlocks []common.Block + for _, block := range card.Blocks { + rightBlocks = append(rightBlocks, statsBlockToBlock(block, sizes[block.Tag])) + } + rightBlock := common.NewBlocksContent(common.Style{ + JustifyContent: common.JustifyContentCenter, + AlignItems: common.AlignItemsCenter, + Gap: 10, + // Debug: true, + }, rightBlocks...) + + style.Direction = common.DirectionHorizontal + style.AlignItems = common.AlignItemsCenter + style.JustifyContent = common.JustifyContentSpaceBetween + // style.Debug = true + + return common.NewBlocksContent(style, leftBlock, rightBlock) +} + +func playerNameBlock(player prepare.Player, protagonist bool) common.Block { + nameColor := common.TextSecondary + if protagonist { + nameColor = protagonistColor + } + + var nameBlocks []common.Block + nameBlocks = append(nameBlocks, common.NewTextContent(common.Style{ + Font: &common.FontLarge, + FontColor: nameColor, + // Debug: true, + }, player.Nickname)) + if player.ClanTag != "" { + nameBlocks = append(nameBlocks, common.NewTextContent(common.Style{ + FontColor: common.TextSecondary, + Font: &common.FontLarge, + // Debug: true, + }, fmt.Sprintf("[%s]", player.ClanTag))) + } + return common.NewBlocksContent(common.Style{Direction: common.DirectionHorizontal, Gap: 5, AlignItems: common.AlignItemsCenter}, nameBlocks...) +} diff --git a/internal/stats/render/replay/cards.go.todo b/internal/stats/render/replay/cards.go.todo new file mode 100644 index 00000000..277b3384 --- /dev/null +++ b/internal/stats/render/replay/cards.go.todo @@ -0,0 +1,90 @@ +package replay + +import ( + "fmt" + "image" + + "github.com/cufee/aftermath-core/dataprep" + "github.com/cufee/aftermath-core/dataprep/replay" + "golang.org/x/text/language" +) + +// Tank Name // WN8 Winrate Damage Blocker Assisted Kills +// Player Name [Tag] + +type ReplayData struct { + Cards replay.Cards + Replay *parse.Replay +} + +type RenderOptions struct { + Locale language.Tag +} + +func RenderReplayImage(data ReplayData, opts RenderOptions) (image.Image, error) { + var alliesBlocks, enemiesBlocks []render.Block + + printer := localization.GetPrinter(opts.Locale) + + var playerNameWidth float64 + statsSizes := make(map[dataprep.Tag]float64) + for _, card := range append(data.Cards.Allies, data.Cards.Enemies...) { + // Measure player name and tag or vehicle name + name := card.Meta.Player.Nickname + if card.Meta.Player.ClanTag != "" { + name += fmt.Sprintf(" [%s]", card.Meta.Player.ClanTag) + } + nameSize := render.MeasureString(name, render.FontLarge) + tankSize := render.MeasureString(card.Title, render.FontLarge) + size := nameSize + if tankSize.TotalWidth > nameSize.TotalWidth { + size = tankSize + } + if size.TotalWidth > playerNameWidth { + playerNameWidth = size.TotalWidth + } + + // Measure stats value and label + for _, block := range card.Blocks { + valueSize := render.MeasureString(block.Value.String, render.FontLarge) + labelSize := render.MeasureString(block.Label, render.FontSmall) + w := valueSize.TotalWidth + if labelSize.TotalWidth > valueSize.TotalWidth { + w = labelSize.TotalWidth + } + if w > statsSizes[block.Tag] { + statsSizes[block.Tag] = w + } + } + } + + var totalStatsWidth float64 + for _, width := range statsSizes { + totalStatsWidth += width + } + + playerStatsCardStyle := defaultCardStyle(playerNameWidth+(float64(len(statsSizes)*10))+totalStatsWidth, 0) + totalCardsWidth := (playerStatsCardStyle.Width * 2) - 30 + + // Allies + for _, card := range data.Cards.Allies { + alliesBlocks = append(alliesBlocks, newPlayerCard(playerStatsCardStyle, statsSizes, card, card.Meta.Player, true, card.Meta.Player.ID == data.Replay.Protagonist.ID)) + } + // Enemies + for _, card := range data.Cards.Enemies { + enemiesBlocks = append(enemiesBlocks, newPlayerCard(playerStatsCardStyle, statsSizes, card, card.Meta.Player, false, false)) + } + + // Title Card + titleBlock := newTitleBlock(data.Replay, totalCardsWidth, printer) + + // Teams + var teamsBlocks []render.Block + teamsBlocks = append(teamsBlocks, render.NewBlocksContent(render.Style{Direction: render.DirectionVertical, Gap: 10}, alliesBlocks...)) + teamsBlocks = append(teamsBlocks, render.NewBlocksContent(render.Style{Direction: render.DirectionVertical, Gap: 10}, enemiesBlocks...)) + playersBlock := render.NewBlocksContent(render.Style{Direction: render.DirectionHorizontal, Gap: 10}, teamsBlocks...) + teamsBlock := render.NewBlocksContent(render.Style{Direction: render.DirectionVertical, Gap: 10}, playersBlock) + + frame := render.NewBlocksContent(frameStyle, titleBlock, teamsBlock) + return frame.Render() +} diff --git a/internal/stats/render/replay/constants.go.todo b/internal/stats/render/replay/constants.go.todo new file mode 100644 index 00000000..d7404b02 --- /dev/null +++ b/internal/stats/render/replay/constants.go.todo @@ -0,0 +1,27 @@ +package replay + +import ( + "image/color" + + "github.com/cufee/aftermath-core/internal/logic/render" +) + +var ( + frameStyle = render.Style{Direction: render.DirectionVertical, PaddingX: 30, PaddingY: 30, Gap: 10} + + hpBarColorAllies = color.RGBA{R: 120, G: 255, B: 120, A: 255} + hpBarColorEnemies = color.RGBA{R: 255, G: 120, B: 120, A: 255} + + protagonistColor = color.RGBA{255, 223, 0, 255} +) + +func defaultCardStyle(width, height float64) render.Style { + return render.Style{ + Direction: render.DirectionVertical, + Width: width + 40, + Height: height, + BackgroundColor: render.DefaultCardColor, + PaddingX: 10, + BorderRadius: 15, + } +} diff --git a/internal/stats/render/replay/elements.go.todo b/internal/stats/render/replay/elements.go.todo new file mode 100644 index 00000000..fd8fe1b3 --- /dev/null +++ b/internal/stats/render/replay/elements.go.todo @@ -0,0 +1,48 @@ +package replay + +import ( + "image/color" + + "github.com/cufee/aftermath-core/internal/logic/render" + "github.com/fogleman/gg" +) + +type progressDirection int + +const ( + progressDirectionHorizontal progressDirection = iota + progressDirectionVertical +) + +const progressBarWidth = 8 + +func newProgressBar(size int, progress int, direction progressDirection, fillColor color.Color) render.Block { + var width, height int + if direction == progressDirectionHorizontal { + width = (size) + height = progressBarWidth + } else { + width = progressBarWidth + height = (size) + } + + ctx := gg.NewContext((width), (height)) + ctx.SetColor(color.RGBA{70, 70, 70, 255}) + ctx.DrawRoundedRectangle(0, 0, float64(width), float64(height), 5) + ctx.Fill() + + if progress > 0 { + ctx.SetColor(fillColor) + if direction == progressDirectionHorizontal { + ctx.DrawRoundedRectangle(0, 0, float64(progress)/100*float64(width), float64(height), 5) + } else { + ctx.DrawRoundedRectangle(0, float64(height)-float64(progress)/100*float64(height), float64(width), float64(progress)/100*float64(height), 5) + } + ctx.Fill() + } + + if direction == progressDirectionHorizontal { + return render.NewImageContent(render.Style{Width: float64(size), Height: progressBarWidth}, ctx.Image()) + } + return render.NewImageContent(render.Style{Width: progressBarWidth, Height: float64(size)}, ctx.Image()) +} diff --git a/internal/stats/render/replay/preset.go.todo b/internal/stats/render/replay/preset.go.todo new file mode 100644 index 00000000..b8abf9cb --- /dev/null +++ b/internal/stats/render/replay/preset.go.todo @@ -0,0 +1,18 @@ +package replay + +import ( + "github.com/cufee/aftermath-core/dataprep/replay" + "github.com/cufee/aftermath-core/internal/logic/render" +) + +func statsBlockToBlock(stats replay.StatsBlock, width float64) render.Block { + return render.NewBlocksContent(render.Style{Direction: render.DirectionVertical, AlignItems: render.AlignItemsCenter, Width: width}, + render.NewTextContent(render.Style{ + Font: &render.FontLarge, + FontColor: render.TextPrimary, + }, stats.Value.String), + render.NewTextContent(render.Style{ + Font: &render.FontSmall, + FontColor: render.TextAlt, + }, stats.Label)) +} diff --git a/main.go b/main.go index 4351c111..b28ad25e 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,8 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler" + "github.com/cufee/aftermath/cmds/core/scheduler/tasks" "github.com/cufee/aftermath/cmds/discord" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" @@ -34,9 +36,12 @@ func main() { zerolog.SetGlobalLevel(level) loadStaticAssets(static) - coreClient := coreClientFromEnv() + // this can be adjusted to balance memory usage vs time for cache updates + taskQueueConcurrency := 10 + tasksQueue := tasks.NewQueue(coreClient, tasks.DefaultHandlers(), taskQueueConcurrency) + scheduler.StartCronJobs(coreClient, tasksQueue) // scheduler.UpdateAveragesWorker(coreClient)() // scheduler.UpdateGlossaryWorker(coreClient)() From 6beb2eeb47aef274a83f5f4be54093ce44401b94 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 7 Jun 2024 16:54:52 -0400 Subject: [PATCH 018/341] logging body on error --- cmds/discord/router/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index ff110617..5ac61771 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -262,7 +262,7 @@ func (r *Router) sendInteractionReply(interaction discordgo.Interaction, state * err := handler() if err != nil { - log.Err(err).Msg("failed to send an interaction response") + log.Err(err).Any("data", data).Msg("failed to send an interaction response") return } state.acked = true From 963a13d3d0ff91217fab45516acce7ba7ddf0b47 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 7 Jun 2024 16:55:59 -0400 Subject: [PATCH 019/341] migrations --- .../migration.sql | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 internal/database/prisma/migrations/20240607205538_added_cron_task/migration.sql diff --git a/internal/database/prisma/migrations/20240607205538_added_cron_task/migration.sql b/internal/database/prisma/migrations/20240607205538_added_cron_task/migration.sql new file mode 100644 index 00000000..c334d540 --- /dev/null +++ b/internal/database/prisma/migrations/20240607205538_added_cron_task/migration.sql @@ -0,0 +1,23 @@ +-- CreateTable +CREATE TABLE "cron_tasks" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "type" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "targetsEncoded" TEXT NOT NULL, + "status" TEXT NOT NULL, + "lastRun" DATETIME NOT NULL, + "scheduledAfter" DATETIME NOT NULL, + "logsEncoded" TEXT NOT NULL DEFAULT '', + "dataEncoded" TEXT NOT NULL DEFAULT '' +); + +-- CreateIndex +CREATE INDEX "cron_tasks_referenceId_idx" ON "cron_tasks"("referenceId"); + +-- CreateIndex +CREATE INDEX "cron_tasks_status_referenceId_scheduledAfter_idx" ON "cron_tasks"("status", "referenceId", "scheduledAfter"); + +-- CreateIndex +CREATE INDEX "cron_tasks_lastRun_status_idx" ON "cron_tasks"("lastRun", "status"); From 290f56e430f437ee2836d857599cc1ee31712e92 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 7 Jun 2024 17:03:00 -0400 Subject: [PATCH 020/341] more logs --- cmds/core/scheduler/glossary.go | 6 ++---- cmds/discord/router/handler.go | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go index 587d651b..40165014 100644 --- a/cmds/core/scheduler/glossary.go +++ b/cmds/core/scheduler/glossary.go @@ -10,16 +10,14 @@ import ( "golang.org/x/text/language" ) -// CurrentTankAverages - func UpdateAveragesWorker(client core.Client) func() { + // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails return func() { log.Info().Msg("updating tank averages cache") ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) defer cancel() - // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails averages, err := client.Fetch().CurrentTankAverages(ctx) if err != nil { log.Err(err).Msg("failed to update averages cache") @@ -37,8 +35,8 @@ func UpdateAveragesWorker(client core.Client) func() { } func UpdateGlossaryWorker(client core.Client) func() { + // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails return func() { - // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails log.Info().Msg("updating glossary cache") ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 5ac61771..223b9a18 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -262,7 +262,7 @@ func (r *Router) sendInteractionReply(interaction discordgo.Interaction, state * err := handler() if err != nil { - log.Err(err).Any("data", data).Msg("failed to send an interaction response") + log.Err(err).Any("data", data).Str("id", interaction.ID).Stack().Msg("failed to send an interaction response") return } state.acked = true From c8ca0be3ef01dc092978f091266fc3565f4e85d8 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 8 Jun 2024 16:13:48 -0400 Subject: [PATCH 021/341] encoding changes --- cmds/core/scheduler/glossary.go | 2 +- cmds/discord/common/context.go | 21 +- cmds/discord/router/handler.go | 7 +- internal/database/prisma/schema.prisma | 308 ++++++++++++++----------- internal/database/tasks.go | 35 +-- internal/database/users.go | 14 +- internal/database/vehicles.go | 33 +-- internal/encoding/gob.go | 26 +++ internal/stats/frame/frame.go | 19 +- 9 files changed, 271 insertions(+), 194 deletions(-) create mode 100644 internal/encoding/gob.go diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go index 40165014..b6961d73 100644 --- a/cmds/core/scheduler/glossary.go +++ b/cmds/core/scheduler/glossary.go @@ -56,7 +56,7 @@ func UpdateGlossaryWorker(client core.Client) func() { Type: data.Type, // Class: , Nation: data.Nation, - LocalizedNames: map[language.Tag]string{language.English: data.Name}, + LocalizedNames: map[string]string{language.English.String(): data.Name}, } } diff --git a/cmds/discord/common/context.go b/cmds/discord/common/context.go index 96018022..e9e6b74e 100644 --- a/cmds/discord/common/context.go +++ b/cmds/discord/common/context.go @@ -75,27 +75,36 @@ func NewContext(ctx context.Context, interaction discordgo.Interaction, respondC return c, nil } -func (c *Context) Reply(key string) error { - c.respondCh <- discordgo.InteractionResponseData{Content: c.Localize(key)} +func (c *Context) Message(message string) error { + if message == "" { + return errors.New("bad reply call with blank message") + } + c.respondCh <- discordgo.InteractionResponseData{Content: message} return nil } +func (c *Context) Reply(key string) error { + return c.Message(c.Localize(key)) +} + func (c *Context) Err(err error) error { log.Err(err).Str("interactionId", c.interaction.ID).Msg("error while handling an interaction") - return c.Reply(err.Error()) + return c.Reply("common_error_unhandled_not_reported") } func (c *Context) Error(message string) error { log.Error().Str("message", message).Str("interactionId", c.interaction.ID).Msg("error while handling an interaction") - return c.Reply(message) + return c.Reply("common_error_unhandled_not_reported") } func (c *Context) ReplyFmt(key string, args ...any) error { - c.respondCh <- discordgo.InteractionResponseData{Content: fmt.Sprintf(c.Localize(key), args...)} - return nil + return c.Message(fmt.Sprintf(c.Localize(key), args...)) } func (c *Context) File(r io.Reader, name string) error { + if r == nil { + return errors.New("bad Context#File call with nil io.Reader") + } c.respondCh <- discordgo.InteractionResponseData{Files: []*discordgo.File{{Reader: r, Name: name}}} return nil } diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 223b9a18..7f11f828 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -162,24 +162,25 @@ func (r *Router) routeInteraction(interaction discordgo.Interaction) (*builder.C } func (r *Router) handleInteraction(ctx context.Context, cancel context.CancelFunc, interaction discordgo.Interaction, command *builder.Command, reply chan<- discordgo.InteractionResponseData) { - defer cancel() defer func() { if r := recover(); r != nil { log.Error().Interface("error", r).Msg("interaction handler panic") + reply <- discordgo.InteractionResponseData{Content: "Something went really wrong while working on your command. Please try again later."} } + cancel() }() cCtx, err := common.NewContext(ctx, interaction, reply, r.restClient, r.core) if err != nil { log.Err(err).Msg("failed to create a common.Context for a handler") - reply <- discordgo.InteractionResponseData{Content: "Something went wrong while working on your command. Please try again later."} + reply <- discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")} return } err = command.Handler(cCtx) if err != nil { log.Err(err).Msg("handler returned an error") - reply <- discordgo.InteractionResponseData{Content: "Something went wrong while working on your command. Please try again later."} + reply <- discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")} return } } diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index beff86c1..3dc63ba0 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -8,51 +8,79 @@ generator db { } // -// === App Configuration === +// === App Data === // +// Stores some long-lived app condiguration values +// the values here would typically be set programmatically model AppConfiguration { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - valueEncoded String - metadataEncoded String @default("") + valueEncoded Bytes + metadataEncoded Bytes @default("") @@map("app_configurations") } -// -// === Authentification === -// - -model AuthNonce { +// A record of a cron task +model CronTask { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - expiresAt DateTime + type String + referenceId String + targetsEncoded Bytes + + status String + lastRun DateTime + scheduledAfter DateTime - referenceId String - metadataEncoded String @default("") + logsEncoded Bytes @default("") + dataEncoded Bytes @default("") - @@index([createdAt]) @@index([referenceId]) - @@index([referenceId, expiresAt]) - @@map("auth_nonces") + @@index([status, referenceId, scheduledAfter]) + @@index([lastRun, status]) + @@map("cron_tasks") } +// +// === Authentification === +// + +// // A single-use nonce value generated during the auth flow +// model AuthNonce { +// id String @id @default(cuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt + +// expiresAt DateTime + +// referenceId String +// metadataEncoded Bytes @default("") + +// @@index([createdAt]) +// @@index([referenceId]) +// @@index([referenceId, expiresAt]) +// @@map("auth_nonces") +// @@ignore +// } + // // === Users === // +// User record withing the app, primary id is a Discord user ID model User { id String @id createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - permissions String - featureFlagsEncoded String @default("") + permissions String + featureFlags Int @default(0) subscriptions UserSubscription[] connections UserConnection[] @@ -61,6 +89,7 @@ model User { @@map("users") } +// A subscription record for a user model UserSubscription { id String @id @default(cuid()) createdAt DateTime @default(now()) @@ -74,15 +103,11 @@ model UserSubscription { referenceId String permissions String - @@index([userId]) - @@index([expiresAt]) - @@index([type, userId, expiresAt]) - @@index([type, userId]) - @@index([referenceId]) - @@index([type, referenceId]) @@map("user_subscriptions") } +// A connection to some external service for a user, like Google or Wargaming +// - ReferenceID here would be a user id received from the external service model UserConnection { id String @id @default(cuid()) createdAt DateTime @default(now()) @@ -95,7 +120,7 @@ model UserConnection { permissions String referenceId String - metadataEncoded String @default("") + metadataEncoded Bytes @default("") @@index([userId]) @@index([type, userId]) @@ -104,6 +129,7 @@ model UserConnection { @@map("user_connections") } +// Content uploaded by a user, such as an image model UserContent { id String @id @default(cuid()) createdAt DateTime @default(now()) @@ -115,8 +141,8 @@ model UserContent { type String referenceId String - valueEncoded String - metadataEncoded String @default("") + valueEncoded Bytes + metadataEncoded Bytes @default("") @@index([userId]) @@index([type, userId]) @@ -126,9 +152,10 @@ model UserContent { } // -// === External Data === +// === Wargaming Data === // +// Wargaming Account record model Account { id String @id createdAt DateTime @default(now()) @@ -151,73 +178,108 @@ model Account { @@map("accounts") } +// Wargaming Clan record +model Clan { + id String @id + createdAt DateTime + updatedAt DateTime @updatedAt + + tag String + name String + emblemId String @default("") + + accounts Account[] + membersString String + + recordUpdatedAt DateTime @updatedAt + + @@index([tag]) + @@map("account_clans") +} + +// A snapshot of account statistics model AccountSnapshot { id String @id @default(cuid()) createdAt DateTime - updatedAt DateTime @updatedAt type String - accountId String - referenceId String lastBattleTime DateTime - dataEncoded String + accountId String + referenceId String + + frameEncoded Bytes @@index([createdAt]) - @@index([type, accountId]) - @@index([type, accountId, createdAt]) - @@index([type, accountId, lastBattleTime]) - @@index([type, referenceId]) - @@index([type, referenceId, createdAt]) + @@index([accountId, lastBattleTime]) @@map("account_snapshots") } -model AccountRatingSeasonSnapshot { +// A snapshot of vehicle statistics on a specific account +model VehicleSnapshot { id String @id @default(cuid()) createdAt DateTime - updatedAt DateTime @updatedAt - seasonId String - accountId String - referenceId String + type String lastBattleTime DateTime - dataEncoded String + accountId String + vehicleId String + referenceId String + + frameEncoded Bytes @@index([createdAt]) - @@index([seasonId, accountId]) - @@index([seasonId, referenceId]) - @@map("account_rating_season_snapshots") + @@index([accountId, vehicleId, lastBattleTime]) + @@map("vehicle_snapshots") } -model Clan { - id String @id - createdAt DateTime +// A snapshot of all account achievements +model AchievementsSnapshot { + id String @id @default(uuid()) + createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - tag String - name String - emblemId String @default("") - - accounts Account[] - membersString String + accountId String + referenceId String - recordUpdatedAt DateTime @updatedAt + dataEncoded Bytes - @@index([tag]) - @@map("account_clans") + @@map("achievements_snapshots") } +// // A snapshot of account rating season stats +// model AccountRatingSeasonSnapshot { +// id String @id @default(cuid()) +// createdAt DateTime +// updatedAt DateTime @updatedAt + +// seasonId String +// accountId String +// referenceId String +// lastBattleTime DateTime + +// dataEncoded Bytes + +// @@index([createdAt]) +// @@index([seasonId, accountId]) +// @@index([seasonId, referenceId]) +// @@map("account_rating_season_snapshots") +// @@ignore // unused atm and may require significant changes +// } + +// A stats frame representing an average player performance on a vehicle model VehicleAverage { id String @id createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - dataEncoded String + dataEncoded Bytes @@map("glossary_averages") } +// Wargaming Vehicle glossary information model Vehicle { id String @id createdAt DateTime @default(now()) @@ -228,104 +290,84 @@ model Vehicle { class String nation String - localizedNamesEncoded String + localizedNamesEncoded Bytes @@map("glossary_vehicles") } -model Achievement { - id String @id @default(uuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt +// // Wargaming Achievement glossary information +// model Achievement { +// id String @id @default(uuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt - section String +// section String - dataEncoded String +// dataEncoded Bytes - @@map("glossary_achievements") -} +// @@map("glossary_achievements") +// @@ignore +// } // -// === App Data === +// === Discord Data === // -model UserInteraction { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - type String - userId String - referenceId String - - dataEncoded String - metadataEncoded String @default("") - - @@index([type]) - @@index([userId]) - @@index([userId, type]) - @@index([referenceId]) - @@index([referenceId, type]) - @@map("user_interactions") -} - -model DiscordBotLiveSession { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt +// // A log of a user interaction with the bot +// model DiscordInteraction { +// id String @id @default(cuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt - locale String +// type String +// userId String +// referenceId String - userId String - referenceId String +// dataEncoded Bytes +// metadataEncoded Bytes @default("") - guildId String? - channelId String - messageId String +// @@map("user_interactions") +// @@ignore +// } - lastUpdate DateTime @default(now()) - lastBattleTime DateTime @default(now()) +// // An active live session record of a user, requested through the bot +// model LiveSession { +// id String @id @default(cuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt - optionsEncoded String +// locale String - @@index([referenceId]) - @@map("discord_live_sessions") -} +// userId String +// referenceId String -model StatsRequestsOptions { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt +// lastUpdate DateTime @default(now()) +// lastBattleTime DateTime @default(now()) - type String - userId String - accountId String - referenceId String +// options StatsRequestsOptions @relation(fields: [optionsId], references: [id]) +// optionsId String - dataEncoded String +// metadataEncoded Bytes @default("") - @@index([referenceId]) - @@map("stats_request_options") -} +// @@index([referenceId]) +// @@map("live_sessions") +// @@ignore +// } -model CronTask { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt +// model StatsRequestsOptions { +// id String @id @default(cuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt - type String - referenceId String - targetsEncoded String +// type String +// userId String +// accountId String +// referenceId String - status String - lastRun DateTime - scheduledAfter DateTime - - logsEncoded String @default("") - dataEncoded String @default("") +// dataEncoded Bytes +// liveSessions LiveSession[] @ignore - @@index([referenceId]) - @@index([status, referenceId, scheduledAfter]) - @@index([lastRun, status]) - @@map("cron_tasks") -} +// @@index([referenceId]) +// @@map("stats_request_options") +// @@ignore +// } diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 1890cff3..b6cc64e6 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -2,11 +2,11 @@ package database import ( "context" - "encoding/json" "strings" "time" "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/encoding" ) type TaskType string @@ -77,31 +77,34 @@ func (t *Task) OnUpdated() { t.UpdatedAt = time.Now() } -func (t *Task) encodeTargets() string { - return strings.Join(t.Targets, ";") +func (t *Task) encodeTargets() []byte { + return []byte(strings.Join(t.Targets, ";")) } -func (t *Task) decodeTargets(targets string) { - t.Targets = strings.Split(targets, ";") +func (t *Task) decodeTargets(targets []byte) { + t.Targets = strings.Split(string(targets), ";") } -func (t *Task) encodeLogs() string { - data, _ := json.Marshal(t.Logs) - return string(data) +func (t *Task) encodeLogs() []byte { + if t.Logs == nil { + return nil + } + data, _ := encoding.EncodeGob(t.Logs) + return data } -func (t *Task) decodeLogs(logs string) { - _ = json.Unmarshal([]byte(logs), &t.Logs) +func (t *Task) decodeLogs(logs []byte) { + _ = encoding.DecodeGob(logs, &t.Logs) } -func (t *Task) encodeData() string { +func (t *Task) encodeData() []byte { if t.Data == nil { - return "" + return nil } - data, _ := json.Marshal(t.Data) - return string(data) + data, _ := encoding.EncodeGob(t.Data) + return data } -func (t *Task) decodeData(data string) { +func (t *Task) decodeData(data []byte) { t.Data = make(map[string]any) - _ = json.Unmarshal([]byte(data), &t.Data) + _ = encoding.DecodeGob(data, &t.Data) } type TaskLog struct { diff --git a/internal/database/users.go b/internal/database/users.go index 270e7ab1..09dbc345 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -2,13 +2,13 @@ package database import ( "context" - "encoding/json" "errors" "fmt" "slices" "time" "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/encoding" "github.com/cufee/aftermath/internal/permissions" ) @@ -65,8 +65,8 @@ func (c UserConnection) FromModel(model *db.UserConnectionModel) UserConnection c.Permissions = permissions.Parse(model.Permissions, permissions.Blank) c.Type = ConnectionType(model.Type) - if model.MetadataEncoded != "" { - _ = json.Unmarshal([]byte(model.MetadataEncoded), &c.Metadata) + if model.MetadataEncoded != nil { + _ = encoding.DecodeGob(model.MetadataEncoded, &c.Metadata) } return c } @@ -219,7 +219,7 @@ func (c *client) UpdateConnection(ctx context.Context, connection UserConnection return UserConnection{}, errors.New("connection referenceID cannot be left blank") } - encoded, err := json.Marshal(connection.Metadata) + encoded, err := encoding.EncodeGob(connection.Metadata) if err != nil { return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) } @@ -227,7 +227,7 @@ func (c *client) UpdateConnection(ctx context.Context, connection UserConnection model, err := c.prisma.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( db.UserConnection.ReferenceID.Set(connection.ReferenceID), db.UserConnection.Permissions.Set(connection.Permissions.Encode()), - db.UserConnection.MetadataEncoded.Set(string(encoded)), + db.UserConnection.MetadataEncoded.Set(encoded), ).Exec(ctx) if err != nil { return UserConnection{}, err @@ -250,7 +250,7 @@ func (c *client) UpsertConnection(ctx context.Context, connection UserConnection return UserConnection{}, errors.New("connection Type cannot be left blank") } - encoded, err := json.Marshal(connection.Metadata) + encoded, err := encoding.EncodeGob(connection.Metadata) if err != nil { return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) } @@ -260,7 +260,7 @@ func (c *client) UpsertConnection(ctx context.Context, connection UserConnection db.UserConnection.Type.Set(string(connection.Type)), db.UserConnection.Permissions.Set(connection.Permissions.Encode()), db.UserConnection.ReferenceID.Set(connection.ReferenceID), - db.UserConnection.MetadataEncoded.Set(string(encoded)), + db.UserConnection.MetadataEncoded.Set(encoded), ).Exec(ctx) if err != nil { return UserConnection{}, err diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index eb16c885..bbb67a0b 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -2,9 +2,9 @@ package database import ( "context" - "encoding/json" "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/encoding" "github.com/rs/zerolog/log" "github.com/steebchen/prisma-client-go/runtime/transaction" "golang.org/x/text/language" @@ -17,7 +17,7 @@ type Vehicle struct { Class string Nation string - LocalizedNames map[language.Tag]string + LocalizedNames map[string]string } func (v Vehicle) FromModel(model db.VehicleModel) Vehicle { @@ -25,18 +25,27 @@ func (v Vehicle) FromModel(model db.VehicleModel) Vehicle { v.Tier = model.Tier v.Type = model.Type v.Nation = model.Nation - v.LocalizedNames = make(map[language.Tag]string) - if model.LocalizedNamesEncoded != "" { - _ = json.Unmarshal([]byte(model.LocalizedNamesEncoded), &v.LocalizedNames) + v.LocalizedNames = make(map[string]string) + if model.LocalizedNamesEncoded != nil { + err := encoding.DecodeGob(model.LocalizedNamesEncoded, &v.LocalizedNames) + if err != nil { + log.Err(err).Str("id", v.ID).Msg("failed to decode vehicle localized names") + } + } return v } +func (v Vehicle) EncodeNames() []byte { + encoded, _ := encoding.EncodeGob(v.LocalizedNames) + return encoded +} + func (v Vehicle) Name(locale language.Tag) string { - if n := v.LocalizedNames[locale]; n != "" { + if n := v.LocalizedNames[locale.String()]; n != "" { return n } - if n := v.LocalizedNames[language.English]; n != "" { + if n := v.LocalizedNames[language.English.String()]; n != "" { return n } return "Secret Tank" @@ -49,12 +58,6 @@ func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle var transactions []transaction.Transaction for id, data := range vehicles { - encoded, err := json.Marshal(data.LocalizedNames) - if err != nil { - log.Err(err).Str("id", id).Msg("failed to encode a stats frame for vehicle averages") - continue - } - transactions = append(transactions, c.prisma.Vehicle. UpsertOne(db.Vehicle.ID.Equals(id)). Create( @@ -63,14 +66,14 @@ func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle db.Vehicle.Type.Set(data.Type), db.Vehicle.Class.Set(data.Class), db.Vehicle.Nation.Set(data.Nation), - db.Vehicle.LocalizedNamesEncoded.Set(string(encoded)), + db.Vehicle.LocalizedNamesEncoded.Set(data.EncodeNames()), ). Update( db.Vehicle.Tier.Set(data.Tier), db.Vehicle.Type.Set(data.Type), db.Vehicle.Class.Set(data.Class), db.Vehicle.Nation.Set(data.Nation), - db.Vehicle.LocalizedNamesEncoded.Set(string(encoded)), + db.Vehicle.LocalizedNamesEncoded.Set(data.EncodeNames()), ).Tx(), ) } diff --git a/internal/encoding/gob.go b/internal/encoding/gob.go new file mode 100644 index 00000000..d52c5e76 --- /dev/null +++ b/internal/encoding/gob.go @@ -0,0 +1,26 @@ +package encoding + +import ( + "bytes" + "encoding/gob" +) + +func EncodeGob(data any) ([]byte, error) { + if data == nil { + return nil, nil + } + var buf bytes.Buffer + err := gob.NewEncoder(&buf).Encode(data) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func DecodeGob(data []byte, target any) error { + err := gob.NewDecoder(bytes.NewBuffer(data)).Decode(target) + if err != nil { + return err + } + return nil +} diff --git a/internal/stats/frame/frame.go b/internal/stats/frame/frame.go index 570f97bd..7889ecae 100644 --- a/internal/stats/frame/frame.go +++ b/internal/stats/frame/frame.go @@ -1,9 +1,10 @@ package frame import ( - "encoding/json" "math" "time" + + "github.com/cufee/aftermath/internal/encoding" ) /* @@ -39,24 +40,16 @@ type StatsFrame struct { survivalRatio ValueFloatDecimal `json:"-" bson:"-"` } -func DecodeStatsFrame(encoded string) (StatsFrame, error) { +func DecodeStatsFrame(encoded []byte) (StatsFrame, error) { var data StatsFrame - err := json.Unmarshal([]byte(encoded), &data) - if err != nil { - return StatsFrame{}, err - } - return data, nil + return data, encoding.DecodeGob(encoded, &data) } /* Encode StatsFrame to string */ -func (r *StatsFrame) Encode() (string, error) { - data, err := json.Marshal(r) - if err != nil { - return "", err - } - return string(data), nil +func (r StatsFrame) Encode() ([]byte, error) { + return encoding.EncodeGob(r) } /* From 399c2c795df77aefdafe35677c03e25d0ab6b5de Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 8 Jun 2024 16:53:29 -0400 Subject: [PATCH 022/341] refactored queue --- cmds/core/scheduler/cron.go | 22 ++++----- cmds/core/scheduler/{tasks => }/queue.go | 11 +++-- cmds/core/scheduler/tasks/handler.go | 4 +- cmds/core/scheduler/tasks/sessions.go | 48 ++++++++++---------- cmds/core/scheduler/workers.go | 58 +++++++++++++----------- internal/database/client.go | 1 + internal/database/tasks.go | 15 ++++-- internal/database/vehicles.go | 1 - main.go | 50 ++++++++++++++++---- 9 files changed, 127 insertions(+), 83 deletions(-) rename cmds/core/scheduler/{tasks => }/queue.go (90%) diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go index d78ad736..1506bc34 100644 --- a/cmds/core/scheduler/cron.go +++ b/cmds/core/scheduler/cron.go @@ -3,13 +3,11 @@ package scheduler import ( "time" - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/scheduler/tasks" "github.com/go-co-op/gocron" "github.com/rs/zerolog/log" ) -func StartCronJobs(client core.Client, queue *tasks.Queue) { +func (queue *Queue) StartCronJobsAsync() { defer log.Info().Msg("started cron scheduler") c := gocron.NewScheduler(time.UTC) @@ -18,19 +16,17 @@ func StartCronJobs(client core.Client, queue *tasks.Queue) { c.Cron("0 * * * *").Do(restartTasksWorker(queue)) // Glossary - Do it around the same time WG releases game updates - c.Cron("0 10 * * *").Do(UpdateGlossaryWorker(client)) - c.Cron("0 12 * * *").Do(UpdateGlossaryWorker(client)) + c.Cron("0 10 * * *").Do(UpdateGlossaryWorker(queue.core)) + c.Cron("0 12 * * *").Do(UpdateGlossaryWorker(queue.core)) // c.AddFunc("40 9 * * 0", updateAchievementsWorker) - // Averages - Update averages shortly after session refreshes - c.Cron("0 10 * * *").Do(UpdateAveragesWorker(client)) - c.Cron("0 2 * * *").Do(UpdateAveragesWorker(client)) - c.Cron("0 19 * * *").Do(UpdateAveragesWorker(client)) + // Averages - Update averages once daily + c.Cron("0 0 * * *").Do(UpdateAveragesWorker(queue.core)) // Sessions - c.Cron("0 9 * * *").Do(createSessionTasksWorker(client, "NA")) // NA - c.Cron("0 1 * * *").Do(createSessionTasksWorker(client, "EU")) // EU - c.Cron("0 18 * * *").Do(createSessionTasksWorker(client, "AS")) // Asia + c.Cron("0 9 * * *").Do(createSessionTasksWorker(queue.core, "NA")) // NA + c.Cron("0 1 * * *").Do(createSessionTasksWorker(queue.core, "EU")) // EU + c.Cron("0 18 * * *").Do(createSessionTasksWorker(queue.core, "AS")) // Asia // Refresh WN8 // "45 9 * * *" // NA @@ -38,7 +34,7 @@ func StartCronJobs(client core.Client, queue *tasks.Queue) { // "45 18 * * *" // Asia // Configurations - c.Cron("0 0 */7 * *").Do(rotateBackgroundPresetsWorker(client)) + c.Cron("0 0 */7 * *").Do(rotateBackgroundPresetsWorker(queue.core)) // Start the Cron job scheduler c.StartAsync() diff --git a/cmds/core/scheduler/tasks/queue.go b/cmds/core/scheduler/queue.go similarity index 90% rename from cmds/core/scheduler/tasks/queue.go rename to cmds/core/scheduler/queue.go index 9a5dc94d..bce1ef41 100644 --- a/cmds/core/scheduler/tasks/queue.go +++ b/cmds/core/scheduler/queue.go @@ -1,4 +1,4 @@ -package tasks +package scheduler import ( "context" @@ -6,6 +6,7 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler/tasks" "github.com/cufee/aftermath/internal/database" "github.com/rs/zerolog/log" ) @@ -15,7 +16,7 @@ type Queue struct { concurrencyLimit int lastTaskRun time.Time - handlers map[database.TaskType]TaskHandler + handlers map[database.TaskType]tasks.TaskHandler core core.Client } @@ -31,7 +32,7 @@ func (q *Queue) LastTaskRun() time.Time { return q.lastTaskRun } -func NewQueue(client core.Client, handlers map[database.TaskType]TaskHandler, concurrencyLimit int) *Queue { +func NewQueue(client core.Client, handlers map[database.TaskType]tasks.TaskHandler, concurrencyLimit int) *Queue { return &Queue{ core: client, handlers: handlers, @@ -83,7 +84,7 @@ func (q *Queue) Process(callback func(error), tasks ...database.Task) { Timestamp: time.Now(), } - message, err := handler.process(nil, t) + message, err := handler.Process(nil, t) attempt.Comment = message if err != nil { attempt.Error = err.Error() @@ -106,7 +107,7 @@ func (q *Queue) Process(callback func(error), tasks ...database.Task) { continue } - if task.Status == database.TaskStatusFailed && handler.shouldRetry(&task) { + if task.Status == database.TaskStatusFailed && handler.ShouldRetry(&task) { rescheduledCount++ task.Status = database.TaskStatusScheduled } diff --git a/cmds/core/scheduler/tasks/handler.go b/cmds/core/scheduler/tasks/handler.go index 4c99d1ff..fa3d0e6d 100644 --- a/cmds/core/scheduler/tasks/handler.go +++ b/cmds/core/scheduler/tasks/handler.go @@ -6,8 +6,8 @@ import ( ) type TaskHandler struct { - process func(client core.Client, task database.Task) (string, error) - shouldRetry func(task *database.Task) bool + Process func(client core.Client, task database.Task) (string, error) + ShouldRetry func(task *database.Task) bool } var defaultHandlers = make(map[database.TaskType]TaskHandler) diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index 45c03af2..8a98df0c 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -12,7 +12,7 @@ import ( func init() { defaultHandlers[database.TaskTypeRecordSessions] = TaskHandler{ - process: func(client core.Client, task database.Task) (string, error) { + Process: func(client core.Client, task database.Task) (string, error) { if task.Data == nil { return "no data provided", errors.New("no data provided") } @@ -42,7 +42,7 @@ func init() { // task.Targets = failedAccounts // return "retrying failed accounts", errors.New("some accounts failed") }, - shouldRetry: func(task *database.Task) bool { + ShouldRetry: func(task *database.Task) bool { triesLeft, ok := task.Data["triesLeft"].(int32) if !ok { return false @@ -59,30 +59,28 @@ func init() { } } -func CreateSessionUpdateTasks(client core.Client) func(realm string) error { - return func(realm string) error { - realm = strings.ToUpper(realm) - task := database.Task{ - Type: database.TaskTypeRecordSessions, - ReferenceID: "realm_" + realm, - Data: map[string]any{ - "realm": realm, - "triesLeft": int32(3), - }, - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() +func CreateSessionUpdateTasks(client core.Client, realm string) error { + realm = strings.ToUpper(realm) + task := database.Task{ + Type: database.TaskTypeRecordSessions, + ReferenceID: "realm_" + realm, + Data: map[string]any{ + "realm": realm, + "triesLeft": int32(3), + }, + } - accounts, err := client.Database().GetRealmAccounts(ctx, realm) - if err != nil { - return err - } - if len(accounts) < 1 { - return nil - } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() - // This update requires (2 + n) requests per n players - return client.Database().CreateTasks(ctx, splitTaskByTargets(task, 50)...) + accounts, err := client.Database().GetRealmAccounts(ctx, realm) + if err != nil { + return err } + if len(accounts) < 1 { + return nil + } + + // This update requires (2 + n) requests per n players + return client.Database().CreateTasks(ctx, splitTaskByTargets(task, 90)...) } diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index 06c8eff7..e3ca2508 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -1,8 +1,12 @@ package scheduler import ( + "context" + "time" + "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/cmds/core/scheduler/tasks" + "github.com/rs/zerolog/log" ) func rotateBackgroundPresetsWorker(client core.Client) func() { @@ -23,42 +27,44 @@ func rotateBackgroundPresetsWorker(client core.Client) func() { func createSessionTasksWorker(client core.Client, realm string) func() { return func() { - // err := tasks.CreateSessionUpdateTasks(realm) - // if err != nil { - // log.Err(err).Msg("failed to create session update tasks") - // } + err := tasks.CreateSessionUpdateTasks(client, realm) + if err != nil { + log.Err(err).Str("realm", realm).Msg("failed to schedule session update tasks") + } } } -func runTasksWorker(queue *tasks.Queue) func() { +func runTasksWorker(queue *Queue) func() { return func() { - // if tasks.DefaultQueue.ActiveWorkers() > 0 { - // return - // } - - // activeTasks, err := tasks.StartScheduledTasks(nil, 50) - // if err != nil { - // log.Err(err).Msg("failed to start scheduled tasks") - // return - // } - // if len(activeTasks) == 0 { - // return - // } + activeWorkers := queue.ActiveWorkers() + if activeWorkers >= queue.concurrencyLimit { + return + } - // tasks.DefaultQueue.Process(func(err error) { - // if err != nil { - // log.Err(err).Msg("failed to process tasks") - // return - // } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() - // // If the queue is now empty, we can run the next batch of tasks right away - // runTasksWorker() + // each task worked handles 1 task at a time, but tasks might be very fast + // for now, we queue up 10 tasks per worker, this can be adjusted later/smarter + batchSize := queue.concurrencyLimit - activeWorkers + tasks, err := queue.core.Database().GetAndStartTasks(ctx, batchSize*10) + if err != nil { + log.Err(err).Msg("failed to start scheduled tasks") + return + } - // }, activeTasks...) + queue.Process(func(err error) { + if err != nil { + log.Err(err).Msg("failed to process tasks") + return + } + // if the queue is now empty, we can run the next batch of tasks right away + runTasksWorker(queue) + }, tasks...) } } -func restartTasksWorker(queue *tasks.Queue) func() { +func restartTasksWorker(queue *Queue) func() { return func() { // _, err := tasks.RestartAbandonedTasks(nil) // if err != nil { diff --git a/internal/database/client.go b/internal/database/client.go index d9225317..b7a2be01 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -27,6 +27,7 @@ type Client interface { CreateTasks(ctx context.Context, tasks ...Task) error UpdateTasks(ctx context.Context, tasks ...Task) error DeleteTasks(ctx context.Context, ids ...string) error + GetAndStartTasks(ctx context.Context, limit int) ([]Task, error) } var _ Client = &client{} diff --git a/internal/database/tasks.go b/internal/database/tasks.go index b6cc64e6..99db4dae 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -127,25 +127,32 @@ func NewAttemptLog(task Task, comment, err string) TaskLog { GetAndStartTasks retrieves up to limit number of tasks matching the referenceId and updates their status to in progress - this func will block until all other calls to task update funcs are done */ -func (c *client) GetAndStartTasks(ctx context.Context, referenceId string, limit int) ([]Task, error) { +func (c *client) GetAndStartTasks(ctx context.Context, limit int) ([]Task, error) { if limit < 1 { return nil, nil } if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { return nil, err } + defer c.tasksUpdateSem.Release(1) models, err := c.prisma.CronTask. FindMany( db.CronTask.Status.Equals(string(TaskStatusScheduled)), - db.CronTask.ReferenceID.Equals(referenceId), - db.CronTask.ScheduledAfter.Gte(time.Now()), + db.CronTask.ScheduledAfter.Lt(time.Now()), ). + OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)). Take(limit). Exec(ctx) if err != nil { + if db.IsErrNotFound(err) { + return nil, nil + } return nil, err } + if len(models) < 1 { + return nil, nil + } var ids []string var tasks []Task @@ -206,6 +213,7 @@ func (c *client) UpdateTasks(ctx context.Context, tasks ...Task) error { if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { return err } + defer c.tasksUpdateSem.Release(1) var txns []db.PrismaTransaction for _, task := range tasks { @@ -241,6 +249,7 @@ func (c *client) DeleteTasks(ctx context.Context, ids ...string) error { if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { return nil } + defer c.tasksUpdateSem.Release(1) _, err := c.prisma.CronTask.FindMany(db.CronTask.ID.In(ids)).Delete().Exec(ctx) if err != nil { diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index bbb67a0b..2e11eb6e 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -31,7 +31,6 @@ func (v Vehicle) FromModel(model db.VehicleModel) Vehicle { if err != nil { log.Err(err).Str("id", v.ID).Msg("failed to decode vehicle localized names") } - } return v } diff --git a/main.go b/main.go index b28ad25e..794493d5 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "os" + "strconv" "time" "github.com/cufee/aftermath/cmds/core" @@ -37,13 +38,7 @@ func main() { loadStaticAssets(static) coreClient := coreClientFromEnv() - - // this can be adjusted to balance memory usage vs time for cache updates - taskQueueConcurrency := 10 - tasksQueue := tasks.NewQueue(coreClient, tasks.DefaultHandlers(), taskQueueConcurrency) - scheduler.StartCronJobs(coreClient, tasksQueue) - // scheduler.UpdateAveragesWorker(coreClient)() - // scheduler.UpdateGlossaryWorker(coreClient)() + startSchedulerFromEnvAsync() discordHandler, err := discord.NewRouterHandler(coreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) if err != nil { @@ -54,11 +49,50 @@ func main() { panic(http.ListenAndServe(":"+os.Getenv("PORT"), nil)) } +func startSchedulerFromEnvAsync() { + if os.Getenv("SCHEDULER_ENABLED") != "true" { + return + } + + // This wargaming client is using a different proxy as it needs a lot higher rps, but can be slow + wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_CACHE_APP_ID"), os.Getenv("WG_CACHE_APP_RPS"), os.Getenv("WG_CACHE_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_CACHE_PROXY_HOST_LIST")) + if err != nil { + log.Fatalf("wgClient: wargaming#NewClientFromEnv failed %s", err) + } + dbClient, err := database.NewClient() + if err != nil { + log.Fatalf("database#NewClient failed %s", err) + } + bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) + if err != nil { + log.Fatalf("failed to init a blitzstars client %s", err) + } + + // Fetch client + client, err := fetch.NewMultiSourceClient(wgClient, bsClient, dbClient) + if err != nil { + log.Fatalf("fetch#NewMultiSourceClient failed %s", err) + } + coreClient := core.NewClient(client, wgClient, dbClient) + + // Queue + taskQueueConcurrency := 10 + if v, err := strconv.Atoi(os.Getenv("SCHEDULER_CONCURRENT_WORKERS")); err == nil { + taskQueueConcurrency = v + } + queue := scheduler.NewQueue(coreClient, tasks.DefaultHandlers(), taskQueueConcurrency) + queue.StartCronJobsAsync() + + // Some tasks should run on startup + // scheduler.UpdateAveragesWorker(coreClient)() + // scheduler.UpdateGlossaryWorker(coreClient)() +} + func coreClientFromEnv() core.Client { // Dependencies wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_PRIMARY_APP_ID"), os.Getenv("WG_PRIMARY_APP_RPS"), os.Getenv("WG_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_PROXY_HOST_LIST")) if err != nil { - log.Fatalf("wargaming#NewClientFromEnv failed %s", err) + log.Fatalf("wgClient: wargaming#NewClientFromEnv failed %s", err) } dbClient, err := database.NewClient() if err != nil { From 7592df7eb12835b10725d5b76d1c1f6059f08b36 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 8 Jun 2024 17:21:44 -0400 Subject: [PATCH 023/341] updating accounts cache --- cmds/core/scheduler/cron.go | 1 + go.mod | 2 +- go.sum | 4 ++-- internal/database/accounts.go | 13 ++++++++++++- internal/database/prisma/schema.prisma | 1 - internal/stats/fetch/convert.go | 9 ++++++--- internal/stats/fetch/multisource.go | 11 +++++++++++ 7 files changed, 33 insertions(+), 8 deletions(-) diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go index 1506bc34..daea6b15 100644 --- a/cmds/core/scheduler/cron.go +++ b/cmds/core/scheduler/cron.go @@ -13,6 +13,7 @@ func (queue *Queue) StartCronJobsAsync() { c := gocron.NewScheduler(time.UTC) // Tasks c.Cron("* * * * *").Do(runTasksWorker(queue)) + // some tasks might be stuck due to a panic or restart, restart them c.Cron("0 * * * *").Do(restartTasksWorker(queue)) // Glossary - Do it around the same time WG releases game updates diff --git a/go.mod b/go.mod index 56825584..06d528a3 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.3 require ( github.com/bwmarrin/discordgo v0.28.1 - github.com/cufee/am-wg-proxy-next/v2 v2.1.2 + github.com/cufee/am-wg-proxy-next/v2 v2.1.3 github.com/disintegration/imaging v1.6.2 github.com/fogleman/gg v1.3.0 github.com/go-co-op/gocron v1.37.0 diff --git a/go.sum b/go.sum index 4b9b9c9d..4b57b235 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cufee/am-wg-proxy-next/v2 v2.1.2 h1:j45xbLPs5FqSsfEccls8AWXK1DIWjnIFEKoiB7M/TGY= -github.com/cufee/am-wg-proxy-next/v2 v2.1.2/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= +github.com/cufee/am-wg-proxy-next/v2 v2.1.3 h1:qTzzgEapbI/z81R3nXoiiITfBZeHQ1cnebzSNgaoZlQ= +github.com/cufee/am-wg-proxy-next/v2 v2.1.3/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= 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= diff --git a/internal/database/accounts.go b/internal/database/accounts.go index 14330bcf..c8fb1ab7 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -106,7 +106,11 @@ func (c *client) UpsertAccounts(ctx context.Context, accounts []Account) map[str defer wg.Done() optional := []db.AccountSetParam{db.Account.Private.Set(account.Private)} if account.ClanID != "" { - optional = append(optional, db.Account.Clan.Link(db.Clan.ID.Equals(account.ClanID))) + clan, err := c.prisma.Clan.FindUnique(db.Clan.ID.Equals(account.ClanID)).Exec(ctx) + if err == nil { + optional = append(optional, db.Account.Clan.Link(db.Clan.ID.Equals(clan.ID))) + } + } _, err := c.prisma.Account. @@ -118,11 +122,18 @@ func (c *client) UpsertAccounts(ctx context.Context, accounts []Account) map[str db.Account.Nickname.Set(account.Nickname), optional..., ). + Update( + append(optional, + db.Account.Nickname.Set(account.Nickname), + db.Account.LastBattleTime.Set(account.LastBattleTime), + )..., + ). Exec(ctx) if err != nil { mx.Lock() errors[account.ID] = err mx.Unlock() + return } }(account) } diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index 3dc63ba0..ced783a5 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -172,7 +172,6 @@ model Account { clanId String? @@index([realm]) - @@index([clanId]) @@index([nickname]) @@index([lastBattleTime]) @@map("accounts") diff --git a/internal/stats/fetch/convert.go b/internal/stats/fetch/convert.go index 7db9f1c4..04377552 100644 --- a/internal/stats/fetch/convert.go +++ b/internal/stats/fetch/convert.go @@ -16,17 +16,20 @@ func timestampToTime(timestamp int) time.Time { } func wargamingToAccount(realm string, account types.ExtendedAccount, clan types.ClanMember, private bool) database.Account { - return database.Account{ + a := database.Account{ ID: strconv.Itoa(account.ID), Realm: realm, Nickname: account.Nickname, - ClanTag: clan.Clan.Tag, - ClanID: strconv.Itoa(clan.ClanID), Private: private, CreatedAt: timestampToTime(account.CreatedAt), LastBattleTime: timestampToTime(account.LastBattleTime), } + if clan.ClanID > 0 { + a.ClanTag = clan.Clan.Tag + a.ClanID = strconv.Itoa(clan.ClanID) + } + return a } func wargamingToStats(realm string, accountData types.ExtendedAccount, clanMember types.ClanMember, vehicleData []types.VehicleStatsFrame) AccountStatsOverPeriod { diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index ddd08882..300716ee 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -13,6 +13,7 @@ import ( "github.com/cufee/aftermath/internal/retry" "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/am-wg-proxy-next/v2/types" + "github.com/rs/zerolog/log" ) var _ Client = &multiSourceClient{} @@ -115,6 +116,16 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. stats.AddWN8(averages.Data) } + go func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + errMap := c.database.UpsertAccounts(ctx, []database.Account{stats.Account}) + if err := errMap[id]; err != nil { + log.Err(err).Msg("failed to update account cache") + } + }() + return stats, nil } From 64f4006152fc98bd7407e5875ba655a609f8e93f Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 8 Jun 2024 17:39:39 -0400 Subject: [PATCH 024/341] minor encoding/scheduler fixes --- cmds/core/scheduler/cron.go | 12 ++++++------ cmds/core/scheduler/tasks/sessions.go | 5 +++-- cmds/core/scheduler/workers.go | 10 +++++----- fly.canary.toml | 4 ++++ internal/database/accounts.go | 1 - internal/database/prisma/schema.prisma | 21 +++++++++++---------- internal/database/tasks.go | 8 ++++---- internal/encoding/gob.go | 4 ++-- main.go | 2 +- 9 files changed, 36 insertions(+), 31 deletions(-) diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go index daea6b15..523c7037 100644 --- a/cmds/core/scheduler/cron.go +++ b/cmds/core/scheduler/cron.go @@ -12,9 +12,9 @@ func (queue *Queue) StartCronJobsAsync() { c := gocron.NewScheduler(time.UTC) // Tasks - c.Cron("* * * * *").Do(runTasksWorker(queue)) + c.Cron("* * * * *").Do(RunTasksWorker(queue)) // some tasks might be stuck due to a panic or restart, restart them - c.Cron("0 * * * *").Do(restartTasksWorker(queue)) + c.Cron("0 * * * *").Do(RestartTasksWorker(queue)) // Glossary - Do it around the same time WG releases game updates c.Cron("0 10 * * *").Do(UpdateGlossaryWorker(queue.core)) @@ -25,9 +25,9 @@ func (queue *Queue) StartCronJobsAsync() { c.Cron("0 0 * * *").Do(UpdateAveragesWorker(queue.core)) // Sessions - c.Cron("0 9 * * *").Do(createSessionTasksWorker(queue.core, "NA")) // NA - c.Cron("0 1 * * *").Do(createSessionTasksWorker(queue.core, "EU")) // EU - c.Cron("0 18 * * *").Do(createSessionTasksWorker(queue.core, "AS")) // Asia + c.Cron("0 9 * * *").Do(CreateSessionTasksWorker(queue.core, "NA")) // NA + c.Cron("0 1 * * *").Do(CreateSessionTasksWorker(queue.core, "EU")) // EU + c.Cron("0 18 * * *").Do(CreateSessionTasksWorker(queue.core, "AS")) // Asia // Refresh WN8 // "45 9 * * *" // NA @@ -35,7 +35,7 @@ func (queue *Queue) StartCronJobsAsync() { // "45 18 * * *" // Asia // Configurations - c.Cron("0 0 */7 * *").Do(rotateBackgroundPresetsWorker(queue.core)) + c.Cron("0 0 */7 * *").Do(RotateBackgroundPresetsWorker(queue.core)) // Start the Cron job scheduler c.StartAsync() diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index 8a98df0c..f999f95c 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -62,8 +62,9 @@ func init() { func CreateSessionUpdateTasks(client core.Client, realm string) error { realm = strings.ToUpper(realm) task := database.Task{ - Type: database.TaskTypeRecordSessions, - ReferenceID: "realm_" + realm, + Type: database.TaskTypeRecordSessions, + ReferenceID: "realm_" + realm, + ScheduledAfter: time.Now(), Data: map[string]any{ "realm": realm, "triesLeft": int32(3), diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index e3ca2508..fa7e033f 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -9,7 +9,7 @@ import ( "github.com/rs/zerolog/log" ) -func rotateBackgroundPresetsWorker(client core.Client) func() { +func RotateBackgroundPresetsWorker(client core.Client) func() { return func() { // // We just run the logic directly as it's not a heavy task and it doesn't matter if it fails due to the app failing // log.Info().Msg("rotating background presets") @@ -25,7 +25,7 @@ func rotateBackgroundPresetsWorker(client core.Client) func() { } } -func createSessionTasksWorker(client core.Client, realm string) func() { +func CreateSessionTasksWorker(client core.Client, realm string) func() { return func() { err := tasks.CreateSessionUpdateTasks(client, realm) if err != nil { @@ -34,7 +34,7 @@ func createSessionTasksWorker(client core.Client, realm string) func() { } } -func runTasksWorker(queue *Queue) func() { +func RunTasksWorker(queue *Queue) func() { return func() { activeWorkers := queue.ActiveWorkers() if activeWorkers >= queue.concurrencyLimit { @@ -59,12 +59,12 @@ func runTasksWorker(queue *Queue) func() { return } // if the queue is now empty, we can run the next batch of tasks right away - runTasksWorker(queue) + RunTasksWorker(queue) }, tasks...) } } -func restartTasksWorker(queue *Queue) func() { +func RestartTasksWorker(queue *Queue) func() { return func() { // _, err := tasks.RestartAbandonedTasks(nil) // if err != nil { diff --git a/fly.canary.toml b/fly.canary.toml index 72616c2a..12b4efb0 100644 --- a/fly.canary.toml +++ b/fly.canary.toml @@ -17,6 +17,8 @@ DATABASE_URL = "file:/data/db/canary.db" # Wargaming API WG_PRIMARY_APP_RPS = "10" WG_REQUEST_TIMEOUT_SEC = "5" +WG_CACHE_APP_RPS = "10" +WG_CACHE_REQUEST_TIMEOUT_SEC = "15" # External services BLITZ_STARS_API_URL = "https://www.blitzstars.com/api" WOT_BLITZ_PUBLIC_API_URL_FMT = "https://%s.wotblitz.com/en/api" @@ -25,6 +27,8 @@ WOT_INSPECTOR_TANK_DB_URL = "https://armor.wotinspector.com/static/armorinspecto # Discord DISCORD_PRIMARY_GUILD_ID = "1090331056449781793" # Misc configuration +SCHEDULER_CONCURRENT_WORKERS = 10 +SCHEDULER_ENABLED = "true" LOG_LEVEL = "info" NETWORK = "tcp" PORT = "8080" diff --git a/internal/database/accounts.go b/internal/database/accounts.go index c8fb1ab7..50281a1a 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -110,7 +110,6 @@ func (c *client) UpsertAccounts(ctx context.Context, accounts []Account) map[str if err == nil { optional = append(optional, db.Account.Clan.Link(db.Clan.ID.Equals(clan.ID))) } - } _, err := c.prisma.Account. diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index ced783a5..8b1303cd 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -19,7 +19,7 @@ model AppConfiguration { updatedAt DateTime @updatedAt valueEncoded Bytes - metadataEncoded Bytes @default("") + metadataEncoded Bytes @@map("app_configurations") } @@ -38,8 +38,8 @@ model CronTask { lastRun DateTime scheduledAfter DateTime - logsEncoded Bytes @default("") - dataEncoded Bytes @default("") + logsEncoded Bytes + dataEncoded Bytes @@index([referenceId]) @@index([status, referenceId, scheduledAfter]) @@ -60,7 +60,7 @@ model CronTask { // expiresAt DateTime // referenceId String -// metadataEncoded Bytes @default("") +// metadataEncoded Bytes // @@index([createdAt]) // @@index([referenceId]) @@ -120,7 +120,7 @@ model UserConnection { permissions String referenceId String - metadataEncoded Bytes @default("") + metadataEncoded Bytes @@index([userId]) @@index([type, userId]) @@ -142,7 +142,7 @@ model UserContent { referenceId String valueEncoded Bytes - metadataEncoded Bytes @default("") + metadataEncoded Bytes @@index([userId]) @@index([type, userId]) @@ -172,8 +172,9 @@ model Account { clanId String? @@index([realm]) - @@index([nickname]) - @@index([lastBattleTime]) + @@index([realm, lastBattleTime]) + @@index([id, lastBattleTime]) + @@index([clanId]) @@map("accounts") } @@ -323,7 +324,7 @@ model Vehicle { // referenceId String // dataEncoded Bytes -// metadataEncoded Bytes @default("") +// metadataEncoded Bytes // @@map("user_interactions") // @@ignore @@ -346,7 +347,7 @@ model Vehicle { // options StatsRequestsOptions @relation(fields: [optionsId], references: [id]) // optionsId String -// metadataEncoded Bytes @default("") +// metadataEncoded Bytes // @@index([referenceId]) // @@map("live_sessions") diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 99db4dae..196cec4e 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -86,7 +86,7 @@ func (t *Task) decodeTargets(targets []byte) { func (t *Task) encodeLogs() []byte { if t.Logs == nil { - return nil + return []byte{} } data, _ := encoding.EncodeGob(t.Logs) return data @@ -97,7 +97,7 @@ func (t *Task) decodeLogs(logs []byte) { func (t *Task) encodeData() []byte { if t.Data == nil { - return nil + return []byte{} } data, _ := encoding.EncodeGob(t.Data) return data @@ -185,8 +185,8 @@ func (c *client) CreateTasks(ctx context.Context, tasks ...Task) error { db.CronTask.Type.Set(string(task.Type)), db.CronTask.ReferenceID.Set(task.ReferenceID), db.CronTask.TargetsEncoded.Set(task.encodeTargets()), - db.CronTask.Status.Set(string(task.Status)), - db.CronTask.LastRun.Set(task.LastRun), + db.CronTask.Status.Set(string(TaskStatusScheduled)), + db.CronTask.LastRun.Set(time.Now()), db.CronTask.ScheduledAfter.Set(task.ScheduledAfter), db.CronTask.LogsEncoded.Set(task.encodeLogs()), db.CronTask.DataEncoded.Set(task.encodeData()), diff --git a/internal/encoding/gob.go b/internal/encoding/gob.go index d52c5e76..4c3017f7 100644 --- a/internal/encoding/gob.go +++ b/internal/encoding/gob.go @@ -7,12 +7,12 @@ import ( func EncodeGob(data any) ([]byte, error) { if data == nil { - return nil, nil + return []byte{}, nil } var buf bytes.Buffer err := gob.NewEncoder(&buf).Encode(data) if err != nil { - return nil, err + return []byte{}, err } return buf.Bytes(), nil } diff --git a/main.go b/main.go index 794493d5..1b99021d 100644 --- a/main.go +++ b/main.go @@ -85,7 +85,7 @@ func startSchedulerFromEnvAsync() { // Some tasks should run on startup // scheduler.UpdateAveragesWorker(coreClient)() - // scheduler.UpdateGlossaryWorker(coreClient)() + // scheduler.CreateSessionTasksWorker(coreClient, "NA")() } func coreClientFromEnv() core.Client { From e765991d22bb74c1b9ac5f20cd352938bbca3a91 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 8 Jun 2024 17:42:37 -0400 Subject: [PATCH 025/341] added recover --- main.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/main.go b/main.go index 1b99021d..ee853493 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "embed" "io/fs" - "log" + "net/http" "os" "strconv" @@ -22,6 +22,7 @@ import ( "github.com/cufee/aftermath/internal/stats/render/assets" render "github.com/cufee/aftermath/internal/stats/render/common" "github.com/rs/zerolog" + "github.com/rs/zerolog/log" _ "github.com/joho/godotenv/autoload" ) @@ -57,21 +58,21 @@ func startSchedulerFromEnvAsync() { // This wargaming client is using a different proxy as it needs a lot higher rps, but can be slow wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_CACHE_APP_ID"), os.Getenv("WG_CACHE_APP_RPS"), os.Getenv("WG_CACHE_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_CACHE_PROXY_HOST_LIST")) if err != nil { - log.Fatalf("wgClient: wargaming#NewClientFromEnv failed %s", err) + log.Fatal().Msgf("wgClient: wargaming#NewClientFromEnv failed %s", err) } dbClient, err := database.NewClient() if err != nil { - log.Fatalf("database#NewClient failed %s", err) + log.Fatal().Msgf("database#NewClient failed %s", err) } bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) if err != nil { - log.Fatalf("failed to init a blitzstars client %s", err) + log.Fatal().Msgf("failed to init a blitzstars client %s", err) } // Fetch client client, err := fetch.NewMultiSourceClient(wgClient, bsClient, dbClient) if err != nil { - log.Fatalf("fetch#NewMultiSourceClient failed %s", err) + log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } coreClient := core.NewClient(client, wgClient, dbClient) @@ -81,8 +82,14 @@ func startSchedulerFromEnvAsync() { taskQueueConcurrency = v } queue := scheduler.NewQueue(coreClient, tasks.DefaultHandlers(), taskQueueConcurrency) - queue.StartCronJobsAsync() + defer func() { + if r := recover(); r != nil { + log.Error().Interface("error", r).Stack().Msg("scheduler panic") + } + }() + + queue.StartCronJobsAsync() // Some tasks should run on startup // scheduler.UpdateAveragesWorker(coreClient)() // scheduler.CreateSessionTasksWorker(coreClient, "NA")() @@ -92,21 +99,21 @@ func coreClientFromEnv() core.Client { // Dependencies wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_PRIMARY_APP_ID"), os.Getenv("WG_PRIMARY_APP_RPS"), os.Getenv("WG_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_PROXY_HOST_LIST")) if err != nil { - log.Fatalf("wgClient: wargaming#NewClientFromEnv failed %s", err) + log.Fatal().Msgf("wgClient: wargaming#NewClientFromEnv failed %s", err) } dbClient, err := database.NewClient() if err != nil { - log.Fatalf("database#NewClient failed %s", err) + log.Fatal().Msgf("database#NewClient failed %s", err) } bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) if err != nil { - log.Fatalf("failed to init a blitzstars client %s", err) + log.Fatal().Msgf("failed to init a blitzstars client %s", err) } // Fetch client client, err := fetch.NewMultiSourceClient(wgClient, bsClient, dbClient) if err != nil { - log.Fatalf("fetch#NewMultiSourceClient failed %s", err) + log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } return core.NewClient(client, wgClient, dbClient) @@ -116,14 +123,14 @@ func loadStaticAssets(static fs.FS) { // Assets for rendering err := assets.LoadAssets(static, "static") if err != nil { - log.Fatalf("assets#LoadAssets failed to load assets from static/ embed.FS %s", err) + log.Fatal().Msgf("assets#LoadAssets failed to load assets from static/ embed.FS %s", err) } err = render.InitLoadedAssets() if err != nil { - log.Fatalf("render#InitLoadedAssets failed %s", err) + log.Fatal().Msgf("render#InitLoadedAssets failed %s", err) } err = localization.LoadAssets(static, "static/localization") if err != nil { - log.Fatalf("localization#LoadAssets failed %s", err) + log.Fatal().Msgf("localization#LoadAssets failed %s", err) } } From f3d2f8ef91e9282c90db4af179526684696265bb Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 8 Jun 2024 18:40:58 -0400 Subject: [PATCH 026/341] reset db --- .../migration.sql | 23 --- .../migration.sql | 185 +++++------------- 2 files changed, 48 insertions(+), 160 deletions(-) delete mode 100644 internal/database/prisma/migrations/20240607205538_added_cron_task/migration.sql rename internal/database/prisma/migrations/{20240606205337_init => 20240608224040_init}/migration.sql (52%) diff --git a/internal/database/prisma/migrations/20240607205538_added_cron_task/migration.sql b/internal/database/prisma/migrations/20240607205538_added_cron_task/migration.sql deleted file mode 100644 index c334d540..00000000 --- a/internal/database/prisma/migrations/20240607205538_added_cron_task/migration.sql +++ /dev/null @@ -1,23 +0,0 @@ --- CreateTable -CREATE TABLE "cron_tasks" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "type" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "targetsEncoded" TEXT NOT NULL, - "status" TEXT NOT NULL, - "lastRun" DATETIME NOT NULL, - "scheduledAfter" DATETIME NOT NULL, - "logsEncoded" TEXT NOT NULL DEFAULT '', - "dataEncoded" TEXT NOT NULL DEFAULT '' -); - --- CreateIndex -CREATE INDEX "cron_tasks_referenceId_idx" ON "cron_tasks"("referenceId"); - --- CreateIndex -CREATE INDEX "cron_tasks_status_referenceId_scheduledAfter_idx" ON "cron_tasks"("status", "referenceId", "scheduledAfter"); - --- CreateIndex -CREATE INDEX "cron_tasks_lastRun_status_idx" ON "cron_tasks"("lastRun", "status"); diff --git a/internal/database/prisma/migrations/20240606205337_init/migration.sql b/internal/database/prisma/migrations/20240608224040_init/migration.sql similarity index 52% rename from internal/database/prisma/migrations/20240606205337_init/migration.sql rename to internal/database/prisma/migrations/20240608224040_init/migration.sql index d8843022..9b2a667d 100644 --- a/internal/database/prisma/migrations/20240606205337_init/migration.sql +++ b/internal/database/prisma/migrations/20240608224040_init/migration.sql @@ -3,18 +3,23 @@ CREATE TABLE "app_configurations" ( "id" TEXT NOT NULL PRIMARY KEY, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" DATETIME NOT NULL, - "valueEncoded" TEXT NOT NULL, - "metadataEncoded" TEXT NOT NULL DEFAULT '' + "valueEncoded" BLOB NOT NULL, + "metadataEncoded" BLOB NOT NULL ); -- CreateTable -CREATE TABLE "auth_nonces" ( +CREATE TABLE "cron_tasks" ( "id" TEXT NOT NULL PRIMARY KEY, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" DATETIME NOT NULL, - "expiresAt" DATETIME NOT NULL, + "type" TEXT NOT NULL, "referenceId" TEXT NOT NULL, - "metadataEncoded" TEXT NOT NULL DEFAULT '' + "targetsEncoded" BLOB NOT NULL, + "status" TEXT NOT NULL, + "lastRun" DATETIME NOT NULL, + "scheduledAfter" DATETIME NOT NULL, + "logsEncoded" BLOB NOT NULL, + "dataEncoded" BLOB NOT NULL ); -- CreateTable @@ -23,7 +28,7 @@ CREATE TABLE "users" ( "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" DATETIME NOT NULL, "permissions" TEXT NOT NULL, - "featureFlagsEncoded" TEXT NOT NULL DEFAULT '' + "featureFlags" INTEGER NOT NULL DEFAULT 0 ); -- CreateTable @@ -48,7 +53,7 @@ CREATE TABLE "user_connections" ( "type" TEXT NOT NULL, "permissions" TEXT NOT NULL, "referenceId" TEXT NOT NULL, - "metadataEncoded" TEXT NOT NULL DEFAULT '', + "metadataEncoded" BLOB NOT NULL, CONSTRAINT "user_connections_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); @@ -60,8 +65,8 @@ CREATE TABLE "user_content" ( "userId" TEXT NOT NULL, "type" TEXT NOT NULL, "referenceId" TEXT NOT NULL, - "valueEncoded" TEXT NOT NULL, - "metadataEncoded" TEXT NOT NULL DEFAULT '', + "valueEncoded" BLOB NOT NULL, + "metadataEncoded" BLOB NOT NULL, CONSTRAINT "user_content_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); @@ -79,30 +84,6 @@ CREATE TABLE "accounts" ( CONSTRAINT "accounts_clanId_fkey" FOREIGN KEY ("clanId") REFERENCES "account_clans" ("id") ON DELETE SET NULL ON UPDATE CASCADE ); --- CreateTable -CREATE TABLE "account_snapshots" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL, - "updatedAt" DATETIME NOT NULL, - "type" TEXT NOT NULL, - "accountId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "lastBattleTime" DATETIME NOT NULL, - "dataEncoded" TEXT NOT NULL -); - --- CreateTable -CREATE TABLE "account_rating_season_snapshots" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL, - "updatedAt" DATETIME NOT NULL, - "seasonId" TEXT NOT NULL, - "accountId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "lastBattleTime" DATETIME NOT NULL, - "dataEncoded" TEXT NOT NULL -); - -- CreateTable CREATE TABLE "account_clans" ( "id" TEXT NOT NULL PRIMARY KEY, @@ -116,100 +97,66 @@ CREATE TABLE "account_clans" ( ); -- CreateTable -CREATE TABLE "glossary_averages" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "dataEncoded" TEXT NOT NULL -); - --- CreateTable -CREATE TABLE "glossary_vehicles" ( +CREATE TABLE "account_snapshots" ( "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "tier" INTEGER NOT NULL, + "createdAt" DATETIME NOT NULL, "type" TEXT NOT NULL, - "class" TEXT NOT NULL, - "nation" TEXT NOT NULL, - "localizedNamesEncoded" TEXT NOT NULL + "lastBattleTime" DATETIME NOT NULL, + "accountId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "frameEncoded" BLOB NOT NULL ); -- CreateTable -CREATE TABLE "glossary_achievements" ( +CREATE TABLE "vehicle_snapshots" ( "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "section" TEXT NOT NULL, - "dataEncoded" TEXT NOT NULL + "createdAt" DATETIME NOT NULL, + "type" TEXT NOT NULL, + "lastBattleTime" DATETIME NOT NULL, + "accountId" TEXT NOT NULL, + "vehicleId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "frameEncoded" BLOB NOT NULL ); -- CreateTable -CREATE TABLE "user_interactions" ( +CREATE TABLE "achievements_snapshots" ( "id" TEXT NOT NULL PRIMARY KEY, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" DATETIME NOT NULL, - "type" TEXT NOT NULL, - "userId" TEXT NOT NULL, + "accountId" TEXT NOT NULL, "referenceId" TEXT NOT NULL, - "dataEncoded" TEXT NOT NULL, - "metadataEncoded" TEXT NOT NULL DEFAULT '' + "dataEncoded" BLOB NOT NULL ); -- CreateTable -CREATE TABLE "discord_live_sessions" ( +CREATE TABLE "glossary_averages" ( "id" TEXT NOT NULL PRIMARY KEY, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" DATETIME NOT NULL, - "locale" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "guildId" TEXT, - "channelId" TEXT NOT NULL, - "messageId" TEXT NOT NULL, - "lastUpdate" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "lastBattleTime" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "optionsEncoded" TEXT NOT NULL + "dataEncoded" BLOB NOT NULL ); -- CreateTable -CREATE TABLE "stats_request_options" ( +CREATE TABLE "glossary_vehicles" ( "id" TEXT NOT NULL PRIMARY KEY, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" DATETIME NOT NULL, + "tier" INTEGER NOT NULL, "type" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "accountId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "dataEncoded" TEXT NOT NULL + "class" TEXT NOT NULL, + "nation" TEXT NOT NULL, + "localizedNamesEncoded" BLOB NOT NULL ); -- CreateIndex -CREATE INDEX "auth_nonces_createdAt_idx" ON "auth_nonces"("createdAt"); +CREATE INDEX "cron_tasks_referenceId_idx" ON "cron_tasks"("referenceId"); -- CreateIndex -CREATE INDEX "auth_nonces_referenceId_idx" ON "auth_nonces"("referenceId"); +CREATE INDEX "cron_tasks_status_referenceId_scheduledAfter_idx" ON "cron_tasks"("status", "referenceId", "scheduledAfter"); -- CreateIndex -CREATE INDEX "auth_nonces_referenceId_expiresAt_idx" ON "auth_nonces"("referenceId", "expiresAt"); - --- CreateIndex -CREATE INDEX "user_subscriptions_userId_idx" ON "user_subscriptions"("userId"); - --- CreateIndex -CREATE INDEX "user_subscriptions_expiresAt_idx" ON "user_subscriptions"("expiresAt"); - --- CreateIndex -CREATE INDEX "user_subscriptions_type_userId_expiresAt_idx" ON "user_subscriptions"("type", "userId", "expiresAt"); - --- CreateIndex -CREATE INDEX "user_subscriptions_type_userId_idx" ON "user_subscriptions"("type", "userId"); - --- CreateIndex -CREATE INDEX "user_subscriptions_referenceId_idx" ON "user_subscriptions"("referenceId"); - --- CreateIndex -CREATE INDEX "user_subscriptions_type_referenceId_idx" ON "user_subscriptions"("type", "referenceId"); +CREATE INDEX "cron_tasks_lastRun_status_idx" ON "cron_tasks"("lastRun", "status"); -- CreateIndex CREATE INDEX "user_connections_userId_idx" ON "user_connections"("userId"); @@ -239,61 +186,25 @@ CREATE INDEX "user_content_type_referenceId_idx" ON "user_content"("type", "refe CREATE INDEX "accounts_realm_idx" ON "accounts"("realm"); -- CreateIndex -CREATE INDEX "accounts_clanId_idx" ON "accounts"("clanId"); - --- CreateIndex -CREATE INDEX "accounts_nickname_idx" ON "accounts"("nickname"); +CREATE INDEX "accounts_realm_lastBattleTime_idx" ON "accounts"("realm", "lastBattleTime"); -- CreateIndex -CREATE INDEX "accounts_lastBattleTime_idx" ON "accounts"("lastBattleTime"); +CREATE INDEX "accounts_id_lastBattleTime_idx" ON "accounts"("id", "lastBattleTime"); -- CreateIndex -CREATE INDEX "account_snapshots_createdAt_idx" ON "account_snapshots"("createdAt"); - --- CreateIndex -CREATE INDEX "account_snapshots_type_accountId_idx" ON "account_snapshots"("type", "accountId"); - --- CreateIndex -CREATE INDEX "account_snapshots_type_accountId_createdAt_idx" ON "account_snapshots"("type", "accountId", "createdAt"); - --- CreateIndex -CREATE INDEX "account_snapshots_type_accountId_lastBattleTime_idx" ON "account_snapshots"("type", "accountId", "lastBattleTime"); - --- CreateIndex -CREATE INDEX "account_snapshots_type_referenceId_idx" ON "account_snapshots"("type", "referenceId"); - --- CreateIndex -CREATE INDEX "account_snapshots_type_referenceId_createdAt_idx" ON "account_snapshots"("type", "referenceId", "createdAt"); - --- CreateIndex -CREATE INDEX "account_rating_season_snapshots_createdAt_idx" ON "account_rating_season_snapshots"("createdAt"); - --- CreateIndex -CREATE INDEX "account_rating_season_snapshots_seasonId_accountId_idx" ON "account_rating_season_snapshots"("seasonId", "accountId"); - --- CreateIndex -CREATE INDEX "account_rating_season_snapshots_seasonId_referenceId_idx" ON "account_rating_season_snapshots"("seasonId", "referenceId"); +CREATE INDEX "accounts_clanId_idx" ON "accounts"("clanId"); -- CreateIndex CREATE INDEX "account_clans_tag_idx" ON "account_clans"("tag"); -- CreateIndex -CREATE INDEX "user_interactions_type_idx" ON "user_interactions"("type"); - --- CreateIndex -CREATE INDEX "user_interactions_userId_idx" ON "user_interactions"("userId"); - --- CreateIndex -CREATE INDEX "user_interactions_userId_type_idx" ON "user_interactions"("userId", "type"); - --- CreateIndex -CREATE INDEX "user_interactions_referenceId_idx" ON "user_interactions"("referenceId"); +CREATE INDEX "account_snapshots_createdAt_idx" ON "account_snapshots"("createdAt"); -- CreateIndex -CREATE INDEX "user_interactions_referenceId_type_idx" ON "user_interactions"("referenceId", "type"); +CREATE INDEX "account_snapshots_accountId_lastBattleTime_idx" ON "account_snapshots"("accountId", "lastBattleTime"); -- CreateIndex -CREATE INDEX "discord_live_sessions_referenceId_idx" ON "discord_live_sessions"("referenceId"); +CREATE INDEX "vehicle_snapshots_createdAt_idx" ON "vehicle_snapshots"("createdAt"); -- CreateIndex -CREATE INDEX "stats_request_options_referenceId_idx" ON "stats_request_options"("referenceId"); +CREATE INDEX "vehicle_snapshots_accountId_vehicleId_lastBattleTime_idx" ON "vehicle_snapshots"("accountId", "vehicleId", "lastBattleTime"); From 96fa6fa31f65d834e2336facefa5317fffd4b9ac Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 13:15:48 -0400 Subject: [PATCH 027/341] added session tracking --- cmds/core/scheduler/queue.go | 2 +- cmds/core/scheduler/tasks/sessions.go | 152 +++++++++++++++++++++---- internal/database/accounts.go | 8 ++ internal/database/client.go | 3 + internal/database/prisma/schema.prisma | 6 +- internal/database/snapshots.go | 104 +++++++++++++++++ internal/database/tasks.go | 9 +- internal/stats/fetch/convert.go | 2 +- internal/stats/fetch/multisource.go | 2 +- 9 files changed, 259 insertions(+), 29 deletions(-) create mode 100644 internal/database/snapshots.go diff --git a/cmds/core/scheduler/queue.go b/cmds/core/scheduler/queue.go index bce1ef41..b724ee11 100644 --- a/cmds/core/scheduler/queue.go +++ b/cmds/core/scheduler/queue.go @@ -84,7 +84,7 @@ func (q *Queue) Process(callback func(error), tasks ...database.Task) { Timestamp: time.Now(), } - message, err := handler.Process(nil, t) + message, err := handler.Process(q.core, t) attempt.Comment = message if err != nil { attempt.Error = err.Error() diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index f999f95c..c8bc87b4 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -4,12 +4,19 @@ import ( "context" "errors" "strings" + "sync" "time" "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/retry" + "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/am-wg-proxy-next/v2/types" + "github.com/rs/zerolog/log" ) +type vehicleResponseData map[string][]types.VehicleStatsFrame + func init() { defaultHandlers[database.TaskTypeRecordSessions] = TaskHandler{ Process: func(client core.Client, task database.Task) (string, error) { @@ -18,32 +25,130 @@ func init() { } realm, ok := task.Data["realm"].(string) if !ok { + task.Data["triesLeft"] = int(0) // do not retry return "invalid realm", errors.New("invalid realm") } + if len(task.Targets) > 100 { + task.Data["triesLeft"] = int(0) // do not retry + return "cannot process 100+ accounts at a time", errors.New("invalid targets length") + } + if len(task.Targets) < 1 { + task.Data["triesLeft"] = int(0) // do not retry + return "targed ids cannot be left blank", errors.New("invalid targets length") + } + + log.Debug().Str("taskId", task.ID).Any("targets", task.Targets).Msg("started working on a session refresh task") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) + defer cancel() + + accounts, err := client.Wargaming().BatchAccountByID(ctx, realm, task.Targets) + if err != nil { + return "failed to fetch accounts", err + } - return "did nothing for a task on realm " + realm, nil - - // accountErrs, err := cache.RefreshSessionsAndAccounts(models.SessionTypeDaily, nil, realm, task.Targets...) - // if err != nil { - // return "failed to refresh sessions on all account", err - // } - - // var failedAccounts []int - // for accountId, err := range accountErrs { - // if err != nil && accountId != 0 { - // failedAccounts = append(failedAccounts, accountId) - // } - // } - // if len(failedAccounts) == 0 { - // return "finished session update on all accounts", nil - // } - - // // Retry failed accounts - // task.Targets = failedAccounts - // return "retrying failed accounts", errors.New("some accounts failed") + // Make a new slice just in case some accounts were not returned/are private + var validAccouts []string + for _, id := range task.Targets { + data, ok := accounts[id] + if !ok { + go func(id string) { + log.Debug().Str("accountId", id).Str("taskId", task.ID).Msg("account is private") + // update account cache (if it exists) to set account as private + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + err := client.Database().AccountSetPrivate(ctx, id, true) + if err != nil { + log.Err(err).Str("accountId", id).Msg("failed to set account status as private") + } + }(id) + continue + } + if data.LastBattleTime < int(time.Now().Add(time.Hour*-25).Unix()) { + log.Debug().Str("accountId", id).Str("taskId", task.ID).Msg("account played no battles") + // if the last battle was played 25+ hours ago, there is nothing for us to update + continue + } + validAccouts = append(validAccouts, id) + } + + if len(validAccouts) == 0 { + return "no updates required due to last battle or private accounts status", nil + } + + // clans are options-ish + clans, err := client.Wargaming().BatchAccountClan(ctx, realm, validAccouts) + if err != nil { + log.Err(err).Msg("failed to get batch account clans") + clans = make(map[string]types.ClanMember) + } + + vehicleCh := make(chan retry.DataWithErr[vehicleResponseData], len(validAccouts)) + var group sync.WaitGroup + group.Add(len(validAccouts)) + for _, id := range validAccouts { + go func(id string) { + defer group.Done() + data, err := client.Wargaming().AccountVehicles(ctx, realm, id) + vehicleCh <- retry.DataWithErr[vehicleResponseData]{Data: vehicleResponseData{id: data}, Err: err} + }(id) + } + group.Wait() + close(vehicleCh) + + var withErrors []string + var accountUpdates []database.Account + var snapshots []database.AccountSnapshot + for result := range vehicleCh { + // there is only 1 key in this map + for id, vehicles := range result.Data { + if result.Err != nil { + withErrors = append(withErrors, id) + continue + } + + stats := fetch.WargamingToStats(realm, accounts[id], clans[id], vehicles) + + accountUpdates = append(accountUpdates, database.Account{ + Realm: stats.Realm, + ID: stats.Account.ID, + Nickname: stats.Account.Nickname, + + Private: false, + CreatedAt: stats.Account.CreatedAt, + LastBattleTime: stats.LastBattleTime, + + ClanID: stats.Account.ClanID, + ClanTag: stats.Account.ClanTag, + }) + + snapshots = append(snapshots, database.AccountSnapshot{ + Type: database.SnapshotTypeDaily, + CreatedAt: time.Now(), + AccountID: stats.Account.ID, + ReferenceID: stats.Account.ID, + LastBattleTime: stats.LastBattleTime, + RatingBattles: stats.RatingBattles.StatsFrame, + RegularBattles: stats.RegularBattles.StatsFrame, + }) + } + } + + err = client.Database().CreateAccountSnapshots(ctx, snapshots...) + if err != nil { + return "failed to save snapshots to database", err + } + + if len(withErrors) == 0 { + return "finished session update on all accounts", nil + } + + // Retry failed accounts + task.Targets = withErrors + return "retrying failed accounts", errors.New("some accounts failed") }, ShouldRetry: func(task *database.Task) bool { - triesLeft, ok := task.Data["triesLeft"].(int32) + triesLeft, ok := task.Data["triesLeft"].(int) if !ok { return false } @@ -67,7 +172,7 @@ func CreateSessionUpdateTasks(client core.Client, realm string) error { ScheduledAfter: time.Now(), Data: map[string]any{ "realm": realm, - "triesLeft": int32(3), + "triesLeft": int(3), }, } @@ -81,6 +186,9 @@ func CreateSessionUpdateTasks(client core.Client, realm string) error { if len(accounts) < 1 { return nil } + for _, account := range accounts { + task.Targets = append(task.Targets, account.ID) + } // This update requires (2 + n) requests per n players return client.Database().CreateTasks(ctx, splitTaskByTargets(task, 90)...) diff --git a/internal/database/accounts.go b/internal/database/accounts.go index 50281a1a..98867e6e 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -140,3 +140,11 @@ func (c *client) UpsertAccounts(ctx context.Context, accounts []Account) map[str return errors } + +func (c *client) AccountSetPrivate(ctx context.Context, id string, value bool) error { + _, err := c.prisma.Account.FindUnique(db.Account.ID.Equals(id)).Update(db.Account.Private.Set(value)).Exec(ctx) + if err != nil && !db.IsErrNotFound(err) { + return err + } + return nil +} diff --git a/internal/database/client.go b/internal/database/client.go index b7a2be01..d7b2e961 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -13,6 +13,7 @@ type Client interface { GetAccounts(ctx context.Context, ids []string) ([]Account, error) GetRealmAccounts(ctx context.Context, realm string) ([]Account, error) UpsertAccounts(ctx context.Context, accounts []Account) map[string]error + AccountSetPrivate(ctx context.Context, id string, value bool) error GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error @@ -24,6 +25,8 @@ type Client interface { UpdateConnection(ctx context.Context, connection UserConnection) (UserConnection, error) UpsertConnection(ctx context.Context, connection UserConnection) (UserConnection, error) + CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error + CreateTasks(ctx context.Context, tasks ...Task) error UpdateTasks(ctx context.Context, tasks ...Task) error DeleteTasks(ctx context.Context, ids ...string) error diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index 8b1303cd..36669dd2 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -208,7 +208,11 @@ model AccountSnapshot { accountId String referenceId String - frameEncoded Bytes + ratingBattles Int + ratingFrameEncoded Bytes + + regularBattles Int + regularFrameEncoded Bytes @@index([createdAt]) @@index([accountId, lastBattleTime]) diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go new file mode 100644 index 00000000..2ae81f9a --- /dev/null +++ b/internal/database/snapshots.go @@ -0,0 +1,104 @@ +package database + +import ( + "context" + "time" + + "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/stats/frame" + "github.com/rs/zerolog/log" + "github.com/steebchen/prisma-client-go/runtime/transaction" +) + +// model AccountSnapshot { +// id String @id @default(cuid()) +// createdAt DateTime + +// type String +// lastBattleTime DateTime + +// accountId String +// referenceId String + +// frameEncoded Bytes + +// @@index([createdAt]) +// @@index([accountId, lastBattleTime]) +// @@map("account_snapshots") +// } + +type snapshotType string + +const ( + SnapshotTypeDaily snapshotType = "daily" +) + +type AccountSnapshot struct { + ID string + Type snapshotType + CreatedAt time.Time + AccountID string + ReferenceID string + LastBattleTime time.Time + RatingBattles frame.StatsFrame + RegularBattles frame.StatsFrame +} + +func (s *AccountSnapshot) FromModel(model db.AccountSnapshotModel) error { + s.ID = model.ID + s.Type = snapshotType(model.Type) + s.CreatedAt = model.CreatedAt + s.AccountID = model.AccountID + s.ReferenceID = model.ReferenceID + s.LastBattleTime = model.LastBattleTime + + rating, err := frame.DecodeStatsFrame(model.RatingFrameEncoded) + if err != nil { + return err + } + s.RatingBattles = rating + + regular, err := frame.DecodeStatsFrame(model.RegularFrameEncoded) + if err != nil { + return err + } + s.RegularBattles = regular + + return nil +} + +func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error { + if len(snapshots) < 1 { + return nil + } + + var transactions []transaction.Transaction + for _, data := range snapshots { + ratingEncoded, err := data.RatingBattles.Encode() + if err != nil { + log.Err(err).Str("accountId", data.AccountID).Msg("failed to encode rating stats frame for account snapthsot") + continue + } + regularEncoded, err := data.RegularBattles.Encode() + if err != nil { + log.Err(err).Str("accountId", data.AccountID).Msg("failed to encode regular stats frame for account snapthsot") + continue + } + + transactions = append(transactions, c.prisma.AccountSnapshot. + CreateOne( + db.AccountSnapshot.CreatedAt.Set(data.CreatedAt), + db.AccountSnapshot.Type.Set(string(data.Type)), + db.AccountSnapshot.LastBattleTime.Set(data.LastBattleTime), + db.AccountSnapshot.AccountID.Set(data.AccountID), + db.AccountSnapshot.ReferenceID.Set(data.ReferenceID), + db.AccountSnapshot.RatingBattles.Set(int(data.RatingBattles.Battles)), + db.AccountSnapshot.RatingFrameEncoded.Set(ratingEncoded), + db.AccountSnapshot.RegularBattles.Set(int(data.RegularBattles.Battles)), + db.AccountSnapshot.RegularFrameEncoded.Set(regularEncoded), + ).Tx(), + ) + } + + return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) +} diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 196cec4e..292253ee 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -2,6 +2,7 @@ package database import ( "context" + "encoding/json" "strings" "time" @@ -81,18 +82,20 @@ func (t *Task) encodeTargets() []byte { return []byte(strings.Join(t.Targets, ";")) } func (t *Task) decodeTargets(targets []byte) { - t.Targets = strings.Split(string(targets), ";") + if string(targets) != "" { + t.Targets = strings.Split(string(targets), ";") + } } func (t *Task) encodeLogs() []byte { if t.Logs == nil { return []byte{} } - data, _ := encoding.EncodeGob(t.Logs) + data, _ := json.Marshal(t.Logs) return data } func (t *Task) decodeLogs(logs []byte) { - _ = encoding.DecodeGob(logs, &t.Logs) + _ = json.Unmarshal(logs, &t.Logs) } func (t *Task) encodeData() []byte { diff --git a/internal/stats/fetch/convert.go b/internal/stats/fetch/convert.go index 04377552..96a4a838 100644 --- a/internal/stats/fetch/convert.go +++ b/internal/stats/fetch/convert.go @@ -32,7 +32,7 @@ func wargamingToAccount(realm string, account types.ExtendedAccount, clan types. return a } -func wargamingToStats(realm string, accountData types.ExtendedAccount, clanMember types.ClanMember, vehicleData []types.VehicleStatsFrame) AccountStatsOverPeriod { +func WargamingToStats(realm string, accountData types.ExtendedAccount, clanMember types.ClanMember, vehicleData []types.VehicleStatsFrame) AccountStatsOverPeriod { stats := AccountStatsOverPeriod{ Realm: realm, // we got the stats, so the account is obv not private at this point diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 300716ee..a98242e9 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -111,7 +111,7 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. return AccountStatsOverPeriod{}, vehicles.Err } - stats := wargamingToStats(realm, account.Data, clan, vehicles.Data) + stats := WargamingToStats(realm, account.Data, clan, vehicles.Data) if options.withWN8 { stats.AddWN8(averages.Data) } From 5f8a4fd54c54180a69ab2874a07191b62cab3b8a Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 14:06:06 -0400 Subject: [PATCH 028/341] added vehicle snapshots and cleanup tasks --- cmds/core/scheduler/cron.go | 1 + cmds/core/scheduler/tasks/cleanup.go | 67 ++++++++++++++++++++++ cmds/core/scheduler/tasks/sessions.go | 34 +++++++++++- cmds/core/scheduler/workers.go | 37 +++++++++++-- internal/database/cleanup.go | 16 ++++++ internal/database/client.go | 6 ++ internal/database/prisma/schema.prisma | 1 + internal/database/snapshots.go | 77 +++++++++++++++++++++++--- internal/database/tasks.go | 22 ++++++++ internal/stats/fetch/convert.go | 12 ++-- 10 files changed, 252 insertions(+), 21 deletions(-) create mode 100644 cmds/core/scheduler/tasks/cleanup.go create mode 100644 internal/database/cleanup.go diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go index 523c7037..144954f7 100644 --- a/cmds/core/scheduler/cron.go +++ b/cmds/core/scheduler/cron.go @@ -15,6 +15,7 @@ func (queue *Queue) StartCronJobsAsync() { c.Cron("* * * * *").Do(RunTasksWorker(queue)) // some tasks might be stuck due to a panic or restart, restart them c.Cron("0 * * * *").Do(RestartTasksWorker(queue)) + c.Cron("0 5 * * *").Do(CreateCleanupTaskWorker(queue.core)) // delete expired documents // Glossary - Do it around the same time WG releases game updates c.Cron("0 10 * * *").Do(UpdateGlossaryWorker(queue.core)) diff --git a/cmds/core/scheduler/tasks/cleanup.go b/cmds/core/scheduler/tasks/cleanup.go new file mode 100644 index 00000000..afc1eaab --- /dev/null +++ b/cmds/core/scheduler/tasks/cleanup.go @@ -0,0 +1,67 @@ +package tasks + +import ( + "context" + "errors" + "time" + + "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/internal/database" +) + + +func init() { + defaultHandlers[database.TaskTypeDatabaseCleanup] = TaskHandler{ + Process: func(client core.Client, task database.Task) (string, error) { + if task.Data == nil { + return "no data provided", errors.New("no data provided") + } + snapshotExpiration, ok := task.Data["expiration_snapshots"].(time.Time) + if !ok { + return "invalid expiration_snapshots", errors.New("failed to cast expiration_snapshots to time") + } + taskExpiration, ok := task.Data["expiration_tasks"].(time.Time) + if !ok { + task.Data["triesLeft"] = int(0) // do not retry + return "invalid expiration_tasks", errors.New("failed to cast expiration_tasks to time") + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5) + defer cancel() + + err := client.Database().DeleteExpiredTasks(ctx, taskExpiration) + if err != nil { + return "failed to delete expired tasks", err + } + + err = client.Database().DeleteExpiredSnapshots(ctx, snapshotExpiration) + if err != nil { + return "failed to delete expired snapshots", err + } + + return "cleanup complete", nil + }, + ShouldRetry: func(task *database.Task) bool { + return false + }, + } +} + +func CreateCleanupTasks(client core.Client) error { + now := time.Now() + + task := database.Task{ + Type: database.TaskTypeDatabaseCleanup, + ReferenceID: "database_cleanup", + ScheduledAfter: now, + Data: map[string]any{ + "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90), // 90 days + "expiration_tasks": now.Add(-1 * time.Hour * 24 * 7), // 7 days + }, + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + return client.Database().CreateTasks(ctx, task) +} diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index c8bc87b4..137d2e90 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -39,6 +39,7 @@ func init() { log.Debug().Str("taskId", task.ID).Any("targets", task.Targets).Msg("started working on a session refresh task") + createdAt := time.Now() ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() @@ -64,7 +65,7 @@ func init() { }(id) continue } - if data.LastBattleTime < int(time.Now().Add(time.Hour*-25).Unix()) { + if data.LastBattleTime < int(createdAt.Add(time.Hour*-25).Unix()) { log.Debug().Str("accountId", id).Str("taskId", task.ID).Msg("account played no battles") // if the last battle was played 25+ hours ago, there is nothing for us to update continue @@ -99,6 +100,7 @@ func init() { var withErrors []string var accountUpdates []database.Account var snapshots []database.AccountSnapshot + var vehicleSnapshots []database.VehicleSnapshot for result := range vehicleCh { // there is only 1 key in this map for id, vehicles := range result.Data { @@ -124,19 +126,45 @@ func init() { snapshots = append(snapshots, database.AccountSnapshot{ Type: database.SnapshotTypeDaily, - CreatedAt: time.Now(), + CreatedAt: createdAt, AccountID: stats.Account.ID, ReferenceID: stats.Account.ID, LastBattleTime: stats.LastBattleTime, RatingBattles: stats.RatingBattles.StatsFrame, RegularBattles: stats.RegularBattles.StatsFrame, }) + + if len(vehicles) < 1 { + continue + } + vehicleStats := fetch.WargamingVehiclesToFrame(vehicles) + for _, vehicle := range vehicleStats { + if vehicle.LastBattleTime.Before(createdAt.Add(time.Hour * -25)) { + log.Debug().Str("accountId", id).Str("taskId", task.ID).Msg("account played no battles") + // if the last battle was played 25+ hours ago, there is nothing for us to update + continue + } + vehicleSnapshots = append(vehicleSnapshots, database.VehicleSnapshot{ + CreatedAt: createdAt, + Type: database.SnapshotTypeDaily, + LastBattleTime: vehicle.LastBattleTime, + AccountID: stats.Account.ID, + VehicleID: vehicle.VehicleID, + ReferenceID: stats.Account.ID, + Stats: *vehicle.StatsFrame, + }) + } } } err = client.Database().CreateAccountSnapshots(ctx, snapshots...) if err != nil { - return "failed to save snapshots to database", err + return "failed to save account snapshots to database", err + } + + err = client.Database().CreateVehicleSnapshots(ctx, vehicleSnapshots...) + if err != nil { + return "failed to save vehicle snapshots to database", err } if len(withErrors) == 0 { diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index fa7e033f..65ffb35b 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -6,9 +6,19 @@ import ( "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/cmds/core/scheduler/tasks" + "github.com/cufee/aftermath/internal/database" "github.com/rs/zerolog/log" ) +func CreateCleanupTaskWorker(client core.Client) func() { + return func() { + err := tasks.CreateCleanupTasks(client) + if err != nil { + log.Err(err).Msg("failed to schedule a cleanup tasks") + } + } +} + func RotateBackgroundPresetsWorker(client core.Client) func() { return func() { // // We just run the logic directly as it's not a heavy task and it doesn't matter if it fails due to the app failing @@ -66,10 +76,27 @@ func RunTasksWorker(queue *Queue) func() { func RestartTasksWorker(queue *Queue) func() { return func() { - // _, err := tasks.RestartAbandonedTasks(nil) - // if err != nil { - // log.Err(err).Msg("failed to start scheduled tasks") - // return - // } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + staleTasks, err := queue.core.Database().GetStaleTasks(ctx, 100) + if err != nil { + log.Err(err).Msg("failed to reschedule stale tasks") + } + + if len(staleTasks) < 1 { + return + } + + now := time.Now() + for _, task := range staleTasks { + task.Status = database.TaskStatusScheduled + task.ScheduledAfter = now + } + + err = queue.core.Database().UpdateTasks(ctx, staleTasks...) + if err != nil { + log.Err(err).Msg("failed to update stale tasks") + } } } diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go new file mode 100644 index 00000000..dffc0bf3 --- /dev/null +++ b/internal/database/cleanup.go @@ -0,0 +1,16 @@ +package database + +import ( + "context" + "time" + + "github.com/cufee/aftermath/internal/database/prisma/db" +) + +func (c *client) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { + _, err := c.prisma.CronTask.FindMany(db.CronTask.CreatedAt.Before(expiration)).Delete().Exec(ctx) + if err != nil && db.IsErrNotFound(err) { + return err + } + return nil +} diff --git a/internal/database/client.go b/internal/database/client.go index d7b2e961..67a25310 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -2,6 +2,7 @@ package database import ( "context" + "time" "github.com/cufee/aftermath/internal/database/prisma/db" "github.com/cufee/aftermath/internal/stats/frame" @@ -26,11 +27,16 @@ type Client interface { UpsertConnection(ctx context.Context, connection UserConnection) (UserConnection, error) CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error + CreateVehicleSnapshots(ctx context.Context, snapshots ...VehicleSnapshot) error CreateTasks(ctx context.Context, tasks ...Task) error UpdateTasks(ctx context.Context, tasks ...Task) error DeleteTasks(ctx context.Context, ids ...string) error + GetStaleTasks(ctx context.Context, limit int) ([]Task, error) GetAndStartTasks(ctx context.Context, limit int) ([]Task, error) + + DeleteExpiredTasks(ctx context.Context, expiration time.Time) error + DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error } var _ Client = &client{} diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index 36669dd2..1947d466 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -231,6 +231,7 @@ model VehicleSnapshot { vehicleId String referenceId String + battles Int frameEncoded Bytes @@index([createdAt]) diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 2ae81f9a..e458d407 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -10,7 +10,13 @@ import ( "github.com/steebchen/prisma-client-go/runtime/transaction" ) -// model AccountSnapshot { +type snapshotType string + +const ( + SnapshotTypeDaily snapshotType = "daily" +) + +// model VehicleSnapshot { // id String @id @default(cuid()) // createdAt DateTime @@ -18,20 +24,47 @@ import ( // lastBattleTime DateTime // accountId String +// vehicleId String // referenceId String // frameEncoded Bytes // @@index([createdAt]) -// @@index([accountId, lastBattleTime]) -// @@map("account_snapshots") +// @@index([accountId, vehicleId, lastBattleTime]) +// @@map("vehicle_snapshots") // } -type snapshotType string +type VehicleSnapshot struct { + ID string + CreatedAt time.Time -const ( - SnapshotTypeDaily snapshotType = "daily" -) + Type snapshotType + LastBattleTime time.Time + + AccountID string + VehicleID string + ReferenceID string + + Stats frame.StatsFrame +} + +func (s *VehicleSnapshot) FromModel(model db.VehicleSnapshotModel) error { + s.ID = model.ID + s.Type = snapshotType(model.Type) + s.CreatedAt = model.CreatedAt + s.LastBattleTime = model.LastBattleTime + + s.AccountID = model.AccountID + s.VehicleID = model.VehicleID + s.ReferenceID = model.ReferenceID + + stats, err := frame.DecodeStatsFrame(model.FrameEncoded) + if err != nil { + return err + } + s.Stats = stats + return nil +} type AccountSnapshot struct { ID string @@ -67,6 +100,36 @@ func (s *AccountSnapshot) FromModel(model db.AccountSnapshotModel) error { return nil } +func (c *client) CreateVehicleSnapshots(ctx context.Context, snapshots ...VehicleSnapshot) error { + if len(snapshots) < 1 { + return nil + } + + var transactions []transaction.Transaction + for _, data := range snapshots { + encoded, err := data.Stats.Encode() + if err != nil { + log.Err(err).Str("accountId", data.AccountID).Str("vehicleId", data.VehicleID).Msg("failed to encode a stats frame for vehicle snapthsot") + continue + } + + transactions = append(transactions, c.prisma.VehicleSnapshot. + CreateOne( + db.VehicleSnapshot.CreatedAt.Set(data.CreatedAt), + db.VehicleSnapshot.Type.Set(string(data.Type)), + db.VehicleSnapshot.LastBattleTime.Set(data.LastBattleTime), + db.VehicleSnapshot.AccountID.Set(data.AccountID), + db.VehicleSnapshot.VehicleID.Set(data.VehicleID), + db.VehicleSnapshot.ReferenceID.Set(data.ReferenceID), + db.VehicleSnapshot.Battles.Set(int(data.Stats.Battles)), + db.VehicleSnapshot.FrameEncoded.Set(encoded), + ).Tx(), + ) + } + + return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) +} + func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error { if len(snapshots) < 1 { return nil diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 292253ee..b87fcfed 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -17,6 +17,8 @@ const ( TaskTypeRecordSessions TaskType = "RECORD_ACCOUNT_SESSIONS" TaskTypeUpdateAccountWN8 TaskType = "UPDATE_ACCOUNT_WN8" TaskTypeRecordPlayerAchievements TaskType = "UPDATE_ACCOUNT_ACHIEVEMENTS" + + TaskTypeDatabaseCleanup TaskType = "CLEANUP_DATABASE" ) // Task statuses @@ -126,6 +128,26 @@ func NewAttemptLog(task Task, comment, err string) TaskLog { } } +/* +Returns up limit tasks that have TaskStatusInProgress and were last updates 1+ hours ago +*/ +func (c *client) GetStaleTasks(ctx context.Context, limit int) ([]Task, error) { + models, err := c.prisma.CronTask.FindMany(db.CronTask.And( + db.CronTask.Status.Equals(string(TaskStatusInProgress)), + db.CronTask.UpdatedAt.Before(time.Now().Add(time.Hour*-1)), + )).OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)).Take(limit).Exec(ctx) + if err != nil && !db.IsErrNotFound(err) { + return nil, err + } + + var tasks []Task + for _, model := range models { + tasks = append(tasks, Task{}.FromModel(model)) + } + + return tasks, nil +} + /* GetAndStartTasks retrieves up to limit number of tasks matching the referenceId and updates their status to in progress - this func will block until all other calls to task update funcs are done diff --git a/internal/stats/fetch/convert.go b/internal/stats/fetch/convert.go index 96a4a838..84c0a4ff 100644 --- a/internal/stats/fetch/convert.go +++ b/internal/stats/fetch/convert.go @@ -38,11 +38,11 @@ func WargamingToStats(realm string, accountData types.ExtendedAccount, clanMembe // we got the stats, so the account is obv not private at this point Account: wargamingToAccount(realm, accountData, clanMember, false), RegularBattles: StatsWithVehicles{ - StatsFrame: wargamingToFrame(accountData.Statistics.All), - Vehicles: wargamingVehiclesToFrame(vehicleData), + StatsFrame: WargamingToFrame(accountData.Statistics.All), + Vehicles: WargamingVehiclesToFrame(vehicleData), }, RatingBattles: StatsWithVehicles{ - StatsFrame: wargamingToFrame(accountData.Statistics.Rating), + StatsFrame: WargamingToFrame(accountData.Statistics.Rating), Vehicles: make(map[string]frame.VehicleStatsFrame), }, LastBattleTime: timestampToTime(accountData.LastBattleTime), @@ -57,7 +57,7 @@ func WargamingToStats(realm string, accountData types.ExtendedAccount, clanMembe return stats } -func wargamingToFrame(wg types.StatsFrame) frame.StatsFrame { +func WargamingToFrame(wg types.StatsFrame) frame.StatsFrame { return frame.StatsFrame{ Battles: frame.ValueInt(wg.Battles), BattlesWon: frame.ValueInt(wg.Wins), @@ -75,12 +75,12 @@ func wargamingToFrame(wg types.StatsFrame) frame.StatsFrame { } } -func wargamingVehiclesToFrame(wg []types.VehicleStatsFrame) map[string]frame.VehicleStatsFrame { +func WargamingVehiclesToFrame(wg []types.VehicleStatsFrame) map[string]frame.VehicleStatsFrame { stats := make(map[string]frame.VehicleStatsFrame) for _, record := range wg { id := strconv.Itoa(record.TankID) - inner := wargamingToFrame(record.Stats) + inner := WargamingToFrame(record.Stats) stats[id] = frame.VehicleStatsFrame{ VehicleID: id, StatsFrame: &inner, From 39d1dba61db5f0232ced5514e45d558f784cebe0 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 14:07:32 -0400 Subject: [PATCH 029/341] migration --- .../migration.sql | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 internal/database/prisma/migrations/20240611180703_added_snapshots/migration.sql diff --git a/internal/database/prisma/migrations/20240611180703_added_snapshots/migration.sql b/internal/database/prisma/migrations/20240611180703_added_snapshots/migration.sql new file mode 100644 index 00000000..6a503643 --- /dev/null +++ b/internal/database/prisma/migrations/20240611180703_added_snapshots/migration.sql @@ -0,0 +1,48 @@ +/* + Warnings: + + - You are about to drop the column `frameEncoded` on the `account_snapshots` table. All the data in the column will be lost. + - Added the required column `battles` to the `vehicle_snapshots` table without a default value. This is not possible if the table is not empty. + - Added the required column `ratingBattles` to the `account_snapshots` table without a default value. This is not possible if the table is not empty. + - Added the required column `ratingFrameEncoded` to the `account_snapshots` table without a default value. This is not possible if the table is not empty. + - Added the required column `regularBattles` to the `account_snapshots` table without a default value. This is not possible if the table is not empty. + - Added the required column `regularFrameEncoded` to the `account_snapshots` table without a default value. This is not possible if the table is not empty. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_vehicle_snapshots" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL, + "type" TEXT NOT NULL, + "lastBattleTime" DATETIME NOT NULL, + "accountId" TEXT NOT NULL, + "vehicleId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "battles" INTEGER NOT NULL, + "frameEncoded" BLOB NOT NULL +); +INSERT INTO "new_vehicle_snapshots" ("accountId", "createdAt", "frameEncoded", "id", "lastBattleTime", "referenceId", "type", "vehicleId") SELECT "accountId", "createdAt", "frameEncoded", "id", "lastBattleTime", "referenceId", "type", "vehicleId" FROM "vehicle_snapshots"; +DROP TABLE "vehicle_snapshots"; +ALTER TABLE "new_vehicle_snapshots" RENAME TO "vehicle_snapshots"; +CREATE INDEX "vehicle_snapshots_createdAt_idx" ON "vehicle_snapshots"("createdAt"); +CREATE INDEX "vehicle_snapshots_accountId_vehicleId_lastBattleTime_idx" ON "vehicle_snapshots"("accountId", "vehicleId", "lastBattleTime"); +CREATE TABLE "new_account_snapshots" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL, + "type" TEXT NOT NULL, + "lastBattleTime" DATETIME NOT NULL, + "accountId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "ratingBattles" INTEGER NOT NULL, + "ratingFrameEncoded" BLOB NOT NULL, + "regularBattles" INTEGER NOT NULL, + "regularFrameEncoded" BLOB NOT NULL +); +INSERT INTO "new_account_snapshots" ("accountId", "createdAt", "id", "lastBattleTime", "referenceId", "type") SELECT "accountId", "createdAt", "id", "lastBattleTime", "referenceId", "type" FROM "account_snapshots"; +DROP TABLE "account_snapshots"; +ALTER TABLE "new_account_snapshots" RENAME TO "account_snapshots"; +CREATE INDEX "account_snapshots_createdAt_idx" ON "account_snapshots"("createdAt"); +CREATE INDEX "account_snapshots_accountId_lastBattleTime_idx" ON "account_snapshots"("accountId", "lastBattleTime"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; From fbd192a5ab43838d939f212f73c19e144c962de8 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 14:09:12 -0400 Subject: [PATCH 030/341] added missing code --- internal/database/cleanup.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go index dffc0bf3..4f045503 100644 --- a/internal/database/cleanup.go +++ b/internal/database/cleanup.go @@ -9,7 +9,19 @@ import ( func (c *client) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { _, err := c.prisma.CronTask.FindMany(db.CronTask.CreatedAt.Before(expiration)).Delete().Exec(ctx) - if err != nil && db.IsErrNotFound(err) { + if err != nil && !db.IsErrNotFound(err) { + return err + } + return nil +} + +func (c *client) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { + _, err := c.prisma.AccountSnapshot.FindMany(db.AccountSnapshot.CreatedAt.Before(expiration)).Delete().Exec(ctx) + if err != nil && !db.IsErrNotFound(err) { + return err + } + _, err = c.prisma.VehicleSnapshot.FindMany(db.VehicleSnapshot.CreatedAt.Before(expiration)).Delete().Exec(ctx) + if err != nil && !db.IsErrNotFound(err) { return err } return nil From 518a988c5a45358aa4f521908f7477d0c5908988 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 14:12:28 -0400 Subject: [PATCH 031/341] more missing changes :sweat_smile: --- cmds/core/scheduler/tasks/sessions.go | 11 ++++++----- internal/stats/fetch/convert.go | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index 137d2e90..872b5b82 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -36,6 +36,7 @@ func init() { task.Data["triesLeft"] = int(0) // do not retry return "targed ids cannot be left blank", errors.New("invalid targets length") } + forceUpdate, _ := task.Data["force"].(bool) log.Debug().Str("taskId", task.ID).Any("targets", task.Targets).Msg("started working on a session refresh task") @@ -65,9 +66,9 @@ func init() { }(id) continue } - if data.LastBattleTime < int(createdAt.Add(time.Hour*-25).Unix()) { - log.Debug().Str("accountId", id).Str("taskId", task.ID).Msg("account played no battles") + if !forceUpdate && data.LastBattleTime < int(createdAt.Add(time.Hour*-25).Unix()) { // if the last battle was played 25+ hours ago, there is nothing for us to update + log.Debug().Str("accountId", id).Str("taskId", task.ID).Msg("account played no battles") continue } validAccouts = append(validAccouts, id) @@ -133,15 +134,15 @@ func init() { RatingBattles: stats.RatingBattles.StatsFrame, RegularBattles: stats.RegularBattles.StatsFrame, }) - if len(vehicles) < 1 { continue } + vehicleStats := fetch.WargamingVehiclesToFrame(vehicles) for _, vehicle := range vehicleStats { - if vehicle.LastBattleTime.Before(createdAt.Add(time.Hour * -25)) { - log.Debug().Str("accountId", id).Str("taskId", task.ID).Msg("account played no battles") + if !forceUpdate && vehicle.LastBattleTime.Before(createdAt.Add(time.Hour*-25)) { // if the last battle was played 25+ hours ago, there is nothing for us to update + log.Debug().Str("accountId", id).Str("vehicleId", vehicle.VehicleID).Str("taskId", task.ID).Msg("vehicle played no battles") continue } vehicleSnapshots = append(vehicleSnapshots, database.VehicleSnapshot{ diff --git a/internal/stats/fetch/convert.go b/internal/stats/fetch/convert.go index 84c0a4ff..224ea83c 100644 --- a/internal/stats/fetch/convert.go +++ b/internal/stats/fetch/convert.go @@ -122,7 +122,7 @@ func blitzstarsToStats(vehicles map[string]frame.VehicleStatsFrame, histories ma } if selectedEntry.Stats.Battles < int(vehicle.Battles) { - selectedFrame := wargamingToFrame(selectedEntry.Stats) + selectedFrame := WargamingToFrame(selectedEntry.Stats) vehicle.StatsFrame.Subtract(selectedFrame) stats.Vehicles[vehicle.VehicleID] = vehicle From ab32f0e7791952736c0aa03028ce6be107a31b85 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 14:28:28 -0400 Subject: [PATCH 032/341] fixed how period is calculated --- internal/stats/fetch/convert.go | 4 ++-- internal/stats/fetch/multisource.go | 1 + internal/stats/prepare/period/card.go | 18 +++++++++++------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/internal/stats/fetch/convert.go b/internal/stats/fetch/convert.go index 224ea83c..342d1a18 100644 --- a/internal/stats/fetch/convert.go +++ b/internal/stats/fetch/convert.go @@ -46,11 +46,11 @@ func WargamingToStats(realm string, accountData types.ExtendedAccount, clanMembe Vehicles: make(map[string]frame.VehicleStatsFrame), }, LastBattleTime: timestampToTime(accountData.LastBattleTime), - PeriodStart: time.Now(), PeriodEnd: timestampToTime(accountData.LastBattleTime), + PeriodStart: timestampToTime(accountData.CreatedAt), } // An account can be blank with no last battle played - if stats.PeriodEnd.Before(stats.PeriodStart) { + if stats.LastBattleTime.IsZero() { stats.PeriodEnd = stats.PeriodStart } diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index a98242e9..bd59099e 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -193,6 +193,7 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt return AccountStatsOverPeriod{}, histories.Err } + current.Data.PeriodEnd = time.Now() current.Data.PeriodStart = periodStart current.Data.RatingBattles = StatsWithVehicles{} // blitzstars do not provide rating battles stats current.Data.RegularBattles = blitzstarsToStats(current.Data.RegularBattles.Vehicles, histories.Data, periodStart) diff --git a/internal/stats/prepare/period/card.go b/internal/stats/prepare/period/card.go index 5931d109..1b43c812 100644 --- a/internal/stats/prepare/period/card.go +++ b/internal/stats/prepare/period/card.go @@ -2,6 +2,7 @@ package period import ( "fmt" + "math" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/fetch" @@ -41,21 +42,24 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.V } // Vehicle Highlights - var minimumBattles int = 5 + var minimumBattles float64 = 5 periodDays := stats.PeriodEnd.Sub(stats.PeriodStart).Hours() / 24 + withFallback := func(battles float64) float64 { + return math.Min(battles, float64(stats.RegularBattles.Battles.Float())/float64(len(selectedHighlights))) + } if periodDays > 90 { - minimumBattles = 100 + minimumBattles = withFallback(100) } else if periodDays > 60 { - minimumBattles = 75 + minimumBattles = withFallback(75) } else if periodDays > 30 { - minimumBattles = 50 + minimumBattles = withFallback(50) } else if periodDays > 14 { - minimumBattles = 25 + minimumBattles = withFallback(25) } else if periodDays > 7 { - minimumBattles = 10 + minimumBattles = withFallback(10) } - highlightedVehicles := getHighlightedVehicles(selectedHighlights, stats.RegularBattles.Vehicles, minimumBattles) + highlightedVehicles := getHighlightedVehicles(selectedHighlights, stats.RegularBattles.Vehicles, int(minimumBattles)) for _, data := range highlightedVehicles { var vehicleBlocks []common.StatsBlock[BlockData] From 9c678a4b8815344e12e479c698bdaabdd10f3069 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 15:26:48 -0400 Subject: [PATCH 033/341] fixed bad calculations --- .air.toml | 2 + internal/stats/render/common/images.go | 56 +++++++------------- internal/stats/render/common/player-title.go | 3 +- internal/stats/render/common/strings.go | 3 ++ internal/stats/render/period/cards.go | 8 ++- internal/stats/render/period/constants.go | 2 +- 6 files changed, 30 insertions(+), 44 deletions(-) create mode 100644 .air.toml diff --git a/.air.toml b/.air.toml new file mode 100644 index 00000000..44d5d232 --- /dev/null +++ b/.air.toml @@ -0,0 +1,2 @@ +[build] +stop_on_error = true diff --git a/internal/stats/render/common/images.go b/internal/stats/render/common/images.go index b350da6c..5e5f6ab3 100644 --- a/internal/stats/render/common/images.go +++ b/internal/stats/render/common/images.go @@ -142,8 +142,6 @@ type imageSize struct { extraSpacingX float64 extraSpacingY float64 - totalGap float64 - maxElementWidth float64 maxElementHeight float64 } @@ -151,63 +149,47 @@ type imageSize struct { func getDetailedSize(images []image.Image, style Style) imageSize { imageWidth, imageHeight := style.Width, style.Height - var totalGap float64 - if len(images) > 1 { - totalGap = float64(len(images)-1) * style.Gap - } - - var totalWidth float64 = style.PaddingX * 2 - var totalHeight float64 = style.PaddingY * 2 - + var totalWidth float64 + var totalHeight float64 maxWidth, maxHeight := 0.0, 0.0 - for _, img := range images { imgX := float64(img.Bounds().Dx()) - if imgX > maxWidth { - maxWidth = imgX - } + maxWidth = max(maxWidth, imgX) imgY := float64(img.Bounds().Dy()) - if imgY > maxHeight { - maxHeight = imgY - } + maxHeight = max(maxHeight, imgY) if style.Direction == DirectionHorizontal { totalWidth += float64(img.Bounds().Dx()) + totalHeight = max(totalHeight, imgY) } else { totalHeight += float64(img.Bounds().Dy()) + totalWidth = max(totalWidth, imgX) } } - if style.Width == 0 { + totalWidth += style.PaddingX * 2 + totalHeight += style.PaddingY * 2 + totalGap := float64(len(images)-1) * style.Gap + + switch style.Direction { + case DirectionVertical: + totalHeight += totalGap + case DirectionHorizontal: + totalWidth += totalGap + } + + if imageWidth < 1 { imageWidth = totalWidth } - if style.Height == 0 { + if imageHeight < 1 { imageHeight = totalHeight } extraSpacingX := imageWidth - totalWidth extraSpacingY := imageHeight - totalHeight - switch style.Direction { - case DirectionVertical: - if extraSpacingY < totalGap { - imageHeight += totalGap - } - if style.Width == 0 { - imageWidth = maxWidth + (style.PaddingX * 2) - } - default: // DirectionHorizontal - if extraSpacingX < totalGap { - imageWidth += totalGap - } - if style.Height == 0 { - imageHeight = maxHeight + (style.PaddingY)*2 - } - } - return imageSize{ - totalGap: totalGap, width: imageWidth, height: imageHeight, extraSpacingX: extraSpacingX, diff --git a/internal/stats/render/common/player-title.go b/internal/stats/render/common/player-title.go index e8bd5036..bced65f9 100644 --- a/internal/stats/render/common/player-title.go +++ b/internal/stats/render/common/player-title.go @@ -20,6 +20,7 @@ func (style TitleCardStyle) TotalPaddingAndGaps() float64 { func DefaultPlayerTitleStyle(containerStyle Style) TitleCardStyle { containerStyle.AlignItems = AlignItemsCenter containerStyle.Direction = DirectionHorizontal + // containerStyle.Debug = true clanTagBackgroundColor := DefaultCardColor clanTagBackgroundColor.R += 10 @@ -29,7 +30,7 @@ func DefaultPlayerTitleStyle(containerStyle Style) TitleCardStyle { return TitleCardStyle{ Container: containerStyle, Nickname: Style{Font: &FontLarge, FontColor: TextPrimary}, - ClanTag: Style{Font: &FontMedium, FontColor: TextSecondary, PaddingX: 10, PaddingY: 5, BackgroundColor: clanTagBackgroundColor, BorderRadius: 10}, + ClanTag: Style{Font: &FontMedium, FontColor: TextSecondary, PaddingX: 10, PaddingY: 5, BackgroundColor: clanTagBackgroundColor, BorderRadius: 15}, } } diff --git a/internal/stats/render/common/strings.go b/internal/stats/render/common/strings.go index 4fd5f8f6..c6a3ba15 100644 --- a/internal/stats/render/common/strings.go +++ b/internal/stats/render/common/strings.go @@ -18,6 +18,9 @@ func MeasureString(text string, font font.Face) stringSize { if font == nil { return stringSize{} } + if text == "" { + return stringSize{} + } measureCtx := gg.NewContext(1, 1) measureCtx.SetFontFace(font) diff --git a/internal/stats/render/period/cards.go b/internal/stats/render/period/cards.go index 7c23d2f1..fb83d91d 100644 --- a/internal/stats/render/period/cards.go +++ b/internal/stats/render/period/cards.go @@ -103,17 +103,15 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs if err != nil { return segments, err } - cardWidth = common.Max(cardWidth, float64(footerImage.Bounds().Dx())) segments.AddFooter(common.NewImageContent(common.Style{Width: cardWidth, Height: float64(footerImage.Bounds().Dy())}, footerImage)) } // Header card - if headerCard, headerCardExists := newHeaderCard(subs, opts); headerCardExists { + if headerCard, headerCardExists := newHeaderCard(cardWidth, subs, opts); headerCardExists { headerImage, err := headerCard.Render() if err != nil { return segments, err } - cardWidth = common.Max(cardWidth, float64(headerImage.Bounds().Dx())) segments.AddHeader(common.NewImageContent(common.Style{Width: cardWidth, Height: float64(headerImage.Bounds().Dy())}, headerImage)) } @@ -141,7 +139,7 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs return segments, nil } -func newHeaderCard(subscriptions []database.UserSubscription, options render.Options) (common.Block, bool) { +func newHeaderCard(width float64, subscriptions []database.UserSubscription, options render.Options) (common.Block, bool) { var cards []common.Block var addPromoText = true @@ -180,7 +178,7 @@ func newHeaderCard(subscriptions []database.UserSubscription, options render.Opt return common.Block{}, false } - return common.NewBlocksContent(common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, JustifyContent: common.JustifyContentCenter, Gap: 10}, cards...), true + return common.NewBlocksContent(common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, JustifyContent: common.JustifyContentCenter, Gap: 10, Width: width}, cards...), true } func newHighlightCard(style highlightStyle, card period.VehicleCard) common.Block { diff --git a/internal/stats/render/period/constants.go b/internal/stats/render/period/constants.go index bbb2b7a3..a919ef50 100644 --- a/internal/stats/render/period/constants.go +++ b/internal/stats/render/period/constants.go @@ -82,7 +82,7 @@ func overviewCardStyle(width float64) common.Style { func highlightCardStyle(containerStyle common.Style) highlightStyle { container := containerStyle - container.Gap = 5 + container.Gap = 10 container.PaddingX = 20 container.PaddingY = 15 container.Direction = common.DirectionHorizontal From 3a83c557a32db4d7e80795edb109133c6a6db7bc Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 15:54:02 -0400 Subject: [PATCH 034/341] fixed bad gap math --- internal/stats/render/common/images.go | 6 ++++-- internal/stats/render/common/player-title.go | 6 +++--- internal/stats/render/period/cards.go | 6 +----- internal/stats/render/period/constants.go | 1 + 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/internal/stats/render/common/images.go b/internal/stats/render/common/images.go index 5e5f6ab3..7747d34f 100644 --- a/internal/stats/render/common/images.go +++ b/internal/stats/render/common/images.go @@ -93,7 +93,7 @@ func renderImages(images []image.Image, style Style) (image.Image, error) { switch style.Direction { case DirectionVertical: if i > 0 { - posY += max(style.Gap, justifyOffsetY) + posY += justifyOffsetY + style.Gap } lastY = posY + targetHeight @@ -107,7 +107,7 @@ func renderImages(images []image.Image, style Style) (image.Image, error) { } default: // DirectionHorizontal if i > 0 { - posX += max(style.Gap, justifyOffsetX) + posX += justifyOffsetX + style.Gap } lastX = posX + targetWidth @@ -138,6 +138,7 @@ type imageSize struct { width float64 height float64 + totalGap float64 // The amount of extra spacing added to the image, used for alignment extraSpacingX float64 extraSpacingY float64 @@ -192,6 +193,7 @@ func getDetailedSize(images []image.Image, style Style) imageSize { return imageSize{ width: imageWidth, height: imageHeight, + totalGap: totalGap, extraSpacingX: extraSpacingX, extraSpacingY: extraSpacingY, maxElementWidth: maxWidth, diff --git a/internal/stats/render/common/player-title.go b/internal/stats/render/common/player-title.go index bced65f9..07bf771c 100644 --- a/internal/stats/render/common/player-title.go +++ b/internal/stats/render/common/player-title.go @@ -23,9 +23,9 @@ func DefaultPlayerTitleStyle(containerStyle Style) TitleCardStyle { // containerStyle.Debug = true clanTagBackgroundColor := DefaultCardColor - clanTagBackgroundColor.R += 10 - clanTagBackgroundColor.G += 10 - clanTagBackgroundColor.B += 10 + clanTagBackgroundColor.R += 15 + clanTagBackgroundColor.G += 15 + clanTagBackgroundColor.B += 15 return TitleCardStyle{ Container: containerStyle, diff --git a/internal/stats/render/period/cards.go b/internal/stats/render/period/cards.go index fb83d91d..69ed06e0 100644 --- a/internal/stats/render/period/cards.go +++ b/internal/stats/render/period/cards.go @@ -108,11 +108,7 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs // Header card if headerCard, headerCardExists := newHeaderCard(cardWidth, subs, opts); headerCardExists { - headerImage, err := headerCard.Render() - if err != nil { - return segments, err - } - segments.AddHeader(common.NewImageContent(common.Style{Width: cardWidth, Height: float64(headerImage.Bounds().Dy())}, headerImage)) + segments.AddHeader(headerCard) } // Player Title card diff --git a/internal/stats/render/period/constants.go b/internal/stats/render/period/constants.go index a919ef50..d50686ee 100644 --- a/internal/stats/render/period/constants.go +++ b/internal/stats/render/period/constants.go @@ -65,6 +65,7 @@ func defaultCardStyle(width float64) common.Style { func titleCardStyle(width float64) common.Style { style := defaultCardStyle(width) style.PaddingX = style.PaddingY + // style.Debug = true return style } From 06605f90fdb8797114aac86e50cde3a01e965d47 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 15:58:34 -0400 Subject: [PATCH 035/341] shorted date strings --- internal/stats/render/period/cards.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/stats/render/period/cards.go b/internal/stats/render/period/cards.go index 69ed06e0..27e2326a 100644 --- a/internal/stats/render/period/cards.go +++ b/internal/stats/render/period/cards.go @@ -91,8 +91,8 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs footer = append(footer, "Asia") } - sessionTo := stats.PeriodEnd.Format("January 2, 2006") - sessionFrom := stats.PeriodStart.Format("January 2, 2006") + sessionTo := stats.PeriodEnd.Format("Jan 2, 2006") + sessionFrom := stats.PeriodStart.Format("Jan 2, 2006") if sessionFrom == sessionTo { footer = append(footer, sessionTo) } else { From 63d973b5e0a46b46a5571e7ce15ff478a3677aad Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 11 Jun 2024 16:15:37 -0400 Subject: [PATCH 036/341] more logs on err --- cmds/core/scheduler/tasks/cleanup.go | 14 +++++++------- cmds/core/scheduler/tasks/sessions.go | 3 ++- cmds/discord/common/context.go | 3 ++- cmds/discord/rest/client.go | 9 ++++++--- cmds/discord/router/handler.go | 12 ++++-------- internal/database/users.go | 3 ++- internal/localization/load.go | 3 ++- internal/retry/retry.go | 3 ++- internal/stats/fetch/multisource.go | 3 ++- internal/stats/fetch/replay/errors.go | 2 +- internal/stats/image.go | 3 ++- internal/stats/prepare/common/card.go | 2 +- internal/stats/prepare/common/tags.go | 2 +- internal/stats/render/common/badges.go | 3 ++- internal/stats/render/common/block.go | 3 ++- internal/stats/render/common/constants.go | 3 ++- internal/stats/render/common/images.go | 3 ++- internal/stats/render/period/cards.go | 5 ++++- internal/stats/render/segments.go | 3 ++- 19 files changed, 48 insertions(+), 34 deletions(-) diff --git a/cmds/core/scheduler/tasks/cleanup.go b/cmds/core/scheduler/tasks/cleanup.go index afc1eaab..47177792 100644 --- a/cmds/core/scheduler/tasks/cleanup.go +++ b/cmds/core/scheduler/tasks/cleanup.go @@ -2,14 +2,14 @@ package tasks import ( "context" - "errors" "time" + "github.com/pkg/errors" + "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/internal/database" ) - func init() { defaultHandlers[database.TaskTypeDatabaseCleanup] = TaskHandler{ Process: func(client core.Client, task database.Task) (string, error) { @@ -26,17 +26,17 @@ func init() { return "invalid expiration_tasks", errors.New("failed to cast expiration_tasks to time") } - ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() err := client.Database().DeleteExpiredTasks(ctx, taskExpiration) if err != nil { - return "failed to delete expired tasks", err + return "failed to delete expired tasks", err } err = client.Database().DeleteExpiredSnapshots(ctx, snapshotExpiration) if err != nil { - return "failed to delete expired snapshots", err + return "failed to delete expired snapshots", err } return "cleanup complete", nil @@ -55,8 +55,8 @@ func CreateCleanupTasks(client core.Client) error { ReferenceID: "database_cleanup", ScheduledAfter: now, Data: map[string]any{ - "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90), // 90 days - "expiration_tasks": now.Add(-1 * time.Hour * 24 * 7), // 7 days + "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90), // 90 days + "expiration_tasks": now.Add(-1 * time.Hour * 24 * 7), // 7 days }, } diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index 872b5b82..93f69a0d 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -2,11 +2,12 @@ package tasks import ( "context" - "errors" "strings" "sync" "time" + "github.com/pkg/errors" + "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/retry" diff --git a/cmds/discord/common/context.go b/cmds/discord/common/context.go index e9e6b74e..48f745f4 100644 --- a/cmds/discord/common/context.go +++ b/cmds/discord/common/context.go @@ -2,10 +2,11 @@ package common import ( "context" - "errors" "fmt" "io" + "github.com/pkg/errors" + "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/cmds/discord/rest" diff --git a/cmds/discord/rest/client.go b/cmds/discord/rest/client.go index bbc26f9f..859686cc 100644 --- a/cmds/discord/rest/client.go +++ b/cmds/discord/rest/client.go @@ -3,15 +3,16 @@ package rest import ( "bytes" "encoding/json" - "errors" "fmt" "io" "mime/multipart" "net/http" "net/textproto" - "strings" + "runtime/debug" "time" + "github.com/pkg/errors" + "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/internal/retry" ) @@ -125,7 +126,9 @@ func (c *Client) do(req *http.Request, target any) error { message = res.Status } - return nil, errors.New("discord: " + strings.ToLower(message)) + println(string(debug.Stack())) + + return nil, errors.New("discord error: " + message) } if target != nil { diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 7f11f828..c84fb04a 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -6,13 +6,14 @@ import ( "crypto/ed25519" "encoding/hex" "encoding/json" - "errors" "fmt" "io" "net/http" "sync" "time" + "github.com/pkg/errors" + "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmds/discord/commands/builder" "github.com/cufee/aftermath/cmds/discord/common" @@ -242,16 +243,11 @@ func (r *Router) sendInteractionReply(interaction discordgo.Interaction, state * switch interaction.Type { case discordgo.InteractionApplicationCommand: - if state.replied { + if state.replied || state.acked { // We already replied to this interaction - edit the message handler = func() error { return r.restClient.UpdateInteractionResponse(interaction.AppID, interaction.Token, data) } - } else if state.acked { - // We acked this interaction and an initial reply is pending - edit the message - handler = func() error { - return r.restClient.UpdateInteractionResponse(interaction.AppID, interaction.Token, data) - } } else { // We never replied to this message, create a new reply handler = func() error { @@ -263,7 +259,7 @@ func (r *Router) sendInteractionReply(interaction discordgo.Interaction, state * err := handler() if err != nil { - log.Err(err).Any("data", data).Str("id", interaction.ID).Stack().Msg("failed to send an interaction response") + log.Err(err).Stack().Any("state", state).Any("data", data).Str("id", interaction.ID).Msg("failed to send an interaction response") return } state.acked = true diff --git a/internal/database/users.go b/internal/database/users.go index 09dbc345..24d89cee 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -2,11 +2,12 @@ package database import ( "context" - "errors" "fmt" "slices" "time" + "github.com/pkg/errors" + "github.com/cufee/aftermath/internal/database/prisma/db" "github.com/cufee/aftermath/internal/encoding" "github.com/cufee/aftermath/internal/permissions" diff --git a/internal/localization/load.go b/internal/localization/load.go index 28300e4a..3db9b5e6 100644 --- a/internal/localization/load.go +++ b/internal/localization/load.go @@ -2,12 +2,13 @@ package localization import ( "bytes" - "errors" "fmt" "io/fs" "path/filepath" "strings" + "github.com/pkg/errors" + "github.com/cufee/aftermath/internal/files" "golang.org/x/text/language" "gopkg.in/yaml.v3" diff --git a/internal/retry/retry.go b/internal/retry/retry.go index f45c4ad9..7585e5c6 100644 --- a/internal/retry/retry.go +++ b/internal/retry/retry.go @@ -1,8 +1,9 @@ package retry import ( - "errors" "time" + + "github.com/pkg/errors" ) type DataWithErr[T any] struct { diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index bd59099e..42de615c 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -2,11 +2,12 @@ package fetch import ( "context" - "errors" "fmt" "sync" "time" + "github.com/pkg/errors" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" diff --git a/internal/stats/fetch/replay/errors.go b/internal/stats/fetch/replay/errors.go index 03d97a99..2e80a91e 100644 --- a/internal/stats/fetch/replay/errors.go +++ b/internal/stats/fetch/replay/errors.go @@ -1,6 +1,6 @@ package replay -import "errors" +import "github.com/pkg/errors" var ( ErrInvalidReplayFile = errors.New("invalid replay file") diff --git a/internal/stats/image.go b/internal/stats/image.go index 9e73843b..7e46305c 100644 --- a/internal/stats/image.go +++ b/internal/stats/image.go @@ -1,11 +1,12 @@ package stats import ( - "errors" "image" "image/png" "io" + "github.com/pkg/errors" + "github.com/cufee/aftermath/internal/stats/render/common" ) diff --git a/internal/stats/prepare/common/card.go b/internal/stats/prepare/common/card.go index 56e370ce..bfae299f 100644 --- a/internal/stats/prepare/common/card.go +++ b/internal/stats/prepare/common/card.go @@ -1,7 +1,7 @@ package common import ( - "errors" + "github.com/pkg/errors" "github.com/cufee/aftermath/internal/stats/frame" ) diff --git a/internal/stats/prepare/common/tags.go b/internal/stats/prepare/common/tags.go index 34301e15..4e171eae 100644 --- a/internal/stats/prepare/common/tags.go +++ b/internal/stats/prepare/common/tags.go @@ -1,7 +1,7 @@ package common import ( - "errors" + "github.com/pkg/errors" ) type Tag string diff --git a/internal/stats/render/common/badges.go b/internal/stats/render/common/badges.go index 12e6e1dd..3d8e24c0 100644 --- a/internal/stats/render/common/badges.go +++ b/internal/stats/render/common/badges.go @@ -1,10 +1,11 @@ package common import ( - "errors" "image/color" "slices" + "github.com/pkg/errors" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/render/assets" ) diff --git a/internal/stats/render/common/block.go b/internal/stats/render/common/block.go index da5987ec..c5b20853 100644 --- a/internal/stats/render/common/block.go +++ b/internal/stats/render/common/block.go @@ -1,10 +1,11 @@ package common import ( - "errors" "image" "strings" + "github.com/pkg/errors" + "github.com/disintegration/imaging" "github.com/fogleman/gg" ) diff --git a/internal/stats/render/common/constants.go b/internal/stats/render/common/constants.go index dcdcb341..05e81b19 100644 --- a/internal/stats/render/common/constants.go +++ b/internal/stats/render/common/constants.go @@ -1,9 +1,10 @@ package common import ( - "errors" "image/color" + "github.com/pkg/errors" + "github.com/cufee/aftermath/internal/stats/render/assets" "golang.org/x/image/font" ) diff --git a/internal/stats/render/common/images.go b/internal/stats/render/common/images.go index 7747d34f..bda0c696 100644 --- a/internal/stats/render/common/images.go +++ b/internal/stats/render/common/images.go @@ -1,10 +1,11 @@ package common import ( - "errors" "image" "math" + "github.com/pkg/errors" + "github.com/disintegration/imaging" "github.com/fogleman/gg" ) diff --git a/internal/stats/render/period/cards.go b/internal/stats/render/period/cards.go index 27e2326a..107a5f02 100644 --- a/internal/stats/render/period/cards.go +++ b/internal/stats/render/period/cards.go @@ -1,9 +1,10 @@ package period import ( - "errors" "strings" + "github.com/pkg/errors" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/fetch" prepare "github.com/cufee/aftermath/internal/stats/prepare/common" @@ -103,6 +104,8 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs if err != nil { return segments, err } + + cardWidth = max(cardWidth, float64(footerImage.Bounds().Dx())) segments.AddFooter(common.NewImageContent(common.Style{Width: cardWidth, Height: float64(footerImage.Bounds().Dy())}, footerImage)) } diff --git a/internal/stats/render/segments.go b/internal/stats/render/segments.go index 561e915c..e3135c50 100644 --- a/internal/stats/render/segments.go +++ b/internal/stats/render/segments.go @@ -1,9 +1,10 @@ package render import ( - "errors" "image" + "github.com/pkg/errors" + "github.com/cufee/aftermath/internal/stats/render/common" ) From f1862ba8c9fed2563ffab1fc116a87da0907acf7 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 12 Jun 2024 16:28:28 -0400 Subject: [PATCH 037/341] added sessions --- Taskfile.yaml | 21 +- cmds/core/scheduler/tasks/sessions.go | 8 +- cmds/discord/commands/builder/option.go | 7 + cmds/discord/commands/session.go | 76 ++++++ cmds/discord/rest/client.go | 10 +- internal/database/client.go | 6 +- .../20240612202803_sessions/migration.sql | 50 ++++ internal/database/prisma/schema.prisma | 16 +- internal/database/snapshots.go | 254 ++++++++++++++---- internal/database/snapshots_test.go | 121 +++++++++ internal/stats/fetch/client.go | 2 + internal/stats/fetch/multisource.go | 132 +++++++++ internal/stats/fetch/multisource_test.go | 27 ++ internal/stats/frame/frame.go | 18 ++ internal/stats/period.go | 8 - .../{player-title.go => player_title.go} | 2 +- ...{tier-percentage.go => tier_percentage.go} | 0 internal/stats/render/period/constants.go | 1 + internal/stats/renderer.go | 8 +- internal/stats/session.go | 62 +++++ main.go | 2 +- 21 files changed, 746 insertions(+), 85 deletions(-) create mode 100644 cmds/discord/commands/session.go create mode 100644 internal/database/prisma/migrations/20240612202803_sessions/migration.sql create mode 100644 internal/database/snapshots_test.go create mode 100644 internal/stats/fetch/multisource_test.go rename internal/stats/render/common/{player-title.go => player_title.go} (95%) rename internal/stats/render/common/{tier-percentage.go => tier_percentage.go} (100%) diff --git a/Taskfile.yaml b/Taskfile.yaml index 37aee1de..8f01f48b 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -8,6 +8,19 @@ vars: PRISMA_SCHEMA: --schema ./internal/database/prisma/schema.prisma tasks: + test: + desc: runs tests + env: + DATABASE_URL: "file:{{.USER_WORKING_DIR}}/tmp/local_test.db" + cmds: + - go run {{ .PRISMA_CLIENT }} db push {{ .PRISMA_SCHEMA }} + - | + if [ -z "{{ .CLI_ARGS }}" ]; then + go test -timeout 30s --count=1 -v ./... + else + go test -timeout 30s --count=1 -v -run {{ .CLI_ARGS }} + fi + prisma-install: desc: installs the correct version of prisma cmd: go get {{ .PRISMA_CLIENT }} @@ -18,19 +31,19 @@ tasks: db-push: desc: sync the database with the schema for development - cmd: go run {{ .PRISMA_CLIENT }} db push {{ .PRISMA_SCHEMA }} + cmd: go run {{ .PRISMA_CLIENT }} db push {{ .PRISMA_SCHEMA }} db-generate: desc: re-generate the Go client - cmd: go run {{ .PRISMA_CLIENT }} generate {{ .PRISMA_SCHEMA }} + cmd: go run {{ .PRISMA_CLIENT }} generate {{ .PRISMA_SCHEMA }} db-migrate-dev: desc: for production use, create a migration locally - cmd: go run {{ .PRISMA_CLIENT }} migrate dev {{ .PRISMA_SCHEMA }} + cmd: go run {{ .PRISMA_CLIENT }} migrate dev {{ .PRISMA_SCHEMA }} db-migrate-deploy: desc: sync production database with migrations - cmd: go run {{ .PRISMA_CLIENT }} migrate deploy {{ .PRISMA_SCHEMA }} + cmd: go run {{ .PRISMA_CLIENT }} migrate deploy {{ .PRISMA_SCHEMA }} dev: desc: Start a local dev server diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index 93f69a0d..3a731bd0 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -112,7 +112,6 @@ func init() { } stats := fetch.WargamingToStats(realm, accounts[id], clans[id], vehicles) - accountUpdates = append(accountUpdates, database.Account{ Realm: stats.Realm, ID: stats.Account.ID, @@ -125,7 +124,6 @@ func init() { ClanID: stats.Account.ClanID, ClanTag: stats.Account.ClanTag, }) - snapshots = append(snapshots, database.AccountSnapshot{ Type: database.SnapshotTypeDaily, CreatedAt: createdAt, @@ -135,15 +133,13 @@ func init() { RatingBattles: stats.RatingBattles.StatsFrame, RegularBattles: stats.RegularBattles.StatsFrame, }) + if len(vehicles) < 1 { continue } - vehicleStats := fetch.WargamingVehiclesToFrame(vehicles) for _, vehicle := range vehicleStats { - if !forceUpdate && vehicle.LastBattleTime.Before(createdAt.Add(time.Hour*-25)) { - // if the last battle was played 25+ hours ago, there is nothing for us to update - log.Debug().Str("accountId", id).Str("vehicleId", vehicle.VehicleID).Str("taskId", task.ID).Msg("vehicle played no battles") + if vehicle.LastBattleTime.IsZero() { continue } vehicleSnapshots = append(vehicleSnapshots, database.VehicleSnapshot{ diff --git a/cmds/discord/commands/builder/option.go b/cmds/discord/commands/builder/option.go index b669a8bc..2b259aa4 100644 --- a/cmds/discord/commands/builder/option.go +++ b/cmds/discord/commands/builder/option.go @@ -25,6 +25,8 @@ type Option struct { maxLength int choices []OptionChoice + + options []Option } func NewOption(name string, kind discordgo.ApplicationCommandOptionType) Option { @@ -69,6 +71,11 @@ func (o Option) Params(params ...Param) Option { return o } +func (o Option) Options(options ...Option) Option { + o.options = append(o.options, options...) + return o +} + func (o Option) Choices(choices ...OptionChoice) Option { o.choices = append(o.choices, choices...) return o diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go new file mode 100644 index 00000000..b8fbd348 --- /dev/null +++ b/cmds/discord/commands/session.go @@ -0,0 +1,76 @@ +package commands + +import ( + "bytes" + "context" + "fmt" + "strings" + + "github.com/cufee/aftermath/cmds/discord/commands/builder" + "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/stats/render" + "github.com/cufee/aftermath/internal/stats/render/assets" +) + +func init() { + Loaded.add( + builder.NewCommand("session"). + Options(defaultStatsOptions...). + Handler(func(ctx *common.Context) error { + options := getDefaultStatsOptions(ctx) + message, valid := options.Validate(ctx) + if !valid { + return ctx.Reply(message) + } + + var accountID string + background, _ := assets.GetLoadedImage("bg-default") + + switch { + case options.UserID != "": + // mentioned another user, check if the user has an account linked + mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithContent()) + defaultAccount, hasDefaultAccount := mentionedUser.Connection(database.ConnectionTypeWargaming) + if !hasDefaultAccount { + return ctx.Reply("stats_error_connection_not_found_vague") + } + accountID = defaultAccount.ReferenceID + // TODO: Get user background + + case options.Nickname != "" && options.Server != "": + // nickname provided and server selected - lookup the account + account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) + if err != nil { + if err.Error() == "no results found" { + return ctx.ReplyFmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)) + } + return ctx.Err(err) + } + accountID = fmt.Sprint(account.ID) + + default: + defaultAccount, hasDefaultAccount := ctx.User.Connection(database.ConnectionTypeWargaming) + if !hasDefaultAccount { + return ctx.Reply("stats_error_nickname_or_server_missing") + } + // command used without options, but user has a default connection + accountID = defaultAccount.ReferenceID + // TODO: Get user background + } + + image, _, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), accountID, options.PeriodStart, render.WithBackground(background)) + if err != nil { + return ctx.Err(err) + } + + var buf bytes.Buffer + err = image.PNG(&buf) + if err != nil { + return ctx.Err(err) + } + + return ctx.File(&buf, "session_command_by_aftermath.png") + }), + ) +} diff --git a/cmds/discord/rest/client.go b/cmds/discord/rest/client.go index 859686cc..a58c9061 100644 --- a/cmds/discord/rest/client.go +++ b/cmds/discord/rest/client.go @@ -8,7 +8,6 @@ import ( "mime/multipart" "net/http" "net/textproto" - "runtime/debug" "time" "github.com/pkg/errors" @@ -120,13 +119,18 @@ func (c *Client) do(req *http.Request, target any) error { if res.StatusCode > 299 { var body discordgo.APIErrorMessage - _ = json.NewDecoder(res.Body).Decode(&body) + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("failed to read body: %w", err) + } + + _ = json.Unmarshal(data, &body) message := body.Message if message == "" { message = res.Status } - println(string(debug.Stack())) + println(string(data)) return nil, errors.New("discord error: " + message) } diff --git a/internal/database/client.go b/internal/database/client.go index 67a25310..748a5c69 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -9,6 +9,8 @@ import ( "golang.org/x/sync/semaphore" ) +var _ Client = &client{} + type Client interface { GetAccountByID(ctx context.Context, id string) (Account, error) GetAccounts(ctx context.Context, ids []string) ([]Account, error) @@ -27,7 +29,9 @@ type Client interface { UpsertConnection(ctx context.Context, connection UserConnection) (UserConnection, error) CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error + GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) (AccountSnapshot, error) CreateVehicleSnapshots(ctx context.Context, snapshots ...VehicleSnapshot) error + GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) ([]VehicleSnapshot, error) CreateTasks(ctx context.Context, tasks ...Task) error UpdateTasks(ctx context.Context, tasks ...Task) error @@ -39,8 +43,6 @@ type Client interface { DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error } -var _ Client = &client{} - type client struct { prisma *db.PrismaClient // Prisma does not currently support updateManyAndReturn diff --git a/internal/database/prisma/migrations/20240612202803_sessions/migration.sql b/internal/database/prisma/migrations/20240612202803_sessions/migration.sql new file mode 100644 index 00000000..0d46d0ba --- /dev/null +++ b/internal/database/prisma/migrations/20240612202803_sessions/migration.sql @@ -0,0 +1,50 @@ +/* + Warnings: + + - You are about to drop the column `updatedAt` on the `achievements_snapshots` table. All the data in the column will be lost. + +*/ +-- DropIndex +DROP INDEX "account_snapshots_accountId_lastBattleTime_idx"; + +-- DropIndex +DROP INDEX "vehicle_snapshots_accountId_vehicleId_lastBattleTime_idx"; + +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_achievements_snapshots" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL, + "accountId" TEXT NOT NULL, + "referenceId" TEXT NOT NULL, + "dataEncoded" BLOB NOT NULL +); +INSERT INTO "new_achievements_snapshots" ("accountId", "createdAt", "dataEncoded", "id", "referenceId") SELECT "accountId", "createdAt", "dataEncoded", "id", "referenceId" FROM "achievements_snapshots"; +DROP TABLE "achievements_snapshots"; +ALTER TABLE "new_achievements_snapshots" RENAME TO "achievements_snapshots"; +CREATE INDEX "achievements_snapshots_createdAt_idx" ON "achievements_snapshots"("createdAt"); +CREATE INDEX "achievements_snapshots_accountId_referenceId_idx" ON "achievements_snapshots"("accountId", "referenceId"); +CREATE INDEX "achievements_snapshots_accountId_referenceId_createdAt_idx" ON "achievements_snapshots"("accountId", "referenceId", "createdAt"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; + +-- CreateIndex +CREATE INDEX "account_snapshots_type_accountId_referenceId_idx" ON "account_snapshots"("type", "accountId", "referenceId"); + +-- CreateIndex +CREATE INDEX "account_snapshots_type_accountId_referenceId_createdAt_idx" ON "account_snapshots"("type", "accountId", "referenceId", "createdAt"); + +-- CreateIndex +CREATE INDEX "vehicle_snapshots_vehicleId_createdAt_idx" ON "vehicle_snapshots"("vehicleId", "createdAt"); + +-- CreateIndex +CREATE INDEX "vehicle_snapshots_accountId_referenceId_idx" ON "vehicle_snapshots"("accountId", "referenceId"); + +-- CreateIndex +CREATE INDEX "vehicle_snapshots_accountId_referenceId_vehicleId_idx" ON "vehicle_snapshots"("accountId", "referenceId", "vehicleId"); + +-- CreateIndex +CREATE INDEX "vehicle_snapshots_accountId_referenceId_type_idx" ON "vehicle_snapshots"("accountId", "referenceId", "type"); + +-- CreateIndex +CREATE INDEX "vehicle_snapshots_accountId_referenceId_createdAt_idx" ON "vehicle_snapshots"("accountId", "referenceId", "createdAt"); diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index 1947d466..791b59a2 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -215,7 +215,8 @@ model AccountSnapshot { regularFrameEncoded Bytes @@index([createdAt]) - @@index([accountId, lastBattleTime]) + @@index([type, accountId, referenceId]) + @@index([type, accountId, referenceId, createdAt]) @@map("account_snapshots") } @@ -235,21 +236,27 @@ model VehicleSnapshot { frameEncoded Bytes @@index([createdAt]) - @@index([accountId, vehicleId, lastBattleTime]) + @@index([vehicleId, createdAt]) + @@index([accountId, referenceId]) + @@index([accountId, referenceId, vehicleId]) + @@index([accountId, referenceId, type]) + @@index([accountId, referenceId, createdAt]) @@map("vehicle_snapshots") } // A snapshot of all account achievements model AchievementsSnapshot { id String @id @default(uuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime accountId String referenceId String dataEncoded Bytes + @@index([createdAt]) + @@index([accountId, referenceId]) + @@index([accountId, referenceId, createdAt]) @@map("achievements_snapshots") } @@ -257,7 +264,6 @@ model AchievementsSnapshot { // model AccountRatingSeasonSnapshot { // id String @id @default(cuid()) // createdAt DateTime -// updatedAt DateTime @updatedAt // seasonId String // accountId String diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index e458d407..dd525cb4 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -2,6 +2,8 @@ package database import ( "context" + "fmt" + "strings" "time" "github.com/cufee/aftermath/internal/database/prisma/db" @@ -10,29 +12,134 @@ import ( "github.com/steebchen/prisma-client-go/runtime/transaction" ) -type snapshotType string +type getSnapshotQuery struct { + vehicleIDs []string -const ( - SnapshotTypeDaily snapshotType = "daily" -) + createdAfter *time.Time + createdBefore *time.Time +} + +func (s getSnapshotQuery) accountParams(accountID, referenceID string, kind snapshotType) []db.AccountSnapshotWhereParam { + var params []db.AccountSnapshotWhereParam + params = append(params, db.AccountSnapshot.Type.Equals(string(kind))) + params = append(params, db.AccountSnapshot.AccountID.Equals(accountID)) + params = append(params, db.AccountSnapshot.ReferenceID.Equals(referenceID)) + + if s.createdAfter != nil { + params = append(params, db.AccountSnapshot.CreatedAt.After(*s.createdAfter)) + } + if s.createdBefore != nil { + params = append(params, db.AccountSnapshot.CreatedAt.Before(*s.createdBefore)) + } + + return params +} + +func (s getSnapshotQuery) vehiclesQuery(accountID, referenceID string, kind snapshotType) (query string, params []interface{}) { + var conditions []string + var args []interface{} + + // Mandatory conditions + conditions = append(conditions, "type = ?") + args = append(args, kind) + + conditions = append(conditions, "accountId = ?") + args = append(args, accountID) + + conditions = append(conditions, "referenceId = ?") + args = append(args, referenceID) + + // Optional conditions + if s.createdAfter != nil { + conditions = append(conditions, "createdAt > ?") + args = append(args, *s.createdAfter) + } + if s.createdBefore != nil { + conditions = append(conditions, "createdAt < ?") + args = append(args, *s.createdBefore) + } + + // Filter by vehicle IDs if provided + if len(s.vehicleIDs) > 0 { + placeholders := make([]string, len(s.vehicleIDs)) + for i, id := range s.vehicleIDs { + placeholders[i] = "?" + args = append(args, id) + } + conditions = append(conditions, fmt.Sprintf("vehicleId IN (%s)", strings.Join(placeholders, ","))) + } + + // Determine the order by clause + var orderBy string = "createdAt ASC" + if s.createdBefore != nil { + orderBy = "createdAt DESC" + } + + // Base query + query = ` + SELECT + id, createdAt, type, lastBattleTime, accountId, vehicleId, referenceId, battles, frameEncoded + FROM + vehicle_snapshots + WHERE + %s + ORDER BY + %s + ` + + // Combine conditions into a single string + conditionsStr := strings.Join(conditions, " AND ") + query = fmt.Sprintf(query, conditionsStr, orderBy) + + // Wrap the query to select the latest or earliest snapshot per vehicleId + wrappedQuery := ` + SELECT * FROM ( + %s + ) AS ordered_snapshots + GROUP BY vehicleId + ` + + query = fmt.Sprintf(wrappedQuery, query) + return query, args +} + +func (s getSnapshotQuery) accountOrder() []db.AccountSnapshotOrderByParam { + var order []db.AccountSnapshotOrderByParam + + switch { + case s.createdAfter != nil: + order = append(order, db.AccountSnapshot.CreatedAt.Order(db.ASC)) + default: + order = append(order, db.AccountSnapshot.CreatedAt.Order(db.DESC)) + } -// model VehicleSnapshot { -// id String @id @default(cuid()) -// createdAt DateTime + return order +} -// type String -// lastBattleTime DateTime +type SnapshotQuery func(*getSnapshotQuery) -// accountId String -// vehicleId String -// referenceId String +func WithVehicleIDs(ids []string) SnapshotQuery { + return func(q *getSnapshotQuery) { + q.vehicleIDs = ids + } +} +func WithCreatedAfter(after time.Time) SnapshotQuery { + return func(q *getSnapshotQuery) { + q.createdAfter = &after + } +} +func WithCreatedBefore(before time.Time) SnapshotQuery { + return func(q *getSnapshotQuery) { + q.createdBefore = &before + } +} -// frameEncoded Bytes +type snapshotType string -// @@index([createdAt]) -// @@index([accountId, vehicleId, lastBattleTime]) -// @@map("vehicle_snapshots") -// } +const ( + SnapshotTypeLive snapshotType = "live" + SnapshotTypeDaily snapshotType = "daily" +) type VehicleSnapshot struct { ID string @@ -48,7 +155,7 @@ type VehicleSnapshot struct { Stats frame.StatsFrame } -func (s *VehicleSnapshot) FromModel(model db.VehicleSnapshotModel) error { +func (s VehicleSnapshot) FromModel(model db.VehicleSnapshotModel) (VehicleSnapshot, error) { s.ID = model.ID s.Type = snapshotType(model.Type) s.CreatedAt = model.CreatedAt @@ -60,44 +167,10 @@ func (s *VehicleSnapshot) FromModel(model db.VehicleSnapshotModel) error { stats, err := frame.DecodeStatsFrame(model.FrameEncoded) if err != nil { - return err + return VehicleSnapshot{}, err } s.Stats = stats - return nil -} - -type AccountSnapshot struct { - ID string - Type snapshotType - CreatedAt time.Time - AccountID string - ReferenceID string - LastBattleTime time.Time - RatingBattles frame.StatsFrame - RegularBattles frame.StatsFrame -} - -func (s *AccountSnapshot) FromModel(model db.AccountSnapshotModel) error { - s.ID = model.ID - s.Type = snapshotType(model.Type) - s.CreatedAt = model.CreatedAt - s.AccountID = model.AccountID - s.ReferenceID = model.ReferenceID - s.LastBattleTime = model.LastBattleTime - - rating, err := frame.DecodeStatsFrame(model.RatingFrameEncoded) - if err != nil { - return err - } - s.RatingBattles = rating - - regular, err := frame.DecodeStatsFrame(model.RegularFrameEncoded) - if err != nil { - return err - } - s.RegularBattles = regular - - return nil + return s, nil } func (c *client) CreateVehicleSnapshots(ctx context.Context, snapshots ...VehicleSnapshot) error { @@ -130,6 +203,65 @@ func (c *client) CreateVehicleSnapshots(ctx context.Context, snapshots ...Vehicl return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) } +func (c *client) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) ([]VehicleSnapshot, error) { + var query getSnapshotQuery + for _, apply := range options { + apply(&query) + } + + var models []db.VehicleSnapshotModel + raw, args := query.vehiclesQuery(accountID, referenceID, kind) + err := c.prisma.Prisma.Raw.QueryRaw(raw, args...).Exec(ctx, &models) + if err != nil { + return nil, err + } + + var snapshots []VehicleSnapshot + for _, model := range models { + vehicle, err := VehicleSnapshot{}.FromModel(model) + if err != nil { + return nil, err + } + snapshots = append(snapshots, vehicle) + } + + return snapshots, nil +} + +type AccountSnapshot struct { + ID string + Type snapshotType + CreatedAt time.Time + AccountID string + ReferenceID string + LastBattleTime time.Time + RatingBattles frame.StatsFrame + RegularBattles frame.StatsFrame +} + +func (s AccountSnapshot) FromModel(model *db.AccountSnapshotModel) (AccountSnapshot, error) { + s.ID = model.ID + s.Type = snapshotType(model.Type) + s.CreatedAt = model.CreatedAt + s.AccountID = model.AccountID + s.ReferenceID = model.ReferenceID + s.LastBattleTime = model.LastBattleTime + + rating, err := frame.DecodeStatsFrame(model.RatingFrameEncoded) + if err != nil { + return AccountSnapshot{}, err + } + s.RatingBattles = rating + + regular, err := frame.DecodeStatsFrame(model.RegularFrameEncoded) + if err != nil { + return AccountSnapshot{}, err + } + s.RegularBattles = regular + + return s, nil +} + func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error { if len(snapshots) < 1 { return nil @@ -165,3 +297,17 @@ func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...Accoun return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) } + +func (c *client) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) (AccountSnapshot, error) { + var query getSnapshotQuery + for _, apply := range options { + apply(&query) + } + + model, err := c.prisma.AccountSnapshot.FindFirst(query.accountParams(accountID, referenceID, kind)...).OrderBy(query.accountOrder()...).Exec(ctx) + if err != nil { + return AccountSnapshot{}, err + } + + return AccountSnapshot{}.FromModel(model) +} diff --git a/internal/database/snapshots_test.go b/internal/database/snapshots_test.go new file mode 100644 index 00000000..c4f7553d --- /dev/null +++ b/internal/database/snapshots_test.go @@ -0,0 +1,121 @@ +package database + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +/* +DATABASE_URL needs to be set and migrations need to be applied +*/ +func TestGetVehicleSnapshots(t *testing.T) { + client, err := NewClient() + assert.NoError(t, err, "new client should not error") + defer client.prisma.Disconnect() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + client.prisma.VehicleSnapshot.FindMany().Delete().Exec(ctx) + defer client.prisma.VehicleSnapshot.FindMany().Delete().Exec(ctx) + + createdAtVehicle1 := time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC) + createdAtVehicle2 := time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC) + createdAtVehicle3 := time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC) + + createdAtVehicle4 := time.Date(2023, 9, 1, 0, 0, 0, 0, time.UTC) + createdAtVehicle5 := time.Date(2023, 9, 2, 0, 0, 0, 0, time.UTC) + + vehicle1 := VehicleSnapshot{ + VehicleID: "v1", + AccountID: "a1", + ReferenceID: "r1", + + Type: SnapshotTypeDaily, + CreatedAt: createdAtVehicle1, + LastBattleTime: createdAtVehicle1, + } + vehicle2 := VehicleSnapshot{ + VehicleID: "v1", + AccountID: "a1", + ReferenceID: "r1", + + Type: SnapshotTypeDaily, + CreatedAt: createdAtVehicle2, + LastBattleTime: createdAtVehicle2, + } + vehicle3 := VehicleSnapshot{ + VehicleID: "v1", + AccountID: "a1", + ReferenceID: "r1", + + Type: SnapshotTypeDaily, + CreatedAt: createdAtVehicle3, + LastBattleTime: createdAtVehicle3, + } + vehicle4 := VehicleSnapshot{ + VehicleID: "v4", + AccountID: "a1", + ReferenceID: "r2", + + Type: SnapshotTypeDaily, + CreatedAt: createdAtVehicle4, + LastBattleTime: createdAtVehicle4, + } + vehicle5 := VehicleSnapshot{ + VehicleID: "v5", + AccountID: "a1", + ReferenceID: "r2", + + Type: SnapshotTypeDaily, + CreatedAt: createdAtVehicle5, + LastBattleTime: createdAtVehicle5, + } + vehicle6 := VehicleSnapshot{ + VehicleID: "v5", + AccountID: "a1", + ReferenceID: "r2", + + Type: SnapshotTypeDaily, + CreatedAt: createdAtVehicle5, + LastBattleTime: createdAtVehicle5, + } + + { // create snapshots + snaphots := []VehicleSnapshot{vehicle1, vehicle2, vehicle3, vehicle4, vehicle5, vehicle6} + err = client.CreateVehicleSnapshots(ctx, snaphots...) + assert.NoError(t, err, "create vehicle snapshot should not error") + } + { // when we check created after, vehicles need to be ordered by createdAt ASC, so we expect to get vehicle2 back + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedAfter(createdAtVehicle1)) + assert.NoError(t, err, "get vehicle snapshot error") + assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") + assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned", vehicles) + } + { // when we check created before, vehicles need to be ordered by createdAt DESC, so we expect to get vehicle2 back + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle3)) + assert.NoError(t, err, "get vehicle snapshot error") + assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") + assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned", vehicles) + } + { // make sure only 1 vehicle is returned per ID + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle5.Add(time.Hour))) + assert.NoError(t, err, "get vehicle snapshot error") + assert.Len(t, vehicles, 2, "should return exactly 2 snapshots") + assert.NotEqual(t, vehicles[0].ID, vehicles[1].ID, "each vehicle id should only be returned once", vehicles) + } + { // get a cehicle with a specific id + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", SnapshotTypeDaily, WithVehicleIDs([]string{"v5"})) + assert.NoError(t, err, "get vehicle snapshot error") + assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") + assert.NotEqual(t, vehicles[0].ID, "v5", "incorrect vehicle returned", vehicles) + } + { // this should return no result + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle1)) + assert.NoError(t, err, "no results from a raw query does not trigger an error") + assert.Len(t, vehicles, 0, "return should have no results", vehicles) + } +} diff --git a/internal/stats/fetch/client.go b/internal/stats/fetch/client.go index b0dd727e..7cfa4506 100644 --- a/internal/stats/fetch/client.go +++ b/internal/stats/fetch/client.go @@ -49,7 +49,9 @@ type StatsWithVehicles struct { type Client interface { Search(ctx context.Context, nickname, realm string) (types.Account, error) CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) + PeriodStats(ctx context.Context, id string, from time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) + SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) } diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 42de615c..3dd3c7a7 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/prisma/db" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/retry" @@ -111,6 +112,10 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. if vehicles.Err != nil { return AccountStatsOverPeriod{}, vehicles.Err } + if averages.Err != nil { + // not critical, this will only affect WN8 + log.Err(averages.Err).Msg("failed to get tank averages") + } stats := WargamingToStats(realm, account.Data, clan, vehicles.Data) if options.withWN8 { @@ -169,6 +174,10 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt if current.Err != nil { return AccountStatsOverPeriod{}, current.Err } + if averages.Err != nil { + // not critical, this will only affect WN8 + log.Err(averages.Err).Msg("failed to get tank averages") + } stats := current.Data if options.withWN8 { @@ -193,6 +202,10 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt if histories.Err != nil { return AccountStatsOverPeriod{}, histories.Err } + if averages.Err != nil { + // not critical, this will only affect WN8 + log.Err(averages.Err).Msg("failed to get tank averages") + } current.Data.PeriodEnd = time.Now() current.Data.PeriodStart = periodStart @@ -206,6 +219,125 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt return stats, nil } +func (c *multiSourceClient) SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) { + var options statsOptions + for _, apply := range opts { + apply(&options) + } + if sessionStart.IsZero() { + sessionStart = time.Now().Add(time.Hour * 25 * -1) + } + + var accountSnapshot retry.DataWithErr[database.AccountSnapshot] + var vehiclesSnapshots retry.DataWithErr[[]database.VehicleSnapshot] + var averages retry.DataWithErr[map[string]frame.StatsFrame] + var current retry.DataWithErr[AccountStatsOverPeriod] + + var group sync.WaitGroup + group.Add(1) + go func() { + defer group.Done() + + stats, err := c.CurrentStats(ctx, id) + current = retry.DataWithErr[AccountStatsOverPeriod]{Data: stats, Err: err} + + if err != nil || stats.RegularBattles.Battles < 1 || !options.withWN8 { + return + } + + var ids []string + for id := range stats.RegularBattles.Vehicles { + ids = append(ids, id) + } + a, err := c.database.GetVehicleAverages(ctx, ids) + averages = retry.DataWithErr[map[string]frame.StatsFrame]{Data: a, Err: err} + }() + + // return career stats if stats are requested for 0 or 90+ days, we do not track that far + if days := time.Since(sessionStart).Hours() / 24; days > 90 || days < 1 { + group.Wait() + if current.Err != nil { + return AccountStatsOverPeriod{}, current.Err + } + if averages.Err != nil { + // not critical, this will only affect WN8 + log.Err(averages.Err).Msg("failed to get tank averages") + } + + stats := current.Data + if options.withWN8 { + stats.AddWN8(averages.Data) + } + return stats, nil + } + + group.Add(1) + go func() { + defer group.Done() + s, err := c.database.GetAccountSnapshot(ctx, id, id, database.SnapshotTypeDaily, database.WithCreatedAfter(sessionStart)) + accountSnapshot = retry.DataWithErr[database.AccountSnapshot]{Data: s, Err: err} + + v, err := c.database.GetVehicleSnapshots(ctx, id, id, database.SnapshotTypeDaily, database.WithCreatedAfter(sessionStart)) + vehiclesSnapshots = retry.DataWithErr[[]database.VehicleSnapshot]{Data: v, Err: err} + }() + + // wait for all requests to finish and check errors + group.Wait() + if current.Err != nil { + return AccountStatsOverPeriod{}, current.Err + } + if accountSnapshot.Err != nil { + if !db.IsErrNotFound(accountSnapshot.Err) { + return AccountStatsOverPeriod{}, accountSnapshot.Err + } + // if there is no snapshot, we just return 0 battles + stats := current.Data + stats.PeriodEnd = time.Now() + stats.PeriodStart = sessionStart + stats.RatingBattles.StatsFrame = frame.StatsFrame{} + stats.RegularBattles.StatsFrame = frame.StatsFrame{} + stats.RegularBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, 0) + return stats, nil + } + if averages.Err != nil { + // not critical, this will only affect WN8 + log.Err(averages.Err).Msg("failed to get tank averages") + } + + stats := current.Data + stats.PeriodEnd = time.Now() + stats.PeriodStart = sessionStart + stats.RatingBattles.StatsFrame.Subtract(accountSnapshot.Data.RatingBattles) + stats.RegularBattles.StatsFrame.Subtract(accountSnapshot.Data.RegularBattles) + + snapshotsMap := make(map[string]int, len(vehiclesSnapshots.Data)) + for i, data := range vehiclesSnapshots.Data { + snapshotsMap[data.VehicleID] = i + } + + stats.RegularBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, len(current.Data.RegularBattles.Vehicles)) + for id, current := range current.Data.RegularBattles.Vehicles { + snapshotIndex, exists := snapshotsMap[id] + if !exists { + stats.RegularBattles.Vehicles[id] = current + continue + } + + snapshot := vehiclesSnapshots.Data[snapshotIndex] + if current.Battles == 0 || current.Battles == snapshot.Stats.Battles { + continue + } + current.StatsFrame.Subtract(snapshot.Stats) + stats.RegularBattles.Vehicles[id] = current + } + + if options.withWN8 { + stats.AddWN8(averages.Data) + } + return stats, nil + +} + func (c *multiSourceClient) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) { return c.blitzstars.CurrentTankAverages(ctx) } diff --git a/internal/stats/fetch/multisource_test.go b/internal/stats/fetch/multisource_test.go new file mode 100644 index 00000000..b3718416 --- /dev/null +++ b/internal/stats/fetch/multisource_test.go @@ -0,0 +1,27 @@ +package fetch + +import ( + "context" + "testing" + "time" + + "github.com/cufee/aftermath/internal/database" + "github.com/stretchr/testify/assert" +) + +func TestSessionStats(t *testing.T) { + db, err := database.NewClient() + assert.NoError(t, err, "failed to create a database client") + defer db.Prisma().Disconnect() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + sessionStartTime := time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC) + + client := &multiSourceClient{database: db} + stats, err := client.SessionStats(ctx, "", sessionStartTime.Add(time.Hour*-1)) + assert.NoError(t, err, "failed to get session stats") + + _ = stats +} diff --git a/internal/stats/frame/frame.go b/internal/stats/frame/frame.go index 7889ecae..b35b5be5 100644 --- a/internal/stats/frame/frame.go +++ b/internal/stats/frame/frame.go @@ -207,6 +207,15 @@ func (r *StatsFrame) Add(other StatsFrame) { r.EnemiesSpotted += other.EnemiesSpotted r.CapturePoints += other.CapturePoints r.DroppedCapturePoints += other.DroppedCapturePoints + + // Reset cache + r.wn8 = 0 + r.winRate = 0 + r.accuracy = 0 + r.avgDamage = 0 + r.damageRatio = 0 + r.survivalRatio = 0 + r.survivalPercent = 0 } /* @@ -230,6 +239,15 @@ func (r *StatsFrame) Subtract(other StatsFrame) { r.EnemiesSpotted -= other.EnemiesSpotted r.CapturePoints -= other.CapturePoints r.DroppedCapturePoints -= other.DroppedCapturePoints + + // Reset cache + r.wn8 = 0 + r.winRate = 0 + r.accuracy = 0 + r.avgDamage = 0 + r.damageRatio = 0 + r.survivalRatio = 0 + r.survivalPercent = 0 } /* diff --git a/internal/stats/period.go b/internal/stats/period.go index aeb41fdb..f99c7749 100644 --- a/internal/stats/period.go +++ b/internal/stats/period.go @@ -5,21 +5,13 @@ import ( "slices" "time" - "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/stats/fetch" prepare "github.com/cufee/aftermath/internal/stats/prepare/period" options "github.com/cufee/aftermath/internal/stats/render" render "github.com/cufee/aftermath/internal/stats/render/period" - "golang.org/x/text/language" ) -type renderer struct { - fetchClient fetch.Client - database database.Client - locale language.Tag -} - func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, opts ...options.Option) (Image, Metadata, error) { meta := Metadata{} diff --git a/internal/stats/render/common/player-title.go b/internal/stats/render/common/player_title.go similarity index 95% rename from internal/stats/render/common/player-title.go rename to internal/stats/render/common/player_title.go index 07bf771c..1160b8a2 100644 --- a/internal/stats/render/common/player-title.go +++ b/internal/stats/render/common/player_title.go @@ -14,7 +14,7 @@ type TitleCardStyle struct { } func (style TitleCardStyle) TotalPaddingAndGaps() float64 { - return style.Container.PaddingX*2 + style.Container.Gap + style.Nickname.PaddingX*2 + style.ClanTag.PaddingX*2 + return style.Container.PaddingX*2 + style.Container.Gap*2 + style.Nickname.PaddingX*2 + style.ClanTag.PaddingX*2 } func DefaultPlayerTitleStyle(containerStyle Style) TitleCardStyle { diff --git a/internal/stats/render/common/tier-percentage.go b/internal/stats/render/common/tier_percentage.go similarity index 100% rename from internal/stats/render/common/tier-percentage.go rename to internal/stats/render/common/tier_percentage.go diff --git a/internal/stats/render/period/constants.go b/internal/stats/render/period/constants.go index d50686ee..cc9f101f 100644 --- a/internal/stats/render/period/constants.go +++ b/internal/stats/render/period/constants.go @@ -65,6 +65,7 @@ func defaultCardStyle(width float64) common.Style { func titleCardStyle(width float64) common.Style { style := defaultCardStyle(width) style.PaddingX = style.PaddingY + style.Gap = style.PaddingY * 2 // style.Debug = true return style } diff --git a/internal/stats/renderer.go b/internal/stats/renderer.go index 3bd2e7ad..ac0639c4 100644 --- a/internal/stats/renderer.go +++ b/internal/stats/renderer.go @@ -12,11 +12,17 @@ import ( var _ Renderer = &renderer{} +type renderer struct { + fetchClient fetch.Client + database database.Client + locale language.Tag +} + type Renderer interface { Period(ctx context.Context, accountId string, from time.Time, opts ...render.Option) (Image, Metadata, error) + Session(ctx context.Context, accountId string, from time.Time, opts ...render.Option) (Image, Metadata, error) // Replay(accountId string, from time.Time) (image.Image, error) - // Session(accountId string, from time.Time) (image.Image, error) } func NewRenderer(fetch fetch.Client, database database.Client, locale language.Tag) *renderer { diff --git a/internal/stats/session.go b/internal/stats/session.go index 43b4fd56..67e75340 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -1 +1,63 @@ package stats + +import ( + "context" + "slices" + "time" + + "github.com/cufee/aftermath/internal/localization" + "github.com/cufee/aftermath/internal/stats/fetch" + prepare "github.com/cufee/aftermath/internal/stats/prepare/period" + options "github.com/cufee/aftermath/internal/stats/render" + render "github.com/cufee/aftermath/internal/stats/render/period" +) + +func (r *renderer) Session(ctx context.Context, accountId string, from time.Time, opts ...options.Option) (Image, Metadata, error) { + meta := Metadata{} + + printer, err := localization.NewPrinter("stats", r.locale) + if err != nil { + return nil, meta, err + } + + stop := meta.Timer("fetchClient#SessionStats") + stats, err := r.fetchClient.SessionStats(ctx, accountId, from, fetch.WithWN8()) + stop() + if err != nil { + return nil, meta, err + } + meta.Stats = stats + + stop = meta.Timer("prepare#GetVehicles") + var vehicles []string + for id := range stats.RegularBattles.Vehicles { + vehicles = append(vehicles, id) + } + for id := range stats.RatingBattles.Vehicles { + if !slices.Contains(vehicles, id) { + vehicles = append(vehicles, id) + } + } + + glossary, err := r.database.GetVehicles(ctx, vehicles) + if err != nil { + return nil, meta, err + } + stop() + + stop = meta.Timer("prepare#NewCards") + cards, err := prepare.NewCards(stats, glossary, prepare.WithPrinter(printer, r.locale)) + stop() + if err != nil { + return nil, meta, err + } + + stop = meta.Timer("render#CardsToImage") + image, err := render.CardsToImage(stats, cards, nil, opts...) + stop() + if err != nil { + return nil, meta, err + } + + return &imageImp{image}, meta, err +} diff --git a/main.go b/main.go index ee853493..cba576bb 100644 --- a/main.go +++ b/main.go @@ -92,7 +92,7 @@ func startSchedulerFromEnvAsync() { queue.StartCronJobsAsync() // Some tasks should run on startup // scheduler.UpdateAveragesWorker(coreClient)() - // scheduler.CreateSessionTasksWorker(coreClient, "NA")() + // scheduler.CreateSessionTasksWorker(coreClient, "AS")() } func coreClientFromEnv() core.Client { From 7c2624c58fcaf9978d282a547c9457742dee8544 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 12 Jun 2024 19:17:21 -0400 Subject: [PATCH 038/341] refactored sessions --- cmds/core/client.go | 2 +- cmds/core/scheduler/tasks/sessions.go | 134 ++----------------- cmds/discord/commands/options.go | 2 + cmds/discord/commands/session.go | 5 + internal/database/client.go | 1 + internal/database/prisma/schema.prisma | 1 + internal/database/snapshots.go | 103 +++++++++++++-- internal/logic/sessions.go | 174 +++++++++++++++++++++++++ internal/stats/errors.go | 7 + internal/stats/fetch/client.go | 1 + internal/stats/fetch/errors.go | 8 ++ internal/stats/fetch/multisource.go | 95 +++++++++----- internal/stats/renderer.go | 6 +- internal/stats/session.go | 46 ++++++- static/localization/en/discord.yaml | 8 +- 15 files changed, 418 insertions(+), 175 deletions(-) create mode 100644 internal/logic/sessions.go create mode 100644 internal/stats/errors.go create mode 100644 internal/stats/fetch/errors.go diff --git a/cmds/core/client.go b/cmds/core/client.go index db5ca9b7..05d6060a 100644 --- a/cmds/core/client.go +++ b/cmds/core/client.go @@ -37,7 +37,7 @@ func (c *client) Fetch() fetch.Client { } func (c *client) Render(locale language.Tag) stats.Renderer { - return stats.NewRenderer(c.fetch, c.db, locale) + return stats.NewRenderer(c.fetch, c.db, c.wargaming, locale) } func NewClient(fetch fetch.Client, wargaming wargaming.Client, database database.Client) *client { diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index 3a731bd0..f5147654 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -3,21 +3,16 @@ package tasks import ( "context" "strings" - "sync" "time" "github.com/pkg/errors" "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/internal/database" - "github.com/cufee/aftermath/internal/retry" - "github.com/cufee/aftermath/internal/stats/fetch" - "github.com/cufee/am-wg-proxy-next/v2/types" + "github.com/cufee/aftermath/internal/logic" "github.com/rs/zerolog/log" ) -type vehicleResponseData map[string][]types.VehicleStatsFrame - func init() { defaultHandlers[database.TaskTypeRecordSessions] = TaskHandler{ Process: func(client core.Client, task database.Task) (string, error) { @@ -41,136 +36,23 @@ func init() { log.Debug().Str("taskId", task.ID).Any("targets", task.Targets).Msg("started working on a session refresh task") - createdAt := time.Now() ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() - accounts, err := client.Wargaming().BatchAccountByID(ctx, realm, task.Targets) - if err != nil { - return "failed to fetch accounts", err - } - - // Make a new slice just in case some accounts were not returned/are private - var validAccouts []string - for _, id := range task.Targets { - data, ok := accounts[id] - if !ok { - go func(id string) { - log.Debug().Str("accountId", id).Str("taskId", task.ID).Msg("account is private") - // update account cache (if it exists) to set account as private - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - err := client.Database().AccountSetPrivate(ctx, id, true) - if err != nil { - log.Err(err).Str("accountId", id).Msg("failed to set account status as private") - } - }(id) - continue - } - if !forceUpdate && data.LastBattleTime < int(createdAt.Add(time.Hour*-25).Unix()) { - // if the last battle was played 25+ hours ago, there is nothing for us to update - log.Debug().Str("accountId", id).Str("taskId", task.ID).Msg("account played no battles") - continue - } - validAccouts = append(validAccouts, id) - } - - if len(validAccouts) == 0 { - return "no updates required due to last battle or private accounts status", nil - } - - // clans are options-ish - clans, err := client.Wargaming().BatchAccountClan(ctx, realm, validAccouts) + accountErrors, err := logic.RecordAccountSnapshots(ctx, client.Wargaming(), client.Database(), realm, forceUpdate, task.Targets...) if err != nil { - log.Err(err).Msg("failed to get batch account clans") - clans = make(map[string]types.ClanMember) - } - - vehicleCh := make(chan retry.DataWithErr[vehicleResponseData], len(validAccouts)) - var group sync.WaitGroup - group.Add(len(validAccouts)) - for _, id := range validAccouts { - go func(id string) { - defer group.Done() - data, err := client.Wargaming().AccountVehicles(ctx, realm, id) - vehicleCh <- retry.DataWithErr[vehicleResponseData]{Data: vehicleResponseData{id: data}, Err: err} - }(id) + return "failed to record sessions", err } - group.Wait() - close(vehicleCh) - - var withErrors []string - var accountUpdates []database.Account - var snapshots []database.AccountSnapshot - var vehicleSnapshots []database.VehicleSnapshot - for result := range vehicleCh { - // there is only 1 key in this map - for id, vehicles := range result.Data { - if result.Err != nil { - withErrors = append(withErrors, id) - continue - } - - stats := fetch.WargamingToStats(realm, accounts[id], clans[id], vehicles) - accountUpdates = append(accountUpdates, database.Account{ - Realm: stats.Realm, - ID: stats.Account.ID, - Nickname: stats.Account.Nickname, - Private: false, - CreatedAt: stats.Account.CreatedAt, - LastBattleTime: stats.LastBattleTime, - - ClanID: stats.Account.ClanID, - ClanTag: stats.Account.ClanTag, - }) - snapshots = append(snapshots, database.AccountSnapshot{ - Type: database.SnapshotTypeDaily, - CreatedAt: createdAt, - AccountID: stats.Account.ID, - ReferenceID: stats.Account.ID, - LastBattleTime: stats.LastBattleTime, - RatingBattles: stats.RatingBattles.StatsFrame, - RegularBattles: stats.RegularBattles.StatsFrame, - }) - - if len(vehicles) < 1 { - continue - } - vehicleStats := fetch.WargamingVehiclesToFrame(vehicles) - for _, vehicle := range vehicleStats { - if vehicle.LastBattleTime.IsZero() { - continue - } - vehicleSnapshots = append(vehicleSnapshots, database.VehicleSnapshot{ - CreatedAt: createdAt, - Type: database.SnapshotTypeDaily, - LastBattleTime: vehicle.LastBattleTime, - AccountID: stats.Account.ID, - VehicleID: vehicle.VehicleID, - ReferenceID: stats.Account.ID, - Stats: *vehicle.StatsFrame, - }) - } - } - } - - err = client.Database().CreateAccountSnapshots(ctx, snapshots...) - if err != nil { - return "failed to save account snapshots to database", err - } - - err = client.Database().CreateVehicleSnapshots(ctx, vehicleSnapshots...) - if err != nil { - return "failed to save vehicle snapshots to database", err - } - - if len(withErrors) == 0 { + if len(accountErrors) == 0 { return "finished session update on all accounts", nil } // Retry failed accounts - task.Targets = withErrors + task.Targets = make([]string, len(accountErrors)) + for id := range accountErrors { + task.Targets = append(task.Targets, id) + } return "retrying failed accounts", errors.New("some accounts failed") }, ShouldRetry: func(task *database.Task) bool { diff --git a/cmds/discord/commands/options.go b/cmds/discord/commands/options.go index fa4de325..3ee45d2b 100644 --- a/cmds/discord/commands/options.go +++ b/cmds/discord/commands/options.go @@ -50,6 +50,7 @@ var defaultStatsOptions = append([]builder.Option{ type statsOptions struct { PeriodStart time.Time + Days int Server string Nickname string UserID string @@ -84,6 +85,7 @@ func getDefaultStatsOptions(ctx *common.Context) statsOptions { options.UserID, _ = common.GetOption[string](ctx, "user") if days, _ := common.GetOption[float64](ctx, "days"); days > 0 { + options.Days = int(days) options.PeriodStart = time.Now().Add(time.Hour * 24 * time.Duration(days) * -1) } diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go index b8fbd348..b8de76a2 100644 --- a/cmds/discord/commands/session.go +++ b/cmds/discord/commands/session.go @@ -9,8 +9,10 @@ import ( "github.com/cufee/aftermath/cmds/discord/commands/builder" "github.com/cufee/aftermath/cmds/discord/common" "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/stats" "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" + "github.com/pkg/errors" ) func init() { @@ -61,6 +63,9 @@ func init() { image, _, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), accountID, options.PeriodStart, render.WithBackground(background)) if err != nil { + if errors.Is(err, stats.ErrAccountNotTracked) { + return ctx.Reply("session_error_account_was_not_tracked") + } return ctx.Err(err) } diff --git a/internal/database/client.go b/internal/database/client.go index 748a5c69..a4e69e3a 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -30,6 +30,7 @@ type Client interface { CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) (AccountSnapshot, error) + GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind snapshotType, options ...SnapshotQuery) ([]AccountSnapshot, error) CreateVehicleSnapshots(ctx context.Context, snapshots ...VehicleSnapshot) error GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) ([]VehicleSnapshot, error) diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index 791b59a2..5885f1b8 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -215,6 +215,7 @@ model AccountSnapshot { regularFrameEncoded Bytes @@index([createdAt]) + @@index([type, accountId, createdAt]) @@index([type, accountId, referenceId]) @@index([type, accountId, referenceId, createdAt]) @@map("account_snapshots") diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index dd525cb4..5d1d661f 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -70,9 +70,9 @@ func (s getSnapshotQuery) vehiclesQuery(accountID, referenceID string, kind snap } // Determine the order by clause - var orderBy string = "createdAt ASC" - if s.createdBefore != nil { - orderBy = "createdAt DESC" + var orderBy string = "createdAt DESC" + if s.createdAfter != nil { + orderBy = "createdAt ASC" } // Base query @@ -103,17 +103,72 @@ func (s getSnapshotQuery) vehiclesQuery(accountID, referenceID string, kind snap return query, args } -func (s getSnapshotQuery) accountOrder() []db.AccountSnapshotOrderByParam { - var order []db.AccountSnapshotOrderByParam +func (s getSnapshotQuery) manyAccountsQuery(accountIDs []string, kind snapshotType) (query string, params []interface{}) { + var conditions []string + var args []interface{} + + // Mandatory conditions + conditions = append(conditions, "type = ?") + args = append(args, kind) - switch { - case s.createdAfter != nil: - order = append(order, db.AccountSnapshot.CreatedAt.Order(db.ASC)) - default: - order = append(order, db.AccountSnapshot.CreatedAt.Order(db.DESC)) + // Optional conditions + if s.createdAfter != nil { + conditions = append(conditions, "createdAt > ?") + args = append(args, *s.createdAfter) } + if s.createdBefore != nil { + conditions = append(conditions, "createdAt < ?") + args = append(args, *s.createdBefore) + } + + // Filter by account IDs + placeholders := make([]string, len(accountIDs)) + for i := range accountIDs { + placeholders[i] = "?" + } + conditions = append(conditions, fmt.Sprintf("accountId IN (%s)", strings.Join(placeholders, ","))) + for _, id := range accountIDs { + args = append(args, id) + } + + // Determine the order by clause + var orderBy string = "createdAt DESC" + if s.createdAfter != nil { + orderBy = "createdAt ASC" + } + + // Combine conditions into a single string + conditionsStr := strings.Join(conditions, " AND ") - return order + // Base query + query = fmt.Sprintf(` + SELECT + id, createdAt, type, lastBattleTime, accountId, referenceId, ratingBattles, ratingFrameEncoded, regularBattles, regularFrameEncoded + FROM + account_snapshots + WHERE + %s + ORDER BY + %s + `, conditionsStr, orderBy) + + // Wrap the query to select the latest snapshot per accountId + wrappedQuery := fmt.Sprintf(` + SELECT * FROM ( + %s + ) AS ordered_snapshots + GROUP BY accountId + `, query) + + return wrappedQuery, args +} + +func (s getSnapshotQuery) accountOrder() []db.AccountSnapshotOrderByParam { + order := db.DESC + if s.createdAfter != nil { + order = db.ASC + } + return []db.AccountSnapshotOrderByParam{db.AccountSnapshot.CreatedAt.Order(order)} } type SnapshotQuery func(*getSnapshotQuery) @@ -311,3 +366,29 @@ func (c *client) GetAccountSnapshot(ctx context.Context, accountID, referenceID return AccountSnapshot{}.FromModel(model) } + +func (c *client) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind snapshotType, options ...SnapshotQuery) ([]AccountSnapshot, error) { + var query getSnapshotQuery + for _, apply := range options { + apply(&query) + } + + var models []db.AccountSnapshotModel + raw, args := query.manyAccountsQuery(accountIDs, kind) + err := c.prisma.Prisma.Raw.QueryRaw(raw, args...).Exec(ctx, &models) + if err != nil { + return nil, err + } + + var snapshots []AccountSnapshot + for _, model := range models { + snapshot, err := AccountSnapshot{}.FromModel(&model) + if err != nil { + return nil, err + } + snapshots = append(snapshots, snapshot) + } + + return snapshots, nil + +} diff --git a/internal/logic/sessions.go b/internal/logic/sessions.go new file mode 100644 index 00000000..3630b1a3 --- /dev/null +++ b/internal/logic/sessions.go @@ -0,0 +1,174 @@ +package logic + +import ( + "context" + "sync" + "time" + + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/external/wargaming" + "github.com/cufee/aftermath/internal/retry" + "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/am-wg-proxy-next/v2/types" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +type vehicleResponseData map[string][]types.VehicleStatsFrame + +func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbClient database.Client, realm string, force bool, accountIDs ...string) (map[string]error, error) { + if len(accountIDs) < 1 { + return nil, nil + } + + createdAt := time.Now() + accounts, err := wgClient.BatchAccountByID(ctx, realm, accountIDs) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch accounts") + } + + // existing snaphsots for accounts + existingSnapshots, err := dbClient.GetManyAccountSnapshots(ctx, accountIDs, database.SnapshotTypeDaily) + if err != nil && !db.IsErrNotFound(err) { + return nil, errors.Wrap(err, "failed to get existing snapshots") + } + existingSnapshotsMap := make(map[string]*database.AccountSnapshot) + for _, snapshot := range existingSnapshots { + existingSnapshotsMap[snapshot.AccountID] = &snapshot + } + + // make a new slice just in case some accounts were not returned/are private + var validAccouts []string + for _, id := range accountIDs { + data, ok := accounts[id] + if !ok { + go func(id string) { + log.Debug().Str("accountId", id).Msg("account is private") + // update account cache (if it exists) to set account as private + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + err := dbClient.AccountSetPrivate(ctx, id, true) + if err != nil { + log.Err(err).Str("accountId", id).Msg("failed to set account status as private") + } + }(id) + continue + } + if data.LastBattleTime == 0 { + continue + } + if s, ok := existingSnapshotsMap[id]; !force && ok && data.LastBattleTime == int(s.LastBattleTime.Unix()) { + // last snapshot is the same, we can skip it + continue + } + validAccouts = append(validAccouts, id) + } + + if len(validAccouts) == 0 { + return nil, nil + } + + // clans are options-ish + clans, err := wgClient.BatchAccountClan(ctx, realm, validAccouts) + if err != nil { + log.Err(err).Msg("failed to get batch account clans") + clans = make(map[string]types.ClanMember) + } + + vehicleCh := make(chan retry.DataWithErr[vehicleResponseData], len(validAccouts)) + var group sync.WaitGroup + group.Add(len(validAccouts)) + for _, id := range validAccouts { + go func(id string) { + defer group.Done() + data, err := wgClient.AccountVehicles(ctx, realm, id) + vehicleCh <- retry.DataWithErr[vehicleResponseData]{Data: vehicleResponseData{id: data}, Err: err} + }(id) + } + group.Wait() + close(vehicleCh) + + var accountErrors = make(map[string]error) + var accountUpdates []database.Account + var snapshots []database.AccountSnapshot + var vehicleSnapshots []database.VehicleSnapshot + for result := range vehicleCh { + // there is only 1 key in this map + for id, vehicles := range result.Data { + if result.Err != nil { + accountErrors[id] = result.Err + continue + } + existingSnapshots, err := dbClient.GetVehicleSnapshots(ctx, id, id, database.SnapshotTypeDaily) + if err != nil && !db.IsErrNotFound(err) { + accountErrors[id] = err + continue + } + existingSnapshotsMap := make(map[string]*database.VehicleSnapshot) + for _, snapshot := range existingSnapshots { + existingSnapshotsMap[snapshot.VehicleID] = &snapshot + } + + stats := fetch.WargamingToStats(realm, accounts[id], clans[id], vehicles) + accountUpdates = append(accountUpdates, database.Account{ + Realm: stats.Realm, + ID: stats.Account.ID, + Nickname: stats.Account.Nickname, + + Private: false, + CreatedAt: stats.Account.CreatedAt, + LastBattleTime: stats.LastBattleTime, + + ClanID: stats.Account.ClanID, + ClanTag: stats.Account.ClanTag, + }) + snapshots = append(snapshots, database.AccountSnapshot{ + Type: database.SnapshotTypeDaily, + CreatedAt: createdAt, + AccountID: stats.Account.ID, + ReferenceID: stats.Account.ID, + LastBattleTime: stats.LastBattleTime, + RatingBattles: stats.RatingBattles.StatsFrame, + RegularBattles: stats.RegularBattles.StatsFrame, + }) + + if len(vehicles) < 1 { + continue + } + vehicleStats := fetch.WargamingVehiclesToFrame(vehicles) + for id, vehicle := range vehicleStats { + if vehicle.LastBattleTime.IsZero() { + // vehicle was never played + continue + } + if s, ok := existingSnapshotsMap[id]; !force && ok && s.Stats.Battles == vehicle.Battles { + // last snapshot is the same, we can skip it + continue + } + + vehicleSnapshots = append(vehicleSnapshots, database.VehicleSnapshot{ + CreatedAt: createdAt, + Type: database.SnapshotTypeDaily, + LastBattleTime: vehicle.LastBattleTime, + AccountID: stats.Account.ID, + VehicleID: vehicle.VehicleID, + ReferenceID: stats.Account.ID, + Stats: *vehicle.StatsFrame, + }) + } + } + } + + err = dbClient.CreateAccountSnapshots(ctx, snapshots...) + if err != nil { + return nil, errors.Wrap(err, "failed to save account snapshots to database") + } + + err = dbClient.CreateVehicleSnapshots(ctx, vehicleSnapshots...) + if err != nil { + return nil, errors.Wrap(err, "failed to save vehicle snapshots to database") + } + + return accountErrors, nil +} diff --git a/internal/stats/errors.go b/internal/stats/errors.go new file mode 100644 index 00000000..be6b8daa --- /dev/null +++ b/internal/stats/errors.go @@ -0,0 +1,7 @@ +package stats + +import "github.com/pkg/errors" + +var ( + ErrAccountNotTracked = errors.New("account not tracked") +) diff --git a/internal/stats/fetch/client.go b/internal/stats/fetch/client.go index 7cfa4506..60e65777 100644 --- a/internal/stats/fetch/client.go +++ b/internal/stats/fetch/client.go @@ -47,6 +47,7 @@ type StatsWithVehicles struct { } type Client interface { + Account(ctx context.Context, id string) (database.Account, error) Search(ctx context.Context, nickname, realm string) (types.Account, error) CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) diff --git a/internal/stats/fetch/errors.go b/internal/stats/fetch/errors.go new file mode 100644 index 00000000..995c2692 --- /dev/null +++ b/internal/stats/fetch/errors.go @@ -0,0 +1,8 @@ +package fetch + +import "github.com/pkg/errors" + +var ( + ErrSessionNotFound = errors.New("account sessions not found") + ErrInvalidSessionStart = errors.New("invalid session start provided") +) diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 3dd3c7a7..fb95ad9d 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -52,6 +52,51 @@ func (c *multiSourceClient) Search(ctx context.Context, nickname, realm string) return accounts[0], nil } +/* +Gets account info from wg and updates cache +*/ +func (c *multiSourceClient) Account(ctx context.Context, id string) (database.Account, error) { + realm := c.wargaming.RealmFromAccountID(id) + + var group sync.WaitGroup + group.Add(2) + + var clan types.ClanMember + var wgAccount retry.DataWithErr[types.ExtendedAccount] + + go func() { + defer group.Done() + + wgAccount = retry.Retry(func() (types.ExtendedAccount, error) { + return c.wargaming.AccountByID(ctx, realm, id) + }, c.retriesPerRequest, c.retrySleepInterval) + }() + go func() { + defer group.Done() + // we should not retry this request since it is not critical and will fail on accounts without a clan + clan, _ = c.wargaming.AccountClan(ctx, realm, id) + }() + + group.Wait() + + if wgAccount.Err != nil { + return database.Account{}, wgAccount.Err + } + + account := wargamingToAccount(realm, wgAccount.Data, clan, false) + go func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + errMap := c.database.UpsertAccounts(ctx, []database.Account{account}) + if err := errMap[id]; err != nil { + log.Err(err).Msg("failed to update account cache") + } + }() + + return account, nil +} + /* Get live account stats from wargaming - Each request will be retried c.retriesPerRequest times @@ -224,8 +269,20 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session for _, apply := range opts { apply(&options) } + + // sessions and period stats are tracked differenttly + // we look up the latest session _before_ sessionBefore, not after sessionStart if sessionStart.IsZero() { - sessionStart = time.Now().Add(time.Hour * 25 * -1) + sessionStart = time.Now() + } + if days := time.Since(sessionStart).Hours() / 24; days > 90 { + return AccountStatsOverPeriod{}, ErrInvalidSessionStart + } + + sessionBefore := sessionStart + if time.Since(sessionStart).Hours() >= 24 { + // 3 days would mean today and yest, so before the reset 2 days ago and so on + sessionBefore = sessionBefore.Add(time.Hour * 24 * -1) } var accountSnapshot retry.DataWithErr[database.AccountSnapshot] @@ -253,31 +310,12 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session averages = retry.DataWithErr[map[string]frame.StatsFrame]{Data: a, Err: err} }() - // return career stats if stats are requested for 0 or 90+ days, we do not track that far - if days := time.Since(sessionStart).Hours() / 24; days > 90 || days < 1 { - group.Wait() - if current.Err != nil { - return AccountStatsOverPeriod{}, current.Err - } - if averages.Err != nil { - // not critical, this will only affect WN8 - log.Err(averages.Err).Msg("failed to get tank averages") - } - - stats := current.Data - if options.withWN8 { - stats.AddWN8(averages.Data) - } - return stats, nil - } - group.Add(1) go func() { defer group.Done() - s, err := c.database.GetAccountSnapshot(ctx, id, id, database.SnapshotTypeDaily, database.WithCreatedAfter(sessionStart)) + s, err := c.database.GetAccountSnapshot(ctx, id, id, database.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) accountSnapshot = retry.DataWithErr[database.AccountSnapshot]{Data: s, Err: err} - - v, err := c.database.GetVehicleSnapshots(ctx, id, id, database.SnapshotTypeDaily, database.WithCreatedAfter(sessionStart)) + v, err := c.database.GetVehicleSnapshots(ctx, id, id, database.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) vehiclesSnapshots = retry.DataWithErr[[]database.VehicleSnapshot]{Data: v, Err: err} }() @@ -287,17 +325,10 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session return AccountStatsOverPeriod{}, current.Err } if accountSnapshot.Err != nil { - if !db.IsErrNotFound(accountSnapshot.Err) { - return AccountStatsOverPeriod{}, accountSnapshot.Err + if db.IsErrNotFound(accountSnapshot.Err) { + return AccountStatsOverPeriod{}, ErrSessionNotFound } - // if there is no snapshot, we just return 0 battles - stats := current.Data - stats.PeriodEnd = time.Now() - stats.PeriodStart = sessionStart - stats.RatingBattles.StatsFrame = frame.StatsFrame{} - stats.RegularBattles.StatsFrame = frame.StatsFrame{} - stats.RegularBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, 0) - return stats, nil + return AccountStatsOverPeriod{}, accountSnapshot.Err } if averages.Err != nil { // not critical, this will only affect WN8 diff --git a/internal/stats/renderer.go b/internal/stats/renderer.go index ac0639c4..9c144826 100644 --- a/internal/stats/renderer.go +++ b/internal/stats/renderer.go @@ -5,6 +5,7 @@ import ( "time" "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/render" "golang.org/x/text/language" @@ -14,6 +15,7 @@ var _ Renderer = &renderer{} type renderer struct { fetchClient fetch.Client + wargaming wargaming.Client database database.Client locale language.Tag } @@ -25,6 +27,6 @@ type Renderer interface { // Replay(accountId string, from time.Time) (image.Image, error) } -func NewRenderer(fetch fetch.Client, database database.Client, locale language.Tag) *renderer { - return &renderer{fetch, database, locale} +func NewRenderer(fetch fetch.Client, database database.Client, wargaming wargaming.Client, locale language.Tag) *renderer { + return &renderer{fetch, wargaming, database, locale} } diff --git a/internal/stats/session.go b/internal/stats/session.go index 67e75340..5cdd86b5 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -5,24 +5,68 @@ import ( "slices" "time" + "github.com/cufee/aftermath/internal/database/prisma/db" "github.com/cufee/aftermath/internal/localization" + "github.com/cufee/aftermath/internal/logic" "github.com/cufee/aftermath/internal/stats/fetch" prepare "github.com/cufee/aftermath/internal/stats/prepare/period" options "github.com/cufee/aftermath/internal/stats/render" render "github.com/cufee/aftermath/internal/stats/render/period" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" ) func (r *renderer) Session(ctx context.Context, accountId string, from time.Time, opts ...options.Option) (Image, Metadata, error) { meta := Metadata{} + stop := meta.Timer("database#GetAccountByID") + _, err := r.database.GetAccountByID(ctx, accountId) + stop() + if err != nil { + if db.IsErrNotFound(err) { + _, err := r.fetchClient.Account(ctx, accountId) // this will cache the account + if err != nil { + return nil, meta, err + } + go func() { + // record a session in the background + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + _, err := logic.RecordAccountSnapshots(ctx, r.wargaming, r.database, r.wargaming.RealmFromAccountID(accountId), false, accountId) + if err != nil { + log.Err(err).Str("accountId", accountId).Msg("failed to record account snapshot") + } + }() + return nil, meta, ErrAccountNotTracked + } + return nil, meta, err + } printer, err := localization.NewPrinter("stats", r.locale) if err != nil { return nil, meta, err } - stop := meta.Timer("fetchClient#SessionStats") + stop = meta.Timer("fetchClient#SessionStats") stats, err := r.fetchClient.SessionStats(ctx, accountId, from, fetch.WithWN8()) stop() + if errors.Is(err, fetch.ErrSessionNotFound) && time.Since(from).Hours()/24 <= 90 { + // we dont have a session, but one might be available from blitzstars + stats, err = r.fetchClient.PeriodStats(ctx, accountId, from, fetch.WithWN8()) + // the error will be checked below + } + if errors.Is(err, fetch.ErrSessionNotFound) { + // Get account info and return a blank session + current, err := r.fetchClient.CurrentStats(ctx, accountId) + if err != nil { + return nil, meta, err + } + current.PeriodEnd = time.Now() + current.PeriodStart = current.PeriodEnd + current.RatingBattles = fetch.StatsWithVehicles{} + current.RegularBattles = fetch.StatsWithVehicles{} + stats = current + } if err != nil { return nil, meta, err } diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml index fb009c7f..86c74f1e 100755 --- a/static/localization/en/discord.yaml +++ b/static/localization/en/discord.yaml @@ -131,6 +131,10 @@ value: "I was not able to find a player named **%s** on **%s**. Was the name spelled correctly?" context: Invalid Blitz username provided +- key: session_error_account_was_not_tracked + value: "Aftermath just started tracking this account. Please play a few battles before using this command again." + context: We were not tracking this account, but successfully started tracking it just now + # /background errors - key: background_error_payment_required value: "This feature of Aftermath is only available for users with an active subscription.\nYou can subscribe by using the `/subscribe` command or pick a background using `/fancy` instead." @@ -152,7 +156,7 @@ - key: replay_error_missing_attachment value: "You need to provide a link or attach a WoT Blitz replay file." context: Invalid file attached - + # /link errors - key: link_error_missing_input value: "I need both the nickname and server to find your account." @@ -162,7 +166,7 @@ - key: verify_error_missing_server value: "Please select a server your account is registered on." context: Server not selected - + # Wargaming specific errors - key: wargaming_error_private_account value: "This account is marked private by Wargaming and no stats are available for it at this time." From 7f9335ac3ed1b8b459956fe5f87255ab360ae21e Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 12 Jun 2024 19:23:56 -0400 Subject: [PATCH 039/341] resetting error --- internal/stats/session.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/internal/stats/session.go b/internal/stats/session.go index 5cdd86b5..4d7a3a6a 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -19,7 +19,7 @@ import ( func (r *renderer) Session(ctx context.Context, accountId string, from time.Time, opts ...options.Option) (Image, Metadata, error) { meta := Metadata{} stop := meta.Timer("database#GetAccountByID") - _, err := r.database.GetAccountByID(ctx, accountId) + account, err := r.database.GetAccountByID(ctx, accountId) stop() if err != nil { if db.IsErrNotFound(err) { @@ -56,17 +56,16 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time // the error will be checked below } if errors.Is(err, fetch.ErrSessionNotFound) { - // Get account info and return a blank session - current, err := r.fetchClient.CurrentStats(ctx, accountId) - if err != nil { - return nil, meta, err + // blank session + err = nil + stats = fetch.AccountStatsOverPeriod{ + Account: account, + LastBattleTime: account.LastBattleTime, + PeriodStart: time.Now(), + PeriodEnd: time.Now(), } - current.PeriodEnd = time.Now() - current.PeriodStart = current.PeriodEnd - current.RatingBattles = fetch.StatsWithVehicles{} - current.RegularBattles = fetch.StatsWithVehicles{} - stats = current } + if err != nil { return nil, meta, err } From 4f7e1de592cbee20ef6842cf33a451a3358fff14 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 12 Jun 2024 19:24:28 -0400 Subject: [PATCH 040/341] missing realm --- internal/stats/session.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/stats/session.go b/internal/stats/session.go index 4d7a3a6a..c3e66ce9 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -59,10 +59,11 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time // blank session err = nil stats = fetch.AccountStatsOverPeriod{ + Realm: account.Realm, Account: account, - LastBattleTime: account.LastBattleTime, - PeriodStart: time.Now(), PeriodEnd: time.Now(), + PeriodStart: time.Now(), + LastBattleTime: account.LastBattleTime, } } From 33f0fdd2cf6c9738ad72dabcdef793ca23c7f193 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 13 Jun 2024 15:37:32 -0400 Subject: [PATCH 041/341] removed fly workflow --- .github/workflows/canary_fly.yml | 38 ++++++++++++++++---------------- internal/stats/session.go | 1 - 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/.github/workflows/canary_fly.yml b/.github/workflows/canary_fly.yml index 41ce70af..353e70cb 100644 --- a/.github/workflows/canary_fly.yml +++ b/.github/workflows/canary_fly.yml @@ -1,21 +1,21 @@ -name: Fly Deploy to Canary +# name: Fly Deploy to Canary -on: - push: - branches: - - canary - workflow_dispatch: {} +# on: +# push: +# branches: +# - canary +# workflow_dispatch: {} -jobs: - deploy: - name: Deploy app to canary - runs-on: ubuntu-latest - environment: Fly Canary - concurrency: deploy-group - timeout-minutes: 3 - steps: - - uses: actions/checkout@v4 - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --remote-only -c fly.canary.toml - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} +# jobs: +# deploy: +# name: Deploy app to canary +# runs-on: ubuntu-latest +# environment: Fly Canary +# concurrency: deploy-group +# timeout-minutes: 3 +# steps: +# - uses: actions/checkout@v4 +# - uses: superfly/flyctl-actions/setup-flyctl@master +# - run: flyctl deploy --remote-only -c fly.canary.toml +# env: +# FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/internal/stats/session.go b/internal/stats/session.go index c3e66ce9..7a1d5990 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -66,7 +66,6 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time LastBattleTime: account.LastBattleTime, } } - if err != nil { return nil, meta, err } From cff6da1b94d96cde432dd35099046008e9329986 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 13 Jun 2024 19:49:25 -0400 Subject: [PATCH 042/341] added some debug/manage commands --- .env.example | 36 +++++++- .github/workflows/canary_fly.yml | 21 ----- cmds/discord/commands/builder/command.go | 31 ++++++- cmds/discord/commands/builder/option.go | 7 ++ cmds/discord/commands/manage.go | 107 +++++++++++++++++++++++ cmds/discord/commands/options.go | 12 +-- cmds/discord/common/context.go | 31 +++++-- cmds/discord/middleware/middleware.go | 14 ++- cmds/discord/router/handler.go | 7 +- internal/database/client.go | 3 + internal/database/snapshots.go | 17 ++++ internal/database/users.go | 14 +++ internal/logic/init.go | 25 ++++++ internal/stats/session.go | 5 -- main.go | 6 +- 15 files changed, 293 insertions(+), 43 deletions(-) delete mode 100644 .github/workflows/canary_fly.yml create mode 100644 cmds/discord/commands/manage.go create mode 100644 internal/logic/init.go diff --git a/.env.example b/.env.example index dde0d0aa..483f66e7 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,35 @@ -DATABASE_URL="file:/tmp/dev_local.db" # This should be an absolute path, otherwise prisma fails to run correctly \ No newline at end of file +# Path to a .db file where SQLite database will be created +DATABASE_URL="file:/tmp/dev_local.db" # This should be an absolute path, otherwise prisma fails to run correctly + +# Init +INIT_GLOBAL_ADMIN_USER="" # Discord user ID for a user who will be assigned permissions.GlobalAdmin on startup, can be left blank + +# Wargaming API +WG_AUTH_APP_ID="" # Used to verify WG accounts, this app id will be exposed to the end user + +# Requests initiated by a user will always go through the "primary" wg app id and proxy +WG_PRIMARY_APP_ID="" +WG_PRIMARY_APP_RPS="10" +WG_PRIMARY_REQUEST_TIMEOUT_SEC="5" +WG_PROXY_HOST_LIST="" # can be left blank to disable proxy + +# Requests initiated from tasks during cache updates and etc will always go through this wg app id and proxy +WG_CACHE_APP_ID="" +WG_CACHE_APP_RPS="10" +WG_CACHE_REQUEST_TIMEOUT_SEC="15" +WG_CACHE_PROXY_HOST_LIST="" # can be left blank to disable proxy + +# Discord +DISCORD_TOKEN="" +DISCORD_PUBLIC_KEY="" +DISCORD_PRIMARY_GUILD_ID="" # Discord ID of the primary guild, some features will be locked to this guild only +DISCORD_ERROR_REPORT_WEBHOOK_URL="" # A Discord webhook URL for a channel where the bot should report errors + +# Optional components +SCHEDULER_ENABLED="true" +SCHEDULER_CONCURRENT_WORKERS="10" + +# Misc configuration +LOG_LEVEL="debug" +NETWORK="tcp" +PORT="9092" diff --git a/.github/workflows/canary_fly.yml b/.github/workflows/canary_fly.yml deleted file mode 100644 index 353e70cb..00000000 --- a/.github/workflows/canary_fly.yml +++ /dev/null @@ -1,21 +0,0 @@ -# name: Fly Deploy to Canary - -# on: -# push: -# branches: -# - canary -# workflow_dispatch: {} - -# jobs: -# deploy: -# name: Deploy app to canary -# runs-on: ubuntu-latest -# environment: Fly Canary -# concurrency: deploy-group -# timeout-minutes: 3 -# steps: -# - uses: actions/checkout@v4 -# - uses: superfly/flyctl-actions/setup-flyctl@master -# - run: flyctl deploy --remote-only -c fly.canary.toml -# env: -# FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/cmds/discord/commands/builder/command.go b/cmds/discord/commands/builder/command.go index 8ed9a206..97e15aa2 100644 --- a/cmds/discord/commands/builder/command.go +++ b/cmds/discord/commands/builder/command.go @@ -21,6 +21,10 @@ type CommandHandler func(*common.Context) error type Command struct { discordgo.ApplicationCommand + DMOnly bool + GuildIDs []string + GuildOnly bool + Type commandType Match func(string) bool Handler CommandHandler @@ -33,6 +37,10 @@ type Builder struct { kind commandType params parameters + dmOnly bool + guildOnly bool + guilds []string + match func(string) bool handler CommandHandler middleware []middleware.MiddlewareFunc @@ -70,7 +78,11 @@ func (c Builder) Build() Command { DescriptionLocalizations: &descLocalized, Options: options, Type: discordgo.ChatApplicationCommand, + DMPermission: &c.dmOnly, }, + c.dmOnly, + c.guilds, + c.guildOnly, c.kind, c.match, c.handler, @@ -91,12 +103,27 @@ func (c Builder) Params(params ...Param) Builder { return c } -func (c Builder) IsEphemeral() Builder { +func (c Builder) DMOnly() Builder { + c.dmOnly = true + return c +} + +func (c Builder) GuildOnly() Builder { + c.guildOnly = true + return c +} + +func (c Builder) ExclusiveToGuilds(guilds ...string) Builder { + c.guilds = append(c.guilds, guilds...) + return c +} + +func (c Builder) Ephemeral() Builder { c.ephemeral = true return c } -func (c Builder) IsComponentType() Builder { +func (c Builder) ComponentType() Builder { c.kind = CommandTypeComponent return c } diff --git a/cmds/discord/commands/builder/option.go b/cmds/discord/commands/builder/option.go index 2b259aa4..e3133179 100644 --- a/cmds/discord/commands/builder/option.go +++ b/cmds/discord/commands/builder/option.go @@ -86,6 +86,12 @@ func (o Option) Build(command string) discordgo.ApplicationCommandOption { panic("option type is not set") } + var options []*discordgo.ApplicationCommandOption + for _, option := range o.options { + opt := option.Build(command + "_" + o.name) + options = append(options, &opt) + } + nameLocalized := common.LocalizeKey(o.nameKey(command)) descLocalized := common.LocalizeKey(o.descKey(command)) @@ -106,6 +112,7 @@ func (o Option) Build(command string) discordgo.ApplicationCommandOption { MaxValue: o.maxValue, Required: o.required, Choices: choices, + Options: options, Type: o.kind, } } diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go new file mode 100644 index 00000000..8994b7b2 --- /dev/null +++ b/cmds/discord/commands/manage.go @@ -0,0 +1,107 @@ +package commands + +import ( + "encoding/json" + "os" + + "github.com/bwmarrin/discordgo" + "github.com/cufee/aftermath/cmds/discord/commands/builder" + "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmds/discord/middleware" + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/permissions" +) + +func init() { + Loaded.add( + builder.NewCommand("manage"). + ExclusiveToGuilds(os.Getenv("DISCORD_PRIMARY_GUILD_ID")). + Middleware(middleware.RequirePermissions(permissions.GlobalAdmin)). + Options( + builder.NewOption("users", discordgo.ApplicationCommandOptionSubCommandGroup).Options( + builder.NewOption("lookup", discordgo.ApplicationCommandOptionSubCommand).Options( + userOption, + ), + ), + builder.NewOption("accounts", discordgo.ApplicationCommandOptionSubCommandGroup).Options( + builder.NewOption("search", discordgo.ApplicationCommandOptionSubCommand).Options( + nicknameAndServerOptions..., + ), + ), + builder.NewOption("tasks", discordgo.ApplicationCommandOptionSubCommandGroup).Options( + builder.NewOption("view", discordgo.ApplicationCommandOptionSubCommand), + ), + builder.NewOption("snapshots", discordgo.ApplicationCommandOptionSubCommandGroup).Options( + builder.NewOption("view", discordgo.ApplicationCommandOptionSubCommand).Options( + builder.NewOption("account_id", discordgo.ApplicationCommandOptionString).Required(), + ), + ), + ). + Handler(func(ctx *common.Context) error { + command, opts, _ := ctx.Options().Subcommand() + + switch command { + case "users_lookup": + userID, _ := opts.Value("user").(string) + result, err := ctx.Core.Database().GetUserByID(ctx.Context, userID, database.WithConnections()) + if err != nil { + return ctx.Reply("Database#GetUserByID: " + err.Error()) + } + + data, err := json.MarshalIndent(result, "", " ") + if err != nil { + return ctx.Reply("MarshalIndent: " + err.Error()) + } + return ctx.Reply("```" + string(data) + "```") + + case "accounts_search": + nickname, _ := opts.Value("nickname").(string) + server, _ := opts.Value("server").(string) + result, err := ctx.Core.Fetch().Search(ctx.Context, nickname, server) + if err != nil { + return ctx.Reply("Fetch#Search: " + err.Error()) + } + data, err := json.MarshalIndent(result, "", " ") + if err != nil { + return ctx.Reply("MarshalIndent: " + err.Error()) + } + return ctx.Reply("```" + string(data) + "```") + + case "snapshots_view": + accountId, ok := opts.Value("account_id").(string) + if !ok { + return ctx.Reply("invalid accountId, failed to cast to string") + } + snapshots, err := ctx.Core.Database().GetLastAccountSnapshots(ctx.Context, accountId, 3) + if err != nil { + return ctx.Reply("GetLastAccountSnapshots: " + err.Error()) + } + + var data []map[string]string + for _, snapshot := range snapshots { + data = append(data, map[string]string{ + "id": snapshot.ID, + "type": string(snapshot.Type), + "referenceId": snapshot.ReferenceID, + "createdAt": snapshot.CreatedAt.String(), + "lastBattleTime": snapshot.LastBattleTime.String(), + "battlesRating": snapshot.RatingBattles.Battles.String(), + "battlesRegular": snapshot.RegularBattles.Battles.String(), + }) + } + + bytes, err := json.MarshalIndent(data, "", " ") + if err != nil { + return ctx.Reply("json.Marshal: " + err.Error()) + } + return ctx.Reply("```" + string(bytes) + "```") + + case "tasks_view": + return ctx.Reply("tasks view") + + default: + return ctx.Reply("invalid subcommand, thought this should never happen") + } + }), + ) +} diff --git a/cmds/discord/commands/options.go b/cmds/discord/commands/options.go index 3ee45d2b..0511a87e 100644 --- a/cmds/discord/commands/options.go +++ b/cmds/discord/commands/options.go @@ -39,13 +39,15 @@ var nicknameAndServerOptions = []builder.Option{ ), } +var userOption = builder.NewOption("user", discordgo.ApplicationCommandOptionUser). + Params( + builder.SetNameKey("common_option_stats_user_name"), + builder.SetDescKey("common_option_stats_user_description"), + ) + var defaultStatsOptions = append([]builder.Option{ daysOption, - builder.NewOption("user", discordgo.ApplicationCommandOptionUser). - Params( - builder.SetNameKey("common_option_stats_user_name"), - builder.SetDescKey("common_option_stats_user_description"), - ), + userOption, }, nicknameAndServerOptions...) type statsOptions struct { diff --git a/cmds/discord/common/context.go b/cmds/discord/common/context.go index 48f745f4..fea2b00f 100644 --- a/cmds/discord/common/context.go +++ b/cmds/discord/common/context.go @@ -130,18 +130,37 @@ func (c *Context) ID() string { return "" } -func (c *Context) Option(name string) any { +func (c *Context) Options() options { if data, ok := c.CommandData(); ok { - for _, opt := range data.Options { - fmt.Printf("%+v", opt) - if opt.Name == name { - return opt.Value - } + return data.Options + } + return options{} +} + +type options []*discordgo.ApplicationCommandInteractionDataOption + +func (o options) Value(name string) any { + for _, opt := range o { + if opt.Name == name { + return opt.Value } } return nil } +func (o options) Subcommand() (string, options, bool) { + for _, opt := range o { + if opt.Type == discordgo.ApplicationCommandOptionSubCommandGroup { + name, opts, ok := options(opt.Options).Subcommand() + return opt.Name + "_" + name, opts, ok + } + if opt.Type == discordgo.ApplicationCommandOptionSubCommand { + return opt.Name, opt.Options, true + } + } + return "", options{}, false +} + func GetOption[T any](c *Context, name string) (T, bool) { var v T if data, ok := c.CommandData(); ok { diff --git a/cmds/discord/middleware/middleware.go b/cmds/discord/middleware/middleware.go index 691eb6eb..2ceb941d 100644 --- a/cmds/discord/middleware/middleware.go +++ b/cmds/discord/middleware/middleware.go @@ -2,6 +2,18 @@ package middleware import ( "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/internal/permissions" ) -type MiddlewareFunc func(*common.Context, func(*common.Context)) func(*common.Context) +type MiddlewareFunc func(*common.Context, func(*common.Context) error) func(*common.Context) error + +func RequirePermissions(required permissions.Permissions) MiddlewareFunc { + return func(ctx *common.Context, next func(*common.Context) error) func(*common.Context) error { + if !ctx.User.Permissions.Has(required) { + return func(ctx *common.Context) error { + return ctx.Reply("common_error_command_missing_permissions") + } + } + return next + } +} diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index c84fb04a..142782a0 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -178,7 +178,12 @@ func (r *Router) handleInteraction(ctx context.Context, cancel context.CancelFun return } - err = command.Handler(cCtx) + handler := command.Handler + for i := len(command.Middleware) - 1; i >= 0; i-- { + handler = command.Middleware[i](cCtx, handler) + } + + err = handler(cCtx) if err != nil { log.Err(err).Msg("handler returned an error") reply <- discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")} diff --git a/internal/database/client.go b/internal/database/client.go index a4e69e3a..6ee91676 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -5,6 +5,7 @@ import ( "time" "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/permissions" "github.com/cufee/aftermath/internal/stats/frame" "golang.org/x/sync/semaphore" ) @@ -27,8 +28,10 @@ type Client interface { GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) UpdateConnection(ctx context.Context, connection UserConnection) (UserConnection, error) UpsertConnection(ctx context.Context, connection UserConnection) (UserConnection, error) + UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (User, error) CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error + GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]AccountSnapshot, error) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) (AccountSnapshot, error) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind snapshotType, options ...SnapshotQuery) ([]AccountSnapshot, error) CreateVehicleSnapshots(ctx context.Context, snapshots ...VehicleSnapshot) error diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 5d1d661f..671e3ba6 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -353,6 +353,23 @@ func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...Accoun return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) } +func (c *client) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]AccountSnapshot, error) { + models, err := c.prisma.AccountSnapshot.FindMany(db.AccountSnapshot.AccountID.Equals(accountID)).OrderBy(db.AccountSnapshot.CreatedAt.Order(db.DESC)).Take(limit).Exec(ctx) + if err != nil { + return nil, err + } + + var snapshots []AccountSnapshot + for _, model := range models { + s, err := AccountSnapshot{}.FromModel(&model) + if err != nil { + return nil, err + } + snapshots = append(snapshots, s) + } + return snapshots, nil +} + func (c *client) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) (AccountSnapshot, error) { var query getSnapshotQuery for _, apply := range options { diff --git a/internal/database/users.go b/internal/database/users.go index 24d89cee..ed953375 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -215,6 +215,20 @@ func (c *client) GetUserByID(ctx context.Context, id string, opts ...userGetOpti return user, nil } +func (c *client) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (User, error) { + model, err := c.prisma.User.UpsertOne(db.User.ID.Equals(userID)). + Create(db.User.ID.Set(userID), db.User.Permissions.Set(perms.String())). + Update(db.User.Permissions.Set(perms.String())).Exec(ctx) + if err != nil { + return User{}, err + } + + var user User + user.ID = model.ID + user.Permissions = permissions.Parse(model.Permissions, permissions.User) + return user, nil +} + func (c *client) UpdateConnection(ctx context.Context, connection UserConnection) (UserConnection, error) { if connection.ReferenceID == "" { return UserConnection{}, errors.New("connection referenceID cannot be left blank") diff --git a/internal/logic/init.go b/internal/logic/init.go new file mode 100644 index 00000000..bf4d01e3 --- /dev/null +++ b/internal/logic/init.go @@ -0,0 +1,25 @@ +package logic + +import ( + "context" + "os" + "time" + + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/permissions" + "github.com/pkg/errors" +) + +func ApplyInitOptions(database database.Client) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + if id := os.Getenv("INIT_GLOBAL_ADMIN_USER"); id != "" { + _, err := database.UpsertUserWithPermissions(ctx, id, permissions.GlobalAdmin) + if err != nil { + return errors.Wrap(err, "failed to upsert a user with permissions") + } + } + + return nil +} diff --git a/internal/stats/session.go b/internal/stats/session.go index 7a1d5990..4c463f64 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -50,11 +50,6 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time stop = meta.Timer("fetchClient#SessionStats") stats, err := r.fetchClient.SessionStats(ctx, accountId, from, fetch.WithWN8()) stop() - if errors.Is(err, fetch.ErrSessionNotFound) && time.Since(from).Hours()/24 <= 90 { - // we dont have a session, but one might be available from blitzstars - stats, err = r.fetchClient.PeriodStats(ctx, accountId, from, fetch.WithWN8()) - // the error will be checked below - } if errors.Is(err, fetch.ErrSessionNotFound) { // blank session err = nil diff --git a/main.go b/main.go index cba576bb..ed76a496 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/localization" + "github.com/cufee/aftermath/internal/logic" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/render/assets" @@ -41,6 +42,9 @@ func main() { coreClient := coreClientFromEnv() startSchedulerFromEnvAsync() + // Load some init options to registed admin accounts and etc + logic.ApplyInitOptions(coreClient.Database()) + discordHandler, err := discord.NewRouterHandler(coreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) if err != nil { panic(err) @@ -97,7 +101,7 @@ func startSchedulerFromEnvAsync() { func coreClientFromEnv() core.Client { // Dependencies - wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_PRIMARY_APP_ID"), os.Getenv("WG_PRIMARY_APP_RPS"), os.Getenv("WG_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_PROXY_HOST_LIST")) + wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_PRIMARY_APP_ID"), os.Getenv("WG_PRIMARY_APP_RPS"), os.Getenv("WG_PRIMARY_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_PROXY_HOST_LIST")) if err != nil { log.Fatal().Msgf("wgClient: wargaming#NewClientFromEnv failed %s", err) } From 2f58c0da571d5303472b56b9267469a879479c15 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 13 Jun 2024 19:50:46 -0400 Subject: [PATCH 043/341] updated required pems --- cmds/discord/commands/manage.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index 8994b7b2..a8be285f 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -2,7 +2,6 @@ package commands import ( "encoding/json" - "os" "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmds/discord/commands/builder" @@ -15,8 +14,8 @@ import ( func init() { Loaded.add( builder.NewCommand("manage"). - ExclusiveToGuilds(os.Getenv("DISCORD_PRIMARY_GUILD_ID")). - Middleware(middleware.RequirePermissions(permissions.GlobalAdmin)). + // ExclusiveToGuilds(os.Getenv("DISCORD_PRIMARY_GUILD_ID")). + Middleware(middleware.RequirePermissions(permissions.ContentModerator)). Options( builder.NewOption("users", discordgo.ApplicationCommandOptionSubCommandGroup).Options( builder.NewOption("lookup", discordgo.ApplicationCommandOptionSubCommand).Options( From 32061fdba1b07eccca8582902fd3962afe86367c Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 13 Jun 2024 21:14:49 -0400 Subject: [PATCH 044/341] added smart command updates --- cmds/discord/commands/builder/command.go | 10 +- cmds/discord/rest/commands.go | 26 +++- cmds/discord/router/router.go | 128 +++++++++++------- internal/database/client.go | 3 + internal/database/discord.go | 81 +++++++++++ .../migration.sql | 11 ++ .../20240614000247_added_index/migration.sql | 2 + .../20240614002652_added_name/migration.sql | 22 +++ internal/database/prisma/schema.prisma | 13 ++ internal/logic/hash.go | 29 ++++ 10 files changed, 265 insertions(+), 60 deletions(-) create mode 100644 internal/database/discord.go create mode 100644 internal/database/prisma/migrations/20240614000132_added_app_commands/migration.sql create mode 100644 internal/database/prisma/migrations/20240614000247_added_index/migration.sql create mode 100644 internal/database/prisma/migrations/20240614002652_added_name/migration.sql create mode 100644 internal/logic/hash.go diff --git a/cmds/discord/commands/builder/command.go b/cmds/discord/commands/builder/command.go index 97e15aa2..976db0cc 100644 --- a/cmds/discord/commands/builder/command.go +++ b/cmds/discord/commands/builder/command.go @@ -21,7 +21,6 @@ type CommandHandler func(*common.Context) error type Command struct { discordgo.ApplicationCommand - DMOnly bool GuildIDs []string GuildOnly bool @@ -37,9 +36,9 @@ type Builder struct { kind commandType params parameters - dmOnly bool - guildOnly bool + dmOnly *bool guilds []string + guildOnly bool match func(string) bool handler CommandHandler @@ -76,11 +75,10 @@ func (c Builder) Build() Command { Description: stringOr(descLocalized[discordgo.EnglishUS], c.name), NameLocalizations: &nameLocalized, DescriptionLocalizations: &descLocalized, + DMPermission: c.dmOnly, Options: options, Type: discordgo.ChatApplicationCommand, - DMPermission: &c.dmOnly, }, - c.dmOnly, c.guilds, c.guildOnly, c.kind, @@ -104,7 +102,7 @@ func (c Builder) Params(params ...Param) Builder { } func (c Builder) DMOnly() Builder { - c.dmOnly = true + c.dmOnly = common.Pointer(true) return c } diff --git a/cmds/discord/rest/commands.go b/cmds/discord/rest/commands.go index d72e12a0..1a6599e2 100644 --- a/cmds/discord/rest/commands.go +++ b/cmds/discord/rest/commands.go @@ -2,6 +2,23 @@ package rest import "github.com/bwmarrin/discordgo" +func (c *Client) OverwriteGlobalApplicationCommands(data []discordgo.ApplicationCommand) error { + req, err := c.request("PUT", discordgo.EndpointApplicationGlobalCommands(c.applicationID), data) + if err != nil { + return err + } + return c.do(req, nil) +} + +func (c *Client) CreateGlobalApplicationCommand(data discordgo.ApplicationCommand) (discordgo.ApplicationCommand, error) { + req, err := c.request("POST", discordgo.EndpointApplicationGlobalCommands(c.applicationID), data) + if err != nil { + return discordgo.ApplicationCommand{}, err + } + var command discordgo.ApplicationCommand + return command, c.do(req, &command) +} + func (c *Client) GetGlobalApplicationCommands() ([]discordgo.ApplicationCommand, error) { req, err := c.request("GET", discordgo.EndpointApplicationGlobalCommands(c.applicationID), nil) if err != nil { @@ -12,12 +29,13 @@ func (c *Client) GetGlobalApplicationCommands() ([]discordgo.ApplicationCommand, return data, c.do(req, &data) } -func (c *Client) OverwriteGlobalApplicationCommands(data []discordgo.ApplicationCommand) error { - req, err := c.request("PUT", discordgo.EndpointApplicationGlobalCommands(c.applicationID), data) +func (c *Client) UpdateGlobalApplicationCommand(id string, data discordgo.ApplicationCommand) (discordgo.ApplicationCommand, error) { + req, err := c.request("PATCH", discordgo.EndpointApplicationGlobalCommand(c.applicationID, id), data) if err != nil { - return err + return discordgo.ApplicationCommand{}, err } - return c.do(req, nil) + var command discordgo.ApplicationCommand + return command, c.do(req, &command) } func (c *Client) DeleteGlobalApplicationCommand(id string) error { diff --git a/cmds/discord/router/router.go b/cmds/discord/router/router.go index 00ed8674..3c24cfb8 100644 --- a/cmds/discord/router/router.go +++ b/cmds/discord/router/router.go @@ -1,14 +1,16 @@ package router import ( + "context" "fmt" - "slices" "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/cmds/discord/commands/builder" "github.com/cufee/aftermath/cmds/discord/middleware" "github.com/cufee/aftermath/cmds/discord/rest" + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/logic" "github.com/rs/zerolog/log" ) @@ -51,19 +53,22 @@ func (r *Router) LoadMiddleware(middleware ...middleware.MiddlewareFunc) { r.middleware = append(r.middleware, middleware...) } +type command struct { + requested *builder.Command + current *discordgo.ApplicationCommand + cached *database.ApplicationCommand + action string +} + /* Updates all loaded commands using the Discord REST API - any commands that are loaded will be created/updated - any commands that were not loaded will be deleted */ func (r *Router) UpdateLoadedCommands() error { - toOverwrite := make(map[string]discordgo.ApplicationCommand) - + var commandByName = make(map[string]command) for _, cmd := range r.commands { - if cmd.Type != builder.CommandTypeChat { - continue - } - toOverwrite[cmd.Name] = cmd.ApplicationCommand + commandByName[cmd.Name] = command{requested: &cmd} } current, err := r.restClient.GetGlobalApplicationCommands() @@ -71,57 +76,80 @@ func (r *Router) UpdateLoadedCommands() error { return err } - var toDelete []string - var currentCommands []string - for _, command := range current { - currentCommands = append(currentCommands, command.Name) - if _, ok := toOverwrite[command.Name]; !ok { - toDelete = append(toDelete, command.ID) - continue - } - - newCmd := toOverwrite[command.Name] - newCmd.ID = command.ID - - toOverwrite[command.Name] = newCmd - } - log.Debug().Any("commands", currentCommands).Msg("current application commands") - - for _, cmd := range r.commands { - if cmd.Type != builder.CommandTypeChat { - continue - } - if !slices.Contains(currentCommands, cmd.Name) { - // it will be created during the bulk overwrite call - continue - } - } + var currentIDs []string + for _, cmd := range current { + command := commandByName[cmd.Name] + command.current = &cmd + commandByName[cmd.Name] = command - for _, id := range toDelete { - log.Debug().Str("id", id).Msg("deleting old application command") - err := r.restClient.DeleteGlobalApplicationCommand(id) - if err != nil { - return fmt.Errorf("failed to delete a command: %w", err) - } - log.Debug().Str("id", id).Msg("deleted old application command") + currentIDs = append(currentIDs, cmd.ID) } - if len(toOverwrite) < 1 || len(currentCommands) == len(toOverwrite) { - log.Debug().Msg("no application commands need an update") - return nil + cachedCommands, err := r.core.Database().GetCommandsByID(context.Background(), currentIDs...) + if err != nil { + return err } - var commandUpdates []discordgo.ApplicationCommand - for _, cmd := range toOverwrite { - commandUpdates = append(commandUpdates, cmd) + for _, cmd := range cachedCommands { + command := commandByName[cmd.Name] + command.cached = &cmd + commandByName[cmd.Name] = command } - log.Debug().Msg("updating application commands") - err = r.restClient.OverwriteGlobalApplicationCommands(commandUpdates) - if err != nil { - return fmt.Errorf("failed to bulk update application commands: %w", err) + for _, data := range commandByName { + switch { + case data.requested != nil && data.current == nil: + log.Debug().Str("name", data.requested.Name).Msg("creating a global command") + hash, err := logic.HashAny(data.requested.ApplicationCommand) + if err != nil { + return err + } + command, err := r.restClient.CreateGlobalApplicationCommand(data.requested.ApplicationCommand) + if err != nil { + return err + } + err = r.core.Database().UpsertCommands(context.Background(), database.ApplicationCommand{ID: command.ID, Name: command.Name, Hash: hash, Version: command.Version}) + if err != nil { + return err + } + log.Debug().Str("name", data.requested.Name).Str("id", command.ID).Msg("created and cached a global command") + + case data.requested == nil && data.current != nil: + log.Debug().Str("name", data.current.Name).Str("id", data.current.ID).Msg("deleting a global command") + err := r.restClient.DeleteGlobalApplicationCommand(data.current.ID) + if err != nil { + return err + } + log.Debug().Str("name", data.current.Name).Str("id", data.current.ID).Msg("deleted a global command") + + case data.current != nil && data.requested != nil: + hash, err := logic.HashAny(data.requested.ApplicationCommand) + if err != nil { + return err + } + if data.cached == nil || data.cached.Hash != hash { + log.Debug().Str("name", data.current.Name).Str("id", data.current.ID).Msg("updating a global command") + hash, err := logic.HashAny(data.requested.ApplicationCommand) + if err != nil { + return err + } + command, err := r.restClient.UpdateGlobalApplicationCommand(data.current.ID, data.requested.ApplicationCommand) + if err != nil { + return err + } + err = r.core.Database().UpsertCommands(context.Background(), database.ApplicationCommand{ID: command.ID, Name: command.Name, Hash: hash, Version: command.Version}) + if err != nil { + return err + } + log.Debug().Str("name", data.current.Name).Str("id", data.current.ID).Msg("updated global command") + } else { + log.Debug().Str("name", data.current.Name).Str("id", data.current.ID).Msg("global command is up to date") + } + + default: + log.Debug().Str("name", data.current.Name).Str("id", data.current.ID).Msg("global command does not require any action") + } } - log.Debug().Msg("updated application commands") return nil } diff --git a/internal/database/client.go b/internal/database/client.go index 6ee91676..65557e0e 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -45,6 +45,9 @@ type Client interface { DeleteExpiredTasks(ctx context.Context, expiration time.Time) error DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error + + UpsertCommands(ctx context.Context, commands ...ApplicationCommand) error + GetCommandsByID(ctx context.Context, commandIDs ...string) ([]ApplicationCommand, error) } type client struct { diff --git a/internal/database/discord.go b/internal/database/discord.go new file mode 100644 index 00000000..ac1a925d --- /dev/null +++ b/internal/database/discord.go @@ -0,0 +1,81 @@ +package database + +import ( + "context" + + "github.com/cufee/aftermath/internal/database/prisma/db" +) + +type ApplicationCommand struct { + ID string + Hash string + Name string + Version string +} + +func (c ApplicationCommand) fromModel(model db.ApplicationCommandModel) ApplicationCommand { + return ApplicationCommand{ + ID: model.ID, + Name: model.Name, + Hash: model.OptionsHash, + Version: model.Version, + } +} + +func (c *client) GetCommandsByID(ctx context.Context, commandIDs ...string) ([]ApplicationCommand, error) { + if len(commandIDs) < 1 { + return nil, nil + } + + models, err := c.prisma.ApplicationCommand.FindMany(db.ApplicationCommand.ID.In(commandIDs)).Exec(ctx) + if err != nil { + return nil, err + } + + var commands []ApplicationCommand + for _, model := range models { + commands = append(commands, ApplicationCommand{}.fromModel(model)) + } + return commands, nil +} + +func (c *client) GetCommandsByHash(ctx context.Context, commandHashes ...string) ([]ApplicationCommand, error) { + if len(commandHashes) < 1 { + return nil, nil + } + + models, err := c.prisma.ApplicationCommand.FindMany(db.ApplicationCommand.OptionsHash.In(commandHashes)).Exec(ctx) + if err != nil { + return nil, err + } + + var commands []ApplicationCommand + for _, model := range models { + commands = append(commands, ApplicationCommand{}.fromModel(model)) + } + return commands, nil +} + +func (c *client) UpsertCommands(ctx context.Context, commands ...ApplicationCommand) error { + if len(commands) < 1 { + return nil + } + + var tx []db.PrismaTransaction + for _, cmd := range commands { + tx = append(tx, c.prisma.ApplicationCommand.UpsertOne(db.ApplicationCommand.ID.Equals(cmd.ID)). + Create( + db.ApplicationCommand.ID.Set(cmd.ID), + db.ApplicationCommand.Name.Set(cmd.Name), + db.ApplicationCommand.Version.Set(cmd.Version), + db.ApplicationCommand.OptionsHash.Set(cmd.Hash), + ). + Update( + db.ApplicationCommand.Name.Set(cmd.Name), + db.ApplicationCommand.Version.Set(cmd.Version), + db.ApplicationCommand.OptionsHash.Set(cmd.Hash), + ).Tx(), + ) + } + return c.prisma.Prisma.Transaction(tx...).Exec(ctx) +} diff --git a/internal/database/prisma/migrations/20240614000132_added_app_commands/migration.sql b/internal/database/prisma/migrations/20240614000132_added_app_commands/migration.sql new file mode 100644 index 00000000..b8a4abf1 --- /dev/null +++ b/internal/database/prisma/migrations/20240614000132_added_app_commands/migration.sql @@ -0,0 +1,11 @@ +-- CreateTable +CREATE TABLE "application_commands" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "version" TEXT NOT NULL, + "optionsHash" TEXT NOT NULL +); + +-- CreateIndex +CREATE INDEX "account_snapshots_type_accountId_createdAt_idx" ON "account_snapshots"("type", "accountId", "createdAt"); diff --git a/internal/database/prisma/migrations/20240614000247_added_index/migration.sql b/internal/database/prisma/migrations/20240614000247_added_index/migration.sql new file mode 100644 index 00000000..428604b0 --- /dev/null +++ b/internal/database/prisma/migrations/20240614000247_added_index/migration.sql @@ -0,0 +1,2 @@ +-- CreateIndex +CREATE INDEX "application_commands_optionsHash_idx" ON "application_commands"("optionsHash"); diff --git a/internal/database/prisma/migrations/20240614002652_added_name/migration.sql b/internal/database/prisma/migrations/20240614002652_added_name/migration.sql new file mode 100644 index 00000000..c7c9687b --- /dev/null +++ b/internal/database/prisma/migrations/20240614002652_added_name/migration.sql @@ -0,0 +1,22 @@ +/* + Warnings: + + - Added the required column `name` to the `application_commands` table without a default value. This is not possible if the table is not empty. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_application_commands" ( + "id" TEXT NOT NULL PRIMARY KEY, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "name" TEXT NOT NULL, + "version" TEXT NOT NULL, + "optionsHash" TEXT NOT NULL +); +INSERT INTO "new_application_commands" ("createdAt", "id", "optionsHash", "updatedAt", "version") SELECT "createdAt", "id", "optionsHash", "updatedAt", "version" FROM "application_commands"; +DROP TABLE "application_commands"; +ALTER TABLE "new_application_commands" RENAME TO "application_commands"; +CREATE INDEX "application_commands_optionsHash_idx" ON "application_commands"("optionsHash"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index 5885f1b8..04274134 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -325,6 +325,19 @@ model Vehicle { // === Discord Data === // +model ApplicationCommand { + id String @id + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String + version String + optionsHash String + + @@index([optionsHash]) + @@map("application_commands") +} + // // A log of a user interaction with the bot // model DiscordInteraction { // id String @id @default(cuid()) diff --git a/internal/logic/hash.go b/internal/logic/hash.go new file mode 100644 index 00000000..ce8b2634 --- /dev/null +++ b/internal/logic/hash.go @@ -0,0 +1,29 @@ +package logic + +import ( + "crypto/sha256" + "fmt" + + "github.com/cufee/aftermath/internal/encoding" +) + +/* +returns a sha256 hash of the input string +*/ +func HashString(input string) string { + hash := sha256.New() + hash.Write([]byte(input)) + sum := hash.Sum(nil) + return fmt.Sprintf("%x", sum) +} + +/* +gob encode the input and sha256 hashes the resulting string +*/ +func HashAny(input any) (string, error) { + data, err := encoding.EncodeGob(input) + if err != nil { + return "", err + } + return HashString(string(data)), nil +} From f42832d87a360f806df13905eaef46be7cefa77f Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 13 Jun 2024 21:43:18 -0400 Subject: [PATCH 045/341] added command to view tasks --- cmds/discord/commands/manage.go | 27 ++++++++++++++++++++-- internal/database/client.go | 28 +++++++++++++++++++--- internal/database/tasks.go | 41 +++++++++++++++++++++++++++------ 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index a8be285f..1c872bf8 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -2,6 +2,7 @@ package commands import ( "encoding/json" + "time" "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmds/discord/commands/builder" @@ -28,7 +29,13 @@ func init() { ), ), builder.NewOption("tasks", discordgo.ApplicationCommandOptionSubCommandGroup).Options( - builder.NewOption("view", discordgo.ApplicationCommandOptionSubCommand), + builder.NewOption("view", discordgo.ApplicationCommandOptionSubCommand).Options( + builder.NewOption("status", discordgo.ApplicationCommandOptionString).Choices( + builder.NewChoice("failed", string(database.TaskStatusFailed)), + builder.NewChoice("complete", string(database.TaskStatusComplete)), + builder.NewChoice("in-progress", string(database.TaskStatusInProgress)), + ).Required(), + ), ), builder.NewOption("snapshots", discordgo.ApplicationCommandOptionSubCommandGroup).Options( builder.NewOption("view", discordgo.ApplicationCommandOptionSubCommand).Options( @@ -96,7 +103,23 @@ func init() { return ctx.Reply("```" + string(bytes) + "```") case "tasks_view": - return ctx.Reply("tasks view") + status, _ := opts.Value("status").(string) + tasks, err := ctx.Core.Database().GetRecentTasks(ctx.Context, time.Now().Add(time.Hour*24*-1), database.TaskStatus(status)) + if err != nil { + return ctx.Reply("Database#GetRecentTasks: " + err.Error()) + } + if len(tasks) < 1 { + return ctx.Reply("No recent tasks with status " + status) + } + + bytes, err := json.MarshalIndent(tasks, "", " ") + if err != nil { + return ctx.Reply("json.Marshal: " + err.Error()) + } + if len(string(bytes)) > 1990 { + return ctx.ReplyFmt("Too many tasks to show - %d", len(tasks)) + } + return ctx.Reply("```" + string(bytes) + "```") default: return ctx.Reply("invalid subcommand, thought this should never happen") diff --git a/internal/database/client.go b/internal/database/client.go index 65557e0e..f8b63e7c 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -12,44 +12,66 @@ import ( var _ Client = &client{} -type Client interface { +type AccountsClient interface { GetAccountByID(ctx context.Context, id string) (Account, error) GetAccounts(ctx context.Context, ids []string) ([]Account, error) GetRealmAccounts(ctx context.Context, realm string) ([]Account, error) UpsertAccounts(ctx context.Context, accounts []Account) map[string]error AccountSetPrivate(ctx context.Context, id string, value bool) error +} +type GlossaryClient interface { GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error GetVehicles(ctx context.Context, ids []string) (map[string]Vehicle, error) UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle) error +} +type UsersClient interface { GetUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) UpdateConnection(ctx context.Context, connection UserConnection) (UserConnection, error) UpsertConnection(ctx context.Context, connection UserConnection) (UserConnection, error) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (User, error) +} +type SnapshotsClient interface { CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]AccountSnapshot, error) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) (AccountSnapshot, error) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind snapshotType, options ...SnapshotQuery) ([]AccountSnapshot, error) CreateVehicleSnapshots(ctx context.Context, snapshots ...VehicleSnapshot) error GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) ([]VehicleSnapshot, error) + DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error +} +type TasksClient interface { CreateTasks(ctx context.Context, tasks ...Task) error UpdateTasks(ctx context.Context, tasks ...Task) error DeleteTasks(ctx context.Context, ids ...string) error GetStaleTasks(ctx context.Context, limit int) ([]Task, error) GetAndStartTasks(ctx context.Context, limit int) ([]Task, error) - DeleteExpiredTasks(ctx context.Context, expiration time.Time) error - DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error + GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...TaskStatus) ([]Task, error) +} +type DiscordDataClient interface { UpsertCommands(ctx context.Context, commands ...ApplicationCommand) error GetCommandsByID(ctx context.Context, commandIDs ...string) ([]ApplicationCommand, error) } +type Client interface { + UsersClient + + GlossaryClient + AccountsClient + SnapshotsClient + + TasksClient + + DiscordDataClient +} + type client struct { prisma *db.PrismaClient // Prisma does not currently support updateManyAndReturn diff --git a/internal/database/tasks.go b/internal/database/tasks.go index b87fcfed..8bef200a 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -22,13 +22,13 @@ const ( ) // Task statuses -type taskStatus string +type TaskStatus string const ( - TaskStatusScheduled taskStatus = "TASK_SCHEDULED" - TaskStatusInProgress taskStatus = "TASK_IN_PROGRESS" - TaskStatusComplete taskStatus = "TASK_COMPLETE" - TaskStatusFailed taskStatus = "TASK_FAILED" + TaskStatusScheduled TaskStatus = "TASK_SCHEDULED" + TaskStatusInProgress TaskStatus = "TASK_IN_PROGRESS" + TaskStatusComplete TaskStatus = "TASK_COMPLETE" + TaskStatusFailed TaskStatus = "TASK_FAILED" ) type Task struct { @@ -42,7 +42,7 @@ type Task struct { Logs []TaskLog `json:"logs"` - Status taskStatus `json:"status"` + Status TaskStatus `json:"status"` ScheduledAfter time.Time `json:"scheduledAfter"` LastRun time.Time `json:"lastRun"` @@ -53,7 +53,7 @@ func (t Task) FromModel(model db.CronTaskModel) Task { t.ID = model.ID t.Type = TaskType(model.Type) - t.Status = taskStatus(model.Status) + t.Status = TaskStatus(model.Status) t.ReferenceID = model.ReferenceID t.LastRun = model.LastRun @@ -148,6 +148,33 @@ func (c *client) GetStaleTasks(ctx context.Context, limit int) ([]Task, error) { return tasks, nil } +/* +Returns all tasks that were created after createdAfter, sorted by ScheduledAfter (DESC) +*/ +func (c *client) GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...TaskStatus) ([]Task, error) { + var statusStr []string + for _, s := range status { + statusStr = append(statusStr, string(s)) + } + + params := []db.CronTaskWhereParam{db.CronTask.CreatedAt.After(createdAfter)} + if len(statusStr) > 0 { + params = append(params, db.CronTask.Status.In(statusStr)) + } + + models, err := c.prisma.CronTask.FindMany(params...).OrderBy(db.CronTask.ScheduledAfter.Order(db.DESC)).Exec(ctx) + if err != nil && !db.IsErrNotFound(err) { + return nil, err + } + + var tasks []Task + for _, model := range models { + tasks = append(tasks, Task{}.FromModel(model)) + } + + return tasks, nil +} + /* GetAndStartTasks retrieves up to limit number of tasks matching the referenceId and updates their status to in progress - this func will block until all other calls to task update funcs are done From f738239daefa67abec7f5b3dec61f59e7f863eee Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 13 Jun 2024 21:45:04 -0400 Subject: [PATCH 046/341] added hours as arg --- cmds/discord/commands/manage.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index 1c872bf8..6f863c53 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -35,6 +35,7 @@ func init() { builder.NewChoice("complete", string(database.TaskStatusComplete)), builder.NewChoice("in-progress", string(database.TaskStatusInProgress)), ).Required(), + builder.NewOption("hours", discordgo.ApplicationCommandOptionNumber).Required(), ), ), builder.NewOption("snapshots", discordgo.ApplicationCommandOptionSubCommandGroup).Options( @@ -103,8 +104,13 @@ func init() { return ctx.Reply("```" + string(bytes) + "```") case "tasks_view": + hours, _ := opts.Value("status").(float64) status, _ := opts.Value("status").(string) - tasks, err := ctx.Core.Database().GetRecentTasks(ctx.Context, time.Now().Add(time.Hour*24*-1), database.TaskStatus(status)) + if hours < 1 { + hours = 1 + } + + tasks, err := ctx.Core.Database().GetRecentTasks(ctx.Context, time.Now().Add(time.Hour*time.Duration(hours)*-1), database.TaskStatus(status)) if err != nil { return ctx.Reply("Database#GetRecentTasks: " + err.Error()) } From f9f582b6d9920a25df6280e510e3bf53e4780706 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 13 Jun 2024 21:46:47 -0400 Subject: [PATCH 047/341] fixed hours arg --- cmds/discord/commands/manage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index 6f863c53..97f2aa4b 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -104,7 +104,7 @@ func init() { return ctx.Reply("```" + string(bytes) + "```") case "tasks_view": - hours, _ := opts.Value("status").(float64) + hours, _ := opts.Value("hours").(float64) status, _ := opts.Value("status").(string) if hours < 1 { hours = 1 From bfba7c335f7cd88bdd9c2906aafe07da53cf89a9 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 14 Jun 2024 13:27:27 -0400 Subject: [PATCH 048/341] upgrade, guild only support --- cmds/discord/commands/builder/command.go | 23 +++-------------------- cmds/discord/commands/ping.go | 16 ---------------- go.mod | 20 ++++++++++---------- go.sum | 18 ++++++++++++++++++ 4 files changed, 31 insertions(+), 46 deletions(-) delete mode 100644 cmds/discord/commands/ping.go diff --git a/cmds/discord/commands/builder/command.go b/cmds/discord/commands/builder/command.go index 976db0cc..691f95b5 100644 --- a/cmds/discord/commands/builder/command.go +++ b/cmds/discord/commands/builder/command.go @@ -21,9 +21,6 @@ type CommandHandler func(*common.Context) error type Command struct { discordgo.ApplicationCommand - GuildIDs []string - GuildOnly bool - Type commandType Match func(string) bool Handler CommandHandler @@ -36,8 +33,6 @@ type Builder struct { kind commandType params parameters - dmOnly *bool - guilds []string guildOnly bool match func(string) bool @@ -75,12 +70,10 @@ func (c Builder) Build() Command { Description: stringOr(descLocalized[discordgo.EnglishUS], c.name), NameLocalizations: &nameLocalized, DescriptionLocalizations: &descLocalized, - DMPermission: c.dmOnly, Options: options, + DMPermission: common.Pointer(!c.guildOnly), Type: discordgo.ChatApplicationCommand, }, - c.guilds, - c.guildOnly, c.kind, c.match, c.handler, @@ -101,8 +94,8 @@ func (c Builder) Params(params ...Param) Builder { return c } -func (c Builder) DMOnly() Builder { - c.dmOnly = common.Pointer(true) +func (c Builder) Ephemeral() Builder { + c.ephemeral = true return c } @@ -111,16 +104,6 @@ func (c Builder) GuildOnly() Builder { return c } -func (c Builder) ExclusiveToGuilds(guilds ...string) Builder { - c.guilds = append(c.guilds, guilds...) - return c -} - -func (c Builder) Ephemeral() Builder { - c.ephemeral = true - return c -} - func (c Builder) ComponentType() Builder { c.kind = CommandTypeComponent return c diff --git a/cmds/discord/commands/ping.go b/cmds/discord/commands/ping.go deleted file mode 100644 index 7e062030..00000000 --- a/cmds/discord/commands/ping.go +++ /dev/null @@ -1,16 +0,0 @@ -package commands - -import ( - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/common" -) - -func init() { - Loaded.add( - builder.NewCommand("ping"). - Params(builder.SetDescKey("Pong!")). - Handler(func(ctx *common.Context) error { - return ctx.Reply("Pong!") - }), - ) -} diff --git a/go.mod b/go.mod index 06d528a3..0232d5a1 100644 --- a/go.mod +++ b/go.mod @@ -11,29 +11,29 @@ require ( github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/joho/godotenv v1.5.1 github.com/nlpodyssey/gopickle v0.3.0 + github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/shopspring/decimal v1.4.0 github.com/steebchen/prisma-client-go v0.37.0 github.com/stretchr/testify v1.9.0 go.dedis.ch/protobuf v1.0.11 - golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 - golang.org/x/image v0.16.0 + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 + golang.org/x/image v0.17.0 golang.org/x/sync v0.7.0 - golang.org/x/text v0.15.0 + golang.org/x/text v0.16.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/google/uuid v1.5.0 // indirect - github.com/gorilla/websocket v1.5.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - go.uber.org/atomic v1.9.0 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index 4b57b235..4a980a34 100644 --- a/go.sum +++ b/go.sum @@ -19,9 +19,13 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -77,18 +81,28 @@ go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 h1:Mi0bCswbz+9cXmwFAdxoo5GPFMKONUpua6iUdtQS7lk= golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= +golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= +golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -98,11 +112,15 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= From 4c11ebc9db657f41d8085ae7111d4696b2594e6e Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 14 Jun 2024 14:11:51 -0400 Subject: [PATCH 049/341] split migration and service containers --- Dockerfile | 28 ++++++++-------------------- Dockerfile.migrate | 17 +++++++++++++++++ docker-compose.yaml | 25 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 20 deletions(-) create mode 100644 Dockerfile.migrate create mode 100644 docker-compose.yaml diff --git a/Dockerfile b/Dockerfile index 993f4f5e..990393ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,27 +16,15 @@ RUN go generate ./... # build a fully standalone binary with zero dependencies RUN CGO_ENABLED=0 GOOS=linux go build -o app . +# Make a scratch container with required files and binary +FROM scratch -# railway.app does not really have a way to run a migration container on the volume, so we have to improvise here -# in order to run migrations, we need to have go and prisma installed -FROM golang:1.22.3-alpine +WORKDIR /app -# set timezone ENV TZ=Europe/Berlin +ENV ZONEINFO=/zoneinfo.zip +COPY --from=builder /workspace/app /usr/bin/ +COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip / +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -# copy final binary -COPY --from=builder /workspace/app /app - -# copy migrations and schema -COPY --from=builder /workspace/internal/database/prisma/migrations /prisma/migrations -COPY --from=builder /workspace/internal/database/prisma/schema.prisma /prisma/schema.prisma -COPY --from=builder /workspace/docker-entrypoint.sh /docker-entrypoint.sh -COPY --from=builder /workspace/go.mod /go.mod -COPY --from=builder /workspace/go.sum /go.sum - -# install prisma and prefetch binaries -RUN go install github.com/steebchen/prisma-client-go -RUN go run github.com/steebchen/prisma-client-go prefetch -RUN chmod +x /docker-entrypoint.sh - -ENTRYPOINT ["/docker-entrypoint.sh"] +ENTRYPOINT ["app"] diff --git a/Dockerfile.migrate b/Dockerfile.migrate new file mode 100644 index 00000000..3764f1af --- /dev/null +++ b/Dockerfile.migrate @@ -0,0 +1,17 @@ +FROM golang:1.22.3-alpine + +WORKDIR /migrate + +# copy migrations and schema +COPY ./internal/database/prisma/migrations /prisma/migrations +COPY ./internal/database/prisma/schema.prisma /prisma/schema.prisma + +# copy go.mod +COPY ./go.mod /go.mod +COPY ./go.sum /go.sum + +# install prisma and prefetch binaries +RUN go install github.com/steebchen/prisma-client-go@latest +RUN go run github.com/steebchen/prisma-client-go prefetch + +ENTRYPOINT ["sh", "-c", "go run github.com/steebchen/prisma-client-go migrate deploy --schema /prisma/schema.prisma"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..c4741b78 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,25 @@ +services: + apply-migrations: + image: aftermath-prisma-migrate + build: + dockerfile: Dockerfile.migrate + environment: + - DATABASE_URL=file:/database/${DATABASE_NAME} + volumes: + - ${DATABASE_DIR}:/database + command: sh -c "go run github.com/steebchen/prisma-client-go migrate deploy --schema /prisma/schema.prisma" + + aftermath-service: + image: aftermath-service + build: + dockerfile: Dockerfile + env_file: + - .env + environment: + - DATABASE_URL=file:/database/${DATABASE_NAME} + volumes: + - ${DATABASE_DIR}:/database + depends_on: + apply-migrations: + condition: service_completed_successfully + entrypoint: ["app"] From 9ff5d01fd7a7714cda198be31f2557442da5aa38 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 14 Jun 2024 14:29:57 -0400 Subject: [PATCH 050/341] added traefik labels --- docker-compose.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index c4741b78..e54819aa 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -16,6 +16,7 @@ services: env_file: - .env environment: + - PORT=3000 - DATABASE_URL=file:/database/${DATABASE_NAME} volumes: - ${DATABASE_DIR}:/database @@ -23,3 +24,18 @@ services: apply-migrations: condition: service_completed_successfully entrypoint: ["app"] + restart: always + ports: + - 3000 + networks: + - dokploy-network + labels: + - "traefik.enable=true" + - "traefik.http.routers.aftermath-monorepo-compose.rule=Host(`${TRAEFIK_HOST}`)" + - "traefik.http.routers.aftermath-monorepo-compose.entrypoints=websecure" + - "traefik.http.routers.aftermath-monorepo-compose.tls.certResolver=letsencrypt" + - "traefik.http.services.aftermath-monorepo-compose.loadbalancer.server.port=3000" + +networks: + dokploy-network: + external: true From cea4268d8d87eacc7f8626d5344786ff58954429 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 14 Jun 2024 15:01:35 -0400 Subject: [PATCH 051/341] added memory limit and reservation --- docker-compose.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index e54819aa..1f203e05 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -25,6 +25,12 @@ services: condition: service_completed_successfully entrypoint: ["app"] restart: always + deploy: + resources: + reservations: + memory: 128m + limits: + memory: 256m ports: - 3000 networks: From 75f153ae6dee1fd78efe4a2e279563eda449ebf8 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 14 Jun 2024 15:38:08 -0400 Subject: [PATCH 052/341] added readme --- .env.example | 16 ++++++++++++---- README.md | 25 +++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 483f66e7..5f143144 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,11 @@ -# Path to a .db file where SQLite database will be created -DATABASE_URL="file:/tmp/dev_local.db" # This should be an absolute path, otherwise prisma fails to run correctly +# This is not required for local deployment. When deploying with Dokploy, this is the domain aftermath service will be available on. +TRAEFIK_HOST="local.amth.one" + +# Database configuration for compose +DATABASE_NAME="dev_local.db" +DATABASE_DIR="/tmp" +# This should be an absolute path, otherwise prisma fails to run correctly. This variable is only used when developing locally or deploying without Compose. +DATABASE_URL="file:/tmp/dev_local.db" # Init INIT_GLOBAL_ADMIN_USER="" # Discord user ID for a user who will be assigned permissions.GlobalAdmin on startup, can be left blank @@ -11,13 +17,15 @@ WG_AUTH_APP_ID="" # Used to verify WG accounts, this app id will be exposed to t WG_PRIMARY_APP_ID="" WG_PRIMARY_APP_RPS="10" WG_PRIMARY_REQUEST_TIMEOUT_SEC="5" -WG_PROXY_HOST_LIST="" # can be left blank to disable proxy +# List of proxy hosts separated by comma in the format user:password@host:port?wgAppId=your_app_id&maxRps=20, this can be left blank to disable proxying +WG_PROXY_HOST_LIST="" # Requests initiated from tasks during cache updates and etc will always go through this wg app id and proxy WG_CACHE_APP_ID="" WG_CACHE_APP_RPS="10" WG_CACHE_REQUEST_TIMEOUT_SEC="15" -WG_CACHE_PROXY_HOST_LIST="" # can be left blank to disable proxy +# List of proxy hosts separated by comma in the format user:password@host:port?wgAppId=your_app_id&maxRps=20, this can be left blank to disable proxying +WG_CACHE_PROXY_HOST_LIST="" # Discord DISCORD_TOKEN="" diff --git a/README.md b/README.md index cd4a7b9e..bbff7d4b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,24 @@ -# Thank you for checking out Afteramth! +# Aftermath +This is the most recent revision/rewrite of Aftermath. The main goal of this project is to simplify deployment and make it significantly cheaper to host. -[Here is a link to the Wiki page](https://github.com/Cufee/aftermath/wiki) where you can learn how to setup and use the bot. +## Deploying +Deploying Aftermath is relatively simple. You can build the binary manually, use Docker Compose, or build a Docker image. + +### Docker Compose with [Dokploy](https://dokploy.com/) +- Get a VPS instalnce. Purchase a domain and point it to this instance. +- Create a new Bot Application in Discord +- [Install Dokploy and Docker](https://docs.dokploy.com/get-started/installation). +- Create a new Project and add a new Compose service. +- In Service settings > General > Provider, select your forked repository or Git > `git@github.com:Cufee/aftermath.git`. The branch should be `master`, but you can pick something different. +- Change the Compose Path to `./docker-compose.yaml`. +- If you are using your own repository, [setup a webhook under Deployments](https://docs.dokploy.com/application/overview#github). +- Add your environment configuration under Environment, you can start by copying `.env.example`. + - Set `TRAEFIK_HOST` to your domain. For example, `amth.one`. + - Add a Discord Bot token and public key + - Ensure the `DATABASE_DIR` and `DATABASE_NAME` are set correctly, this will be the path **on your host**. If this is misconfigured, you will lose data on restart. + - Add Wargaming App IDs, not that one of them will be public. + - If you are planning to track a lot of users, add proxies for Wargaming API in the following format: `user:password@host:port?wgAppId=your_app_id&maxRps=20` + - Read through all variables prefixed with `INIT_`. Those will allow you to create admin user accounts and etc. +- Head over to General and click on Deploy. +- Once the app is deployed and everything looks good in Logs, set the INTERACTIONS ENDPOINT URL under General Information in Discord Application settings to `https://yourdomain/discord/callback`. For example, `https://amth.one/discord/callback`. +- You can start using Discord commands now! From 2fd9f58edbf84ade0e7265f9b64664c09693d001 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 15 Jun 2024 12:27:41 -0400 Subject: [PATCH 053/341] work towards sessions --- cmds/discord/commands/session.go | 6 +- cmds/discord/common/context.go | 16 +- cmds/discord/rest/client.go | 12 +- cmds/discord/router/handler.go | 42 ++-- internal/logic/sessions.go | 2 +- internal/stats/errors.go | 4 +- internal/stats/fetch/client.go | 2 +- internal/stats/fetch/multisource.go | 51 +++-- internal/stats/metadata.go | 2 +- internal/stats/period.go | 7 +- internal/stats/prepare/common/card.go | 54 +++-- internal/stats/prepare/common/highlights.go | 62 +++++ internal/stats/prepare/common/options.go | 30 +++ internal/stats/prepare/period/card.go | 44 ++-- internal/stats/prepare/period/constants.go | 4 +- internal/stats/prepare/period/highlight.go | 60 ----- internal/stats/prepare/period/options.go | 16 -- internal/stats/prepare/period/preset.go | 7 +- internal/stats/prepare/session/card.go | 152 +++++++++++++ internal/stats/prepare/session/constants.go | 35 +++ internal/stats/prepare/session/preset.go | 28 +++ internal/stats/render/common/header.go | 47 ++++ internal/stats/render/period/cards.go | 44 +--- .../render/period/{render.go => image.go} | 0 internal/stats/render/session/blocks.go | 145 ++++++++++++ internal/stats/render/session/cards.go | 215 ++++++++++++++++++ internal/stats/render/session/constants.go | 120 ++++++++++ internal/stats/render/session/image.go | 24 ++ internal/stats/session.go | 45 ++-- render_test.go | 34 +++ static/localization/en/discord.yaml | 4 + 31 files changed, 1072 insertions(+), 242 deletions(-) create mode 100644 internal/stats/prepare/common/highlights.go create mode 100644 internal/stats/prepare/common/options.go delete mode 100644 internal/stats/prepare/period/highlight.go delete mode 100644 internal/stats/prepare/period/options.go create mode 100644 internal/stats/prepare/session/card.go create mode 100644 internal/stats/prepare/session/constants.go create mode 100644 internal/stats/prepare/session/preset.go create mode 100644 internal/stats/render/common/header.go rename internal/stats/render/period/{render.go => image.go} (100%) create mode 100644 internal/stats/render/session/blocks.go create mode 100644 internal/stats/render/session/cards.go create mode 100644 internal/stats/render/session/constants.go create mode 100644 internal/stats/render/session/image.go create mode 100644 render_test.go diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go index b8de76a2..4c11f155 100644 --- a/cmds/discord/commands/session.go +++ b/cmds/discord/commands/session.go @@ -10,6 +10,7 @@ import ( "github.com/cufee/aftermath/cmds/discord/common" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats" + "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" "github.com/pkg/errors" @@ -63,9 +64,12 @@ func init() { image, _, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), accountID, options.PeriodStart, render.WithBackground(background)) if err != nil { - if errors.Is(err, stats.ErrAccountNotTracked) { + if errors.Is(err, stats.ErrAccountNotTracked) || (errors.Is(err, fetch.ErrSessionNotFound) && options.Days < 1) { return ctx.Reply("session_error_account_was_not_tracked") } + if errors.Is(err, fetch.ErrSessionNotFound) { + return ctx.Reply("session_error_no_session_for_period") + } return ctx.Err(err) } diff --git a/cmds/discord/common/context.go b/cmds/discord/common/context.go index fea2b00f..02e6bcf5 100644 --- a/cmds/discord/common/context.go +++ b/cmds/discord/common/context.go @@ -76,12 +76,21 @@ func NewContext(ctx context.Context, interaction discordgo.Interaction, respondC return c, nil } +func (c *Context) respond(data discordgo.InteractionResponseData) error { + select { + case <-c.Context.Done(): + return c.Context.Err() + default: + c.respondCh <- data + } + return nil +} + func (c *Context) Message(message string) error { if message == "" { return errors.New("bad reply call with blank message") } - c.respondCh <- discordgo.InteractionResponseData{Content: message} - return nil + return c.respond(discordgo.InteractionResponseData{Content: message}) } func (c *Context) Reply(key string) error { @@ -106,8 +115,7 @@ func (c *Context) File(r io.Reader, name string) error { if r == nil { return errors.New("bad Context#File call with nil io.Reader") } - c.respondCh <- discordgo.InteractionResponseData{Files: []*discordgo.File{{Reader: r, Name: name}}} - return nil + return c.respond(discordgo.InteractionResponseData{Files: []*discordgo.File{{Reader: r, Name: name}}}) } func (c *Context) isCommand() bool { diff --git a/cmds/discord/rest/client.go b/cmds/discord/rest/client.go index a58c9061..2802913c 100644 --- a/cmds/discord/rest/client.go +++ b/cmds/discord/rest/client.go @@ -11,6 +11,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/rs/zerolog/log" "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/internal/retry" @@ -110,6 +111,7 @@ func partHeader(contentDisposition string, contentType string) textproto.MIMEHea } func (c *Client) do(req *http.Request, target any) error { + log.Debug().Str("url", req.URL.String()).Str("contentType", req.Header.Get("Content-Type")).Msg("sending a request to Discord API") result := retry.Retry(func() (any, error) { res, err := c.http.Do(req) if err != nil { @@ -127,9 +129,8 @@ func (c *Client) do(req *http.Request, target any) error { _ = json.Unmarshal(data, &body) message := body.Message if message == "" { - message = res.Status + message = res.Status + ", response was not valid json" } - println(string(data)) return nil, errors.New("discord error: " + message) @@ -145,7 +146,12 @@ func (c *Client) do(req *http.Request, target any) error { return nil, nil }, 3, time.Millisecond*150) - return result.Err + if result.Err != nil { + log.Debug().Err(result.Err).Str("url", req.URL.String()).Str("contentType", req.Header.Get("Content-Type")).Msg("request finished with error") + return result.Err + } + log.Debug().Str("url", req.URL.String()).Str("contentType", req.Header.Get("Content-Type")).Msg("request finished successfully") + return nil } func (c *Client) lookupApplicationID() (string, error) { diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 142782a0..7d91f37a 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -20,9 +20,6 @@ import ( "github.com/rs/zerolog/log" ) -// we need to respond within 15 minutes of the initial interaction -var interactionTimeout = time.Minute*14 + time.Second*45 - type interactionState struct { mx *sync.Mutex acked bool @@ -163,18 +160,17 @@ func (r *Router) routeInteraction(interaction discordgo.Interaction) (*builder.C } func (r *Router) handleInteraction(ctx context.Context, cancel context.CancelFunc, interaction discordgo.Interaction, command *builder.Command, reply chan<- discordgo.InteractionResponseData) { - defer func() { - if r := recover(); r != nil { - log.Error().Interface("error", r).Msg("interaction handler panic") - reply <- discordgo.InteractionResponseData{Content: "Something went really wrong while working on your command. Please try again later."} - } - cancel() - }() + defer cancel() cCtx, err := common.NewContext(ctx, interaction, reply, r.restClient, r.core) if err != nil { log.Err(err).Msg("failed to create a common.Context for a handler") - reply <- discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")} + select { + case <-ctx.Done(): + return + default: + reply <- discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")} + } return } @@ -186,7 +182,12 @@ func (r *Router) handleInteraction(ctx context.Context, cancel context.CancelFun err = handler(cCtx) if err != nil { log.Err(err).Msg("handler returned an error") - reply <- discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")} + select { + case <-ctx.Done(): + return + default: + reply <- discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")} + } return } } @@ -202,12 +203,9 @@ func sendPingReply(w http.ResponseWriter) { func (router *Router) startInteractionHandlerAsync(interaction discordgo.Interaction, state *interactionState, command *builder.Command) { log.Info().Str("id", interaction.ID).Msg("started handling an interaction") - // create a timer for the interaction response - responseTimer := time.NewTimer(interactionTimeout) - defer responseTimer.Stop() - // create a new context and cancel it on reply - workerCtx, cancelWorker := context.WithCancel(context.Background()) + // we need to respond within 15 minutes, but it's safe to assume something failed if we did not reply quickly + workerCtx, cancelWorker := context.WithTimeout(context.Background(), time.Second*10) // handle the interaction responseCh := make(chan discordgo.InteractionResponseData) @@ -215,21 +213,15 @@ func (router *Router) startInteractionHandlerAsync(interaction discordgo.Interac go func() { defer close(responseCh) + defer cancelWorker() for { select { - case <-responseTimer.C: - state.mx.Lock() - // discord will handle the user-facing error, so we just log it - log.Error().Any("interaction", interaction).Msg("interaction response timed out") - defer state.mx.Unlock() - return - case <-workerCtx.Done(): state.mx.Lock() + defer state.mx.Unlock() // we are done, there is nothing else we should do here // lock in case responseCh is still busy sending some data over - defer state.mx.Unlock() log.Info().Str("id", interaction.ID).Msg("finished handling an interaction") return diff --git a/internal/logic/sessions.go b/internal/logic/sessions.go index 3630b1a3..d5e75343 100644 --- a/internal/logic/sessions.go +++ b/internal/logic/sessions.go @@ -138,7 +138,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } vehicleStats := fetch.WargamingVehiclesToFrame(vehicles) for id, vehicle := range vehicleStats { - if vehicle.LastBattleTime.IsZero() { + if vehicle.LastBattleTime.Unix() < 1 { // vehicle was never played continue } diff --git a/internal/stats/errors.go b/internal/stats/errors.go index be6b8daa..4bcd0ddf 100644 --- a/internal/stats/errors.go +++ b/internal/stats/errors.go @@ -1,6 +1,8 @@ package stats -import "github.com/pkg/errors" +import ( + "github.com/pkg/errors" +) var ( ErrAccountNotTracked = errors.New("account not tracked") diff --git a/internal/stats/fetch/client.go b/internal/stats/fetch/client.go index 60e65777..7844d93f 100644 --- a/internal/stats/fetch/client.go +++ b/internal/stats/fetch/client.go @@ -52,7 +52,7 @@ type Client interface { CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) PeriodStats(ctx context.Context, id string, from time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) - SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) + SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, AccountStatsOverPeriod, error) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) } diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index fb95ad9d..f01ab0be 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -264,7 +264,7 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt return stats, nil } -func (c *multiSourceClient) SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) { +func (c *multiSourceClient) SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, AccountStatsOverPeriod, error) { var options statsOptions for _, apply := range opts { apply(&options) @@ -276,7 +276,7 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session sessionStart = time.Now() } if days := time.Since(sessionStart).Hours() / 24; days > 90 { - return AccountStatsOverPeriod{}, ErrInvalidSessionStart + return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, ErrInvalidSessionStart } sessionBefore := sessionStart @@ -322,35 +322,54 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session // wait for all requests to finish and check errors group.Wait() if current.Err != nil { - return AccountStatsOverPeriod{}, current.Err + return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, current.Err } if accountSnapshot.Err != nil { if db.IsErrNotFound(accountSnapshot.Err) { - return AccountStatsOverPeriod{}, ErrSessionNotFound + return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, ErrSessionNotFound } - return AccountStatsOverPeriod{}, accountSnapshot.Err + return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, accountSnapshot.Err } if averages.Err != nil { // not critical, this will only affect WN8 log.Err(averages.Err).Msg("failed to get tank averages") } - stats := current.Data - stats.PeriodEnd = time.Now() - stats.PeriodStart = sessionStart - stats.RatingBattles.StatsFrame.Subtract(accountSnapshot.Data.RatingBattles) - stats.RegularBattles.StatsFrame.Subtract(accountSnapshot.Data.RegularBattles) + session := current.Data + session.PeriodEnd = time.Now() + session.PeriodStart = sessionStart + session.RatingBattles.StatsFrame.Subtract(accountSnapshot.Data.RatingBattles) + session.RegularBattles.StatsFrame.Subtract(accountSnapshot.Data.RegularBattles) + session.RegularBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, len(current.Data.RegularBattles.Vehicles)) + + career := AccountStatsOverPeriod{ + Realm: current.Data.Realm, + Account: current.Data.Account, + LastBattleTime: accountSnapshot.Data.LastBattleTime, + PeriodStart: current.Data.Account.CreatedAt, + PeriodEnd: sessionStart, + } + career.Account.LastBattleTime = accountSnapshot.Data.LastBattleTime + career.RatingBattles.StatsFrame = accountSnapshot.Data.RatingBattles + career.RegularBattles.StatsFrame = accountSnapshot.Data.RegularBattles + career.RatingBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, 0) + career.RegularBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, len(vehiclesSnapshots.Data)) snapshotsMap := make(map[string]int, len(vehiclesSnapshots.Data)) for i, data := range vehiclesSnapshots.Data { snapshotsMap[data.VehicleID] = i + + career.RegularBattles.Vehicles[data.VehicleID] = frame.VehicleStatsFrame{ + LastBattleTime: data.LastBattleTime, + VehicleID: data.VehicleID, + StatsFrame: &data.Stats, + } } - stats.RegularBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, len(current.Data.RegularBattles.Vehicles)) for id, current := range current.Data.RegularBattles.Vehicles { snapshotIndex, exists := snapshotsMap[id] if !exists { - stats.RegularBattles.Vehicles[id] = current + session.RegularBattles.Vehicles[id] = current continue } @@ -359,13 +378,15 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session continue } current.StatsFrame.Subtract(snapshot.Stats) - stats.RegularBattles.Vehicles[id] = current + session.RegularBattles.Vehicles[id] = current } if options.withWN8 { - stats.AddWN8(averages.Data) + career.AddWN8(averages.Data) + session.AddWN8(averages.Data) } - return stats, nil + + return session, career, nil } diff --git a/internal/stats/metadata.go b/internal/stats/metadata.go index 498ab10a..78c6a2a8 100644 --- a/internal/stats/metadata.go +++ b/internal/stats/metadata.go @@ -7,7 +7,7 @@ import ( ) type Metadata struct { - Stats fetch.AccountStatsOverPeriod + Stats map[string]fetch.AccountStatsOverPeriod Timings map[string]time.Duration timers map[string]time.Time } diff --git a/internal/stats/period.go b/internal/stats/period.go index f99c7749..96b518f7 100644 --- a/internal/stats/period.go +++ b/internal/stats/period.go @@ -7,13 +7,14 @@ import ( "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/prepare/common" prepare "github.com/cufee/aftermath/internal/stats/prepare/period" options "github.com/cufee/aftermath/internal/stats/render" render "github.com/cufee/aftermath/internal/stats/render/period" ) func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, opts ...options.Option) (Image, Metadata, error) { - meta := Metadata{} + meta := Metadata{Stats: make(map[string]fetch.AccountStatsOverPeriod)} printer, err := localization.NewPrinter("stats", r.locale) if err != nil { @@ -26,7 +27,7 @@ func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, if err != nil { return nil, meta, err } - meta.Stats = stats + meta.Stats["period"] = stats stop = meta.Timer("prepare#GetVehicles") var vehicles []string @@ -46,7 +47,7 @@ func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, stop() stop = meta.Timer("prepare#NewCards") - cards, err := prepare.NewCards(stats, glossary, prepare.WithPrinter(printer, r.locale)) + cards, err := prepare.NewCards(stats, glossary, common.WithPrinter(printer, r.locale)) stop() if err != nil { return nil, meta, err diff --git a/internal/stats/prepare/common/card.go b/internal/stats/prepare/common/card.go index bfae299f..f9826112 100644 --- a/internal/stats/prepare/common/card.go +++ b/internal/stats/prepare/common/card.go @@ -6,18 +6,18 @@ import ( "github.com/cufee/aftermath/internal/stats/frame" ) -type cardType string +type CardType string const ( - CardTypeVehicle cardType = "vehicle" - CardTypeRatingVehicle cardType = "ratingVehicle" - CardTypeOverview cardType = "overview" - CardTypeHighlight cardType = "overview" - CardTypeTierPercentage cardType = "tierPercentage" + CardTypeVehicle CardType = "vehicle" + CardTypeRatingVehicle CardType = "ratingVehicle" + CardTypeOverview CardType = "overview" + CardTypeHighlight CardType = "overview" + CardTypeTierPercentage CardType = "tierPercentage" ) type StatsCard[B, M any] struct { - Type cardType `json:"type"` + Type CardType `json:"type"` Title string `json:"title"` Blocks []B `json:"blocks"` Meta M `json:"meta,omitempty"` @@ -39,31 +39,40 @@ func (block *StatsBlock[D]) Localize(printer func(string) string) { } func (block *StatsBlock[D]) FillValue(stats frame.StatsFrame, args ...any) error { - switch block.Tag { + value, err := PresetValue(block.Tag, stats) + if err != nil { + return err + } + block.Value = value + return nil +} + +func PresetValue(preset Tag, stats frame.StatsFrame, args ...any) (frame.Value, error) { + switch preset { case TagWN8: - block.Value = stats.WN8(args...) + return stats.WN8(args...), nil case TagFrags: - block.Value = stats.Frags + return stats.Frags, nil case TagBattles: - block.Value = stats.Battles + return stats.Battles, nil case TagWinrate: - block.Value = stats.WinRate(args...) + return stats.WinRate(args...), nil case TagAccuracy: - block.Value = stats.Accuracy(args...) + return stats.Accuracy(args...), nil case TagRankedRating: - block.Value = stats.Rating + return stats.Rating, nil case TagAvgDamage: - block.Value = stats.AvgDamage(args...) + return stats.AvgDamage(args...), nil case TagDamageRatio: - block.Value = stats.DamageRatio(args...) + return stats.DamageRatio(args...), nil case TagSurvivalRatio: - block.Value = stats.SurvivalRatio(args...) + return stats.SurvivalRatio(args...), nil case TagSurvivalPercent: - block.Value = stats.Survival(args...) + return stats.Survival(args...), nil case TagDamageDealt: - block.Value = stats.DamageDealt + return stats.DamageDealt, nil case TagDamageTaken: - block.Value = stats.DamageReceived + return stats.DamageReceived, nil // Some tags cannot be parsed here and should be implemented by the package // TagAvgTier - period @@ -71,9 +80,6 @@ func (block *StatsBlock[D]) FillValue(stats frame.StatsFrame, args ...any) error // TagDamageAssisted - replay // TagDamageAssistedCombined - replay default: - return errors.New("invalid preset") + return frame.InvalidValue, errors.New("invalid preset " + preset.String()) } - - return nil - } diff --git a/internal/stats/prepare/common/highlights.go b/internal/stats/prepare/common/highlights.go new file mode 100644 index 00000000..451aad56 --- /dev/null +++ b/internal/stats/prepare/common/highlights.go @@ -0,0 +1,62 @@ +package common + +import ( + "github.com/cufee/aftermath/internal/stats/frame" +) + +type Highlight struct { + CompareWith Tag + Blocks []Tag + Label string +} + +var ( + HighlightAvgDamage = Highlight{TagAvgDamage, []Tag{TagBattles, TagAvgDamage,TagWN8}, "label_highlight_avg_damage"} + HighlightBattles = Highlight{TagBattles, []Tag{TagBattles, TagAvgDamage, TagWN8}, "label_highlight_battles"} + HighlightWN8 = Highlight{TagWN8, []Tag{TagBattles, TagAvgDamage,TagWN8}, "label_highlight_wn8"} +) + +type highlightedVehicle struct { + Highlight Highlight + Vehicle frame.VehicleStatsFrame + Value frame.Value +} + +func GetHighlightedVehicles(highlights []Highlight, vehicles map[string]frame.VehicleStatsFrame, minBattles int) ([]highlightedVehicle, error) { + leadersMap := make(map[string]highlightedVehicle) + for _, vehicle := range vehicles { + if int(vehicle.Battles.Float()) < minBattles { + continue + } + + for _, highlight := range highlights { + currentLeader, leaderExists := leadersMap[highlight.Label] + value, err := PresetValue(highlight.CompareWith, *vehicle.StatsFrame) + if err != nil { + return nil, err + } + + if !frame.InvalidValue.Equals(value) && (!leaderExists || value.Float() > currentLeader.Value.Float()) { + currentLeader.Highlight = highlight + currentLeader.Vehicle = vehicle + currentLeader.Value = value + leadersMap[highlight.Label] = currentLeader + } + } + } + + nominateVehicles := make(map[string]struct{}) + var highlightedVehicles []highlightedVehicle + for _, highlight := range highlights { + leader, leaderExists := leadersMap[highlight.Label] + if !leaderExists { + continue + } + if _, nominated := nominateVehicles[leader.Vehicle.VehicleID]; nominated { + continue + } + highlightedVehicles = append(highlightedVehicles, leader) + nominateVehicles[leader.Vehicle.VehicleID] = struct{}{} + } + return highlightedVehicles, nil +} diff --git a/internal/stats/prepare/common/options.go b/internal/stats/prepare/common/options.go new file mode 100644 index 00000000..e27fba12 --- /dev/null +++ b/internal/stats/prepare/common/options.go @@ -0,0 +1,30 @@ +package common + +import "golang.org/x/text/language" + +var DefaultOptions = options{} + +type options struct { + localePrinter func(string) string + locale *language.Tag +} + +func (o options) Printer() func(s string) string { + if o.localePrinter != nil { + return o.localePrinter + } + return func(s string) string { return s } +} + +func (o options) Locale() language.Tag { + if o.locale != nil { + return *o.locale + } + return language.English +} + +type Option func(*options) + +func WithPrinter(printer func(string) string, locale language.Tag) func(*options) { + return func(o *options) { o.localePrinter = printer; o.locale = &locale } +} diff --git a/internal/stats/prepare/period/card.go b/internal/stats/prepare/period/card.go index 1b43c812..65488820 100644 --- a/internal/stats/prepare/period/card.go +++ b/internal/stats/prepare/period/card.go @@ -9,8 +9,8 @@ import ( "github.com/cufee/aftermath/internal/stats/prepare/common" ) -func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.Vehicle, opts ...Option) (Cards, error) { - options := defaultOptions +func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.Vehicle, opts ...common.Option) (Cards, error) { + options := common.DefaultOptions for _, apply := range opts { apply(&options) } @@ -21,15 +21,20 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.V var cards Cards // Overview Card - for _, column := range selectedBlocks { + for _, column := range overviewBlocks { var columnBlocks []common.StatsBlock[BlockData] for _, preset := range column { - block := presetToBlock(preset, stats.RegularBattles.StatsFrame) + var block common.StatsBlock[BlockData] if preset == TagAvgTier { block = avgTierBlock(stats.RegularBattles.Vehicles, glossary) + } else { + b, err := presetToBlock(preset, stats.RegularBattles.StatsFrame) + if err != nil { + return Cards{}, err + } + block = b } - - block.Localize(options.localePrinter) + block.Localize(options.Printer()) columnBlocks = append(columnBlocks, block) } @@ -37,7 +42,7 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.V cards.Overview.Blocks = append(cards.Overview.Blocks, columnBlocks) } - if len(stats.RegularBattles.Vehicles) < 1 || len(selectedHighlights) < 1 { + if len(stats.RegularBattles.Vehicles) < 1 || len(highlights) < 1 { return cards, nil } @@ -45,7 +50,7 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.V var minimumBattles float64 = 5 periodDays := stats.PeriodEnd.Sub(stats.PeriodStart).Hours() / 24 withFallback := func(battles float64) float64 { - return math.Min(battles, float64(stats.RegularBattles.Battles.Float())/float64(len(selectedHighlights))) + return math.Min(battles, float64(stats.RegularBattles.Battles.Float())/float64(len(highlights))) } if periodDays > 90 { minimumBattles = withFallback(100) @@ -59,24 +64,31 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.V minimumBattles = withFallback(10) } - highlightedVehicles := getHighlightedVehicles(selectedHighlights, stats.RegularBattles.Vehicles, int(minimumBattles)) + highlightedVehicles, err := common.GetHighlightedVehicles(highlights, stats.RegularBattles.Vehicles, int(minimumBattles)) + if err != nil { + return Cards{}, err + } + for _, data := range highlightedVehicles { var vehicleBlocks []common.StatsBlock[BlockData] - for _, preset := range data.highlight.blocks { - block := presetToBlock(preset, *data.vehicle.StatsFrame) - block.Localize(options.localePrinter) + for _, preset := range data.Highlight.Blocks { + block, err := presetToBlock(preset, *data.Vehicle.StatsFrame) + if err != nil { + return Cards{}, err + } + block.Localize(options.Printer()) vehicleBlocks = append(vehicleBlocks, block) } - glossary := glossary[data.vehicle.VehicleID] - glossary.ID = data.vehicle.VehicleID + glossary := glossary[data.Vehicle.VehicleID] + glossary.ID = data.Vehicle.VehicleID cards.Highlights = append(cards.Highlights, VehicleCard{ - Title: fmt.Sprintf("%s %s", common.IntToRoman(glossary.Tier), glossary.Name(options.locale)), + Title: fmt.Sprintf("%s %s", common.IntToRoman(glossary.Tier), glossary.Name(options.Locale())), + Meta: options.Printer()(data.Highlight.Label), Type: common.CardTypeVehicle, Blocks: vehicleBlocks, - Meta: options.localePrinter(data.highlight.label), }) } diff --git a/internal/stats/prepare/period/constants.go b/internal/stats/prepare/period/constants.go index 0997fa59..7b4a70c2 100644 --- a/internal/stats/prepare/period/constants.go +++ b/internal/stats/prepare/period/constants.go @@ -6,8 +6,8 @@ import ( const TagAvgTier common.Tag = "avg_tier" -var selectedBlocks = [][]common.Tag{{common.TagDamageRatio, common.TagAvgDamage, common.TagAccuracy}, {common.TagWN8, common.TagBattles}, {TagAvgTier, common.TagWinrate, common.TagSurvivalPercent}} -var selectedHighlights = []highlight{HighlightBattles, HighlightWN8, HighlightAvgDamage} +var overviewBlocks = [][]common.Tag{{common.TagDamageRatio, common.TagAvgDamage, common.TagAccuracy}, {common.TagWN8, common.TagBattles}, {TagAvgTier, common.TagWinrate, common.TagSurvivalPercent}} +var highlights = []common.Highlight{common.HighlightBattles, common.HighlightWN8, common.HighlightAvgDamage} type Cards struct { Overview OverviewCard `json:"overview"` diff --git a/internal/stats/prepare/period/highlight.go b/internal/stats/prepare/period/highlight.go deleted file mode 100644 index 003a274e..00000000 --- a/internal/stats/prepare/period/highlight.go +++ /dev/null @@ -1,60 +0,0 @@ -package period - -import ( - "github.com/cufee/aftermath/internal/stats/frame" - "github.com/cufee/aftermath/internal/stats/prepare/common" -) - -type highlight struct { - compareWith common.Tag - blocks []common.Tag - label string -} - -var ( - HighlightAvgDamage = highlight{common.TagAvgDamage, []common.Tag{common.TagBattles, common.TagAvgDamage, common.TagWN8}, "label_highlight_avg_damage"} - HighlightBattles = highlight{common.TagBattles, []common.Tag{common.TagBattles, common.TagAvgDamage, common.TagWN8}, "label_highlight_battles"} - HighlightWN8 = highlight{common.TagWN8, []common.Tag{common.TagBattles, common.TagAvgDamage, common.TagWN8}, "label_highlight_wn8"} -) - -type highlightedVehicle struct { - highlight highlight - vehicle frame.VehicleStatsFrame - value frame.Value -} - -func getHighlightedVehicles(highlights []highlight, vehicles map[string]frame.VehicleStatsFrame, minBattles int) []highlightedVehicle { - leadersMap := make(map[string]highlightedVehicle) - for _, vehicle := range vehicles { - if int(vehicle.Battles.Float()) < minBattles { - continue - } - - for _, highlight := range highlights { - currentLeader, leaderExists := leadersMap[highlight.label] - block := presetToBlock(highlight.compareWith, *vehicle.StatsFrame) - - if !frame.InvalidValue.Equals(block.Value) && (!leaderExists || block.Value.Float() > currentLeader.value.Float()) { - currentLeader.highlight = highlight - currentLeader.value = block.Value - currentLeader.vehicle = vehicle - leadersMap[highlight.label] = currentLeader - } - } - } - - nominateVehicles := make(map[string]struct{}) - var highlightedVehicles []highlightedVehicle - for _, highlight := range highlights { - leader, leaderExists := leadersMap[highlight.label] - if !leaderExists { - continue - } - if _, nominated := nominateVehicles[leader.vehicle.VehicleID]; nominated { - continue - } - highlightedVehicles = append(highlightedVehicles, leader) - nominateVehicles[leader.vehicle.VehicleID] = struct{}{} - } - return highlightedVehicles -} diff --git a/internal/stats/prepare/period/options.go b/internal/stats/prepare/period/options.go deleted file mode 100644 index a7a3f1d8..00000000 --- a/internal/stats/prepare/period/options.go +++ /dev/null @@ -1,16 +0,0 @@ -package period - -import "golang.org/x/text/language" - -var defaultOptions = options{localePrinter: func(s string) string { return s }, locale: language.English} - -type options struct { - localePrinter func(string) string - locale language.Tag -} - -type Option func(*options) - -func WithPrinter(printer func(string) string, locale language.Tag) func(*options) { - return func(o *options) { o.localePrinter = printer; o.locale = locale } -} diff --git a/internal/stats/prepare/period/preset.go b/internal/stats/prepare/period/preset.go index 781aaaec..2251369b 100644 --- a/internal/stats/prepare/period/preset.go +++ b/internal/stats/prepare/period/preset.go @@ -5,10 +5,9 @@ import ( "github.com/cufee/aftermath/internal/stats/prepare/common" ) -func presetToBlock(preset common.Tag, stats frame.StatsFrame) common.StatsBlock[BlockData] { +func presetToBlock(preset common.Tag, stats frame.StatsFrame) (common.StatsBlock[BlockData], error) { block := common.StatsBlock[BlockData](common.NewBlock(preset, BlockData{})) - var args []any switch preset { case common.TagWN8: block.Data.Flavor = BlockFlavorSpecial @@ -32,6 +31,6 @@ func presetToBlock(preset common.Tag, stats frame.StatsFrame) common.StatsBlock[ block.Data.Flavor = BlockFlavorDefault } - block.FillValue(stats, args...) - return block + err := block.FillValue(stats) + return block, err } diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go new file mode 100644 index 00000000..81d3bd45 --- /dev/null +++ b/internal/stats/prepare/session/card.go @@ -0,0 +1,152 @@ +package session + +import ( + "fmt" + + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/frame" + "github.com/cufee/aftermath/internal/stats/prepare/common" + "golang.org/x/text/language" +) + +func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string]database.Vehicle, opts ...common.Option) (Cards, error) { + options := common.DefaultOptions + for _, apply := range opts { + apply(&options) + } + if glossary == nil { + glossary = make(map[string]database.Vehicle) + } + + var cards Cards + + // Rating battles overview + if session.RatingBattles.Battles > 0 { + card, err := makeOverviewCard( + ratingOverviewBlocks, + session.RatingBattles.StatsFrame, + career.RatingBattles.StatsFrame, + "label_overview_rating", + options.Printer(), + func(t common.Tag) common.Tag { + if t == common.TagWN8 { + return common.TagRankedRating + } + return t + }, + ) + if err != nil { + return Cards{}, err + } + cards.Rating.Overview = card + } + // Regular battles overview + if session.RegularBattles.Battles > 0 { + card, err := makeOverviewCard( + unratedOverviewBlocks, + session.RatingBattles.StatsFrame, + career.RatingBattles.StatsFrame, + "label_overview_unrated", + options.Printer(), + nil, + ) + if err != nil { + return Cards{}, err + } + cards.Unrated.Overview = card + } + + // Rating battles vehicles + for id, data := range session.RatingBattles.Vehicles { + var careerFrame frame.StatsFrame + if frame, ok := career.RatingBattles.Vehicles[id]; ok { + careerFrame = *frame.StatsFrame + } + + glossary := glossary[id] + glossary.ID = id + + card, err := makeVehicleCard( + []common.Tag{common.TagWN8}, + common.CardTypeRatingVehicle, + *data.StatsFrame, + careerFrame, + options.Printer(), + options.Locale(), + glossary, + ) + if err != nil { + return Cards{}, err + } + cards.Rating.Vehicles = append(cards.Rating.Vehicles, card) + } + // Regular battles vehicles + for id, data := range session.RegularBattles.Vehicles { + var careerFrame frame.StatsFrame + if frame, ok := career.RegularBattles.Vehicles[id]; ok { + careerFrame = *frame.StatsFrame + } + glossary := glossary[id] + glossary.ID = id + + card, err := makeVehicleCard( + vehicleBlocks, + common.CardTypeVehicle, + *data.StatsFrame, + careerFrame, + options.Printer(), + options.Locale(), + glossary, + ) + if err != nil { + return Cards{}, err + } + cards.Unrated.Vehicles = append(cards.Unrated.Vehicles, card) + } + + return cards, nil +} + +func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, career frame.StatsFrame, printer func(string) string, locale language.Tag, glossary database.Vehicle) (VehicleCard, error) { + var blocks []common.StatsBlock[BlockData] + for _, preset := range presets { + block, err := presetToBlock(preset, session, career) + if err != nil { + return VehicleCard{}, err + } + block.Localize(printer) + blocks = append(blocks, block) + } + + return VehicleCard{ + Title: fmt.Sprintf("%s %s", common.IntToRoman(glossary.Tier), glossary.Name(locale)), + Type: cardType, + Blocks: blocks, + }, nil +} + +func makeOverviewCard(columns [][]common.Tag, session, career frame.StatsFrame, label string, printer func(string) string, replace func(common.Tag) common.Tag) (OverviewCard, error) { + var blocks [][]common.StatsBlock[BlockData] + for _, presets := range columns { + var column []common.StatsBlock[BlockData] + for _, p := range presets { + preset := p + if replace != nil { + preset = replace(p) + } + block, err := presetToBlock(preset, session, career) + if err != nil { + return OverviewCard{}, err + } + block.Localize(printer) + column = append(column, block) + } + blocks = append(blocks, column) + } + return OverviewCard{ + Type: common.CardTypeOverview, + Title: printer(label), + Blocks: blocks, + }, nil +} diff --git a/internal/stats/prepare/session/constants.go b/internal/stats/prepare/session/constants.go new file mode 100644 index 00000000..c49fa6b2 --- /dev/null +++ b/internal/stats/prepare/session/constants.go @@ -0,0 +1,35 @@ +package session + +import ( + "github.com/cufee/aftermath/internal/stats/frame" + "github.com/cufee/aftermath/internal/stats/prepare/common" +) + +var unratedOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagAvgDamage}, {common.TagWinrate, common.TagDamageRatio}, {common.TagWN8}} +var ratingOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagAvgDamage}, {common.TagWinrate, common.TagDamageRatio}, {common.TagRankedRating}} +var vehicleBlocks = []common.Tag{common.TagBattles, common.TagAvgDamage, common.TagDamageRatio, common.TagWinrate, common.TagWN8} +var highlights = []common.Highlight{common.HighlightBattles, common.HighlightWN8, common.HighlightAvgDamage} + +type Cards struct { + Unrated UnratedCards `json:"unrated"` + Rating RatingCards `json:"rating"` +} + +type UnratedCards struct { + Overview OverviewCard + Vehicles []VehicleCard + Highlights []VehicleCard +} + +type RatingCards struct { + Overview OverviewCard + Vehicles []VehicleCard +} + +type OverviewCard common.StatsCard[[]common.StatsBlock[BlockData], string] +type VehicleCard common.StatsCard[common.StatsBlock[BlockData], string] + +type BlockData struct { + Session frame.Value + Career frame.Value +} diff --git a/internal/stats/prepare/session/preset.go b/internal/stats/prepare/session/preset.go new file mode 100644 index 00000000..5c496b0c --- /dev/null +++ b/internal/stats/prepare/session/preset.go @@ -0,0 +1,28 @@ +package session + +import ( + "github.com/cufee/aftermath/internal/stats/frame" + "github.com/cufee/aftermath/internal/stats/prepare/common" +) + +func presetToBlock(preset common.Tag, session, career frame.StatsFrame) (common.StatsBlock[BlockData], error) { + // create blocks from stats + // this module has no special blocks, so we can just use the common.Block#FillValue + sessionBlock := common.StatsBlock[BlockData](common.NewBlock(preset, BlockData{})) + err := sessionBlock.FillValue(session) + if err != nil { + return common.StatsBlock[BlockData]{}, nil + } + careerBlock := common.StatsBlock[BlockData](common.NewBlock(preset, BlockData{})) + err = careerBlock.FillValue(career) + if err != nil { + return common.StatsBlock[BlockData]{}, nil + } + + block := common.StatsBlock[BlockData](common.NewBlock(preset, BlockData{ + Session: sessionBlock.Value, + Career: careerBlock.Value, + })) + block.Value = sessionBlock.Value + return block, nil +} diff --git a/internal/stats/render/common/header.go b/internal/stats/render/common/header.go new file mode 100644 index 00000000..e28f925b --- /dev/null +++ b/internal/stats/render/common/header.go @@ -0,0 +1,47 @@ +package common + +import ( + "github.com/cufee/aftermath/internal/database" +) + +func NewHeaderCard(width float64, subscriptions []database.UserSubscription, promoText []string) (Block, bool) { + var cards []Block + + var addPromoText = len(promoText) > 0 + for _, sub := range subscriptions { + switch sub.Type { + case database.SubscriptionTypePro, database.SubscriptionTypePlus, database.SubscriptionTypeDeveloper: + addPromoText = false + } + if !addPromoText { + break + } + } + + if addPromoText { + // Users without a subscription get promo text + var textBlocks []Block + for _, text := range promoText { + textBlocks = append(textBlocks, NewTextContent(Style{Font: &FontMedium, FontColor: TextPrimary}, text)) + } + cards = append(cards, NewBlocksContent(Style{ + Direction: DirectionVertical, + AlignItems: AlignItemsCenter, + }, + textBlocks..., + )) + } + + // User Subscription Badge and promo text + if badges, _ := SubscriptionsBadges(subscriptions); len(badges) > 0 { + cards = append(cards, NewBlocksContent(Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, Gap: 10}, + badges..., + )) + } + + if len(cards) < 1 { + return Block{}, false + } + + return NewBlocksContent(Style{Direction: DirectionVertical, AlignItems: AlignItemsCenter, JustifyContent: JustifyContentCenter, Gap: 10, Width: width}, cards...), true +} diff --git a/internal/stats/render/period/cards.go b/internal/stats/render/period/cards.go index 107a5f02..57918453 100644 --- a/internal/stats/render/period/cards.go +++ b/internal/stats/render/period/cards.go @@ -110,7 +110,7 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs } // Header card - if headerCard, headerCardExists := newHeaderCard(cardWidth, subs, opts); headerCardExists { + if headerCard, headerCardExists := common.NewHeaderCard(cardWidth, subs, opts.PromoText); headerCardExists { segments.AddHeader(headerCard) } @@ -138,48 +138,6 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs return segments, nil } -func newHeaderCard(width float64, subscriptions []database.UserSubscription, options render.Options) (common.Block, bool) { - var cards []common.Block - - var addPromoText = true - for _, sub := range subscriptions { - switch sub.Type { - case database.SubscriptionTypePro, database.SubscriptionTypePlus, database.SubscriptionTypeDeveloper: - addPromoText = false - } - if !addPromoText { - break - } - } - - if addPromoText && options.PromoText != nil { - // Users without a subscription get promo text - var textBlocks []common.Block - for _, text := range options.PromoText { - textBlocks = append(textBlocks, common.NewTextContent(common.Style{Font: &common.FontMedium, FontColor: common.TextPrimary}, text)) - } - cards = append(cards, common.NewBlocksContent(common.Style{ - Direction: common.DirectionVertical, - AlignItems: common.AlignItemsCenter, - }, - textBlocks..., - )) - } - - // User Subscription Badge and promo text - if badges, _ := common.SubscriptionsBadges(subscriptions); len(badges) > 0 { - cards = append(cards, common.NewBlocksContent(common.Style{Direction: common.DirectionHorizontal, AlignItems: common.AlignItemsCenter, Gap: 10}, - badges..., - )) - } - - if len(cards) < 1 { - return common.Block{}, false - } - - return common.NewBlocksContent(common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, JustifyContent: common.JustifyContentCenter, Gap: 10, Width: width}, cards...), true -} - func newHighlightCard(style highlightStyle, card period.VehicleCard) common.Block { titleBlock := common.NewBlocksContent(common.Style{ diff --git a/internal/stats/render/period/render.go b/internal/stats/render/period/image.go similarity index 100% rename from internal/stats/render/period/render.go rename to internal/stats/render/period/image.go diff --git a/internal/stats/render/session/blocks.go b/internal/stats/render/session/blocks.go new file mode 100644 index 00000000..85ea47e8 --- /dev/null +++ b/internal/stats/render/session/blocks.go @@ -0,0 +1,145 @@ +package session + +// type convertOptions struct { +// showSessionStats bool +// showCareerStats bool +// showLabels bool +// showIcons bool +// highlightBlockIndex int +// } + +// func newVehicleCard(style render.Style, card session.Card, sizes map[int]float64, opts convertOptions) (render.Block, error) { +// if card.Type == dataprep.CardTypeRatingVehicle { +// return slimVehicleCard(style, card, sizes, opts) +// } + +// return defaultVehicleCard(style, card, sizes, opts) +// } + +// func defaultVehicleCard(style render.Style, card session.Card, sizes map[int]float64, opts convertOptions) (render.Block, error) { +// blocks, err := statsBlocksToCardBlocks(card.Blocks, sizes, opts) +// if err != nil { +// return render.Block{}, err +// } + +// cardContentBlocks := []render.Block{newCardTitle(card.Title)} +// contentWidth := style.Width - style.PaddingX*2 + +// statsRowBlock := render.NewBlocksContent(statsRowStyle(contentWidth), blocks...) +// cardContentBlocks = append(cardContentBlocks, statsRowBlock) + +// return render.NewBlocksContent(style, cardContentBlocks...), nil +// } + +// func slimVehicleCard(style render.Style, card session.Card, sizes map[int]float64, opts convertOptions) (render.Block, error) { +// opts.highlightBlockIndex = -1 +// opts.showCareerStats = false +// opts.showLabels = false +// opts.showIcons = true + +// blocks, err := statsBlocksToCardBlocks(card.Blocks, sizes, opts) +// if err != nil { +// return render.Block{}, err +// } + +// titleBlock := render.NewTextContent(ratingVehicleTitleStyle, card.Title) +// statsRowBlock := render.NewBlocksContent(statsRowStyle(0), blocks...) + +// containerStyle := style +// containerStyle.Direction = render.DirectionHorizontal +// containerStyle.JustifyContent = render.JustifyContentSpaceBetween + +// return render.NewBlocksContent(containerStyle, titleBlock, statsRowBlock), nil +// } + +// func statsBlocksToCardBlocks(stats []session.StatsBlock, blockWidth map[int]float64, opts ...convertOptions) ([]render.Block, error) { +// var options convertOptions = convertOptions{ +// showSessionStats: true, +// showCareerStats: true, +// showLabels: true, +// showIcons: true, +// highlightBlockIndex: 0, +// } +// if len(opts) > 0 { +// options = opts[0] +// } + +// var content []render.Block +// for index, statsBlock := range stats { +// blocks := make([]render.Block, 0, 3) +// if options.showSessionStats { +// if options.showIcons && statsBlock.Tag != dataprep.TagBattles { +// blocks = append(blocks, newStatsBlockRow(defaultBlockStyle.session, statsBlock.Session.String, comparisonIconFromBlock(statsBlock))) +// } else { +// blocks = append(blocks, render.NewTextContent(defaultBlockStyle.session, statsBlock.Session.String)) +// } +// } +// if options.showCareerStats && statsBlock.Career.String != "" { +// if options.showIcons && statsBlock.Tag != dataprep.TagBattles { +// blocks = append(blocks, newStatsBlockRow(defaultBlockStyle.career, statsBlock.Career.String, blockToWN8Icon(statsBlock.Career, statsBlock.Tag))) +// } else { +// blocks = append(blocks, render.NewTextContent(defaultBlockStyle.career, statsBlock.Career.String)) +// } +// } +// if options.showLabels && statsBlock.Tag != dataprep.TagBattles { +// if options.showIcons { +// blocks = append(blocks, newStatsBlockRow(defaultBlockStyle.label, statsBlock.Label, blankIconBlock)) +// } else { +// blocks = append(blocks, render.NewTextContent(defaultBlockStyle.label, statsBlock.Label)) +// } +// } + +// containerStyle := defaultStatsBlockStyle(blockWidth[index]) +// if index == options.highlightBlockIndex { +// containerStyle = highlightStatsBlockStyle(blockWidth[index]) +// } +// content = append(content, render.NewBlocksContent(containerStyle, blocks...)) +// } +// return content, nil +// } + +// func newStatsBlockRow(style render.Style, value string, icon render.Block) render.Block { +// return render.NewBlocksContent( +// render.Style{Direction: render.DirectionHorizontal, AlignItems: render.AlignItemsCenter}, +// icon, +// render.NewTextContent(style, value), +// ) +// } + +// func newCardTitle(label string) render.Block { +// return render.NewTextContent(defaultBlockStyle.career, label) +// } + +// func comparisonIconFromBlock(block session.StatsBlock) render.Block { +// if !stats.ValueValid(block.Session.Value) || !stats.ValueValid(block.Career.Value) { +// return blankIconBlock +// } + +// if block.Tag == dataprep.TagWN8 { +// // WN8 icons need to show the color +// return blockToWN8Icon(block.Session, block.Tag) +// } + +// var icon image.Image +// var iconColor color.Color +// if block.Session.Value > block.Career.Value { +// icon, _ = assets.GetImage("images/icons/chevron-up-single") +// iconColor = color.RGBA{R: 0, G: 255, B: 0, A: 255} +// } +// if block.Session.Value < block.Career.Value { +// icon, _ = assets.GetImage("images/icons/chevron-down-single") +// iconColor = color.RGBA{R: 255, G: 0, B: 0, A: 255} +// } +// if icon == nil { +// return blankIconBlock +// } + +// return render.NewImageContent(render.Style{Width: float64(iconSize), Height: float64(iconSize), BackgroundColor: iconColor}, icon) +// } + +// func blockToWN8Icon(value dataprep.Value, tag dataprep.Tag) render.Block { +// if tag != dataprep.TagWN8 || !stats.ValueValid(value.Value) { +// return blankIconBlock +// } +// return render.NewImageContent(render.Style{Width: float64(iconSize), Height: float64(iconSize), BackgroundColor: shared.GetWN8Colors(int(value.Value)).Background}, wn8Icon) +// } diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go new file mode 100644 index 00000000..b41a141d --- /dev/null +++ b/internal/stats/render/session/cards.go @@ -0,0 +1,215 @@ +package session + +import ( + "strings" + + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/prepare/session" + "github.com/cufee/aftermath/internal/stats/render/common" + + "github.com/cufee/aftermath/internal/stats/render" +) + +func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session.Cards, subs []database.UserSubscription, opts render.Options) (render.Segments, error) { + var segments render.Segments + var primaryColumn []common.Block + var secondaryColumn []common.Block + + // Calculate minimal card width to fit all the content + primaryCardBlockSizes := make(map[string]float64) + secondaryCardBlockSizes := make(map[string]float64) + var primaryCardWidth, secondaryCardWidth, totalFrameWidth float64 + // rating and unrated battles > 0 unrated battles > 0 rating battles > 0 + // [title card ] | [vehicle] [title card ] | [vehicle] [title card ] + // [overview unrated] | [vehicle] OR [overview unrated] | [vehicle] OR [overview rating ] + // [overview rating ] | [... ] [highlight ] | [... ] [vehicle ] + + { + titleStyle := common.DefaultPlayerTitleStyle(playerNameCardStyle(0)) + clanSize := common.MeasureString(session.Account.ClanTag, *titleStyle.ClanTag.Font) + nameSize := common.MeasureString(session.Account.Nickname, *titleStyle.Nickname.Font) + primaryCardWidth = common.Max(primaryCardWidth, titleStyle.TotalPaddingAndGaps()+nameSize.TotalWidth+clanSize.TotalWidth*2) + } + { + for _, text := range opts.PromoText { + size := common.MeasureString(text, *promoTextStyle.Font) + totalFrameWidth = common.Max(size.TotalWidth, totalFrameWidth) + } + } + + // unrated overview - show if there are no rating battles (so the card is not empty), or there are battles + if session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 { + // + } + // rating overview + if session.RatingBattles.Battles > 0 { + // + } + // rating vehicle cards go on the primary block - only show if there are no highlights + if len(cards.Unrated.Highlights) == 0 { + for _, card := range cards.Rating.Vehicles { + // [title] [session] + titleSize := common.MeasureString(card.Title, *ratingVehicleCardTitleStyle.Font) + presetBlockWidth, contentWidth := vehicleBlocksWidth(card, ratingVehicleBlockStyle.session, ratingVehicleBlockStyle.career, ratingVehicleBlockStyle.label, ratingVehicleBlockStyle.container) + // add the gap and card padding, the gap here accounts for title being inline with content + contentWidth += ratingVehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)) + ratingVehicleCardStyle(0).PaddingX*2 + titleSize.TotalWidth + + primaryCardWidth = common.Max(primaryCardWidth, contentWidth) + for key, width := range presetBlockWidth { + primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) + } + } + } + // highlighted vehicles go on the primary block + { + for _, card := range cards.Unrated.Highlights { + // [card label] [session] + // [title] [label] + labelSize := common.MeasureString(card.Meta, *vehicleCardTitleStyle.Font) + titleSize := common.MeasureString(card.Title, *vehicleCardTitleStyle.Font) + presetBlockWidth, contentWidth := vehicleBlocksWidth(card, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlockStyle.container) + // add the gap and card padding, the gap here accounts for title/label being inline with content + contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)) + highlightCardStyle(0).PaddingX*2 + common.Max(titleSize.TotalWidth, labelSize.TotalWidth) + primaryCardWidth = common.Max(primaryCardWidth, contentWidth) + + for key, width := range presetBlockWidth { + primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) + } + } + } + { // unrated vehicles go on the secondary block + for _, card := range cards.Unrated.Vehicles { + // [ label ] + // [session] + // [career ] + // [label ] ... + presetBlockWidth, contentWidth := vehicleBlocksWidth(card, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlockStyle.container) + contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)-1) + vehicleCardStyle(0).PaddingX*2 + + titleSize := common.MeasureString(card.Title, *vehicleCardTitleStyle.Font) + secondaryCardWidth = common.Max(secondaryCardWidth, contentWidth, titleSize.TotalWidth+vehicleCardStyle(0).PaddingX*2) + + for key, width := range presetBlockWidth { + secondaryCardBlockSizes[key] = common.Max(secondaryCardBlockSizes[key], width) + } + } + } + + frameWidth := secondaryCardWidth + primaryCardWidth + if secondaryCardWidth > 0 && primaryCardWidth > 0 { + frameWidth += frameStyle().Gap + } + totalFrameWidth = common.Max(totalFrameWidth, frameWidth) + + // Header card + if headerCard, headerCardExists := common.NewHeaderCard(totalFrameWidth, subs, opts.PromoText); headerCardExists { + segments.AddHeader(headerCard) + } + + // // User Subscription Badge and promo text + // if addPromoText && opts.PromoText != nil { + // // Users without a subscription get promo text + // var textBlocks []common.Block + // for _, text := range opts.PromoText { + // textBlocks = append(textBlocks, render.NewTextContent(promoTextStyle, text)) + // } + // cards = append(cards, common.NewBlocksContent(render.Style{ + // Direction: render.DirectionVertical, + // AlignItems: render.AlignItemsCenter, + // }, + // textBlocks..., + // )) + // } + // if badges, _ := badges.SubscriptionsBadges(player.Subscriptions); len(badges) > 0 { + // cards = append(cards, render.NewBlocksContent(render.Style{Direction: render.DirectionHorizontal, AlignItems: render.AlignItemsCenter, Gap: 10}, + // badges..., + // )) + // } + + primaryColumn = append(primaryColumn, + common.NewPlayerTitleCard(common.DefaultPlayerTitleStyle(playerNameCardStyle(primaryCardWidth)), session.Account.Nickname, session.Account.ClanTag, subs), + ) + + // // Rating Cards + // if len(player.Cards.Rating) > 0 { + // ratingGroup, err := makeCardsGroup(player.Cards.Rating, cardWidth, cardBlockSizes) + // if err != nil { + // return nil, err + // } + // cards = append(cards, ratingGroup) + // } + + // // Unrated Cards + // if len(player.Cards.Unrated) > 0 { + // unratedGroup, err := makeCardsGroup(player.Cards.Unrated, cardWidth, cardBlockSizes) + // if err != nil { + // return nil, err + // } + // cards = append(cards, unratedGroup) + // } + + columns := []common.Block{common.NewBlocksContent(columnStyle(primaryCardWidth), primaryColumn...)} + if len(secondaryColumn) > 0 { + columns = append(columns, common.NewBlocksContent(columnStyle(secondaryCardWidth), secondaryColumn...)) + } + segments.AddContent(common.NewBlocksContent(frameStyle(), columns...)) + + var footer []string + switch session.Realm { + case "na": + footer = append(footer, "North America") + case "eu": + footer = append(footer, "Europe") + case "as": + footer = append(footer, "Asia") + } + if session.LastBattleTime.Unix() > 1 { + sessionTo := session.PeriodEnd.Format("Jan 2") + sessionFrom := session.PeriodStart.Format("Jan 2") + if sessionFrom == sessionTo { + footer = append(footer, sessionTo) + } else { + footer = append(footer, sessionFrom+" - "+sessionTo) + } + } + + if len(footer) > 0 { + segments.AddFooter(common.NewFooterCard(strings.Join(footer, " • "))) + } + + return segments, nil +} + +func vehicleBlocksWidth(card session.VehicleCard, sessionStyle, careerStyle, labelStyle, containerStyle common.Style) (map[string]float64, float64) { + presetBlockWidth := make(map[string]float64) + var contentWidth, maxBlockWidth float64 + for _, block := range card.Blocks { + var width float64 + { + size := common.MeasureString(block.Data.Session.String(), *sessionStyle.Font) + width = common.Max(width, size.TotalWidth+sessionStyle.PaddingX*2) + } + { + size := common.MeasureString(block.Data.Career.String(), *careerStyle.Font) + width = common.Max(width, size.TotalWidth+careerStyle.PaddingX*2) + } + { + size := common.MeasureString(block.Label, *labelStyle.Font) + width = common.Max(width, size.TotalWidth+labelStyle.PaddingX*2) + } + w := width + float64(iconSize) + maxBlockWidth = common.Max(maxBlockWidth, w) + presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], w) // add space for comparison icons + } + + if containerStyle.Direction == common.DirectionHorizontal { + for _, w := range presetBlockWidth { + contentWidth += w + } + } else { + contentWidth = maxBlockWidth + } + + return presetBlockWidth, contentWidth +} diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go new file mode 100644 index 00000000..4282e434 --- /dev/null +++ b/internal/stats/render/session/constants.go @@ -0,0 +1,120 @@ +package session + +import ( + "image" + "image/color" + + "github.com/cufee/aftermath/internal/stats/render/common" + "github.com/fogleman/gg" +) + +type blockStyle struct { + container common.Style + session common.Style + career common.Style + label common.Style +} + +func init() { + { + ctx := gg.NewContext(iconSize, iconSize) + ctx.DrawRoundedRectangle(13, 2.5, 6, 17.5, 3) + ctx.SetColor(color.RGBA{R: 255, G: 255, B: 255, A: 255}) + ctx.Fill() + wn8Icon = ctx.Image() + } + + { + ctx := gg.NewContext(iconSize, 1) + blankIconBlock = common.NewImageContent(common.Style{Width: float64(iconSize), Height: 1}, ctx.Image()) + } +} + +var ( + iconSize = 25 + wn8Icon image.Image + blankIconBlock common.Block +) + +var ( + promoTextStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextPrimary} +) + +func frameStyle() common.Style { + return common.Style{Gap: 10, Direction: common.DirectionHorizontal} +} + +func columnStyle(width float64) common.Style { + return common.Style{Gap: 10, Direction: common.DirectionVertical, Width: width} +} + +var ( + vehicleCardTitleStyle = common.Style{Font: &common.FontLarge, FontColor: common.TextSecondary, PaddingX: 5} + vehicleBlockStyle = blockStyle{ + common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter}, + common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, + common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, + common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, + } +) + +func vehicleCardStyle(width float64) common.Style { + return defaultCardStyle(width) +} + +func vehicleBlocksRowStyle(width float64) common.Style { + return common.Style{ + JustifyContent: common.JustifyContentSpaceBetween, + Direction: common.DirectionHorizontal, + AlignItems: common.AlignItemsCenter, + Width: width, + Gap: 10, + } +} + +var ( + ratingVehicleCardTitleStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary, PaddingX: 5} + ratingVehicleBlockStyle = blockStyle{ + common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter}, + common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, + common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, + common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, + } +) + +func ratingVehicleCardStyle(width float64) common.Style { + return defaultCardStyle(width) +} + +func ratingVehicleBlocksRowStyle(width float64) common.Style { + return vehicleBlocksRowStyle(width) +} + +func highlightCardStyle(width float64) common.Style { + return defaultCardStyle(width) +} + +func highlightedVehicleCardStyle(width float64) common.Style { + return vehicleCardStyle(width) +} + +func defaultCardStyle(width float64) common.Style { + style := common.Style{ + JustifyContent: common.JustifyContentCenter, + AlignItems: common.AlignItemsCenter, + Direction: common.DirectionVertical, + PaddingX: 20, + PaddingY: 20, + BackgroundColor: common.DefaultCardColor, + BorderRadius: 20, + Width: width, + // Debug: true, + } + return style +} + +func playerNameCardStyle(width float64) common.Style { + style := defaultCardStyle(width) + style.PaddingX, style.PaddingY = 10, 10 + return style +} diff --git a/internal/stats/render/session/image.go b/internal/stats/render/session/image.go new file mode 100644 index 00000000..dd19c6c7 --- /dev/null +++ b/internal/stats/render/session/image.go @@ -0,0 +1,24 @@ +package session + +import ( + "image" + + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/prepare/session" + "github.com/cufee/aftermath/internal/stats/render" +) + +func CardsToImage(session, career fetch.AccountStatsOverPeriod, cards session.Cards, subs []database.UserSubscription, opts ...render.Option) (image.Image, error) { + o := render.DefaultOptions() + for _, apply := range opts { + apply(&o) + } + + segments, err := cardsToSegments(session, career, cards, subs, o) + if err != nil { + return nil, err + } + + return segments.Render(opts...) +} diff --git a/internal/stats/session.go b/internal/stats/session.go index 4c463f64..bedbd34d 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -9,17 +9,18 @@ import ( "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/logic" "github.com/cufee/aftermath/internal/stats/fetch" - prepare "github.com/cufee/aftermath/internal/stats/prepare/period" + "github.com/cufee/aftermath/internal/stats/prepare/common" + prepare "github.com/cufee/aftermath/internal/stats/prepare/session" options "github.com/cufee/aftermath/internal/stats/render" - render "github.com/cufee/aftermath/internal/stats/render/period" - "github.com/pkg/errors" + render "github.com/cufee/aftermath/internal/stats/render/session" "github.com/rs/zerolog/log" ) func (r *renderer) Session(ctx context.Context, accountId string, from time.Time, opts ...options.Option) (Image, Metadata, error) { - meta := Metadata{} + meta := Metadata{Stats: make(map[string]fetch.AccountStatsOverPeriod)} + stop := meta.Timer("database#GetAccountByID") - account, err := r.database.GetAccountByID(ctx, accountId) + _, err := r.database.GetAccountByID(ctx, accountId) stop() if err != nil { if db.IsErrNotFound(err) { @@ -48,30 +49,30 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time } stop = meta.Timer("fetchClient#SessionStats") - stats, err := r.fetchClient.SessionStats(ctx, accountId, from, fetch.WithWN8()) + session, career, err := r.fetchClient.SessionStats(ctx, accountId, from, fetch.WithWN8()) stop() - if errors.Is(err, fetch.ErrSessionNotFound) { - // blank session - err = nil - stats = fetch.AccountStatsOverPeriod{ - Realm: account.Realm, - Account: account, - PeriodEnd: time.Now(), - PeriodStart: time.Now(), - LastBattleTime: account.LastBattleTime, - } - } if err != nil { return nil, meta, err } - meta.Stats = stats + meta.Stats["career"] = career + meta.Stats["session"] = session stop = meta.Timer("prepare#GetVehicles") var vehicles []string - for id := range stats.RegularBattles.Vehicles { + for id := range session.RegularBattles.Vehicles { vehicles = append(vehicles, id) } - for id := range stats.RatingBattles.Vehicles { + for id := range session.RatingBattles.Vehicles { + if !slices.Contains(vehicles, id) { + vehicles = append(vehicles, id) + } + } + for id := range career.RegularBattles.Vehicles { + if !slices.Contains(vehicles, id) { + vehicles = append(vehicles, id) + } + } + for id := range career.RatingBattles.Vehicles { if !slices.Contains(vehicles, id) { vehicles = append(vehicles, id) } @@ -84,14 +85,14 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time stop() stop = meta.Timer("prepare#NewCards") - cards, err := prepare.NewCards(stats, glossary, prepare.WithPrinter(printer, r.locale)) + cards, err := prepare.NewCards(session, career, glossary, common.WithPrinter(printer, r.locale)) stop() if err != nil { return nil, meta, err } stop = meta.Timer("render#CardsToImage") - image, err := render.CardsToImage(stats, cards, nil, opts...) + image, err := render.CardsToImage(session, career, cards, nil, opts...) stop() if err != nil { return nil, meta, err diff --git a/render_test.go b/render_test.go new file mode 100644 index 00000000..7d74d107 --- /dev/null +++ b/render_test.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + "os" + "testing" + "time" + + "github.com/cufee/aftermath/internal/stats" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "golang.org/x/text/language" +) + +func TestRenderSession(t *testing.T) { + // Logger + level, _ := zerolog.ParseLevel(os.Getenv("LOG_LEVEL")) + zerolog.SetGlobalLevel(level) + + loadStaticAssets(static) + coreClient := coreClientFromEnv() + + renderer := stats.NewRenderer(coreClient.Fetch(), coreClient.Database(), coreClient.Wargaming(), language.English) + image, _, err := renderer.Session(context.Background(), "1013072123", time.Now()) + assert.NoError(t, err, "failed to render a session image") + assert.NotNil(t, image, "image is nil") + + f, err := os.Open("render_test_session.png") + assert.NoError(t, err, "failed to open a file") + defer f.Close() + + err = image.PNG(f) + assert.NoError(t, err, "failed to encode a png image") +} diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml index 86c74f1e..c62ca987 100755 --- a/static/localization/en/discord.yaml +++ b/static/localization/en/discord.yaml @@ -135,6 +135,10 @@ value: "Aftermath just started tracking this account. Please play a few battles before using this command again." context: We were not tracking this account, but successfully started tracking it just now +- key: session_error_no_session_for_period + value: "Aftermath does not yet have a session for this period. Try changing the number of days or using `/stats` instead." + context: This account is being tracked, but there is no session available + # /background errors - key: background_error_payment_required value: "This feature of Aftermath is only available for users with an active subscription.\nYou can subscribe by using the `/subscribe` command or pick a background using `/fancy` instead." From f7200337636269b8d697a67ad5a9af77cd63a66a Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 12:35:36 -0400 Subject: [PATCH 054/341] added session-specific UI --- go.mod | 1 - go.sum | 19 -- internal/database/client.go | 6 + internal/stats/fetch/multisource.go | 4 +- internal/stats/fetch/multisource_test.go | 27 --- internal/stats/frame/value.go | 13 +- internal/stats/prepare/common/card.go | 31 ++- internal/stats/prepare/common/highlights.go | 4 +- internal/stats/prepare/period/card.go | 15 +- internal/stats/prepare/period/preset.go | 8 +- internal/stats/prepare/period/tier.go | 27 --- internal/stats/prepare/session/card.go | 30 +-- internal/stats/prepare/session/constants.go | 4 +- internal/stats/render/common/constants.go | 5 + internal/stats/render/common/footer.go | 2 +- internal/stats/render/common/player_title.go | 4 +- internal/stats/render/period/constants.go | 2 +- internal/stats/render/segments.go | 2 +- internal/stats/render/session/cards.go | 230 +++++++++++++++---- internal/stats/render/session/constants.go | 102 ++++++-- main.go | 1 + render_test.go | 12 +- 22 files changed, 354 insertions(+), 195 deletions(-) delete mode 100644 internal/stats/fetch/multisource_test.go delete mode 100644 internal/stats/prepare/period/tier.go diff --git a/go.mod b/go.mod index 0232d5a1..6a0e992e 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,5 @@ require ( github.com/robfig/cron/v3 v3.0.1 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.24.0 // indirect - golang.org/x/net v0.26.0 // indirect golang.org/x/sys v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index 4a980a34..fb439afa 100644 --- a/go.sum +++ b/go.sum @@ -17,13 +17,9 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -79,30 +75,19 @@ go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRL go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 h1:Mi0bCswbz+9cXmwFAdxoo5GPFMKONUpua6iUdtQS7lk= -golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= -golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -110,15 +95,11 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w 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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/database/client.go b/internal/database/client.go index f8b63e7c..464988ce 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -70,6 +70,8 @@ type Client interface { TasksClient DiscordDataClient + + Disconnect() error } type client struct { @@ -79,6 +81,10 @@ type client struct { tasksUpdateSem *semaphore.Weighted } +func (c *client) Disconnect() error { + return c.prisma.Disconnect() +} + func (c *client) Prisma() *db.PrismaClient { return c.prisma } diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index f01ab0be..ddb62321 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -337,7 +337,7 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session session := current.Data session.PeriodEnd = time.Now() - session.PeriodStart = sessionStart + session.PeriodStart = accountSnapshot.Data.LastBattleTime session.RatingBattles.StatsFrame.Subtract(accountSnapshot.Data.RatingBattles) session.RegularBattles.StatsFrame.Subtract(accountSnapshot.Data.RegularBattles) session.RegularBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, len(current.Data.RegularBattles.Vehicles)) @@ -347,7 +347,7 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session Account: current.Data.Account, LastBattleTime: accountSnapshot.Data.LastBattleTime, PeriodStart: current.Data.Account.CreatedAt, - PeriodEnd: sessionStart, + PeriodEnd: accountSnapshot.Data.LastBattleTime, } career.Account.LastBattleTime = accountSnapshot.Data.LastBattleTime career.RatingBattles.StatsFrame = accountSnapshot.Data.RatingBattles diff --git a/internal/stats/fetch/multisource_test.go b/internal/stats/fetch/multisource_test.go deleted file mode 100644 index b3718416..00000000 --- a/internal/stats/fetch/multisource_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package fetch - -import ( - "context" - "testing" - "time" - - "github.com/cufee/aftermath/internal/database" - "github.com/stretchr/testify/assert" -) - -func TestSessionStats(t *testing.T) { - db, err := database.NewClient() - assert.NoError(t, err, "failed to create a database client") - defer db.Prisma().Disconnect() - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - defer cancel() - - sessionStartTime := time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC) - - client := &multiSourceClient{database: db} - stats, err := client.SessionStats(ctx, "", sessionStartTime.Add(time.Hour*-1)) - assert.NoError(t, err, "failed to get session stats") - - _ = stats -} diff --git a/internal/stats/frame/value.go b/internal/stats/frame/value.go index 6b15afbe..c5e136e1 100644 --- a/internal/stats/frame/value.go +++ b/internal/stats/frame/value.go @@ -54,15 +54,18 @@ var InvalidValue = valueInvalid{} type ValueSpecialRating float32 -func (value ValueSpecialRating) int() uint32 { - if value <= 0 { - return uint32(InvalidValue.Float()) +func (value ValueSpecialRating) int() int { + if value >= 0 { + return int((value * 10) + 3000) } - return uint32((value * 10) + 3000) + return int(InvalidValue.Float()) } func (value ValueSpecialRating) String() string { - return fmt.Sprintf("%d", int(value.int())) + if value > 1 { + return fmt.Sprintf("%d", value.int()) + } + return InvalidValue.String() } func (value ValueSpecialRating) Float() float32 { diff --git a/internal/stats/prepare/common/card.go b/internal/stats/prepare/common/card.go index f9826112..38eb4b89 100644 --- a/internal/stats/prepare/common/card.go +++ b/internal/stats/prepare/common/card.go @@ -3,6 +3,7 @@ package common import ( "github.com/pkg/errors" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/frame" ) @@ -39,7 +40,7 @@ func (block *StatsBlock[D]) Localize(printer func(string) string) { } func (block *StatsBlock[D]) FillValue(stats frame.StatsFrame, args ...any) error { - value, err := PresetValue(block.Tag, stats) + value, err := PresetValue(block.Tag, stats, args...) if err != nil { return err } @@ -73,9 +74,21 @@ func PresetValue(preset Tag, stats frame.StatsFrame, args ...any) (frame.Value, return stats.DamageDealt, nil case TagDamageTaken: return stats.DamageReceived, nil + case TagAvgTier: + if len(args) != 2 { + return frame.InvalidValue, errors.New("invalid args length for avg_tier") + } + vehicles, ok := args[0].(map[string]frame.VehicleStatsFrame) + if !ok { + return frame.InvalidValue, errors.New("invalid args for avg_tier, first arg should be vehicles") + } + glossary, ok := args[1].(map[string]database.Vehicle) + if !ok { + return frame.InvalidValue, errors.New("invalid args for avg_tier, second arg should be glossary") + } + return avgTierValue(vehicles, glossary), nil // Some tags cannot be parsed here and should be implemented by the package - // TagAvgTier - period // TagDamageBlocked - replay // TagDamageAssisted - replay // TagDamageAssistedCombined - replay @@ -83,3 +96,17 @@ func PresetValue(preset Tag, stats frame.StatsFrame, args ...any) (frame.Value, return frame.InvalidValue, errors.New("invalid preset " + preset.String()) } } + +func avgTierValue(vehicles map[string]frame.VehicleStatsFrame, glossary map[string]database.Vehicle) frame.Value { + var weightedTotal, battlesTotal float32 + for _, vehicle := range vehicles { + if data, ok := glossary[vehicle.VehicleID]; ok && data.Tier > 0 { + battlesTotal += vehicle.Battles.Float() + weightedTotal += vehicle.Battles.Float() * float32(data.Tier) + } + } + if battlesTotal == 0 { + return frame.InvalidValue + } + return frame.ValueFloatDecimal(weightedTotal / battlesTotal) +} diff --git a/internal/stats/prepare/common/highlights.go b/internal/stats/prepare/common/highlights.go index 451aad56..151879a3 100644 --- a/internal/stats/prepare/common/highlights.go +++ b/internal/stats/prepare/common/highlights.go @@ -11,9 +11,9 @@ type Highlight struct { } var ( - HighlightAvgDamage = Highlight{TagAvgDamage, []Tag{TagBattles, TagAvgDamage,TagWN8}, "label_highlight_avg_damage"} + HighlightAvgDamage = Highlight{TagAvgDamage, []Tag{TagBattles, TagAvgDamage, TagWN8}, "label_highlight_avg_damage"} HighlightBattles = Highlight{TagBattles, []Tag{TagBattles, TagAvgDamage, TagWN8}, "label_highlight_battles"} - HighlightWN8 = Highlight{TagWN8, []Tag{TagBattles, TagAvgDamage,TagWN8}, "label_highlight_wn8"} + HighlightWN8 = Highlight{TagWN8, []Tag{TagBattles, TagAvgDamage, TagWN8}, "label_highlight_wn8"} ) type highlightedVehicle struct { diff --git a/internal/stats/prepare/period/card.go b/internal/stats/prepare/period/card.go index 65488820..d8511cb1 100644 --- a/internal/stats/prepare/period/card.go +++ b/internal/stats/prepare/period/card.go @@ -25,15 +25,12 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.V var columnBlocks []common.StatsBlock[BlockData] for _, preset := range column { var block common.StatsBlock[BlockData] - if preset == TagAvgTier { - block = avgTierBlock(stats.RegularBattles.Vehicles, glossary) - } else { - b, err := presetToBlock(preset, stats.RegularBattles.StatsFrame) - if err != nil { - return Cards{}, err - } - block = b + b, err := presetToBlock(preset, stats.RegularBattles.StatsFrame, stats.RegularBattles.Vehicles, glossary) + if err != nil { + return Cards{}, err } + block = b + block.Localize(options.Printer()) columnBlocks = append(columnBlocks, block) } @@ -73,7 +70,7 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.V var vehicleBlocks []common.StatsBlock[BlockData] for _, preset := range data.Highlight.Blocks { - block, err := presetToBlock(preset, *data.Vehicle.StatsFrame) + block, err := presetToBlock(preset, *data.Vehicle.StatsFrame, nil, nil) if err != nil { return Cards{}, err } diff --git a/internal/stats/prepare/period/preset.go b/internal/stats/prepare/period/preset.go index 2251369b..3a64bbc6 100644 --- a/internal/stats/prepare/period/preset.go +++ b/internal/stats/prepare/period/preset.go @@ -1,11 +1,12 @@ package period import ( + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/aftermath/internal/stats/prepare/common" ) -func presetToBlock(preset common.Tag, stats frame.StatsFrame) (common.StatsBlock[BlockData], error) { +func presetToBlock(preset common.Tag, stats frame.StatsFrame, vehicles map[string]frame.VehicleStatsFrame, glossary map[string]database.Vehicle) (common.StatsBlock[BlockData], error) { block := common.StatsBlock[BlockData](common.NewBlock(preset, BlockData{})) switch preset { @@ -18,6 +19,11 @@ func presetToBlock(preset common.Tag, stats frame.StatsFrame) (common.StatsBlock case common.TagSurvivalRatio: block.Data.Flavor = BlockFlavorSecondary + case common.TagAvgTier: + block.Data.Flavor = BlockFlavorSecondary + err := block.FillValue(frame.StatsFrame{}, vehicles, glossary) + return block, err + case common.TagSurvivalPercent: block.Data.Flavor = BlockFlavorSecondary diff --git a/internal/stats/prepare/period/tier.go b/internal/stats/prepare/period/tier.go deleted file mode 100644 index 5b085788..00000000 --- a/internal/stats/prepare/period/tier.go +++ /dev/null @@ -1,27 +0,0 @@ -package period - -import ( - "github.com/cufee/aftermath/internal/database" - "github.com/cufee/aftermath/internal/stats/frame" - "github.com/cufee/aftermath/internal/stats/prepare/common" -) - -func avgTierBlock(vehicles map[string]frame.VehicleStatsFrame, glossary map[string]database.Vehicle) common.StatsBlock[BlockData] { - block := common.StatsBlock[BlockData](common.NewBlock(common.TagAvgTier, BlockData{Flavor: BlockFlavorSecondary})) - - var weightedTotal, battlesTotal float32 - - for _, vehicle := range vehicles { - if data, ok := glossary[vehicle.VehicleID]; ok && data.Tier > 0 { - battlesTotal += vehicle.Battles.Float() - weightedTotal += vehicle.Battles.Float() * float32(data.Tier) - } - } - if battlesTotal == 0 { - block.Value = frame.InvalidValue - return block - } - - block.Value = frame.ValueFloatDecimal(weightedTotal / battlesTotal) - return block -} diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index 81d3bd45..c2f80ffb 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -1,8 +1,6 @@ package session import ( - "fmt" - "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/frame" @@ -45,8 +43,8 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] if session.RegularBattles.Battles > 0 { card, err := makeOverviewCard( unratedOverviewBlocks, - session.RatingBattles.StatsFrame, - career.RatingBattles.StatsFrame, + session.RegularBattles.StatsFrame, + career.RegularBattles.StatsFrame, "label_overview_unrated", options.Printer(), nil, @@ -59,19 +57,14 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] // Rating battles vehicles for id, data := range session.RatingBattles.Vehicles { - var careerFrame frame.StatsFrame - if frame, ok := career.RatingBattles.Vehicles[id]; ok { - careerFrame = *frame.StatsFrame - } - glossary := glossary[id] glossary.ID = id card, err := makeVehicleCard( []common.Tag{common.TagWN8}, common.CardTypeRatingVehicle, - *data.StatsFrame, - careerFrame, + data, + career.RatingBattles.Vehicles[id], options.Printer(), options.Locale(), glossary, @@ -83,18 +76,14 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] } // Regular battles vehicles for id, data := range session.RegularBattles.Vehicles { - var careerFrame frame.StatsFrame - if frame, ok := career.RegularBattles.Vehicles[id]; ok { - careerFrame = *frame.StatsFrame - } glossary := glossary[id] glossary.ID = id card, err := makeVehicleCard( vehicleBlocks, common.CardTypeVehicle, - *data.StatsFrame, - careerFrame, + data, + career.RegularBattles.Vehicles[id], options.Printer(), options.Locale(), glossary, @@ -108,10 +97,10 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] return cards, nil } -func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, career frame.StatsFrame, printer func(string) string, locale language.Tag, glossary database.Vehicle) (VehicleCard, error) { +func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, career frame.VehicleStatsFrame, printer func(string) string, locale language.Tag, glossary database.Vehicle) (VehicleCard, error) { var blocks []common.StatsBlock[BlockData] for _, preset := range presets { - block, err := presetToBlock(preset, session, career) + block, err := presetToBlock(preset, *session.StatsFrame, *career.StatsFrame) if err != nil { return VehicleCard{}, err } @@ -120,7 +109,8 @@ func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, ca } return VehicleCard{ - Title: fmt.Sprintf("%s %s", common.IntToRoman(glossary.Tier), glossary.Name(locale)), + Meta: common.IntToRoman(glossary.Tier), + Title: glossary.Name(locale), Type: cardType, Blocks: blocks, }, nil diff --git a/internal/stats/prepare/session/constants.go b/internal/stats/prepare/session/constants.go index c49fa6b2..4c3e404b 100644 --- a/internal/stats/prepare/session/constants.go +++ b/internal/stats/prepare/session/constants.go @@ -5,8 +5,8 @@ import ( "github.com/cufee/aftermath/internal/stats/prepare/common" ) -var unratedOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagAvgDamage}, {common.TagWinrate, common.TagDamageRatio}, {common.TagWN8}} -var ratingOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagAvgDamage}, {common.TagWinrate, common.TagDamageRatio}, {common.TagRankedRating}} +var unratedOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagWinrate}, {common.TagWN8}, {common.TagAvgDamage, common.TagDamageRatio}} +var ratingOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagWinrate}, {common.TagRankedRating}, {common.TagAvgDamage, common.TagDamageRatio}} var vehicleBlocks = []common.Tag{common.TagBattles, common.TagAvgDamage, common.TagDamageRatio, common.TagWinrate, common.TagWN8} var highlights = []common.Highlight{common.HighlightBattles, common.HighlightWN8, common.HighlightAvgDamage} diff --git a/internal/stats/render/common/constants.go b/internal/stats/render/common/constants.go index 05e81b19..e637e251 100644 --- a/internal/stats/render/common/constants.go +++ b/internal/stats/render/common/constants.go @@ -30,6 +30,11 @@ var ( ColorAftermathRed = color.RGBA{255, 0, 120, 255} ColorAftermathBlue = color.RGBA{90, 90, 255, 255} + + BorderRadiusLG = 25.0 + BorderRadiusMD = 20.0 + BorderRadiusSM = 15.0 + BorderRadiusXS = 10.0 ) var fontCache map[float64]font.Face diff --git a/internal/stats/render/common/footer.go b/internal/stats/render/common/footer.go index 53055e89..99051184 100644 --- a/internal/stats/render/common/footer.go +++ b/internal/stats/render/common/footer.go @@ -10,7 +10,7 @@ func NewFooterCard(text string) Block { PaddingX: 12.5, PaddingY: 5, BackgroundColor: backgroundColor, - BorderRadius: 15, + BorderRadius: BorderRadiusSM, // Debug: true, }, NewTextContent(Style{Font: &FontSmall, FontColor: TextSecondary}, text)) } diff --git a/internal/stats/render/common/player_title.go b/internal/stats/render/common/player_title.go index 1160b8a2..d77c6a07 100644 --- a/internal/stats/render/common/player_title.go +++ b/internal/stats/render/common/player_title.go @@ -14,7 +14,7 @@ type TitleCardStyle struct { } func (style TitleCardStyle) TotalPaddingAndGaps() float64 { - return style.Container.PaddingX*2 + style.Container.Gap*2 + style.Nickname.PaddingX*2 + style.ClanTag.PaddingX*2 + return style.Container.PaddingX*2 + style.Container.Gap*2 + style.Nickname.PaddingX*2 + style.ClanTag.PaddingX*4 } func DefaultPlayerTitleStyle(containerStyle Style) TitleCardStyle { @@ -30,7 +30,7 @@ func DefaultPlayerTitleStyle(containerStyle Style) TitleCardStyle { return TitleCardStyle{ Container: containerStyle, Nickname: Style{Font: &FontLarge, FontColor: TextPrimary}, - ClanTag: Style{Font: &FontMedium, FontColor: TextSecondary, PaddingX: 10, PaddingY: 5, BackgroundColor: clanTagBackgroundColor, BorderRadius: 15}, + ClanTag: Style{Font: &FontMedium, FontColor: TextSecondary, PaddingX: 10, PaddingY: 5, BackgroundColor: clanTagBackgroundColor, BorderRadius: BorderRadiusSM}, } } diff --git a/internal/stats/render/period/constants.go b/internal/stats/render/period/constants.go index cc9f101f..74837a58 100644 --- a/internal/stats/render/period/constants.go +++ b/internal/stats/render/period/constants.go @@ -52,7 +52,7 @@ func defaultCardStyle(width float64) common.Style { AlignItems: common.AlignItemsCenter, Direction: common.DirectionVertical, BackgroundColor: common.DefaultCardColor, - BorderRadius: 25, + BorderRadius: common.BorderRadiusLG, PaddingY: 10, PaddingX: 20, Gap: 20, diff --git a/internal/stats/render/segments.go b/internal/stats/render/segments.go index e3135c50..222d8eaf 100644 --- a/internal/stats/render/segments.go +++ b/internal/stats/render/segments.go @@ -74,7 +74,7 @@ func (s *Segments) Render(opts ...Option) (image.Image, error) { common.Style{ Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, - Gap: 10, + Gap: 5, }, frameParts..., ) diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index b41a141d..97db342c 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -5,6 +5,7 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/fetch" + prepare "github.com/cufee/aftermath/internal/stats/prepare/common" "github.com/cufee/aftermath/internal/stats/prepare/session" "github.com/cufee/aftermath/internal/stats/render/common" @@ -40,18 +41,23 @@ func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session // unrated overview - show if there are no rating battles (so the card is not empty), or there are battles if session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 { - // + for _, column := range cards.Unrated.Overview.Blocks { + presetBlockWidth, _ := overviewColumnBlocksWidth(column, overviewStatsBlockStyle.session, overviewStatsBlockStyle.career, overviewStatsBlockStyle.label, overviewColumnStyle(0)) + for key, width := range presetBlockWidth { + primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) + } + } } // rating overview if session.RatingBattles.Battles > 0 { // } - // rating vehicle cards go on the primary block - only show if there are no highlights - if len(cards.Unrated.Highlights) == 0 { + // rating vehicle cards go on the primary block - only show if there are no unrated battles/vehicles + if len(cards.Unrated.Vehicles) == 0 { for _, card := range cards.Rating.Vehicles { // [title] [session] titleSize := common.MeasureString(card.Title, *ratingVehicleCardTitleStyle.Font) - presetBlockWidth, contentWidth := vehicleBlocksWidth(card, ratingVehicleBlockStyle.session, ratingVehicleBlockStyle.career, ratingVehicleBlockStyle.label, ratingVehicleBlockStyle.container) + presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, ratingVehicleBlockStyle.session, ratingVehicleBlockStyle.career, ratingVehicleBlockStyle.label, ratingVehicleBlocksRowStyle(0)) // add the gap and card padding, the gap here accounts for title being inline with content contentWidth += ratingVehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)) + ratingVehicleCardStyle(0).PaddingX*2 + titleSize.TotalWidth @@ -62,13 +68,13 @@ func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session } } // highlighted vehicles go on the primary block - { + if len(cards.Unrated.Highlights) > len(cards.Unrated.Vehicles) { for _, card := range cards.Unrated.Highlights { // [card label] [session] // [title] [label] labelSize := common.MeasureString(card.Meta, *vehicleCardTitleStyle.Font) titleSize := common.MeasureString(card.Title, *vehicleCardTitleStyle.Font) - presetBlockWidth, contentWidth := vehicleBlocksWidth(card, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlockStyle.container) + presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlocksRowStyle(0)) // add the gap and card padding, the gap here accounts for title/label being inline with content contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)) + highlightCardStyle(0).PaddingX*2 + common.Max(titleSize.TotalWidth, labelSize.TotalWidth) primaryCardWidth = common.Max(primaryCardWidth, contentWidth) @@ -84,7 +90,7 @@ func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session // [session] // [career ] // [label ] ... - presetBlockWidth, contentWidth := vehicleBlocksWidth(card, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlockStyle.container) + presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlocksRowStyle(0)) contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)-1) + vehicleCardStyle(0).PaddingX*2 titleSize := common.MeasureString(card.Title, *vehicleCardTitleStyle.Font) @@ -102,56 +108,44 @@ func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session } totalFrameWidth = common.Max(totalFrameWidth, frameWidth) - // Header card + // header card if headerCard, headerCardExists := common.NewHeaderCard(totalFrameWidth, subs, opts.PromoText); headerCardExists { segments.AddHeader(headerCard) } - // // User Subscription Badge and promo text - // if addPromoText && opts.PromoText != nil { - // // Users without a subscription get promo text - // var textBlocks []common.Block - // for _, text := range opts.PromoText { - // textBlocks = append(textBlocks, render.NewTextContent(promoTextStyle, text)) - // } - // cards = append(cards, common.NewBlocksContent(render.Style{ - // Direction: render.DirectionVertical, - // AlignItems: render.AlignItemsCenter, - // }, - // textBlocks..., - // )) - // } - // if badges, _ := badges.SubscriptionsBadges(player.Subscriptions); len(badges) > 0 { - // cards = append(cards, render.NewBlocksContent(render.Style{Direction: render.DirectionHorizontal, AlignItems: render.AlignItemsCenter, Gap: 10}, - // badges..., - // )) - // } - + // player title primaryColumn = append(primaryColumn, common.NewPlayerTitleCard(common.DefaultPlayerTitleStyle(playerNameCardStyle(primaryCardWidth)), session.Account.Nickname, session.Account.ClanTag, subs), ) - // // Rating Cards - // if len(player.Cards.Rating) > 0 { - // ratingGroup, err := makeCardsGroup(player.Cards.Rating, cardWidth, cardBlockSizes) - // if err != nil { - // return nil, err - // } - // cards = append(cards, ratingGroup) - // } - - // // Unrated Cards - // if len(player.Cards.Unrated) > 0 { - // unratedGroup, err := makeCardsGroup(player.Cards.Unrated, cardWidth, cardBlockSizes) - // if err != nil { - // return nil, err - // } - // cards = append(cards, unratedGroup) - // } - - columns := []common.Block{common.NewBlocksContent(columnStyle(primaryCardWidth), primaryColumn...)} + // overview cards + if len(cards.Unrated.Overview.Blocks) > 0 { + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, primaryCardWidth)) + } + if len(cards.Rating.Overview.Blocks) > 0 { + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, primaryCardWidth)) + } + + // highlights + if len(cards.Unrated.Highlights) > len(cards.Unrated.Vehicles) { + // + } + // rating vehicle cards + if len(cards.Unrated.Vehicles) == 0 { + // + } + + // unrated vehicles + for i, vehicle := range cards.Unrated.Vehicles { + if i == 0 { + secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) + } + secondaryColumn = append(secondaryColumn, makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth)) + } + + columns := []common.Block{common.NewBlocksContent(overviewColumnStyle(primaryCardWidth), primaryColumn...)} if len(secondaryColumn) > 0 { - columns = append(columns, common.NewBlocksContent(columnStyle(secondaryCardWidth), secondaryColumn...)) + columns = append(columns, common.NewBlocksContent(overviewColumnStyle(secondaryCardWidth), secondaryColumn...)) } segments.AddContent(common.NewBlocksContent(frameStyle(), columns...)) @@ -181,10 +175,10 @@ func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session return segments, nil } -func vehicleBlocksWidth(card session.VehicleCard, sessionStyle, careerStyle, labelStyle, containerStyle common.Style) (map[string]float64, float64) { +func vehicleBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], sessionStyle, careerStyle, labelStyle, containerStyle common.Style) (map[string]float64, float64) { presetBlockWidth := make(map[string]float64) var contentWidth, maxBlockWidth float64 - for _, block := range card.Blocks { + for _, block := range blocks { var width float64 { size := common.MeasureString(block.Data.Session.String(), *sessionStyle.Font) @@ -213,3 +207,139 @@ func vehicleBlocksWidth(card session.VehicleCard, sessionStyle, careerStyle, lab return presetBlockWidth, contentWidth } + +func overviewColumnBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], sessionStyle, careerStyle, labelStyle, containerStyle common.Style) (map[string]float64, float64) { + presetBlockWidth, contentWidth := vehicleBlocksWidth(blocks, sessionStyle, careerStyle, labelStyle, containerStyle) + for _, block := range blocks { + // adjust width if this column includes a special icon + if block.Tag == prepare.TagWN8 || block.Tag == prepare.TagRankedRating { + tierNameSize := common.MeasureString(common.GetWN8TierName(block.Value.Float()), *overviewSpecialRatingLabelStyle(nil).Font) + tierNameWithPadding := tierNameSize.TotalWidth + overviewSpecialRatingPillStyle(nil).PaddingX*2 + presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], specialRatingIconSize, tierNameWithPadding) + } + } + return presetBlockWidth, contentWidth +} + +func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { + var content []common.Block + for _, block := range vehicle.Blocks { + content = append(content, + common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), + common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String()), + ), + ) + } + return common.NewBlocksContent(vehicleCardStyle(cardWidth), + common.NewBlocksContent(vehicleCardTitleContainerStyle(cardWidth), + common.NewTextContent(vehicleCardTitleStyle, vehicle.Title), // vehicle name + common.NewTextContent(vehicleCardTitleStyle, vehicle.Meta), // tier + ), + common.NewBlocksContent(vehicleBlocksRowStyle(0), content...), + ) +} + +func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { + var content []common.Block + for _, block := range reference.Blocks { + style := statsBlockStyle(blockSizes[block.Tag.String()]) + style.BackgroundColor = common.DefaultCardColor + style.BorderRadius = common.BorderRadiusSM + style.PaddingY = 5 + content = append(content, + common.NewBlocksContent(style, + common.NewTextContent(vehicleBlockStyle.label, block.Label), + ), + ) + } + style := vehicleCardStyle(cardWidth) + style.BackgroundColor = nil + style.PaddingY = 0 + style.PaddingX = 0 + return common.NewBlocksContent(style, common.NewBlocksContent(vehicleBlocksRowStyle(0), content...)) +} + +func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, cardWidth float64) common.Block { + var content []common.Block + for _, column := range card.Blocks { + var columnContent []common.Block + for _, block := range column { + var col common.Block + if block.Tag == prepare.TagWN8 || block.Tag == prepare.TagRankedRating { + col = makeSpecialRatingColumn(block, blockSizes[block.Tag.String()]) + } else { + col = common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), + common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + // common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String()), + common.NewTextContent(vehicleBlockStyle.label, block.Label), + ) + } + columnContent = append(columnContent, col) + } + content = append(content, common.NewBlocksContent(overviewColumnStyle(0), columnContent...)) + } + return common.NewBlocksContent(overviewCardStyle(cardWidth), content...) +} + +func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width float64) common.Block { + switch block.Tag { + case prepare.TagWN8: + ratingColors := common.GetWN8Colors(block.Value.Float()) + if block.Value.Float() <= 0 { + ratingColors.Content = common.TextAlt + ratingColors.Background = common.TextAlt + } + + var column []common.Block + iconTop := common.AftermathLogo(ratingColors.Background, common.DefaultLogoOptions()) + column = append(column, common.NewImageContent(common.Style{Width: specialRatingIconSize, Height: specialRatingIconSize}, iconTop)) + + column = append(column, common.NewBlocksContent(overviewColumnStyle(width), + common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + common.NewBlocksContent( + overviewSpecialRatingPillStyle(ratingColors.Background), + common.NewTextContent(overviewSpecialRatingLabelStyle(ratingColors.Content), common.GetWN8TierName(block.Value.Float())), + ), + )) + return common.NewBlocksContent(specialRatingColumnStyle, column...) + + default: + return common.NewBlocksContent(statsBlockStyle(width), + common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + // common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String()), + common.NewTextContent(vehicleBlockStyle.label, block.Label), + ) + } +} + +// func uniqueBlockWN8(stats prepare.StatsBlock[period.BlockData]) common.Block { +// var blocks []common.Block + +// valueStyle, labelStyle := style.block(stats) +// valueBlock := common.NewTextContent(valueStyle, stats.Value.String()) + +// ratingColors := common.GetWN8Colors(stats.Value.Float()) +// if stats.Value.Float() <= 0 { +// ratingColors.Content = common.TextAlt +// ratingColors.Background = common.TextAlt +// } + +// iconTop := common.AftermathLogo(ratingColors.Background, common.DefaultLogoOptions()) +// iconBlockTop := common.NewImageContent(common.Style{Width: float64(iconTop.Bounds().Dx()), Height: float64(iconTop.Bounds().Dy())}, iconTop) + +// style.blockContainer.Gap = 10 +// blocks = append(blocks, common.NewBlocksContent(style.blockContainer, iconBlockTop, valueBlock)) + +// if stats.Value.Float() >= 0 { +// labelStyle.FontColor = ratingColors.Content +// blocks = append(blocks, common.NewBlocksContent(common.Style{ +// PaddingY: 5, +// PaddingX: 10, +// BorderRadius: 15, +// BackgroundColor: ratingColors.Background, +// }, common.NewTextContent(labelStyle, common.GetWN8TierName(stats.Value.Float())))) +// } + +// return common.NewBlocksContent(common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, Gap: 10, PaddingY: 5}, blocks...) +// } diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go index 4282e434..6ad909f1 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/constants.go @@ -9,10 +9,9 @@ import ( ) type blockStyle struct { - container common.Style - session common.Style - career common.Style - label common.Style + session common.Style + career common.Style + label common.Style } func init() { @@ -31,35 +30,97 @@ func init() { } var ( - iconSize = 25 - wn8Icon image.Image - blankIconBlock common.Block + iconSize = 25 + specialRatingIconSize = 60.0 + wn8Icon image.Image + blankIconBlock common.Block ) var ( - promoTextStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextPrimary} + specialRatingColumnStyle = common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, Gap: 5} + promoTextStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextPrimary} ) func frameStyle() common.Style { - return common.Style{Gap: 10, Direction: common.DirectionHorizontal} + return common.Style{Gap: 20, Direction: common.DirectionHorizontal} } -func columnStyle(width float64) common.Style { - return common.Style{Gap: 10, Direction: common.DirectionVertical, Width: width} +var ( + overviewCardTitleStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextAlt, PaddingX: 5} + overviewStatsBlockStyle = blockStyle{ + common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, + common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, + common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, + } +) + +func overviewSpecialRatingLabelStyle(color color.Color) common.Style { + return common.Style{FontColor: color, Font: &common.FontSmall} +} + +func overviewSpecialRatingPillStyle(color color.Color) common.Style { + return common.Style{ + PaddingY: 2, + PaddingX: 7.5, + BorderRadius: common.BorderRadiusXS, + BackgroundColor: color, + } +} + +func overviewColumnStyle(width float64) common.Style { + return common.Style{ + Gap: 5, + Width: width, + AlignItems: common.AlignItemsCenter, + Direction: common.DirectionVertical, + JustifyContent: common.JustifyContentCenter, + } +} + +func overviewCardStyle(width float64) common.Style { + style := defaultCardStyle(width) + style.JustifyContent = common.JustifyContentSpaceAround + style.Direction = common.DirectionHorizontal + style.AlignItems = common.AlignItemsEnd + style.PaddingY = 20 + style.PaddingX = 10 + style.Gap = 5 + // style.Debug = true + return style +} + +func statsBlockStyle(width float64) common.Style { + return common.Style{ + JustifyContent: common.JustifyContentCenter, + Direction: common.DirectionVertical, + AlignItems: common.AlignItemsCenter, + Width: width, + } } var ( - vehicleCardTitleStyle = common.Style{Font: &common.FontLarge, FontColor: common.TextSecondary, PaddingX: 5} + vehicleCardTitleStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextAlt, PaddingX: 5} vehicleBlockStyle = blockStyle{ - common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter}, common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, } ) +func vehicleCardTitleContainerStyle(width float64) common.Style { + return common.Style{ + JustifyContent: common.JustifyContentSpaceBetween, + Direction: common.DirectionHorizontal, + PaddingX: defaultCardStyle(0).PaddingX * 2, + Width: width, + Gap: 10, + } +} + func vehicleCardStyle(width float64) common.Style { - return defaultCardStyle(width) + style := defaultCardStyle(width) + style.Gap = 5 + return style } func vehicleBlocksRowStyle(width float64) common.Style { @@ -73,9 +134,9 @@ func vehicleBlocksRowStyle(width float64) common.Style { } var ( - ratingVehicleCardTitleStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary, PaddingX: 5} - ratingVehicleBlockStyle = blockStyle{ - common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter}, + ratingVehicleCardTitleContainerStyle = common.Style{Direction: common.DirectionHorizontal, Gap: 10, JustifyContent: common.JustifyContentSpaceBetween} + ratingVehicleCardTitleStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary, PaddingX: 5} + ratingVehicleBlockStyle = blockStyle{ common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, @@ -103,10 +164,10 @@ func defaultCardStyle(width float64) common.Style { JustifyContent: common.JustifyContentCenter, AlignItems: common.AlignItemsCenter, Direction: common.DirectionVertical, - PaddingX: 20, - PaddingY: 20, + PaddingX: 10, + PaddingY: 15, BackgroundColor: common.DefaultCardColor, - BorderRadius: 20, + BorderRadius: common.BorderRadiusLG, Width: width, // Debug: true, } @@ -116,5 +177,6 @@ func defaultCardStyle(width float64) common.Style { func playerNameCardStyle(width float64) common.Style { style := defaultCardStyle(width) style.PaddingX, style.PaddingY = 10, 10 + style.Gap = 10 return style } diff --git a/main.go b/main.go index ed76a496..be6bd79a 100644 --- a/main.go +++ b/main.go @@ -95,6 +95,7 @@ func startSchedulerFromEnvAsync() { queue.StartCronJobsAsync() // Some tasks should run on startup + // scheduler.UpdateGlossaryWorker(coreClient)() // scheduler.UpdateAveragesWorker(coreClient)() // scheduler.CreateSessionTasksWorker(coreClient, "AS")() } diff --git a/render_test.go b/render_test.go index 7d74d107..fe9ff97c 100644 --- a/render_test.go +++ b/render_test.go @@ -7,6 +7,8 @@ import ( "time" "github.com/cufee/aftermath/internal/stats" + "github.com/cufee/aftermath/internal/stats/render" + "github.com/cufee/aftermath/internal/stats/render/assets" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "golang.org/x/text/language" @@ -19,14 +21,18 @@ func TestRenderSession(t *testing.T) { loadStaticAssets(static) coreClient := coreClientFromEnv() + defer coreClient.Database().Disconnect() + + bgImage, ok := assets.GetLoadedImage("bg-default") + assert.True(t, ok, "failed to load a background image") renderer := stats.NewRenderer(coreClient.Fetch(), coreClient.Database(), coreClient.Wargaming(), language.English) - image, _, err := renderer.Session(context.Background(), "1013072123", time.Now()) + image, _, err := renderer.Session(context.Background(), "1013072123", time.Now(), render.WithBackground(bgImage)) assert.NoError(t, err, "failed to render a session image") assert.NotNil(t, image, "image is nil") - f, err := os.Open("render_test_session.png") - assert.NoError(t, err, "failed to open a file") + f, err := os.Create("tmp/render_test_session.png") + assert.NoError(t, err, "failed to create a file") defer f.Close() err = image.PNG(f) From 98df6feae176bd67bc0a50a994cc4c668085c47e Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 12:45:43 -0400 Subject: [PATCH 055/341] fixed panic, added panic recovery --- cmds/discord/router/handler.go | 13 ++++++++++++- internal/stats/prepare/session/card.go | 10 +++++++++- internal/stats/render/session/cards.go | 9 ++++----- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 7d91f37a..1c2e26fd 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -209,7 +209,18 @@ func (router *Router) startInteractionHandlerAsync(interaction discordgo.Interac // handle the interaction responseCh := make(chan discordgo.InteractionResponseData) - go router.handleInteraction(workerCtx, cancelWorker, interaction, command, responseCh) + + go func() { + defer func() { + if r := recover(); r != nil { + log.Error().Stack().Any("recover", r).Msg("panic in interaction handler") + state.mx.Lock() + router.sendInteractionReply(interaction, state, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed. Please try again in a few seconds."}) + state.mx.Unlock() + } + }() + router.handleInteraction(workerCtx, cancelWorker, interaction, command, responseCh) + }() go func() { defer close(responseCh) diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index c2f80ffb..f1e7f992 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -98,9 +98,17 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] } func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, career frame.VehicleStatsFrame, printer func(string) string, locale language.Tag, glossary database.Vehicle) (VehicleCard, error) { + var sFrame, cFrame frame.StatsFrame + if session.StatsFrame != nil { + sFrame = *session.StatsFrame + } + if career.StatsFrame != nil { + sFrame = *career.StatsFrame + } + var blocks []common.StatsBlock[BlockData] for _, preset := range presets { - block, err := presetToBlock(preset, *session.StatsFrame, *career.StatsFrame) + block, err := presetToBlock(preset, sFrame, cFrame) if err != nil { return VehicleCard{}, err } diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 97db342c..ccd76e16 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -12,7 +12,7 @@ import ( "github.com/cufee/aftermath/internal/stats/render" ) -func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session.Cards, subs []database.UserSubscription, opts render.Options) (render.Segments, error) { +func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []database.UserSubscription, opts render.Options) (render.Segments, error) { var segments render.Segments var primaryColumn []common.Block var secondaryColumn []common.Block @@ -55,7 +55,7 @@ func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session // rating vehicle cards go on the primary block - only show if there are no unrated battles/vehicles if len(cards.Unrated.Vehicles) == 0 { for _, card := range cards.Rating.Vehicles { - // [title] [session] + // [title] [session] titleSize := common.MeasureString(card.Title, *ratingVehicleCardTitleStyle.Font) presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, ratingVehicleBlockStyle.session, ratingVehicleBlockStyle.career, ratingVehicleBlockStyle.label, ratingVehicleBlocksRowStyle(0)) // add the gap and card padding, the gap here accounts for title being inline with content @@ -70,8 +70,8 @@ func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session // highlighted vehicles go on the primary block if len(cards.Unrated.Highlights) > len(cards.Unrated.Vehicles) { for _, card := range cards.Unrated.Highlights { - // [card label] [session] - // [title] [label] + // [card label] [session] + // [title] [label] labelSize := common.MeasureString(card.Meta, *vehicleCardTitleStyle.Font) titleSize := common.MeasureString(card.Title, *vehicleCardTitleStyle.Font) presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlocksRowStyle(0)) @@ -89,7 +89,6 @@ func cardsToSegments(session, career fetch.AccountStatsOverPeriod, cards session // [ label ] // [session] // [career ] - // [label ] ... presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlocksRowStyle(0)) contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)-1) + vehicleCardStyle(0).PaddingX*2 From d3af8c45d2a1e12fda9c9046c30c2cfb6af4ac3e Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 13:38:32 -0400 Subject: [PATCH 056/341] rating icons --- cmds/discord/router/router.go | 1 - internal/stats/frame/value.go | 2 +- internal/stats/prepare/session/card.go | 4 +++ internal/stats/render/session/cards.go | 40 ++++++++++++++------- internal/stats/render/session/constants.go | 1 - internal/stats/render/session/icons.go | 40 +++++++++++++++++++++ render_test.go | 12 +++++++ static/images/game/rating-bronze.png | Bin 0 -> 52350 bytes static/images/game/rating-calibration.png | Bin 0 -> 2231 bytes static/images/game/rating-diamond.png | Bin 0 -> 58867 bytes static/images/game/rating-gold.png | Bin 0 -> 57541 bytes static/images/game/rating-platinum.png | Bin 0 -> 68497 bytes static/images/game/rating-silver.png | Bin 0 -> 56479 bytes 13 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 internal/stats/render/session/icons.go create mode 100644 static/images/game/rating-bronze.png create mode 100644 static/images/game/rating-calibration.png create mode 100644 static/images/game/rating-diamond.png create mode 100644 static/images/game/rating-gold.png create mode 100644 static/images/game/rating-platinum.png create mode 100644 static/images/game/rating-silver.png diff --git a/cmds/discord/router/router.go b/cmds/discord/router/router.go index 3c24cfb8..12aa0072 100644 --- a/cmds/discord/router/router.go +++ b/cmds/discord/router/router.go @@ -57,7 +57,6 @@ type command struct { requested *builder.Command current *discordgo.ApplicationCommand cached *database.ApplicationCommand - action string } /* diff --git a/internal/stats/frame/value.go b/internal/stats/frame/value.go index c5e136e1..b14a2148 100644 --- a/internal/stats/frame/value.go +++ b/internal/stats/frame/value.go @@ -55,7 +55,7 @@ var InvalidValue = valueInvalid{} type ValueSpecialRating float32 func (value ValueSpecialRating) int() int { - if value >= 0 { + if value > 0 { return int((value * 10) + 3000) } return int(InvalidValue.Float()) diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index f1e7f992..e4aaef91 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -76,6 +76,10 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] } // Regular battles vehicles for id, data := range session.RegularBattles.Vehicles { + if data.Battles < 1 { + continue + } + glossary := glossary[id] glossary.ID = id diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index ccd76e16..7d18674f 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -136,10 +136,10 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // unrated vehicles for i, vehicle := range cards.Unrated.Vehicles { - if i == 0 { + secondaryColumn = append(secondaryColumn, makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth)) + if i == len(cards.Unrated.Vehicles)-1 { secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) } - secondaryColumn = append(secondaryColumn, makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth)) } columns := []common.Block{common.NewBlocksContent(overviewColumnStyle(primaryCardWidth), primaryColumn...)} @@ -223,11 +223,14 @@ func overviewColumnBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], s func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { var content []common.Block for _, block := range vehicle.Blocks { + var blockContent []common.Block + blockContent = append(blockContent, common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String())) + if block.Data.Career.Float() > 0 { + blockContent = append(blockContent, common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String())) + } + content = append(content, - common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), - common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), - common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String()), - ), + common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), blockContent...), ) } return common.NewBlocksContent(vehicleCardStyle(cardWidth), @@ -242,13 +245,15 @@ func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { var content []common.Block for _, block := range reference.Blocks { - style := statsBlockStyle(blockSizes[block.Tag.String()]) - style.BackgroundColor = common.DefaultCardColor - style.BorderRadius = common.BorderRadiusSM - style.PaddingY = 5 + labelContainer := common.Style{ + BackgroundColor: common.DefaultCardColor, + BorderRadius: common.BorderRadiusSM, + PaddingY: 5, + PaddingX: 10, + } content = append(content, - common.NewBlocksContent(style, - common.NewTextContent(vehicleBlockStyle.label, block.Label), + common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), + common.NewBlocksContent(labelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label)), ), ) } @@ -303,6 +308,17 @@ func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width )) return common.NewBlocksContent(specialRatingColumnStyle, column...) + case prepare.TagRankedRating: + var column []common.Block + icon, ok := getRatingIcon(block.Value) + if ok { + column = append(column, icon) + } + column = append(column, common.NewBlocksContent(overviewColumnStyle(width), + common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + )) + return common.NewBlocksContent(specialRatingColumnStyle, column...) + default: return common.NewBlocksContent(statsBlockStyle(width), common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go index 6ad909f1..5c550a43 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/constants.go @@ -129,7 +129,6 @@ func vehicleBlocksRowStyle(width float64) common.Style { Direction: common.DirectionHorizontal, AlignItems: common.AlignItemsCenter, Width: width, - Gap: 10, } } diff --git a/internal/stats/render/session/icons.go b/internal/stats/render/session/icons.go new file mode 100644 index 00000000..33a87c4e --- /dev/null +++ b/internal/stats/render/session/icons.go @@ -0,0 +1,40 @@ +package session + +import ( + "github.com/cufee/aftermath/internal/stats/frame" + "github.com/cufee/aftermath/internal/stats/render/assets" + "github.com/cufee/aftermath/internal/stats/render/common" +) + +var iconsCache = make(map[string]common.Block, 6) + +func getRatingIcon(rating frame.Value) (common.Block, bool) { + style := common.Style{Width: specialRatingIconSize, Height: specialRatingIconSize} + name := "rating-" + switch { + case rating.Float() > 5000: + name += "diamond" + case rating.Float() > 4000: + name += "platinum" + case rating.Float() > 3000: + name += "gold" + case rating.Float() > 2000: + name += "silver" + case rating.Float() > 0: + name += "bronze" + default: + name += "calibration" + style.BackgroundColor = common.TextAlt + } + if b, ok := iconsCache[name]; ok { + return b, true + } + + img, ok := assets.GetLoadedImage(name) + if !ok { + return common.Block{}, false + } + + iconsCache[name] = common.NewImageContent(style, img) + return iconsCache[name], true +} diff --git a/render_test.go b/render_test.go index fe9ff97c..c014682e 100644 --- a/render_test.go +++ b/render_test.go @@ -2,6 +2,7 @@ package main import ( "context" + "image/png" "os" "testing" "time" @@ -9,6 +10,8 @@ import ( "github.com/cufee/aftermath/internal/stats" "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" + "github.com/disintegration/imaging" + "github.com/fogleman/gg" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "golang.org/x/text/language" @@ -23,6 +26,15 @@ func TestRenderSession(t *testing.T) { coreClient := coreClientFromEnv() defer coreClient.Database().Disconnect() + rating, _ := assets.GetLoadedImage("rating-calibration") + c := imaging.PasteCenter(gg.NewContext(int(float64(rating.Bounds().Dx())*1.5), int(float64(rating.Bounds().Dy())*1.5)).Image(), rating) + + fc, err := os.Create("tmp/rating-calibration.png") + assert.NoError(t, err, "failed to create a file") + defer fc.Close() + + png.Encode(fc, c) + bgImage, ok := assets.GetLoadedImage("bg-default") assert.True(t, ok, "failed to load a background image") diff --git a/static/images/game/rating-bronze.png b/static/images/game/rating-bronze.png new file mode 100644 index 0000000000000000000000000000000000000000..9c9759bb506c9e79f0dcc30f336429b5ddd50e44 GIT binary patch literal 52350 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 LNkvXXu0mjfTChFp literal 0 HcmV?d00001 diff --git a/static/images/game/rating-calibration.png b/static/images/game/rating-calibration.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb133a82817b856c40187e40ae193d01fb45ef0 GIT binary patch literal 2231 zcmdT``#;nBAOGxPb4XTkmI^zCI-+r;zAmvOHcm(#gjk0!!pw0S&6tpDVTmu2OFKAW ze0|+E8-WHW)ITHDo~_N?VX-*&0}Ztm|Mu}KWdGPZ zWL5tzV+g-X%g|mgJ+r<%$rzqdUUgdkq6$Jg!}r#8EPUAc6`CV>_@BJX!-ws*!a*9KOR{kL9$MFHh|mi?e~oaOu#cb!1X-_+4~`|w0|(PbeR%P z+ZX0+b^!nL=I1iOHZ;sPluQq3OXl|S4q+M;p-G&H*hyaLsRjLrknrs|?+EYiy`S2! zKeyOY6mq)~<)$B&0EZ?vKO%jk!L}8Js!B%`B zPI)Y( z7O0wQUEgyaJnq|HAl(83!19Cx?HZVR!G;FNNI2l2yInc1?$-Z$+3OdlJ_-!(65hNK zOKcqdD}lVvEVJNV1dG~>)3=mIUFm;4?`7cBSFou1Lj6i@j3@u1SR2+f^&_Ui-3at~ z!lXXI>EAs|=3CTk)yVh-5T05y_Vv+JHW2XV1<2uQW1~e#FfQoKeoUi#my%>Ca0w|h zOg#mAUqz9=jf`fPml+`19o)t(K)K87KM)+-u+S<}L$YS^*D~r~(Q1}mBb2zUGopjH zA}XKx+};iuv=d-Na>Fyq%(q|fjl>ZoVOZdKVDgr$^Eev~_K+4mGU_A%xXH!IgQ9-w_m|5y_hc!c z=1RLHS%!?BKJ?-=WVh*QHrm9l^{C6CalE^1kw1N|l|tziJQsckjC*6ZX~*400q@t+ z>oba|nNlNd5ga_6P{Cu%WvSDg`AO6qde>D5^pNiZMnnTOjehQvZC!AFM7oT2xovIU zHS3I9G9i#7uzNz;=D4K9ZYqbGKp(DyB~1*jW=B*|W9XAJuh+M74`)iG9W(IqD(vqiS7HyJ&rdMN;}k z!&P4$nmsPbWQI>85rQ}0Z_Q615fzKOprnTXmPLc>dPL&q5Hd!%jpq!}oJvWjax34A zXHrU-_EShX!Mh&BJa2@CFTL$>q9v$SRZ_$3n@WKTmWxyuim4uP$x=Gbqc(rDE`2TT zB@I{F-+tLpczikqCJ4UGL?4%nZIXuKCP^$6iLp2lLZUF4{<_`NS}F1IvCsJAXiJHO zih)9p*0*cV*Fyf#RM@YIRyGmVv6bPO4Da2BLUQN=qSQpP|99^AKI~8p^_GgnMMG2Z zWKbA>O{ga_0I$(6hOeOJdFT0a;zZ!#+s2>F{Wfj86{9^ZiCjvqBN-Afk+x_P_N_;H ziBLV20@#e^LFRcy(U6iR#`jnLT+rUMhXxRHX{^EM7J~cx_Gn_R`t*{Iji#>mCnRyR z*J7UL#&4B&CR}WB=I=YxBgJnOsL@|9%2*XMQk;PZojquk^${hJXsb)w0|2Dw#5r!f zp5q1*)eY;*q&$kXQ^cW)^3)`E89GyF#aW`ImkoDPn>Ro!19nG^{qkTLg|9o>rX=80!Il)RR4oMUUywQ!fHElJypT3=1l=NrK0tTCg) z%qzxQmr@STer!Wfw);}alxp#MX=edoUmOCl_XrVV2>qvmG+y=x+1`E)sg9Y?88nkM N0O7oYHOq>4^Zy4%*~|a{ literal 0 HcmV?d00001 diff --git a/static/images/game/rating-diamond.png b/static/images/game/rating-diamond.png new file mode 100644 index 0000000000000000000000000000000000000000..8ae642921cf8723d44e57f9d9c65dd14c87b3402 GIT binary patch literal 58867 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

&obmDG_bGH>=|$;;10es~G`*1%N>?ns|FgB}b? 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 literal 0 HcmV?d00001 diff --git a/static/images/game/rating-gold.png b/static/images/game/rating-gold.png new file mode 100644 index 0000000000000000000000000000000000000000..deb5feb38ed4582fd50b3b4e101dff6669852343 GIT binary patch literal 57541 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 z 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 uo#Hg4Hn^Cy%#pu>u?SR~CP!A+T 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

^8;KG^;J2K?c`fe9>GmEmE*;0ax_u< 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 literal 0 HcmV?d00001 diff --git a/static/images/game/rating-silver.png b/static/images/game/rating-silver.png new file mode 100644 index 0000000000000000000000000000000000000000..4643af0b30bbf7b56ffd4701bdd95b018830b08e GIT binary patch literal 56479 zcmV)NK)1h%P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91rl11=1ONa40RR91%m4rY09+b0%>V#E07*naRCodGy$O_L=~dr(BQqjn z%gBA-Dyu4M@2c+V>P>2Ox76B@5XNZ4VVyx{I~dPmk73|2%<&91wlSKqWQ6hYfDs3< z@c{{0G6JpLEva|)R{NIwzGq})#vYmT|9vmM?C9*0N7dq}ZQ!1aeEW+R@BQE1@BKpd zH+0)~;75P-M|;EZj_#v(-0`j}FF&1H%%=`?cDKYhQC)ia+HS0MB3v%NRL-p|{=pyo z!TfE1!QUKUp})BYx4r`&d;=rL4^2$mZ0_wpc;D)F?(FFJa`@D6+qv{s(U#Y@+`+Mi zy1kP$M8XzrXs|-D=+-emv$FLRSu|2_05iQ^+M5TQ#ZuYw#huq2 zyO}qO)>qtqg;GWAwf+Tm0JPn)%p{x^(6;oBY<%MX{picDIry*H!e4n{dhl(`-)y<> z-GBR@^-SR~;M*=1yJNeR`xA)_qH9cZ=HwKs_J^1VZ)?=jxt-T=+W!`H+W)G6>Ba7? zz!$13ANLQTR47=fC;()OhPW+?nE)^h5&(%_2#|&cx*RCe(gm9(iaF-`_>uukj^p@3 zY1f^WW4yrvFah+$%#uwmXYEK|liLRndhX~&H2`5As^xZWh5mVv)&go>`Y6i{;rc!B z`u+VBU;L18jilr>yhjk72o`F}A1EvfR}Ye&WAY(7x%w{HGuL?Vp=p z%Rb}{4sI9}X{dKV0(21EkagSJdZ3zT{dTtC00bxjm^V}a7$8a|6V~3^xOc1vGn|rh ztVz6w>H`t&j}5*p;G7sYyTy#fk^zTh8zA)DlI@;6SM-2l8G?guWJ|9Bq33o0&x>o% z@i+!f>(@8Q2?b}&6$18H(r-&9;_S8uZnvO+p7Z*1`DK5s2e+U4tYsZ%^dcBA?p6fKsu<=*N5i{& z%WzFV)YskSI48ga+)Ltu90zf1?J=z5IM4k6X3mlhEX~pbAMY8=eC$rUTB!!^B{Lyk6-awlhw&-g8Q~ zlm2ngZdu7MMQtO-; z-mL`le!cixCRdiug+3+=T<2lFEFE%XW6L(y*L2>nm1UoV!vUCQmbYzTBX1>%-IdLv zZDg|6mWu1xIxAInEm9w{Ouk~9xj-bRVqv$QD4adlfD4z9;v|K7B@)p0nl_p#n{E6=?=*J2x7 z6`=Wbejm2oiV($qU$z4>;Kp&a$Fk3MHqep%G3=Yr^|cHfaHz-acB}9BPON*=fH}Le zo(?D4O!C!w+7f05yKHJ^*8S!qBYieKvuZPo8&2$cYuQg-$WK0Qzsx}%ge&Qs1Ebdw zqmdo=a4{IXFVPz$9LIJ~9-#T{4S_3oXn+v{ge|Qt*45oBoi%DFPQ2F+54Bl;Yr=B* zK)g5p9Sf+0Vy=lNC>SEi=Mhs$m}iw?AkG+9mt)$~TeuH9I%`ROG~uP@uUUOu=h+!*rH;AIHJ zD(eGL`zj~g4@f}gfyVx|n@&bo{PClQ4t82+U#H!7s*hc4w_LRLR7@&EUN1|`O1TYNSl$$vGEN`u=xDWcdehA} zCBoL!)F>S}NbHEn9~IK?+STui%g9PLHQR1|RP!;5CzAr|wymsf*!ou9CTEvzZf?s- ziSdbPn_QDlTB8bhuI(;r-@jXd)bHZK3UEE`Uk5O2L0QhMy#^Ca%TK>42GEA3mm zfcxp#NWXQqHM^+J&_JgR_QtKXF=pLTC3?EM-0tZAtIMla5*Vka=IvTxNA9D%9USg; zk)5gWX*+gw$POJmAQu;WRsb&LZDvlcnB{fr@9%Oz%vW7lfOB&*;{cmZZ&@f3vxr`l z=H@1;DN#E%&|-HU?lGJKs047=mG#UXSa~tWr^ao0rSLlSg6|HvDRZ7)<*c=UWb1zJ z2_PGrQ|`e1fb?f0y-)Ms@zlLH1(^Kk;?AZ?rFXx-7NCeYTt>%xJEWgg?4l5Q|J?_z zy)9*rJ$=I&GVUP#!*zqs2EhR#MDc;4UTf+*;6jV21j8eP){+WaM?%KUZq6d&ej|qm ztUi*kt+jbuSzZ$erv!|G0~OPz=4LC^#|5N{H7V6{d?I)@y#sx6EtPHNxmRpsBP}CG z(V4BRWyJZy4dRBECoQ4aJe)S1Xtc)0xW&ai;iw&*t?n8*H7;6 ztZek0bzvoAON(2!xxQ*+(`lPuSaQbJN;WXg+==v0x6cvW6mWVYrzsUgumB|FLFpFs zFI8HzOypI`#CoD|ntaDIonFJXxsbnXBFxA+dGmaz@#ezDhtkVa_cz8G-Pa+?YY8`y zPqxUG0x@*vXq#?u-ah_s{>1GH0DF47T-HxlTcfPwcI#8f;y{0=b+o3eqpj82b$SoJ z*(GtUblO&>e|oVCKpUb_3l+Dlup6<)SiNn@EwsA4VmUeQ7iQ}rkP2bJ5E*!jMh3uAIs6d?i-;@$p<~WFb0d2gcjS!)`mSDZWTLrA!QUA7z zA;*mYYb+Unxo#(K*_{TbX3WXOH8;0x1#$PemCBy(y0o%sW3vG_=Uh$g9S)rCySoCP z@qSSH?_}B--mtB55bZk9RQb&M($uHPY+~DYD*5>@4VVD%!6&( zR8~I1srgx}=(Vl|rstZ;#^3??OrOS7N@A|w#+I`7?gs{qw6wA~+FQ;JSZhnlJ>*8M z1AvuM)`>PmF#v06X|m+=&e`7lV1Pj8lY(Xj>NYtV!5bh=hrQ_wdnxrIBbAt$7N%B zNs98DHaCgmHbyMf(qeICLp3AL0+{&hA<+o3(gCZX-=|a-u`=NS#DD(WQt38ipCPyt=9Mx{_P(R_GtgP-b&mV9z^8 zKFdwzfAI{p_)grf0Na5C^%}Ly^ zEH2xF_a3!Bd1l2v_8XtJT407#ZF`=2^vtbv#=-@;wB$0Y$n$XQ}h$L0JUhqvDpPpLyxxWm6e%rtVlr5djzn)g85Ar}H2NK#8_koC0L&T~J zSt<9U6SgHF$6S77)B)7(pnm|Bo535J&uqFZpLc)Y5&PNiyWi%Dg7c zy}7+boVH|h%fZV?JRjOAXXThz#6@9FLY}S30H^>qyS?dh023?p>+2tIi(a)Ng%Hn)r&j6T!$Td0`)qVF zz`)eyD`&smB#>_ln27IUs%P%(*|V0En5pHM05mS^b3d^jnwea*^zyXWfVfaD5QF7X zK@Tp7y1+@};-ZLlLXvgON&Bt;>wmIDXvwZ!z3%kInI*Yu6!F0bS(RfQ5U$Bm#=6BV z1GPy%Yi|xX-L7<5z=jBhk`3Z2GQl9$`E4GMi)+DtYZCPUX+HtBx^VCUl7J${tTG9O zB(=W0o3OARG*h?@uCQceSD8WLGUBox(6NTHJ2%G^#VrBtege>tPi*n~tgda#e5?WQ zdQRUJ+`|OiTHpn+O^sn280@oXYq$N|PhPdk){4uT;+$~BP$^@(k(4#cOHdbWb_P{a zoW8fG%P|OkmnRsD2j)OWQ~`%ZXOOKZ5P)Qih->fegs=YNfA|w8{4sB2`fUO8yB~Sr zSC=-nerf35NA2*@JB`95 z-Gh((&F!tU^Sk=v;OwgsJDIwcyPZ0|0t3bH4TMSHbUfY}R}IoB3N& zJgR&F>6^90%dkzDRxfE*9JsADZc9rW;`nP)NrIQDJ`q^w90w2yr!sZRO|32~3#nC0 zJNZqE$)$J4p+MakmrD<+h(tuphdK}2+|3uQerf94;+o%EKmnY*NjZJ?b5H7QlG$b@JQSTF#E;KzDP^ot4@X?It`dMI>lkd_b_!{` z^89nw-q~);5}_CheA=Yjp!<0pj`P%4W90P`*LhOdDj_RZf``7AfeThyKw037CaqEk zL@7EVyf_|T9m6Pq2%H&iOmh2AF+m&oZAk#8R>d} zH6#OVC7-dP++0y{N}u6Wly4L6+A0ZI*p10gt8_Y@*Jo1g_gd@+>Y8p+rIc3UNVB9s zAdz$zl&qrbOE%f@L*j-4)8h81HFm~beklQmL|yRm1ISuXM&c>wYV)9sMap`?lU9^2 z-!3kS=vD;40Hv=S0Icb9+?HSYqLsEXdfp9Z^%LH(o9WvE=F&me@ z8#;5=5be#4QD;4SKzd!%>z+JNI7wr^$tv|^yd7m z-(BW#ajM|Ev5L_#x2qr|t0+%)Q63dRpsa>2YdK+EXg9S0 z&f6?!d|PwGdOE}vwI0F~0EH?AYint?8)MTJ>ufaA>DFg+HX=v7Hdtfbl8h5^adDi< zN!1PIgBLBGZnXLX-A-blkCu0X@{VnJ9?;OznKc)FjZ1|I>HQ&-9)bgU4qPY&+>s;} zz#WlsxTE{$_Ny9-K98WTKIgz*kc1)_K@y9n>`qJ^T^abi5CUpeDlE%wy03gM+z~FA zF9{}65!!p&ZKx}5>r$5%7G*$6bi?sIDCaIcVdc#Yi}wxcgs^R{jR4=1avbfpqFag^Dr zh>PibF{PMQ^(}O-tN@G^Oln%2{FDF@%8C=FRb&R0#TmEq!9%O%f&lb))>5%>rCpcVj>nEp+|Lbvt|KAs32VlMb6(TacqWBL{t#i{dQG9~ui) z_M$>Lu{m5u7+?O{m~P1Nj-k~l?zAHvw^KUi;S(cD&XZ9^hEiXjoqy%B6{U+}KvoLM zXOarBxUk`_V|6RYHDv)H>K)Rx2QIakWu2-}RSFz%-9h?CE^0vB=^$I*mw>Pg0BgxF zQ@cdge^b}1#wFHwV|%-G#`3l#zv=wa+Fm6=AvI*1x<4CpVvY)4gi`{vKpD!f57(am zcIV%)j)~#3F#FQn{x96w4y@2(a@fw?|8_(4Ne1lSO>bmyJBeaoTtHs=_c|Ti3*80q zLTz$O92#tLU_`IX%kuW_8pJQaLq+gPINX}=K|x<%S+M)7s;tBJ>Pu0) zzY@{yt!`;ecJ0#io|gp!XhSZkTzWi^}^Qu-WUGEM?cKD-w3_> zOK$V%t&NRi90Ip2E4=8bE*a=@J8Q4lr6(4pV?JtUPaSsv3Cn$gh3#}NjGG4WN$Ge| zaS;HRk|P<*^;O!njjD^0hp{2hUl_}{eTsFsToO878FPItc2EbUBz`Id6`m>JO|}n? zbU@PH*=(yxWf!d#tuIm18@*{8df+(XQvw=Mo*98Fzgur1g&TVZnr-m#5t~!eM_!h{ z4=r}fvSweva(uYmZeG1%PhFm~FFpRE{hc3rm))FNwwb8~`{^HfhxSX#qD?xN)y(vq zbxAkFDGzNatG=ZrXKM>-8y}sruHH`Dlv@f)b@jWKi1`zW1}d@|kicqH2{Jc1rAo-M ztu8D(z{V4bQZ27L3B!HD@9QIG#NZPpo{xT;?iVCCsMRGKEiN4xumV&H@G#W8URevu zko3u!@vXh`7r5z`^vTV+7aey5l;NF%mFwj8YiPDgF)jTy`wsVIZ}8u1`T1>}WT>a> z(cQZI_wLBK>j{wh<%kTJihlU^%Dl~}#wMlgpk}!)5O3&95&7Q$PFb1NJ1)aqZ}e_h z`pm8j4n2Hj@!(W$R*bx_M`1?!;hN>A8yQO3$&p?Q=N4_aJ8o||cgT+PB<$YP2dq_0 zxXyN!8!q1^R<{ytQ?s zX@0?77hIJOoRUoTHARM6lsbox^b?(& zyGvAeD+67y(6ux*DT*Zn3nK}=cv)f2xO}6#g{*~RfdMx+IcXd6Rv^ks$}q}qNt)=` zR$Z6!_bsc8d6Ga{`iJeohu>lc4~p|j5AN@fdrAgkV|-VB%c7Mt3v#@tZ9qxP%5S$8 zl_S^hN_UqRA*amyta1{5OAO7bT?8auhi=u?n z*K9%Y>Go*J-v0In?eWK-lo52(zUSR{8M^3^gMA7TEIZ?@MFta~oK| z!)5uvRS~bmxz`pJ+_kPt7hj%NzG}UqEnSLqrO4u!TSx{}tarplI{S65!1tPxr(jp! zh6e4^+<*$89M!tAwRB1HL<=@QBN1Gm&`T#t;Z+0e1*aXsKPhhq`XpWz!pOR9mKQh7 zDUyWfj<)q#B-t+RsFz+`w=SA+DvhijZj)XN`BR(UcxKWY1}3hea#>bc)e3Z1^G*IG zS`?*_a(rQ7&bBM>v^!6pltVu7Wu89NVE4<&Xq7<(5u@%I=$A-2Bo4NwgjyvE%inhS z+I3}z*W1b7Ry#CshqWGfi_=|`(%TAl(^>5vaE(n(fe|9ka5z-3wVWK@($|&+$N_PJ zL{z{~f-WgP0r_9R}{v8`1y4sQot?n3l zDMk~zFd0f&iI0%VN&qF^h^@`EJ^R$0IDeo5urJQ-RBNle0bylS9dhaubxzjPBZG9^ z+GON$TvB1pbIRU=@MNr4UsR%BdAZQg`!R4lImsyBjQ6*iH&^Pmd3_x zc`FRFv}1iC`>7xMZtIjWl9k`GwWD8-@}%aaKSnz&6jE3+(dlk}uO8HTM9k7kO~?)F4-hg zJfi*5Se61#C{!mS1TaS+lzEuRU``N7vh(=gwGn-JD&&c-eZp zTWwAb>?UOmMdLBMbn&L07>L>3XOF80437Z?V{%UE`=Oo=U4u5@Wq>26b!`%+*3#K& z-5v54NcGu~iv}rCln*ha!nF*(P_mfB5R#2atC#pkuMR0%w!OJ&1qCK@+8+t9rVJ^i zr8>#crkAa)quo;C#O)m_WtKRPE3(!mcUVX@^AVjVnn+rztxfl0*Sg|*38aUN&t%0Q zTAYysD50>_pdIwxkW{d0aomoA9;gC*D!2_CsotuTd*%GgFFjseaa)r&49tW59lx?u zzniQD<*h(kYaG=}kE_$0wl;a$BFa_#@ZbG0g&22iK@M|idkRyIIsLF%)@lgn?{3C@ z(#n~ju;s~f*)F1c{>h{^00Rfi*xwRU#doQ#=5PI|DCJ`k%^QKCnNb@Ky1s&puEj0UL` zDh;!zzV@uGO<%Xd)bsY#<4;*M)od#TWk9|1oE0aZw!-2KTb!M>d+)zX`k$_&>gI|_ zSF95l0XpZ~k$w#*Azp&U78QcYEwi$kwrM3KM-^5bJakaLO(loufx{6yrE~Wj7#i0!%z>Bj3$ zlyB^`OpIQ9=F@KDZT<^P=-WI9S5Y~#(+Lo3DZg9R4}R^s@NVFu@cShcuPqA{_uhZb znum_N)eXgUIk0zac(BuzE=-S&yX+Q!4AYliz9hYCQyd~`kG}O@8##5xnnKb!<@ zMj(MH#?_s7-|4_9)m-|oj?+uXxp%fUtd*u*qA6MTGcyZzWU$5VIWb__)g>1YATKl# z@3rBhhivl3b+<2|1B70u#8=7uesUsRq8w^VrcdqP{;e**7DeCUQQlPq5ciV@r{CtM zem@MPm|PXj!^bS7)T(+pWqbO2?F*m(y8Y>A9=GeOit;F=2fgjyBleGe;fM90Of@;m zl2L+fgF>K_^PBP|wmVSf@&XL`iq&u;WeyRAGD`Vv=Wiq#cxG$)@XJp+9 zdQuHmUHaIj@-fLYX9^W|Bxa@4CF^$FdPHuMe2o}l?|Sq;d;Ia|^u+Vle{jIn^W*zbSW`xFZ7mj6->7NV*P7hvjKF}w6U>6qw^4KhZ;(w_k>x?YojdHK>c z2hsyaMhv&moE-KLzSDs-CtYH2RxjwW(|3qFCT&8hk(^960cflBM(GXZAxNJGyb;B` zBNe66$&X7SOpA=2r=EDmUbs9j5DVP8j^FuryY@f)@O$j&iKCK4dWlvQ1?p+DPU*$I zc|$D->tx9TOlR~kkMX0a-zU&YMfo>d@2@bX zESC3w@cXSHRFd(Kv?m{b%KqiQ`a@e;*|1HyV|vcMOQjBr_Vibuv==T+**hP(%MSLp zT2ou6y>w|pVs6rf&K(aY!Uj#H;3?(()gJ7qEHc%5=D?P&YFTr$+@C>p1n z#cpTFEX(yFmTX(%W~CiPHN;lXA=lJw0UfYdwQOl#5R*9FRPsjsj`|@W=ejH~Lm!x8| zsFw-xjg_9u7bSX?M=E265Gm@!uJm2>LTcXOrk(O1ZU|tzyGkaO4obo?Ij58r4;~n_ z)A!u1P@{vz`9)ikK7QBP)7C|ZP$A1M`8&V%#B=srpP95-m8~4_lrv5Hf9~m7 z`}F5uvIox&+MRbCwM9J#jI+&hy~+at`;fY?JrvfB;{y-@Wu&1|8Q1~G;o|lK6@7C{)?s_!o%XH|e$d(~ zt9p}{>^J`7C5hK{J9o0jic0Z{DSP{Y`|q(%xq2S^;+Ms3%J%;EKkCxm*3_I5i% z=IWIjHgWyB4Ie%vqoYY7!*$z<>p?3im+;}Jz&*FHBGD{ovUIO_xMDN23ra4=*C~G; zA4OW2LP=%fx^5ZrtHO0+65nBQretm zgA6kPMK6^KDwH%VwM8No|7WVR+ZiWy+P9z}0J93QjeCy!cFYr8D+6+59Crx%}p$$tBfpSQug9#s94_-t8T5*^oi;Eb&3Zu{b& zf7#yq=$&$P^-C2|hQ5OJpZm%s2h3XFp<80`RB~HZClO!Vlm#!H72p=rv)Z;Jkji<# zu_2#p`2T(*zRWkIW8x}8M0+vq^~#cJXZ*aMx+h-!hrXFnqQ}5MkA++NToS~~;(!}X zM|O|B^G9UVtVqvnwUsL`x=doeSlo&uKkKW~` z)1x=s_8v7P2sJACVC3*#*=-a@b9HgtgwMIwJHvbUX^r+PgYjMpK={BIOt4 zf07md_*b5`haSGq{?>Ot=u+^Gt2fe*fBysa{onhbZOUu#)yJN;%NMWOl*)1ECZ;r< z)JwQ1HD$}CE_%Cr$5t5|Z(}`hmBFZIZM@O9-sG~(pK+!Lv zXNz+Rt5)lUzV`A{cIDzLE-cHsR%Hf-bpFNZar@J+oVWHCkO2>zIs8|VD{X_F&k`dSCwj)%ZB0}d2ZTVayv=`aNXM)TmG00>y$8bm-|0c=f$O*XR}cK-u+*-NincY5KPN?du6t$hP(MJ;Es ztZHK56JsyAIP>6%)3z{rUDm(SwNyd5vKX_0JMOi`o0t9NSY4xPcl7WvMB01%<=fPG zsJ4{I1q@5_67cZLnN@K&>0Nq?1#yWyZW%pLN*#!thOXu~S@=jMNRFj?F zA{{T9QgVb0Ba9%H6N;P^bX=Rf1H1?jeo90);nkQdei`-dyRqg+P8_p3iOrpy3^+Aw zc=3Yb>B}231{ElfD8+AyLmsP~+Mki3`|PCd!Q)^2yp5>|?EyuoI#i`c3CF68qG+lMD6id|l@~y1d6Fdfh!3jk$h8YO>sRI#1OTIY*$G3UXF}n|R;$Rr$u@kL zg0Nf9zxB&sAK6>DjhWj9CKmb1{OB1eX?tJOAb(a7AKfji^1YpEX2=bMu*7Gj+)sbw zY1$w^Ca7%MD z%GNod%1BxJ$|k?~%-8JRhu$WKeahCRMx727Z)j2ULi$@-x}Bz2!*E162hPaw!yo`XrP2!VFn64>P-V=$#&Znmpbv2piv3fP`60o3zdSLDg*d6xRNq3 zptxR?VnuqDxNNGrE~Ky~L>LsSKeMWq+DZn2a(ZA5%BZj3-Il8-0Ch}}r(#XaEHCw7 zQygn@N*;g+eoiF|3&d@zgAZ4m#x76X+yiP;N2lCBIVEKex{A&2Aw|im)QKolLE5EE zpze4u56TA|j}-0Ehwie+9(z*3fQ$^IJ|$2?A z0X_if{km-P8AXWpU_MCTa9p#R0vB(RssEK{*!{NXwt>m$5B?kAl7Q2b7vU^>vCTZ| zStcberDzS}cV416y}e}b|G=a6uYc>0?1ksf+c|-Ab!pX}eeR+iJ=|xHK61DHlVAHi zyXP(U+TC~FsbGUFLrep?gchb}Tx2KJdWSvn+*kM-=aXEK7@Lp|m{|=X6#&nRPDHRL zz(7V0K!b9Xg#3D{+>{;*C|TbiE?i4VRZpS=ZYp#ct=>5*G$A-oK?V&V#aW(KTpOyB zwGTwgA?acDDFL(~kAf`OlDM~6OHR=oh?fAs%C8l0R;}AAYrp{Q%e8K#uQ-kx759Xr zw-*??L;WPkc%RYQ1()9YF9# z@PdWqpMCndt2TW40ek;@AF^+}aKUD_Lw4riVe33F%;hP^AmE@e{WjA4Aa9k2iI2-r zEJq~LWt2IRNGEBy5~*|rD{k+9@H*VaG4FZ!Z9iTYsr-(2oMVZHs`-+f2C(iY&sjHR z>B5zM>f@ibzPd##EZnrycN|f-jcI%LyFRRJq!p`A1~J?|#d6>M_A^q#7wsSXZ$D)Z z-*ZG!on_0bXzJR_in~AmqFg07r4yDB2W(UJ&D!FE(*bGZir!Z%g5iW71Y94W@YJph zbwuj8{GB~rtqR!%^(;v}1ZY#9gp7QT4GP`u%C}jPs|X#Dhl<|kiDf=m-q5aIikQ;) z%Ap`r-0#Pf1_%_-kvUG1F3VvVWf128xTp#AQu2jxO)Uzy3Z$j1A~@=l*DdcvTvmK( zb5Z`v;3aBS9Grw0a$Koz0<3pE@PIw?w)aalxopFS-s$f1_k8I46xK}Gmp}IfS9`^M z|D!+uL%X9VVxuc1=ahesxa;O_qy6;H{<@-KN^O>Z(uwe@OUTdrlsA@%rQ^f&>2Lg= zcp-P&{f};&W0F-wQmnKOzSDtnw^}LaR>(5XgROaMn>FVmT zOQXxKR{X^mKV={Le}2cFdG3-zib}#Iy-P^~KmYUJr7ZoNJ@({zrw{TlDLSKOdworA z3R`o=NP116)k6b}fUua^kP}`0!L1p0S=EodZsVk4kNJOt}p-H=FSKTwZR1XzW zf-0&C`Y~J;ojI)+YkO_U<*5Gd@BE5={5Su|`FR(rQKh%O?{C>J{lZV!vEdfQzhidK zxueQ0z2;t!4rN1iXmEsrDlj2$c{5+$T6ZFxJWOv00p3zZt|X;4dT_px{ztbBOqAJ* z{E_w5Piwac;=yOqB7FF*bf zOaUKaDil#Ug?yM0 zoQ+m3^ceD7Zc8D@JuX+XzIGqxA-A8v2l;Fx-*LmQYtZ-AuG-40?g1w5=#3D01zFTcT~_ ztfEmok7?z2&P`8QpY-oX9=_K;{m1{puBwo1MZtof`^BHNp+kq2Qz|cnTFx{N9I&rF z`&G4q(eu;#JKNQBNq*Rgiu!o!%vj!goRjE}K3>=4i(y(NEOT+qx@bxOl@VE^{<(|$ z7XE!pz{C>wy@xKI|JvJj))6IoJFBh`Co***{ub4KZ>^=izOEM19qKm(7-cn*1q9K9 zr&yd-pUK&v?Tcc^(D@s4wyJ1MRtC(a^N+c8{LAU)l;q^Di&6U`uBZpi5FL&$SCK*s!*k~<&(=H~%~18Nm~z{Vd}3pS41zsz|&*x3&Ged1C^xxQLrod+rF z(Wm5%+M`$O0VP@d+x}kz%6SD z{nK$x;o9vdy*@c|&(HkwuYI760kXc;-bddQm^AMn9P0VVjT_?!kyc#9rt~a88B&{R zXjkc2rR=JkuU807648|U=)}9%D?PVlr&jsDT6zx}`Pujfx0U5WGgK&o(k}x7kdkoF zrw$MJ(k2w*<2&GltGTcMs7qF|UtHYJKfq}0)d%k%7U!I=_QUidly$Y_!Q+n6k7Sv7 z9=-=9`}^;4^EJSzJy$JYFmz1iw0y4zpu27zPz%!Fg;MAd1FcGM9RK~iEPOoq^(^~q zV%kW$6zg>pfvi~Bv`J)z`R^j?g+d1XDt!XZ0w;*@i*&5BtNZ0PDg=+4nAy)|4; zDb92e;-`IEX(8z$YRv0%&Z-=Hk z)$&i603XDW+}LVVKgJOI#7Hze+FMgC#>Z7jP_6>PQh?8Aeu{lI})MkV0m=i8Mk z=KZY>lsdLvO|G2LqwDpcnGe_EKYm|-U4A_v zZl3VFc~G)0iJn2BOO;#c{FrH2nR+hfnq+S6Ay ze3rXEY;PL;lD)-XCcC+DXkfU{R%Wl+r+)LF*}Hz|r)}`$Ife5sxkgbfgNH0TKjjLQ z_|dtt3M_;lbj^4je4XJ&0{-reBkwArC@rs^ML5XGh9#0D)e_qwzi;g}=KXd*_1oQf^*}uc{CcMTLGxRAfikkpJ@LS^ zva-<>0V1IEA#Ep_&&PD@d4Mks#phkPk~#|KhLrK zcBbs(m)Vb(14HQEyH8oy!6C;8(&E~O4)jayS#&_eC03OFzACBHUVXtw`r7we$8dYS z=;oZL9z~T^)N)XoEC9?>E~CsRTxIlYDmYLQTzT?R#+7L_s}QfJ{os8%&F^xqT6*nM zt!}|3v?V>Fy{F$3HI)A1Y z<7~iH)Z8g?A|S3RtDMS2j2}dLN;(d)-&N_B%r}U0KK{ZrTT{3Zw~!}9H=L|ZcV|s1 zfa8Q!Ao_j$6rg!j^Fm7v?%JmMU`jc%P)(r?4gNr~8v^ zSDCW(JLNwDR@^{-KQemAX@*Nhq!^ z8@Fda`|s7UcHZ9lfe(tKCDcZ6Qt4|W?f}5KD{%tXlp^ZZA|0n+yl?wbbl_UfN?({{r+tC$Lvk3 zhL1OZ*azYqfRLB3Rb>+WgZ*y((@(wRKuJdr54ya#d{HIHgLj>^@hf8rI`Z2CP;!vx zrnrXqEt}#ZdAWvmG>=U1*9=Jc#h%;2F(E()&{(docExiW^sl1&pajVu;^xdt?{fPv z@6UsNOOWB>l~J3XUsXiyJxVR?voZCWf8^Y~wz92;uhK6`0wEhd)!Jc;%1-KTA5r&| zP8+>;QNRqInFl8v)9IeN20%$Ke0m}QN)J*G%8S>=Tr#nb7;){;2Sk+rR9=Tc=QBlftEq3JWsAL`+=UjWE#|77gER8fI8ge7Nb>DJ+@zkCH|^hl{9oCbhd*Hd{Rhvf z%f_(lWcz>q>c5jtIw@dQW2HI}eKWVCxO3IR**O?kTrp=#aeWU- zxV?AW`*Sd*-zL$ShD4Kn;meop@|B4_5e%n<3$D&hyF=ddKmMZi-}|5y$G&O@hX)1l zR-2!=W~(#fDrpD+?!n1)Nnm~Gt!M0QkG87u*1UQmZAt{w>M+Q30XwL_$ZT!w! z@N!=;bRaLjJx66)s*3W!@Y21l`j6cK`sYehG~H1oX?gs*btt5!iW4b?A-8o1Ef&A1h=pSC7 z{bN?X4w#tlvlqW4D=hF~d9H~FfO9r?j@Z=;7j+}PWFP<4Psk_GX4fu1;~wyP-~E7% zsR*pB*e$dnP6)?DW589^(LdyTm>4~XkS6IgSFhZ3+>%;otiT=VlrzdHq{5E`j0y$H zs#o3vnI3{CxPx~LkywGm!;pYGb&30t;LxQ~SRR^h5zVnA=h7cDs)P#0gCxwE6!@rh z_ibGKcN{s`t#&Sr_NSkH(q2?@gR}T0ewDAG@xB{uY3j0S(>v`4{@y>ZMDGzh(J*Pz zV_mkZzMo@@ao2ifMtbDrwW}`Al56v#e|CDo{^AQ+yYHTpDu3wR16Qd|0}kv25w3K; zY|ToY=f&YzT604cxo+rqv8XP}1Rz#4Ac6PW_A1$VXfWWQWc+(k&OO93Clon!;%+-S z6y#TM-$@GIkfb6RnGZyx+c{#Xa_FQK7=Zv~M2sU)*d(z^dbu0bj6yk>bQYnZ1Ig7x z>VS7;sc2vP{g2-g7a1(%hv&u@DYQ8q9Fpx{e)<5&<(o#o-=M!r7)wg z;VV*dcWtOgiMPc;%PZG!TlFFZh0Dq+Ey(9d;_X(VMXyVDqio@o7hiGW8j%fQ^rWSG zUQ!2%x4-9Ic17!Ghw@({Z>&80_O~eYtwk?~dQ=LGZR$AYX-%~_zUdW42ZNC!fV>c* z6S5m6RcqR-ac7Rjjn$}%$wXV1{qv9hv0Z%mg2I78c#s%)y^<%Erbn%7P$Q^)@844t zs@~qxy=LdmoVGvuc*(DUwAmwZ%FIoDoD)GR?kV9^8ZLS8uvg z^X%w43`L~A(a)Ypn~G4hCZR#BRMDw(kB!eeDN5stHW^Ai?b3OxKxFDNk!lPk&UdTE z2(JSsuAd7s;`qGPb^mvEdVj))=tZ{?Y9Yw8uH2MYHQo4FVs~`XX zKmbWZK~$SN-aQt@#eEtaD<@TYZT1$|sxc{p$#X~#%DjF{ojM?Uj_E*qipsc2g0Zb1{I#-;yGvJtWOKBIK!Gb1>1;rXr=E(#XC;?}BC1b7XVunS2d3MVE zs8%Jr_9>+c-4be$vK@)Fx(;XPnHhD)kj17gE5UZ@iC)LVKi42&B7$2K(j;@}$*(=H zRIOl`5<+f=4v0f{b|@B{+5;V>ybM(WRdhochNZZZRO(o(ypXR2zuQUwCMQ2@oy|&Q z=x$LBd0?2Nl{0$xqaUz`fAFW&z+ufk^j3{I)HkfLBUNCA;UX0SnxCGw^?XRJVh-4% zUZ5AYhwR?(`ym^9{u$d|ln0^;%H}SWn#lb&v%KZ{7Ga%&>zVEvq&~I*(lqyL|Q8p|PX&&0QLwOY@7ugSaJi z`zbvahGJQ&X1sG+pYY*M^(*%#uLCBoqFRp0o*tBlani|}n(FNE!PBy@ z)yPxHwmb|ds|(mr<5fbp6~)ojxWuhr(z-jEg0cX5>(ECB5l(r)nsPFSPM%h`3b{?> zcyEz^b3=fpMwoc5l0;OI-N5VOT~H~>9^-znYBf=xef`G^u(mS%(1ig z>UR&hP-;}%>{xR_g?T}ff>?c48T(vHz|nm_P}aaHvso3&>Ydt=!J&XQtE(2eV&G~9 z5mk+T1?5|}(W^fAGLag61HD$SU#LSKoK;#;8+%meCV9YXZ zr$pz_FxOB^uLCBoqHw5cK~|lyo>OvQUKi2S*5eA5C})6;=jE4^%H@2HZXa>WfM@7t zR7H7nSGV#k#Z9G515Tz}GKHvD8B}uav=rxgwTkZUlQ0%Y8S_TLbeCJnIeFW?63;S( zAK>6Ayh;?&km)92)$W&ZAaQ`Usq>;{vbxYa*KTFrl()o5?rwJv zwLuY$cYNrF?UC>K+j477+QR~6Q){mSrB7NYY^&Xjnk6vw3QrD6;sBwSs!8R;V~>5p zR>j?7gLk{;Rp_nVBM060kt}il2{rwa-Wydg0$N=cSAx`bdSzQ}t)-p_W@prOt#?{al6*@)7WLqtP0|pjYQ^^cX9@>;^%a!9+BrM5xYJy0EOXz-*5nrg* z2$jp5_t&0_`PTtcVz^hb$nBTV&>(~q^Kywff2QhD7Pre%)DO7rlse$rz~?76vG+!L z5$>n#jIzEXOVt}Ytn2EPjyf^t{ACzF0CrimDvT6C6<<{X?K##t2J)HY%nu9=FmdVx zdZHJ}0T400x;4 z5kPYb>l%o)->E~Y=2xOJjltBwMPXdQT1-N{G{rS%)$QZp*$15A1ICA{-WZ}iNu`Tz zOYig@p@({6_60p}$Z<(GCu~R};Akg0v|d*BybR8vAxSqHGe})xFsp{XAZ3mh7%`3r zM>Pl%Ln^Ox8CBik!8hPfq9+k$6Z}vg=Y~`H^nQ7b?8wPLC77KUbWuyTtR|1 zxws%7(#1OGIg%)XUzpMhL^-{fh>PRPJl->G2b$EdRUg3k{FQIGXwc1tz%J-j{ke-? zRaG|t3Dts>Ww>L9yd#W-*{a-HKOlkkeUkZ3x*UXV3RPW3Zr&~L&1uH>BqAHg(`(Y& zB-&}8g5CtJr?=hMh|HIzYk2X^a)T6WhD0WK&-mQr`OPlQ%SSe4NAJGJRUSY0^s|mj zQoK(+^wH{#cE%_t5dlV=$+Y={Q$l2?Q}cvnn-FK(Rulq*hj8E?A$~-=(#`g`bhyoN zPhE~Y2kJ;B4oMsr%IAYICozry9b;3DPCenaVf~i$)`#NK*`&Aiw#u0=qp4B~M84Ze z|7iL}11)-6LcaE$b}Q70k0~joMLIEJkP*nZenNe9HAW3mgGx*CQtQN}iOVZ<#MQuK z6jo$~*XgsVbFkc`4#N8>p&#hOn3P&XyQ_1nc1qs` zAmNrxYBMS4;_I^9U&AqR75S6>@O|Vd*&F3ZH;St_*$HK<- zL+Rt=NI%|*?`ovMiJ}^y0@j`Ipt=cnIK47BC~&C&R9snz9|k)u6%GyoPPpd$^-E3#h^AUp47TciL&tzJeAFH$A(6_5P%ZZZHxops z;Bu4vi-RYPTmr&{fO1uu2_F()qHs5nOT1P(BG=A5)de`<;hq%jc~1eHoh&1C(ZK^6 zC|1e9VU@&8OMM|$PBI7mG!G5+s;%SEKy9j)5D%z3UP}65N!5>flil~yCfm7?v_!%N zboH_lHFO;qL2-EvI@O^VQC~}q^+^ZLt%}+~N=je zO?stg;GWsYsPlq!Fo-bPH>2G4wv8M=Ay<%mZ#Qo^?un}iJ(3Rux7=J?Q**3KHX|qd zj0*Kj)ot9+_IAb7#W_hi^Px6$Tle->zZlmC*0CqolF$RxudJB@+;BlPM^%o=Cd4-c zCuQJ(Rq1O)jflD=^hN+8>a3|{&&cxj29G;P|IpX)IRZ*JB%MMWS8wmKmtJ^9Ney*Q zjN|4av#9mJkZXnGk4GPCj|8qAe6C26nEHQKq?^|g!(H?71gIGxihSA4EqNG3G*N)y zo`91Vyj5yXkO?O4xmj|qGUo^Fp@71 zFf35bsjD#E)I3GRHFA-_-Gu)W&a*A=gC|}DIuSDh1$`@_MY)Wi%dcFp@#`0z7|#)X z(g)uqEx)}OUz}TU`WR!AldVq=Or!@yv)PQ?QE*F78AY(zP9rc+E@4~O%y{Y8Nr-Vz zw4?EaWzLIE<1wgNr+_<>(1A#2cxk$wj7=&yps?|pRIsV)SV~9LDx8K7x@nRs8mbTO zS*4Il4oE0oks89lu(WKXIR(B<00=qXXLWzpuUC5nU`X zYeJR0iHI18-je%iKPcIr(nEJs=}(CBYjQAeEG^l>_%$25@Uk#kp)P{a!# zVbWa>dM1Q^iT(-26lm~-;RAOhR!ocSZaMZFszbd^UOpd)c$Mddq;C;HI(+AG*BF)K zvvS=)oSW(Zl3skFo)h1S|3iQg&jwQf*=Yf=*yhv#8LV701YU#k=D=meI_mAYhgjyk zfV9KLlX3E}IyLi*)y|rGLlprdx(z6E9Vvxa$Il7TJua-CPZ0ezA;dXUsnh6navAaR zVSiu?@LcsL9aGPw##l;JjURU)$8Jp?_VY~l?yReX4DFnuUt*JVotSEx`2ZMfR0q4wl{KHgI0AToSL&%b1=Du^O7fxby|`z3YfotTjL zmUT>PW(cvv%_DmVBGU#j?wb&>uC+RV6NXP#(TCe1I9w^G?D(oEUtiW}4EjDF8Cq2P zZNxFeLoO+jkx)%MtyUNd93Udf`M8^}`^NrA5Z92ZQ4O=|)ow-)iAYOBTz%$?a=&P~ zy>D>OMa22>Ne*{Z>~M#&p5%#O)HK3@a7tPeLUjV?j-3Ax?jkto)YvsQ?Pw45U@UWT z79c;Sk55XW5dyJuehZd4*G~u;&%?9Nylj`11qFEtb54dRMh`iea82}2sJTT+DC$Za zs{)JC61XRv6H$%Jh?qHJA$g*_18R|>x+ltIxwF5gNF;d=81&0~z{FgqfD9_h1(Fv4 z`9NW1I|m=>DuD_kokN~{A(UbC$Zok6Nnp6uWSfw|!hlJ%I2ky4)CP|na{4Bto8kBJ z1ZY<^U@Bne!$bFm5Bs3&dDVdtcOhz6zEy%FaPu)%=)Yc(q2To)_ksyrTjIj-5Zs?lEnyr`0ZTVn)M8>$fc|&Z%jF)GdJ$VjgnA5!jyy;|u!&U6Xd- z-XNkg2+eZoACIm~IR)pW7!68F>Nd(4f`bB7=Al}b(H=0#Dk7m5LUemVxAc@$LPxr4 zG44u#LD3Is1O;DT&VIYEDijxQ3$l5iKR1{xLm1tt$Ck_PXtPpn<5h21Y zLpY7JH!W&@dnuSF4UG6aT!B`CaDo34c@ZAh_>uT9@m~_zfoq~?0@Rpv$pv{3 zX4M_IM?)z?+ZZhxPxjaaYL>xwUd#6#zjPIAM15X zY7pSWsovMF0iX1e(Iy^m1EtHP1tP`)AqEhiex=zV$|0g<6!xVQ25oN*7|3Qti767R zfCZ6_==L0wV?5U+6Q6SEg@qN@1h-Se-OvS(VP+T`$#rJV?OG_8b{0D=wfR56lD@QKXv7&Uq)%;&3z4!(w(iR*;M8!mo-X;vg7?Rr0}cT<02F&vbrX3Yvc7gcFTDx95(OPlV)$SP z0o00&uIcFob@x!Xt4bIZ_%BD)*9XJLlYbEq?euh~nLj-=DJfo9O}nP6Gowm%Q@$pI zo2!5LkR3mB%C-0N=Q((wO;V$1qmy%1P`wacNpN~&jq@Qb4YBUImp_ieJ{sV9YVhu| z>Iw`<3_8>chF$Sovq|lHpWZp>rrS?T+@FyE~Bpg zKKXU^%y{{v;_V8+wbZSom(dA%ZS=k9nG7Tw^v#si7q$Q-y+1vN1*q{I?OWqA+Cw0& zBF{1Xm!MbHf|T`*?hVxdPxInb=u^75#&D2D1h*8l{ozuARc;{N2J3END$pz8j4laS zdvUX}YS{nnpf)k4-bTvk*63i61Es*o4Mi-m47ViG!Oix>h!&1;6Ul+@ zsTP#1V;ka{Yll+;Za}%LVs?I$3+q+iWJYWAgRaUOA}*nBXI0J&=YY^xQ+iv_DcOdX zfftYwDX6W93DiaTMd6mozTVeZ;@y26E@78}3#d+{`!8aTNEo6LK;i=?e}K{5&^Zz3 zxOKYaIAH zPJ>WsG+eUFD~#(XAZJ$QETS5A^c3_Y6z5tF32-v9)H~Ebim1=@)U*aaRir{7BYlm; z5`b81m7`PokN+)jSUzw{taCu=9rUwPbBg1(OX*S5f~x4m@L?o1xFf)+B_19elbHDC z+NvA4l!t%g+D*A+q+&>nL%4{B`}+4nkR)v!ZI}9?@p>Rv123?zD6B}-2Lg16bD}OZ zTSez=+6~0{qQq@_aoX+Y|B;_b__UT_01z*6Q%a>Sge+xT77H7C5mm${%N&tACw+KM zb^r;)>tYJHt@y3$8n3(-oD5A!ca%sn037E=cdb`rT)1IVTbG(96m5KZ*-0a|L(O7p zj$|x(%;q}X+8e2hr z$|w*JZz#ihgp`yE2St2$OPrxgLb#sRl@$YrVJJDk9#@zSJ!MNqfwTIw9~^*(y{Z}< zdQY~B`;=rUE^kO>(6eRq2Z-TSJP4WA5`bkm7C0n^24Dv0dHGILYWIx3iDMlVg>nYC zCHm!NsM#4lL(0h{z0DK92QJN^1TQ@KoH|;lfq=MDn*st5ZLgb^hYGhOYn?12vTYcB z7{6sol7I` zO51KqseM~U;@Ycn9j#Og1UYVYZqezXd2w;X`1R=}aqHj(>gmy_b&3|jSpg>*OrCqf z4HNY$`H*G4s&O>Y*>QtGs6?)PH>#o%P9VSxj`q_YFn4xC_xg>21s-gI8$zt3Fp5Ep z5Mhiy3k&x|Y#dN}KPI{j)OYZ(wGOKru*xesr8q-?p?662KT4#f+5N%O8Z|)%5IShI zRfC(V%$MHe4XUkyP_j|jA#^xgF1U)g;SlGePTFS{xt06=k!uMNo(N3u%oE1aR(8=X_8vW@`>3X7MGEyw&a*+v zC**vaa(O|kON$y3MtPd*c#N?aQue)bXTc$>T+(+UVgFQp_#UQ^Ltm}`C|Ioxa(&c- zlJx~;rnf8c6C(2B-9(lJ!lALyfKg@m51V1nCy^&+DDt8XyTk`i{Pg0_)fGU7qc5tZIP2rr>+|bf`KOxRBh~StL6M+i{i`}=R z=KfOE2_X?WWN`z;)y=%)nlyDqPsa-Y;WFZdJTNE_RvqSy0y5C0Yty|!&m@+9{P2)Q zbv&6sMG{HPUxo|Hm3ZYrU%K;_1QD2sN4UMa2%d9LYu z8988*RTNUipa&S-Z%5Xl=bQkPb#)TUZL*y4*&*)sgNA(|$~{K)QuQSHn-FdynpqLI zW>WocF$3>msh4sWU^7qj_ zABl5lS!sb#DXnm;#!Cl$sA_zdTTecOL^|L^`jQ_}EldkIW^GoH2bZ6^Gw5HrT#=3! z(HP=N1eJ2k4<#Cd%#%; z3H`D!siHFZV4*$BU-Nt?oHHk1Yim`nB$z;Jn+1)bLt`zd@6ZA5t6>TOrTVH9K0H38 za38grdce*ZFBO0=V4HlR&wuUf_IH2s7wpSlc*1c`#)4$nJqSY*BC(ogS6ge#F8Tvt z_nhp?`r<`flWIW!c^0Qci zC3Hz1Eyvd9R~+Nue1MbuSqCt2UR*{~%D%_j(TJ}&OR0&njpVriJ4_vjt0LX&s6Ls) zJ$+n70BJwff|F*Sw2SFiMhv=;;JttMJ^hjm7zP+Vyxk;kbPo@^{n5od(KL&yNUU}D zM$65!E^B|Msti*Ni4H?i8ZHp@H$-;}Sv%s+)KrsC*h$|_Jy25dJYneIM0X^>v+Ru@ zbVaXUIzJ}aBchu(r<`LQ!>5)QQlzW3LDs*lZ@E0Yp6Nl}JvbmRlNTD?6NWLNscd$A z(OqkI?_t%f2m0{p!m5TJlpj&wNqQS#q`O^xY1^i+U$Kw=+Hcw~{?f5@i=Ph{Us)qR!2W0FY;^rNLM_jJ0+j-wV+(yo2G(G2h z#jfg9J>#ipbt8qSGc=w9xGVtW!?j>UE#^}aEvSSdTtyX$?)~IBpfh~*P#U7`ZWsJ? zDAmNf#YCMxis;`@=r&WAFSym{m22X7+!Xf_+g_QTblWg~f;U=&#>=flYZ%^S$hmct zTj<^{E#oR0=8>DI#xUJjh;oFBp%dhr`_ ztm~Yq!5*!VK~lBqaje5pa^fPjgno%zg;1m$v{e4et&Me!tgOuHWmvc8U%qLd`|Oup|9R?w4vH(Kng<=1JzNKFy6GA?F869_~(hGU;Hq0-86 zSX4WgW2#!<9;~XHN=6YXbkKPjU5AEGI{mW48D98NGY*_>s>GZZ2Mw!1EmV#u@FA`# zD~ zaTPTPtr$Gtj5ee^cd5ezgdXPDayZz3KPbJEowmwUKvIeem@#r7M0!LwKd$Z%(9Lf= zBhj~^A0iGTi=1AO<*brc)e4bVHlT!KLb!@>@RF2ET?^DMpC%na2sJuT3Y^YmqzohU zObi~}M9zIwRTlsy{!5PaneQu#zIejTQrHb5K7(UN!4(}-6$9VP`Anr?_hNu7CEjW26>0jCHE69pp4$OL}Qac(cuzk z*{8O&7Eoi6>t)-s$hXUCvZQ0%Bq9hyq95UxgVSONK?#)(WYs0DytQPRwMC6y7KlSc zHe4A%@)F>~%<38tm;Igc<>^Hsj!qM-mTLPFdTKh3>CUX*TNUNr2y#|>aoKH!Yh#5& z4WY8rImbsea<6nO=lk1Saj9B70Jt;U(G$8BAHR-ao#}|NChwD2Muox7fXH$1qmr)-vylpY1r_VUS zjMYPW(tt+s1~^4|k{CJK_vD!DRWSzr&#?q^u2AsYi8kHQ{w@m>v5b%g5reTQHE1b# z&oMYrxIsmctPAs2VMZh+4=xfOEYv4kikpMj<-z6-L+xOBahcF7 z`EmH+cW(<{`u0;{^mJo2LRu}GM9Si1bK=EQbwu>RvP!bgXxpHc zv%C3#ZgPd++FQdj%?B-fM6(~C;Q)bmM23fAV8U)lF`Py+WHg~cFehD-lovuEk61UO z!cEwn6qMlHF`pCj#M?da%(3N*HpJe<-@P6&+& zA!%8kTU>9aW1^l^~c>On@88`gV}gi(8wXMC`Y$dN-6Dn7;W z!6S!BEnlue0~p7opX@7aAXNI~oCerO3o|(3?v>h(^bfEU&5r+I_7KkIXyOzGbcsDRIrnpC5DMqBb7-%&G?Dlu+89Z5;5yqsq9EO;b z79wUz%Sf&Yoyo4{EXa91tM(3s;btWr({%G=!tnbcyGf+ooDspQS|YO@RmY(09bv^d znPye;@aN-Hey5uUR37#MVoR(U!1lb)sHm%nj|~;s2l94ZohM2P!l<-NWT-(2c)Cs% zmZBEOvjUdI=Wb(STY3{3`-)Sxc;6VEcKg zbE$N^RjE`bq$Z3tK6zSYWjd>(Bdr7Wj1kV=&Ld8ZZ`0$)Y^Efw72Fs&K!k=M0_t2W z9@wsU#>5g~yb)cDQ&WoN5)%}m4N&8BA>#NzE-7J1P&Wvhksn`GTV~b#uEtZcOKko7 zZofS|`J*4o9-IudTegR7+qZ`|U$-^vxV%hr#tXt@k3Jdp2x~JfC$c`{{GbYjmz;2V z$cmkwltZXuTyNe>68HkdXV{_OjG~dq)kDN7VLF^2Z2h63F~uSE8|Na?4eU%>lkwUS z2y|rEqcanl9zLp~Wci3Bkc89}zo>N-$#n2SY=0xp+6Ht_#`}#r!{l)MkU!dj8$a`N_n0R7)d|^() z8(Gm+xl2o){WbO-jcADPbmg*fSZXR{%$HZ|2TB}^QFyqGiArCgTW?wp&Pvsak#3~i zjf>3>r(l=>JFk&S6awS;#3y8h0u^_7N^NvPxQ}``S$ImeD~^w)@f7?_es9FH6Iy?IWUOFJ;wXCr9%IyYj`{wJnh07YV!@AmhlVIawl07G^+AL!E_)$l!p62YCz=dy~E>2#1W8XNdJ^i3Z*gDMoo2jEB}C+fr94TzfV1 zQLRN`1$HMY9ikxPlo+zV|DXRS`OQa7Hb5gRm2m+sclo;Iy7@p>L2 zM}JnmM`XRyv-9oz!{l?PC^dYk4-m`HvAc@I5n`4gDP3Cn;w<%u3=Z^%!-C^mySmIS zxPQh0!-%me+!>U0#`=%Tlh4s4uKF<0am+3{3`4dD0y>5^2Jcmwl97HcKB7AD$URKN ziDW_q<5h5cKnN!0i8oBx2kDieU^S_@^lSlYE6l;23MvNuJe)&R8yP5q#~BpMN|h7* zrE#iXX*UW~AdU~j4IG~m6((~NU|abEso8e)sEQOu1Htuv<;M5z-e(%6aog)c<7HP` zm}twIj8LbtHukrA(`MOvc@`iisNQ~Hzo2n?!iuOGH%LOoDa)%?+Xx%->czQ{o8z>+ zaik@L&7KlJuj5sDnzRrPi0>HDHNq5vP}N0w8ekwT0_vSo=2k4?wFaz%mf~ENC~muX zOO2^=mS-r8*CXHth>RD`WxQ6~=Pr@@F!FXXSW78On&oC+CO9 zcO4R-?{NXLWf*p%xI*@6-(8KF7eP2A2{h3y&J{3e{cqlNS$A`EkQj)%H(swv9nfaxXvKsfMb!ZRZ z1iDAgGaY}7Eg{RpT@R6AD9RJ$ikvGi6RWOWyOcIeNLRQ<4CsA5NX+F#P??H#C^IWk zf64db_|%pQCsQVY7X-6Tu~5HA&;9hSha|aAhlW)ZVb{}pLq}JC*#71>gx9_PD$QD7 z6IK@|gfj}~bPJYm{d%>=>c)hSYC~$9eg4_!tri>MaAH&~`{ZDzr{WO43ucQcMq#eL)Lh8n90PfPl@4 z%EOj5m8M-fj-AxKoEPI7%=Fjt>sc-UA216Ho585;rBPL)$T-ARp}t+poRK28iysvMyvOi?Txa z>z7?|ad_YR-VqwtuCwN@!$*!;u?IDY-Li>SWJDx1zm;BZa_Tesoe&;3CIY_+6>hym zl9Z@#)I7xb<>u#^Dsk#wA=-Nh4Iwp&^MWM=QAUU~gB;UnQf5keUXgtdwm5zMWTl{< z4uMZdG81`#N%}kh7#+(8-Lfm_df0mU_hiL&)AO8S)C{zUfn);Txlwyh+$_!42Sa6CGVFr>GV*y zIY5^Ek%WN=6Os7=2i^1HOod2GSMhK3q?0#g_>`?L109-Bwu2KcUbP7jEIXy|hslUx zjt}>1n0>!7Be`?JL@*w9CUz4}0FhGA;xSX-ANFK@i$ zBEeWrN{h6p&q)X)x`3$hG_@?C#v!C1UNzhL_T$QUIq^9votqRVk}#YWEtSa)A6FNE zelPndm%V##`h-EkP*>nICMgDyMXM}ap+2r6v%^`ICX@P%im=gwXJxO}*VinSym5U< zL&h>8+c>dYrm~g$6;}%QOIqj1Va1M>>MT&GFP0&Fo8`}mb94L5#vDDFaJz>CSjinB zdo4+^L<)*`9@=9U1u2I!vKW<|OY6kEAjp1dM#vCS*ADrD)5EsB_p}r?ri0#_D);Y< zhG~$P=B2B=ZCa7vmAQhLkrWsgqm%NYo{}y_Bq5AQjir0iG@QYJti#1}Seux`w7|Oc zqG%zK-2>~T7CVeE<>8jr@buHW6&qEeKxW1%nIu#cHAqrdmgI&XefN&=_kaDb;lYQW z51TH(Mh?u%0PHn(DbgFpq@0iTiQQ7;#D#58l{|Mq!@x1hQacA~e1+;aGozsxq$|ux z253x?2q;Cr1ACHa#~PUlPDs1S6Qc%5P*+u8eK&kY@djy2>IzV^*qzw!sBwkaLxobm zVc~??-k_^s8WGQI+|Ej_2^mT6XI)q6l50^(8QB98bfqL(kMiVW1IH~W&L=A^ z-WZVzsBo8c|LjqvArFMEuH*cSaK-yR9ky=2 zNVYizCJ``%6C@kNMAy9jiqL(mEj;zuQ;Hyz+4?XY@m;b+1k?#yWoHVYRm>Sws7&W& zh5*V6WlGr$aedBr;i=PYi7h%KbqnDwli|r#wLc*>7%wBg+`gOdiPJHp2yWb?k3Gjv zguI3dtA+=;jt~(A*HjzLD_uBxq|3|+i{Gm5m0E+dvOnDiu`iAJhl)NU!2Wq9Dk%9Y zs;sk;y#DqhGP@#LFJ|?ZykuLX?eJXgQ+!|&v#_%IftRcm^Tt&=t-UK!$6*&@!y$3VCfTSH*^EqMnASk|#0(XCsDKox zYbWy8NSqM`g^_A2b%L2g)1z+95i{l$+7QEY5+P19jD;HSQd1NJGxFU$yX5R28aKRi z7!o3m3Y8VfR8hzYPxSfc_uF@tt3M|_aUmRQ?F>J@_15ssFaMWG$Qj{HLgj0=gzAPh zp->?vCkj2rbpm<<1s2ya52V^6*u0UyIj?dTq`Ipf%gOpjm!n%Sme$XH?-1~Xu& zwC7?^;z6E?;_K9`-w-m@tVK!^M+Bz`37jL_&Q+^>=Iqx_xFiBbJplNpRK#HbQp$LY z3JVE}KdmAW<*^9^VPm3pVMYkY$AjuTo7xxCBuc$Oc(fpHaMLvEq;_;24o&-;H3zZU zj^}3&JP~fY_0Dk5o%e@lcX!H+iR9H!2!a}_tgW!wKeWD5jReuW)}wF|5>vC^!yXkx zxEkkk)HuE7sB!E!W;mk8DGae7x$H#LdEarhZ1nWmgYBR(RRlrg$O*7_x3x# z9IC2NP`!BZ?gY6SXFu4POgE!>4MGJWIq}XrRe(U)gJDfD(8|1QYpAS7}BVj}AbX9>UgyVWFIrx)CIW=|v*2 z?u30=ny>mt6&$o4X*CIlLv!<={(ZRfr;l3-GF6TWDi7)6R8Xj-K}M-n5Lr>N7!sl# zw!OVq(rtxp+t=G`00{2%U~>{jgpigim0onhc>!t>b?$^21jCYk#O5S#j{S)m9~wNN z-kVI5lB|zpJfRL$;=fMRI>4NL1Cz?z*M>{4+-hyld_HxGYA$Iat;3K5O5GqL5oYIV z(#GkbUkt3Dlf;H#FdQEcM=(t&U>@n}3<d^4VabANTdnH!2oPQ@hS`^ zOXOXC9@;QiLYV}e1JhR4#Xd13P+L>cfPMd%%HgPeR2YPePb?upPRmAuusug~^4kZc z{wXLH;bqebQopt){NVPx!oU8@H^SHc^{e6jpWZJCsH{v)xms&fhp4`nxY^0Ds!W4V z#h_3@S(T0G9I*Y!Hg{k$TM+$6B%r3U#_Bn7Yz9Yho-(CLelSR=`s6wN?OVpF}kaz06Xs~qoYib>L~nyqkErKcW6W- zF=FSH$n8tpkFO{@kD-TRG-7ry-nB5JBXpcocqda~m{GM>3@f8kRaLHarE0X3b7!!l z)W8T}L`yMh z>y!e)ZSBG+mlugwiN!c7^^~B{6of>kRMFI%vOhRJ1lFgNBQF)`6Yd2eepn@Q1C@JvV#xLda{?w?AxU?zWxymSnW*gCf>2kef(xnf9A%MU zo_<9O&g!#fP2+7lwBOXY?L_|=p_eV&(g{YRw`W?g&^n{0!U21QP}gUborQ5>R1y`K zMTshq!RAJSj*N^OLzii+$+1?Y9wqrspN`DYOx8MFSC76>jGZLYCM`iCZXtqYi8FZy z+g2K|roK>vAmniBySYi4_w3c2NsR=51LKEpoGNCJN?5YiHV(=n3BHRKZsT7R4T)i5O(H>suU296I7X)Y-vI(AZkDr8hARU8?(E! z>m`*$)RUGJsQy%v>eRH5b!A_Y`*-5pRwxujE*!?=0aH3uBW4i=F=o!_rly7!JDfU8 zGG{QNPvLdDkY%RMQBWQxoEM6zO;E+1qS`v}!kQl$IE`;$u%e z+KT#zQ^PW#&M_NObBau}cC~hB8<_t5&Jx=@QzC^46Zt-u7?TBaISuBWlWvT4f*hWZ zbzssQr3KrL9iMMZN)Nz$xXGE5ZMe(rpry94>7cBFB2%ZRCKwc^WMC5~8(0_` zz|JztC0eRNsU4mX;b!wjp+^$%ZoMfi})rJ zZ*hDwC1rX1;HzH>yB>KcRIF++W`rTJ0jsM@!s<1(s{E-6ieM=8Rip8Eo3*VZ)x@Y& zo?x&GlyoSmSJPTVqK``mX5qo_Kn8(0{soO%?4g(wK#_`2&1<&DI^`E12lM#XGEYtg~k-O?nx9(=2p7H+U# zBqg>d#It_Vl6}dtmoR3mC^Z!x(*qRCoY+-8oyzE_muAhn)n*twmS${lo-cqnDO4Jr znL1&J6{sEv=5TNQcvo-eSI;?dPVf{#+0D_(Bd6|d0sf2wgb7hqE@TUx2<%J>5^#Vn z-BcZpb$}VJ)TcsE)MApB#0i4P_&@{du*9ZpXsA`%tv`J8D_;xypM1zvw<0X2s_W}2 zLhTw2k(d3LRaR+P62PDw=W#%$0JbI_k%_rAQu&coDvkm*n^b>StX>y1NG(DvasEoH!INKU)#%_hoze_VjvLr(7@sx{RJqWrjoSBUtjlo3ZjId+uW?|I zn$+!bLzGC{L8p%&3Aw3r=45&;1q|u7rlYkGkS7gk8%6)Un`6A?)t&qRt24v6;b!B7pul$R3U^y;kNP_JGjjXR0fZlW$bdIrroqHu$Ynv@j@s>2w>9SaJC zydgDC`kHbzp9#c`i*1W4pOrLIK-Z)qjw8&8v}7HmC>kYM<`KxwP_g=GM-VG3IenNreq8bQ_>D8M*4DOF-RTb zNp*pW9Ybkdtq?eLjwFmSq%C3CIo&9X-Hs*HxJ%72J@~WO&t4)i`Cx2%?sj*0V94c6 z$vWV)pHt5XBZ%?hK&WNZ3V4{WM24w8I&9$~;&zF;lgXsuT|2|}9Urp5F>S6;Vw%d% zWII7FF>@7=27i$;&e)lRMWBvuH9zSjuPPUkg?f<)uv_dhYImZ-A?!{%B3H&u3K`_E zdDPp43UbM|EvBwlt*JM44dNTjNH~lKkTDS5lgGydeWK&W5lToG@|5 z!tQzM8S&I9(*#6p9Fv`WP>E0E;k29$LOmoG@QN8Q5KA0C7IL3ZZm zCF4_DoM~+*Y=eD?_f7GOt8y<#t6IO7RSehw=}1P3DtXj68Zl1kMEd?Y_n<+;?2fEr z8!r2Y#GE=K}S8gn4~A?Pfwe~M13P6 z{oxz(9y^l}A=G1L<*64)7meO@w=sQPf5s!9Q`a0}A;Lozixb0e=c8({F_D}&LUEcc zgd|B7C@EcwOaUea>D`ia|BY+39hnbaLlh;jPfjw;RK!>Dj$uMFJ!F8`2ZRZ+{i2%i z_8pgn(lr-_HJ4l*@@g71!zN-sCg)WdL+0t2od~>-$~=IHHZ;oS%on3dYGQ+?lwV}A z&m5VLFeL>gd{>#A9vak8<5@CMB5_wSvmB#rWo{rsJx;(w15^u|2j_yR7kV7Y^Cf%hG2<6)EMmDi}iWR2~TY#D7Ov}A+0u}-!$l9Fn8_78Z& z&+fV>jCD4t5B{Y=az7@XQO_JJrEY}G`u090Cs@TZiqa59q|+Ai4hJf}oZPqZ_dG8X zu7WaFdh*nuRD|wiB%{-OG<(F1Azo+ZMBw5R|8KmbWZK~$|0`kEwLO}-c=t40+v>7hY+0CPZ$zxn$HyKH}i zuQDY43Nuwps$*YNB!F51dV!b*{Me&Un;MT_iF%X0HKX?9+~QEBaMcQ_Z?lc1ZPuvh z0tR6p^U@;BqhtaG5|bfTXJm$;#<86-iztGjWRPqcjt>&CP)P*{hbLL7OENwHdhyv( z#z!|(nM{@q<>j_688g~Sw!QuugQuZwgdtSZVJX;+Yj2PMpDy>(bjNhN}gvZw~I2Try--ev!`EROhoDp*KKV|jt?7I1Lj0R zIyr+<&~da~_KE5W8fb)5>kC?2>lIV@|3 zV7&%vs_MqWM1+lCR6s?+9_Qgi&OJ*Ey1t~?7IiB-GCG|W>dJF%vM&p%W@=OJ&0%mY-&Sb>SmpRw4Bfu*#ecuRn0!QfGBIh^7!#&+X7tVG#CQ;P=Dl}6 z6yETb>&qELugC>-w$OtaAczpsxq1m@UVVdRfi~%#CX0J6&4x z3}FL8!vh^wos})sPMByyX4I6PLH+1#KBQxe+_aM#a$2dP7MG0v?1dM+k2pR;h`vZ^ zNL4${2t+HVdessDhQ#JeV9;Su3SW~lk{?@cUvg|O>T2FaiS*>@GZEX;m+=fk@^I0d zq^fBuS5lQQaFvJdo+cqVEnNhg_9AP>|RfCx5KOhI0Txzis66;!X5 zXIvJ^@QC@GIPqOwf~1bxm;kR5=OjJJc#z3SwWx`d<1i(wU>GtLtkSiGm62+9O6(vii6Byt17kCSBUG$Z z%)x`0Cy<22YBzw$^boVXSdcNp61Wd3^?BRZzZ1Uk^&f;s?z=x+ryHJ3*`7VEW&&VK zCaM?)M~fnB@auU#AxTdvl2SK%!nwgIv<6NGa)*c4*nkvQK$N$^$WY(0*04{(bmEje zvIH_q(Fhrzl~ITpTQT|%ri3A#NkL`FP~#bX!Snm~i$@}{WCzS}Uf>4w*X-(G%gC*< z`}No7WlSnOR9ilR8b?|>Loyl73Fc&a#gObeLNrxndAe{fe(uca>mx<+CJ7z%wRhNw zMxDci82b=56;2RPEnGBE(V+NrjX9A6iS{WzjwXz|;~6OXZ%g60?iLvxj|P3BP}@#% z3Ud)cBCt+z1q8fxwnB22)X+6pry2ntjYv$BfId-^Sm(+2=M3rZPbho`Bu>1{uhHhG z&AueB?*0Y-hQ~75##Q^vm0X^eF>S--bd#DeWh_JTnq^RqR7cCZmg^r`l7(LX;LViwUp%upAv44ykGa zJ`${{3EgNfM@UXU42Dmf4HfHTUy8;CkDCzUH8!R@!LhITZ2I*4FNBF$4!ta65;k(R z&8R+SPJA3^&J^9jBf}%%(BbxQ*+m%W=O^;+9!HCp9qL4A1r<*5EPw$JdF@mx%vH?b zV>n8rZb^GWq$I)1EXbA-t<2PnemEmrkOsik5G&1;ZDnn9@9Xt=BUgSr40)&r;JBd(<&gc1Ij>YCgh{^@0_IK&^mzP~-e2)HnoF z(r-=*a&Pl-jmSG-<$dqEVSAYCeq5zyIu_7}b#jqZP)=E$q7#IJB6X}tVQwHK7)b$A zyHqV9MF|aoD@7`jVhNHF6u01!$4WaS=-T=@mMXmR5|u@B?M;BoiJeRwXO~aZw;bAOJB< zD>w@nKB|GKx6Jvbiu;V}K2g~i_FTk4O)#q{$;ww1MgLP$tKxswB&r z)__t$6Oga(Z0nMpJts*r6LQsPN547snpHASKJ=lthA;fXABFPFQ)<*|vy2dda0qG1 ztVxG2WEqk@Sutg7bHK>f%E4gj2MG##e7Gs;JHo!EVGrW>xkru~cdA}p>n{!Imp2(j z6{f9#io|xWTV1CL`HC>4hzlVdXKrW6^X1ED;{#k~BI+$fIUY0RQPbMV9jkwxIYC8FABxkt*?vFJ$nITP)12~$oz=@=KY5Z1CrhwjQzy4>`3@Y%#3Qx5xNKAk>e_#vmR_+Bz`y& z^Mpbi%&!4q3?_rPagfAFOZw6)1t1Ec+K+Y&S`0Cw-i3uO8+A%58!5`XL$-lIVNRHx zi-kScb~0^ob)^ba%vc0f+gq_9LeUpPK1byMZ0E?8#) zQWByEKQ`ktFXzPO3<I4RD)tlR5M5rUU7u|N!kxv5=KScV>2ST z!A?R_Vw+-5B0WI*KuSU|G2M|&emE&LJ~w?L4*L?AJwO25I6a&{h;syF z_>jU<6l*YLB3WK}XRm_dT8>9gL0VK`efUeL^4nX8qWe0 z435yOP>zZJqP+mSh$WYrvCAe$2SQm*jb+7#)I)`ni;!@F4|wm(c#extep&mH?OvHN z)AGOJJCA){&Yakp!@WI*dKO#8PtM)Rib8aN+%?#l!xUFUi3@`hgnbE-zI5hf8UvIr z009VQB>RCAfMY`m9Cjwm?RPb*Z$=Ilv&;`4?NXIae`r2**tX{%3<)JEgoC$TuDD)q z20*2m=7IAWwq$CYY$1IPu^w}4Ko&ub_bKoWt*$Jzjz|Lq5Cf6*Da=X=x7_{(6}W5) z;~j^?+iti%T)gdSTfbBJd}!C>J0*Q1WpKuHVm#)Emy{LR=h&72>JRoQjjMCRIRaah z;5y{B1*a5(qL%cmY|q|d*}hVz%RO=&9~?v3IaX1`_5tjFFgFQQc*MSpS^c~(&zFXbUGWte(;I_grKN8S zb2@`&%Z565q*DkT%t=(yg&5FjQ9lx*7=&8R<%EPmWkTQ=@j#s?3lfiTg-@1UA-fQK z#aIHunM$1rm3GzE0FMHgfm(j$oS@!Qb>}8)Bnr)NWQ1U@T1_XbsV=kKAQg!ObgCT| z)$c?x1u;t`B@z`{Pzxs1VOBFNI+r4WPxbx zQ`J&mi>iN=s9>l)>p(DHwgx!FHlG1GP0i_q(uK5AIIwL?YmX%;2oJ?;9|ZP^C}7U4 zR1A~u#-W&sb?i*HExq`{1ME{L7elTn(Xiuf*BHQ;BYMz&``p71t7J~aDoLu~*ZFb6!DHScu+S6` zmzS0r%p(0s*p{H5P;becb~K6)P{>oRpUNtJFVPCLmgmS5WT!#|_`RZNV(f6lBvJk6 zP*10&BXM>hZmhEWEStS_PBjHyB*SN@ac556BQ04*fr?@Qp<5Xr>`TfetwK!<$!jm4 zbb*z;BI_m9>g;Pd{ZhvC?n&QpIdh&76J%#)g~LaBENpbq=G8B8fC#iRiUo;j<#Kv< zBA$6%NV`zFMjw0EA(iZ?Op0(JJaZhN%))Xj!@|x)okJ8+07y!T5WGxqCxK0zRZ%kg z0PDw7k7R8++PZ{2^g-~Up^Bt4m^0t(30x9{XdFob!l7C#}?yCSF99237wV9N8U@TnLq9@-5Ob%L{2 z2+mO17p9D1Cjw`Ur$-qT4p6oj)1Z@OXGZIVh0qr10U|Yt1H{E(5*TDsQp*Vkk^|0J=LF{msmQr^trxp5wuRgY0%;{@Riim<)01kKP;rQNJ2(#{>jw96 zJu`|H5F?N5<>mkXQpUv2R8Pyu`F63?=OcTwMq!fgeEUb?2eEE)y&zOLLu4LsW>7__a%?po5VeQEO+)8aywt`70S*Sm0aA|6Bqr-&YjLp(l>zZV zs5ob2jY`T(Lq&1KkwU@(F~j;WDxo0kP?++}qKZ5eq~?7jBcx&oHH$%AqY0tDrX(&m zJ>H6e|L#+t4L|zPg9du6hcZ~G~?qTBi4n+1_#tQxXSEH0_*h4BQXgJ zDQ0h#UYu`i{X8`Qj>#Sc3=K?xSk=wGoFRD)CCf>g9@HqmgL|cAA|W^!F_xS`-PU}$ z7Z;f|@@d(*j~RRZq`AShap#_{1lF zKa@)qAMF^3q!lHh@XQGTaZzAY$O|=iO@JJA4x!$ma~b6!>11E{>p%Z{%Wne_!!biJ z<@}6hC+a<6pPKS=Tb?0vhl-uYAAQE09$OgwM{jw7es6}PGfofB1%-;+svVY$581K2 zGNm#VwV*7J%A#nGp4pogSXa7{jE^_(v1>2hpja5V7Kk45Sve+{6by$VyOUZ_8cG!^ z%2aHaeNni9xm(joo)V)71Xm@0u}+RNsYPXEZ_X{oH^p{C zvcL1ut71$Sy&hQi9l0upVb3UJ167p>RZS@+93UuP6+G1yYUGzx#@yh6 zsW721*N6&w(-h``P~A945JkfjZ4?UCmu2lL`p8!6L~>b!eSX_Nd?q~f&~w(}o01b@ zK9~}Qq{|Y#OT6n;dA)Q@LPz%PmrXrjPLJD{c;Xpycwk7@Crrd^Y)aCP5HVIN`(a9m zI3^h%s5mRZ(A;=UPn4A#N}{@5h`OhuXzjWhRXWHV!@j_W!Q3&zfeV)?*2Bcg~eF}pnP%U8viu@xY(QWFN$ z>9e5vlvFMrT$zTZGpq_V9!t)YzKlwEaNQ?IdTkM^WJ*Y3mDQ@g5u+dlotOeP+1F-o zQ&czsJ`cpmJ|9ipDRd6(OlNMY*-?j^HZ_FWhBa3Dws%jHscvjfzAstP0MHc zkt63f+i%tHV8R%V4;ddP)IAkNb@g?&2)pn$n-~SG)kFzpnIiMt! zT01>;S1dfZ*BSziR)NaKaFd=G?<0Q*-MmG+zPF~}c6U*c~A>x*~nJLDgFejlS zVw~A(@hcYi)m^uIJM?z6g^aQd3P86TL)NTWYaj^aYSm51&=@>@5Bc%J=;;9b;K+eP zO2Kv-)XNf6>Le0r@M*s|Jg&x(mV}3BouFugwu$=H)gebITmtO?JurQY-%O7WH<#59 zNa{iEuuxG{St*U7>ju&5PB$uGuRzX#<^d|oqNqujL$37gj(UDPsLP( zvmFfS`F7h+G{Z%mC*J;y28VXFcUy3mOwF)L@?jhZFBoLESxvD`x6-Tv74}w2nnYAD zt-UZO=o~me&upG9ALJec;b06QfKH_fSM3drfM^pM2pl!8luX%xE0Zi3(k%c zb(fy9q_E+^2ObYU{N6qGF@vJYYE~IT)-^WT3`}q{Aucc;dENS28@_bt$l-9`9Y40b zI7|zn$_aJ3v?NRfG5HrY4skp$Ps|JZ zTEgFa{@?8V8cD8A8&`*#HEZNNEXLIgy-{xM63ScOLCsXWo)!qa;C*n8RxJagRLK0dXi8z5`rL zfXwj5DL7`-4G&;w3?fo$oNZm=x~=NRcfM!ZB+}!A)~A~>Ua;gaCp8mXXBg5WGRIHE z{onI#f4*g}nlWh@N|pTuAuUg6l2%&03JsKkeeL@73S$(Rq&%(S1e&jCSeqFc5GdDxIY3rvr0hw8dV~o75H8|5 zQ4Bt!+%pak;U5~Zs5G*SkRTJOD9RRi!8u`zBS|5G>N(M(2A(~JiIjBJUZ_;)!F^2@ zG$%fJ-5a-uP1`Ots2F)Rwjch@C_U&y4Jh2rkw;!E6y1fziSXEikJ)-KB#s(V)6|7 z`+n7oiJht6{Zp!m$o62bLCTOVNlV^S0fV$$PB0=2$-{ewoH+?7qrP!Im()z4MpaZqzPlaKxh}8GEJU+@@b`1i)^^cMb|n`S=By9{AuXNAQxht1ry3@7Modv=2MM~I&I)XPLK%o0<)*Q7P_M)nQRxgw zf(UCY=mDjs7Rc73zAw*!>KA28 zPlqm7XTn$pw3TB1agH2^2+)Ti-L$hO)hncUCq&0&TvF{vVmA4uFJHC9WKVchSve&i zA)xFM^OWM3cwbhy5mV;oX-II}ov=S)P6)Nm1G509irF!9@lWl@>&rR~4m&4RzUJ%lh)LszOpi3_hsBTXOb^ zS&C&UpiMCQzI*O7W+CmmWLxGIn6yNNQ+EjrO^J|%>C&|68e%@AC90Xw4bKpECeYqU zM#f=7V*L4#llLENBt_<%bgEn&(*&GiBO8HV&u zMt`=AUd2CVkT1y2PE@H~vNBuonh}|sS9$x-pWJ!NtJr*Ll^3~YY0)dc;0vyP+k3Yx zEX3`GK{*KQN$g5&3AZQMi?{Ro9$5!wgj|igG$qID_8w}TiLp2}gp6R$k#3!|D10;y z5K;u9T8Gjts&D84VswZVaY$qnfXC6Hh!!;t0qg~FVS2i{?KQYOI7z+2O^Sf{ql)BN zOL7y_q=OOl30a2{=CxsYz5<@32yMVrmG9;ln&ZwxHeC$!j+0 zs~(fxw)2T+!(%_w7(UrxFv*Z?T_iUQ=h+~b5h-j50DT5y*g8=rW#2G1amrPtdEw^| z?o?b>h|B_LrM!*f^I{E{l3+U0k|7riKZS@};_$eA39~qGEfSOP82cx#+B=}pCei{p zK3<^hOhPO-w|c!{V6xwnYDu9A3Ido54`xVk#I*qdo+vN`Aeqt;;HM7>QS{va?evJca>bFrRMgedOfSiF-mw zG;SguFegmPc1&)|&Mi={_h5MJv8O{>d5Nv>Y8+{a3Wo@$7v%_qE6SA24Z_@jP}!b= zp~wZh6N!oWf>}_knn~7G;hTBsFg?udOtYv+M7Ba%U|>Q3F@S)=G-RfP0Gykk3j%Y} z!+&;Kl2gUhNKParHMHJT$Ue`g zGM8rTk4kld>ZtKdtwS3H%btGXIiVo;t9x{%rAG;Y*P?wsl6H5zo@upH?1TH*;B!Z})M4gWx?+Wp% zeny?I6fgkBJgUtedJRp<4n$(Q6%s?&K{UgqM((E7N3`Yd_dz zFa~i-A6*EB6AwIbmXYUgRlc3D7^o+dy7diBg#r}`k1In4IaBhwe|$cyswv>5(2a#} z30Rl*4-xAHIgTt3)kv62v{OTbik1@+G}hj9M)hSbHSx@Um5|W#?}>eXK^T*Rq188b z$1X9Q5s{QIDP+=9CxE~^l%V1TXh@JRURdGrIMfxMG7=MJn(Xhgb!oLAkd2M$b|wxG zle{QS$jgiZx~jF7T2Gh`iA-e?F-r>>={;E|n`|p5Do;$nI#nv=TD@+)F)6`zoG=J` z)8d?BQY0kD2;(~QvQFm83eC1@4UazXvyfT5%9tN%34$;{u#-^-j!Y5#O%$$RSGvTs zmI>XD%xp(e0;lurGy4=ApRnSP1|jRc?(+2}@rR}EK|KcCfSZhJA#6(m>r_Qubj2nP zN%rTEF+a@6xgqO=6GYSBs>XFzyxb?OD6%xKgzDyuXgfX6KQ+ZgG70DT>{nfMK^T)S zEy>U7LNda9E-hK^>Kg{-;i{a^AZK7Il?K&sgS|!I7=jTk#whgybHFs`PV@;PI?Eg& z%EhR?1fr0>A!i^QpvJXzDr*}EEj<&4N<1=g&Sop_Z)yt<-hHp_AISm5DUHS~A*3V^ zh#LfR0-F)ySdfm`k-UZ(VFqkInAatAhJxxZJiSZpyuH?n%Z)-?Zn>NxC(@AR1p@q6 zcFS!_dc?6K34pTcl9I56o9;< z2&swIs8g(Vv&!$Fqr!qB4hmob@k&%TgslnD=;lS+zl`~8gP4D;@2Wg@PEX7#zt+nO z#+b|VpfIP~kT4{#oe5!1J||ZW&Fh3gvvNFWtDtC?iw)U)ZmDtJBQ@idCkB#m{kp1< zk{5A+EMuW{VNM*N=l2}eoAmIhkG&(r&kWkEY#_4-$090^Ov(?xcc)1Yijl!`=h+{8 z4|XMCBq2ZAalkAvBMj>|fJ=3+g0dAk=AHvf^L*&1p;tV+xXkkCr)1O7XO4~P^_6(x zPBfaOY0B`v7Y+t)B8aIOmtA>j_?`D&XYarKXMUn{WD(UnM0sIaG|mNsQy{k*kF}FxdrNSl!R+L}&u@1@4e(HA>x}mS?y^x?Mh6n9?kdVYMA(xgjvW=0NgqU!;PN?F2c4mBOo)0iFAe2C76sOFkhEju4 z2W4JhKk_>v>`3+rfvI_*snuT7ZhY;IYr?zUyj4J4&EfO^_LJ~*>)EhvQ$@&_9}0(B zBFAPcNB;qLxO(-PrA4&m$_F$^L!2JsmZ)`#H+T=ZkXX)??w#}OBN<^rXHcZ2zdj!T z>(W8|iZ3o0V{%ZgreQu9(V6jl#{^TljmhVD<`6a?tqeFGv_+6%LNd7{ME*QpwSy38 zKZ5JdlsqFfFWOLHb(y!^a#wi#xz_N>kG(g%@$x!NK@*&eg^(gr5Zmmb#}5cDu-TXk zQ^6~*?fWH3!Q2Gl;;gFT-NcA6qMEiOeJV@TnxJPwPm37^LQ3mV6}@1nC9%g8D+DtV z=|$DiDWR)Xmqii>9KT87sI&y`hGO9R?|H<)xPT&T&`tT)9oxcNwyzC8zU$HO>Hl*_ z=$%Xo+c)JKQ?~bLIG56pZblH+p|a}Kf{@ze96~rgV+x^RPZC1Ho`q6Wj7&8VG}PA> z(gpK)K7vt~Gpnyhh6sXbrG;J*S;Mz^wa-yG^=emt`Spld0g=71IJc3tTpBKCPL`jK zX<0VEpgA!LX%H(!b>ju&aJVEnr|?c?bzzts>Xm0+VPIR+!mzYn7q#uiDYZ#Z9R_7t zN@8KS>n9J|xWGU9;9KRD?+Z`vI%<*=CM5#L#Ek<7k62Ew(%55Ff#_9dzusY!R}u4ng$ z=bk@gXdI>0HKA4*L)Tq>kzo>j@#{YgcR$pme0^EiSj*Jmsc@)wQmI*uiPrbyCBu|% zUs9yJdDCh;wxO|Z%kEe$r;a^SHwzi4L`C{$QL;);tj;VcT|}jRS%7Hx2i@{wUW9KDOH)L;J!G4R#t9M zP%{)Y3_pKpS9orBvteRpt9ogDLv^_N%8lXN^oj7_-+MSbva3C;t}6}=r7GZ1%C&DO z(l-t>GNvR&SvRsm(<&}$>lz8a`+;|b_q^q8c53_8?Kmj!eSLj}sa)2fa=oe=;D6Pf zc6O>xp<6|~$*QG%ksT72T_fyrRmbWkjI86&sPTmbW#SX_KYz5fwac&PZ~FY=k^H86 z()Q~&Y+g8bdg6oZ3g+YC1s1++Ib)*ESr5jI(;q0lS6;n6Tyn_<*+uFSRE2w?dToGs z0hs@RNB4yn_8c_u(^VCkcc|g@=N2;rnX)e|Y6@s_R=yZqUUkebD-^T!E8o3WtrnZN zZU|e}ln5nGO;-xlSa?Pu7EB3ZgU9eId+E3@U4)xipQnp>XZia4x+B*h%!)H4Rp)gP5@=3P zc4JluXcRZ6sAyq&N|^HcJWaN1(-DmwR3XTmrZ#9`Y}mMEy-CX?#Va?iDpqCpf+S|5 zvNkzk_rA7p*WFL)#?$ybfv(c(u|`r28+6a!BjNss_XiDTGNz=1qDAF&=foV$c7$mt zts}%1uL7F()($Hm{=2XKV|f3Yc32C=M?d-L@J~1YTd3E~f#Y|l&fl?PU7%vG+o2FZG5ClzuZVb!-a_f%>;`-jZ zAJ#<{V63EbfjR(It*SJ8Z`X4N!z0feQygc&--wM~#{8;lDF6oy(|D})f0ywU31 z*V!3Psk9EjTN4=b=S-z@Jq&9o`kw0svmzxSKKF+vxP0eWci*G-!3Fvkxjin>&e#o2 zYg|~E@#P392~#@7dgm;gK-Gv&so3<^d-jC6!b`)`P5oip#cRUo(P!n88u9m=?t1SON)-7$hMjvMIOSxzpz9T)kbR`iip5 z<_jqr8=^=6MHk1~dX!^7W>Eomz;J#tRsTp~GswpfVJ(~;0^2mH;fYhMF{2@+K)4Vr z$3cQ_{o!Nbp$DH*wkgAy5@}geTWKEpBTwuP&+hLHwH3KxYlFa36*Xv=lY{DJ#2*aF z0~_{?v_MPqk?_v<-4y=kmT!bA#Y&;3!_OER%G=-l-cVS#!Il*lu2gAUM!5R=Yr+|6 zmirz#5NfN6g^*VwhfyVlNs%Z8D`WpU_!WgY4(*bl@i)uK)0eNdC3pGuuk`+cGbRVQ zoY7(rfX`rDCtmw|n01Aa%g6u`H18KUz~S}@8z7olTyC4Z?{|MIJo~^84GrVUcYVn4 z&38SqGhDv5G}LSA0(9kD-lI{*Z!Q zy2aBPKgcLSh(cv2YB6K$F<$~!pq7hR)$ZHyd?DkPzTX!)#FzYr&lg{M>Fo5_z^B=b zt8hfVzjnKlXJ6)D8F?>DPABN~J6<1p1}DQK_ud!I_B|JJlg@?CJ+;s5N$`poY*neCdx<1rXGc$XY-h6^ zz4&m&)>;*5qzLJEGR(!RT|*8JHl!0+LI8lV{9pe4Ps5GZzQI4Tcx6-bp77{n&sn%F zH||8}JN$wT831DD$ll#5ZBvqA_ITW|+D%dw#{^HQ`{*Gd#zoZlS(!Wu@o}coadx;y z^dS)&n1Kk9-rCZ5@bhAOi&V;PSh$q#{e4f%U}6Ma8F zuR%un)uDQ|`gBeVg`eH`h-MlF_0sex_&yj;FRloG^EZDS_CEVexaa$K8eso>-}@%B z@$%9ZLw%V>Ez3TOo10R{Ww8c&u2Q?eXy{WRNQy219<0hQ>?=?^D=rCjvL`cDCDgRHDeP(Pw@}bU8>^+x&xOO? zswl#K&5YnEaiB0h42tUBe%6r^ zOCeFXQf6mHuOpwo7WO6kz2K^xojRr_su|VI!)?m*sd*UKAL)wy0nh{^y#EhBu7G-p zAy_1?5K^)1xNm;_ws7~)o(*65%I9sSHyM%}-+Gl@)Q2B>DjeGXd{|v1>DLf(1S%D$ zx#p(d3XPXt8ODaj43dvy1iKt03r0#a2?OweL$W6y-+9oO^~M`tufmC3%Pe6dB4NiA zuG+PyCG39od3Lds3F37ZlB~Q!k77w9#X41}@ywIYhnV1Kind)D>-Z(cjas&keh zEs>xQ&*v5s+wIK-{&&G8CdXG(Rb74d%*2g}GWd~>c;5ctbC;OBhGO5-+%_)Up}qS% z!iPToC*j@idrSDozx$#Z)h5J<8k;gT5{@1l3|qHd9Nzk-%fq(IHilD*{^B(c4^OBZ zOaQKeU9KvonO}oLWfsIqeP>my3mJkj>}qK?r-D`tR60aRNC#n++@tD;VQ^HH3MVew z+z@DR8xYC|%!vvInvneWn5u#Bl1mhPhaP-nufVkGLwU6TV3m^WZfOf0?R^#p#f&Ja zs@AByavLJ>^i#WKMr4JnuGlDv93NV{M=jV-pd6+IGaYi(O<3nMfAz`m@!xyDE%$#1 zI-BL)Kc(Uq<>n>%(*;^kT3)KQ;mmN)kMC9}>!gt78p66w>q4iBmiND)=_0D$`pj3q zDUh-JaPyyhTEj@YB+1Sy{L>uDtEv)lL9;R!&A|N3T^XU3&S& zAx$BvfdQc!YhY7KlCn;6ifH{Mr(dJEV{h+ZIMO{GHea+>fC9(VzkN&+tlyXt=x>0u z=xXdb))o#*Dwo&Pge{xuY+PYS-}qAIWR?8zP5w)4}CX2c}VHSG!q z4>a3Nn6Xk}D-~ua-mbw{#}#T@Cp)(>96i*mn|2{wzvGH<>;K#xP9_wEzx~!N;p9MH z_{)!cSd%FP?NVH&xU7JFgmg+6Q4$LFy4Da!>H8QCgudL&xv+eh;UP*Y|b}dzsfg_WN!OkDkgRig^JWAdkPMajp8tlR|gjh(!$mW^mQbh8ePhQQgNo zJHu7iT^auKUvHM5mAbS~?8&`*Us&o#N)Z-2&dt%L<}f1(2^CkCY3yOS?97SK+|*(Y z47u{UYu+86`1TjWfBe%whnfn7$8v>gEDc4xm~&pLkQqa^2p54tMaKc^-BDH8w}+5; z9-mFU{nzP$WIw-*UK5GQmo}`b-F*7g)CTTEq$R!M@yf<<=kY*d`d!HSC)El}r{4-W z7trikVN(JP|rZ9Ev^=nO}65KDx64J2xaxFRlFcnI5B#XO5kydOma#Zq1p)z z;S?PooRH(y8#V~|zj0MjINH{$?nX7m$>CYhy0k?cA3CXmj1h~!;>|M_1?=@osrDI# zky?))3yW!mVe@5;0?i77!`AraIZdcgz@P6;)Wzq4>b6kO@!s&!KYoAs+t2-(nDRxh z{OOiMVc+x5NfOImRSm$f#+a;-0huZjU{)C^GEoH06D0MKY|}a>z$muvczdYacv<+D zKX`w*>Up@sZFI`=2?R z^2PS>>dTJRr}sP%w%_mu!Az?*v1zBgzNdL#@i_usf$TizX*4MM36ro!n&Ef<2N10ytF(9n8sNRuUs#CI^`# zFgRP(Z?kdBhH&L&>!s$q!;nHQl@*2Z*tH%Z6ly}1@xh)fmo#TMJ#aUJ%KYS4uL)~c zmxjgZ5i#Y6m@r-jgL7(PB>e3c{wjRnruW%r{tu>XS9u;1auJm;rYX%A z%(ZUlzRoU5WdR*X6Ty_wtn&YJvY{`1-^atTu*{dA87f1k`(HI z8DUCJ9IqklOqg*U}hXuwmSLDzDe=64Bs{MD6{sfjOaJ-T;krv+ zv?o#Bk3G9H4EFX|NgmB;i@FgKl#55@lO2O;thQ4XPSgpcDMp!JRU59p@!jFxJMIsk z)iBj~&F`#Rw@$?+ld?r60Ca8_m4=15Ht{M}5r~B`ZK7_*l{#i;O4fl9V;{8X-f`F6 z7O&>>3r(*XW8$f;ttcJ&!Uc#R`^j(Q$I| z*!SVX9Wwh9!cFgdgW)FezF3|xBeEHus!JYv+SZ~oga#3y&{+$l)J2}ycY=IaK5A(61qJcTFH z5Y%N(Xe{%|@evg)kA^MR?Fg&4TpIrBQ=bd}^%tKq=L?i$YE1o{vONq6bHb1&IQ08? z%@s)0k4@2y>yZoA$JR`WQ)!}{*v0r2KW**o`w7cm3wq5MlY@0T6Wh`WNeN@(`~YRB zS31!uxr$0jUyo(WX;DL~@N!dCb@by~e-Ms#^@dM;^pl}dMG38YpAVUNg<(NRY&|{w zCXpEhogq)UTp1$NJOvk-dOs;A;oQ0DP+d^0^rMD3=`76aB9$pPKPJ^U+|v~@!CX?N z14`HHvx}Ofp^HKWq+O~X1dlhpkL1&ddP$A12V*`2@j2Y(b^cf}4> z&g`_xp34;DEGaEC`w-7rnp0EHGy`X1N*=0m#%hZlQvG7W!jwR#>I9Ufd604<;$&ym zRu;=s%~ZQ@tHJxhln?+4It1IN4o9iZ;c%?uczEkuwudV(X$bc}uv6231;T&H6{_Qm z562E24n4giY7tQnyfQg6XXRk&%kjD~6I$B^UZp~jPyg@thL3&lBVvvhJ#UzD|AAIh z%PIQaQ**R+s2D_Q94F`0Nu^2U9<~^;@|eL zesiM%8nDgzn3CU>AZG@KTM$^S0(R0U5g`H~uMSGbtE`g*3F|n`>*kM7FTS9K!M<;Q zeoYyZ!r{}?BOin@Jxl~sVpmd0O~FPiQ*uztnUZCcizP${lky(sT#&S=ttwR*=Ww{? z3;%9*=bPW~PR$B$3wxg5tNO;G2>i1oakjAChb5g;#L$F!4lBe&N!$u~!Q&cmpPOA0 z@->f{Z@XG5%I~rvL!opQ)DV^ zlc+2h5;8-_&-2vySVWDBA(5akCGEItw_O>wT(v!X`zzlHfBw7g3Prhzp{%xE?Z6b3 zP{#I)+J`TMDQRbckea-YRq1m8PUt){fx{USdmTuidmrBU3ES{B`uCbLCWXV;ne+1t z>D+bBlqY4dC&Wh_9DWxFw7iGTcM_c`&uQWpQqyRWCaHx`DQDxH#?JlMm%gQDuL z@A-fN;xhB&-*T2xOUsJ~9U~{wcu9OCA;D?Eq!qBLYEyRSV7Cdig zL1B$oWXvctE-@t=MdvEctLraY-gj>;0%vV%MMMn@ga()O3i8(qii#Sib>M++SH;@s#Ig2?8WRAbD>h@aed=L1Cc`m zV1B9s_j6)K_y^{s;yX#lcewSq2HVrDCcJd1Uzif|OwQndCS&LZyzWichQIj44uzU7 zvd{SkQ??&dTe3npFmi^f+!qvPqj-fFiL{3f4A45;5^55HvF?CiX|QR5Jq9XxQDGFFAN?Bys)&Hpb)iChH{V_8yDR)$Nr z)`xq)|NYP)B)5ZVC)lv%O@c1$wG0jG^$n}hU)47>jxm~!k#&O_JbFg<6eCY)E_>qn zN98}C3|DWxL@7+!pK1}l>1|hq)tfH276p348IuQbk!IzbfI~?U$O<`9%-(@1VMy#r z!a>9*vvLc=n|E9pe*60RP*l3kw&&lTsk7l&bCbQ#RCWgDJR?Vo*U;po@ZiOajA?dS zOfL%C-f&%b;D>jHKiXQMk$G*#lwjbanqfXy(Y_7OE+u1^Eq@I|@^B*fPKvYatt+E`;96~4rvl9bMnv4=@!34%FW zTa?bL->hb?Z-n1|_Xn-2d!Sng!d2C_&g{HKp$Nt=U73<0+5EklbHhYh`gzh|t`zDF z6T_yO^uG?o^o(QgpUoB{40$#BE4Ohd3hz zz2&Pngd47J2pd(1TrSuaQjFM>&8icfRzG@t6nJY!0d8kVUe8V`GZQ7GB#)Y{R~oR` z&HwB3;m2S57fUVXmQ<@fB2xAL>o8@!02lx@i)Bdv-qZy3jEgzZGzgeXX@u9JFxbD( zpMSYb_cedbXY9=M)Rg~y`s8?OveXiU3U|BGRk-gS8#C6v90>;d=keVf?l zv%26#f+>&)0^nRFrNvgffqfRI6HTVWAPW`0!~VQ@ZKDi+ zbvd3^7v&k6(yD}&q(M!pZpzJySFLDiIDE9*ia?4LDvFoXrVSX{&5Glr zvlgl^d!zc3X2aio=y$?HKm3k`g3=}F37W%@sP6NrZ;tJi)c2x(`}vp>J2O%3(23@i zOL~tBiFhvcrw=^z=r`HtYe}y;V{-7D)^8X-IXQA8MYaEbd)ER~WtGSOfCvf*gv9g8 zTR`KLR+^G#rO|4X*_P!tJL@#NZo6Bv)ApDhXWD7EW~ZiRYMYv!vaKzn%*>^9%u-v+ zUByR|iEmBwfhfr3bwR-W{SF*FeEZ!iB*H~FGw^-)eCK|T^ZoAs{NI1Qgp;o{c}x9} z1g!~7_7j{Yf9VOBWD1($g-*M1915C#vFb}%_vPpEn@66&iCd&}^A_dZGZcENO2mG{ z0Hnf%Ye4;|TXjH^cDZVh*Qid)==2fPs)VA%xFmcZ)Dgawp%JlCx*tifD$A8(oq6C1 zPV${+;pa(EGUIW6{&=}-##p%q!D7)dqai>9$#J--?T3+ojWA{&roi@44pK=K=MGS^ zHNxrx7bGQ}j~zczN;hp$s-QjJZdWS5Aj|t+0Rlg25*!*{l@PT*7&r)Y1w}s)|Ud z>>B$CPBv!bh=n9xhD;kpMN4H`VIjPQGUcoF{{>MDM$lIb2r%@5;HjD#D0o2E2(?sr zI0DH4m?L-O7L{1xH2LRyex{N!q%Rio+?KwT4c|@?~zplRllH8azXn}r8)V@ z8xiV}pBEz&5y2TAfeL9@zG+_EckDC(+BWU@Eg&Qpf(5KWZ0z>~@EM154)a8TC)|Mu z&`5{8^U`bb{6q7k74CV|Pc}h1rsd2Nl_VfdO35Xg)X{7$zIZ-7Gc7)5Bem3xA_C)y zA|ey#FJ1P*O5J}cx4i;PJ^-x*x!L26xEksUo5A2~soDxmJ8Ic!Dd)*9u$*gfQvXTU zID+$ggt@-|#+#BiWvWb`I8N$LpHMIUBpgUGkdB3p3a1c7SqpGQ!UZUNL;_wa1J{tk z8m@Bd2?Yn>8iv^MwA2{TykOY|Wq6Gf0b&3sq z)h|l%$;^tU*#~tpn}2px(h5paO2$`NLCMpxudV+qG#4Qip;4JLKVAL#GMh_XlCE9@ zChwk<^X7uv|L#2FOl@{mX81r+OzLSi6PN^~xrBEb2jRq1vx3tR4-i3(^7J1s%h$S51 zwE$yEG_umgr^pW#E=f#+qJ4U%B%_4M_`Ede6Qkfz423z;Z&$}yw{AZybMBiXZ@!IC*M{S=@8CfsdL09k zWIa50@bX=LvKhtU`LfuU=6XVUG?H?K!Ia4iAOJ9y_-aVcMDr8{k);u~7nDfQY7Xl>5nEWK4k50mRtNBWfX>c`{j2?T{V_mJ5%X9Z+>oZ{T zA?D4S7qh2y%c~8}atKEBnrXWo6O^7FwkzP&pw#D~swn`J=RXLk%|x2NqEFsKT9$ak zm>)pW{TSq8uR>A;I9MP=p$r+p}j>w_}hsznRjIjg*m9h1pocC^X8UR6%b& z`)B$1)#t$L!V4d!Rn~v^1W10rJD?;0O@Q+IW?%G`BcEYK68861>r-IrhbHwbKXLel z)~4D}T29zJf|FLCR_H`+sHO&@RoAKed2S8PvYq>2_6wI67X3}`xOKLoo+rTX(G#b# z4z-04_M0>c;lShz8*5O&=_FJ+frvlPhDBzWy!i6#vSQgvRhD{RIdUEW%#E{{q6x-vuZW$@39yQ zpG%l;&8C06y3Z3Jt>Dw&F)5?&qmH@ptY4o}NqsNe_9~BQUfA3x?|DnXthdN}x@xPS zfrgzJi2-yY0O`2Vv4ylr$(+(R|4a#8^OpLUKCb(DPVTor>Wz*8H3Su0^xC^p509A{ zHx(eBdnoMV<;al^GbPN8w6Vv)se?rUd;&*ECY0pEgOa7_!y6az{IwaU%AgQBpXE`CR4cq^PWL!GM;pk!-?fKv-9Nl>z!bS*4U)L|wd%!8_+r|-R4_LqI9 zyv&=BkhKwZa>%nfgrYFP{{kEbV~!wf%HCSMz_4|6qNr5te`Xwsb$#?YwfNj>YF)fpx@wPKy%GXFI2^f}yZO;GAFt?=aGc^$|%wEDBJP?U7J zOuPH1C{68<1Bdo0Gu)X){g#(YYQ;UtwNALNFLEAx-;-MT=j^NO^hTLV59zk0V_Sl58Pr z(Ykc=KCPfMsiGBZ`nf!9xokJVF~boT>s3_Jyl1ZiCU4H9o+vffK|Qq}rWIiPY)2{0 z2j#x?d**Qhl+_^)BKEsz(PD{BOO$Do3*Zu0jXIYnp*uxd734LKj*3Uk$(6ElX|Yu8 z-zLLhH)kArN(Pb&>@HPd9H{RizO{Ri^`l-!#>GNf9|`dLC@a%Rq*)A2%EIXy<;nZ+ zmz9g(R{Uf@Ao3`Ix3z0pg+^!I9|J^vP7TQB7T=PzFy^ONH)y4vt*AEP z5BDpA&0Q4*h-B}rJ)o$v{z$ul+gI>k>u zS~y+4+qzYG1u^8qjJ-GSiII$q%h7>j<}vOQY;-u$Tl2Wt$07^-0VR*}UMgwcyH^8~ zH)>K(#e3G(1p4~XVF51-ew`*jy8=#gyg3nipOlk)XHbZ*EL-xKY~Q{`X5Bd(*@YdD z%;B7i7wiKH{q?%Fpn`3cc4@h(f(0ojD;b{i(Ma)!63mE$PaZ#6#v{xl5PqNx4UURS zly_f!L!MnQPi2W?D6QSnt?z|f@|pym*?13dzE*#%U+lKu0Fw`DrJhX<&OkqePB8AA;50#ZvEbzO2v9EVhv6>w(b82CH7ZJS zvZo>b{D`C@chc5Pn`H;WcOy}B)rH8y2AX!-{GWGgQlvvp-56)d6 zUw{6Y;wPyNJKN|~sk3<eLk5tiJtHZAZRo0g0O`J4sY4M8X?#6j*f4>5z zo@qY`n|bh-P!pU7=D=YgNxD8HQhAo5ZkLY{R8%$TT@ft6YN2x#DG%BerQ1Ecl+ zte*);vKq_3Kt21&BuiN|D0{`5NdHkh9GQW=MRTvMt-4eShH4zKGY!fAa10CpVUjWyXz2)Oa!LOmbe?I z`!$subM?f6WyPPY*Zuu{dszTezXZ*DR#u!!ZE9@Dpx-r}g>}=wyI62)mC{f+VSM$) zIwa(B%58`%Dn?BCoSSCDPwtrFC#w(vslt2Pqd+}TNexJSuXbC4kk7*{1s`Nit{#<; z^iY51CGDP*eO)6F?!N8C{qA^AbIHEgWu1Jd_U}x;dJ-ZP5krJf+IS>RB`A?yrK8z_ z($i8+@VRTu>XDWbw>T^|_0j&$OS*f;%NCe?V9k4;b=4Nq-mE-w+Jz$x(4GKiJsf_2 zIDAz3PtvcHAT&dgy9!Dx7zsdY-{rC$**Yu z+JRA@c5G(CB}*->ui-7*^9-1`+|ebt4@#<8*WQrn9~iv6t_EgB#N9B!pFWRl8lcuo zyQ8Moaq1lH-uDEf{(Ntfca; zOq`b7>#9$u!g)7CmEWVAT01z;tLHnP?Lt-}+U%R)b7|Myfs$kG%!;Iv!69KKV{>x7 z+BMDYS$nE)z)?(d;xzSaY4HiALqtFz61=qQGTl*6ecU_`_uWxUJ5Z|eZlrF4GAB28 zKvGF_P6r&tG#_45PiNIBMLn6Pl!>F5d7?YIPUO;14j)#E*?$w^sigTP+V%%D#WbhG9ra|IAv?~oGtUVRa=6Hu6tkt1lPxt9f z#|9~8H{OP(o53D#=;tUK^yC-J7qD(VBX~U=&JsI;L$Q#UvnEUN1Jz zG5>N2bLe>JUfda^m_Kr|HTCrI3sRx6D*XyT$zZVDiTSaEp!|^&JNUJpn*mbKF-eJ= gxb3;K4(v1i55bP}mqr`m&;S4c07*qoM6N<$f*W77rT_o{ literal 0 HcmV?d00001 From 6c6adbd0d9d25292461ee810ee2ade4e0aece182 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 13:44:20 -0400 Subject: [PATCH 057/341] fixed incorrect frame assigned --- internal/stats/prepare/session/card.go | 2 +- internal/stats/prepare/session/constants.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index e4aaef91..91d09af0 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -107,7 +107,7 @@ func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, ca sFrame = *session.StatsFrame } if career.StatsFrame != nil { - sFrame = *career.StatsFrame + cFrame = *career.StatsFrame } var blocks []common.StatsBlock[BlockData] diff --git a/internal/stats/prepare/session/constants.go b/internal/stats/prepare/session/constants.go index 4c3e404b..6e8a4533 100644 --- a/internal/stats/prepare/session/constants.go +++ b/internal/stats/prepare/session/constants.go @@ -7,7 +7,7 @@ import ( var unratedOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagWinrate}, {common.TagWN8}, {common.TagAvgDamage, common.TagDamageRatio}} var ratingOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagWinrate}, {common.TagRankedRating}, {common.TagAvgDamage, common.TagDamageRatio}} -var vehicleBlocks = []common.Tag{common.TagBattles, common.TagAvgDamage, common.TagDamageRatio, common.TagWinrate, common.TagWN8} +var vehicleBlocks = []common.Tag{common.TagBattles, common.TagWinrate, common.TagAvgDamage, common.TagWN8} var highlights = []common.Highlight{common.HighlightBattles, common.HighlightWN8, common.HighlightAvgDamage} type Cards struct { From cf9e89707cbf3528280d5529bd6d575c950a1114 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 13:54:31 -0400 Subject: [PATCH 058/341] fixed realm --- internal/stats/render/session/cards.go | 79 ++++++++------------------ 1 file changed, 25 insertions(+), 54 deletions(-) diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 7d18674f..146d4ae9 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -101,6 +101,31 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } } + { // render the footer first to make sure the card is wide enough + var footer []string + switch strings.ToLower(session.Realm) { + case "na": + footer = append(footer, "North America") + case "eu": + footer = append(footer, "Europe") + case "as": + footer = append(footer, "Asia") + } + if session.LastBattleTime.Unix() > 1 { + sessionTo := session.PeriodEnd.Format("Jan 2") + sessionFrom := session.PeriodStart.Format("Jan 2") + if sessionFrom == sessionTo { + footer = append(footer, sessionTo) + } else { + footer = append(footer, sessionFrom+" - "+sessionTo) + } + } + + if len(footer) > 0 { + segments.AddFooter(common.NewFooterCard(strings.Join(footer, " • "))) + } + } + frameWidth := secondaryCardWidth + primaryCardWidth if secondaryCardWidth > 0 && primaryCardWidth > 0 { frameWidth += frameStyle().Gap @@ -148,29 +173,6 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } segments.AddContent(common.NewBlocksContent(frameStyle(), columns...)) - var footer []string - switch session.Realm { - case "na": - footer = append(footer, "North America") - case "eu": - footer = append(footer, "Europe") - case "as": - footer = append(footer, "Asia") - } - if session.LastBattleTime.Unix() > 1 { - sessionTo := session.PeriodEnd.Format("Jan 2") - sessionFrom := session.PeriodStart.Format("Jan 2") - if sessionFrom == sessionTo { - footer = append(footer, sessionTo) - } else { - footer = append(footer, sessionFrom+" - "+sessionTo) - } - } - - if len(footer) > 0 { - segments.AddFooter(common.NewFooterCard(strings.Join(footer, " • "))) - } - return segments, nil } @@ -327,34 +329,3 @@ func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width ) } } - -// func uniqueBlockWN8(stats prepare.StatsBlock[period.BlockData]) common.Block { -// var blocks []common.Block - -// valueStyle, labelStyle := style.block(stats) -// valueBlock := common.NewTextContent(valueStyle, stats.Value.String()) - -// ratingColors := common.GetWN8Colors(stats.Value.Float()) -// if stats.Value.Float() <= 0 { -// ratingColors.Content = common.TextAlt -// ratingColors.Background = common.TextAlt -// } - -// iconTop := common.AftermathLogo(ratingColors.Background, common.DefaultLogoOptions()) -// iconBlockTop := common.NewImageContent(common.Style{Width: float64(iconTop.Bounds().Dx()), Height: float64(iconTop.Bounds().Dy())}, iconTop) - -// style.blockContainer.Gap = 10 -// blocks = append(blocks, common.NewBlocksContent(style.blockContainer, iconBlockTop, valueBlock)) - -// if stats.Value.Float() >= 0 { -// labelStyle.FontColor = ratingColors.Content -// blocks = append(blocks, common.NewBlocksContent(common.Style{ -// PaddingY: 5, -// PaddingX: 10, -// BorderRadius: 15, -// BackgroundColor: ratingColors.Background, -// }, common.NewTextContent(labelStyle, common.GetWN8TierName(stats.Value.Float())))) -// } - -// return common.NewBlocksContent(common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, Gap: 10, PaddingY: 5}, blocks...) -// } From 02d61919c2ece4b6374e72d69344f9a44de67f45 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 13:55:08 -0400 Subject: [PATCH 059/341] removed comment --- internal/stats/render/session/cards.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 146d4ae9..a481f897 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -101,7 +101,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } } - { // render the footer first to make sure the card is wide enough + { var footer []string switch strings.ToLower(session.Realm) { case "na": From 2171d0fa711a9052a8026d731a646db6d9b3bfc8 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 13:59:01 -0400 Subject: [PATCH 060/341] tweaked the rating card style --- internal/stats/render/session/cards.go | 8 ++++---- internal/stats/render/session/constants.go | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index a481f897..9d3138de 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -144,10 +144,10 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // overview cards if len(cards.Unrated.Overview.Blocks) > 0 { - primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, primaryCardWidth)) + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, primaryCardWidth, overviewCardStyle(primaryCardWidth))) } if len(cards.Rating.Overview.Blocks) > 0 { - primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, primaryCardWidth)) + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, primaryCardWidth, overviewRatingCardStyle(primaryCardWidth))) } // highlights @@ -266,7 +266,7 @@ func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string] return common.NewBlocksContent(style, common.NewBlocksContent(vehicleBlocksRowStyle(0), content...)) } -func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, cardWidth float64) common.Block { +func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, cardWidth float64, style common.Style) common.Block { var content []common.Block for _, column := range card.Blocks { var columnContent []common.Block @@ -285,7 +285,7 @@ func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, } content = append(content, common.NewBlocksContent(overviewColumnStyle(0), columnContent...)) } - return common.NewBlocksContent(overviewCardStyle(cardWidth), content...) + return common.NewBlocksContent(style, content...) } func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width float64) common.Block { diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go index 5c550a43..1a61b939 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/constants.go @@ -89,6 +89,12 @@ func overviewCardStyle(width float64) common.Style { return style } +func overviewRatingCardStyle(width float64) common.Style { + style := overviewCardStyle(width) + style.AlignItems = common.AlignItemsCenter + return style +} + func statsBlockStyle(width float64) common.Style { return common.Style{ JustifyContent: common.JustifyContentCenter, From 8f8e82985b44a4be6e28e84f3d13802913b298ef Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 14:01:08 -0400 Subject: [PATCH 061/341] een gaps --- internal/stats/render/session/constants.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go index 1a61b939..ff4e50eb 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/constants.go @@ -42,7 +42,7 @@ var ( ) func frameStyle() common.Style { - return common.Style{Gap: 20, Direction: common.DirectionHorizontal} + return common.Style{Gap: 10, Direction: common.DirectionHorizontal} } var ( @@ -69,7 +69,7 @@ func overviewSpecialRatingPillStyle(color color.Color) common.Style { func overviewColumnStyle(width float64) common.Style { return common.Style{ - Gap: 5, + Gap: 10, Width: width, AlignItems: common.AlignItemsCenter, Direction: common.DirectionVertical, From 93d1e1b7da899058d39c5463d5c7961f04eeb208 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 14:06:22 -0400 Subject: [PATCH 062/341] sorting vehicles --- internal/stats/prepare/session/card.go | 33 +++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index 91d09af0..584d6aa6 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -1,6 +1,8 @@ package session import ( + "slices" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/frame" @@ -56,15 +58,22 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] } // Rating battles vehicles - for id, data := range session.RatingBattles.Vehicles { - glossary := glossary[id] - glossary.ID = id + var ratingVehicles []frame.VehicleStatsFrame + for _, vehicle := range session.RatingBattles.Vehicles { + ratingVehicles = append(ratingVehicles, vehicle) + } + slices.SortFunc(ratingVehicles, func(a, b frame.VehicleStatsFrame) int { + return int(b.LastBattleTime.Unix() - a.LastBattleTime.Unix()) + }) + for _, data := range ratingVehicles { + glossary := glossary[data.VehicleID] + glossary.ID = data.VehicleID card, err := makeVehicleCard( []common.Tag{common.TagWN8}, common.CardTypeRatingVehicle, data, - career.RatingBattles.Vehicles[id], + career.RatingBattles.Vehicles[data.VehicleID], options.Printer(), options.Locale(), glossary, @@ -74,20 +83,28 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] } cards.Rating.Vehicles = append(cards.Rating.Vehicles, card) } + // Regular battles vehicles - for id, data := range session.RegularBattles.Vehicles { + var unratedVehicles []frame.VehicleStatsFrame + for _, vehicle := range session.RegularBattles.Vehicles { + unratedVehicles = append(unratedVehicles, vehicle) + } + slices.SortFunc(unratedVehicles, func(a, b frame.VehicleStatsFrame) int { + return int(b.LastBattleTime.Unix() - a.LastBattleTime.Unix()) + }) + for _, data := range unratedVehicles { if data.Battles < 1 { continue } - glossary := glossary[id] - glossary.ID = id + glossary := glossary[data.VehicleID] + glossary.ID = data.VehicleID card, err := makeVehicleCard( vehicleBlocks, common.CardTypeVehicle, data, - career.RegularBattles.Vehicles[id], + career.RegularBattles.Vehicles[data.VehicleID], options.Printer(), options.Locale(), glossary, From 59ea850555de25de1d4e5a500887036a1a8fe882 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 14:07:46 -0400 Subject: [PATCH 063/341] added vehicle limits --- internal/stats/prepare/session/card.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index 584d6aa6..9debc390 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -66,6 +66,10 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] return int(b.LastBattleTime.Unix() - a.LastBattleTime.Unix()) }) for _, data := range ratingVehicles { + if len(cards.Rating.Vehicles) >= 5 { + break + } + glossary := glossary[data.VehicleID] glossary.ID = data.VehicleID @@ -93,6 +97,9 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] return int(b.LastBattleTime.Unix() - a.LastBattleTime.Unix()) }) for _, data := range unratedVehicles { + if len(cards.Unrated.Vehicles) >= 5 { + break + } if data.Battles < 1 { continue } From ff5f362a0f51d4e19570c73dd71f26b0e3a47b19 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 14:16:43 -0400 Subject: [PATCH 064/341] adjusted session lookup, added blank overview card --- internal/stats/fetch/multisource.go | 4 +++- internal/stats/prepare/session/card.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index ddb62321..8f91295b 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -280,9 +280,11 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session } sessionBefore := sessionStart - if time.Since(sessionStart).Hours() >= 24 { + if time.Since(sessionStart).Hours() >= 48 { // 3 days would mean today and yest, so before the reset 2 days ago and so on sessionBefore = sessionBefore.Add(time.Hour * 24 * -1) + } else { + sessionStart = time.Now() } var accountSnapshot retry.DataWithErr[database.AccountSnapshot] diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index 9debc390..9eace0ea 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -42,7 +42,7 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] cards.Rating.Overview = card } // Regular battles overview - if session.RegularBattles.Battles > 0 { + if session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 { card, err := makeOverviewCard( unratedOverviewBlocks, session.RegularBattles.StatsFrame, From bb00947971631c68277af71fa11c026fdb6b9028 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 14:17:33 -0400 Subject: [PATCH 065/341] typo --- internal/stats/fetch/multisource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 8f91295b..2f7e144b 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -284,7 +284,7 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session // 3 days would mean today and yest, so before the reset 2 days ago and so on sessionBefore = sessionBefore.Add(time.Hour * 24 * -1) } else { - sessionStart = time.Now() + sessionBefore = time.Now() } var accountSnapshot retry.DataWithErr[database.AccountSnapshot] From a2702e690cc7ad49d049d00b7fa08cdcad05e0ff Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 16 Jun 2024 14:22:17 -0400 Subject: [PATCH 066/341] moved code, removed color from empty pill --- internal/stats/fetch/multisource.go | 2 +- internal/stats/render/session/blocks.go | 196 +++++++----------------- internal/stats/render/session/cards.go | 42 ----- 3 files changed, 54 insertions(+), 186 deletions(-) diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 2f7e144b..0da01e5f 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -270,7 +270,7 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session apply(&options) } - // sessions and period stats are tracked differenttly + // sessions and period stats are tracked differently // we look up the latest session _before_ sessionBefore, not after sessionStart if sessionStart.IsZero() { sessionStart = time.Now() diff --git a/internal/stats/render/session/blocks.go b/internal/stats/render/session/blocks.go index 85ea47e8..1034bbc6 100644 --- a/internal/stats/render/session/blocks.go +++ b/internal/stats/render/session/blocks.go @@ -1,145 +1,55 @@ package session -// type convertOptions struct { -// showSessionStats bool -// showCareerStats bool -// showLabels bool -// showIcons bool -// highlightBlockIndex int -// } - -// func newVehicleCard(style render.Style, card session.Card, sizes map[int]float64, opts convertOptions) (render.Block, error) { -// if card.Type == dataprep.CardTypeRatingVehicle { -// return slimVehicleCard(style, card, sizes, opts) -// } - -// return defaultVehicleCard(style, card, sizes, opts) -// } - -// func defaultVehicleCard(style render.Style, card session.Card, sizes map[int]float64, opts convertOptions) (render.Block, error) { -// blocks, err := statsBlocksToCardBlocks(card.Blocks, sizes, opts) -// if err != nil { -// return render.Block{}, err -// } - -// cardContentBlocks := []render.Block{newCardTitle(card.Title)} -// contentWidth := style.Width - style.PaddingX*2 - -// statsRowBlock := render.NewBlocksContent(statsRowStyle(contentWidth), blocks...) -// cardContentBlocks = append(cardContentBlocks, statsRowBlock) - -// return render.NewBlocksContent(style, cardContentBlocks...), nil -// } - -// func slimVehicleCard(style render.Style, card session.Card, sizes map[int]float64, opts convertOptions) (render.Block, error) { -// opts.highlightBlockIndex = -1 -// opts.showCareerStats = false -// opts.showLabels = false -// opts.showIcons = true - -// blocks, err := statsBlocksToCardBlocks(card.Blocks, sizes, opts) -// if err != nil { -// return render.Block{}, err -// } - -// titleBlock := render.NewTextContent(ratingVehicleTitleStyle, card.Title) -// statsRowBlock := render.NewBlocksContent(statsRowStyle(0), blocks...) - -// containerStyle := style -// containerStyle.Direction = render.DirectionHorizontal -// containerStyle.JustifyContent = render.JustifyContentSpaceBetween - -// return render.NewBlocksContent(containerStyle, titleBlock, statsRowBlock), nil -// } - -// func statsBlocksToCardBlocks(stats []session.StatsBlock, blockWidth map[int]float64, opts ...convertOptions) ([]render.Block, error) { -// var options convertOptions = convertOptions{ -// showSessionStats: true, -// showCareerStats: true, -// showLabels: true, -// showIcons: true, -// highlightBlockIndex: 0, -// } -// if len(opts) > 0 { -// options = opts[0] -// } - -// var content []render.Block -// for index, statsBlock := range stats { -// blocks := make([]render.Block, 0, 3) -// if options.showSessionStats { -// if options.showIcons && statsBlock.Tag != dataprep.TagBattles { -// blocks = append(blocks, newStatsBlockRow(defaultBlockStyle.session, statsBlock.Session.String, comparisonIconFromBlock(statsBlock))) -// } else { -// blocks = append(blocks, render.NewTextContent(defaultBlockStyle.session, statsBlock.Session.String)) -// } -// } -// if options.showCareerStats && statsBlock.Career.String != "" { -// if options.showIcons && statsBlock.Tag != dataprep.TagBattles { -// blocks = append(blocks, newStatsBlockRow(defaultBlockStyle.career, statsBlock.Career.String, blockToWN8Icon(statsBlock.Career, statsBlock.Tag))) -// } else { -// blocks = append(blocks, render.NewTextContent(defaultBlockStyle.career, statsBlock.Career.String)) -// } -// } -// if options.showLabels && statsBlock.Tag != dataprep.TagBattles { -// if options.showIcons { -// blocks = append(blocks, newStatsBlockRow(defaultBlockStyle.label, statsBlock.Label, blankIconBlock)) -// } else { -// blocks = append(blocks, render.NewTextContent(defaultBlockStyle.label, statsBlock.Label)) -// } -// } - -// containerStyle := defaultStatsBlockStyle(blockWidth[index]) -// if index == options.highlightBlockIndex { -// containerStyle = highlightStatsBlockStyle(blockWidth[index]) -// } -// content = append(content, render.NewBlocksContent(containerStyle, blocks...)) -// } -// return content, nil -// } - -// func newStatsBlockRow(style render.Style, value string, icon render.Block) render.Block { -// return render.NewBlocksContent( -// render.Style{Direction: render.DirectionHorizontal, AlignItems: render.AlignItemsCenter}, -// icon, -// render.NewTextContent(style, value), -// ) -// } - -// func newCardTitle(label string) render.Block { -// return render.NewTextContent(defaultBlockStyle.career, label) -// } - -// func comparisonIconFromBlock(block session.StatsBlock) render.Block { -// if !stats.ValueValid(block.Session.Value) || !stats.ValueValid(block.Career.Value) { -// return blankIconBlock -// } - -// if block.Tag == dataprep.TagWN8 { -// // WN8 icons need to show the color -// return blockToWN8Icon(block.Session, block.Tag) -// } - -// var icon image.Image -// var iconColor color.Color -// if block.Session.Value > block.Career.Value { -// icon, _ = assets.GetImage("images/icons/chevron-up-single") -// iconColor = color.RGBA{R: 0, G: 255, B: 0, A: 255} -// } -// if block.Session.Value < block.Career.Value { -// icon, _ = assets.GetImage("images/icons/chevron-down-single") -// iconColor = color.RGBA{R: 255, G: 0, B: 0, A: 255} -// } -// if icon == nil { -// return blankIconBlock -// } - -// return render.NewImageContent(render.Style{Width: float64(iconSize), Height: float64(iconSize), BackgroundColor: iconColor}, icon) -// } - -// func blockToWN8Icon(value dataprep.Value, tag dataprep.Tag) render.Block { -// if tag != dataprep.TagWN8 || !stats.ValueValid(value.Value) { -// return blankIconBlock -// } -// return render.NewImageContent(render.Style{Width: float64(iconSize), Height: float64(iconSize), BackgroundColor: shared.GetWN8Colors(int(value.Value)).Background}, wn8Icon) -// } +import ( + "image/color" + + prepare "github.com/cufee/aftermath/internal/stats/prepare/common" + "github.com/cufee/aftermath/internal/stats/prepare/session" + "github.com/cufee/aftermath/internal/stats/render/common" +) + +func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width float64) common.Block { + switch block.Tag { + case prepare.TagWN8: + ratingColors := common.GetWN8Colors(block.Value.Float()) + if block.Value.Float() <= 0 { + ratingColors.Content = common.TextAlt + ratingColors.Background = common.TextAlt + } + + var column []common.Block + iconTop := common.AftermathLogo(ratingColors.Background, common.DefaultLogoOptions()) + column = append(column, common.NewImageContent(common.Style{Width: specialRatingIconSize, Height: specialRatingIconSize}, iconTop)) + + pillColor := ratingColors.Background + if block.Value.Float() < 0 { + pillColor = color.Transparent + } + column = append(column, common.NewBlocksContent(overviewColumnStyle(width), + common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + common.NewBlocksContent( + overviewSpecialRatingPillStyle(pillColor), + common.NewTextContent(overviewSpecialRatingLabelStyle(ratingColors.Content), common.GetWN8TierName(block.Value.Float())), + ), + )) + return common.NewBlocksContent(specialRatingColumnStyle, column...) + + case prepare.TagRankedRating: + var column []common.Block + icon, ok := getRatingIcon(block.Value) + if ok { + column = append(column, icon) + } + column = append(column, common.NewBlocksContent(overviewColumnStyle(width), + common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + )) + return common.NewBlocksContent(specialRatingColumnStyle, column...) + + default: + return common.NewBlocksContent(statsBlockStyle(width), + common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + // common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String()), + common.NewTextContent(vehicleBlockStyle.label, block.Label), + ) + } +} diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 9d3138de..2f2ac043 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -287,45 +287,3 @@ func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, } return common.NewBlocksContent(style, content...) } - -func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width float64) common.Block { - switch block.Tag { - case prepare.TagWN8: - ratingColors := common.GetWN8Colors(block.Value.Float()) - if block.Value.Float() <= 0 { - ratingColors.Content = common.TextAlt - ratingColors.Background = common.TextAlt - } - - var column []common.Block - iconTop := common.AftermathLogo(ratingColors.Background, common.DefaultLogoOptions()) - column = append(column, common.NewImageContent(common.Style{Width: specialRatingIconSize, Height: specialRatingIconSize}, iconTop)) - - column = append(column, common.NewBlocksContent(overviewColumnStyle(width), - common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), - common.NewBlocksContent( - overviewSpecialRatingPillStyle(ratingColors.Background), - common.NewTextContent(overviewSpecialRatingLabelStyle(ratingColors.Content), common.GetWN8TierName(block.Value.Float())), - ), - )) - return common.NewBlocksContent(specialRatingColumnStyle, column...) - - case prepare.TagRankedRating: - var column []common.Block - icon, ok := getRatingIcon(block.Value) - if ok { - column = append(column, icon) - } - column = append(column, common.NewBlocksContent(overviewColumnStyle(width), - common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), - )) - return common.NewBlocksContent(specialRatingColumnStyle, column...) - - default: - return common.NewBlocksContent(statsBlockStyle(width), - common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), - // common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String()), - common.NewTextContent(vehicleBlockStyle.label, block.Label), - ) - } -} From eed1923e6d7885439516f19075d464f1ce2f4343 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 17 Jun 2024 13:06:15 -0400 Subject: [PATCH 067/341] added secondary WN8 icon --- internal/stats/render/common/logo.go | 10 +++ internal/stats/render/session/blocks.go | 11 +++ internal/stats/render/session/cards.go | 91 +++++++++++++--------- internal/stats/render/session/constants.go | 35 +++------ 4 files changed, 88 insertions(+), 59 deletions(-) diff --git a/internal/stats/render/common/logo.go b/internal/stats/render/common/logo.go index 07713e31..8de5033e 100644 --- a/internal/stats/render/common/logo.go +++ b/internal/stats/render/common/logo.go @@ -33,6 +33,16 @@ func DefaultLogoOptions() LogoSizingOptions { } } +func SmallLogoOptions() LogoSizingOptions { + return LogoSizingOptions{ + Gap: 2, + Jump: 6, + Lines: 5, + LineStep: 8, + LineWidth: 6, + } +} + func AftermathLogo(fillColor color.Color, opts LogoSizingOptions) image.Image { ctx := gg.NewContext(opts.Width(), opts.Height()) for line := range opts.Lines { diff --git a/internal/stats/render/session/blocks.go b/internal/stats/render/session/blocks.go index 1034bbc6..cc360907 100644 --- a/internal/stats/render/session/blocks.go +++ b/internal/stats/render/session/blocks.go @@ -3,6 +3,7 @@ package session import ( "image/color" + "github.com/cufee/aftermath/internal/stats/frame" prepare "github.com/cufee/aftermath/internal/stats/prepare/common" "github.com/cufee/aftermath/internal/stats/prepare/session" "github.com/cufee/aftermath/internal/stats/render/common" @@ -53,3 +54,13 @@ func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width ) } } + +func vehicleWN8Icon(wn8 frame.Value) common.Block { + ratingColors := common.GetWN8Colors(wn8.Float()) + if wn8.Float() <= 0 { + ratingColors.Content = common.TextAlt + ratingColors.Background = common.TextAlt + } + iconTop := common.AftermathLogo(ratingColors.Background, common.SmallLogoOptions()) + return common.NewImageContent(common.Style{Width: vehicleWN8IconSize, Height: vehicleWN8IconSize}, iconTop) +} diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 2f2ac043..d5949d10 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -1,10 +1,12 @@ package session import ( + "fmt" "strings" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/frame" prepare "github.com/cufee/aftermath/internal/stats/prepare/common" "github.com/cufee/aftermath/internal/stats/prepare/session" "github.com/cufee/aftermath/internal/stats/render/common" @@ -13,6 +15,16 @@ import ( ) func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []database.UserSubscription, opts render.Options) (render.Segments, error) { + var ( + // primary card + shouldRenderUnratedOverview = session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 + shouldRenderUnratedHighlights = len(session.RegularBattles.Vehicles) > 3 + shouldRenderRatingOverview = session.RatingBattles.Battles > 0 + shouldRenderRatingVehicles = len(cards.Unrated.Vehicles) == 0 + // secondary card + shouldRenderUnratedVehicles = len(cards.Unrated.Vehicles) > 0 + ) + var segments render.Segments var primaryColumn []common.Block var secondaryColumn []common.Block @@ -20,7 +32,8 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // Calculate minimal card width to fit all the content primaryCardBlockSizes := make(map[string]float64) secondaryCardBlockSizes := make(map[string]float64) - var primaryCardWidth, secondaryCardWidth, totalFrameWidth float64 + var primaryCardWidth float64 = minPrimaryCardWidth + var secondaryCardWidth, totalFrameWidth float64 // rating and unrated battles > 0 unrated battles > 0 rating battles > 0 // [title card ] | [vehicle] [title card ] | [vehicle] [title card ] // [overview unrated] | [vehicle] OR [overview unrated] | [vehicle] OR [overview rating ] @@ -39,8 +52,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } } - // unrated overview - show if there are no rating battles (so the card is not empty), or there are battles - if session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 { + if shouldRenderUnratedOverview { for _, column := range cards.Unrated.Overview.Blocks { presetBlockWidth, _ := overviewColumnBlocksWidth(column, overviewStatsBlockStyle.session, overviewStatsBlockStyle.career, overviewStatsBlockStyle.label, overviewColumnStyle(0)) for key, width := range presetBlockWidth { @@ -48,12 +60,17 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } } } - // rating overview - if session.RatingBattles.Battles > 0 { - // + + if shouldRenderRatingOverview { + for _, column := range cards.Unrated.Overview.Blocks { + presetBlockWidth, _ := overviewColumnBlocksWidth(column, overviewStatsBlockStyle.session, overviewStatsBlockStyle.career, overviewStatsBlockStyle.label, overviewColumnStyle(0)) + for key, width := range presetBlockWidth { + primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) + } + } } // rating vehicle cards go on the primary block - only show if there are no unrated battles/vehicles - if len(cards.Unrated.Vehicles) == 0 { + if shouldRenderRatingVehicles { for _, card := range cards.Rating.Vehicles { // [title] [session] titleSize := common.MeasureString(card.Title, *ratingVehicleCardTitleStyle.Font) @@ -68,12 +85,12 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } } // highlighted vehicles go on the primary block - if len(cards.Unrated.Highlights) > len(cards.Unrated.Vehicles) { + if shouldRenderUnratedHighlights { for _, card := range cards.Unrated.Highlights { // [card label] [session] // [title] [label] - labelSize := common.MeasureString(card.Meta, *vehicleCardTitleStyle.Font) - titleSize := common.MeasureString(card.Title, *vehicleCardTitleStyle.Font) + labelSize := common.MeasureString(card.Meta, *vehicleCardTitleTextStyle.Font) + titleSize := common.MeasureString(card.Title, *vehicleCardTitleTextStyle.Font) presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlocksRowStyle(0)) // add the gap and card padding, the gap here accounts for title/label being inline with content contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)) + highlightCardStyle(0).PaddingX*2 + common.Max(titleSize.TotalWidth, labelSize.TotalWidth) @@ -84,7 +101,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } } } - { // unrated vehicles go on the secondary block + if shouldRenderUnratedVehicles { // unrated vehicles go on the secondary block for _, card := range cards.Unrated.Vehicles { // [ label ] // [session] @@ -92,7 +109,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlocksRowStyle(0)) contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)-1) + vehicleCardStyle(0).PaddingX*2 - titleSize := common.MeasureString(card.Title, *vehicleCardTitleStyle.Font) + titleSize := common.MeasureString(card.Title, *vehicleCardTitleTextStyle.Font) secondaryCardWidth = common.Max(secondaryCardWidth, contentWidth, titleSize.TotalWidth+vehicleCardStyle(0).PaddingX*2) for key, width := range presetBlockWidth { @@ -143,27 +160,29 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card ) // overview cards - if len(cards.Unrated.Overview.Blocks) > 0 { + if shouldRenderUnratedOverview { primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, primaryCardWidth, overviewCardStyle(primaryCardWidth))) } - if len(cards.Rating.Overview.Blocks) > 0 { + if shouldRenderRatingOverview { primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, primaryCardWidth, overviewRatingCardStyle(primaryCardWidth))) } // highlights - if len(cards.Unrated.Highlights) > len(cards.Unrated.Vehicles) { + if shouldRenderUnratedHighlights { // } // rating vehicle cards - if len(cards.Unrated.Vehicles) == 0 { + if shouldRenderRatingVehicles { // } // unrated vehicles - for i, vehicle := range cards.Unrated.Vehicles { - secondaryColumn = append(secondaryColumn, makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth)) - if i == len(cards.Unrated.Vehicles)-1 { - secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) + if shouldRenderUnratedVehicles { + for i, vehicle := range cards.Unrated.Vehicles { + secondaryColumn = append(secondaryColumn, makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth)) + if i == len(cards.Unrated.Vehicles)-1 { + secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) + } } } @@ -178,7 +197,8 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card func vehicleBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], sessionStyle, careerStyle, labelStyle, containerStyle common.Style) (map[string]float64, float64) { presetBlockWidth := make(map[string]float64) - var contentWidth, maxBlockWidth float64 + var contentWidth float64 + var maxBlockWidth float64 for _, block := range blocks { var width float64 { @@ -191,11 +211,10 @@ func vehicleBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], sessionS } { size := common.MeasureString(block.Label, *labelStyle.Font) - width = common.Max(width, size.TotalWidth+labelStyle.PaddingX*2) + width = common.Max(width, size.TotalWidth+labelStyle.PaddingX*2+vehicleLegendLabelContainer.PaddingX*2) } - w := width + float64(iconSize) - maxBlockWidth = common.Max(maxBlockWidth, w) - presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], w) // add space for comparison icons + maxBlockWidth = common.Max(maxBlockWidth, width) + presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], width) // add space for comparison icons } if containerStyle.Direction == common.DirectionHorizontal { @@ -223,6 +242,7 @@ func overviewColumnBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], s } func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { + var vehicleWN8 frame.Value = frame.InvalidValue var content []common.Block for _, block := range vehicle.Blocks { var blockContent []common.Block @@ -230,15 +250,20 @@ func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, if block.Data.Career.Float() > 0 { blockContent = append(blockContent, common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String())) } - content = append(content, common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), blockContent...), ) + if block.Tag == prepare.TagWN8 { + vehicleWN8 = block.Value + } } + + titleStyle := vehicleCardTitleContainerStyle(cardWidth) + titleStyle.Width -= vehicleCardStyle(0).PaddingX * 2 return common.NewBlocksContent(vehicleCardStyle(cardWidth), - common.NewBlocksContent(vehicleCardTitleContainerStyle(cardWidth), - common.NewTextContent(vehicleCardTitleStyle, vehicle.Title), // vehicle name - common.NewTextContent(vehicleCardTitleStyle, vehicle.Meta), // tier + common.NewBlocksContent(titleStyle, + common.NewTextContent(vehicleCardTitleTextStyle, fmt.Sprintf("%s %s", vehicle.Meta, vehicle.Title)), // name and tier + vehicleWN8Icon(vehicleWN8), ), common.NewBlocksContent(vehicleBlocksRowStyle(0), content...), ) @@ -247,15 +272,9 @@ func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { var content []common.Block for _, block := range reference.Blocks { - labelContainer := common.Style{ - BackgroundColor: common.DefaultCardColor, - BorderRadius: common.BorderRadiusSM, - PaddingY: 5, - PaddingX: 10, - } content = append(content, common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), - common.NewBlocksContent(labelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label)), + common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label)), ), ) } diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go index ff4e50eb..375d88f3 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/constants.go @@ -1,11 +1,9 @@ package session import ( - "image" "image/color" "github.com/cufee/aftermath/internal/stats/render/common" - "github.com/fogleman/gg" ) type blockStyle struct { @@ -14,26 +12,10 @@ type blockStyle struct { label common.Style } -func init() { - { - ctx := gg.NewContext(iconSize, iconSize) - ctx.DrawRoundedRectangle(13, 2.5, 6, 17.5, 3) - ctx.SetColor(color.RGBA{R: 255, G: 255, B: 255, A: 255}) - ctx.Fill() - wn8Icon = ctx.Image() - } - - { - ctx := gg.NewContext(iconSize, 1) - blankIconBlock = common.NewImageContent(common.Style{Width: float64(iconSize), Height: 1}, ctx.Image()) - } -} - var ( - iconSize = 25 + vehicleWN8IconSize = 20.0 specialRatingIconSize = 60.0 - wn8Icon image.Image - blankIconBlock common.Block + minPrimaryCardWidth = 300.0 // making the primary card too small looks bad if there are no battles in a session ) var ( @@ -105,8 +87,14 @@ func statsBlockStyle(width float64) common.Style { } var ( - vehicleCardTitleStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextAlt, PaddingX: 5} - vehicleBlockStyle = blockStyle{ + vehicleLegendLabelContainer = common.Style{ + BackgroundColor: common.DefaultCardColor, + BorderRadius: common.BorderRadiusSM, + PaddingY: 5, + PaddingX: 10, + } + vehicleCardTitleTextStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextAlt} + vehicleBlockStyle = blockStyle{ common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, @@ -117,7 +105,6 @@ func vehicleCardTitleContainerStyle(width float64) common.Style { return common.Style{ JustifyContent: common.JustifyContentSpaceBetween, Direction: common.DirectionHorizontal, - PaddingX: defaultCardStyle(0).PaddingX * 2, Width: width, Gap: 10, } @@ -125,6 +112,7 @@ func vehicleCardTitleContainerStyle(width float64) common.Style { func vehicleCardStyle(width float64) common.Style { style := defaultCardStyle(width) + style.PaddingX, style.PaddingY = 20, 15 style.Gap = 5 return style } @@ -135,6 +123,7 @@ func vehicleBlocksRowStyle(width float64) common.Style { Direction: common.DirectionHorizontal, AlignItems: common.AlignItemsCenter, Width: width, + Gap: 10, } } From 7e84327353e57f8c3bab1b06f11e5526e7e443a3 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 17 Jun 2024 13:15:29 -0400 Subject: [PATCH 068/341] more consistent session time calc --- internal/stats/fetch/multisource.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 0da01e5f..a43c82d5 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -279,12 +279,10 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, ErrInvalidSessionStart } - sessionBefore := sessionStart - if time.Since(sessionStart).Hours() >= 48 { + sessionBefore := time.Now() + if time.Since(sessionStart).Hours() >= 24 { // 3 days would mean today and yest, so before the reset 2 days ago and so on - sessionBefore = sessionBefore.Add(time.Hour * 24 * -1) - } else { - sessionBefore = time.Now() + sessionBefore = sessionStart.Add(time.Hour * 24 * -1) } var accountSnapshot retry.DataWithErr[database.AccountSnapshot] From 1c9f189480856c5e8fd7736f687a782a0043df28 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 17 Jun 2024 13:17:02 -0400 Subject: [PATCH 069/341] cleaned up logic --- internal/stats/fetch/multisource.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index a43c82d5..d40f2d6e 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -270,16 +270,13 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session apply(&options) } - // sessions and period stats are tracked differently - // we look up the latest session _before_ sessionBefore, not after sessionStart - if sessionStart.IsZero() { - sessionStart = time.Now() - } - if days := time.Since(sessionStart).Hours() / 24; days > 90 { + if days := time.Since(sessionStart).Hours() / 24; sessionStart.IsZero() || days > 90 { return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, ErrInvalidSessionStart } - sessionBefore := time.Now() + // sessions and period stats are tracked differently + // we look up the latest session _before_ sessionBefore, not after sessionStart + sessionBefore := time.Now() // current session if time.Since(sessionStart).Hours() >= 24 { // 3 days would mean today and yest, so before the reset 2 days ago and so on sessionBefore = sessionStart.Add(time.Hour * 24 * -1) From 9e30aaa32f23d5839ab111fb93844cca0f6a5941 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 17 Jun 2024 13:23:50 -0400 Subject: [PATCH 070/341] calculating session time only on 2+ days --- internal/stats/fetch/multisource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index d40f2d6e..5868e23f 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -277,7 +277,7 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session // sessions and period stats are tracked differently // we look up the latest session _before_ sessionBefore, not after sessionStart sessionBefore := time.Now() // current session - if time.Since(sessionStart).Hours() >= 24 { + if time.Since(sessionStart).Hours() >= 48 { // 3 days would mean today and yest, so before the reset 2 days ago and so on sessionBefore = sessionStart.Add(time.Hour * 24 * -1) } From b125dc01fc921283a584d36ef282757fe6ca8b27 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 17 Jun 2024 13:27:11 -0400 Subject: [PATCH 071/341] removed discord retry --- cmds/discord/rest/client.go | 56 ++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/cmds/discord/rest/client.go b/cmds/discord/rest/client.go index 2802913c..730af69c 100644 --- a/cmds/discord/rest/client.go +++ b/cmds/discord/rest/client.go @@ -14,7 +14,6 @@ import ( "github.com/rs/zerolog/log" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/internal/retry" ) type Client struct { @@ -112,44 +111,37 @@ func partHeader(contentDisposition string, contentType string) textproto.MIMEHea func (c *Client) do(req *http.Request, target any) error { log.Debug().Str("url", req.URL.String()).Str("contentType", req.Header.Get("Content-Type")).Msg("sending a request to Discord API") - result := retry.Retry(func() (any, error) { - res, err := c.http.Do(req) + + res, err := c.http.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode > 299 { + var body discordgo.APIErrorMessage + data, err := io.ReadAll(res.Body) if err != nil { - return nil, err - } - defer res.Body.Close() - - if res.StatusCode > 299 { - var body discordgo.APIErrorMessage - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read body: %w", err) - } - - _ = json.Unmarshal(data, &body) - message := body.Message - if message == "" { - message = res.Status + ", response was not valid json" - } - println(string(data)) - - return nil, errors.New("discord error: " + message) + return fmt.Errorf("failed to read body: %w", err) } - if target != nil { - err = json.NewDecoder(res.Body).Decode(target) - if err != nil { - return nil, fmt.Errorf("failed to decode response body :%w", err) - } + _ = json.Unmarshal(data, &body) + message := body.Message + if message == "" { + message = res.Status + ", response was not valid json" } + println(string(data)) - return nil, nil - }, 3, time.Millisecond*150) + return errors.New("discord error: " + message) + } - if result.Err != nil { - log.Debug().Err(result.Err).Str("url", req.URL.String()).Str("contentType", req.Header.Get("Content-Type")).Msg("request finished with error") - return result.Err + if target != nil { + err = json.NewDecoder(res.Body).Decode(target) + if err != nil { + return fmt.Errorf("failed to decode response body :%w", err) + } } + log.Debug().Str("url", req.URL.String()).Str("contentType", req.Header.Get("Content-Type")).Msg("request finished successfully") return nil } From 5d59df212a2e40aebfab50d13c5f73a5c983988b Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 17 Jun 2024 13:29:10 -0400 Subject: [PATCH 072/341] handling 0 time correctly --- internal/stats/session.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/stats/session.go b/internal/stats/session.go index bedbd34d..aa079f46 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -49,6 +49,9 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time } stop = meta.Timer("fetchClient#SessionStats") + if from.IsZero() { + from = time.Now() + } session, career, err := r.fetchClient.SessionStats(ctx, accountId, from, fetch.WithWN8()) stop() if err != nil { From f6ec227fed5d29b77d6f7bbc0bc3178e8a4d6659 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 17 Jun 2024 16:05:21 -0400 Subject: [PATCH 073/341] adjusted session start time again --- internal/stats/fetch/multisource.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 5868e23f..d82c9328 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -274,12 +274,10 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, ErrInvalidSessionStart } - // sessions and period stats are tracked differently - // we look up the latest session _before_ sessionBefore, not after sessionStart - sessionBefore := time.Now() // current session - if time.Since(sessionStart).Hours() >= 48 { - // 3 days would mean today and yest, so before the reset 2 days ago and so on - sessionBefore = sessionStart.Add(time.Hour * 24 * -1) + // 1 day should be the same as today + sessionBefore := time.Now() + if time.Since(sessionStart).Hours() >= 24 { + sessionBefore = sessionStart } var accountSnapshot retry.DataWithErr[database.AccountSnapshot] From 35c778e1195549a13b35d0f378bc1fb9b52bc01c Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 17 Jun 2024 16:05:59 -0400 Subject: [PATCH 074/341] simplified session times even further --- internal/stats/fetch/multisource.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index d82c9328..1f1a50c9 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -274,11 +274,7 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, ErrInvalidSessionStart } - // 1 day should be the same as today - sessionBefore := time.Now() - if time.Since(sessionStart).Hours() >= 24 { - sessionBefore = sessionStart - } + sessionBefore := sessionStart var accountSnapshot retry.DataWithErr[database.AccountSnapshot] var vehiclesSnapshots retry.DataWithErr[[]database.VehicleSnapshot] From 75d8bcc11699cb8efd0776b69f08989cf9c9bc34 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 18 Jun 2024 13:11:36 -0400 Subject: [PATCH 075/341] added highlight cards --- internal/stats/prepare/session/card.go | 69 ++++++++++++++++++++++ internal/stats/render/common/images.go | 10 ++-- internal/stats/render/session/cards.go | 66 ++++++++++++++++++--- internal/stats/render/session/constants.go | 28 +++++++-- 4 files changed, 158 insertions(+), 15 deletions(-) diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index 9eace0ea..b81b8cc4 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -1,6 +1,7 @@ package session import ( + "math" "slices" "github.com/cufee/aftermath/internal/database" @@ -122,6 +123,47 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] cards.Unrated.Vehicles = append(cards.Unrated.Vehicles, card) } + // Vehicle Highlights + var minimumBattles float64 = 5 + periodDays := session.PeriodEnd.Sub(session.PeriodStart).Hours() / 24 + withFallback := func(battles float64) float64 { + return math.Min(battles, float64(session.RegularBattles.Battles.Float())/float64(len(highlights))) + } + if periodDays > 90 { + minimumBattles = withFallback(100) + } else if periodDays > 60 { + minimumBattles = withFallback(75) + } else if periodDays > 30 { + minimumBattles = withFallback(50) + } else if periodDays > 14 { + minimumBattles = withFallback(25) + } else if periodDays > 7 { + minimumBattles = withFallback(10) + } + + highlightedVehicles, err := common.GetHighlightedVehicles(highlights, session.RegularBattles.Vehicles, int(minimumBattles)) + if err != nil { + return Cards{}, err + } + + for _, data := range highlightedVehicles { + glossary := glossary[data.Vehicle.VehicleID] + glossary.ID = data.Vehicle.VehicleID + + card, err := makeHighlightCard( + data.Highlight, + data.Vehicle, + frame.VehicleStatsFrame{}, + options.Printer(), + options.Locale(), + glossary, + ) + if err != nil { + return Cards{}, err + } + cards.Unrated.Highlights = append(cards.Unrated.Highlights, card) + } + return cards, nil } @@ -152,6 +194,33 @@ func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, ca }, nil } +func makeHighlightCard(highlight common.Highlight, session, career frame.VehicleStatsFrame, printer func(string) string, locale language.Tag, glossary database.Vehicle) (VehicleCard, error) { + var sFrame, cFrame frame.StatsFrame + if session.StatsFrame != nil { + sFrame = *session.StatsFrame + } + if career.StatsFrame != nil { + cFrame = *career.StatsFrame + } + + var blocks []common.StatsBlock[BlockData] + for _, preset := range highlight.Blocks { + block, err := presetToBlock(preset, sFrame, cFrame) + if err != nil { + return VehicleCard{}, err + } + block.Localize(printer) + blocks = append(blocks, block) + } + + return VehicleCard{ + Meta: printer(highlight.Label), + Title: common.IntToRoman(glossary.Tier) + " " + glossary.Name(locale), + Type: common.CardTypeHighlight, + Blocks: blocks, + }, nil +} + func makeOverviewCard(columns [][]common.Tag, session, career frame.StatsFrame, label string, printer func(string) string, replace func(common.Tag) common.Tag) (OverviewCard, error) { var blocks [][]common.StatsBlock[BlockData] for _, presets := range columns { diff --git a/internal/stats/render/common/images.go b/internal/stats/render/common/images.go index bda0c696..4074fb5e 100644 --- a/internal/stats/render/common/images.go +++ b/internal/stats/render/common/images.go @@ -57,8 +57,10 @@ func renderImages(images []image.Image, style Style) (image.Image, error) { lastX += float64(imageSize.extraSpacingX) lastY += float64(imageSize.extraSpacingY) case JustifyContentSpaceBetween: - justifyOffsetX = float64(imageSize.extraSpacingX / float64(len(images)-1)) - justifyOffsetY = float64(imageSize.extraSpacingY / float64(len(images)-1)) + if len(images) > 0 { + justifyOffsetX = float64(imageSize.extraSpacingX / float64(len(images)-1)) + justifyOffsetY = float64(imageSize.extraSpacingY / float64(len(images)-1)) + } case JustifyContentSpaceAround: spacingX := float64(imageSize.extraSpacingX / float64(len(images)+1)) spacingY := float64(imageSize.extraSpacingY / float64(len(images)+1)) @@ -188,8 +190,8 @@ func getDetailedSize(images []image.Image, style Style) imageSize { imageHeight = totalHeight } - extraSpacingX := imageWidth - totalWidth - extraSpacingY := imageHeight - totalHeight + extraSpacingX := max(0, imageWidth-totalWidth) + extraSpacingY := max(0, imageHeight-totalHeight) return imageSize{ width: imageWidth, diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index d5949d10..0df453bb 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -32,6 +32,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // Calculate minimal card width to fit all the content primaryCardBlockSizes := make(map[string]float64) secondaryCardBlockSizes := make(map[string]float64) + highlightCardBlockSizes := make(map[string]float64) var primaryCardWidth float64 = minPrimaryCardWidth var secondaryCardWidth, totalFrameWidth float64 // rating and unrated battles > 0 unrated battles > 0 rating battles > 0 @@ -89,15 +90,15 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card for _, card := range cards.Unrated.Highlights { // [card label] [session] // [title] [label] - labelSize := common.MeasureString(card.Meta, *vehicleCardTitleTextStyle.Font) - titleSize := common.MeasureString(card.Title, *vehicleCardTitleTextStyle.Font) - presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlocksRowStyle(0)) + labelSize := common.MeasureString(card.Meta, *highlightCardTitleTextStyle.Font) + titleSize := common.MeasureString(card.Title, *highlightVehicleNameTextStyle.Font) + presetBlockWidth, contentWidth := highlightedVehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, highlightedVehicleBlockRowStyle(0)) // add the gap and card padding, the gap here accounts for title/label being inline with content - contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)) + highlightCardStyle(0).PaddingX*2 + common.Max(titleSize.TotalWidth, labelSize.TotalWidth) + contentWidth += highlightedVehicleBlockRowStyle(0).Gap*float64(len(card.Blocks)) + highlightedVehicleCardStyle(0).Gap + highlightedVehicleCardStyle(0).PaddingX*2 + common.Max(titleSize.TotalWidth, labelSize.TotalWidth) primaryCardWidth = common.Max(primaryCardWidth, contentWidth) for key, width := range presetBlockWidth { - primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) + highlightCardBlockSizes[key] = common.Max(highlightCardBlockSizes[key], width) } } } @@ -169,7 +170,9 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // highlights if shouldRenderUnratedHighlights { - // + for _, vehicle := range cards.Unrated.Highlights { + primaryColumn = append(primaryColumn, makeVehicleHighlightCard(vehicle, highlightCardBlockSizes, primaryCardWidth)) + } } // rating vehicle cards if shouldRenderRatingVehicles { @@ -214,7 +217,36 @@ func vehicleBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], sessionS width = common.Max(width, size.TotalWidth+labelStyle.PaddingX*2+vehicleLegendLabelContainer.PaddingX*2) } maxBlockWidth = common.Max(maxBlockWidth, width) - presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], width) // add space for comparison icons + presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], width) + } + + if containerStyle.Direction == common.DirectionHorizontal { + for _, w := range presetBlockWidth { + contentWidth += w + } + } else { + contentWidth = maxBlockWidth + } + + return presetBlockWidth, contentWidth +} + +func highlightedVehicleBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], sessionStyle, _, labelStyle, containerStyle common.Style) (map[string]float64, float64) { + presetBlockWidth := make(map[string]float64) + var contentWidth float64 + var maxBlockWidth float64 + for _, block := range blocks { + var width float64 + { + size := common.MeasureString(block.Data.Session.String(), *sessionStyle.Font) + width = common.Max(width, size.TotalWidth+sessionStyle.PaddingX*2) + } + { + size := common.MeasureString(block.Label, *labelStyle.Font) + width = common.Max(width, size.TotalWidth+labelStyle.PaddingX*2) + } + maxBlockWidth = common.Max(maxBlockWidth, width) + presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], width) } if containerStyle.Direction == common.DirectionHorizontal { @@ -269,6 +301,26 @@ func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, ) } +func makeVehicleHighlightCard(vehicle session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { + var content []common.Block + for _, block := range vehicle.Blocks { + content = append(content, + common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), + common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + common.NewTextContent(vehicleBlockStyle.label, block.Label), + ), + ) + } + + return common.NewBlocksContent(highlightedVehicleCardStyle(cardWidth), + common.NewBlocksContent(common.Style{Direction: common.DirectionVertical}, + common.NewTextContent(highlightCardTitleTextStyle, vehicle.Meta), + common.NewTextContent(highlightVehicleNameTextStyle, vehicle.Title), + ), + common.NewBlocksContent(highlightedVehicleBlockRowStyle(0), content...), + ) +} + func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { var content []common.Block for _, block := range reference.Blocks { diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go index 375d88f3..fbcfc71b 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/constants.go @@ -124,6 +124,7 @@ func vehicleBlocksRowStyle(width float64) common.Style { AlignItems: common.AlignItemsCenter, Width: width, Gap: 10, + // Debug: true, } } @@ -145,12 +146,31 @@ func ratingVehicleBlocksRowStyle(width float64) common.Style { return vehicleBlocksRowStyle(width) } -func highlightCardStyle(width float64) common.Style { - return defaultCardStyle(width) -} +var ( + highlightCardTitleTextStyle = common.Style{Font: &common.FontSmall, FontColor: common.TextSecondary} + highlightVehicleNameTextStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextPrimary} +) func highlightedVehicleCardStyle(width float64) common.Style { - return vehicleCardStyle(width) + style := defaultCardStyle(width) + style.JustifyContent = common.JustifyContentSpaceBetween + style.Direction = common.DirectionHorizontal + style.AlignItems = common.AlignItemsCenter + style.PaddingX, style.PaddingY = 20, 15 + style.Gap = 15 + // style.Debug = true + return style +} + +func highlightedVehicleBlockRowStyle(width float64) common.Style { + return common.Style{ + JustifyContent: common.JustifyContentSpaceBetween, + Direction: common.DirectionHorizontal, + AlignItems: common.AlignItemsCenter, + Width: width, + Gap: 10, + // Debug: true, + } } func defaultCardStyle(width float64) common.Style { From a4bf6d63a9a7f340db59949ab09eeb302d10da2a Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 20 Jun 2024 16:53:44 -0400 Subject: [PATCH 076/341] added internal API --- cmds/core/server/handlers/private/accounts.go | 112 ++++++++++++++++++ cmds/core/server/server.go | 27 +++++ docker-entrypoint.sh | 2 - internal/stats/fetch/convert.go | 4 +- internal/stats/fetch/multisource.go | 2 +- internal/stats/render/session/cards.go | 6 +- internal/stats/render/session/constants.go | 1 - main.go | 67 +++++++---- 8 files changed, 191 insertions(+), 30 deletions(-) create mode 100644 cmds/core/server/handlers/private/accounts.go create mode 100644 cmds/core/server/server.go delete mode 100755 docker-entrypoint.sh diff --git a/cmds/core/server/handlers/private/accounts.go b/cmds/core/server/handlers/private/accounts.go new file mode 100644 index 00000000..ab8f0547 --- /dev/null +++ b/cmds/core/server/handlers/private/accounts.go @@ -0,0 +1,112 @@ +package private + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "sync" + + "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/am-wg-proxy-next/v2/types" + "github.com/rs/zerolog/log" + "golang.org/x/sync/semaphore" +) + +/* +This function is temporary and does not need to be good :D +*/ +func LoadAccountsHandler(client core.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + payload, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer r.Body.Close() + + var accounts []string + err = json.Unmarshal(payload, &accounts) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + existing, err := client.Database().GetAccounts(context.Background(), accounts) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Write([]byte(fmt.Sprintf("working on %d accounts", len(accounts)-len(existing)))) + log.Info().Int("count", len(accounts)-len(existing)).Msg("importing accounts") + + go func() { + existingMap := make(map[string]struct{}) + for _, a := range existing { + existingMap[a.ID] = struct{}{} + } + + accountsByRealm := make(map[string][]string) + for _, a := range accounts { + if _, ok := existingMap[a]; ok { + continue + } + + id := client.Wargaming().RealmFromAccountID(a) + accountsByRealm[id] = append(accountsByRealm[id], a) + } + + batchSize := 50 + var wg sync.WaitGroup + sem := semaphore.NewWeighted(5) + + for realm, accounts := range accountsByRealm { + for i := 0; i < len(accounts); i += batchSize { + end := i + batchSize + if end > len(accounts) { + end = len(accounts) + } + + wg.Add(1) + go func(accounts []string, realm string) { + defer wg.Done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := sem.Acquire(ctx, 1) + if err != nil { + log.Err(err).Msg("failed to acquire a semaphore") + return + } + defer sem.Release(1) + + data, err := client.Wargaming().BatchAccountByID(ctx, realm, accounts) + if err != nil { + log.Err(err).Msg("failed to get accounts from wg") + return + } + + var inserts []database.Account + for _, account := range data { + inserts = append(inserts, fetch.WargamingToAccount(realm, account, types.ClanMember{}, false)) + } + + errors := client.Database().UpsertAccounts(ctx, inserts) + if len(errors) > 0 { + log.Error().Any("errors", errors).Msg("failed to upsert some accounts") + return + } + + }(accounts[i:end], realm) + } + } + wg.Wait() + log.Info().Int("count", len(accounts)-len(existing)).Msg("finished importing accounts") + }() + } +} diff --git a/cmds/core/server/server.go b/cmds/core/server/server.go new file mode 100644 index 00000000..ffc6827c --- /dev/null +++ b/cmds/core/server/server.go @@ -0,0 +1,27 @@ +package server + +import ( + "net/http" + "time" +) + +type Handler struct { + Path string + Func http.HandlerFunc +} + +func NewServer(port string, handlers ...Handler) func() { + mux := http.NewServeMux() + for _, h := range handlers { + mux.Handle(h.Path, h.Func) + } + srv := &http.Server{ + Addr: "127.0.0.1:" + port, + WriteTimeout: 15 * time.Second, + ReadTimeout: 5 * time.Second, + Handler: mux, + } + return func() { + panic(srv.ListenAndServe()) + } +} diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100755 index e03049b6..00000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -go run github.com/steebchen/prisma-client-go migrate deploy --schema /prisma/schema.prisma && /app diff --git a/internal/stats/fetch/convert.go b/internal/stats/fetch/convert.go index 342d1a18..39fa8c39 100644 --- a/internal/stats/fetch/convert.go +++ b/internal/stats/fetch/convert.go @@ -15,7 +15,7 @@ func timestampToTime(timestamp int) time.Time { return time.Unix(int64(timestamp), 0) } -func wargamingToAccount(realm string, account types.ExtendedAccount, clan types.ClanMember, private bool) database.Account { +func WargamingToAccount(realm string, account types.ExtendedAccount, clan types.ClanMember, private bool) database.Account { a := database.Account{ ID: strconv.Itoa(account.ID), Realm: realm, @@ -36,7 +36,7 @@ func WargamingToStats(realm string, accountData types.ExtendedAccount, clanMembe stats := AccountStatsOverPeriod{ Realm: realm, // we got the stats, so the account is obv not private at this point - Account: wargamingToAccount(realm, accountData, clanMember, false), + Account: WargamingToAccount(realm, accountData, clanMember, false), RegularBattles: StatsWithVehicles{ StatsFrame: WargamingToFrame(accountData.Statistics.All), Vehicles: WargamingVehiclesToFrame(vehicleData), diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 1f1a50c9..b893916f 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -83,7 +83,7 @@ func (c *multiSourceClient) Account(ctx context.Context, id string) (database.Ac return database.Account{}, wgAccount.Err } - account := wargamingToAccount(realm, wgAccount.Data, clan, false) + account := WargamingToAccount(realm, wgAccount.Data, clan, false) go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 0df453bb..00738329 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -162,10 +162,10 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // overview cards if shouldRenderUnratedOverview { - primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, primaryCardWidth, overviewCardStyle(primaryCardWidth))) + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, overviewCardStyle(primaryCardWidth))) } if shouldRenderRatingOverview { - primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, primaryCardWidth, overviewRatingCardStyle(primaryCardWidth))) + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, overviewRatingCardStyle(primaryCardWidth))) } // highlights @@ -337,7 +337,7 @@ func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string] return common.NewBlocksContent(style, common.NewBlocksContent(vehicleBlocksRowStyle(0), content...)) } -func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, cardWidth float64, style common.Style) common.Block { +func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, style common.Style) common.Block { var content []common.Block for _, column := range card.Blocks { var columnContent []common.Block diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go index fbcfc71b..54230f80 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/constants.go @@ -28,7 +28,6 @@ func frameStyle() common.Style { } var ( - overviewCardTitleStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextAlt, PaddingX: 5} overviewStatsBlockStyle = blockStyle{ common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, diff --git a/main.go b/main.go index be6bd79a..cd67cab4 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "embed" "io/fs" - "net/http" "os" "strconv" "time" @@ -12,6 +11,8 @@ import ( "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/cmds/core/scheduler" "github.com/cufee/aftermath/cmds/core/scheduler/tasks" + "github.com/cufee/aftermath/cmds/core/server" + "github.com/cufee/aftermath/cmds/core/server/handlers/private" "github.com/cufee/aftermath/cmds/discord" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" @@ -39,31 +40,42 @@ func main() { zerolog.SetGlobalLevel(level) loadStaticAssets(static) - coreClient := coreClientFromEnv() - startSchedulerFromEnvAsync() - // Load some init options to registed admin accounts and etc - logic.ApplyInitOptions(coreClient.Database()) + liveCoreClient, cacheCoreClient := coreClientsFromEnv() + startSchedulerFromEnvAsync(cacheCoreClient.Wargaming()) - discordHandler, err := discord.NewRouterHandler(coreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) + // Load some init options to registered admin accounts and etc + logic.ApplyInitOptions(liveCoreClient.Database()) + + discordHandler, err := discord.NewRouterHandler(liveCoreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) if err != nil { panic(err) } - http.Handle("/discord/callback", discordHandler) - panic(http.ListenAndServe(":"+os.Getenv("PORT"), nil)) + if e := os.Getenv("PRIVATE_SERVER_ENABLED"); e == "true" { + port := os.Getenv("PRIVATE_SERVER_PORT") + servePrivate := server.NewServer(port, server.Handler{ + Path: "POST /accounts/load", + Func: private.LoadAccountsHandler(cacheCoreClient), + }) + log.Info().Str("port", port).Msg("starting a private server") + go servePrivate() + } + + port := os.Getenv("PORT") + servePublic := server.NewServer(port, server.Handler{ + Path: "POST /discord/callback", + Func: discordHandler, + }) + log.Info().Str("port", port).Msg("starting a public server") + servePublic() } -func startSchedulerFromEnvAsync() { +func startSchedulerFromEnvAsync(wgClient wargaming.Client) { if os.Getenv("SCHEDULER_ENABLED") != "true" { return } - // This wargaming client is using a different proxy as it needs a lot higher rps, but can be slow - wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_CACHE_APP_ID"), os.Getenv("WG_CACHE_APP_RPS"), os.Getenv("WG_CACHE_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_CACHE_PROXY_HOST_LIST")) - if err != nil { - log.Fatal().Msgf("wgClient: wargaming#NewClientFromEnv failed %s", err) - } dbClient, err := database.NewClient() if err != nil { log.Fatal().Msgf("database#NewClient failed %s", err) @@ -100,12 +112,8 @@ func startSchedulerFromEnvAsync() { // scheduler.CreateSessionTasksWorker(coreClient, "AS")() } -func coreClientFromEnv() core.Client { +func coreClientsFromEnv() (core.Client, core.Client) { // Dependencies - wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_PRIMARY_APP_ID"), os.Getenv("WG_PRIMARY_APP_RPS"), os.Getenv("WG_PRIMARY_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_PROXY_HOST_LIST")) - if err != nil { - log.Fatal().Msgf("wgClient: wargaming#NewClientFromEnv failed %s", err) - } dbClient, err := database.NewClient() if err != nil { log.Fatal().Msgf("database#NewClient failed %s", err) @@ -115,13 +123,15 @@ func coreClientFromEnv() core.Client { log.Fatal().Msgf("failed to init a blitzstars client %s", err) } + liveClient, cacheClient := wargamingClientsFromEnv() + // Fetch client - client, err := fetch.NewMultiSourceClient(wgClient, bsClient, dbClient) + fetchClient, err := fetch.NewMultiSourceClient(cacheClient, bsClient, dbClient) if err != nil { log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } - return core.NewClient(client, wgClient, dbClient) + return core.NewClient(fetchClient, liveClient, dbClient), core.NewClient(fetchClient, cacheClient, dbClient) } func loadStaticAssets(static fs.FS) { @@ -139,3 +149,18 @@ func loadStaticAssets(static fs.FS) { log.Fatal().Msgf("localization#LoadAssets failed %s", err) } } + +func wargamingClientsFromEnv() (wargaming.Client, wargaming.Client) { + liveClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_PRIMARY_APP_ID"), os.Getenv("WG_PRIMARY_APP_RPS"), os.Getenv("WG_PRIMARY_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_PROXY_HOST_LIST")) + if err != nil { + log.Fatal().Msgf("wargamingClientsFromEnv#NewClientFromEnv failed %s", err) + } + + // This wargaming client is using a different proxy as it needs a lot higher rps, but can be slow + cacheClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_CACHE_APP_ID"), os.Getenv("WG_CACHE_APP_RPS"), os.Getenv("WG_CACHE_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_CACHE_PROXY_HOST_LIST")) + if err != nil { + log.Fatal().Msgf("wargamingClientsFromEnv#NewClientFromEnv failed %s", err) + } + + return liveClient, cacheClient +} From f757dbeb5740391404ec3c01d932eda4eff770c7 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 20 Jun 2024 17:08:08 -0400 Subject: [PATCH 077/341] added second port --- .env.example | 6 ++++-- docker-compose.yaml | 1 + render_test.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 5f143144..6c94cc36 100644 --- a/.env.example +++ b/.env.example @@ -34,8 +34,10 @@ DISCORD_PRIMARY_GUILD_ID="" # Discord ID of the primary guild, some features wil DISCORD_ERROR_REPORT_WEBHOOK_URL="" # A Discord webhook URL for a channel where the bot should report errors # Optional components -SCHEDULER_ENABLED="true" -SCHEDULER_CONCURRENT_WORKERS="10" +SCHEDULER_ENABLED="true" # Scheduler is responsible for refreshing glossary cache and recording sessions +SCHEDULER_CONCURRENT_WORKERS="10" +PRIVATE_SERVER_ENABLED="true" # A private server can be used to access some managemenet endpoints +PRIVATE_SERVER_PORT="9093" # Misc configuration LOG_LEVEL="debug" diff --git a/docker-compose.yaml b/docker-compose.yaml index 1f203e05..844cb133 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -33,6 +33,7 @@ services: memory: 256m ports: - 3000 + - ${PRIVATE_SERVER_PORT} networks: - dokploy-network labels: diff --git a/render_test.go b/render_test.go index c014682e..a6770d64 100644 --- a/render_test.go +++ b/render_test.go @@ -23,7 +23,7 @@ func TestRenderSession(t *testing.T) { zerolog.SetGlobalLevel(level) loadStaticAssets(static) - coreClient := coreClientFromEnv() + coreClient, _ := coreClientsFromEnv() defer coreClient.Database().Disconnect() rating, _ := assets.GetLoadedImage("rating-calibration") From fd5eb5fc46dfaa9c3fe2695e4765c346601d5d75 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 20 Jun 2024 17:19:49 -0400 Subject: [PATCH 078/341] exposing the sane port on host --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 844cb133..5651e7cb 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -33,7 +33,7 @@ services: memory: 256m ports: - 3000 - - ${PRIVATE_SERVER_PORT} + - ${PRIVATE_SERVER_PORT}:${PRIVATE_SERVER_PORT} networks: - dokploy-network labels: From 22cf22143169748d5e4d9305061fdaa1245cd06f Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 20 Jun 2024 17:32:41 -0400 Subject: [PATCH 079/341] fixed addr --- cmds/core/server/server.go | 2 +- docker-compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmds/core/server/server.go b/cmds/core/server/server.go index ffc6827c..8c272295 100644 --- a/cmds/core/server/server.go +++ b/cmds/core/server/server.go @@ -16,7 +16,7 @@ func NewServer(port string, handlers ...Handler) func() { mux.Handle(h.Path, h.Func) } srv := &http.Server{ - Addr: "127.0.0.1:" + port, + Addr: ":" + port, WriteTimeout: 15 * time.Second, ReadTimeout: 5 * time.Second, Handler: mux, diff --git a/docker-compose.yaml b/docker-compose.yaml index 5651e7cb..6362e4ff 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -33,7 +33,7 @@ services: memory: 256m ports: - 3000 - - ${PRIVATE_SERVER_PORT}:${PRIVATE_SERVER_PORT} + - "${PRIVATE_SERVER_PORT}" networks: - dokploy-network labels: From 2021d67399d72ccb14a5905ec9d73a9fef07eed4 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 20 Jun 2024 17:46:39 -0400 Subject: [PATCH 080/341] fixed highlights on no battles --- internal/stats/render/session/cards.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 00738329..3d0e8eb6 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -16,12 +16,12 @@ import ( func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []database.UserSubscription, opts render.Options) (render.Segments, error) { var ( - // primary card + // primary cards shouldRenderUnratedOverview = session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 - shouldRenderUnratedHighlights = len(session.RegularBattles.Vehicles) > 3 + shouldRenderUnratedHighlights = session.RegularBattles.Battles > 0 && len(session.RegularBattles.Vehicles) > 3 shouldRenderRatingOverview = session.RatingBattles.Battles > 0 shouldRenderRatingVehicles = len(cards.Unrated.Vehicles) == 0 - // secondary card + // secondary cards shouldRenderUnratedVehicles = len(cards.Unrated.Vehicles) > 0 ) From 99ab616ae94fc400248d983e3da6b62fe6183de9 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Jun 2024 01:08:10 -0400 Subject: [PATCH 081/341] minor task fixes --- cmds/core/scheduler/tasks/cleanup.go | 12 ++++++------ internal/database/tasks.go | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmds/core/scheduler/tasks/cleanup.go b/cmds/core/scheduler/tasks/cleanup.go index 47177792..b753c59d 100644 --- a/cmds/core/scheduler/tasks/cleanup.go +++ b/cmds/core/scheduler/tasks/cleanup.go @@ -16,11 +16,11 @@ func init() { if task.Data == nil { return "no data provided", errors.New("no data provided") } - snapshotExpiration, ok := task.Data["expiration_snapshots"].(time.Time) + snapshotExpiration, ok := task.Data["expiration_snapshots"].(int64) if !ok { return "invalid expiration_snapshots", errors.New("failed to cast expiration_snapshots to time") } - taskExpiration, ok := task.Data["expiration_tasks"].(time.Time) + taskExpiration, ok := task.Data["expiration_tasks"].(int64) if !ok { task.Data["triesLeft"] = int(0) // do not retry return "invalid expiration_tasks", errors.New("failed to cast expiration_tasks to time") @@ -29,12 +29,12 @@ func init() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - err := client.Database().DeleteExpiredTasks(ctx, taskExpiration) + err := client.Database().DeleteExpiredTasks(ctx, time.Unix(taskExpiration, 0)) if err != nil { return "failed to delete expired tasks", err } - err = client.Database().DeleteExpiredSnapshots(ctx, snapshotExpiration) + err = client.Database().DeleteExpiredSnapshots(ctx, time.Unix(snapshotExpiration, 0)) if err != nil { return "failed to delete expired snapshots", err } @@ -55,8 +55,8 @@ func CreateCleanupTasks(client core.Client) error { ReferenceID: "database_cleanup", ScheduledAfter: now, Data: map[string]any{ - "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90), // 90 days - "expiration_tasks": now.Add(-1 * time.Hour * 24 * 7), // 7 days + "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90).Unix(), // 90 days + "expiration_tasks": now.Add(-1 * time.Hour * 24 * 7).Unix(), // 7 days }, } diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 8bef200a..1301dbcb 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -132,10 +132,10 @@ func NewAttemptLog(task Task, comment, err string) TaskLog { Returns up limit tasks that have TaskStatusInProgress and were last updates 1+ hours ago */ func (c *client) GetStaleTasks(ctx context.Context, limit int) ([]Task, error) { - models, err := c.prisma.CronTask.FindMany(db.CronTask.And( + models, err := c.prisma.CronTask.FindMany( db.CronTask.Status.Equals(string(TaskStatusInProgress)), - db.CronTask.UpdatedAt.Before(time.Now().Add(time.Hour*-1)), - )).OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)).Take(limit).Exec(ctx) + db.CronTask.LastRun.Before(time.Now().Add(time.Hour*-1)), + ).OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)).Take(limit).Exec(ctx) if err != nil && !db.IsErrNotFound(err) { return nil, err } From 0b2b26ceed0fc29a303992799f52c4a3af9e8a16 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Jun 2024 01:15:15 -0400 Subject: [PATCH 082/341] updating last run --- cmds/core/scheduler/queue.go | 1 + cmds/core/scheduler/workers.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/cmds/core/scheduler/queue.go b/cmds/core/scheduler/queue.go index b724ee11..faf92a8a 100644 --- a/cmds/core/scheduler/queue.go +++ b/cmds/core/scheduler/queue.go @@ -78,6 +78,7 @@ func (q *Queue) Process(callback func(error), tasks ...database.Task) { }) return } + t.LastRun = time.Now() attempt := database.TaskLog{ Targets: t.Targets, diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index 65ffb35b..1a9900d8 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -82,6 +82,7 @@ func RestartTasksWorker(queue *Queue) func() { staleTasks, err := queue.core.Database().GetStaleTasks(ctx, 100) if err != nil { log.Err(err).Msg("failed to reschedule stale tasks") + return } if len(staleTasks) < 1 { @@ -97,6 +98,8 @@ func RestartTasksWorker(queue *Queue) func() { err = queue.core.Database().UpdateTasks(ctx, staleTasks...) if err != nil { log.Err(err).Msg("failed to update stale tasks") + return } + log.Debug().Int("count", len(staleTasks)).Msg("rescheduled stale tasks") } } From 560ec3614ba586239eedf2a6b3c03565a9d24afa Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Jun 2024 01:16:18 -0400 Subject: [PATCH 083/341] typo --- cmds/core/scheduler/tasks/sessions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index f5147654..8e430cce 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -30,7 +30,7 @@ func init() { } if len(task.Targets) < 1 { task.Data["triesLeft"] = int(0) // do not retry - return "targed ids cannot be left blank", errors.New("invalid targets length") + return "target ids cannot be left blank", errors.New("invalid targets length") } forceUpdate, _ := task.Data["force"].(bool) From 5fa08b42cfa32081f6be513bc7fdee2059e75a42 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Jun 2024 12:02:51 -0400 Subject: [PATCH 084/341] fixed tasks --- cmds/core/scheduler/workers.go | 5 ++++- internal/database/tasks.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index 1a9900d8..c23f2f78 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -84,17 +84,20 @@ func RestartTasksWorker(queue *Queue) func() { log.Err(err).Msg("failed to reschedule stale tasks") return } + log.Debug().Int("count", len(staleTasks)).Msg("fetched stale tasks from database") if len(staleTasks) < 1 { return } now := time.Now() - for _, task := range staleTasks { + for i, task := range staleTasks { task.Status = database.TaskStatusScheduled task.ScheduledAfter = now + staleTasks[i] = task } + log.Debug().Int("count", len(staleTasks)).Msg("updating stale tasks") err = queue.core.Database().UpdateTasks(ctx, staleTasks...) if err != nil { log.Err(err).Msg("failed to update stale tasks") diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 1301dbcb..a2d64be9 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -215,7 +215,7 @@ func (c *client) GetAndStartTasks(ctx context.Context, limit int) ([]Task, error _, err = c.prisma.CronTask. FindMany(db.CronTask.ID.In(ids)). - Update(db.CronTask.Status.Set(string(TaskStatusInProgress))). + Update(db.CronTask.Status.Set(string(TaskStatusInProgress)), db.CronTask.LastRun.Set(time.Now())). Exec(ctx) if err != nil { return nil, err From 86171d67d69aec8b3bea3fed06e56310c3424226 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Jun 2024 13:10:32 -0400 Subject: [PATCH 085/341] added indicator icons --- internal/stats/render/session/blocks.go | 35 ++++++++++++++++++++++ internal/stats/render/session/cards.go | 21 ++++++++----- internal/stats/render/session/constants.go | 13 +++++--- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/internal/stats/render/session/blocks.go b/internal/stats/render/session/blocks.go index cc360907..197187ed 100644 --- a/internal/stats/render/session/blocks.go +++ b/internal/stats/render/session/blocks.go @@ -6,7 +6,10 @@ import ( "github.com/cufee/aftermath/internal/stats/frame" prepare "github.com/cufee/aftermath/internal/stats/prepare/common" "github.com/cufee/aftermath/internal/stats/prepare/session" + "github.com/cufee/aftermath/internal/stats/render/assets" "github.com/cufee/aftermath/internal/stats/render/common" + "github.com/disintegration/imaging" + "github.com/fogleman/gg" ) func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width float64) common.Block { @@ -55,6 +58,10 @@ func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width } } +func blankBlock(width, height float64) common.Block { + return common.NewImageContent(common.Style{Width: width, Height: height}, gg.NewContext(int(width), int(height)).Image()) +} + func vehicleWN8Icon(wn8 frame.Value) common.Block { ratingColors := common.GetWN8Colors(wn8.Float()) if wn8.Float() <= 0 { @@ -64,3 +71,31 @@ func vehicleWN8Icon(wn8 frame.Value) common.Block { iconTop := common.AftermathLogo(ratingColors.Background, common.SmallLogoOptions()) return common.NewImageContent(common.Style{Width: vehicleWN8IconSize, Height: vehicleWN8IconSize}, iconTop) } + +func vehicleComparisonIcon(session, career frame.Value) common.Block { + ctx := gg.NewContext(int(vehicleComparisonIconSize), int(vehicleComparisonIconSize)) + switch { + case session.Float() > career.Float()*1.05: + icon, _ := assets.GetLoadedImage("triangle-up-solid") + ctx.DrawImage(imaging.Fill(icon, int(vehicleComparisonIconSize), int(vehicleComparisonIconSize), imaging.Center, imaging.Linear), 0, 0) + return common.NewImageContent(common.Style{BackgroundColor: color.RGBA{3, 201, 169, 255}}, ctx.Image()) + // return common.NewImageContent(common.Style{BackgroundColor: common.TextPrimary}, ctx.Image()) + + case session.Float() < career.Float()*0.95: + icon, _ := assets.GetLoadedImage("triangle-down-solid") + ctx.DrawImage(imaging.Fill(icon, int(vehicleComparisonIconSize), int(vehicleComparisonIconSize), imaging.Center, imaging.Linear), 0, 0) + return common.NewImageContent(common.Style{BackgroundColor: color.RGBA{231, 130, 141, 255}}, ctx.Image()) + // return common.NewImageContent(common.Style{BackgroundColor: common.TextPrimary}, ctx.Image()) + + default: + return common.NewImageContent(common.Style{}, ctx.Image()) + } +} + +func blockWithVehicleIcon(block common.Block, session, career frame.Value) common.Block { + return common.NewBlocksContent(common.Style{}, block, vehicleComparisonIcon(session, career)) +} + +func blockShouldHaveCompareIcon(block prepare.StatsBlock[session.BlockData]) bool { + return block.Tag != prepare.TagBattles +} diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 3d0e8eb6..453244c4 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -216,6 +216,7 @@ func vehicleBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], sessionS size := common.MeasureString(block.Label, *labelStyle.Font) width = common.Max(width, size.TotalWidth+labelStyle.PaddingX*2+vehicleLegendLabelContainer.PaddingX*2) } + width += vehicleComparisonIconSize maxBlockWidth = common.Max(maxBlockWidth, width) presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], width) } @@ -278,13 +279,17 @@ func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, var content []common.Block for _, block := range vehicle.Blocks { var blockContent []common.Block - blockContent = append(blockContent, common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String())) - if block.Data.Career.Float() > 0 { - blockContent = append(blockContent, common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String())) + if blockShouldHaveCompareIcon(block) { + blockContent = append(blockContent, blockWithVehicleIcon(common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), block.Data.Session, block.Data.Career)) + } else { + blockContent = append(blockContent, common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String())) } + + style := statsBlockStyle(blockSizes[block.Tag.String()]) content = append(content, - common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), blockContent...), + common.NewBlocksContent(style, blockContent...), ) + if block.Tag == prepare.TagWN8 { vehicleWN8 = block.Value } @@ -324,10 +329,12 @@ func makeVehicleHighlightCard(vehicle session.VehicleCard, blockSizes map[string func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { var content []common.Block for _, block := range reference.Blocks { + containerStyle := statsBlockStyle(blockSizes[block.Tag.String()]) + if blockShouldHaveCompareIcon(block) { + containerStyle.AlignItems = common.AlignItemsStart + } content = append(content, - common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), - common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label)), - ), + common.NewBlocksContent(containerStyle, common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label))), ) } style := vehicleCardStyle(cardWidth) diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go index 54230f80..9b1f2d96 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/constants.go @@ -13,9 +13,10 @@ type blockStyle struct { } var ( - vehicleWN8IconSize = 20.0 - specialRatingIconSize = 60.0 - minPrimaryCardWidth = 300.0 // making the primary card too small looks bad if there are no battles in a session + vehicleWN8IconSize = 20.0 + specialRatingIconSize = 60.0 + vehicleComparisonIconSize = 10.0 + minPrimaryCardWidth = 300.0 // making the primary card too small looks bad if there are no battles in a session ) var ( @@ -104,15 +105,19 @@ func vehicleCardTitleContainerStyle(width float64) common.Style { return common.Style{ JustifyContent: common.JustifyContentSpaceBetween, Direction: common.DirectionHorizontal, + AlignItems: common.AlignItemsCenter, Width: width, + PaddingX: 2.5, + PaddingY: 2.5, Gap: 10, } } func vehicleCardStyle(width float64) common.Style { style := defaultCardStyle(width) - style.PaddingX, style.PaddingY = 20, 15 + style.PaddingX, style.PaddingY = 15, 10 style.Gap = 5 + // style.Debug = true return style } From 18ce916699a1709df58ecadba1f2a6caf03e74d7 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Jun 2024 13:14:37 -0400 Subject: [PATCH 086/341] fixed horizontal alignment --- internal/stats/render/session/cards.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 453244c4..09c6d854 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -329,12 +329,13 @@ func makeVehicleHighlightCard(vehicle session.VehicleCard, blockSizes map[string func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { var content []common.Block for _, block := range reference.Blocks { - containerStyle := statsBlockStyle(blockSizes[block.Tag.String()]) + label := common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label)) if blockShouldHaveCompareIcon(block) { - containerStyle.AlignItems = common.AlignItemsStart + label = common.NewBlocksContent(vehicleLegendLabelContainer, blockWithVehicleIcon(common.NewTextContent(vehicleBlockStyle.label, block.Label), frame.InvalidValue, frame.InvalidValue)) } + containerStyle := statsBlockStyle(blockSizes[block.Tag.String()]) content = append(content, - common.NewBlocksContent(containerStyle, common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label))), + common.NewBlocksContent(containerStyle, label), ) } style := vehicleCardStyle(cardWidth) From 044f684ddb34339295a6709dfcdd0737fc1ea740 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Jun 2024 13:17:49 -0400 Subject: [PATCH 087/341] moved the icon outside the pill --- internal/stats/render/session/blocks.go | 9 +++++---- internal/stats/render/session/cards.go | 3 +-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/stats/render/session/blocks.go b/internal/stats/render/session/blocks.go index 197187ed..ed30dae9 100644 --- a/internal/stats/render/session/blocks.go +++ b/internal/stats/render/session/blocks.go @@ -75,20 +75,21 @@ func vehicleWN8Icon(wn8 frame.Value) common.Block { func vehicleComparisonIcon(session, career frame.Value) common.Block { ctx := gg.NewContext(int(vehicleComparisonIconSize), int(vehicleComparisonIconSize)) switch { + case session.Float() < 0, career.Float() < 0: + fallthrough + default: + return common.NewImageContent(common.Style{}, ctx.Image()) + case session.Float() > career.Float()*1.05: icon, _ := assets.GetLoadedImage("triangle-up-solid") ctx.DrawImage(imaging.Fill(icon, int(vehicleComparisonIconSize), int(vehicleComparisonIconSize), imaging.Center, imaging.Linear), 0, 0) return common.NewImageContent(common.Style{BackgroundColor: color.RGBA{3, 201, 169, 255}}, ctx.Image()) - // return common.NewImageContent(common.Style{BackgroundColor: common.TextPrimary}, ctx.Image()) case session.Float() < career.Float()*0.95: icon, _ := assets.GetLoadedImage("triangle-down-solid") ctx.DrawImage(imaging.Fill(icon, int(vehicleComparisonIconSize), int(vehicleComparisonIconSize), imaging.Center, imaging.Linear), 0, 0) return common.NewImageContent(common.Style{BackgroundColor: color.RGBA{231, 130, 141, 255}}, ctx.Image()) - // return common.NewImageContent(common.Style{BackgroundColor: common.TextPrimary}, ctx.Image()) - default: - return common.NewImageContent(common.Style{}, ctx.Image()) } } diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 09c6d854..540f521b 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -331,7 +331,7 @@ func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string] for _, block := range reference.Blocks { label := common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label)) if blockShouldHaveCompareIcon(block) { - label = common.NewBlocksContent(vehicleLegendLabelContainer, blockWithVehicleIcon(common.NewTextContent(vehicleBlockStyle.label, block.Label), frame.InvalidValue, frame.InvalidValue)) + label = blockWithVehicleIcon(common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label)), frame.InvalidValue, frame.InvalidValue) } containerStyle := statsBlockStyle(blockSizes[block.Tag.String()]) content = append(content, @@ -356,7 +356,6 @@ func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, } else { col = common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), - // common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String()), common.NewTextContent(vehicleBlockStyle.label, block.Label), ) } From ecdb55c5d66599736deab1dc141eca8c5d47e815 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Jun 2024 13:27:53 -0400 Subject: [PATCH 088/341] proactively acking interaction --- cmds/discord/rest/interactions.go | 6 +++++- cmds/discord/router/handler.go | 23 ++++++++++------------- internal/stats/render/session/blocks.go | 1 - 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cmds/discord/rest/interactions.go b/cmds/discord/rest/interactions.go index 1c87c2d6..692b2b7d 100644 --- a/cmds/discord/rest/interactions.go +++ b/cmds/discord/rest/interactions.go @@ -7,7 +7,11 @@ import ( ) func (c *Client) SendInteractionResponse(id, token string, data discordgo.InteractionResponse) error { - req, err := c.interactionRequest("POST", discordgo.EndpointInteractionResponse(id, token), data, data.Data.Files) + var files []*discordgo.File + if data.Data != nil { + files = data.Data.Files + } + req, err := c.interactionRequest("POST", discordgo.EndpointInteractionResponse(id, token), data, files) if err != nil { return err } diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 1c2e26fd..327f1858 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -111,26 +111,23 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { return } - // ack the interaction - err = writeDeferredInteractionResponseAck(w, data.Type, command.Ephemeral) + // ack the interaction proactively + err = router.restClient.SendInteractionResponse(data.ID, data.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseDeferredChannelMessageWithSource}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Err(err).Str("id", data.ID).Msg("failed to ack an interaction") return } + // write the ack into response + err = writeDeferredInteractionResponseAck(w, data.Type, command.Ephemeral) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Err(err).Str("id", data.ID).Msg("failed to write an interaction ack response") + return + } // route the interaction to a proper handler for a later reply - state := &interactionState{mx: &sync.Mutex{}} - state.mx.Lock() - go func() { - // unlock once this context is done and the ack is delivered - <-r.Context().Done() - log.Debug().Str("id", data.ID).Msg("sent an interaction ack") - - state.acked = true - state.mx.Unlock() - }() - + state := &interactionState{mx: &sync.Mutex{}, acked: true} router.startInteractionHandlerAsync(data, state, command) }, nil } diff --git a/internal/stats/render/session/blocks.go b/internal/stats/render/session/blocks.go index ed30dae9..47540f1e 100644 --- a/internal/stats/render/session/blocks.go +++ b/internal/stats/render/session/blocks.go @@ -89,7 +89,6 @@ func vehicleComparisonIcon(session, career frame.Value) common.Block { icon, _ := assets.GetLoadedImage("triangle-down-solid") ctx.DrawImage(imaging.Fill(icon, int(vehicleComparisonIconSize), int(vehicleComparisonIconSize), imaging.Center, imaging.Linear), 0, 0) return common.NewImageContent(common.Style{BackgroundColor: color.RGBA{231, 130, 141, 255}}, ctx.Image()) - } } From 9210fcef67a3f16076f3555f5f25d05877113d17 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Jun 2024 16:26:37 -0400 Subject: [PATCH 089/341] comparison icons on overview cards --- internal/stats/prepare/session/card.go | 2 +- internal/stats/render/session/blocks.go | 16 +++++-- internal/stats/render/session/cards.go | 53 +++++++++++++++++----- internal/stats/render/session/constants.go | 4 +- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index b81b8cc4..fc3f947d 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -98,7 +98,7 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] return int(b.LastBattleTime.Unix() - a.LastBattleTime.Unix()) }) for _, data := range unratedVehicles { - if len(cards.Unrated.Vehicles) >= 5 { + if len(cards.Unrated.Vehicles) >= 10 { break } if data.Battles < 1 { diff --git a/internal/stats/render/session/blocks.go b/internal/stats/render/session/blocks.go index 47540f1e..8f7bc811 100644 --- a/internal/stats/render/session/blocks.go +++ b/internal/stats/render/session/blocks.go @@ -45,7 +45,7 @@ func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width column = append(column, icon) } column = append(column, common.NewBlocksContent(overviewColumnStyle(width), - common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + blockWithDoubleVehicleIcon(common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), block.Data.Session, block.Data.Career), )) return common.NewBlocksContent(specialRatingColumnStyle, column...) @@ -80,15 +80,21 @@ func vehicleComparisonIcon(session, career frame.Value) common.Block { default: return common.NewImageContent(common.Style{}, ctx.Image()) - case session.Float() > career.Float()*1.05: + case session.Float() > career.Float(): icon, _ := assets.GetLoadedImage("triangle-up-solid") ctx.DrawImage(imaging.Fill(icon, int(vehicleComparisonIconSize), int(vehicleComparisonIconSize), imaging.Center, imaging.Linear), 0, 0) return common.NewImageContent(common.Style{BackgroundColor: color.RGBA{3, 201, 169, 255}}, ctx.Image()) - case session.Float() < career.Float()*0.95: + case session.Float() < career.Float(): icon, _ := assets.GetLoadedImage("triangle-down-solid") ctx.DrawImage(imaging.Fill(icon, int(vehicleComparisonIconSize), int(vehicleComparisonIconSize), imaging.Center, imaging.Linear), 0, 0) return common.NewImageContent(common.Style{BackgroundColor: color.RGBA{231, 130, 141, 255}}, ctx.Image()) + + case session.Float() == career.Float(): + ctx.DrawRectangle(vehicleComparisonIconSize*0.2, (vehicleComparisonIconSize-2)/2, vehicleComparisonIconSize*0.8, 2) + ctx.SetColor(common.TextAlt) + ctx.Fill() + return common.NewImageContent(common.Style{}, ctx.Image()) } } @@ -96,6 +102,10 @@ func blockWithVehicleIcon(block common.Block, session, career frame.Value) commo return common.NewBlocksContent(common.Style{}, block, vehicleComparisonIcon(session, career)) } +func blockWithDoubleVehicleIcon(block common.Block, session, career frame.Value) common.Block { + return common.NewBlocksContent(common.Style{}, blankBlock(vehicleComparisonIconSize, 1), block, vehicleComparisonIcon(session, career)) +} + func blockShouldHaveCompareIcon(block prepare.StatsBlock[session.BlockData]) bool { return block.Tag != prepare.TagBattles } diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index 540f521b..c09ec22e 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -17,10 +17,13 @@ import ( func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []database.UserSubscription, opts render.Options) (render.Segments, error) { var ( // primary cards - shouldRenderUnratedOverview = session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 - shouldRenderUnratedHighlights = session.RegularBattles.Battles > 0 && len(session.RegularBattles.Vehicles) > 3 - shouldRenderRatingOverview = session.RatingBattles.Battles > 0 - shouldRenderRatingVehicles = len(cards.Unrated.Vehicles) == 0 + // when there are some unrated battles or no battles at all + shouldRenderUnratedOverview = session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 + // when there are 3 vehicle cards and no rating overview cards or there are 6 vehicle cards and some rating battles + shouldRenderUnratedHighlights = (session.RegularBattles.Battles > 0 && session.RatingBattles.Battles < 1 && len(cards.Unrated.Vehicles) > 3) || + (session.RegularBattles.Battles > 0 && len(cards.Unrated.Vehicles) > 6) + shouldRenderRatingOverview = session.RatingBattles.Battles > 0 + shouldRenderRatingVehicles = len(cards.Unrated.Vehicles) == 0 // secondary cards shouldRenderUnratedVehicles = len(cards.Unrated.Vehicles) > 0 ) @@ -54,21 +57,33 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } if shouldRenderUnratedOverview { + var totalContentWidth float64 = overviewCardStyle(0).Gap * float64(len(cards.Unrated.Overview.Blocks)-1) for _, column := range cards.Unrated.Overview.Blocks { - presetBlockWidth, _ := overviewColumnBlocksWidth(column, overviewStatsBlockStyle.session, overviewStatsBlockStyle.career, overviewStatsBlockStyle.label, overviewColumnStyle(0)) + styleWithIconOffset := overviewStatsBlockStyle + styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize + + presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) + totalContentWidth += contentWidth for key, width := range presetBlockWidth { primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) } } + primaryCardWidth = common.Max(primaryCardWidth, totalContentWidth) } if shouldRenderRatingOverview { + var totalContentWidth float64 = overviewCardStyle(0).Gap * float64(len(cards.Rating.Overview.Blocks)-1) for _, column := range cards.Unrated.Overview.Blocks { - presetBlockWidth, _ := overviewColumnBlocksWidth(column, overviewStatsBlockStyle.session, overviewStatsBlockStyle.career, overviewStatsBlockStyle.label, overviewColumnStyle(0)) + styleWithIconOffset := overviewStatsBlockStyle + styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize + + presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) + totalContentWidth += contentWidth for key, width := range presetBlockWidth { primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) } } + primaryCardWidth = common.Max(primaryCardWidth, totalContentWidth) } // rating vehicle cards go on the primary block - only show if there are no unrated battles/vehicles if shouldRenderRatingVehicles { @@ -92,6 +107,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // [title] [label] labelSize := common.MeasureString(card.Meta, *highlightCardTitleTextStyle.Font) titleSize := common.MeasureString(card.Title, *highlightVehicleNameTextStyle.Font) + presetBlockWidth, contentWidth := highlightedVehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, highlightedVehicleBlockRowStyle(0)) // add the gap and card padding, the gap here accounts for title/label being inline with content contentWidth += highlightedVehicleBlockRowStyle(0).Gap*float64(len(card.Blocks)) + highlightedVehicleCardStyle(0).Gap + highlightedVehicleCardStyle(0).PaddingX*2 + common.Max(titleSize.TotalWidth, labelSize.TotalWidth) @@ -107,7 +123,13 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // [ label ] // [session] // [career ] - presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, vehicleBlocksRowStyle(0)) + + styleWithIconOffset := vehicleBlockStyle + // icon is only on one side, so we divide by 2 + styleWithIconOffset.label.PaddingX += vehicleComparisonIconSize / 2 + styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize / 2 + + presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, vehicleBlocksRowStyle(0)) contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)-1) + vehicleCardStyle(0).PaddingX*2 titleSize := common.MeasureString(card.Title, *vehicleCardTitleTextStyle.Font) @@ -216,7 +238,6 @@ func vehicleBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], sessionS size := common.MeasureString(block.Label, *labelStyle.Font) width = common.Max(width, size.TotalWidth+labelStyle.PaddingX*2+vehicleLegendLabelContainer.PaddingX*2) } - width += vehicleComparisonIconSize maxBlockWidth = common.Max(maxBlockWidth, width) presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], width) } @@ -265,11 +286,14 @@ func overviewColumnBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], s presetBlockWidth, contentWidth := vehicleBlocksWidth(blocks, sessionStyle, careerStyle, labelStyle, containerStyle) for _, block := range blocks { // adjust width if this column includes a special icon - if block.Tag == prepare.TagWN8 || block.Tag == prepare.TagRankedRating { + if block.Tag == prepare.TagWN8 { tierNameSize := common.MeasureString(common.GetWN8TierName(block.Value.Float()), *overviewSpecialRatingLabelStyle(nil).Font) tierNameWithPadding := tierNameSize.TotalWidth + overviewSpecialRatingPillStyle(nil).PaddingX*2 presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], specialRatingIconSize, tierNameWithPadding) } + if block.Tag == prepare.TagRankedRating { + presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], specialRatingIconSize) + } } return presetBlockWidth, contentWidth } @@ -346,13 +370,20 @@ func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string] } func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, style common.Style) common.Block { - var content []common.Block + // made all columns the same width for things to be centered + columnWidth := (style.Width - style.Gap*float64(len(card.Blocks)-1) - style.PaddingX*2) / float64(len(card.Blocks)) + var content []common.Block // add a blank block to balance the offset added from icons for _, column := range card.Blocks { var columnContent []common.Block for _, block := range column { var col common.Block if block.Tag == prepare.TagWN8 || block.Tag == prepare.TagRankedRating { col = makeSpecialRatingColumn(block, blockSizes[block.Tag.String()]) + } else if blockShouldHaveCompareIcon(block) { + col = common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), + blockWithDoubleVehicleIcon(common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), block.Data.Session, block.Data.Career), + common.NewTextContent(vehicleBlockStyle.label, block.Label), + ) } else { col = common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), @@ -361,7 +392,7 @@ func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, } columnContent = append(columnContent, col) } - content = append(content, common.NewBlocksContent(overviewColumnStyle(0), columnContent...)) + content = append(content, common.NewBlocksContent(overviewColumnStyle(columnWidth), columnContent...)) } return common.NewBlocksContent(style, content...) } diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/constants.go index 9b1f2d96..e85ffc62 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/constants.go @@ -61,12 +61,10 @@ func overviewColumnStyle(width float64) common.Style { func overviewCardStyle(width float64) common.Style { style := defaultCardStyle(width) - style.JustifyContent = common.JustifyContentSpaceAround + style.JustifyContent = common.JustifyContentSpaceBetween style.Direction = common.DirectionHorizontal style.AlignItems = common.AlignItemsEnd style.PaddingY = 20 - style.PaddingX = 10 - style.Gap = 5 // style.Debug = true return style } From 19fef9e57fcb6da6a8f8298824fb034dbbad39ec Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 18:11:18 -0400 Subject: [PATCH 090/341] initial rewrite --- .env.example | 14 +- Dockerfile | 3 - Dockerfile.migrate | 19 +- Taskfile.yaml | 41 +- cmds/core/scheduler/glossary.go | 6 +- cmds/core/scheduler/queue.go | 28 +- cmds/core/scheduler/tasks/cleanup.go | 12 +- cmds/core/scheduler/tasks/handler.go | 10 +- cmds/core/scheduler/tasks/sessions.go | 18 +- cmds/core/scheduler/tasks/split.go | 8 +- cmds/core/scheduler/workers.go | 6 +- cmds/core/server/handlers/private/accounts.go | 4 +- cmds/discord/commands/link.go | 6 +- cmds/discord/commands/manage.go | 9 +- cmds/discord/commands/session.go | 7 +- cmds/discord/commands/stats.go | 7 +- cmds/discord/common/context.go | 3 +- cmds/discord/router/router.go | 8 +- docker-compose.yaml | 39 +- go.mod | 18 +- go.sum | 56 +- internal/database/accounts.go | 209 +- internal/database/averages.go | 119 +- internal/database/cleanup.go | 30 +- internal/database/client.go | 84 +- internal/database/discord.go | 109 +- internal/database/ent/db/account.go | 259 + internal/database/ent/db/account/account.go | 232 + internal/database/ent/db/account/where.go | 586 + internal/database/ent/db/account_create.go | 457 + internal/database/ent/db/account_delete.go | 88 + internal/database/ent/db/account_query.go | 830 ++ internal/database/ent/db/account_update.go | 1087 ++ internal/database/ent/db/accountsnapshot.go | 240 + .../ent/db/accountsnapshot/accountsnapshot.go | 161 + .../database/ent/db/accountsnapshot/where.go | 498 + .../database/ent/db/accountsnapshot_create.go | 392 + .../database/ent/db/accountsnapshot_delete.go | 88 + .../database/ent/db/accountsnapshot_query.go | 605 + .../database/ent/db/accountsnapshot_update.go | 573 + .../database/ent/db/achievementssnapshot.go | 216 + .../achievementssnapshot.go | 150 + .../ent/db/achievementssnapshot/where.go | 453 + .../ent/db/achievementssnapshot_create.go | 366 + .../ent/db/achievementssnapshot_delete.go | 88 + .../ent/db/achievementssnapshot_query.go | 605 + .../ent/db/achievementssnapshot_update.go | 485 + internal/database/ent/db/appconfiguration.go | 154 + .../db/appconfiguration/appconfiguration.go | 82 + .../database/ent/db/appconfiguration/where.go | 248 + .../ent/db/appconfiguration_create.go | 290 + .../ent/db/appconfiguration_delete.go | 88 + .../database/ent/db/appconfiguration_query.go | 526 + .../ent/db/appconfiguration_update.go | 345 + .../database/ent/db/applicationcommand.go | 147 + .../applicationcommand/applicationcommand.go | 96 + .../ent/db/applicationcommand/where.go | 378 + .../ent/db/applicationcommand_create.go | 303 + .../ent/db/applicationcommand_delete.go | 88 + .../ent/db/applicationcommand_query.go | 526 + .../ent/db/applicationcommand_update.go | 379 + internal/database/ent/db/clan.go | 189 + internal/database/ent/db/clan/clan.go | 128 + internal/database/ent/db/clan/where.go | 412 + internal/database/ent/db/clan_create.go | 340 + internal/database/ent/db/clan_delete.go | 88 + internal/database/ent/db/clan_query.go | 605 + internal/database/ent/db/clan_update.go | 591 + internal/database/ent/db/client.go | 2416 ++++ internal/database/ent/db/crontask.go | 212 + internal/database/ent/db/crontask/crontask.go | 121 + internal/database/ent/db/crontask/where.go | 468 + internal/database/ent/db/crontask_create.go | 369 + internal/database/ent/db/crontask_delete.go | 88 + internal/database/ent/db/crontask_query.go | 526 + internal/database/ent/db/crontask_update.go | 587 + internal/database/ent/db/ent.go | 634 + internal/database/ent/db/enttest/enttest.go | 84 + internal/database/ent/db/hook/hook.go | 355 + internal/database/ent/db/migrate/migrate.go | 96 + internal/database/ent/db/migrate/schema.go | 524 + internal/database/ent/db/mutation.go | 11540 ++++++++++++++++ .../database/ent/db/predicate/predicate.go | 49 + internal/database/ent/db/runtime.go | 363 + internal/database/ent/db/runtime/runtime.go | 10 + internal/database/ent/db/tx.go | 249 + internal/database/ent/db/user.go | 199 + internal/database/ent/db/user/user.go | 168 + internal/database/ent/db/user/where.go | 318 + internal/database/ent/db/user_create.go | 368 + internal/database/ent/db/user_delete.go | 88 + internal/database/ent/db/user_query.go | 753 + internal/database/ent/db/user_update.go | 813 ++ internal/database/ent/db/userconnection.go | 204 + .../ent/db/userconnection/userconnection.go | 140 + .../database/ent/db/userconnection/where.go | 453 + .../database/ent/db/userconnection_create.go | 348 + .../database/ent/db/userconnection_delete.go | 88 + .../database/ent/db/userconnection_query.go | 605 + .../database/ent/db/userconnection_update.go | 420 + internal/database/ent/db/usercontent.go | 206 + .../ent/db/usercontent/usercontent.go | 133 + internal/database/ent/db/usercontent/where.go | 363 + .../database/ent/db/usercontent_create.go | 342 + .../database/ent/db/usercontent_delete.go | 88 + internal/database/ent/db/usercontent_query.go | 605 + .../database/ent/db/usercontent_update.go | 368 + internal/database/ent/db/usersubscription.go | 199 + .../db/usersubscription/usersubscription.go | 149 + .../database/ent/db/usersubscription/where.go | 478 + .../ent/db/usersubscription_create.go | 357 + .../ent/db/usersubscription_delete.go | 88 + .../database/ent/db/usersubscription_query.go | 605 + .../ent/db/usersubscription_update.go | 440 + internal/database/ent/db/vehicle.go | 141 + internal/database/ent/db/vehicle/vehicle.go | 77 + internal/database/ent/db/vehicle/where.go | 213 + internal/database/ent/db/vehicle_create.go | 268 + internal/database/ent/db/vehicle_delete.go | 88 + internal/database/ent/db/vehicle_query.go | 526 + internal/database/ent/db/vehicle_update.go | 329 + internal/database/ent/db/vehicleaverage.go | 131 + .../ent/db/vehicleaverage/vehicleaverage.go | 67 + .../database/ent/db/vehicleaverage/where.go | 168 + .../database/ent/db/vehicleaverage_create.go | 251 + .../database/ent/db/vehicleaverage_delete.go | 88 + .../database/ent/db/vehicleaverage_query.go | 526 + .../database/ent/db/vehicleaverage_update.go | 250 + internal/database/ent/db/vehiclesnapshot.go | 227 + .../ent/db/vehiclesnapshot/vehiclesnapshot.go | 160 + .../database/ent/db/vehiclesnapshot/where.go | 523 + .../database/ent/db/vehiclesnapshot_create.go | 384 + .../database/ent/db/vehiclesnapshot_delete.go | 88 + .../database/ent/db/vehiclesnapshot_query.go | 605 + .../database/ent/db/vehiclesnapshot_update.go | 485 + internal/database/ent/generate.go | 3 + internal/database/ent/migrate.go | 63 + internal/database/ent/migrate/main.go | 40 + .../migrations/20240622203812_init.sql | 102 + .../database/ent/migrate/migrations/atlas.sum | 2 + internal/database/ent/schema/account.go | 57 + .../database/ent/schema/accountsnapshot.go | 49 + .../ent/schema/achievementssnapshot.go | 44 + .../database/ent/schema/appconfiguration.go | 33 + .../database/ent/schema/applicationcommand.go | 45 + internal/database/ent/schema/clan.go | 65 + internal/database/ent/schema/crontask.go | 43 + internal/database/ent/schema/defaults.go | 24 + internal/database/ent/schema/user.go | 43 + .../database/ent/schema/userconnection.go | 64 + internal/database/ent/schema/usercontent.go | 43 + .../database/ent/schema/usersubscription.go | 43 + internal/database/ent/schema/vehicle.go | 41 + .../database/ent/schema/vehicleaverage.go | 38 + .../database/ent/schema/vehiclesnapshot.go | 47 + internal/database/errors.go | 7 + internal/database/models/account.go | 16 + internal/database/models/account_snapshot.go | 18 + .../database/models/application_command.go | 8 + internal/database/models/task.go | 139 + internal/database/models/user.go | 30 + internal/database/models/user_connection.go | 32 + internal/database/models/user_subscription.go | 106 + internal/database/models/vehicle.go | 23 + internal/database/models/vehicle_snapshot.go | 40 + internal/database/prisma/db/.gitignore | 2 - .../20240608224040_init/migration.sql | 210 - .../migration.sql | 48 - .../20240612202803_sessions/migration.sql | 50 - .../migration.sql | 11 - .../20240614000247_added_index/migration.sql | 2 - .../20240614002652_added_name/migration.sql | 22 - .../prisma/migrations/migration_lock.toml | 3 - internal/database/prisma/schema.prisma | 398 - internal/database/snapshots.go | 693 +- internal/database/snapshots_test.go | 206 +- internal/database/tasks.go | 417 +- internal/database/users.go | 356 +- internal/database/vehicles.go | 139 +- internal/logic/sessions.go | 31 +- internal/stats/fetch/client.go | 6 +- internal/stats/fetch/convert.go | 6 +- internal/stats/fetch/multisource.go | 25 +- internal/stats/prepare/common/card.go | 6 +- internal/stats/prepare/period/card.go | 6 +- internal/stats/prepare/period/preset.go | 4 +- internal/stats/prepare/session/card.go | 10 +- internal/stats/render/common/badges.go | 48 +- internal/stats/render/common/header.go | 6 +- internal/stats/render/common/player_title.go | 6 +- .../stats/render/common/tier_percentage.go | 4 +- internal/stats/render/period/cards.go | 4 +- internal/stats/render/period/image.go | 4 +- internal/stats/render/session/cards.go | 4 +- internal/stats/render/session/image.go | 4 +- internal/stats/session.go | 4 +- main.go | 12 +- 197 files changed, 50758 insertions(+), 2304 deletions(-) create mode 100644 internal/database/ent/db/account.go create mode 100644 internal/database/ent/db/account/account.go create mode 100644 internal/database/ent/db/account/where.go create mode 100644 internal/database/ent/db/account_create.go create mode 100644 internal/database/ent/db/account_delete.go create mode 100644 internal/database/ent/db/account_query.go create mode 100644 internal/database/ent/db/account_update.go create mode 100644 internal/database/ent/db/accountsnapshot.go create mode 100644 internal/database/ent/db/accountsnapshot/accountsnapshot.go create mode 100644 internal/database/ent/db/accountsnapshot/where.go create mode 100644 internal/database/ent/db/accountsnapshot_create.go create mode 100644 internal/database/ent/db/accountsnapshot_delete.go create mode 100644 internal/database/ent/db/accountsnapshot_query.go create mode 100644 internal/database/ent/db/accountsnapshot_update.go create mode 100644 internal/database/ent/db/achievementssnapshot.go create mode 100644 internal/database/ent/db/achievementssnapshot/achievementssnapshot.go create mode 100644 internal/database/ent/db/achievementssnapshot/where.go create mode 100644 internal/database/ent/db/achievementssnapshot_create.go create mode 100644 internal/database/ent/db/achievementssnapshot_delete.go create mode 100644 internal/database/ent/db/achievementssnapshot_query.go create mode 100644 internal/database/ent/db/achievementssnapshot_update.go create mode 100644 internal/database/ent/db/appconfiguration.go create mode 100644 internal/database/ent/db/appconfiguration/appconfiguration.go create mode 100644 internal/database/ent/db/appconfiguration/where.go create mode 100644 internal/database/ent/db/appconfiguration_create.go create mode 100644 internal/database/ent/db/appconfiguration_delete.go create mode 100644 internal/database/ent/db/appconfiguration_query.go create mode 100644 internal/database/ent/db/appconfiguration_update.go create mode 100644 internal/database/ent/db/applicationcommand.go create mode 100644 internal/database/ent/db/applicationcommand/applicationcommand.go create mode 100644 internal/database/ent/db/applicationcommand/where.go create mode 100644 internal/database/ent/db/applicationcommand_create.go create mode 100644 internal/database/ent/db/applicationcommand_delete.go create mode 100644 internal/database/ent/db/applicationcommand_query.go create mode 100644 internal/database/ent/db/applicationcommand_update.go create mode 100644 internal/database/ent/db/clan.go create mode 100644 internal/database/ent/db/clan/clan.go create mode 100644 internal/database/ent/db/clan/where.go create mode 100644 internal/database/ent/db/clan_create.go create mode 100644 internal/database/ent/db/clan_delete.go create mode 100644 internal/database/ent/db/clan_query.go create mode 100644 internal/database/ent/db/clan_update.go create mode 100644 internal/database/ent/db/client.go create mode 100644 internal/database/ent/db/crontask.go create mode 100644 internal/database/ent/db/crontask/crontask.go create mode 100644 internal/database/ent/db/crontask/where.go create mode 100644 internal/database/ent/db/crontask_create.go create mode 100644 internal/database/ent/db/crontask_delete.go create mode 100644 internal/database/ent/db/crontask_query.go create mode 100644 internal/database/ent/db/crontask_update.go create mode 100644 internal/database/ent/db/ent.go create mode 100644 internal/database/ent/db/enttest/enttest.go create mode 100644 internal/database/ent/db/hook/hook.go create mode 100644 internal/database/ent/db/migrate/migrate.go create mode 100644 internal/database/ent/db/migrate/schema.go create mode 100644 internal/database/ent/db/mutation.go create mode 100644 internal/database/ent/db/predicate/predicate.go create mode 100644 internal/database/ent/db/runtime.go create mode 100644 internal/database/ent/db/runtime/runtime.go create mode 100644 internal/database/ent/db/tx.go create mode 100644 internal/database/ent/db/user.go create mode 100644 internal/database/ent/db/user/user.go create mode 100644 internal/database/ent/db/user/where.go create mode 100644 internal/database/ent/db/user_create.go create mode 100644 internal/database/ent/db/user_delete.go create mode 100644 internal/database/ent/db/user_query.go create mode 100644 internal/database/ent/db/user_update.go create mode 100644 internal/database/ent/db/userconnection.go create mode 100644 internal/database/ent/db/userconnection/userconnection.go create mode 100644 internal/database/ent/db/userconnection/where.go create mode 100644 internal/database/ent/db/userconnection_create.go create mode 100644 internal/database/ent/db/userconnection_delete.go create mode 100644 internal/database/ent/db/userconnection_query.go create mode 100644 internal/database/ent/db/userconnection_update.go create mode 100644 internal/database/ent/db/usercontent.go create mode 100644 internal/database/ent/db/usercontent/usercontent.go create mode 100644 internal/database/ent/db/usercontent/where.go create mode 100644 internal/database/ent/db/usercontent_create.go create mode 100644 internal/database/ent/db/usercontent_delete.go create mode 100644 internal/database/ent/db/usercontent_query.go create mode 100644 internal/database/ent/db/usercontent_update.go create mode 100644 internal/database/ent/db/usersubscription.go create mode 100644 internal/database/ent/db/usersubscription/usersubscription.go create mode 100644 internal/database/ent/db/usersubscription/where.go create mode 100644 internal/database/ent/db/usersubscription_create.go create mode 100644 internal/database/ent/db/usersubscription_delete.go create mode 100644 internal/database/ent/db/usersubscription_query.go create mode 100644 internal/database/ent/db/usersubscription_update.go create mode 100644 internal/database/ent/db/vehicle.go create mode 100644 internal/database/ent/db/vehicle/vehicle.go create mode 100644 internal/database/ent/db/vehicle/where.go create mode 100644 internal/database/ent/db/vehicle_create.go create mode 100644 internal/database/ent/db/vehicle_delete.go create mode 100644 internal/database/ent/db/vehicle_query.go create mode 100644 internal/database/ent/db/vehicle_update.go create mode 100644 internal/database/ent/db/vehicleaverage.go create mode 100644 internal/database/ent/db/vehicleaverage/vehicleaverage.go create mode 100644 internal/database/ent/db/vehicleaverage/where.go create mode 100644 internal/database/ent/db/vehicleaverage_create.go create mode 100644 internal/database/ent/db/vehicleaverage_delete.go create mode 100644 internal/database/ent/db/vehicleaverage_query.go create mode 100644 internal/database/ent/db/vehicleaverage_update.go create mode 100644 internal/database/ent/db/vehiclesnapshot.go create mode 100644 internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go create mode 100644 internal/database/ent/db/vehiclesnapshot/where.go create mode 100644 internal/database/ent/db/vehiclesnapshot_create.go create mode 100644 internal/database/ent/db/vehiclesnapshot_delete.go create mode 100644 internal/database/ent/db/vehiclesnapshot_query.go create mode 100644 internal/database/ent/db/vehiclesnapshot_update.go create mode 100644 internal/database/ent/generate.go create mode 100644 internal/database/ent/migrate.go create mode 100644 internal/database/ent/migrate/main.go create mode 100644 internal/database/ent/migrate/migrations/20240622203812_init.sql create mode 100644 internal/database/ent/migrate/migrations/atlas.sum create mode 100644 internal/database/ent/schema/account.go create mode 100644 internal/database/ent/schema/accountsnapshot.go create mode 100644 internal/database/ent/schema/achievementssnapshot.go create mode 100644 internal/database/ent/schema/appconfiguration.go create mode 100644 internal/database/ent/schema/applicationcommand.go create mode 100644 internal/database/ent/schema/clan.go create mode 100644 internal/database/ent/schema/crontask.go create mode 100644 internal/database/ent/schema/defaults.go create mode 100644 internal/database/ent/schema/user.go create mode 100644 internal/database/ent/schema/userconnection.go create mode 100644 internal/database/ent/schema/usercontent.go create mode 100644 internal/database/ent/schema/usersubscription.go create mode 100644 internal/database/ent/schema/vehicle.go create mode 100644 internal/database/ent/schema/vehicleaverage.go create mode 100644 internal/database/ent/schema/vehiclesnapshot.go create mode 100644 internal/database/errors.go create mode 100644 internal/database/models/account.go create mode 100644 internal/database/models/account_snapshot.go create mode 100644 internal/database/models/application_command.go create mode 100644 internal/database/models/task.go create mode 100644 internal/database/models/user.go create mode 100644 internal/database/models/user_connection.go create mode 100644 internal/database/models/user_subscription.go create mode 100644 internal/database/models/vehicle.go create mode 100644 internal/database/models/vehicle_snapshot.go delete mode 100644 internal/database/prisma/db/.gitignore delete mode 100644 internal/database/prisma/migrations/20240608224040_init/migration.sql delete mode 100644 internal/database/prisma/migrations/20240611180703_added_snapshots/migration.sql delete mode 100644 internal/database/prisma/migrations/20240612202803_sessions/migration.sql delete mode 100644 internal/database/prisma/migrations/20240614000132_added_app_commands/migration.sql delete mode 100644 internal/database/prisma/migrations/20240614000247_added_index/migration.sql delete mode 100644 internal/database/prisma/migrations/20240614002652_added_name/migration.sql delete mode 100644 internal/database/prisma/migrations/migration_lock.toml delete mode 100644 internal/database/prisma/schema.prisma diff --git a/.env.example b/.env.example index 6c94cc36..d205038b 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,15 @@ +# Atlas is currently broken for libsql +# You need to clone and build https://github.com/Cufee/atlas-libsql +ATLAS_TEMP_DIR="path/to/atlas-libsql/cmd/atlas/binary" + # This is not required for local deployment. When deploying with Dokploy, this is the domain aftermath service will be available on. TRAEFIK_HOST="local.amth.one" -# Database configuration for compose -DATABASE_NAME="dev_local.db" -DATABASE_DIR="/tmp" -# This should be an absolute path, otherwise prisma fails to run correctly. This variable is only used when developing locally or deploying without Compose. -DATABASE_URL="file:/tmp/dev_local.db" +# Path for a bind volume when running with compose +DATABASE_DIR="tmp/" +# URL to a libsql instance +# https://github.com/tursodatabase/libsql/blob/main/docs/DOCKER.md +DATABASE_URL="libsql://0.0.0.0:8080" # Init INIT_GLOBAL_ADMIN_USER="" # Discord user ID for a user who will be assigned permissions.GlobalAdmin on startup, can be left blank diff --git a/Dockerfile b/Dockerfile index 990393ba..ddcfd59f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,6 @@ WORKDIR /workspace COPY go.mod go.sum ./ RUN go mod download -# prefetch the binaries, so that they will be cached and not downloaded on each change -RUN go run github.com/steebchen/prisma-client-go prefetch - COPY ./ ./ # generate the Prisma Client Go client RUN go generate ./... diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 3764f1af..16909e31 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -1,17 +1,14 @@ -FROM golang:1.22.3-alpine +FROM golang:1.22.3-bookworm WORKDIR /migrate # copy migrations and schema -COPY ./internal/database/prisma/migrations /prisma/migrations -COPY ./internal/database/prisma/schema.prisma /prisma/schema.prisma +COPY ./internal/database/ent/migrate/migrations /migrations -# copy go.mod -COPY ./go.mod /go.mod -COPY ./go.sum /go.sum +RUN git clone https://github.com/Cufee/atlas-libsql.git +WORKDIR /migrate/atlas-libsql/cmd/atlas +RUN go mod download +RUN go generate ./... +RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/atlas -# install prisma and prefetch binaries -RUN go install github.com/steebchen/prisma-client-go@latest -RUN go run github.com/steebchen/prisma-client-go prefetch - -ENTRYPOINT ["sh", "-c", "go run github.com/steebchen/prisma-client-go migrate deploy --schema /prisma/schema.prisma"] +ENTRYPOINT ["sh", "-c", 'atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://0.0.0.0:8080'] diff --git a/Taskfile.yaml b/Taskfile.yaml index 8f01f48b..8cbe17a8 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -4,14 +4,12 @@ dotenv: - ".env" vars: - PRISMA_CLIENT: github.com/steebchen/prisma-client-go@v0.37.0 - PRISMA_SCHEMA: --schema ./internal/database/prisma/schema.prisma + ENT_CMD: go run -mod=mod entgo.io/ent/cmd/ent + ENT_DIRECTORY: internal/database/ent/schema tasks: test: desc: runs tests - env: - DATABASE_URL: "file:{{.USER_WORKING_DIR}}/tmp/local_test.db" cmds: - go run {{ .PRISMA_CLIENT }} db push {{ .PRISMA_SCHEMA }} - | @@ -21,33 +19,22 @@ tasks: go test -timeout 30s --count=1 -v -run {{ .CLI_ARGS }} fi - prisma-install: - desc: installs the correct version of prisma - cmd: go get {{ .PRISMA_CLIENT }} + ent-new: + desc: init a new schema with a given name + cmd: '{{.ENT_CMD}} new --target {{.ENT_DIRECTORY}} {{.CLI_ARGS}}' - prisma: - desc: run a custom prisma command by adding `-- some command` to this task - cmd: go run {{ .PRISMA_CLIENT }} {{ .PRISMA_SCHEMA }} {{ .CLI_ARGS }} + ent-generate: + desc: generate go code for the ent schema directory + cmd: go generate internal/database/ent - db-push: - desc: sync the database with the schema for development - cmd: go run {{ .PRISMA_CLIENT }} db push {{ .PRISMA_SCHEMA }} - - db-generate: - desc: re-generate the Go client - cmd: go run {{ .PRISMA_CLIENT }} generate {{ .PRISMA_SCHEMA }} - - db-migrate-dev: - desc: for production use, create a migration locally - cmd: go run {{ .PRISMA_CLIENT }} migrate dev {{ .PRISMA_SCHEMA }} - - db-migrate-deploy: - desc: sync production database with migrations - cmd: go run {{ .PRISMA_CLIENT }} migrate deploy {{ .PRISMA_SCHEMA }} + db-migrate: + desc: generate migrations + cmd: atlas migrate diff init --dir "file://internal/database/ent/migrate/migrations" --to "ent://internal/database/ent/schema" --dev-url "sqlite://tmp/file?mode=memory&_fk=1" + db-migrate-apply: + desc: apply migrations using atlas + cmd: ${ATLAS_TEMP_DIR} migrate apply --allow-dirty --dir "file://internal/database/ent/migrate/migrations" --tx-mode all --url "libsql+ws://0.0.0.0:8080" dev: desc: Start a local dev server - deps: - - db-push cmds: - air diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go index b6961d73..89f5d23e 100644 --- a/cmds/core/scheduler/glossary.go +++ b/cmds/core/scheduler/glossary.go @@ -5,7 +5,7 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/rs/zerolog/log" "golang.org/x/text/language" ) @@ -48,9 +48,9 @@ func UpdateGlossaryWorker(client core.Client) func() { return } - vehicles := make(map[string]database.Vehicle) + vehicles := make(map[string]models.Vehicle) for id, data := range glossary { - vehicles[id] = database.Vehicle{ + vehicles[id] = models.Vehicle{ ID: id, Tier: data.Tier, Type: data.Type, diff --git a/cmds/core/scheduler/queue.go b/cmds/core/scheduler/queue.go index faf92a8a..d0bd8c8d 100644 --- a/cmds/core/scheduler/queue.go +++ b/cmds/core/scheduler/queue.go @@ -7,7 +7,7 @@ import ( "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/cmds/core/scheduler/tasks" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/rs/zerolog/log" ) @@ -16,7 +16,7 @@ type Queue struct { concurrencyLimit int lastTaskRun time.Time - handlers map[database.TaskType]tasks.TaskHandler + handlers map[models.TaskType]tasks.TaskHandler core core.Client } @@ -32,7 +32,7 @@ func (q *Queue) LastTaskRun() time.Time { return q.lastTaskRun } -func NewQueue(client core.Client, handlers map[database.TaskType]tasks.TaskHandler, concurrencyLimit int) *Queue { +func NewQueue(client core.Client, handlers map[models.TaskType]tasks.TaskHandler, concurrencyLimit int) *Queue { return &Queue{ core: client, handlers: handlers, @@ -41,7 +41,7 @@ func NewQueue(client core.Client, handlers map[database.TaskType]tasks.TaskHandl } } -func (q *Queue) Process(callback func(error), tasks ...database.Task) { +func (q *Queue) Process(callback func(error), tasks ...models.Task) { var err error if callback != nil { defer callback(err) @@ -55,10 +55,10 @@ func (q *Queue) Process(callback func(error), tasks ...database.Task) { var wg sync.WaitGroup q.lastTaskRun = time.Now() - processedTasks := make(chan database.Task, len(tasks)) + processedTasks := make(chan models.Task, len(tasks)) for _, task := range tasks { wg.Add(1) - go func(t database.Task) { + go func(t models.Task) { q.limiter <- struct{}{} defer func() { processedTasks <- t @@ -70,8 +70,8 @@ func (q *Queue) Process(callback func(error), tasks ...database.Task) { handler, ok := q.handlers[t.Type] if !ok { - t.Status = database.TaskStatusFailed - t.LogAttempt(database.TaskLog{ + t.Status = models.TaskStatusFailed + t.LogAttempt(models.TaskLog{ Targets: t.Targets, Timestamp: time.Now(), Error: "missing task type handler", @@ -80,7 +80,7 @@ func (q *Queue) Process(callback func(error), tasks ...database.Task) { } t.LastRun = time.Now() - attempt := database.TaskLog{ + attempt := models.TaskLog{ Targets: t.Targets, Timestamp: time.Now(), } @@ -89,9 +89,9 @@ func (q *Queue) Process(callback func(error), tasks ...database.Task) { attempt.Comment = message if err != nil { attempt.Error = err.Error() - t.Status = database.TaskStatusFailed + t.Status = models.TaskStatusFailed } else { - t.Status = database.TaskStatusComplete + t.Status = models.TaskStatusComplete } t.LogAttempt(attempt) }(task) @@ -101,16 +101,16 @@ func (q *Queue) Process(callback func(error), tasks ...database.Task) { close(processedTasks) rescheduledCount := 0 - processedSlice := make([]database.Task, 0, len(processedTasks)) + processedSlice := make([]models.Task, 0, len(processedTasks)) for task := range processedTasks { handler, ok := q.handlers[task.Type] if !ok { continue } - if task.Status == database.TaskStatusFailed && handler.ShouldRetry(&task) { + if task.Status == models.TaskStatusFailed && handler.ShouldRetry(&task) { rescheduledCount++ - task.Status = database.TaskStatusScheduled + task.Status = models.TaskStatusScheduled } processedSlice = append(processedSlice, task) } diff --git a/cmds/core/scheduler/tasks/cleanup.go b/cmds/core/scheduler/tasks/cleanup.go index b753c59d..18b28de1 100644 --- a/cmds/core/scheduler/tasks/cleanup.go +++ b/cmds/core/scheduler/tasks/cleanup.go @@ -7,12 +7,12 @@ import ( "github.com/pkg/errors" "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" ) func init() { - defaultHandlers[database.TaskTypeDatabaseCleanup] = TaskHandler{ - Process: func(client core.Client, task database.Task) (string, error) { + defaultHandlers[models.TaskTypeDatabaseCleanup] = TaskHandler{ + Process: func(client core.Client, task models.Task) (string, error) { if task.Data == nil { return "no data provided", errors.New("no data provided") } @@ -41,7 +41,7 @@ func init() { return "cleanup complete", nil }, - ShouldRetry: func(task *database.Task) bool { + ShouldRetry: func(task *models.Task) bool { return false }, } @@ -50,8 +50,8 @@ func init() { func CreateCleanupTasks(client core.Client) error { now := time.Now() - task := database.Task{ - Type: database.TaskTypeDatabaseCleanup, + task := models.Task{ + Type: models.TaskTypeDatabaseCleanup, ReferenceID: "database_cleanup", ScheduledAfter: now, Data: map[string]any{ diff --git a/cmds/core/scheduler/tasks/handler.go b/cmds/core/scheduler/tasks/handler.go index fa3d0e6d..11e49bb1 100644 --- a/cmds/core/scheduler/tasks/handler.go +++ b/cmds/core/scheduler/tasks/handler.go @@ -2,16 +2,16 @@ package tasks import ( "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" ) type TaskHandler struct { - Process func(client core.Client, task database.Task) (string, error) - ShouldRetry func(task *database.Task) bool + Process func(client core.Client, task models.Task) (string, error) + ShouldRetry func(task *models.Task) bool } -var defaultHandlers = make(map[database.TaskType]TaskHandler) +var defaultHandlers = make(map[models.TaskType]TaskHandler) -func DefaultHandlers() map[database.TaskType]TaskHandler { +func DefaultHandlers() map[models.TaskType]TaskHandler { return defaultHandlers } diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index 8e430cce..93ea0273 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -8,14 +8,14 @@ import ( "github.com/pkg/errors" "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/logic" "github.com/rs/zerolog/log" ) func init() { - defaultHandlers[database.TaskTypeRecordSessions] = TaskHandler{ - Process: func(client core.Client, task database.Task) (string, error) { + defaultHandlers[models.TaskTypeRecordSessions] = TaskHandler{ + Process: func(client core.Client, task models.Task) (string, error) { if task.Data == nil { return "no data provided", errors.New("no data provided") } @@ -55,7 +55,7 @@ func init() { } return "retrying failed accounts", errors.New("some accounts failed") }, - ShouldRetry: func(task *database.Task) bool { + ShouldRetry: func(task *models.Task) bool { triesLeft, ok := task.Data["triesLeft"].(int) if !ok { return false @@ -74,8 +74,8 @@ func init() { func CreateSessionUpdateTasks(client core.Client, realm string) error { realm = strings.ToUpper(realm) - task := database.Task{ - Type: database.TaskTypeRecordSessions, + task := models.Task{ + Type: models.TaskTypeRecordSessions, ReferenceID: "realm_" + realm, ScheduledAfter: time.Now(), Data: map[string]any{ @@ -87,16 +87,14 @@ func CreateSessionUpdateTasks(client core.Client, realm string) error { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - accounts, err := client.Database().GetRealmAccounts(ctx, realm) + accounts, err := client.Database().GetRealmAccountIDs(ctx, realm) if err != nil { return err } if len(accounts) < 1 { return nil } - for _, account := range accounts { - task.Targets = append(task.Targets, account.ID) - } + task.Targets = append(task.Targets, accounts...) // This update requires (2 + n) requests per n players return client.Database().CreateTasks(ctx, splitTaskByTargets(task, 90)...) diff --git a/cmds/core/scheduler/tasks/split.go b/cmds/core/scheduler/tasks/split.go index 38a9df21..1414d306 100644 --- a/cmds/core/scheduler/tasks/split.go +++ b/cmds/core/scheduler/tasks/split.go @@ -1,13 +1,13 @@ package tasks -import "github.com/cufee/aftermath/internal/database" +import "github.com/cufee/aftermath/internal/database/models" -func splitTaskByTargets(task database.Task, batchSize int) []database.Task { +func splitTaskByTargets(task models.Task, batchSize int) []models.Task { if len(task.Targets) <= batchSize { - return []database.Task{task} + return []models.Task{task} } - var tasks []database.Task + var tasks []models.Task subTasks := len(task.Targets) / batchSize for i := 0; i <= subTasks; i++ { diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index c23f2f78..683027be 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -6,7 +6,7 @@ import ( "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/cmds/core/scheduler/tasks" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/rs/zerolog/log" ) @@ -28,7 +28,7 @@ func RotateBackgroundPresetsWorker(client core.Client) func() { // log.Err(err).Msg("failed to pick random background images") // return // } - // err = database.UpdateAppConfiguration[[]string]("backgroundImagesSelection", images, nil, true) + // err = models.UpdateAppConfiguration[[]string]("backgroundImagesSelection", images, nil, true) // if err != nil { // log.Err(err).Msg("failed to update background images selection") // } @@ -92,7 +92,7 @@ func RestartTasksWorker(queue *Queue) func() { now := time.Now() for i, task := range staleTasks { - task.Status = database.TaskStatusScheduled + task.Status = models.TaskStatusScheduled task.ScheduledAfter = now staleTasks[i] = task } diff --git a/cmds/core/server/handlers/private/accounts.go b/cmds/core/server/handlers/private/accounts.go index ab8f0547..2afb639b 100644 --- a/cmds/core/server/handlers/private/accounts.go +++ b/cmds/core/server/handlers/private/accounts.go @@ -9,7 +9,7 @@ import ( "sync" "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/am-wg-proxy-next/v2/types" "github.com/rs/zerolog/log" @@ -91,7 +91,7 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { return } - var inserts []database.Account + var inserts []models.Account for _, account := range data { inserts = append(inserts, fetch.WargamingToAccount(realm, account, types.ClanMember{}, false)) } diff --git a/cmds/discord/commands/link.go b/cmds/discord/commands/link.go index 0eb0c5d2..ea4c873b 100644 --- a/cmds/discord/commands/link.go +++ b/cmds/discord/commands/link.go @@ -7,7 +7,7 @@ import ( "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmds/discord/commands/builder" "github.com/cufee/aftermath/cmds/discord/common" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" ) func init() { @@ -49,11 +49,11 @@ func init() { return ctx.Err(err) } - currentConnection, exists := ctx.User.Connection(database.ConnectionTypeWargaming) + currentConnection, exists := ctx.User.Connection(models.ConnectionTypeWargaming) if !exists { currentConnection.UserID = ctx.User.ID currentConnection.Metadata = make(map[string]any) - currentConnection.Type = database.ConnectionTypeWargaming + currentConnection.Type = models.ConnectionTypeWargaming } currentConnection.Metadata["verified"] = false diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index 97f2aa4b..34f9d6a6 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -9,6 +9,7 @@ import ( "github.com/cufee/aftermath/cmds/discord/common" "github.com/cufee/aftermath/cmds/discord/middleware" "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" ) @@ -31,9 +32,9 @@ func init() { builder.NewOption("tasks", discordgo.ApplicationCommandOptionSubCommandGroup).Options( builder.NewOption("view", discordgo.ApplicationCommandOptionSubCommand).Options( builder.NewOption("status", discordgo.ApplicationCommandOptionString).Choices( - builder.NewChoice("failed", string(database.TaskStatusFailed)), - builder.NewChoice("complete", string(database.TaskStatusComplete)), - builder.NewChoice("in-progress", string(database.TaskStatusInProgress)), + builder.NewChoice("failed", string(models.TaskStatusFailed)), + builder.NewChoice("complete", string(models.TaskStatusComplete)), + builder.NewChoice("in-progress", string(models.TaskStatusInProgress)), ).Required(), builder.NewOption("hours", discordgo.ApplicationCommandOptionNumber).Required(), ), @@ -110,7 +111,7 @@ func init() { hours = 1 } - tasks, err := ctx.Core.Database().GetRecentTasks(ctx.Context, time.Now().Add(time.Hour*time.Duration(hours)*-1), database.TaskStatus(status)) + tasks, err := ctx.Core.Database().GetRecentTasks(ctx.Context, time.Now().Add(time.Hour*time.Duration(hours)*-1), models.TaskStatus(status)) if err != nil { return ctx.Reply("Database#GetRecentTasks: " + err.Error()) } diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go index 4c11f155..591b7267 100644 --- a/cmds/discord/commands/session.go +++ b/cmds/discord/commands/session.go @@ -9,6 +9,7 @@ import ( "github.com/cufee/aftermath/cmds/discord/commands/builder" "github.com/cufee/aftermath/cmds/discord/common" "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/render" @@ -33,8 +34,8 @@ func init() { switch { case options.UserID != "": // mentioned another user, check if the user has an account linked - mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithContent()) - defaultAccount, hasDefaultAccount := mentionedUser.Connection(database.ConnectionTypeWargaming) + mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithSubscriptions(), database.WithContent()) + defaultAccount, hasDefaultAccount := mentionedUser.Connection(models.ConnectionTypeWargaming) if !hasDefaultAccount { return ctx.Reply("stats_error_connection_not_found_vague") } @@ -53,7 +54,7 @@ func init() { accountID = fmt.Sprint(account.ID) default: - defaultAccount, hasDefaultAccount := ctx.User.Connection(database.ConnectionTypeWargaming) + defaultAccount, hasDefaultAccount := ctx.User.Connection(models.ConnectionTypeWargaming) if !hasDefaultAccount { return ctx.Reply("stats_error_nickname_or_server_missing") } diff --git a/cmds/discord/commands/stats.go b/cmds/discord/commands/stats.go index 9af35d25..70f57879 100644 --- a/cmds/discord/commands/stats.go +++ b/cmds/discord/commands/stats.go @@ -9,6 +9,7 @@ import ( "github.com/cufee/aftermath/cmds/discord/commands/builder" "github.com/cufee/aftermath/cmds/discord/common" "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" ) @@ -30,8 +31,8 @@ func init() { switch { case options.UserID != "": // mentioned another user, check if the user has an account linked - mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithContent()) - defaultAccount, hasDefaultAccount := mentionedUser.Connection(database.ConnectionTypeWargaming) + mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithSubscriptions(), database.WithContent()) + defaultAccount, hasDefaultAccount := mentionedUser.Connection(models.ConnectionTypeWargaming) if !hasDefaultAccount { return ctx.Reply("stats_error_connection_not_found_vague") } @@ -50,7 +51,7 @@ func init() { accountID = fmt.Sprint(account.ID) default: - defaultAccount, hasDefaultAccount := ctx.User.Connection(database.ConnectionTypeWargaming) + defaultAccount, hasDefaultAccount := ctx.User.Connection(models.ConnectionTypeWargaming) if !hasDefaultAccount { return ctx.Reply("stats_error_nickname_or_server_missing") } diff --git a/cmds/discord/common/context.go b/cmds/discord/common/context.go index 02e6bcf5..4fa0c75d 100644 --- a/cmds/discord/common/context.go +++ b/cmds/discord/common/context.go @@ -12,6 +12,7 @@ import ( "github.com/cufee/aftermath/cmds/discord/rest" "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/localization" "github.com/rs/zerolog/log" @@ -28,7 +29,7 @@ const ( type Context struct { context.Context - User database.User + User models.User Member discordgo.User Locale language.Tag diff --git a/cmds/discord/router/router.go b/cmds/discord/router/router.go index 12aa0072..ef30be48 100644 --- a/cmds/discord/router/router.go +++ b/cmds/discord/router/router.go @@ -9,7 +9,7 @@ import ( "github.com/cufee/aftermath/cmds/discord/commands/builder" "github.com/cufee/aftermath/cmds/discord/middleware" "github.com/cufee/aftermath/cmds/discord/rest" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/logic" "github.com/rs/zerolog/log" ) @@ -56,7 +56,7 @@ func (r *Router) LoadMiddleware(middleware ...middleware.MiddlewareFunc) { type command struct { requested *builder.Command current *discordgo.ApplicationCommand - cached *database.ApplicationCommand + cached *models.ApplicationCommand } /* @@ -107,7 +107,7 @@ func (r *Router) UpdateLoadedCommands() error { if err != nil { return err } - err = r.core.Database().UpsertCommands(context.Background(), database.ApplicationCommand{ID: command.ID, Name: command.Name, Hash: hash, Version: command.Version}) + err = r.core.Database().UpsertCommands(context.Background(), models.ApplicationCommand{ID: command.ID, Name: command.Name, Hash: hash, Version: command.Version}) if err != nil { return err } @@ -136,7 +136,7 @@ func (r *Router) UpdateLoadedCommands() error { if err != nil { return err } - err = r.core.Database().UpsertCommands(context.Background(), database.ApplicationCommand{ID: command.ID, Name: command.Name, Hash: hash, Version: command.Version}) + err = r.core.Database().UpsertCommands(context.Background(), models.ApplicationCommand{ID: command.ID, Name: command.Name, Hash: hash, Version: command.Version}) if err != nil { return err } diff --git a/docker-compose.yaml b/docker-compose.yaml index 6362e4ff..57f25438 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,13 +1,26 @@ services: - apply-migrations: - image: aftermath-prisma-migrate - build: - dockerfile: Dockerfile.migrate + aftermath-database: + image: ghcr.io/tursodatabase/libsql-server:latest + platform: linux/amd64 + restart: always environment: - - DATABASE_URL=file:/database/${DATABASE_NAME} + - SQLD_NODE=primary volumes: - - ${DATABASE_DIR}:/database - command: sh -c "go run github.com/steebchen/prisma-client-go migrate deploy --schema /prisma/schema.prisma" + - ${DATABASE_DIR}:/var/lib/sqld + networks: + - dokploy-network + hostname: aftermath-database + + aftermath-migrate: + image: aftermath-migrate + build: + dockerfile: Dockerfile.migrate + depends_on: + aftermath-database: + condition: service_started + command: sh -c 'migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080"' + networks: + - dokploy-network aftermath-service: image: aftermath-service @@ -17,11 +30,11 @@ services: - .env environment: - PORT=3000 - - DATABASE_URL=file:/database/${DATABASE_NAME} - volumes: - - ${DATABASE_DIR}:/database + - DATABASE_URL=libsql://aftermath-database:8080 depends_on: - apply-migrations: + aftermath-database: + condition: service_started + aftermath-migrate: condition: service_completed_successfully entrypoint: ["app"] restart: always @@ -31,11 +44,9 @@ services: memory: 128m limits: memory: 256m - ports: - - 3000 - - "${PRIVATE_SERVER_PORT}" networks: - dokploy-network + hostname: aftermath-service labels: - "traefik.enable=true" - "traefik.http.routers.aftermath-monorepo-compose.rule=Host(`${TRAEFIK_HOST}`)" diff --git a/go.mod b/go.mod index 6a0e992e..330fc17a 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/cufee/aftermath go 1.22.3 require ( + ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 + entgo.io/ent v0.13.1 github.com/bwmarrin/discordgo v0.28.1 github.com/cufee/am-wg-proxy-next/v2 v2.1.3 github.com/disintegration/imaging v1.6.2 @@ -10,12 +12,12 @@ require ( github.com/go-co-op/gocron v1.37.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/joho/godotenv v1.5.1 + github.com/lucsky/cuid v1.2.1 github.com/nlpodyssey/gopickle v0.3.0 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 - github.com/shopspring/decimal v1.4.0 - github.com/steebchen/prisma-client-go v0.37.0 github.com/stretchr/testify v1.9.0 + github.com/tursodatabase/libsql-client-go v0.0.0-20240416075003-747366ff79c4 go.dedis.ch/protobuf v1.0.11 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/image v0.17.0 @@ -25,14 +27,26 @@ require ( ) require ( + github.com/agext/levenshtein v1.2.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-openapi/inflect v0.19.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/hashicorp/hcl/v2 v2.13.0 // indirect + github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/zclconf/go-cty v1.8.0 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.24.0 // indirect + golang.org/x/mod v0.18.0 // indirect golang.org/x/sys v0.21.0 // indirect + nhooyr.io/websocket v1.8.10 // indirect ) diff --git a/go.sum b/go.sum index fb439afa..24ee26f5 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,15 @@ +ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 h1:GwdJbXydHCYPedeeLt4x/lrlIISQ4JTH1mRWuE5ZZ14= +ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43/go.mod h1:uj3pm+hUTVN/X5yfdBexHlZv+1Xu5u5ZbZx7+CDavNU= +entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE= +entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -13,15 +25,26 @@ github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= +github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= +github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +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/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= +github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -32,12 +55,22 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM= +github.com/lucsky/cuid v1.2.1 h1:MtJrL2OFhvYufUIn48d35QGXyeTC8tn0upumW9WwTHg= +github.com/lucsky/cuid v1.2.1/go.mod h1:QaaJqckboimOmhRSJXSx/+IT+VTfxfPGSo/6mfgUfmE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -53,10 +86,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= -github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/steebchen/prisma-client-go v0.37.0 h1:CYfRxUnIsJRlCvPM4Yw2fElB7Y9rC4f2/PPmHliqyTc= -github.com/steebchen/prisma-client-go v0.37.0/go.mod h1:wp2xU9HO5WIefc65vcl1HOiFUzaHKyOhHw5atrzs8hc= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -66,6 +97,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tursodatabase/libsql-client-go v0.0.0-20240416075003-747366ff79c4 h1:wNN8t3qiLLzFiETD4jL086WemAgQLfARClUx2Jfk78w= +github.com/tursodatabase/libsql-client-go v0.0.0-20240416075003-747366ff79c4/go.mod h1:2Fu26tjM011BLeR5+jwTfs6DX/fNMEWV/3CBZvggrA4= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= +github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= @@ -79,6 +116,7 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= @@ -87,10 +125,15 @@ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXy golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= @@ -99,10 +142,13 @@ golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -111,3 +157,5 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 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= +nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= +nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/internal/database/accounts.go b/internal/database/accounts.go index 98867e6e..79f5a55e 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -2,149 +2,130 @@ package database import ( "context" - "sync" + "strings" "time" - "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/models" ) -type Account struct { - ID string - Realm string - Nickname string - - Private bool - CreatedAt time.Time - LastBattleTime time.Time - - ClanID string - ClanTag string -} - -func (a Account) FromModel(model db.AccountModel) Account { - a.ID = model.ID - a.Realm = model.Realm - a.Nickname = model.Nickname - a.Private = model.Private - a.CreatedAt = model.CreatedAt - a.LastBattleTime = model.LastBattleTime - return a -} - -func (a *Account) AddClan(model *db.ClanModel) { - a.ClanID = model.ID - a.ClanTag = model.Tag +func toAccount(model db.Account) models.Account { + return models.Account{ + ID: model.ID, + Realm: model.Realm, + Nickname: model.Nickname, + Private: model.Private, + CreatedAt: time.Unix(int64(model.AccountCreatedAt), 0), + LastBattleTime: time.Unix(int64(model.LastBattleTime), 0), + } } -func (c *client) GetRealmAccounts(ctx context.Context, realm string) ([]Account, error) { - models, err := c.prisma.Account.FindMany(db.Account.Realm.Equals(realm)).With(db.Account.Clan.Fetch()).Exec(ctx) +func (c *libsqlClient) GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) { + result, err := c.db.Account.Query().Where(account.Realm(strings.ToLower(realm))).Select(account.FieldID).All(ctx) if err != nil { return nil, err } - var accounts []Account - for _, model := range models { - account := Account{}.FromModel(model) - if clan, ok := model.Clan(); ok { - account.AddClan(clan) - } - accounts = append(accounts, account) + var accounts []string + for _, model := range result { + accounts = append(accounts, model.ID) } return accounts, nil } -func (c *client) GetAccountByID(ctx context.Context, id string) (Account, error) { - model, err := c.prisma.Account.FindUnique(db.Account.ID.Equals(id)).With(db.Account.Clan.Fetch()).Exec(ctx) - if err != nil { - return Account{}, err - } +func (c *libsqlClient) GetAccountByID(ctx context.Context, id string) (models.Account, error) { + // model, err := c.prisma.Account.FindUnique(db.Account.ID.Equals(id)).With(db.Account.Clan.Fetch()).Exec(ctx) + // if err != nil { + // return Account{}, err + // } - account := Account{}.FromModel(*model) - if clan, ok := model.Clan(); ok { - account.AddClan(clan) - } + // account := Account{}.FromModel(*model) + // if clan, ok := model.Clan(); ok { + // account.AddClan(clan) + // } - return account, nil + return models.Account{}, nil } -func (c *client) GetAccounts(ctx context.Context, ids []string) ([]Account, error) { - if len(ids) < 1 { - return nil, nil - } - - models, err := c.prisma.Account.FindMany(db.Account.ID.In(ids)).With(db.Account.Clan.Fetch()).Exec(ctx) - if err != nil { - return nil, err - } - - var accounts []Account - for _, model := range models { - account := Account{}.FromModel(model) - if clan, ok := model.Clan(); ok { - account.AddClan(clan) - } - accounts = append(accounts, account) - } +func (c *libsqlClient) GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) { + // if len(ids) < 1 { + // return nil, nil + // } + + // models, err := c.prisma.Account.FindMany(db.Account.ID.In(ids)).With(db.Account.Clan.Fetch()).Exec(ctx) + // if err != nil { + // return nil, err + // } + + var accounts []models.Account + // for _, model := range models { + // account := Account{}.FromModel(model) + // if clan, ok := model.Clan(); ok { + // account.AddClan(clan) + // } + // accounts = append(accounts, account) + // } return accounts, nil } -func (c *client) UpsertAccounts(ctx context.Context, accounts []Account) map[string]error { - if len(accounts) < 1 { - return nil - } +func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Account) map[string]error { + // if len(accounts) < 1 { + // return nil + // } - var mx sync.Mutex - var wg sync.WaitGroup + // var mx sync.Mutex + // var wg sync.WaitGroup errors := make(map[string]error, len(accounts)) - // we don't really want to exit if one fails - for _, account := range accounts { - wg.Add(1) - go func(account Account) { - defer wg.Done() - optional := []db.AccountSetParam{db.Account.Private.Set(account.Private)} - if account.ClanID != "" { - clan, err := c.prisma.Clan.FindUnique(db.Clan.ID.Equals(account.ClanID)).Exec(ctx) - if err == nil { - optional = append(optional, db.Account.Clan.Link(db.Clan.ID.Equals(clan.ID))) - } - } - - _, err := c.prisma.Account. - UpsertOne(db.Account.ID.Equals(account.ID)). - Create(db.Account.ID.Set(account.ID), - db.Account.LastBattleTime.Set(account.LastBattleTime), - db.Account.AccountCreatedAt.Set(account.CreatedAt), - db.Account.Realm.Set(account.Realm), - db.Account.Nickname.Set(account.Nickname), - optional..., - ). - Update( - append(optional, - db.Account.Nickname.Set(account.Nickname), - db.Account.LastBattleTime.Set(account.LastBattleTime), - )..., - ). - Exec(ctx) - if err != nil { - mx.Lock() - errors[account.ID] = err - mx.Unlock() - return - } - }(account) - } - wg.Wait() + // // we don't really want to exit if one fails + // for _, account := range accounts { + // wg.Add(1) + // go func(account Account) { + // defer wg.Done() + // optional := []db.AccountSetParam{db.Account.Private.Set(account.Private)} + // if account.ClanID != "" { + // clan, err := c.prisma.Clan.FindUnique(db.Clan.ID.Equals(account.ClanID)).Exec(ctx) + // if err == nil { + // optional = append(optional, db.Account.Clan.Link(db.Clan.ID.Equals(clan.ID))) + // } + // } + + // _, err := c.prisma.Account. + // UpsertOne(db.Account.ID.Equals(account.ID)). + // Create(db.Account.ID.Set(account.ID), + // db.Account.LastBattleTime.Set(account.LastBattleTime), + // db.Account.AccountCreatedAt.Set(account.CreatedAt), + // db.Account.Realm.Set(account.Realm), + // db.Account.Nickname.Set(account.Nickname), + // optional..., + // ). + // Update( + // append(optional, + // db.Account.Nickname.Set(account.Nickname), + // db.Account.LastBattleTime.Set(account.LastBattleTime), + // )..., + // ). + // Exec(ctx) + // if err != nil { + // mx.Lock() + // errors[account.ID] = err + // mx.Unlock() + // return + // } + // }(account) + // } + // wg.Wait() return errors } -func (c *client) AccountSetPrivate(ctx context.Context, id string, value bool) error { - _, err := c.prisma.Account.FindUnique(db.Account.ID.Equals(id)).Update(db.Account.Private.Set(value)).Exec(ctx) - if err != nil && !db.IsErrNotFound(err) { - return err - } +func (c *libsqlClient) AccountSetPrivate(ctx context.Context, id string, value bool) error { + // _, err := c.prisma.Account.FindUnique(db.Account.ID.Equals(id)).Update(db.Account.Private.Set(value)).Exec(ctx) + // if err != nil && !database.IsNotFound(err) { + // return err + // } return nil } diff --git a/internal/database/averages.go b/internal/database/averages.go index a2e59a64..b798fe9b 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -2,82 +2,79 @@ package database import ( "context" - "time" - "github.com/cufee/aftermath/internal/database/prisma/db" "github.com/cufee/aftermath/internal/stats/frame" - "github.com/rs/zerolog/log" - "github.com/steebchen/prisma-client-go/runtime/transaction" ) -func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { - if len(averages) < 1 { - return nil - } +func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { + // if len(averages) < 1 { + // return nil + // } - var transactions []transaction.Transaction - for id, data := range averages { - encoded, err := data.Encode() - if err != nil { - log.Err(err).Str("id", id).Msg("failed to encode a stats frame for vehicle averages") - continue - } + // var transactions []transaction.Transaction + // for id, data := range averages { + // encoded, err := data.Encode() + // if err != nil { + // log.Err(err).Str("id", id).Msg("failed to encode a stats frame for vehicle averages") + // continue + // } - transactions = append(transactions, c.prisma.VehicleAverage. - UpsertOne(db.VehicleAverage.ID.Equals(id)). - Create( - db.VehicleAverage.ID.Set(id), - db.VehicleAverage.DataEncoded.Set(encoded), - ). - Update( - db.VehicleAverage.DataEncoded.Set(encoded), - ).Tx(), - ) - } + // transactions = append(transactions, c.prisma.VehicleAverage. + // UpsertOne(db.VehicleAverage.ID.Equals(id)). + // Create( + // db.VehicleAverage.ID.Set(id), + // db.VehicleAverage.DataEncoded.Set(encoded), + // ). + // Update( + // db.VehicleAverage.DataEncoded.Set(encoded), + // ).Tx(), + // ) + // } - return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) + // return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) + return nil } -func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { - if len(ids) < 1 { - return nil, nil - } +func (c *libsqlClient) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { + // if len(ids) < 1 { + // return nil, nil + // } - qCtx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - records, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(ids)).Exec(qCtx) - if err != nil { - return nil, err - } + // qCtx, cancel := context.WithTimeout(ctx, time.Second) + // defer cancel() + // records, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(ids)).Exec(qCtx) + // if err != nil { + // return nil, err + // } averages := make(map[string]frame.StatsFrame) - var badRecords []string - var lastErr error + // var badRecords []string + // var lastErr error - for _, record := range records { - parsed, err := frame.DecodeStatsFrame(record.DataEncoded) - lastErr = err - if err != nil { - badRecords = append(badRecords, record.ID) - continue - } - averages[record.ID] = parsed - } + // for _, record := range records { + // parsed, err := frame.DecodeStatsFrame(record.DataEncoded) + // lastErr = err + // if err != nil { + // badRecords = append(badRecords, record.ID) + // continue + // } + // averages[record.ID] = parsed + // } - if len(badRecords) == len(ids) || len(badRecords) == 0 { - return averages, lastErr - } + // if len(badRecords) == len(ids) || len(badRecords) == 0 { + // return averages, lastErr + // } - go func() { - // one bad record should not break the whole query since this data is optional - // we can just delete the record and move on - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - _, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(badRecords)).Delete().Exec(ctx) - if err != nil { - log.Err(err).Strs("ids", badRecords).Msg("failed to delete a bad vehicle average records") - } - }() + // go func() { + // // one bad record should not break the whole query since this data is optional + // // we can just delete the record and move on + // ctx, cancel := context.WithCancel(context.Background()) + // defer cancel() + // _, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(badRecords)).Delete().Exec(ctx) + // if err != nil { + // log.Err(err).Strs("ids", badRecords).Msg("failed to delete a bad vehicle average records") + // } + // }() return averages, nil } diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go index 4f045503..22eae805 100644 --- a/internal/database/cleanup.go +++ b/internal/database/cleanup.go @@ -3,26 +3,24 @@ package database import ( "context" "time" - - "github.com/cufee/aftermath/internal/database/prisma/db" ) -func (c *client) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { - _, err := c.prisma.CronTask.FindMany(db.CronTask.CreatedAt.Before(expiration)).Delete().Exec(ctx) - if err != nil && !db.IsErrNotFound(err) { - return err - } +func (c *libsqlClient) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { + // _, err := c.prisma.CronTask.FindMany(db.CronTask.CreatedAt.Before(expiration)).Delete().Exec(ctx) + // if err != nil && !database.IsNotFound(err) { + // return err + // } return nil } -func (c *client) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { - _, err := c.prisma.AccountSnapshot.FindMany(db.AccountSnapshot.CreatedAt.Before(expiration)).Delete().Exec(ctx) - if err != nil && !db.IsErrNotFound(err) { - return err - } - _, err = c.prisma.VehicleSnapshot.FindMany(db.VehicleSnapshot.CreatedAt.Before(expiration)).Delete().Exec(ctx) - if err != nil && !db.IsErrNotFound(err) { - return err - } +func (c *libsqlClient) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { + // _, err := c.prisma.AccountSnapshot.FindMany(db.AccountSnapshot.CreatedAt.Before(expiration)).Delete().Exec(ctx) + // if err != nil && !database.IsNotFound(err) { + // return err + // } + // _, err = c.prisma.VehicleSnapshot.FindMany(db.VehicleSnapshot.CreatedAt.Before(expiration)).Delete().Exec(ctx) + // if err != nil && !database.IsNotFound(err) { + // return err + // } return nil } diff --git a/internal/database/client.go b/internal/database/client.go index 464988ce..3166f44b 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -2,62 +2,68 @@ package database import ( "context" + "database/sql" "time" - "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" "github.com/cufee/aftermath/internal/stats/frame" "golang.org/x/sync/semaphore" + + "entgo.io/ent/dialect" + entsql "entgo.io/ent/dialect/sql" + _ "github.com/tursodatabase/libsql-client-go/libsql" ) -var _ Client = &client{} +var _ Client = &libsqlClient{} type AccountsClient interface { - GetAccountByID(ctx context.Context, id string) (Account, error) - GetAccounts(ctx context.Context, ids []string) ([]Account, error) - GetRealmAccounts(ctx context.Context, realm string) ([]Account, error) - UpsertAccounts(ctx context.Context, accounts []Account) map[string]error + GetAccountByID(ctx context.Context, id string) (models.Account, error) + GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) + GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) + UpsertAccounts(ctx context.Context, accounts []models.Account) map[string]error AccountSetPrivate(ctx context.Context, id string, value bool) error } type GlossaryClient interface { GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error - GetVehicles(ctx context.Context, ids []string) (map[string]Vehicle, error) - UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle) error + GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) + UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) error } type UsersClient interface { - GetUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) - GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) - UpdateConnection(ctx context.Context, connection UserConnection) (UserConnection, error) - UpsertConnection(ctx context.Context, connection UserConnection) (UserConnection, error) - UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (User, error) + GetUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) + GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) + UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) + UpsertConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) + UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) } type SnapshotsClient interface { - CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error - GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]AccountSnapshot, error) - GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) (AccountSnapshot, error) - GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind snapshotType, options ...SnapshotQuery) ([]AccountSnapshot, error) - CreateVehicleSnapshots(ctx context.Context, snapshots ...VehicleSnapshot) error - GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) ([]VehicleSnapshot, error) + CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) error + GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) + GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) (models.AccountSnapshot, error) + GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.AccountSnapshot, error) + CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error + GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error } type TasksClient interface { - CreateTasks(ctx context.Context, tasks ...Task) error - UpdateTasks(ctx context.Context, tasks ...Task) error + CreateTasks(ctx context.Context, tasks ...models.Task) error + UpdateTasks(ctx context.Context, tasks ...models.Task) error DeleteTasks(ctx context.Context, ids ...string) error - GetStaleTasks(ctx context.Context, limit int) ([]Task, error) - GetAndStartTasks(ctx context.Context, limit int) ([]Task, error) + GetStaleTasks(ctx context.Context, limit int) ([]models.Task, error) + GetAndStartTasks(ctx context.Context, limit int) ([]models.Task, error) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error - GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...TaskStatus) ([]Task, error) + GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) } type DiscordDataClient interface { - UpsertCommands(ctx context.Context, commands ...ApplicationCommand) error - GetCommandsByID(ctx context.Context, commandIDs ...string) ([]ApplicationCommand, error) + UpsertCommands(ctx context.Context, commands ...models.ApplicationCommand) error + GetCommandsByID(ctx context.Context, commandIDs ...string) ([]models.ApplicationCommand, error) } type Client interface { @@ -74,27 +80,25 @@ type Client interface { Disconnect() error } -type client struct { - prisma *db.PrismaClient - // Prisma does not currently support updateManyAndReturn - // in order to avoid a case where we +type libsqlClient struct { + db *db.Client tasksUpdateSem *semaphore.Weighted } -func (c *client) Disconnect() error { - return c.prisma.Disconnect() -} - -func (c *client) Prisma() *db.PrismaClient { - return c.prisma +func (c *libsqlClient) Disconnect() error { + return c.db.Close() } -func NewClient() (*client, error) { - prisma := db.NewClient() - err := prisma.Connect() +func NewLibSQLClient(primaryUrl string) (*libsqlClient, error) { + driver, err := sql.Open("libsql", primaryUrl) if err != nil { return nil, err } - return &client{prisma: prisma, tasksUpdateSem: semaphore.NewWeighted(1)}, nil + dbClient := db.NewClient(db.Driver(entsql.OpenDB(dialect.SQLite, driver))) + + return &libsqlClient{ + db: dbClient, + tasksUpdateSem: semaphore.NewWeighted(1), + }, nil } diff --git a/internal/database/discord.go b/internal/database/discord.go index ac1a925d..c9d368ba 100644 --- a/internal/database/discord.go +++ b/internal/database/discord.go @@ -3,79 +3,64 @@ package database import ( "context" - "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/database/models" ) -type ApplicationCommand struct { - ID string - Hash string - Name string - Version string -} - -func (c ApplicationCommand) fromModel(model db.ApplicationCommandModel) ApplicationCommand { - return ApplicationCommand{ - ID: model.ID, - Name: model.Name, - Hash: model.OptionsHash, - Version: model.Version, - } -} - -func (c *client) GetCommandsByID(ctx context.Context, commandIDs ...string) ([]ApplicationCommand, error) { - if len(commandIDs) < 1 { - return nil, nil - } +func (c *libsqlClient) GetCommandsByID(ctx context.Context, commandIDs ...string) ([]models.ApplicationCommand, error) { + // if len(commandIDs) < 1 { + // return nil, nil + // } - models, err := c.prisma.ApplicationCommand.FindMany(db.ApplicationCommand.ID.In(commandIDs)).Exec(ctx) - if err != nil { - return nil, err - } + // models, err := c.prisma.models.ApplicationCommand.FindMany(db.models.ApplicationCommand.ID.In(commandIDs)).Exec(ctx) + // if err != nil { + // return nil, err + // } - var commands []ApplicationCommand - for _, model := range models { - commands = append(commands, ApplicationCommand{}.fromModel(model)) - } + var commands []models.ApplicationCommand + // for _, model := range models { + // commands = append(commands, models.ApplicationCommand{}.fromModel(model)) + // } return commands, nil } -func (c *client) GetCommandsByHash(ctx context.Context, commandHashes ...string) ([]ApplicationCommand, error) { - if len(commandHashes) < 1 { - return nil, nil - } +func (c *libsqlClient) GetCommandsByHash(ctx context.Context, commandHashes ...string) ([]models.ApplicationCommand, error) { + // if len(commandHashes) < 1 { + // return nil, nil + // } - models, err := c.prisma.ApplicationCommand.FindMany(db.ApplicationCommand.OptionsHash.In(commandHashes)).Exec(ctx) - if err != nil { - return nil, err - } + // models, err := c.prisma.models.ApplicationCommand.FindMany(db.models.ApplicationCommand.OptionsHash.In(commandHashes)).Exec(ctx) + // if err != nil { + // return nil, err + // } - var commands []ApplicationCommand - for _, model := range models { - commands = append(commands, ApplicationCommand{}.fromModel(model)) - } + var commands []models.ApplicationCommand + // for _, model := range models { + // commands = append(commands, models.ApplicationCommand{}.fromModel(model)) + // } return commands, nil } -func (c *client) UpsertCommands(ctx context.Context, commands ...ApplicationCommand) error { - if len(commands) < 1 { - return nil - } +func (c *libsqlClient) UpsertCommands(ctx context.Context, commands ...models.ApplicationCommand) error { + // if len(commands) < 1 { + // return nil + // } - var tx []db.PrismaTransaction - for _, cmd := range commands { - tx = append(tx, c.prisma.ApplicationCommand.UpsertOne(db.ApplicationCommand.ID.Equals(cmd.ID)). - Create( - db.ApplicationCommand.ID.Set(cmd.ID), - db.ApplicationCommand.Name.Set(cmd.Name), - db.ApplicationCommand.Version.Set(cmd.Version), - db.ApplicationCommand.OptionsHash.Set(cmd.Hash), - ). - Update( - db.ApplicationCommand.Name.Set(cmd.Name), - db.ApplicationCommand.Version.Set(cmd.Version), - db.ApplicationCommand.OptionsHash.Set(cmd.Hash), - ).Tx(), - ) - } - return c.prisma.Prisma.Transaction(tx...).Exec(ctx) + // var tx []db.PrismaTransaction + // for _, cmd := range commands { + // tx = append(tx, c.prisma.models.ApplicationCommand.UpsertOne(db.models.ApplicationCommand.ID.Equals(cmd.ID)). + // Create( + // db.models.ApplicationCommand.ID.Set(cmd.ID), + // db.models.ApplicationCommand.Name.Set(cmd.Name), + // db.models.ApplicationCommand.Version.Set(cmd.Version), + // db.models.ApplicationCommand.OptionsHash.Set(cmd.Hash), + // ). + // Update( + // db.models.ApplicationCommand.Name.Set(cmd.Name), + // db.models.ApplicationCommand.Version.Set(cmd.Version), + // db.models.ApplicationCommand.OptionsHash.Set(cmd.Hash), + // ).Tx(), + // ) + // } + // return c.prisma.Prisma.Transaction(tx...).Exec(ctx) + return nil } diff --git a/internal/database/ent/db/account.go b/internal/database/ent/db/account.go new file mode 100644 index 00000000..7cdf8b15 --- /dev/null +++ b/internal/database/ent/db/account.go @@ -0,0 +1,259 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/clan" +) + +// Account is the model entity for the Account schema. +type Account struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // LastBattleTime holds the value of the "last_battle_time" field. + LastBattleTime int `json:"last_battle_time,omitempty"` + // AccountCreatedAt holds the value of the "account_created_at" field. + AccountCreatedAt int `json:"account_created_at,omitempty"` + // Realm holds the value of the "realm" field. + Realm string `json:"realm,omitempty"` + // Nickname holds the value of the "nickname" field. + Nickname string `json:"nickname,omitempty"` + // Private holds the value of the "private" field. + Private bool `json:"private,omitempty"` + // ClanID holds the value of the "clan_id" field. + ClanID string `json:"clan_id,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the AccountQuery when eager-loading is set. + Edges AccountEdges `json:"edges"` + selectValues sql.SelectValues +} + +// AccountEdges holds the relations/edges for other nodes in the graph. +type AccountEdges struct { + // Clan holds the value of the clan edge. + Clan *Clan `json:"clan,omitempty"` + // Snapshots holds the value of the snapshots edge. + Snapshots []*AccountSnapshot `json:"snapshots,omitempty"` + // VehicleSnapshots holds the value of the vehicle_snapshots edge. + VehicleSnapshots []*VehicleSnapshot `json:"vehicle_snapshots,omitempty"` + // AchievementSnapshots holds the value of the achievement_snapshots edge. + AchievementSnapshots []*AchievementsSnapshot `json:"achievement_snapshots,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [4]bool +} + +// ClanOrErr returns the Clan value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e AccountEdges) ClanOrErr() (*Clan, error) { + if e.Clan != nil { + return e.Clan, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: clan.Label} + } + return nil, &NotLoadedError{edge: "clan"} +} + +// SnapshotsOrErr returns the Snapshots value or an error if the edge +// was not loaded in eager-loading. +func (e AccountEdges) SnapshotsOrErr() ([]*AccountSnapshot, error) { + if e.loadedTypes[1] { + return e.Snapshots, nil + } + return nil, &NotLoadedError{edge: "snapshots"} +} + +// VehicleSnapshotsOrErr returns the VehicleSnapshots value or an error if the edge +// was not loaded in eager-loading. +func (e AccountEdges) VehicleSnapshotsOrErr() ([]*VehicleSnapshot, error) { + if e.loadedTypes[2] { + return e.VehicleSnapshots, nil + } + return nil, &NotLoadedError{edge: "vehicle_snapshots"} +} + +// AchievementSnapshotsOrErr returns the AchievementSnapshots value or an error if the edge +// was not loaded in eager-loading. +func (e AccountEdges) AchievementSnapshotsOrErr() ([]*AchievementsSnapshot, error) { + if e.loadedTypes[3] { + return e.AchievementSnapshots, nil + } + return nil, &NotLoadedError{edge: "achievement_snapshots"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Account) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case account.FieldPrivate: + values[i] = new(sql.NullBool) + case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldLastBattleTime, account.FieldAccountCreatedAt: + values[i] = new(sql.NullInt64) + case account.FieldID, account.FieldRealm, account.FieldNickname, account.FieldClanID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Account fields. +func (a *Account) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case account.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + a.ID = value.String + } + case account.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + a.CreatedAt = int(value.Int64) + } + case account.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + a.UpdatedAt = int(value.Int64) + } + case account.FieldLastBattleTime: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) + } else if value.Valid { + a.LastBattleTime = int(value.Int64) + } + case account.FieldAccountCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field account_created_at", values[i]) + } else if value.Valid { + a.AccountCreatedAt = int(value.Int64) + } + case account.FieldRealm: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field realm", values[i]) + } else if value.Valid { + a.Realm = value.String + } + case account.FieldNickname: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field nickname", values[i]) + } else if value.Valid { + a.Nickname = value.String + } + case account.FieldPrivate: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field private", values[i]) + } else if value.Valid { + a.Private = value.Bool + } + case account.FieldClanID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field clan_id", values[i]) + } else if value.Valid { + a.ClanID = value.String + } + default: + a.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the Account. +// This includes values selected through modifiers, order, etc. +func (a *Account) Value(name string) (ent.Value, error) { + return a.selectValues.Get(name) +} + +// QueryClan queries the "clan" edge of the Account entity. +func (a *Account) QueryClan() *ClanQuery { + return NewAccountClient(a.config).QueryClan(a) +} + +// QuerySnapshots queries the "snapshots" edge of the Account entity. +func (a *Account) QuerySnapshots() *AccountSnapshotQuery { + return NewAccountClient(a.config).QuerySnapshots(a) +} + +// QueryVehicleSnapshots queries the "vehicle_snapshots" edge of the Account entity. +func (a *Account) QueryVehicleSnapshots() *VehicleSnapshotQuery { + return NewAccountClient(a.config).QueryVehicleSnapshots(a) +} + +// QueryAchievementSnapshots queries the "achievement_snapshots" edge of the Account entity. +func (a *Account) QueryAchievementSnapshots() *AchievementsSnapshotQuery { + return NewAccountClient(a.config).QueryAchievementSnapshots(a) +} + +// Update returns a builder for updating this Account. +// Note that you need to call Account.Unwrap() before calling this method if this Account +// was returned from a transaction, and the transaction was committed or rolled back. +func (a *Account) Update() *AccountUpdateOne { + return NewAccountClient(a.config).UpdateOne(a) +} + +// Unwrap unwraps the Account entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (a *Account) Unwrap() *Account { + _tx, ok := a.config.driver.(*txDriver) + if !ok { + panic("db: Account is not a transactional entity") + } + a.config.driver = _tx.drv + return a +} + +// String implements the fmt.Stringer. +func (a *Account) String() string { + var builder strings.Builder + builder.WriteString("Account(") + builder.WriteString(fmt.Sprintf("id=%v, ", a.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", a.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", a.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("last_battle_time=") + builder.WriteString(fmt.Sprintf("%v", a.LastBattleTime)) + builder.WriteString(", ") + builder.WriteString("account_created_at=") + builder.WriteString(fmt.Sprintf("%v", a.AccountCreatedAt)) + builder.WriteString(", ") + builder.WriteString("realm=") + builder.WriteString(a.Realm) + builder.WriteString(", ") + builder.WriteString("nickname=") + builder.WriteString(a.Nickname) + builder.WriteString(", ") + builder.WriteString("private=") + builder.WriteString(fmt.Sprintf("%v", a.Private)) + builder.WriteString(", ") + builder.WriteString("clan_id=") + builder.WriteString(a.ClanID) + builder.WriteByte(')') + return builder.String() +} + +// Accounts is a parsable slice of Account. +type Accounts []*Account diff --git a/internal/database/ent/db/account/account.go b/internal/database/ent/db/account/account.go new file mode 100644 index 00000000..d2979c1d --- /dev/null +++ b/internal/database/ent/db/account/account.go @@ -0,0 +1,232 @@ +// Code generated by ent, DO NOT EDIT. + +package account + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the account type in the database. + Label = "account" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldLastBattleTime holds the string denoting the last_battle_time field in the database. + FieldLastBattleTime = "last_battle_time" + // FieldAccountCreatedAt holds the string denoting the account_created_at field in the database. + FieldAccountCreatedAt = "account_created_at" + // FieldRealm holds the string denoting the realm field in the database. + FieldRealm = "realm" + // FieldNickname holds the string denoting the nickname field in the database. + FieldNickname = "nickname" + // FieldPrivate holds the string denoting the private field in the database. + FieldPrivate = "private" + // FieldClanID holds the string denoting the clan_id field in the database. + FieldClanID = "clan_id" + // EdgeClan holds the string denoting the clan edge name in mutations. + EdgeClan = "clan" + // EdgeSnapshots holds the string denoting the snapshots edge name in mutations. + EdgeSnapshots = "snapshots" + // EdgeVehicleSnapshots holds the string denoting the vehicle_snapshots edge name in mutations. + EdgeVehicleSnapshots = "vehicle_snapshots" + // EdgeAchievementSnapshots holds the string denoting the achievement_snapshots edge name in mutations. + EdgeAchievementSnapshots = "achievement_snapshots" + // Table holds the table name of the account in the database. + Table = "accounts" + // ClanTable is the table that holds the clan relation/edge. + ClanTable = "accounts" + // ClanInverseTable is the table name for the Clan entity. + // It exists in this package in order to avoid circular dependency with the "clan" package. + ClanInverseTable = "clans" + // ClanColumn is the table column denoting the clan relation/edge. + ClanColumn = "clan_id" + // SnapshotsTable is the table that holds the snapshots relation/edge. + SnapshotsTable = "account_snapshots" + // SnapshotsInverseTable is the table name for the AccountSnapshot entity. + // It exists in this package in order to avoid circular dependency with the "accountsnapshot" package. + SnapshotsInverseTable = "account_snapshots" + // SnapshotsColumn is the table column denoting the snapshots relation/edge. + SnapshotsColumn = "account_id" + // VehicleSnapshotsTable is the table that holds the vehicle_snapshots relation/edge. + VehicleSnapshotsTable = "vehicle_snapshots" + // VehicleSnapshotsInverseTable is the table name for the VehicleSnapshot entity. + // It exists in this package in order to avoid circular dependency with the "vehiclesnapshot" package. + VehicleSnapshotsInverseTable = "vehicle_snapshots" + // VehicleSnapshotsColumn is the table column denoting the vehicle_snapshots relation/edge. + VehicleSnapshotsColumn = "account_id" + // AchievementSnapshotsTable is the table that holds the achievement_snapshots relation/edge. + AchievementSnapshotsTable = "achievements_snapshots" + // AchievementSnapshotsInverseTable is the table name for the AchievementsSnapshot entity. + // It exists in this package in order to avoid circular dependency with the "achievementssnapshot" package. + AchievementSnapshotsInverseTable = "achievements_snapshots" + // AchievementSnapshotsColumn is the table column denoting the achievement_snapshots relation/edge. + AchievementSnapshotsColumn = "account_id" +) + +// Columns holds all SQL columns for account fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldLastBattleTime, + FieldAccountCreatedAt, + FieldRealm, + FieldNickname, + FieldPrivate, + FieldClanID, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // RealmValidator is a validator for the "realm" field. It is called by the builders before save. + RealmValidator func(string) error + // NicknameValidator is a validator for the "nickname" field. It is called by the builders before save. + NicknameValidator func(string) error + // DefaultPrivate holds the default value on creation for the "private" field. + DefaultPrivate bool +) + +// OrderOption defines the ordering options for the Account queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByLastBattleTime orders the results by the last_battle_time field. +func ByLastBattleTime(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLastBattleTime, opts...).ToFunc() +} + +// ByAccountCreatedAt orders the results by the account_created_at field. +func ByAccountCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAccountCreatedAt, opts...).ToFunc() +} + +// ByRealm orders the results by the realm field. +func ByRealm(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRealm, opts...).ToFunc() +} + +// ByNickname orders the results by the nickname field. +func ByNickname(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldNickname, opts...).ToFunc() +} + +// ByPrivate orders the results by the private field. +func ByPrivate(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPrivate, opts...).ToFunc() +} + +// ByClanID orders the results by the clan_id field. +func ByClanID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldClanID, opts...).ToFunc() +} + +// ByClanField orders the results by clan field. +func ByClanField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newClanStep(), sql.OrderByField(field, opts...)) + } +} + +// BySnapshotsCount orders the results by snapshots count. +func BySnapshotsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newSnapshotsStep(), opts...) + } +} + +// BySnapshots orders the results by snapshots terms. +func BySnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newSnapshotsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByVehicleSnapshotsCount orders the results by vehicle_snapshots count. +func ByVehicleSnapshotsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newVehicleSnapshotsStep(), opts...) + } +} + +// ByVehicleSnapshots orders the results by vehicle_snapshots terms. +func ByVehicleSnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newVehicleSnapshotsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByAchievementSnapshotsCount orders the results by achievement_snapshots count. +func ByAchievementSnapshotsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newAchievementSnapshotsStep(), opts...) + } +} + +// ByAchievementSnapshots orders the results by achievement_snapshots terms. +func ByAchievementSnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newAchievementSnapshotsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newClanStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ClanInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ClanTable, ClanColumn), + ) +} +func newSnapshotsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(SnapshotsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SnapshotsTable, SnapshotsColumn), + ) +} +func newVehicleSnapshotsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(VehicleSnapshotsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, VehicleSnapshotsTable, VehicleSnapshotsColumn), + ) +} +func newAchievementSnapshotsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(AchievementSnapshotsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AchievementSnapshotsTable, AchievementSnapshotsColumn), + ) +} diff --git a/internal/database/ent/db/account/where.go b/internal/database/ent/db/account/where.go new file mode 100644 index 00000000..a6b0cd6d --- /dev/null +++ b/internal/database/ent/db/account/where.go @@ -0,0 +1,586 @@ +// Code generated by ent, DO NOT EDIT. + +package account + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.Account { + return predicate.Account(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.Account { + return predicate.Account(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.Account { + return predicate.Account(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.Account { + return predicate.Account(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.Account { + return predicate.Account(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.Account { + return predicate.Account(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.Account { + return predicate.Account(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.Account { + return predicate.Account(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.Account { + return predicate.Account(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. +func LastBattleTime(v int) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldLastBattleTime, v)) +} + +// AccountCreatedAt applies equality check predicate on the "account_created_at" field. It's identical to AccountCreatedAtEQ. +func AccountCreatedAt(v int) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldAccountCreatedAt, v)) +} + +// Realm applies equality check predicate on the "realm" field. It's identical to RealmEQ. +func Realm(v string) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldRealm, v)) +} + +// Nickname applies equality check predicate on the "nickname" field. It's identical to NicknameEQ. +func Nickname(v string) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldNickname, v)) +} + +// Private applies equality check predicate on the "private" field. It's identical to PrivateEQ. +func Private(v bool) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldPrivate, v)) +} + +// ClanID applies equality check predicate on the "clan_id" field. It's identical to ClanIDEQ. +func ClanID(v string) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldClanID, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.Account { + return predicate.Account(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.Account { + return predicate.Account(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.Account { + return predicate.Account(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.Account { + return predicate.Account(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.Account { + return predicate.Account(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.Account { + return predicate.Account(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.Account { + return predicate.Account(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.Account { + return predicate.Account(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.Account { + return predicate.Account(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.Account { + return predicate.Account(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.Account { + return predicate.Account(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.Account { + return predicate.Account(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.Account { + return predicate.Account(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.Account { + return predicate.Account(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. +func LastBattleTimeEQ(v int) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldLastBattleTime, v)) +} + +// LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. +func LastBattleTimeNEQ(v int) predicate.Account { + return predicate.Account(sql.FieldNEQ(FieldLastBattleTime, v)) +} + +// LastBattleTimeIn applies the In predicate on the "last_battle_time" field. +func LastBattleTimeIn(vs ...int) predicate.Account { + return predicate.Account(sql.FieldIn(FieldLastBattleTime, vs...)) +} + +// LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. +func LastBattleTimeNotIn(vs ...int) predicate.Account { + return predicate.Account(sql.FieldNotIn(FieldLastBattleTime, vs...)) +} + +// LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. +func LastBattleTimeGT(v int) predicate.Account { + return predicate.Account(sql.FieldGT(FieldLastBattleTime, v)) +} + +// LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. +func LastBattleTimeGTE(v int) predicate.Account { + return predicate.Account(sql.FieldGTE(FieldLastBattleTime, v)) +} + +// LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. +func LastBattleTimeLT(v int) predicate.Account { + return predicate.Account(sql.FieldLT(FieldLastBattleTime, v)) +} + +// LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. +func LastBattleTimeLTE(v int) predicate.Account { + return predicate.Account(sql.FieldLTE(FieldLastBattleTime, v)) +} + +// AccountCreatedAtEQ applies the EQ predicate on the "account_created_at" field. +func AccountCreatedAtEQ(v int) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldAccountCreatedAt, v)) +} + +// AccountCreatedAtNEQ applies the NEQ predicate on the "account_created_at" field. +func AccountCreatedAtNEQ(v int) predicate.Account { + return predicate.Account(sql.FieldNEQ(FieldAccountCreatedAt, v)) +} + +// AccountCreatedAtIn applies the In predicate on the "account_created_at" field. +func AccountCreatedAtIn(vs ...int) predicate.Account { + return predicate.Account(sql.FieldIn(FieldAccountCreatedAt, vs...)) +} + +// AccountCreatedAtNotIn applies the NotIn predicate on the "account_created_at" field. +func AccountCreatedAtNotIn(vs ...int) predicate.Account { + return predicate.Account(sql.FieldNotIn(FieldAccountCreatedAt, vs...)) +} + +// AccountCreatedAtGT applies the GT predicate on the "account_created_at" field. +func AccountCreatedAtGT(v int) predicate.Account { + return predicate.Account(sql.FieldGT(FieldAccountCreatedAt, v)) +} + +// AccountCreatedAtGTE applies the GTE predicate on the "account_created_at" field. +func AccountCreatedAtGTE(v int) predicate.Account { + return predicate.Account(sql.FieldGTE(FieldAccountCreatedAt, v)) +} + +// AccountCreatedAtLT applies the LT predicate on the "account_created_at" field. +func AccountCreatedAtLT(v int) predicate.Account { + return predicate.Account(sql.FieldLT(FieldAccountCreatedAt, v)) +} + +// AccountCreatedAtLTE applies the LTE predicate on the "account_created_at" field. +func AccountCreatedAtLTE(v int) predicate.Account { + return predicate.Account(sql.FieldLTE(FieldAccountCreatedAt, v)) +} + +// RealmEQ applies the EQ predicate on the "realm" field. +func RealmEQ(v string) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldRealm, v)) +} + +// RealmNEQ applies the NEQ predicate on the "realm" field. +func RealmNEQ(v string) predicate.Account { + return predicate.Account(sql.FieldNEQ(FieldRealm, v)) +} + +// RealmIn applies the In predicate on the "realm" field. +func RealmIn(vs ...string) predicate.Account { + return predicate.Account(sql.FieldIn(FieldRealm, vs...)) +} + +// RealmNotIn applies the NotIn predicate on the "realm" field. +func RealmNotIn(vs ...string) predicate.Account { + return predicate.Account(sql.FieldNotIn(FieldRealm, vs...)) +} + +// RealmGT applies the GT predicate on the "realm" field. +func RealmGT(v string) predicate.Account { + return predicate.Account(sql.FieldGT(FieldRealm, v)) +} + +// RealmGTE applies the GTE predicate on the "realm" field. +func RealmGTE(v string) predicate.Account { + return predicate.Account(sql.FieldGTE(FieldRealm, v)) +} + +// RealmLT applies the LT predicate on the "realm" field. +func RealmLT(v string) predicate.Account { + return predicate.Account(sql.FieldLT(FieldRealm, v)) +} + +// RealmLTE applies the LTE predicate on the "realm" field. +func RealmLTE(v string) predicate.Account { + return predicate.Account(sql.FieldLTE(FieldRealm, v)) +} + +// RealmContains applies the Contains predicate on the "realm" field. +func RealmContains(v string) predicate.Account { + return predicate.Account(sql.FieldContains(FieldRealm, v)) +} + +// RealmHasPrefix applies the HasPrefix predicate on the "realm" field. +func RealmHasPrefix(v string) predicate.Account { + return predicate.Account(sql.FieldHasPrefix(FieldRealm, v)) +} + +// RealmHasSuffix applies the HasSuffix predicate on the "realm" field. +func RealmHasSuffix(v string) predicate.Account { + return predicate.Account(sql.FieldHasSuffix(FieldRealm, v)) +} + +// RealmEqualFold applies the EqualFold predicate on the "realm" field. +func RealmEqualFold(v string) predicate.Account { + return predicate.Account(sql.FieldEqualFold(FieldRealm, v)) +} + +// RealmContainsFold applies the ContainsFold predicate on the "realm" field. +func RealmContainsFold(v string) predicate.Account { + return predicate.Account(sql.FieldContainsFold(FieldRealm, v)) +} + +// NicknameEQ applies the EQ predicate on the "nickname" field. +func NicknameEQ(v string) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldNickname, v)) +} + +// NicknameNEQ applies the NEQ predicate on the "nickname" field. +func NicknameNEQ(v string) predicate.Account { + return predicate.Account(sql.FieldNEQ(FieldNickname, v)) +} + +// NicknameIn applies the In predicate on the "nickname" field. +func NicknameIn(vs ...string) predicate.Account { + return predicate.Account(sql.FieldIn(FieldNickname, vs...)) +} + +// NicknameNotIn applies the NotIn predicate on the "nickname" field. +func NicknameNotIn(vs ...string) predicate.Account { + return predicate.Account(sql.FieldNotIn(FieldNickname, vs...)) +} + +// NicknameGT applies the GT predicate on the "nickname" field. +func NicknameGT(v string) predicate.Account { + return predicate.Account(sql.FieldGT(FieldNickname, v)) +} + +// NicknameGTE applies the GTE predicate on the "nickname" field. +func NicknameGTE(v string) predicate.Account { + return predicate.Account(sql.FieldGTE(FieldNickname, v)) +} + +// NicknameLT applies the LT predicate on the "nickname" field. +func NicknameLT(v string) predicate.Account { + return predicate.Account(sql.FieldLT(FieldNickname, v)) +} + +// NicknameLTE applies the LTE predicate on the "nickname" field. +func NicknameLTE(v string) predicate.Account { + return predicate.Account(sql.FieldLTE(FieldNickname, v)) +} + +// NicknameContains applies the Contains predicate on the "nickname" field. +func NicknameContains(v string) predicate.Account { + return predicate.Account(sql.FieldContains(FieldNickname, v)) +} + +// NicknameHasPrefix applies the HasPrefix predicate on the "nickname" field. +func NicknameHasPrefix(v string) predicate.Account { + return predicate.Account(sql.FieldHasPrefix(FieldNickname, v)) +} + +// NicknameHasSuffix applies the HasSuffix predicate on the "nickname" field. +func NicknameHasSuffix(v string) predicate.Account { + return predicate.Account(sql.FieldHasSuffix(FieldNickname, v)) +} + +// NicknameEqualFold applies the EqualFold predicate on the "nickname" field. +func NicknameEqualFold(v string) predicate.Account { + return predicate.Account(sql.FieldEqualFold(FieldNickname, v)) +} + +// NicknameContainsFold applies the ContainsFold predicate on the "nickname" field. +func NicknameContainsFold(v string) predicate.Account { + return predicate.Account(sql.FieldContainsFold(FieldNickname, v)) +} + +// PrivateEQ applies the EQ predicate on the "private" field. +func PrivateEQ(v bool) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldPrivate, v)) +} + +// PrivateNEQ applies the NEQ predicate on the "private" field. +func PrivateNEQ(v bool) predicate.Account { + return predicate.Account(sql.FieldNEQ(FieldPrivate, v)) +} + +// ClanIDEQ applies the EQ predicate on the "clan_id" field. +func ClanIDEQ(v string) predicate.Account { + return predicate.Account(sql.FieldEQ(FieldClanID, v)) +} + +// ClanIDNEQ applies the NEQ predicate on the "clan_id" field. +func ClanIDNEQ(v string) predicate.Account { + return predicate.Account(sql.FieldNEQ(FieldClanID, v)) +} + +// ClanIDIn applies the In predicate on the "clan_id" field. +func ClanIDIn(vs ...string) predicate.Account { + return predicate.Account(sql.FieldIn(FieldClanID, vs...)) +} + +// ClanIDNotIn applies the NotIn predicate on the "clan_id" field. +func ClanIDNotIn(vs ...string) predicate.Account { + return predicate.Account(sql.FieldNotIn(FieldClanID, vs...)) +} + +// ClanIDGT applies the GT predicate on the "clan_id" field. +func ClanIDGT(v string) predicate.Account { + return predicate.Account(sql.FieldGT(FieldClanID, v)) +} + +// ClanIDGTE applies the GTE predicate on the "clan_id" field. +func ClanIDGTE(v string) predicate.Account { + return predicate.Account(sql.FieldGTE(FieldClanID, v)) +} + +// ClanIDLT applies the LT predicate on the "clan_id" field. +func ClanIDLT(v string) predicate.Account { + return predicate.Account(sql.FieldLT(FieldClanID, v)) +} + +// ClanIDLTE applies the LTE predicate on the "clan_id" field. +func ClanIDLTE(v string) predicate.Account { + return predicate.Account(sql.FieldLTE(FieldClanID, v)) +} + +// ClanIDContains applies the Contains predicate on the "clan_id" field. +func ClanIDContains(v string) predicate.Account { + return predicate.Account(sql.FieldContains(FieldClanID, v)) +} + +// ClanIDHasPrefix applies the HasPrefix predicate on the "clan_id" field. +func ClanIDHasPrefix(v string) predicate.Account { + return predicate.Account(sql.FieldHasPrefix(FieldClanID, v)) +} + +// ClanIDHasSuffix applies the HasSuffix predicate on the "clan_id" field. +func ClanIDHasSuffix(v string) predicate.Account { + return predicate.Account(sql.FieldHasSuffix(FieldClanID, v)) +} + +// ClanIDIsNil applies the IsNil predicate on the "clan_id" field. +func ClanIDIsNil() predicate.Account { + return predicate.Account(sql.FieldIsNull(FieldClanID)) +} + +// ClanIDNotNil applies the NotNil predicate on the "clan_id" field. +func ClanIDNotNil() predicate.Account { + return predicate.Account(sql.FieldNotNull(FieldClanID)) +} + +// ClanIDEqualFold applies the EqualFold predicate on the "clan_id" field. +func ClanIDEqualFold(v string) predicate.Account { + return predicate.Account(sql.FieldEqualFold(FieldClanID, v)) +} + +// ClanIDContainsFold applies the ContainsFold predicate on the "clan_id" field. +func ClanIDContainsFold(v string) predicate.Account { + return predicate.Account(sql.FieldContainsFold(FieldClanID, v)) +} + +// HasClan applies the HasEdge predicate on the "clan" edge. +func HasClan() predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ClanTable, ClanColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasClanWith applies the HasEdge predicate on the "clan" edge with a given conditions (other predicates). +func HasClanWith(preds ...predicate.Clan) predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := newClanStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasSnapshots applies the HasEdge predicate on the "snapshots" edge. +func HasSnapshots() predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SnapshotsTable, SnapshotsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasSnapshotsWith applies the HasEdge predicate on the "snapshots" edge with a given conditions (other predicates). +func HasSnapshotsWith(preds ...predicate.AccountSnapshot) predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := newSnapshotsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasVehicleSnapshots applies the HasEdge predicate on the "vehicle_snapshots" edge. +func HasVehicleSnapshots() predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, VehicleSnapshotsTable, VehicleSnapshotsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasVehicleSnapshotsWith applies the HasEdge predicate on the "vehicle_snapshots" edge with a given conditions (other predicates). +func HasVehicleSnapshotsWith(preds ...predicate.VehicleSnapshot) predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := newVehicleSnapshotsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasAchievementSnapshots applies the HasEdge predicate on the "achievement_snapshots" edge. +func HasAchievementSnapshots() predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AchievementSnapshotsTable, AchievementSnapshotsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasAchievementSnapshotsWith applies the HasEdge predicate on the "achievement_snapshots" edge with a given conditions (other predicates). +func HasAchievementSnapshotsWith(preds ...predicate.AchievementsSnapshot) predicate.Account { + return predicate.Account(func(s *sql.Selector) { + step := newAchievementSnapshotsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Account) predicate.Account { + return predicate.Account(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Account) predicate.Account { + return predicate.Account(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Account) predicate.Account { + return predicate.Account(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/account_create.go b/internal/database/ent/db/account_create.go new file mode 100644 index 00000000..4afd1e09 --- /dev/null +++ b/internal/database/ent/db/account_create.go @@ -0,0 +1,457 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" +) + +// AccountCreate is the builder for creating a Account entity. +type AccountCreate struct { + config + mutation *AccountMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (ac *AccountCreate) SetCreatedAt(i int) *AccountCreate { + ac.mutation.SetCreatedAt(i) + return ac +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (ac *AccountCreate) SetNillableCreatedAt(i *int) *AccountCreate { + if i != nil { + ac.SetCreatedAt(*i) + } + return ac +} + +// SetUpdatedAt sets the "updated_at" field. +func (ac *AccountCreate) SetUpdatedAt(i int) *AccountCreate { + ac.mutation.SetUpdatedAt(i) + return ac +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (ac *AccountCreate) SetNillableUpdatedAt(i *int) *AccountCreate { + if i != nil { + ac.SetUpdatedAt(*i) + } + return ac +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (ac *AccountCreate) SetLastBattleTime(i int) *AccountCreate { + ac.mutation.SetLastBattleTime(i) + return ac +} + +// SetAccountCreatedAt sets the "account_created_at" field. +func (ac *AccountCreate) SetAccountCreatedAt(i int) *AccountCreate { + ac.mutation.SetAccountCreatedAt(i) + return ac +} + +// SetRealm sets the "realm" field. +func (ac *AccountCreate) SetRealm(s string) *AccountCreate { + ac.mutation.SetRealm(s) + return ac +} + +// SetNickname sets the "nickname" field. +func (ac *AccountCreate) SetNickname(s string) *AccountCreate { + ac.mutation.SetNickname(s) + return ac +} + +// SetPrivate sets the "private" field. +func (ac *AccountCreate) SetPrivate(b bool) *AccountCreate { + ac.mutation.SetPrivate(b) + return ac +} + +// SetNillablePrivate sets the "private" field if the given value is not nil. +func (ac *AccountCreate) SetNillablePrivate(b *bool) *AccountCreate { + if b != nil { + ac.SetPrivate(*b) + } + return ac +} + +// SetClanID sets the "clan_id" field. +func (ac *AccountCreate) SetClanID(s string) *AccountCreate { + ac.mutation.SetClanID(s) + return ac +} + +// SetNillableClanID sets the "clan_id" field if the given value is not nil. +func (ac *AccountCreate) SetNillableClanID(s *string) *AccountCreate { + if s != nil { + ac.SetClanID(*s) + } + return ac +} + +// SetID sets the "id" field. +func (ac *AccountCreate) SetID(s string) *AccountCreate { + ac.mutation.SetID(s) + return ac +} + +// SetClan sets the "clan" edge to the Clan entity. +func (ac *AccountCreate) SetClan(c *Clan) *AccountCreate { + return ac.SetClanID(c.ID) +} + +// AddSnapshotIDs adds the "snapshots" edge to the AccountSnapshot entity by IDs. +func (ac *AccountCreate) AddSnapshotIDs(ids ...string) *AccountCreate { + ac.mutation.AddSnapshotIDs(ids...) + return ac +} + +// AddSnapshots adds the "snapshots" edges to the AccountSnapshot entity. +func (ac *AccountCreate) AddSnapshots(a ...*AccountSnapshot) *AccountCreate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return ac.AddSnapshotIDs(ids...) +} + +// AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. +func (ac *AccountCreate) AddVehicleSnapshotIDs(ids ...string) *AccountCreate { + ac.mutation.AddVehicleSnapshotIDs(ids...) + return ac +} + +// AddVehicleSnapshots adds the "vehicle_snapshots" edges to the VehicleSnapshot entity. +func (ac *AccountCreate) AddVehicleSnapshots(v ...*VehicleSnapshot) *AccountCreate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return ac.AddVehicleSnapshotIDs(ids...) +} + +// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. +func (ac *AccountCreate) AddAchievementSnapshotIDs(ids ...string) *AccountCreate { + ac.mutation.AddAchievementSnapshotIDs(ids...) + return ac +} + +// AddAchievementSnapshots adds the "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (ac *AccountCreate) AddAchievementSnapshots(a ...*AchievementsSnapshot) *AccountCreate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return ac.AddAchievementSnapshotIDs(ids...) +} + +// Mutation returns the AccountMutation object of the builder. +func (ac *AccountCreate) Mutation() *AccountMutation { + return ac.mutation +} + +// Save creates the Account in the database. +func (ac *AccountCreate) Save(ctx context.Context) (*Account, error) { + ac.defaults() + return withHooks(ctx, ac.sqlSave, ac.mutation, ac.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (ac *AccountCreate) SaveX(ctx context.Context) *Account { + v, err := ac.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ac *AccountCreate) Exec(ctx context.Context) error { + _, err := ac.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ac *AccountCreate) ExecX(ctx context.Context) { + if err := ac.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ac *AccountCreate) defaults() { + if _, ok := ac.mutation.CreatedAt(); !ok { + v := account.DefaultCreatedAt() + ac.mutation.SetCreatedAt(v) + } + if _, ok := ac.mutation.UpdatedAt(); !ok { + v := account.DefaultUpdatedAt() + ac.mutation.SetUpdatedAt(v) + } + if _, ok := ac.mutation.Private(); !ok { + v := account.DefaultPrivate + ac.mutation.SetPrivate(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ac *AccountCreate) check() error { + if _, ok := ac.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "Account.created_at"`)} + } + if _, ok := ac.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "Account.updated_at"`)} + } + if _, ok := ac.mutation.LastBattleTime(); !ok { + return &ValidationError{Name: "last_battle_time", err: errors.New(`db: missing required field "Account.last_battle_time"`)} + } + if _, ok := ac.mutation.AccountCreatedAt(); !ok { + return &ValidationError{Name: "account_created_at", err: errors.New(`db: missing required field "Account.account_created_at"`)} + } + if _, ok := ac.mutation.Realm(); !ok { + return &ValidationError{Name: "realm", err: errors.New(`db: missing required field "Account.realm"`)} + } + if v, ok := ac.mutation.Realm(); ok { + if err := account.RealmValidator(v); err != nil { + return &ValidationError{Name: "realm", err: fmt.Errorf(`db: validator failed for field "Account.realm": %w`, err)} + } + } + if _, ok := ac.mutation.Nickname(); !ok { + return &ValidationError{Name: "nickname", err: errors.New(`db: missing required field "Account.nickname"`)} + } + if v, ok := ac.mutation.Nickname(); ok { + if err := account.NicknameValidator(v); err != nil { + return &ValidationError{Name: "nickname", err: fmt.Errorf(`db: validator failed for field "Account.nickname": %w`, err)} + } + } + if _, ok := ac.mutation.Private(); !ok { + return &ValidationError{Name: "private", err: errors.New(`db: missing required field "Account.private"`)} + } + return nil +} + +func (ac *AccountCreate) sqlSave(ctx context.Context) (*Account, error) { + if err := ac.check(); err != nil { + return nil, err + } + _node, _spec := ac.createSpec() + if err := sqlgraph.CreateNode(ctx, ac.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected Account.ID type: %T", _spec.ID.Value) + } + } + ac.mutation.id = &_node.ID + ac.mutation.done = true + return _node, nil +} + +func (ac *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) { + var ( + _node = &Account{config: ac.config} + _spec = sqlgraph.NewCreateSpec(account.Table, sqlgraph.NewFieldSpec(account.FieldID, field.TypeString)) + ) + if id, ok := ac.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := ac.mutation.CreatedAt(); ok { + _spec.SetField(account.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := ac.mutation.UpdatedAt(); ok { + _spec.SetField(account.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := ac.mutation.LastBattleTime(); ok { + _spec.SetField(account.FieldLastBattleTime, field.TypeInt, value) + _node.LastBattleTime = value + } + if value, ok := ac.mutation.AccountCreatedAt(); ok { + _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt, value) + _node.AccountCreatedAt = value + } + if value, ok := ac.mutation.Realm(); ok { + _spec.SetField(account.FieldRealm, field.TypeString, value) + _node.Realm = value + } + if value, ok := ac.mutation.Nickname(); ok { + _spec.SetField(account.FieldNickname, field.TypeString, value) + _node.Nickname = value + } + if value, ok := ac.mutation.Private(); ok { + _spec.SetField(account.FieldPrivate, field.TypeBool, value) + _node.Private = value + } + if nodes := ac.mutation.ClanIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: account.ClanTable, + Columns: []string{account.ClanColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.ClanID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := ac.mutation.SnapshotsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.SnapshotsTable, + Columns: []string{account.SnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := ac.mutation.VehicleSnapshotsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.VehicleSnapshotsTable, + Columns: []string{account.VehicleSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := ac.mutation.AchievementSnapshotsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// AccountCreateBulk is the builder for creating many Account entities in bulk. +type AccountCreateBulk struct { + config + err error + builders []*AccountCreate +} + +// Save creates the Account entities in the database. +func (acb *AccountCreateBulk) Save(ctx context.Context) ([]*Account, error) { + if acb.err != nil { + return nil, acb.err + } + specs := make([]*sqlgraph.CreateSpec, len(acb.builders)) + nodes := make([]*Account, len(acb.builders)) + mutators := make([]Mutator, len(acb.builders)) + for i := range acb.builders { + func(i int, root context.Context) { + builder := acb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AccountMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, acb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, acb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, acb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (acb *AccountCreateBulk) SaveX(ctx context.Context) []*Account { + v, err := acb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (acb *AccountCreateBulk) Exec(ctx context.Context) error { + _, err := acb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (acb *AccountCreateBulk) ExecX(ctx context.Context) { + if err := acb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/account_delete.go b/internal/database/ent/db/account_delete.go new file mode 100644 index 00000000..df490811 --- /dev/null +++ b/internal/database/ent/db/account_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AccountDelete is the builder for deleting a Account entity. +type AccountDelete struct { + config + hooks []Hook + mutation *AccountMutation +} + +// Where appends a list predicates to the AccountDelete builder. +func (ad *AccountDelete) Where(ps ...predicate.Account) *AccountDelete { + ad.mutation.Where(ps...) + return ad +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (ad *AccountDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, ad.sqlExec, ad.mutation, ad.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (ad *AccountDelete) ExecX(ctx context.Context) int { + n, err := ad.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (ad *AccountDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(account.Table, sqlgraph.NewFieldSpec(account.FieldID, field.TypeString)) + if ps := ad.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, ad.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + ad.mutation.done = true + return affected, err +} + +// AccountDeleteOne is the builder for deleting a single Account entity. +type AccountDeleteOne struct { + ad *AccountDelete +} + +// Where appends a list predicates to the AccountDelete builder. +func (ado *AccountDeleteOne) Where(ps ...predicate.Account) *AccountDeleteOne { + ado.ad.mutation.Where(ps...) + return ado +} + +// Exec executes the deletion query. +func (ado *AccountDeleteOne) Exec(ctx context.Context) error { + n, err := ado.ad.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{account.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (ado *AccountDeleteOne) ExecX(ctx context.Context) { + if err := ado.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/account_query.go b/internal/database/ent/db/account_query.go new file mode 100644 index 00000000..9dce0052 --- /dev/null +++ b/internal/database/ent/db/account_query.go @@ -0,0 +1,830 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" +) + +// AccountQuery is the builder for querying Account entities. +type AccountQuery struct { + config + ctx *QueryContext + order []account.OrderOption + inters []Interceptor + predicates []predicate.Account + withClan *ClanQuery + withSnapshots *AccountSnapshotQuery + withVehicleSnapshots *VehicleSnapshotQuery + withAchievementSnapshots *AchievementsSnapshotQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AccountQuery builder. +func (aq *AccountQuery) Where(ps ...predicate.Account) *AccountQuery { + aq.predicates = append(aq.predicates, ps...) + return aq +} + +// Limit the number of records to be returned by this query. +func (aq *AccountQuery) Limit(limit int) *AccountQuery { + aq.ctx.Limit = &limit + return aq +} + +// Offset to start from. +func (aq *AccountQuery) Offset(offset int) *AccountQuery { + aq.ctx.Offset = &offset + return aq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (aq *AccountQuery) Unique(unique bool) *AccountQuery { + aq.ctx.Unique = &unique + return aq +} + +// Order specifies how the records should be ordered. +func (aq *AccountQuery) Order(o ...account.OrderOption) *AccountQuery { + aq.order = append(aq.order, o...) + return aq +} + +// QueryClan chains the current query on the "clan" edge. +func (aq *AccountQuery) QueryClan() *ClanQuery { + query := (&ClanClient{config: aq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := aq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := aq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, selector), + sqlgraph.To(clan.Table, clan.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, account.ClanTable, account.ClanColumn), + ) + fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QuerySnapshots chains the current query on the "snapshots" edge. +func (aq *AccountQuery) QuerySnapshots() *AccountSnapshotQuery { + query := (&AccountSnapshotClient{config: aq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := aq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := aq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, selector), + sqlgraph.To(accountsnapshot.Table, accountsnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.SnapshotsTable, account.SnapshotsColumn), + ) + fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryVehicleSnapshots chains the current query on the "vehicle_snapshots" edge. +func (aq *AccountQuery) QueryVehicleSnapshots() *VehicleSnapshotQuery { + query := (&VehicleSnapshotClient{config: aq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := aq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := aq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, selector), + sqlgraph.To(vehiclesnapshot.Table, vehiclesnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.VehicleSnapshotsTable, account.VehicleSnapshotsColumn), + ) + fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryAchievementSnapshots chains the current query on the "achievement_snapshots" edge. +func (aq *AccountQuery) QueryAchievementSnapshots() *AchievementsSnapshotQuery { + query := (&AchievementsSnapshotClient{config: aq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := aq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := aq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, selector), + sqlgraph.To(achievementssnapshot.Table, achievementssnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.AchievementSnapshotsTable, account.AchievementSnapshotsColumn), + ) + fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first Account entity from the query. +// Returns a *NotFoundError when no Account was found. +func (aq *AccountQuery) First(ctx context.Context) (*Account, error) { + nodes, err := aq.Limit(1).All(setContextOp(ctx, aq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{account.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (aq *AccountQuery) FirstX(ctx context.Context) *Account { + node, err := aq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Account ID from the query. +// Returns a *NotFoundError when no Account ID was found. +func (aq *AccountQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = aq.Limit(1).IDs(setContextOp(ctx, aq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{account.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (aq *AccountQuery) FirstIDX(ctx context.Context) string { + id, err := aq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Account entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Account entity is found. +// Returns a *NotFoundError when no Account entities are found. +func (aq *AccountQuery) Only(ctx context.Context) (*Account, error) { + nodes, err := aq.Limit(2).All(setContextOp(ctx, aq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{account.Label} + default: + return nil, &NotSingularError{account.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (aq *AccountQuery) OnlyX(ctx context.Context) *Account { + node, err := aq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Account ID in the query. +// Returns a *NotSingularError when more than one Account ID is found. +// Returns a *NotFoundError when no entities are found. +func (aq *AccountQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = aq.Limit(2).IDs(setContextOp(ctx, aq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{account.Label} + default: + err = &NotSingularError{account.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (aq *AccountQuery) OnlyIDX(ctx context.Context) string { + id, err := aq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Accounts. +func (aq *AccountQuery) All(ctx context.Context) ([]*Account, error) { + ctx = setContextOp(ctx, aq.ctx, "All") + if err := aq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Account, *AccountQuery]() + return withInterceptors[[]*Account](ctx, aq, qr, aq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (aq *AccountQuery) AllX(ctx context.Context) []*Account { + nodes, err := aq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Account IDs. +func (aq *AccountQuery) IDs(ctx context.Context) (ids []string, err error) { + if aq.ctx.Unique == nil && aq.path != nil { + aq.Unique(true) + } + ctx = setContextOp(ctx, aq.ctx, "IDs") + if err = aq.Select(account.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (aq *AccountQuery) IDsX(ctx context.Context) []string { + ids, err := aq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (aq *AccountQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, aq.ctx, "Count") + if err := aq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, aq, querierCount[*AccountQuery](), aq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (aq *AccountQuery) CountX(ctx context.Context) int { + count, err := aq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (aq *AccountQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, aq.ctx, "Exist") + switch _, err := aq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (aq *AccountQuery) ExistX(ctx context.Context) bool { + exist, err := aq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AccountQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (aq *AccountQuery) Clone() *AccountQuery { + if aq == nil { + return nil + } + return &AccountQuery{ + config: aq.config, + ctx: aq.ctx.Clone(), + order: append([]account.OrderOption{}, aq.order...), + inters: append([]Interceptor{}, aq.inters...), + predicates: append([]predicate.Account{}, aq.predicates...), + withClan: aq.withClan.Clone(), + withSnapshots: aq.withSnapshots.Clone(), + withVehicleSnapshots: aq.withVehicleSnapshots.Clone(), + withAchievementSnapshots: aq.withAchievementSnapshots.Clone(), + // clone intermediate query. + sql: aq.sql.Clone(), + path: aq.path, + } +} + +// WithClan tells the query-builder to eager-load the nodes that are connected to +// the "clan" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AccountQuery) WithClan(opts ...func(*ClanQuery)) *AccountQuery { + query := (&ClanClient{config: aq.config}).Query() + for _, opt := range opts { + opt(query) + } + aq.withClan = query + return aq +} + +// WithSnapshots tells the query-builder to eager-load the nodes that are connected to +// the "snapshots" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AccountQuery) WithSnapshots(opts ...func(*AccountSnapshotQuery)) *AccountQuery { + query := (&AccountSnapshotClient{config: aq.config}).Query() + for _, opt := range opts { + opt(query) + } + aq.withSnapshots = query + return aq +} + +// WithVehicleSnapshots tells the query-builder to eager-load the nodes that are connected to +// the "vehicle_snapshots" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AccountQuery) WithVehicleSnapshots(opts ...func(*VehicleSnapshotQuery)) *AccountQuery { + query := (&VehicleSnapshotClient{config: aq.config}).Query() + for _, opt := range opts { + opt(query) + } + aq.withVehicleSnapshots = query + return aq +} + +// WithAchievementSnapshots tells the query-builder to eager-load the nodes that are connected to +// the "achievement_snapshots" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AccountQuery) WithAchievementSnapshots(opts ...func(*AchievementsSnapshotQuery)) *AccountQuery { + query := (&AchievementsSnapshotClient{config: aq.config}).Query() + for _, opt := range opts { + opt(query) + } + aq.withAchievementSnapshots = query + return aq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Account.Query(). +// GroupBy(account.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (aq *AccountQuery) GroupBy(field string, fields ...string) *AccountGroupBy { + aq.ctx.Fields = append([]string{field}, fields...) + grbuild := &AccountGroupBy{build: aq} + grbuild.flds = &aq.ctx.Fields + grbuild.label = account.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.Account.Query(). +// Select(account.FieldCreatedAt). +// Scan(ctx, &v) +func (aq *AccountQuery) Select(fields ...string) *AccountSelect { + aq.ctx.Fields = append(aq.ctx.Fields, fields...) + sbuild := &AccountSelect{AccountQuery: aq} + sbuild.label = account.Label + sbuild.flds, sbuild.scan = &aq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a AccountSelect configured with the given aggregations. +func (aq *AccountQuery) Aggregate(fns ...AggregateFunc) *AccountSelect { + return aq.Select().Aggregate(fns...) +} + +func (aq *AccountQuery) prepareQuery(ctx context.Context) error { + for _, inter := range aq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, aq); err != nil { + return err + } + } + } + for _, f := range aq.ctx.Fields { + if !account.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if aq.path != nil { + prev, err := aq.path(ctx) + if err != nil { + return err + } + aq.sql = prev + } + return nil +} + +func (aq *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Account, error) { + var ( + nodes = []*Account{} + _spec = aq.querySpec() + loadedTypes = [4]bool{ + aq.withClan != nil, + aq.withSnapshots != nil, + aq.withVehicleSnapshots != nil, + aq.withAchievementSnapshots != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*Account).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &Account{config: aq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, aq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := aq.withClan; query != nil { + if err := aq.loadClan(ctx, query, nodes, nil, + func(n *Account, e *Clan) { n.Edges.Clan = e }); err != nil { + return nil, err + } + } + if query := aq.withSnapshots; query != nil { + if err := aq.loadSnapshots(ctx, query, nodes, + func(n *Account) { n.Edges.Snapshots = []*AccountSnapshot{} }, + func(n *Account, e *AccountSnapshot) { n.Edges.Snapshots = append(n.Edges.Snapshots, e) }); err != nil { + return nil, err + } + } + if query := aq.withVehicleSnapshots; query != nil { + if err := aq.loadVehicleSnapshots(ctx, query, nodes, + func(n *Account) { n.Edges.VehicleSnapshots = []*VehicleSnapshot{} }, + func(n *Account, e *VehicleSnapshot) { n.Edges.VehicleSnapshots = append(n.Edges.VehicleSnapshots, e) }); err != nil { + return nil, err + } + } + if query := aq.withAchievementSnapshots; query != nil { + if err := aq.loadAchievementSnapshots(ctx, query, nodes, + func(n *Account) { n.Edges.AchievementSnapshots = []*AchievementsSnapshot{} }, + func(n *Account, e *AchievementsSnapshot) { + n.Edges.AchievementSnapshots = append(n.Edges.AchievementSnapshots, e) + }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (aq *AccountQuery) loadClan(ctx context.Context, query *ClanQuery, nodes []*Account, init func(*Account), assign func(*Account, *Clan)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*Account) + for i := range nodes { + fk := nodes[i].ClanID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(clan.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "clan_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (aq *AccountQuery) loadSnapshots(ctx context.Context, query *AccountSnapshotQuery, nodes []*Account, init func(*Account), assign func(*Account, *AccountSnapshot)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*Account) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(accountsnapshot.FieldAccountID) + } + query.Where(predicate.AccountSnapshot(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(account.SnapshotsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.AccountID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "account_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} +func (aq *AccountQuery) loadVehicleSnapshots(ctx context.Context, query *VehicleSnapshotQuery, nodes []*Account, init func(*Account), assign func(*Account, *VehicleSnapshot)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*Account) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(vehiclesnapshot.FieldAccountID) + } + query.Where(predicate.VehicleSnapshot(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(account.VehicleSnapshotsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.AccountID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "account_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} +func (aq *AccountQuery) loadAchievementSnapshots(ctx context.Context, query *AchievementsSnapshotQuery, nodes []*Account, init func(*Account), assign func(*Account, *AchievementsSnapshot)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*Account) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(achievementssnapshot.FieldAccountID) + } + query.Where(predicate.AchievementsSnapshot(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(account.AchievementSnapshotsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.AccountID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "account_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} + +func (aq *AccountQuery) sqlCount(ctx context.Context) (int, error) { + _spec := aq.querySpec() + _spec.Node.Columns = aq.ctx.Fields + if len(aq.ctx.Fields) > 0 { + _spec.Unique = aq.ctx.Unique != nil && *aq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, aq.driver, _spec) +} + +func (aq *AccountQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(account.Table, account.Columns, sqlgraph.NewFieldSpec(account.FieldID, field.TypeString)) + _spec.From = aq.sql + if unique := aq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if aq.path != nil { + _spec.Unique = true + } + if fields := aq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, account.FieldID) + for i := range fields { + if fields[i] != account.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if aq.withClan != nil { + _spec.Node.AddColumnOnce(account.FieldClanID) + } + } + if ps := aq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := aq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := aq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := aq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (aq *AccountQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(aq.driver.Dialect()) + t1 := builder.Table(account.Table) + columns := aq.ctx.Fields + if len(columns) == 0 { + columns = account.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if aq.sql != nil { + selector = aq.sql + selector.Select(selector.Columns(columns...)...) + } + if aq.ctx.Unique != nil && *aq.ctx.Unique { + selector.Distinct() + } + for _, p := range aq.predicates { + p(selector) + } + for _, p := range aq.order { + p(selector) + } + if offset := aq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := aq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// AccountGroupBy is the group-by builder for Account entities. +type AccountGroupBy struct { + selector + build *AccountQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (agb *AccountGroupBy) Aggregate(fns ...AggregateFunc) *AccountGroupBy { + agb.fns = append(agb.fns, fns...) + return agb +} + +// Scan applies the selector query and scans the result into the given value. +func (agb *AccountGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, agb.build.ctx, "GroupBy") + if err := agb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AccountQuery, *AccountGroupBy](ctx, agb.build, agb, agb.build.inters, v) +} + +func (agb *AccountGroupBy) sqlScan(ctx context.Context, root *AccountQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(agb.fns)) + for _, fn := range agb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*agb.flds)+len(agb.fns)) + for _, f := range *agb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*agb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := agb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// AccountSelect is the builder for selecting fields of Account entities. +type AccountSelect struct { + *AccountQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (as *AccountSelect) Aggregate(fns ...AggregateFunc) *AccountSelect { + as.fns = append(as.fns, fns...) + return as +} + +// Scan applies the selector query and scans the result into the given value. +func (as *AccountSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, as.ctx, "Select") + if err := as.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AccountQuery, *AccountSelect](ctx, as.AccountQuery, as, as.inters, v) +} + +func (as *AccountSelect) sqlScan(ctx context.Context, root *AccountQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(as.fns)) + for _, fn := range as.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*as.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := as.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/account_update.go b/internal/database/ent/db/account_update.go new file mode 100644 index 00000000..c58f930b --- /dev/null +++ b/internal/database/ent/db/account_update.go @@ -0,0 +1,1087 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" +) + +// AccountUpdate is the builder for updating Account entities. +type AccountUpdate struct { + config + hooks []Hook + mutation *AccountMutation +} + +// Where appends a list predicates to the AccountUpdate builder. +func (au *AccountUpdate) Where(ps ...predicate.Account) *AccountUpdate { + au.mutation.Where(ps...) + return au +} + +// SetUpdatedAt sets the "updated_at" field. +func (au *AccountUpdate) SetUpdatedAt(i int) *AccountUpdate { + au.mutation.ResetUpdatedAt() + au.mutation.SetUpdatedAt(i) + return au +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (au *AccountUpdate) AddUpdatedAt(i int) *AccountUpdate { + au.mutation.AddUpdatedAt(i) + return au +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (au *AccountUpdate) SetLastBattleTime(i int) *AccountUpdate { + au.mutation.ResetLastBattleTime() + au.mutation.SetLastBattleTime(i) + return au +} + +// SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. +func (au *AccountUpdate) SetNillableLastBattleTime(i *int) *AccountUpdate { + if i != nil { + au.SetLastBattleTime(*i) + } + return au +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (au *AccountUpdate) AddLastBattleTime(i int) *AccountUpdate { + au.mutation.AddLastBattleTime(i) + return au +} + +// SetAccountCreatedAt sets the "account_created_at" field. +func (au *AccountUpdate) SetAccountCreatedAt(i int) *AccountUpdate { + au.mutation.ResetAccountCreatedAt() + au.mutation.SetAccountCreatedAt(i) + return au +} + +// SetNillableAccountCreatedAt sets the "account_created_at" field if the given value is not nil. +func (au *AccountUpdate) SetNillableAccountCreatedAt(i *int) *AccountUpdate { + if i != nil { + au.SetAccountCreatedAt(*i) + } + return au +} + +// AddAccountCreatedAt adds i to the "account_created_at" field. +func (au *AccountUpdate) AddAccountCreatedAt(i int) *AccountUpdate { + au.mutation.AddAccountCreatedAt(i) + return au +} + +// SetRealm sets the "realm" field. +func (au *AccountUpdate) SetRealm(s string) *AccountUpdate { + au.mutation.SetRealm(s) + return au +} + +// SetNillableRealm sets the "realm" field if the given value is not nil. +func (au *AccountUpdate) SetNillableRealm(s *string) *AccountUpdate { + if s != nil { + au.SetRealm(*s) + } + return au +} + +// SetNickname sets the "nickname" field. +func (au *AccountUpdate) SetNickname(s string) *AccountUpdate { + au.mutation.SetNickname(s) + return au +} + +// SetNillableNickname sets the "nickname" field if the given value is not nil. +func (au *AccountUpdate) SetNillableNickname(s *string) *AccountUpdate { + if s != nil { + au.SetNickname(*s) + } + return au +} + +// SetPrivate sets the "private" field. +func (au *AccountUpdate) SetPrivate(b bool) *AccountUpdate { + au.mutation.SetPrivate(b) + return au +} + +// SetNillablePrivate sets the "private" field if the given value is not nil. +func (au *AccountUpdate) SetNillablePrivate(b *bool) *AccountUpdate { + if b != nil { + au.SetPrivate(*b) + } + return au +} + +// SetClanID sets the "clan_id" field. +func (au *AccountUpdate) SetClanID(s string) *AccountUpdate { + au.mutation.SetClanID(s) + return au +} + +// SetNillableClanID sets the "clan_id" field if the given value is not nil. +func (au *AccountUpdate) SetNillableClanID(s *string) *AccountUpdate { + if s != nil { + au.SetClanID(*s) + } + return au +} + +// ClearClanID clears the value of the "clan_id" field. +func (au *AccountUpdate) ClearClanID() *AccountUpdate { + au.mutation.ClearClanID() + return au +} + +// SetClan sets the "clan" edge to the Clan entity. +func (au *AccountUpdate) SetClan(c *Clan) *AccountUpdate { + return au.SetClanID(c.ID) +} + +// AddSnapshotIDs adds the "snapshots" edge to the AccountSnapshot entity by IDs. +func (au *AccountUpdate) AddSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.AddSnapshotIDs(ids...) + return au +} + +// AddSnapshots adds the "snapshots" edges to the AccountSnapshot entity. +func (au *AccountUpdate) AddSnapshots(a ...*AccountSnapshot) *AccountUpdate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return au.AddSnapshotIDs(ids...) +} + +// AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. +func (au *AccountUpdate) AddVehicleSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.AddVehicleSnapshotIDs(ids...) + return au +} + +// AddVehicleSnapshots adds the "vehicle_snapshots" edges to the VehicleSnapshot entity. +func (au *AccountUpdate) AddVehicleSnapshots(v ...*VehicleSnapshot) *AccountUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return au.AddVehicleSnapshotIDs(ids...) +} + +// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. +func (au *AccountUpdate) AddAchievementSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.AddAchievementSnapshotIDs(ids...) + return au +} + +// AddAchievementSnapshots adds the "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (au *AccountUpdate) AddAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return au.AddAchievementSnapshotIDs(ids...) +} + +// Mutation returns the AccountMutation object of the builder. +func (au *AccountUpdate) Mutation() *AccountMutation { + return au.mutation +} + +// ClearClan clears the "clan" edge to the Clan entity. +func (au *AccountUpdate) ClearClan() *AccountUpdate { + au.mutation.ClearClan() + return au +} + +// ClearSnapshots clears all "snapshots" edges to the AccountSnapshot entity. +func (au *AccountUpdate) ClearSnapshots() *AccountUpdate { + au.mutation.ClearSnapshots() + return au +} + +// RemoveSnapshotIDs removes the "snapshots" edge to AccountSnapshot entities by IDs. +func (au *AccountUpdate) RemoveSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.RemoveSnapshotIDs(ids...) + return au +} + +// RemoveSnapshots removes "snapshots" edges to AccountSnapshot entities. +func (au *AccountUpdate) RemoveSnapshots(a ...*AccountSnapshot) *AccountUpdate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return au.RemoveSnapshotIDs(ids...) +} + +// ClearVehicleSnapshots clears all "vehicle_snapshots" edges to the VehicleSnapshot entity. +func (au *AccountUpdate) ClearVehicleSnapshots() *AccountUpdate { + au.mutation.ClearVehicleSnapshots() + return au +} + +// RemoveVehicleSnapshotIDs removes the "vehicle_snapshots" edge to VehicleSnapshot entities by IDs. +func (au *AccountUpdate) RemoveVehicleSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.RemoveVehicleSnapshotIDs(ids...) + return au +} + +// RemoveVehicleSnapshots removes "vehicle_snapshots" edges to VehicleSnapshot entities. +func (au *AccountUpdate) RemoveVehicleSnapshots(v ...*VehicleSnapshot) *AccountUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return au.RemoveVehicleSnapshotIDs(ids...) +} + +// ClearAchievementSnapshots clears all "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (au *AccountUpdate) ClearAchievementSnapshots() *AccountUpdate { + au.mutation.ClearAchievementSnapshots() + return au +} + +// RemoveAchievementSnapshotIDs removes the "achievement_snapshots" edge to AchievementsSnapshot entities by IDs. +func (au *AccountUpdate) RemoveAchievementSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.RemoveAchievementSnapshotIDs(ids...) + return au +} + +// RemoveAchievementSnapshots removes "achievement_snapshots" edges to AchievementsSnapshot entities. +func (au *AccountUpdate) RemoveAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return au.RemoveAchievementSnapshotIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (au *AccountUpdate) Save(ctx context.Context) (int, error) { + au.defaults() + return withHooks(ctx, au.sqlSave, au.mutation, au.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (au *AccountUpdate) SaveX(ctx context.Context) int { + affected, err := au.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (au *AccountUpdate) Exec(ctx context.Context) error { + _, err := au.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (au *AccountUpdate) ExecX(ctx context.Context) { + if err := au.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (au *AccountUpdate) defaults() { + if _, ok := au.mutation.UpdatedAt(); !ok { + v := account.UpdateDefaultUpdatedAt() + au.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (au *AccountUpdate) check() error { + if v, ok := au.mutation.Realm(); ok { + if err := account.RealmValidator(v); err != nil { + return &ValidationError{Name: "realm", err: fmt.Errorf(`db: validator failed for field "Account.realm": %w`, err)} + } + } + if v, ok := au.mutation.Nickname(); ok { + if err := account.NicknameValidator(v); err != nil { + return &ValidationError{Name: "nickname", err: fmt.Errorf(`db: validator failed for field "Account.nickname": %w`, err)} + } + } + return nil +} + +func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := au.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(account.Table, account.Columns, sqlgraph.NewFieldSpec(account.FieldID, field.TypeString)) + if ps := au.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := au.mutation.UpdatedAt(); ok { + _spec.SetField(account.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := au.mutation.AddedUpdatedAt(); ok { + _spec.AddField(account.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := au.mutation.LastBattleTime(); ok { + _spec.SetField(account.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := au.mutation.AddedLastBattleTime(); ok { + _spec.AddField(account.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := au.mutation.AccountCreatedAt(); ok { + _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt, value) + } + if value, ok := au.mutation.AddedAccountCreatedAt(); ok { + _spec.AddField(account.FieldAccountCreatedAt, field.TypeInt, value) + } + if value, ok := au.mutation.Realm(); ok { + _spec.SetField(account.FieldRealm, field.TypeString, value) + } + if value, ok := au.mutation.Nickname(); ok { + _spec.SetField(account.FieldNickname, field.TypeString, value) + } + if value, ok := au.mutation.Private(); ok { + _spec.SetField(account.FieldPrivate, field.TypeBool, value) + } + if au.mutation.ClanCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: account.ClanTable, + Columns: []string{account.ClanColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.ClanIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: account.ClanTable, + Columns: []string{account.ClanColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if au.mutation.SnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.SnapshotsTable, + Columns: []string{account.SnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.RemovedSnapshotsIDs(); len(nodes) > 0 && !au.mutation.SnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.SnapshotsTable, + Columns: []string{account.SnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.SnapshotsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.SnapshotsTable, + Columns: []string{account.SnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if au.mutation.VehicleSnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.VehicleSnapshotsTable, + Columns: []string{account.VehicleSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.RemovedVehicleSnapshotsIDs(); len(nodes) > 0 && !au.mutation.VehicleSnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.VehicleSnapshotsTable, + Columns: []string{account.VehicleSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.VehicleSnapshotsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.VehicleSnapshotsTable, + Columns: []string{account.VehicleSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if au.mutation.AchievementSnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.RemovedAchievementSnapshotsIDs(); len(nodes) > 0 && !au.mutation.AchievementSnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := au.mutation.AchievementSnapshotsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, au.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{account.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + au.mutation.done = true + return n, nil +} + +// AccountUpdateOne is the builder for updating a single Account entity. +type AccountUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AccountMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (auo *AccountUpdateOne) SetUpdatedAt(i int) *AccountUpdateOne { + auo.mutation.ResetUpdatedAt() + auo.mutation.SetUpdatedAt(i) + return auo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (auo *AccountUpdateOne) AddUpdatedAt(i int) *AccountUpdateOne { + auo.mutation.AddUpdatedAt(i) + return auo +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (auo *AccountUpdateOne) SetLastBattleTime(i int) *AccountUpdateOne { + auo.mutation.ResetLastBattleTime() + auo.mutation.SetLastBattleTime(i) + return auo +} + +// SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. +func (auo *AccountUpdateOne) SetNillableLastBattleTime(i *int) *AccountUpdateOne { + if i != nil { + auo.SetLastBattleTime(*i) + } + return auo +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (auo *AccountUpdateOne) AddLastBattleTime(i int) *AccountUpdateOne { + auo.mutation.AddLastBattleTime(i) + return auo +} + +// SetAccountCreatedAt sets the "account_created_at" field. +func (auo *AccountUpdateOne) SetAccountCreatedAt(i int) *AccountUpdateOne { + auo.mutation.ResetAccountCreatedAt() + auo.mutation.SetAccountCreatedAt(i) + return auo +} + +// SetNillableAccountCreatedAt sets the "account_created_at" field if the given value is not nil. +func (auo *AccountUpdateOne) SetNillableAccountCreatedAt(i *int) *AccountUpdateOne { + if i != nil { + auo.SetAccountCreatedAt(*i) + } + return auo +} + +// AddAccountCreatedAt adds i to the "account_created_at" field. +func (auo *AccountUpdateOne) AddAccountCreatedAt(i int) *AccountUpdateOne { + auo.mutation.AddAccountCreatedAt(i) + return auo +} + +// SetRealm sets the "realm" field. +func (auo *AccountUpdateOne) SetRealm(s string) *AccountUpdateOne { + auo.mutation.SetRealm(s) + return auo +} + +// SetNillableRealm sets the "realm" field if the given value is not nil. +func (auo *AccountUpdateOne) SetNillableRealm(s *string) *AccountUpdateOne { + if s != nil { + auo.SetRealm(*s) + } + return auo +} + +// SetNickname sets the "nickname" field. +func (auo *AccountUpdateOne) SetNickname(s string) *AccountUpdateOne { + auo.mutation.SetNickname(s) + return auo +} + +// SetNillableNickname sets the "nickname" field if the given value is not nil. +func (auo *AccountUpdateOne) SetNillableNickname(s *string) *AccountUpdateOne { + if s != nil { + auo.SetNickname(*s) + } + return auo +} + +// SetPrivate sets the "private" field. +func (auo *AccountUpdateOne) SetPrivate(b bool) *AccountUpdateOne { + auo.mutation.SetPrivate(b) + return auo +} + +// SetNillablePrivate sets the "private" field if the given value is not nil. +func (auo *AccountUpdateOne) SetNillablePrivate(b *bool) *AccountUpdateOne { + if b != nil { + auo.SetPrivate(*b) + } + return auo +} + +// SetClanID sets the "clan_id" field. +func (auo *AccountUpdateOne) SetClanID(s string) *AccountUpdateOne { + auo.mutation.SetClanID(s) + return auo +} + +// SetNillableClanID sets the "clan_id" field if the given value is not nil. +func (auo *AccountUpdateOne) SetNillableClanID(s *string) *AccountUpdateOne { + if s != nil { + auo.SetClanID(*s) + } + return auo +} + +// ClearClanID clears the value of the "clan_id" field. +func (auo *AccountUpdateOne) ClearClanID() *AccountUpdateOne { + auo.mutation.ClearClanID() + return auo +} + +// SetClan sets the "clan" edge to the Clan entity. +func (auo *AccountUpdateOne) SetClan(c *Clan) *AccountUpdateOne { + return auo.SetClanID(c.ID) +} + +// AddSnapshotIDs adds the "snapshots" edge to the AccountSnapshot entity by IDs. +func (auo *AccountUpdateOne) AddSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.AddSnapshotIDs(ids...) + return auo +} + +// AddSnapshots adds the "snapshots" edges to the AccountSnapshot entity. +func (auo *AccountUpdateOne) AddSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return auo.AddSnapshotIDs(ids...) +} + +// AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. +func (auo *AccountUpdateOne) AddVehicleSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.AddVehicleSnapshotIDs(ids...) + return auo +} + +// AddVehicleSnapshots adds the "vehicle_snapshots" edges to the VehicleSnapshot entity. +func (auo *AccountUpdateOne) AddVehicleSnapshots(v ...*VehicleSnapshot) *AccountUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return auo.AddVehicleSnapshotIDs(ids...) +} + +// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. +func (auo *AccountUpdateOne) AddAchievementSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.AddAchievementSnapshotIDs(ids...) + return auo +} + +// AddAchievementSnapshots adds the "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (auo *AccountUpdateOne) AddAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdateOne { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return auo.AddAchievementSnapshotIDs(ids...) +} + +// Mutation returns the AccountMutation object of the builder. +func (auo *AccountUpdateOne) Mutation() *AccountMutation { + return auo.mutation +} + +// ClearClan clears the "clan" edge to the Clan entity. +func (auo *AccountUpdateOne) ClearClan() *AccountUpdateOne { + auo.mutation.ClearClan() + return auo +} + +// ClearSnapshots clears all "snapshots" edges to the AccountSnapshot entity. +func (auo *AccountUpdateOne) ClearSnapshots() *AccountUpdateOne { + auo.mutation.ClearSnapshots() + return auo +} + +// RemoveSnapshotIDs removes the "snapshots" edge to AccountSnapshot entities by IDs. +func (auo *AccountUpdateOne) RemoveSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.RemoveSnapshotIDs(ids...) + return auo +} + +// RemoveSnapshots removes "snapshots" edges to AccountSnapshot entities. +func (auo *AccountUpdateOne) RemoveSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return auo.RemoveSnapshotIDs(ids...) +} + +// ClearVehicleSnapshots clears all "vehicle_snapshots" edges to the VehicleSnapshot entity. +func (auo *AccountUpdateOne) ClearVehicleSnapshots() *AccountUpdateOne { + auo.mutation.ClearVehicleSnapshots() + return auo +} + +// RemoveVehicleSnapshotIDs removes the "vehicle_snapshots" edge to VehicleSnapshot entities by IDs. +func (auo *AccountUpdateOne) RemoveVehicleSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.RemoveVehicleSnapshotIDs(ids...) + return auo +} + +// RemoveVehicleSnapshots removes "vehicle_snapshots" edges to VehicleSnapshot entities. +func (auo *AccountUpdateOne) RemoveVehicleSnapshots(v ...*VehicleSnapshot) *AccountUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return auo.RemoveVehicleSnapshotIDs(ids...) +} + +// ClearAchievementSnapshots clears all "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (auo *AccountUpdateOne) ClearAchievementSnapshots() *AccountUpdateOne { + auo.mutation.ClearAchievementSnapshots() + return auo +} + +// RemoveAchievementSnapshotIDs removes the "achievement_snapshots" edge to AchievementsSnapshot entities by IDs. +func (auo *AccountUpdateOne) RemoveAchievementSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.RemoveAchievementSnapshotIDs(ids...) + return auo +} + +// RemoveAchievementSnapshots removes "achievement_snapshots" edges to AchievementsSnapshot entities. +func (auo *AccountUpdateOne) RemoveAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdateOne { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return auo.RemoveAchievementSnapshotIDs(ids...) +} + +// Where appends a list predicates to the AccountUpdate builder. +func (auo *AccountUpdateOne) Where(ps ...predicate.Account) *AccountUpdateOne { + auo.mutation.Where(ps...) + return auo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (auo *AccountUpdateOne) Select(field string, fields ...string) *AccountUpdateOne { + auo.fields = append([]string{field}, fields...) + return auo +} + +// Save executes the query and returns the updated Account entity. +func (auo *AccountUpdateOne) Save(ctx context.Context) (*Account, error) { + auo.defaults() + return withHooks(ctx, auo.sqlSave, auo.mutation, auo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (auo *AccountUpdateOne) SaveX(ctx context.Context) *Account { + node, err := auo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (auo *AccountUpdateOne) Exec(ctx context.Context) error { + _, err := auo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (auo *AccountUpdateOne) ExecX(ctx context.Context) { + if err := auo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (auo *AccountUpdateOne) defaults() { + if _, ok := auo.mutation.UpdatedAt(); !ok { + v := account.UpdateDefaultUpdatedAt() + auo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (auo *AccountUpdateOne) check() error { + if v, ok := auo.mutation.Realm(); ok { + if err := account.RealmValidator(v); err != nil { + return &ValidationError{Name: "realm", err: fmt.Errorf(`db: validator failed for field "Account.realm": %w`, err)} + } + } + if v, ok := auo.mutation.Nickname(); ok { + if err := account.NicknameValidator(v); err != nil { + return &ValidationError{Name: "nickname", err: fmt.Errorf(`db: validator failed for field "Account.nickname": %w`, err)} + } + } + return nil +} + +func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err error) { + if err := auo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(account.Table, account.Columns, sqlgraph.NewFieldSpec(account.FieldID, field.TypeString)) + id, ok := auo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "Account.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := auo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, account.FieldID) + for _, f := range fields { + if !account.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != account.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := auo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := auo.mutation.UpdatedAt(); ok { + _spec.SetField(account.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := auo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(account.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := auo.mutation.LastBattleTime(); ok { + _spec.SetField(account.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := auo.mutation.AddedLastBattleTime(); ok { + _spec.AddField(account.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := auo.mutation.AccountCreatedAt(); ok { + _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt, value) + } + if value, ok := auo.mutation.AddedAccountCreatedAt(); ok { + _spec.AddField(account.FieldAccountCreatedAt, field.TypeInt, value) + } + if value, ok := auo.mutation.Realm(); ok { + _spec.SetField(account.FieldRealm, field.TypeString, value) + } + if value, ok := auo.mutation.Nickname(); ok { + _spec.SetField(account.FieldNickname, field.TypeString, value) + } + if value, ok := auo.mutation.Private(); ok { + _spec.SetField(account.FieldPrivate, field.TypeBool, value) + } + if auo.mutation.ClanCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: account.ClanTable, + Columns: []string{account.ClanColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.ClanIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: account.ClanTable, + Columns: []string{account.ClanColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if auo.mutation.SnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.SnapshotsTable, + Columns: []string{account.SnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.RemovedSnapshotsIDs(); len(nodes) > 0 && !auo.mutation.SnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.SnapshotsTable, + Columns: []string{account.SnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.SnapshotsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.SnapshotsTable, + Columns: []string{account.SnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if auo.mutation.VehicleSnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.VehicleSnapshotsTable, + Columns: []string{account.VehicleSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.RemovedVehicleSnapshotsIDs(); len(nodes) > 0 && !auo.mutation.VehicleSnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.VehicleSnapshotsTable, + Columns: []string{account.VehicleSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.VehicleSnapshotsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.VehicleSnapshotsTable, + Columns: []string{account.VehicleSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if auo.mutation.AchievementSnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.RemovedAchievementSnapshotsIDs(); len(nodes) > 0 && !auo.mutation.AchievementSnapshotsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := auo.mutation.AchievementSnapshotsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &Account{config: auo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, auo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{account.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + auo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/accountsnapshot.go b/internal/database/ent/db/accountsnapshot.go new file mode 100644 index 00000000..d0dd7952 --- /dev/null +++ b/internal/database/ent/db/accountsnapshot.go @@ -0,0 +1,240 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// AccountSnapshot is the model entity for the AccountSnapshot schema. +type AccountSnapshot struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Type holds the value of the "type" field. + Type models.SnapshotType `json:"type,omitempty"` + // LastBattleTime holds the value of the "last_battle_time" field. + LastBattleTime int `json:"last_battle_time,omitempty"` + // AccountID holds the value of the "account_id" field. + AccountID string `json:"account_id,omitempty"` + // ReferenceID holds the value of the "reference_id" field. + ReferenceID string `json:"reference_id,omitempty"` + // RatingBattles holds the value of the "rating_battles" field. + RatingBattles int `json:"rating_battles,omitempty"` + // RatingFrame holds the value of the "rating_frame" field. + RatingFrame frame.StatsFrame `json:"rating_frame,omitempty"` + // RegularBattles holds the value of the "regular_battles" field. + RegularBattles int `json:"regular_battles,omitempty"` + // RegularFrame holds the value of the "regular_frame" field. + RegularFrame frame.StatsFrame `json:"regular_frame,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the AccountSnapshotQuery when eager-loading is set. + Edges AccountSnapshotEdges `json:"edges"` + selectValues sql.SelectValues +} + +// AccountSnapshotEdges holds the relations/edges for other nodes in the graph. +type AccountSnapshotEdges struct { + // Account holds the value of the account edge. + Account *Account `json:"account,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// AccountOrErr returns the Account value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e AccountSnapshotEdges) AccountOrErr() (*Account, error) { + if e.Account != nil { + return e.Account, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: account.Label} + } + return nil, &NotLoadedError{edge: "account"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*AccountSnapshot) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case accountsnapshot.FieldRatingFrame, accountsnapshot.FieldRegularFrame: + values[i] = new([]byte) + case accountsnapshot.FieldCreatedAt, accountsnapshot.FieldUpdatedAt, accountsnapshot.FieldLastBattleTime, accountsnapshot.FieldRatingBattles, accountsnapshot.FieldRegularBattles: + values[i] = new(sql.NullInt64) + case accountsnapshot.FieldID, accountsnapshot.FieldType, accountsnapshot.FieldAccountID, accountsnapshot.FieldReferenceID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the AccountSnapshot fields. +func (as *AccountSnapshot) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case accountsnapshot.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + as.ID = value.String + } + case accountsnapshot.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + as.CreatedAt = int(value.Int64) + } + case accountsnapshot.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + as.UpdatedAt = int(value.Int64) + } + case accountsnapshot.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + as.Type = models.SnapshotType(value.String) + } + case accountsnapshot.FieldLastBattleTime: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) + } else if value.Valid { + as.LastBattleTime = int(value.Int64) + } + case accountsnapshot.FieldAccountID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field account_id", values[i]) + } else if value.Valid { + as.AccountID = value.String + } + case accountsnapshot.FieldReferenceID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reference_id", values[i]) + } else if value.Valid { + as.ReferenceID = value.String + } + case accountsnapshot.FieldRatingBattles: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field rating_battles", values[i]) + } else if value.Valid { + as.RatingBattles = int(value.Int64) + } + case accountsnapshot.FieldRatingFrame: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field rating_frame", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &as.RatingFrame); err != nil { + return fmt.Errorf("unmarshal field rating_frame: %w", err) + } + } + case accountsnapshot.FieldRegularBattles: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field regular_battles", values[i]) + } else if value.Valid { + as.RegularBattles = int(value.Int64) + } + case accountsnapshot.FieldRegularFrame: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field regular_frame", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &as.RegularFrame); err != nil { + return fmt.Errorf("unmarshal field regular_frame: %w", err) + } + } + default: + as.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the AccountSnapshot. +// This includes values selected through modifiers, order, etc. +func (as *AccountSnapshot) Value(name string) (ent.Value, error) { + return as.selectValues.Get(name) +} + +// QueryAccount queries the "account" edge of the AccountSnapshot entity. +func (as *AccountSnapshot) QueryAccount() *AccountQuery { + return NewAccountSnapshotClient(as.config).QueryAccount(as) +} + +// Update returns a builder for updating this AccountSnapshot. +// Note that you need to call AccountSnapshot.Unwrap() before calling this method if this AccountSnapshot +// was returned from a transaction, and the transaction was committed or rolled back. +func (as *AccountSnapshot) Update() *AccountSnapshotUpdateOne { + return NewAccountSnapshotClient(as.config).UpdateOne(as) +} + +// Unwrap unwraps the AccountSnapshot entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (as *AccountSnapshot) Unwrap() *AccountSnapshot { + _tx, ok := as.config.driver.(*txDriver) + if !ok { + panic("db: AccountSnapshot is not a transactional entity") + } + as.config.driver = _tx.drv + return as +} + +// String implements the fmt.Stringer. +func (as *AccountSnapshot) String() string { + var builder strings.Builder + builder.WriteString("AccountSnapshot(") + builder.WriteString(fmt.Sprintf("id=%v, ", as.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", as.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", as.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("type=") + builder.WriteString(fmt.Sprintf("%v", as.Type)) + builder.WriteString(", ") + builder.WriteString("last_battle_time=") + builder.WriteString(fmt.Sprintf("%v", as.LastBattleTime)) + builder.WriteString(", ") + builder.WriteString("account_id=") + builder.WriteString(as.AccountID) + builder.WriteString(", ") + builder.WriteString("reference_id=") + builder.WriteString(as.ReferenceID) + builder.WriteString(", ") + builder.WriteString("rating_battles=") + builder.WriteString(fmt.Sprintf("%v", as.RatingBattles)) + builder.WriteString(", ") + builder.WriteString("rating_frame=") + builder.WriteString(fmt.Sprintf("%v", as.RatingFrame)) + builder.WriteString(", ") + builder.WriteString("regular_battles=") + builder.WriteString(fmt.Sprintf("%v", as.RegularBattles)) + builder.WriteString(", ") + builder.WriteString("regular_frame=") + builder.WriteString(fmt.Sprintf("%v", as.RegularFrame)) + builder.WriteByte(')') + return builder.String() +} + +// AccountSnapshots is a parsable slice of AccountSnapshot. +type AccountSnapshots []*AccountSnapshot diff --git a/internal/database/ent/db/accountsnapshot/accountsnapshot.go b/internal/database/ent/db/accountsnapshot/accountsnapshot.go new file mode 100644 index 00000000..d99d867d --- /dev/null +++ b/internal/database/ent/db/accountsnapshot/accountsnapshot.go @@ -0,0 +1,161 @@ +// Code generated by ent, DO NOT EDIT. + +package accountsnapshot + +import ( + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/models" +) + +const ( + // Label holds the string label denoting the accountsnapshot type in the database. + Label = "account_snapshot" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldLastBattleTime holds the string denoting the last_battle_time field in the database. + FieldLastBattleTime = "last_battle_time" + // FieldAccountID holds the string denoting the account_id field in the database. + FieldAccountID = "account_id" + // FieldReferenceID holds the string denoting the reference_id field in the database. + FieldReferenceID = "reference_id" + // FieldRatingBattles holds the string denoting the rating_battles field in the database. + FieldRatingBattles = "rating_battles" + // FieldRatingFrame holds the string denoting the rating_frame field in the database. + FieldRatingFrame = "rating_frame" + // FieldRegularBattles holds the string denoting the regular_battles field in the database. + FieldRegularBattles = "regular_battles" + // FieldRegularFrame holds the string denoting the regular_frame field in the database. + FieldRegularFrame = "regular_frame" + // EdgeAccount holds the string denoting the account edge name in mutations. + EdgeAccount = "account" + // Table holds the table name of the accountsnapshot in the database. + Table = "account_snapshots" + // AccountTable is the table that holds the account relation/edge. + AccountTable = "account_snapshots" + // AccountInverseTable is the table name for the Account entity. + // It exists in this package in order to avoid circular dependency with the "account" package. + AccountInverseTable = "accounts" + // AccountColumn is the table column denoting the account relation/edge. + AccountColumn = "account_id" +) + +// Columns holds all SQL columns for accountsnapshot fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldType, + FieldLastBattleTime, + FieldAccountID, + FieldReferenceID, + FieldRatingBattles, + FieldRatingFrame, + FieldRegularBattles, + FieldRegularFrame, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. + AccountIDValidator func(string) error + // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + ReferenceIDValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save. +func TypeValidator(_type models.SnapshotType) error { + switch _type { + case "live", "daily": + return nil + default: + return fmt.Errorf("accountsnapshot: invalid enum value for type field: %q", _type) + } +} + +// OrderOption defines the ordering options for the AccountSnapshot queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByLastBattleTime orders the results by the last_battle_time field. +func ByLastBattleTime(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLastBattleTime, opts...).ToFunc() +} + +// ByAccountID orders the results by the account_id field. +func ByAccountID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAccountID, opts...).ToFunc() +} + +// ByReferenceID orders the results by the reference_id field. +func ByReferenceID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReferenceID, opts...).ToFunc() +} + +// ByRatingBattles orders the results by the rating_battles field. +func ByRatingBattles(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRatingBattles, opts...).ToFunc() +} + +// ByRegularBattles orders the results by the regular_battles field. +func ByRegularBattles(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRegularBattles, opts...).ToFunc() +} + +// ByAccountField orders the results by account field. +func ByAccountField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newAccountStep(), sql.OrderByField(field, opts...)) + } +} +func newAccountStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(AccountInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, AccountTable, AccountColumn), + ) +} diff --git a/internal/database/ent/db/accountsnapshot/where.go b/internal/database/ent/db/accountsnapshot/where.go new file mode 100644 index 00000000..16101ea9 --- /dev/null +++ b/internal/database/ent/db/accountsnapshot/where.go @@ -0,0 +1,498 @@ +// Code generated by ent, DO NOT EDIT. + +package accountsnapshot + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. +func LastBattleTime(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) +} + +// AccountID applies equality check predicate on the "account_id" field. It's identical to AccountIDEQ. +func AccountID(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldAccountID, v)) +} + +// ReferenceID applies equality check predicate on the "reference_id" field. It's identical to ReferenceIDEQ. +func ReferenceID(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldReferenceID, v)) +} + +// RatingBattles applies equality check predicate on the "rating_battles" field. It's identical to RatingBattlesEQ. +func RatingBattles(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldRatingBattles, v)) +} + +// RegularBattles applies equality check predicate on the "regular_battles" field. It's identical to RegularBattlesEQ. +func RegularBattles(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldRegularBattles, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v models.SnapshotType) predicate.AccountSnapshot { + vc := v + return predicate.AccountSnapshot(sql.FieldEQ(FieldType, vc)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v models.SnapshotType) predicate.AccountSnapshot { + vc := v + return predicate.AccountSnapshot(sql.FieldNEQ(FieldType, vc)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...models.SnapshotType) predicate.AccountSnapshot { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.AccountSnapshot(sql.FieldIn(FieldType, v...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...models.SnapshotType) predicate.AccountSnapshot { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.AccountSnapshot(sql.FieldNotIn(FieldType, v...)) +} + +// LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. +func LastBattleTimeEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) +} + +// LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. +func LastBattleTimeNEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNEQ(FieldLastBattleTime, v)) +} + +// LastBattleTimeIn applies the In predicate on the "last_battle_time" field. +func LastBattleTimeIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldIn(FieldLastBattleTime, vs...)) +} + +// LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. +func LastBattleTimeNotIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNotIn(FieldLastBattleTime, vs...)) +} + +// LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. +func LastBattleTimeGT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGT(FieldLastBattleTime, v)) +} + +// LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. +func LastBattleTimeGTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGTE(FieldLastBattleTime, v)) +} + +// LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. +func LastBattleTimeLT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLT(FieldLastBattleTime, v)) +} + +// LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. +func LastBattleTimeLTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLTE(FieldLastBattleTime, v)) +} + +// AccountIDEQ applies the EQ predicate on the "account_id" field. +func AccountIDEQ(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldAccountID, v)) +} + +// AccountIDNEQ applies the NEQ predicate on the "account_id" field. +func AccountIDNEQ(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNEQ(FieldAccountID, v)) +} + +// AccountIDIn applies the In predicate on the "account_id" field. +func AccountIDIn(vs ...string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldIn(FieldAccountID, vs...)) +} + +// AccountIDNotIn applies the NotIn predicate on the "account_id" field. +func AccountIDNotIn(vs ...string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNotIn(FieldAccountID, vs...)) +} + +// AccountIDGT applies the GT predicate on the "account_id" field. +func AccountIDGT(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGT(FieldAccountID, v)) +} + +// AccountIDGTE applies the GTE predicate on the "account_id" field. +func AccountIDGTE(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGTE(FieldAccountID, v)) +} + +// AccountIDLT applies the LT predicate on the "account_id" field. +func AccountIDLT(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLT(FieldAccountID, v)) +} + +// AccountIDLTE applies the LTE predicate on the "account_id" field. +func AccountIDLTE(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLTE(FieldAccountID, v)) +} + +// AccountIDContains applies the Contains predicate on the "account_id" field. +func AccountIDContains(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldContains(FieldAccountID, v)) +} + +// AccountIDHasPrefix applies the HasPrefix predicate on the "account_id" field. +func AccountIDHasPrefix(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldHasPrefix(FieldAccountID, v)) +} + +// AccountIDHasSuffix applies the HasSuffix predicate on the "account_id" field. +func AccountIDHasSuffix(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldHasSuffix(FieldAccountID, v)) +} + +// AccountIDEqualFold applies the EqualFold predicate on the "account_id" field. +func AccountIDEqualFold(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEqualFold(FieldAccountID, v)) +} + +// AccountIDContainsFold applies the ContainsFold predicate on the "account_id" field. +func AccountIDContainsFold(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldContainsFold(FieldAccountID, v)) +} + +// ReferenceIDEQ applies the EQ predicate on the "reference_id" field. +func ReferenceIDEQ(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldReferenceID, v)) +} + +// ReferenceIDNEQ applies the NEQ predicate on the "reference_id" field. +func ReferenceIDNEQ(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNEQ(FieldReferenceID, v)) +} + +// ReferenceIDIn applies the In predicate on the "reference_id" field. +func ReferenceIDIn(vs ...string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldIn(FieldReferenceID, vs...)) +} + +// ReferenceIDNotIn applies the NotIn predicate on the "reference_id" field. +func ReferenceIDNotIn(vs ...string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNotIn(FieldReferenceID, vs...)) +} + +// ReferenceIDGT applies the GT predicate on the "reference_id" field. +func ReferenceIDGT(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGT(FieldReferenceID, v)) +} + +// ReferenceIDGTE applies the GTE predicate on the "reference_id" field. +func ReferenceIDGTE(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGTE(FieldReferenceID, v)) +} + +// ReferenceIDLT applies the LT predicate on the "reference_id" field. +func ReferenceIDLT(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLT(FieldReferenceID, v)) +} + +// ReferenceIDLTE applies the LTE predicate on the "reference_id" field. +func ReferenceIDLTE(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLTE(FieldReferenceID, v)) +} + +// ReferenceIDContains applies the Contains predicate on the "reference_id" field. +func ReferenceIDContains(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldContains(FieldReferenceID, v)) +} + +// ReferenceIDHasPrefix applies the HasPrefix predicate on the "reference_id" field. +func ReferenceIDHasPrefix(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldHasPrefix(FieldReferenceID, v)) +} + +// ReferenceIDHasSuffix applies the HasSuffix predicate on the "reference_id" field. +func ReferenceIDHasSuffix(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldHasSuffix(FieldReferenceID, v)) +} + +// ReferenceIDEqualFold applies the EqualFold predicate on the "reference_id" field. +func ReferenceIDEqualFold(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEqualFold(FieldReferenceID, v)) +} + +// ReferenceIDContainsFold applies the ContainsFold predicate on the "reference_id" field. +func ReferenceIDContainsFold(v string) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldContainsFold(FieldReferenceID, v)) +} + +// RatingBattlesEQ applies the EQ predicate on the "rating_battles" field. +func RatingBattlesEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldRatingBattles, v)) +} + +// RatingBattlesNEQ applies the NEQ predicate on the "rating_battles" field. +func RatingBattlesNEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNEQ(FieldRatingBattles, v)) +} + +// RatingBattlesIn applies the In predicate on the "rating_battles" field. +func RatingBattlesIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldIn(FieldRatingBattles, vs...)) +} + +// RatingBattlesNotIn applies the NotIn predicate on the "rating_battles" field. +func RatingBattlesNotIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNotIn(FieldRatingBattles, vs...)) +} + +// RatingBattlesGT applies the GT predicate on the "rating_battles" field. +func RatingBattlesGT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGT(FieldRatingBattles, v)) +} + +// RatingBattlesGTE applies the GTE predicate on the "rating_battles" field. +func RatingBattlesGTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGTE(FieldRatingBattles, v)) +} + +// RatingBattlesLT applies the LT predicate on the "rating_battles" field. +func RatingBattlesLT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLT(FieldRatingBattles, v)) +} + +// RatingBattlesLTE applies the LTE predicate on the "rating_battles" field. +func RatingBattlesLTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLTE(FieldRatingBattles, v)) +} + +// RegularBattlesEQ applies the EQ predicate on the "regular_battles" field. +func RegularBattlesEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldEQ(FieldRegularBattles, v)) +} + +// RegularBattlesNEQ applies the NEQ predicate on the "regular_battles" field. +func RegularBattlesNEQ(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNEQ(FieldRegularBattles, v)) +} + +// RegularBattlesIn applies the In predicate on the "regular_battles" field. +func RegularBattlesIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldIn(FieldRegularBattles, vs...)) +} + +// RegularBattlesNotIn applies the NotIn predicate on the "regular_battles" field. +func RegularBattlesNotIn(vs ...int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldNotIn(FieldRegularBattles, vs...)) +} + +// RegularBattlesGT applies the GT predicate on the "regular_battles" field. +func RegularBattlesGT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGT(FieldRegularBattles, v)) +} + +// RegularBattlesGTE applies the GTE predicate on the "regular_battles" field. +func RegularBattlesGTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldGTE(FieldRegularBattles, v)) +} + +// RegularBattlesLT applies the LT predicate on the "regular_battles" field. +func RegularBattlesLT(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLT(FieldRegularBattles, v)) +} + +// RegularBattlesLTE applies the LTE predicate on the "regular_battles" field. +func RegularBattlesLTE(v int) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.FieldLTE(FieldRegularBattles, v)) +} + +// HasAccount applies the HasEdge predicate on the "account" edge. +func HasAccount() predicate.AccountSnapshot { + return predicate.AccountSnapshot(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, AccountTable, AccountColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasAccountWith applies the HasEdge predicate on the "account" edge with a given conditions (other predicates). +func HasAccountWith(preds ...predicate.Account) predicate.AccountSnapshot { + return predicate.AccountSnapshot(func(s *sql.Selector) { + step := newAccountStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.AccountSnapshot) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.AccountSnapshot) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.AccountSnapshot) predicate.AccountSnapshot { + return predicate.AccountSnapshot(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/accountsnapshot_create.go b/internal/database/ent/db/accountsnapshot_create.go new file mode 100644 index 00000000..c4059901 --- /dev/null +++ b/internal/database/ent/db/accountsnapshot_create.go @@ -0,0 +1,392 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// AccountSnapshotCreate is the builder for creating a AccountSnapshot entity. +type AccountSnapshotCreate struct { + config + mutation *AccountSnapshotMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (asc *AccountSnapshotCreate) SetCreatedAt(i int) *AccountSnapshotCreate { + asc.mutation.SetCreatedAt(i) + return asc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (asc *AccountSnapshotCreate) SetNillableCreatedAt(i *int) *AccountSnapshotCreate { + if i != nil { + asc.SetCreatedAt(*i) + } + return asc +} + +// SetUpdatedAt sets the "updated_at" field. +func (asc *AccountSnapshotCreate) SetUpdatedAt(i int) *AccountSnapshotCreate { + asc.mutation.SetUpdatedAt(i) + return asc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (asc *AccountSnapshotCreate) SetNillableUpdatedAt(i *int) *AccountSnapshotCreate { + if i != nil { + asc.SetUpdatedAt(*i) + } + return asc +} + +// SetType sets the "type" field. +func (asc *AccountSnapshotCreate) SetType(mt models.SnapshotType) *AccountSnapshotCreate { + asc.mutation.SetType(mt) + return asc +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (asc *AccountSnapshotCreate) SetLastBattleTime(i int) *AccountSnapshotCreate { + asc.mutation.SetLastBattleTime(i) + return asc +} + +// SetAccountID sets the "account_id" field. +func (asc *AccountSnapshotCreate) SetAccountID(s string) *AccountSnapshotCreate { + asc.mutation.SetAccountID(s) + return asc +} + +// SetReferenceID sets the "reference_id" field. +func (asc *AccountSnapshotCreate) SetReferenceID(s string) *AccountSnapshotCreate { + asc.mutation.SetReferenceID(s) + return asc +} + +// SetRatingBattles sets the "rating_battles" field. +func (asc *AccountSnapshotCreate) SetRatingBattles(i int) *AccountSnapshotCreate { + asc.mutation.SetRatingBattles(i) + return asc +} + +// SetRatingFrame sets the "rating_frame" field. +func (asc *AccountSnapshotCreate) SetRatingFrame(ff frame.StatsFrame) *AccountSnapshotCreate { + asc.mutation.SetRatingFrame(ff) + return asc +} + +// SetRegularBattles sets the "regular_battles" field. +func (asc *AccountSnapshotCreate) SetRegularBattles(i int) *AccountSnapshotCreate { + asc.mutation.SetRegularBattles(i) + return asc +} + +// SetRegularFrame sets the "regular_frame" field. +func (asc *AccountSnapshotCreate) SetRegularFrame(ff frame.StatsFrame) *AccountSnapshotCreate { + asc.mutation.SetRegularFrame(ff) + return asc +} + +// SetID sets the "id" field. +func (asc *AccountSnapshotCreate) SetID(s string) *AccountSnapshotCreate { + asc.mutation.SetID(s) + return asc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (asc *AccountSnapshotCreate) SetNillableID(s *string) *AccountSnapshotCreate { + if s != nil { + asc.SetID(*s) + } + return asc +} + +// SetAccount sets the "account" edge to the Account entity. +func (asc *AccountSnapshotCreate) SetAccount(a *Account) *AccountSnapshotCreate { + return asc.SetAccountID(a.ID) +} + +// Mutation returns the AccountSnapshotMutation object of the builder. +func (asc *AccountSnapshotCreate) Mutation() *AccountSnapshotMutation { + return asc.mutation +} + +// Save creates the AccountSnapshot in the database. +func (asc *AccountSnapshotCreate) Save(ctx context.Context) (*AccountSnapshot, error) { + asc.defaults() + return withHooks(ctx, asc.sqlSave, asc.mutation, asc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (asc *AccountSnapshotCreate) SaveX(ctx context.Context) *AccountSnapshot { + v, err := asc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (asc *AccountSnapshotCreate) Exec(ctx context.Context) error { + _, err := asc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (asc *AccountSnapshotCreate) ExecX(ctx context.Context) { + if err := asc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (asc *AccountSnapshotCreate) defaults() { + if _, ok := asc.mutation.CreatedAt(); !ok { + v := accountsnapshot.DefaultCreatedAt() + asc.mutation.SetCreatedAt(v) + } + if _, ok := asc.mutation.UpdatedAt(); !ok { + v := accountsnapshot.DefaultUpdatedAt() + asc.mutation.SetUpdatedAt(v) + } + if _, ok := asc.mutation.ID(); !ok { + v := accountsnapshot.DefaultID() + asc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (asc *AccountSnapshotCreate) check() error { + if _, ok := asc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "AccountSnapshot.created_at"`)} + } + if _, ok := asc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "AccountSnapshot.updated_at"`)} + } + if _, ok := asc.mutation.GetType(); !ok { + return &ValidationError{Name: "type", err: errors.New(`db: missing required field "AccountSnapshot.type"`)} + } + if v, ok := asc.mutation.GetType(); ok { + if err := accountsnapshot.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "AccountSnapshot.type": %w`, err)} + } + } + if _, ok := asc.mutation.LastBattleTime(); !ok { + return &ValidationError{Name: "last_battle_time", err: errors.New(`db: missing required field "AccountSnapshot.last_battle_time"`)} + } + if _, ok := asc.mutation.AccountID(); !ok { + return &ValidationError{Name: "account_id", err: errors.New(`db: missing required field "AccountSnapshot.account_id"`)} + } + if v, ok := asc.mutation.AccountID(); ok { + if err := accountsnapshot.AccountIDValidator(v); err != nil { + return &ValidationError{Name: "account_id", err: fmt.Errorf(`db: validator failed for field "AccountSnapshot.account_id": %w`, err)} + } + } + if _, ok := asc.mutation.ReferenceID(); !ok { + return &ValidationError{Name: "reference_id", err: errors.New(`db: missing required field "AccountSnapshot.reference_id"`)} + } + if v, ok := asc.mutation.ReferenceID(); ok { + if err := accountsnapshot.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "AccountSnapshot.reference_id": %w`, err)} + } + } + if _, ok := asc.mutation.RatingBattles(); !ok { + return &ValidationError{Name: "rating_battles", err: errors.New(`db: missing required field "AccountSnapshot.rating_battles"`)} + } + if _, ok := asc.mutation.RatingFrame(); !ok { + return &ValidationError{Name: "rating_frame", err: errors.New(`db: missing required field "AccountSnapshot.rating_frame"`)} + } + if _, ok := asc.mutation.RegularBattles(); !ok { + return &ValidationError{Name: "regular_battles", err: errors.New(`db: missing required field "AccountSnapshot.regular_battles"`)} + } + if _, ok := asc.mutation.RegularFrame(); !ok { + return &ValidationError{Name: "regular_frame", err: errors.New(`db: missing required field "AccountSnapshot.regular_frame"`)} + } + if _, ok := asc.mutation.AccountID(); !ok { + return &ValidationError{Name: "account", err: errors.New(`db: missing required edge "AccountSnapshot.account"`)} + } + return nil +} + +func (asc *AccountSnapshotCreate) sqlSave(ctx context.Context) (*AccountSnapshot, error) { + if err := asc.check(); err != nil { + return nil, err + } + _node, _spec := asc.createSpec() + if err := sqlgraph.CreateNode(ctx, asc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected AccountSnapshot.ID type: %T", _spec.ID.Value) + } + } + asc.mutation.id = &_node.ID + asc.mutation.done = true + return _node, nil +} + +func (asc *AccountSnapshotCreate) createSpec() (*AccountSnapshot, *sqlgraph.CreateSpec) { + var ( + _node = &AccountSnapshot{config: asc.config} + _spec = sqlgraph.NewCreateSpec(accountsnapshot.Table, sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString)) + ) + if id, ok := asc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := asc.mutation.CreatedAt(); ok { + _spec.SetField(accountsnapshot.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := asc.mutation.UpdatedAt(); ok { + _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := asc.mutation.GetType(); ok { + _spec.SetField(accountsnapshot.FieldType, field.TypeEnum, value) + _node.Type = value + } + if value, ok := asc.mutation.LastBattleTime(); ok { + _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + _node.LastBattleTime = value + } + if value, ok := asc.mutation.ReferenceID(); ok { + _spec.SetField(accountsnapshot.FieldReferenceID, field.TypeString, value) + _node.ReferenceID = value + } + if value, ok := asc.mutation.RatingBattles(); ok { + _spec.SetField(accountsnapshot.FieldRatingBattles, field.TypeInt, value) + _node.RatingBattles = value + } + if value, ok := asc.mutation.RatingFrame(); ok { + _spec.SetField(accountsnapshot.FieldRatingFrame, field.TypeJSON, value) + _node.RatingFrame = value + } + if value, ok := asc.mutation.RegularBattles(); ok { + _spec.SetField(accountsnapshot.FieldRegularBattles, field.TypeInt, value) + _node.RegularBattles = value + } + if value, ok := asc.mutation.RegularFrame(); ok { + _spec.SetField(accountsnapshot.FieldRegularFrame, field.TypeJSON, value) + _node.RegularFrame = value + } + if nodes := asc.mutation.AccountIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: accountsnapshot.AccountTable, + Columns: []string{accountsnapshot.AccountColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.AccountID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// AccountSnapshotCreateBulk is the builder for creating many AccountSnapshot entities in bulk. +type AccountSnapshotCreateBulk struct { + config + err error + builders []*AccountSnapshotCreate +} + +// Save creates the AccountSnapshot entities in the database. +func (ascb *AccountSnapshotCreateBulk) Save(ctx context.Context) ([]*AccountSnapshot, error) { + if ascb.err != nil { + return nil, ascb.err + } + specs := make([]*sqlgraph.CreateSpec, len(ascb.builders)) + nodes := make([]*AccountSnapshot, len(ascb.builders)) + mutators := make([]Mutator, len(ascb.builders)) + for i := range ascb.builders { + func(i int, root context.Context) { + builder := ascb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AccountSnapshotMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, ascb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, ascb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, ascb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (ascb *AccountSnapshotCreateBulk) SaveX(ctx context.Context) []*AccountSnapshot { + v, err := ascb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ascb *AccountSnapshotCreateBulk) Exec(ctx context.Context) error { + _, err := ascb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ascb *AccountSnapshotCreateBulk) ExecX(ctx context.Context) { + if err := ascb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/accountsnapshot_delete.go b/internal/database/ent/db/accountsnapshot_delete.go new file mode 100644 index 00000000..b218aa4c --- /dev/null +++ b/internal/database/ent/db/accountsnapshot_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AccountSnapshotDelete is the builder for deleting a AccountSnapshot entity. +type AccountSnapshotDelete struct { + config + hooks []Hook + mutation *AccountSnapshotMutation +} + +// Where appends a list predicates to the AccountSnapshotDelete builder. +func (asd *AccountSnapshotDelete) Where(ps ...predicate.AccountSnapshot) *AccountSnapshotDelete { + asd.mutation.Where(ps...) + return asd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (asd *AccountSnapshotDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, asd.sqlExec, asd.mutation, asd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (asd *AccountSnapshotDelete) ExecX(ctx context.Context) int { + n, err := asd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (asd *AccountSnapshotDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(accountsnapshot.Table, sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString)) + if ps := asd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, asd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + asd.mutation.done = true + return affected, err +} + +// AccountSnapshotDeleteOne is the builder for deleting a single AccountSnapshot entity. +type AccountSnapshotDeleteOne struct { + asd *AccountSnapshotDelete +} + +// Where appends a list predicates to the AccountSnapshotDelete builder. +func (asdo *AccountSnapshotDeleteOne) Where(ps ...predicate.AccountSnapshot) *AccountSnapshotDeleteOne { + asdo.asd.mutation.Where(ps...) + return asdo +} + +// Exec executes the deletion query. +func (asdo *AccountSnapshotDeleteOne) Exec(ctx context.Context) error { + n, err := asdo.asd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{accountsnapshot.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (asdo *AccountSnapshotDeleteOne) ExecX(ctx context.Context) { + if err := asdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/accountsnapshot_query.go b/internal/database/ent/db/accountsnapshot_query.go new file mode 100644 index 00000000..af3bf5f9 --- /dev/null +++ b/internal/database/ent/db/accountsnapshot_query.go @@ -0,0 +1,605 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AccountSnapshotQuery is the builder for querying AccountSnapshot entities. +type AccountSnapshotQuery struct { + config + ctx *QueryContext + order []accountsnapshot.OrderOption + inters []Interceptor + predicates []predicate.AccountSnapshot + withAccount *AccountQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AccountSnapshotQuery builder. +func (asq *AccountSnapshotQuery) Where(ps ...predicate.AccountSnapshot) *AccountSnapshotQuery { + asq.predicates = append(asq.predicates, ps...) + return asq +} + +// Limit the number of records to be returned by this query. +func (asq *AccountSnapshotQuery) Limit(limit int) *AccountSnapshotQuery { + asq.ctx.Limit = &limit + return asq +} + +// Offset to start from. +func (asq *AccountSnapshotQuery) Offset(offset int) *AccountSnapshotQuery { + asq.ctx.Offset = &offset + return asq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (asq *AccountSnapshotQuery) Unique(unique bool) *AccountSnapshotQuery { + asq.ctx.Unique = &unique + return asq +} + +// Order specifies how the records should be ordered. +func (asq *AccountSnapshotQuery) Order(o ...accountsnapshot.OrderOption) *AccountSnapshotQuery { + asq.order = append(asq.order, o...) + return asq +} + +// QueryAccount chains the current query on the "account" edge. +func (asq *AccountSnapshotQuery) QueryAccount() *AccountQuery { + query := (&AccountClient{config: asq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := asq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := asq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(accountsnapshot.Table, accountsnapshot.FieldID, selector), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, accountsnapshot.AccountTable, accountsnapshot.AccountColumn), + ) + fromU = sqlgraph.SetNeighbors(asq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first AccountSnapshot entity from the query. +// Returns a *NotFoundError when no AccountSnapshot was found. +func (asq *AccountSnapshotQuery) First(ctx context.Context) (*AccountSnapshot, error) { + nodes, err := asq.Limit(1).All(setContextOp(ctx, asq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{accountsnapshot.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (asq *AccountSnapshotQuery) FirstX(ctx context.Context) *AccountSnapshot { + node, err := asq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first AccountSnapshot ID from the query. +// Returns a *NotFoundError when no AccountSnapshot ID was found. +func (asq *AccountSnapshotQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = asq.Limit(1).IDs(setContextOp(ctx, asq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{accountsnapshot.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (asq *AccountSnapshotQuery) FirstIDX(ctx context.Context) string { + id, err := asq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single AccountSnapshot entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one AccountSnapshot entity is found. +// Returns a *NotFoundError when no AccountSnapshot entities are found. +func (asq *AccountSnapshotQuery) Only(ctx context.Context) (*AccountSnapshot, error) { + nodes, err := asq.Limit(2).All(setContextOp(ctx, asq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{accountsnapshot.Label} + default: + return nil, &NotSingularError{accountsnapshot.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (asq *AccountSnapshotQuery) OnlyX(ctx context.Context) *AccountSnapshot { + node, err := asq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only AccountSnapshot ID in the query. +// Returns a *NotSingularError when more than one AccountSnapshot ID is found. +// Returns a *NotFoundError when no entities are found. +func (asq *AccountSnapshotQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = asq.Limit(2).IDs(setContextOp(ctx, asq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{accountsnapshot.Label} + default: + err = &NotSingularError{accountsnapshot.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (asq *AccountSnapshotQuery) OnlyIDX(ctx context.Context) string { + id, err := asq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of AccountSnapshots. +func (asq *AccountSnapshotQuery) All(ctx context.Context) ([]*AccountSnapshot, error) { + ctx = setContextOp(ctx, asq.ctx, "All") + if err := asq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*AccountSnapshot, *AccountSnapshotQuery]() + return withInterceptors[[]*AccountSnapshot](ctx, asq, qr, asq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (asq *AccountSnapshotQuery) AllX(ctx context.Context) []*AccountSnapshot { + nodes, err := asq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of AccountSnapshot IDs. +func (asq *AccountSnapshotQuery) IDs(ctx context.Context) (ids []string, err error) { + if asq.ctx.Unique == nil && asq.path != nil { + asq.Unique(true) + } + ctx = setContextOp(ctx, asq.ctx, "IDs") + if err = asq.Select(accountsnapshot.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (asq *AccountSnapshotQuery) IDsX(ctx context.Context) []string { + ids, err := asq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (asq *AccountSnapshotQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, asq.ctx, "Count") + if err := asq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, asq, querierCount[*AccountSnapshotQuery](), asq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (asq *AccountSnapshotQuery) CountX(ctx context.Context) int { + count, err := asq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (asq *AccountSnapshotQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, asq.ctx, "Exist") + switch _, err := asq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (asq *AccountSnapshotQuery) ExistX(ctx context.Context) bool { + exist, err := asq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AccountSnapshotQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (asq *AccountSnapshotQuery) Clone() *AccountSnapshotQuery { + if asq == nil { + return nil + } + return &AccountSnapshotQuery{ + config: asq.config, + ctx: asq.ctx.Clone(), + order: append([]accountsnapshot.OrderOption{}, asq.order...), + inters: append([]Interceptor{}, asq.inters...), + predicates: append([]predicate.AccountSnapshot{}, asq.predicates...), + withAccount: asq.withAccount.Clone(), + // clone intermediate query. + sql: asq.sql.Clone(), + path: asq.path, + } +} + +// WithAccount tells the query-builder to eager-load the nodes that are connected to +// the "account" edge. The optional arguments are used to configure the query builder of the edge. +func (asq *AccountSnapshotQuery) WithAccount(opts ...func(*AccountQuery)) *AccountSnapshotQuery { + query := (&AccountClient{config: asq.config}).Query() + for _, opt := range opts { + opt(query) + } + asq.withAccount = query + return asq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.AccountSnapshot.Query(). +// GroupBy(accountsnapshot.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (asq *AccountSnapshotQuery) GroupBy(field string, fields ...string) *AccountSnapshotGroupBy { + asq.ctx.Fields = append([]string{field}, fields...) + grbuild := &AccountSnapshotGroupBy{build: asq} + grbuild.flds = &asq.ctx.Fields + grbuild.label = accountsnapshot.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.AccountSnapshot.Query(). +// Select(accountsnapshot.FieldCreatedAt). +// Scan(ctx, &v) +func (asq *AccountSnapshotQuery) Select(fields ...string) *AccountSnapshotSelect { + asq.ctx.Fields = append(asq.ctx.Fields, fields...) + sbuild := &AccountSnapshotSelect{AccountSnapshotQuery: asq} + sbuild.label = accountsnapshot.Label + sbuild.flds, sbuild.scan = &asq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a AccountSnapshotSelect configured with the given aggregations. +func (asq *AccountSnapshotQuery) Aggregate(fns ...AggregateFunc) *AccountSnapshotSelect { + return asq.Select().Aggregate(fns...) +} + +func (asq *AccountSnapshotQuery) prepareQuery(ctx context.Context) error { + for _, inter := range asq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, asq); err != nil { + return err + } + } + } + for _, f := range asq.ctx.Fields { + if !accountsnapshot.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if asq.path != nil { + prev, err := asq.path(ctx) + if err != nil { + return err + } + asq.sql = prev + } + return nil +} + +func (asq *AccountSnapshotQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AccountSnapshot, error) { + var ( + nodes = []*AccountSnapshot{} + _spec = asq.querySpec() + loadedTypes = [1]bool{ + asq.withAccount != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*AccountSnapshot).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &AccountSnapshot{config: asq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, asq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := asq.withAccount; query != nil { + if err := asq.loadAccount(ctx, query, nodes, nil, + func(n *AccountSnapshot, e *Account) { n.Edges.Account = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (asq *AccountSnapshotQuery) loadAccount(ctx context.Context, query *AccountQuery, nodes []*AccountSnapshot, init func(*AccountSnapshot), assign func(*AccountSnapshot, *Account)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*AccountSnapshot) + for i := range nodes { + fk := nodes[i].AccountID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(account.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "account_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (asq *AccountSnapshotQuery) sqlCount(ctx context.Context) (int, error) { + _spec := asq.querySpec() + _spec.Node.Columns = asq.ctx.Fields + if len(asq.ctx.Fields) > 0 { + _spec.Unique = asq.ctx.Unique != nil && *asq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, asq.driver, _spec) +} + +func (asq *AccountSnapshotQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(accountsnapshot.Table, accountsnapshot.Columns, sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString)) + _spec.From = asq.sql + if unique := asq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if asq.path != nil { + _spec.Unique = true + } + if fields := asq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, accountsnapshot.FieldID) + for i := range fields { + if fields[i] != accountsnapshot.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if asq.withAccount != nil { + _spec.Node.AddColumnOnce(accountsnapshot.FieldAccountID) + } + } + if ps := asq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := asq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := asq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := asq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (asq *AccountSnapshotQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(asq.driver.Dialect()) + t1 := builder.Table(accountsnapshot.Table) + columns := asq.ctx.Fields + if len(columns) == 0 { + columns = accountsnapshot.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if asq.sql != nil { + selector = asq.sql + selector.Select(selector.Columns(columns...)...) + } + if asq.ctx.Unique != nil && *asq.ctx.Unique { + selector.Distinct() + } + for _, p := range asq.predicates { + p(selector) + } + for _, p := range asq.order { + p(selector) + } + if offset := asq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := asq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// AccountSnapshotGroupBy is the group-by builder for AccountSnapshot entities. +type AccountSnapshotGroupBy struct { + selector + build *AccountSnapshotQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (asgb *AccountSnapshotGroupBy) Aggregate(fns ...AggregateFunc) *AccountSnapshotGroupBy { + asgb.fns = append(asgb.fns, fns...) + return asgb +} + +// Scan applies the selector query and scans the result into the given value. +func (asgb *AccountSnapshotGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, asgb.build.ctx, "GroupBy") + if err := asgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AccountSnapshotQuery, *AccountSnapshotGroupBy](ctx, asgb.build, asgb, asgb.build.inters, v) +} + +func (asgb *AccountSnapshotGroupBy) sqlScan(ctx context.Context, root *AccountSnapshotQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(asgb.fns)) + for _, fn := range asgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*asgb.flds)+len(asgb.fns)) + for _, f := range *asgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*asgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := asgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// AccountSnapshotSelect is the builder for selecting fields of AccountSnapshot entities. +type AccountSnapshotSelect struct { + *AccountSnapshotQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ass *AccountSnapshotSelect) Aggregate(fns ...AggregateFunc) *AccountSnapshotSelect { + ass.fns = append(ass.fns, fns...) + return ass +} + +// Scan applies the selector query and scans the result into the given value. +func (ass *AccountSnapshotSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ass.ctx, "Select") + if err := ass.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AccountSnapshotQuery, *AccountSnapshotSelect](ctx, ass.AccountSnapshotQuery, ass, ass.inters, v) +} + +func (ass *AccountSnapshotSelect) sqlScan(ctx context.Context, root *AccountSnapshotQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(ass.fns)) + for _, fn := range ass.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*ass.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ass.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/accountsnapshot_update.go b/internal/database/ent/db/accountsnapshot_update.go new file mode 100644 index 00000000..0d1dcb15 --- /dev/null +++ b/internal/database/ent/db/accountsnapshot_update.go @@ -0,0 +1,573 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// AccountSnapshotUpdate is the builder for updating AccountSnapshot entities. +type AccountSnapshotUpdate struct { + config + hooks []Hook + mutation *AccountSnapshotMutation +} + +// Where appends a list predicates to the AccountSnapshotUpdate builder. +func (asu *AccountSnapshotUpdate) Where(ps ...predicate.AccountSnapshot) *AccountSnapshotUpdate { + asu.mutation.Where(ps...) + return asu +} + +// SetUpdatedAt sets the "updated_at" field. +func (asu *AccountSnapshotUpdate) SetUpdatedAt(i int) *AccountSnapshotUpdate { + asu.mutation.ResetUpdatedAt() + asu.mutation.SetUpdatedAt(i) + return asu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (asu *AccountSnapshotUpdate) AddUpdatedAt(i int) *AccountSnapshotUpdate { + asu.mutation.AddUpdatedAt(i) + return asu +} + +// SetType sets the "type" field. +func (asu *AccountSnapshotUpdate) SetType(mt models.SnapshotType) *AccountSnapshotUpdate { + asu.mutation.SetType(mt) + return asu +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (asu *AccountSnapshotUpdate) SetNillableType(mt *models.SnapshotType) *AccountSnapshotUpdate { + if mt != nil { + asu.SetType(*mt) + } + return asu +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (asu *AccountSnapshotUpdate) SetLastBattleTime(i int) *AccountSnapshotUpdate { + asu.mutation.ResetLastBattleTime() + asu.mutation.SetLastBattleTime(i) + return asu +} + +// SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. +func (asu *AccountSnapshotUpdate) SetNillableLastBattleTime(i *int) *AccountSnapshotUpdate { + if i != nil { + asu.SetLastBattleTime(*i) + } + return asu +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (asu *AccountSnapshotUpdate) AddLastBattleTime(i int) *AccountSnapshotUpdate { + asu.mutation.AddLastBattleTime(i) + return asu +} + +// SetReferenceID sets the "reference_id" field. +func (asu *AccountSnapshotUpdate) SetReferenceID(s string) *AccountSnapshotUpdate { + asu.mutation.SetReferenceID(s) + return asu +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (asu *AccountSnapshotUpdate) SetNillableReferenceID(s *string) *AccountSnapshotUpdate { + if s != nil { + asu.SetReferenceID(*s) + } + return asu +} + +// SetRatingBattles sets the "rating_battles" field. +func (asu *AccountSnapshotUpdate) SetRatingBattles(i int) *AccountSnapshotUpdate { + asu.mutation.ResetRatingBattles() + asu.mutation.SetRatingBattles(i) + return asu +} + +// SetNillableRatingBattles sets the "rating_battles" field if the given value is not nil. +func (asu *AccountSnapshotUpdate) SetNillableRatingBattles(i *int) *AccountSnapshotUpdate { + if i != nil { + asu.SetRatingBattles(*i) + } + return asu +} + +// AddRatingBattles adds i to the "rating_battles" field. +func (asu *AccountSnapshotUpdate) AddRatingBattles(i int) *AccountSnapshotUpdate { + asu.mutation.AddRatingBattles(i) + return asu +} + +// SetRatingFrame sets the "rating_frame" field. +func (asu *AccountSnapshotUpdate) SetRatingFrame(ff frame.StatsFrame) *AccountSnapshotUpdate { + asu.mutation.SetRatingFrame(ff) + return asu +} + +// SetNillableRatingFrame sets the "rating_frame" field if the given value is not nil. +func (asu *AccountSnapshotUpdate) SetNillableRatingFrame(ff *frame.StatsFrame) *AccountSnapshotUpdate { + if ff != nil { + asu.SetRatingFrame(*ff) + } + return asu +} + +// SetRegularBattles sets the "regular_battles" field. +func (asu *AccountSnapshotUpdate) SetRegularBattles(i int) *AccountSnapshotUpdate { + asu.mutation.ResetRegularBattles() + asu.mutation.SetRegularBattles(i) + return asu +} + +// SetNillableRegularBattles sets the "regular_battles" field if the given value is not nil. +func (asu *AccountSnapshotUpdate) SetNillableRegularBattles(i *int) *AccountSnapshotUpdate { + if i != nil { + asu.SetRegularBattles(*i) + } + return asu +} + +// AddRegularBattles adds i to the "regular_battles" field. +func (asu *AccountSnapshotUpdate) AddRegularBattles(i int) *AccountSnapshotUpdate { + asu.mutation.AddRegularBattles(i) + return asu +} + +// SetRegularFrame sets the "regular_frame" field. +func (asu *AccountSnapshotUpdate) SetRegularFrame(ff frame.StatsFrame) *AccountSnapshotUpdate { + asu.mutation.SetRegularFrame(ff) + return asu +} + +// SetNillableRegularFrame sets the "regular_frame" field if the given value is not nil. +func (asu *AccountSnapshotUpdate) SetNillableRegularFrame(ff *frame.StatsFrame) *AccountSnapshotUpdate { + if ff != nil { + asu.SetRegularFrame(*ff) + } + return asu +} + +// Mutation returns the AccountSnapshotMutation object of the builder. +func (asu *AccountSnapshotUpdate) Mutation() *AccountSnapshotMutation { + return asu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (asu *AccountSnapshotUpdate) Save(ctx context.Context) (int, error) { + asu.defaults() + return withHooks(ctx, asu.sqlSave, asu.mutation, asu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (asu *AccountSnapshotUpdate) SaveX(ctx context.Context) int { + affected, err := asu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (asu *AccountSnapshotUpdate) Exec(ctx context.Context) error { + _, err := asu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (asu *AccountSnapshotUpdate) ExecX(ctx context.Context) { + if err := asu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (asu *AccountSnapshotUpdate) defaults() { + if _, ok := asu.mutation.UpdatedAt(); !ok { + v := accountsnapshot.UpdateDefaultUpdatedAt() + asu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (asu *AccountSnapshotUpdate) check() error { + if v, ok := asu.mutation.GetType(); ok { + if err := accountsnapshot.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "AccountSnapshot.type": %w`, err)} + } + } + if v, ok := asu.mutation.ReferenceID(); ok { + if err := accountsnapshot.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "AccountSnapshot.reference_id": %w`, err)} + } + } + if _, ok := asu.mutation.AccountID(); asu.mutation.AccountCleared() && !ok { + return errors.New(`db: clearing a required unique edge "AccountSnapshot.account"`) + } + return nil +} + +func (asu *AccountSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := asu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(accountsnapshot.Table, accountsnapshot.Columns, sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString)) + if ps := asu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := asu.mutation.UpdatedAt(); ok { + _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := asu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := asu.mutation.GetType(); ok { + _spec.SetField(accountsnapshot.FieldType, field.TypeEnum, value) + } + if value, ok := asu.mutation.LastBattleTime(); ok { + _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := asu.mutation.AddedLastBattleTime(); ok { + _spec.AddField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := asu.mutation.ReferenceID(); ok { + _spec.SetField(accountsnapshot.FieldReferenceID, field.TypeString, value) + } + if value, ok := asu.mutation.RatingBattles(); ok { + _spec.SetField(accountsnapshot.FieldRatingBattles, field.TypeInt, value) + } + if value, ok := asu.mutation.AddedRatingBattles(); ok { + _spec.AddField(accountsnapshot.FieldRatingBattles, field.TypeInt, value) + } + if value, ok := asu.mutation.RatingFrame(); ok { + _spec.SetField(accountsnapshot.FieldRatingFrame, field.TypeJSON, value) + } + if value, ok := asu.mutation.RegularBattles(); ok { + _spec.SetField(accountsnapshot.FieldRegularBattles, field.TypeInt, value) + } + if value, ok := asu.mutation.AddedRegularBattles(); ok { + _spec.AddField(accountsnapshot.FieldRegularBattles, field.TypeInt, value) + } + if value, ok := asu.mutation.RegularFrame(); ok { + _spec.SetField(accountsnapshot.FieldRegularFrame, field.TypeJSON, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, asu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{accountsnapshot.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + asu.mutation.done = true + return n, nil +} + +// AccountSnapshotUpdateOne is the builder for updating a single AccountSnapshot entity. +type AccountSnapshotUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AccountSnapshotMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (asuo *AccountSnapshotUpdateOne) SetUpdatedAt(i int) *AccountSnapshotUpdateOne { + asuo.mutation.ResetUpdatedAt() + asuo.mutation.SetUpdatedAt(i) + return asuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (asuo *AccountSnapshotUpdateOne) AddUpdatedAt(i int) *AccountSnapshotUpdateOne { + asuo.mutation.AddUpdatedAt(i) + return asuo +} + +// SetType sets the "type" field. +func (asuo *AccountSnapshotUpdateOne) SetType(mt models.SnapshotType) *AccountSnapshotUpdateOne { + asuo.mutation.SetType(mt) + return asuo +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (asuo *AccountSnapshotUpdateOne) SetNillableType(mt *models.SnapshotType) *AccountSnapshotUpdateOne { + if mt != nil { + asuo.SetType(*mt) + } + return asuo +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (asuo *AccountSnapshotUpdateOne) SetLastBattleTime(i int) *AccountSnapshotUpdateOne { + asuo.mutation.ResetLastBattleTime() + asuo.mutation.SetLastBattleTime(i) + return asuo +} + +// SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. +func (asuo *AccountSnapshotUpdateOne) SetNillableLastBattleTime(i *int) *AccountSnapshotUpdateOne { + if i != nil { + asuo.SetLastBattleTime(*i) + } + return asuo +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (asuo *AccountSnapshotUpdateOne) AddLastBattleTime(i int) *AccountSnapshotUpdateOne { + asuo.mutation.AddLastBattleTime(i) + return asuo +} + +// SetReferenceID sets the "reference_id" field. +func (asuo *AccountSnapshotUpdateOne) SetReferenceID(s string) *AccountSnapshotUpdateOne { + asuo.mutation.SetReferenceID(s) + return asuo +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (asuo *AccountSnapshotUpdateOne) SetNillableReferenceID(s *string) *AccountSnapshotUpdateOne { + if s != nil { + asuo.SetReferenceID(*s) + } + return asuo +} + +// SetRatingBattles sets the "rating_battles" field. +func (asuo *AccountSnapshotUpdateOne) SetRatingBattles(i int) *AccountSnapshotUpdateOne { + asuo.mutation.ResetRatingBattles() + asuo.mutation.SetRatingBattles(i) + return asuo +} + +// SetNillableRatingBattles sets the "rating_battles" field if the given value is not nil. +func (asuo *AccountSnapshotUpdateOne) SetNillableRatingBattles(i *int) *AccountSnapshotUpdateOne { + if i != nil { + asuo.SetRatingBattles(*i) + } + return asuo +} + +// AddRatingBattles adds i to the "rating_battles" field. +func (asuo *AccountSnapshotUpdateOne) AddRatingBattles(i int) *AccountSnapshotUpdateOne { + asuo.mutation.AddRatingBattles(i) + return asuo +} + +// SetRatingFrame sets the "rating_frame" field. +func (asuo *AccountSnapshotUpdateOne) SetRatingFrame(ff frame.StatsFrame) *AccountSnapshotUpdateOne { + asuo.mutation.SetRatingFrame(ff) + return asuo +} + +// SetNillableRatingFrame sets the "rating_frame" field if the given value is not nil. +func (asuo *AccountSnapshotUpdateOne) SetNillableRatingFrame(ff *frame.StatsFrame) *AccountSnapshotUpdateOne { + if ff != nil { + asuo.SetRatingFrame(*ff) + } + return asuo +} + +// SetRegularBattles sets the "regular_battles" field. +func (asuo *AccountSnapshotUpdateOne) SetRegularBattles(i int) *AccountSnapshotUpdateOne { + asuo.mutation.ResetRegularBattles() + asuo.mutation.SetRegularBattles(i) + return asuo +} + +// SetNillableRegularBattles sets the "regular_battles" field if the given value is not nil. +func (asuo *AccountSnapshotUpdateOne) SetNillableRegularBattles(i *int) *AccountSnapshotUpdateOne { + if i != nil { + asuo.SetRegularBattles(*i) + } + return asuo +} + +// AddRegularBattles adds i to the "regular_battles" field. +func (asuo *AccountSnapshotUpdateOne) AddRegularBattles(i int) *AccountSnapshotUpdateOne { + asuo.mutation.AddRegularBattles(i) + return asuo +} + +// SetRegularFrame sets the "regular_frame" field. +func (asuo *AccountSnapshotUpdateOne) SetRegularFrame(ff frame.StatsFrame) *AccountSnapshotUpdateOne { + asuo.mutation.SetRegularFrame(ff) + return asuo +} + +// SetNillableRegularFrame sets the "regular_frame" field if the given value is not nil. +func (asuo *AccountSnapshotUpdateOne) SetNillableRegularFrame(ff *frame.StatsFrame) *AccountSnapshotUpdateOne { + if ff != nil { + asuo.SetRegularFrame(*ff) + } + return asuo +} + +// Mutation returns the AccountSnapshotMutation object of the builder. +func (asuo *AccountSnapshotUpdateOne) Mutation() *AccountSnapshotMutation { + return asuo.mutation +} + +// Where appends a list predicates to the AccountSnapshotUpdate builder. +func (asuo *AccountSnapshotUpdateOne) Where(ps ...predicate.AccountSnapshot) *AccountSnapshotUpdateOne { + asuo.mutation.Where(ps...) + return asuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (asuo *AccountSnapshotUpdateOne) Select(field string, fields ...string) *AccountSnapshotUpdateOne { + asuo.fields = append([]string{field}, fields...) + return asuo +} + +// Save executes the query and returns the updated AccountSnapshot entity. +func (asuo *AccountSnapshotUpdateOne) Save(ctx context.Context) (*AccountSnapshot, error) { + asuo.defaults() + return withHooks(ctx, asuo.sqlSave, asuo.mutation, asuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (asuo *AccountSnapshotUpdateOne) SaveX(ctx context.Context) *AccountSnapshot { + node, err := asuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (asuo *AccountSnapshotUpdateOne) Exec(ctx context.Context) error { + _, err := asuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (asuo *AccountSnapshotUpdateOne) ExecX(ctx context.Context) { + if err := asuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (asuo *AccountSnapshotUpdateOne) defaults() { + if _, ok := asuo.mutation.UpdatedAt(); !ok { + v := accountsnapshot.UpdateDefaultUpdatedAt() + asuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (asuo *AccountSnapshotUpdateOne) check() error { + if v, ok := asuo.mutation.GetType(); ok { + if err := accountsnapshot.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "AccountSnapshot.type": %w`, err)} + } + } + if v, ok := asuo.mutation.ReferenceID(); ok { + if err := accountsnapshot.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "AccountSnapshot.reference_id": %w`, err)} + } + } + if _, ok := asuo.mutation.AccountID(); asuo.mutation.AccountCleared() && !ok { + return errors.New(`db: clearing a required unique edge "AccountSnapshot.account"`) + } + return nil +} + +func (asuo *AccountSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *AccountSnapshot, err error) { + if err := asuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(accountsnapshot.Table, accountsnapshot.Columns, sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString)) + id, ok := asuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "AccountSnapshot.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := asuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, accountsnapshot.FieldID) + for _, f := range fields { + if !accountsnapshot.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != accountsnapshot.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := asuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := asuo.mutation.UpdatedAt(); ok { + _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := asuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := asuo.mutation.GetType(); ok { + _spec.SetField(accountsnapshot.FieldType, field.TypeEnum, value) + } + if value, ok := asuo.mutation.LastBattleTime(); ok { + _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := asuo.mutation.AddedLastBattleTime(); ok { + _spec.AddField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := asuo.mutation.ReferenceID(); ok { + _spec.SetField(accountsnapshot.FieldReferenceID, field.TypeString, value) + } + if value, ok := asuo.mutation.RatingBattles(); ok { + _spec.SetField(accountsnapshot.FieldRatingBattles, field.TypeInt, value) + } + if value, ok := asuo.mutation.AddedRatingBattles(); ok { + _spec.AddField(accountsnapshot.FieldRatingBattles, field.TypeInt, value) + } + if value, ok := asuo.mutation.RatingFrame(); ok { + _spec.SetField(accountsnapshot.FieldRatingFrame, field.TypeJSON, value) + } + if value, ok := asuo.mutation.RegularBattles(); ok { + _spec.SetField(accountsnapshot.FieldRegularBattles, field.TypeInt, value) + } + if value, ok := asuo.mutation.AddedRegularBattles(); ok { + _spec.AddField(accountsnapshot.FieldRegularBattles, field.TypeInt, value) + } + if value, ok := asuo.mutation.RegularFrame(); ok { + _spec.SetField(accountsnapshot.FieldRegularFrame, field.TypeJSON, value) + } + _node = &AccountSnapshot{config: asuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, asuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{accountsnapshot.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + asuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/achievementssnapshot.go b/internal/database/ent/db/achievementssnapshot.go new file mode 100644 index 00000000..b26ece54 --- /dev/null +++ b/internal/database/ent/db/achievementssnapshot.go @@ -0,0 +1,216 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/am-wg-proxy-next/v2/types" +) + +// AchievementsSnapshot is the model entity for the AchievementsSnapshot schema. +type AchievementsSnapshot struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Type holds the value of the "type" field. + Type models.SnapshotType `json:"type,omitempty"` + // AccountID holds the value of the "account_id" field. + AccountID string `json:"account_id,omitempty"` + // ReferenceID holds the value of the "reference_id" field. + ReferenceID string `json:"reference_id,omitempty"` + // Battles holds the value of the "battles" field. + Battles int `json:"battles,omitempty"` + // LastBattleTime holds the value of the "last_battle_time" field. + LastBattleTime int `json:"last_battle_time,omitempty"` + // Data holds the value of the "data" field. + Data types.AchievementsFrame `json:"data,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the AchievementsSnapshotQuery when eager-loading is set. + Edges AchievementsSnapshotEdges `json:"edges"` + selectValues sql.SelectValues +} + +// AchievementsSnapshotEdges holds the relations/edges for other nodes in the graph. +type AchievementsSnapshotEdges struct { + // Account holds the value of the account edge. + Account *Account `json:"account,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// AccountOrErr returns the Account value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e AchievementsSnapshotEdges) AccountOrErr() (*Account, error) { + if e.Account != nil { + return e.Account, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: account.Label} + } + return nil, &NotLoadedError{edge: "account"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*AchievementsSnapshot) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case achievementssnapshot.FieldData: + values[i] = new([]byte) + case achievementssnapshot.FieldCreatedAt, achievementssnapshot.FieldUpdatedAt, achievementssnapshot.FieldBattles, achievementssnapshot.FieldLastBattleTime: + values[i] = new(sql.NullInt64) + case achievementssnapshot.FieldID, achievementssnapshot.FieldType, achievementssnapshot.FieldAccountID, achievementssnapshot.FieldReferenceID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the AchievementsSnapshot fields. +func (as *AchievementsSnapshot) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case achievementssnapshot.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + as.ID = value.String + } + case achievementssnapshot.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + as.CreatedAt = int(value.Int64) + } + case achievementssnapshot.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + as.UpdatedAt = int(value.Int64) + } + case achievementssnapshot.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + as.Type = models.SnapshotType(value.String) + } + case achievementssnapshot.FieldAccountID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field account_id", values[i]) + } else if value.Valid { + as.AccountID = value.String + } + case achievementssnapshot.FieldReferenceID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reference_id", values[i]) + } else if value.Valid { + as.ReferenceID = value.String + } + case achievementssnapshot.FieldBattles: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field battles", values[i]) + } else if value.Valid { + as.Battles = int(value.Int64) + } + case achievementssnapshot.FieldLastBattleTime: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) + } else if value.Valid { + as.LastBattleTime = int(value.Int64) + } + case achievementssnapshot.FieldData: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field data", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &as.Data); err != nil { + return fmt.Errorf("unmarshal field data: %w", err) + } + } + default: + as.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the AchievementsSnapshot. +// This includes values selected through modifiers, order, etc. +func (as *AchievementsSnapshot) Value(name string) (ent.Value, error) { + return as.selectValues.Get(name) +} + +// QueryAccount queries the "account" edge of the AchievementsSnapshot entity. +func (as *AchievementsSnapshot) QueryAccount() *AccountQuery { + return NewAchievementsSnapshotClient(as.config).QueryAccount(as) +} + +// Update returns a builder for updating this AchievementsSnapshot. +// Note that you need to call AchievementsSnapshot.Unwrap() before calling this method if this AchievementsSnapshot +// was returned from a transaction, and the transaction was committed or rolled back. +func (as *AchievementsSnapshot) Update() *AchievementsSnapshotUpdateOne { + return NewAchievementsSnapshotClient(as.config).UpdateOne(as) +} + +// Unwrap unwraps the AchievementsSnapshot entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (as *AchievementsSnapshot) Unwrap() *AchievementsSnapshot { + _tx, ok := as.config.driver.(*txDriver) + if !ok { + panic("db: AchievementsSnapshot is not a transactional entity") + } + as.config.driver = _tx.drv + return as +} + +// String implements the fmt.Stringer. +func (as *AchievementsSnapshot) String() string { + var builder strings.Builder + builder.WriteString("AchievementsSnapshot(") + builder.WriteString(fmt.Sprintf("id=%v, ", as.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", as.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", as.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("type=") + builder.WriteString(fmt.Sprintf("%v", as.Type)) + builder.WriteString(", ") + builder.WriteString("account_id=") + builder.WriteString(as.AccountID) + builder.WriteString(", ") + builder.WriteString("reference_id=") + builder.WriteString(as.ReferenceID) + builder.WriteString(", ") + builder.WriteString("battles=") + builder.WriteString(fmt.Sprintf("%v", as.Battles)) + builder.WriteString(", ") + builder.WriteString("last_battle_time=") + builder.WriteString(fmt.Sprintf("%v", as.LastBattleTime)) + builder.WriteString(", ") + builder.WriteString("data=") + builder.WriteString(fmt.Sprintf("%v", as.Data)) + builder.WriteByte(')') + return builder.String() +} + +// AchievementsSnapshots is a parsable slice of AchievementsSnapshot. +type AchievementsSnapshots []*AchievementsSnapshot diff --git a/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go b/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go new file mode 100644 index 00000000..e6c64d4f --- /dev/null +++ b/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go @@ -0,0 +1,150 @@ +// Code generated by ent, DO NOT EDIT. + +package achievementssnapshot + +import ( + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/models" +) + +const ( + // Label holds the string label denoting the achievementssnapshot type in the database. + Label = "achievements_snapshot" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldAccountID holds the string denoting the account_id field in the database. + FieldAccountID = "account_id" + // FieldReferenceID holds the string denoting the reference_id field in the database. + FieldReferenceID = "reference_id" + // FieldBattles holds the string denoting the battles field in the database. + FieldBattles = "battles" + // FieldLastBattleTime holds the string denoting the last_battle_time field in the database. + FieldLastBattleTime = "last_battle_time" + // FieldData holds the string denoting the data field in the database. + FieldData = "data" + // EdgeAccount holds the string denoting the account edge name in mutations. + EdgeAccount = "account" + // Table holds the table name of the achievementssnapshot in the database. + Table = "achievements_snapshots" + // AccountTable is the table that holds the account relation/edge. + AccountTable = "achievements_snapshots" + // AccountInverseTable is the table name for the Account entity. + // It exists in this package in order to avoid circular dependency with the "account" package. + AccountInverseTable = "accounts" + // AccountColumn is the table column denoting the account relation/edge. + AccountColumn = "account_id" +) + +// Columns holds all SQL columns for achievementssnapshot fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldType, + FieldAccountID, + FieldReferenceID, + FieldBattles, + FieldLastBattleTime, + FieldData, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. + AccountIDValidator func(string) error + // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + ReferenceIDValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save. +func TypeValidator(_type models.SnapshotType) error { + switch _type { + case "live", "daily": + return nil + default: + return fmt.Errorf("achievementssnapshot: invalid enum value for type field: %q", _type) + } +} + +// OrderOption defines the ordering options for the AchievementsSnapshot queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByAccountID orders the results by the account_id field. +func ByAccountID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAccountID, opts...).ToFunc() +} + +// ByReferenceID orders the results by the reference_id field. +func ByReferenceID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReferenceID, opts...).ToFunc() +} + +// ByBattles orders the results by the battles field. +func ByBattles(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldBattles, opts...).ToFunc() +} + +// ByLastBattleTime orders the results by the last_battle_time field. +func ByLastBattleTime(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLastBattleTime, opts...).ToFunc() +} + +// ByAccountField orders the results by account field. +func ByAccountField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newAccountStep(), sql.OrderByField(field, opts...)) + } +} +func newAccountStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(AccountInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, AccountTable, AccountColumn), + ) +} diff --git a/internal/database/ent/db/achievementssnapshot/where.go b/internal/database/ent/db/achievementssnapshot/where.go new file mode 100644 index 00000000..d7b2035f --- /dev/null +++ b/internal/database/ent/db/achievementssnapshot/where.go @@ -0,0 +1,453 @@ +// Code generated by ent, DO NOT EDIT. + +package achievementssnapshot + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// AccountID applies equality check predicate on the "account_id" field. It's identical to AccountIDEQ. +func AccountID(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldAccountID, v)) +} + +// ReferenceID applies equality check predicate on the "reference_id" field. It's identical to ReferenceIDEQ. +func ReferenceID(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldReferenceID, v)) +} + +// Battles applies equality check predicate on the "battles" field. It's identical to BattlesEQ. +func Battles(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldBattles, v)) +} + +// LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. +func LastBattleTime(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v models.SnapshotType) predicate.AchievementsSnapshot { + vc := v + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldType, vc)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v models.SnapshotType) predicate.AchievementsSnapshot { + vc := v + return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldType, vc)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...models.SnapshotType) predicate.AchievementsSnapshot { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.AchievementsSnapshot(sql.FieldIn(FieldType, v...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...models.SnapshotType) predicate.AchievementsSnapshot { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldType, v...)) +} + +// AccountIDEQ applies the EQ predicate on the "account_id" field. +func AccountIDEQ(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldAccountID, v)) +} + +// AccountIDNEQ applies the NEQ predicate on the "account_id" field. +func AccountIDNEQ(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldAccountID, v)) +} + +// AccountIDIn applies the In predicate on the "account_id" field. +func AccountIDIn(vs ...string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldIn(FieldAccountID, vs...)) +} + +// AccountIDNotIn applies the NotIn predicate on the "account_id" field. +func AccountIDNotIn(vs ...string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldAccountID, vs...)) +} + +// AccountIDGT applies the GT predicate on the "account_id" field. +func AccountIDGT(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGT(FieldAccountID, v)) +} + +// AccountIDGTE applies the GTE predicate on the "account_id" field. +func AccountIDGTE(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGTE(FieldAccountID, v)) +} + +// AccountIDLT applies the LT predicate on the "account_id" field. +func AccountIDLT(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLT(FieldAccountID, v)) +} + +// AccountIDLTE applies the LTE predicate on the "account_id" field. +func AccountIDLTE(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLTE(FieldAccountID, v)) +} + +// AccountIDContains applies the Contains predicate on the "account_id" field. +func AccountIDContains(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldContains(FieldAccountID, v)) +} + +// AccountIDHasPrefix applies the HasPrefix predicate on the "account_id" field. +func AccountIDHasPrefix(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldHasPrefix(FieldAccountID, v)) +} + +// AccountIDHasSuffix applies the HasSuffix predicate on the "account_id" field. +func AccountIDHasSuffix(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldHasSuffix(FieldAccountID, v)) +} + +// AccountIDEqualFold applies the EqualFold predicate on the "account_id" field. +func AccountIDEqualFold(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEqualFold(FieldAccountID, v)) +} + +// AccountIDContainsFold applies the ContainsFold predicate on the "account_id" field. +func AccountIDContainsFold(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldContainsFold(FieldAccountID, v)) +} + +// ReferenceIDEQ applies the EQ predicate on the "reference_id" field. +func ReferenceIDEQ(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldReferenceID, v)) +} + +// ReferenceIDNEQ applies the NEQ predicate on the "reference_id" field. +func ReferenceIDNEQ(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldReferenceID, v)) +} + +// ReferenceIDIn applies the In predicate on the "reference_id" field. +func ReferenceIDIn(vs ...string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldIn(FieldReferenceID, vs...)) +} + +// ReferenceIDNotIn applies the NotIn predicate on the "reference_id" field. +func ReferenceIDNotIn(vs ...string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldReferenceID, vs...)) +} + +// ReferenceIDGT applies the GT predicate on the "reference_id" field. +func ReferenceIDGT(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGT(FieldReferenceID, v)) +} + +// ReferenceIDGTE applies the GTE predicate on the "reference_id" field. +func ReferenceIDGTE(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGTE(FieldReferenceID, v)) +} + +// ReferenceIDLT applies the LT predicate on the "reference_id" field. +func ReferenceIDLT(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLT(FieldReferenceID, v)) +} + +// ReferenceIDLTE applies the LTE predicate on the "reference_id" field. +func ReferenceIDLTE(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLTE(FieldReferenceID, v)) +} + +// ReferenceIDContains applies the Contains predicate on the "reference_id" field. +func ReferenceIDContains(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldContains(FieldReferenceID, v)) +} + +// ReferenceIDHasPrefix applies the HasPrefix predicate on the "reference_id" field. +func ReferenceIDHasPrefix(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldHasPrefix(FieldReferenceID, v)) +} + +// ReferenceIDHasSuffix applies the HasSuffix predicate on the "reference_id" field. +func ReferenceIDHasSuffix(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldHasSuffix(FieldReferenceID, v)) +} + +// ReferenceIDEqualFold applies the EqualFold predicate on the "reference_id" field. +func ReferenceIDEqualFold(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEqualFold(FieldReferenceID, v)) +} + +// ReferenceIDContainsFold applies the ContainsFold predicate on the "reference_id" field. +func ReferenceIDContainsFold(v string) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldContainsFold(FieldReferenceID, v)) +} + +// BattlesEQ applies the EQ predicate on the "battles" field. +func BattlesEQ(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldBattles, v)) +} + +// BattlesNEQ applies the NEQ predicate on the "battles" field. +func BattlesNEQ(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldBattles, v)) +} + +// BattlesIn applies the In predicate on the "battles" field. +func BattlesIn(vs ...int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldIn(FieldBattles, vs...)) +} + +// BattlesNotIn applies the NotIn predicate on the "battles" field. +func BattlesNotIn(vs ...int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldBattles, vs...)) +} + +// BattlesGT applies the GT predicate on the "battles" field. +func BattlesGT(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGT(FieldBattles, v)) +} + +// BattlesGTE applies the GTE predicate on the "battles" field. +func BattlesGTE(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGTE(FieldBattles, v)) +} + +// BattlesLT applies the LT predicate on the "battles" field. +func BattlesLT(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLT(FieldBattles, v)) +} + +// BattlesLTE applies the LTE predicate on the "battles" field. +func BattlesLTE(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLTE(FieldBattles, v)) +} + +// LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. +func LastBattleTimeEQ(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) +} + +// LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. +func LastBattleTimeNEQ(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldLastBattleTime, v)) +} + +// LastBattleTimeIn applies the In predicate on the "last_battle_time" field. +func LastBattleTimeIn(vs ...int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldIn(FieldLastBattleTime, vs...)) +} + +// LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. +func LastBattleTimeNotIn(vs ...int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldLastBattleTime, vs...)) +} + +// LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. +func LastBattleTimeGT(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGT(FieldLastBattleTime, v)) +} + +// LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. +func LastBattleTimeGTE(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldGTE(FieldLastBattleTime, v)) +} + +// LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. +func LastBattleTimeLT(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLT(FieldLastBattleTime, v)) +} + +// LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. +func LastBattleTimeLTE(v int) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.FieldLTE(FieldLastBattleTime, v)) +} + +// HasAccount applies the HasEdge predicate on the "account" edge. +func HasAccount() predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, AccountTable, AccountColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasAccountWith applies the HasEdge predicate on the "account" edge with a given conditions (other predicates). +func HasAccountWith(preds ...predicate.Account) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(func(s *sql.Selector) { + step := newAccountStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.AchievementsSnapshot) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.AchievementsSnapshot) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.AchievementsSnapshot) predicate.AchievementsSnapshot { + return predicate.AchievementsSnapshot(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/achievementssnapshot_create.go b/internal/database/ent/db/achievementssnapshot_create.go new file mode 100644 index 00000000..26b95982 --- /dev/null +++ b/internal/database/ent/db/achievementssnapshot_create.go @@ -0,0 +1,366 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/am-wg-proxy-next/v2/types" +) + +// AchievementsSnapshotCreate is the builder for creating a AchievementsSnapshot entity. +type AchievementsSnapshotCreate struct { + config + mutation *AchievementsSnapshotMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (asc *AchievementsSnapshotCreate) SetCreatedAt(i int) *AchievementsSnapshotCreate { + asc.mutation.SetCreatedAt(i) + return asc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (asc *AchievementsSnapshotCreate) SetNillableCreatedAt(i *int) *AchievementsSnapshotCreate { + if i != nil { + asc.SetCreatedAt(*i) + } + return asc +} + +// SetUpdatedAt sets the "updated_at" field. +func (asc *AchievementsSnapshotCreate) SetUpdatedAt(i int) *AchievementsSnapshotCreate { + asc.mutation.SetUpdatedAt(i) + return asc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (asc *AchievementsSnapshotCreate) SetNillableUpdatedAt(i *int) *AchievementsSnapshotCreate { + if i != nil { + asc.SetUpdatedAt(*i) + } + return asc +} + +// SetType sets the "type" field. +func (asc *AchievementsSnapshotCreate) SetType(mt models.SnapshotType) *AchievementsSnapshotCreate { + asc.mutation.SetType(mt) + return asc +} + +// SetAccountID sets the "account_id" field. +func (asc *AchievementsSnapshotCreate) SetAccountID(s string) *AchievementsSnapshotCreate { + asc.mutation.SetAccountID(s) + return asc +} + +// SetReferenceID sets the "reference_id" field. +func (asc *AchievementsSnapshotCreate) SetReferenceID(s string) *AchievementsSnapshotCreate { + asc.mutation.SetReferenceID(s) + return asc +} + +// SetBattles sets the "battles" field. +func (asc *AchievementsSnapshotCreate) SetBattles(i int) *AchievementsSnapshotCreate { + asc.mutation.SetBattles(i) + return asc +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (asc *AchievementsSnapshotCreate) SetLastBattleTime(i int) *AchievementsSnapshotCreate { + asc.mutation.SetLastBattleTime(i) + return asc +} + +// SetData sets the "data" field. +func (asc *AchievementsSnapshotCreate) SetData(tf types.AchievementsFrame) *AchievementsSnapshotCreate { + asc.mutation.SetData(tf) + return asc +} + +// SetID sets the "id" field. +func (asc *AchievementsSnapshotCreate) SetID(s string) *AchievementsSnapshotCreate { + asc.mutation.SetID(s) + return asc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (asc *AchievementsSnapshotCreate) SetNillableID(s *string) *AchievementsSnapshotCreate { + if s != nil { + asc.SetID(*s) + } + return asc +} + +// SetAccount sets the "account" edge to the Account entity. +func (asc *AchievementsSnapshotCreate) SetAccount(a *Account) *AchievementsSnapshotCreate { + return asc.SetAccountID(a.ID) +} + +// Mutation returns the AchievementsSnapshotMutation object of the builder. +func (asc *AchievementsSnapshotCreate) Mutation() *AchievementsSnapshotMutation { + return asc.mutation +} + +// Save creates the AchievementsSnapshot in the database. +func (asc *AchievementsSnapshotCreate) Save(ctx context.Context) (*AchievementsSnapshot, error) { + asc.defaults() + return withHooks(ctx, asc.sqlSave, asc.mutation, asc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (asc *AchievementsSnapshotCreate) SaveX(ctx context.Context) *AchievementsSnapshot { + v, err := asc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (asc *AchievementsSnapshotCreate) Exec(ctx context.Context) error { + _, err := asc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (asc *AchievementsSnapshotCreate) ExecX(ctx context.Context) { + if err := asc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (asc *AchievementsSnapshotCreate) defaults() { + if _, ok := asc.mutation.CreatedAt(); !ok { + v := achievementssnapshot.DefaultCreatedAt() + asc.mutation.SetCreatedAt(v) + } + if _, ok := asc.mutation.UpdatedAt(); !ok { + v := achievementssnapshot.DefaultUpdatedAt() + asc.mutation.SetUpdatedAt(v) + } + if _, ok := asc.mutation.ID(); !ok { + v := achievementssnapshot.DefaultID() + asc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (asc *AchievementsSnapshotCreate) check() error { + if _, ok := asc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "AchievementsSnapshot.created_at"`)} + } + if _, ok := asc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "AchievementsSnapshot.updated_at"`)} + } + if _, ok := asc.mutation.GetType(); !ok { + return &ValidationError{Name: "type", err: errors.New(`db: missing required field "AchievementsSnapshot.type"`)} + } + if v, ok := asc.mutation.GetType(); ok { + if err := achievementssnapshot.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "AchievementsSnapshot.type": %w`, err)} + } + } + if _, ok := asc.mutation.AccountID(); !ok { + return &ValidationError{Name: "account_id", err: errors.New(`db: missing required field "AchievementsSnapshot.account_id"`)} + } + if v, ok := asc.mutation.AccountID(); ok { + if err := achievementssnapshot.AccountIDValidator(v); err != nil { + return &ValidationError{Name: "account_id", err: fmt.Errorf(`db: validator failed for field "AchievementsSnapshot.account_id": %w`, err)} + } + } + if _, ok := asc.mutation.ReferenceID(); !ok { + return &ValidationError{Name: "reference_id", err: errors.New(`db: missing required field "AchievementsSnapshot.reference_id"`)} + } + if v, ok := asc.mutation.ReferenceID(); ok { + if err := achievementssnapshot.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "AchievementsSnapshot.reference_id": %w`, err)} + } + } + if _, ok := asc.mutation.Battles(); !ok { + return &ValidationError{Name: "battles", err: errors.New(`db: missing required field "AchievementsSnapshot.battles"`)} + } + if _, ok := asc.mutation.LastBattleTime(); !ok { + return &ValidationError{Name: "last_battle_time", err: errors.New(`db: missing required field "AchievementsSnapshot.last_battle_time"`)} + } + if _, ok := asc.mutation.Data(); !ok { + return &ValidationError{Name: "data", err: errors.New(`db: missing required field "AchievementsSnapshot.data"`)} + } + if _, ok := asc.mutation.AccountID(); !ok { + return &ValidationError{Name: "account", err: errors.New(`db: missing required edge "AchievementsSnapshot.account"`)} + } + return nil +} + +func (asc *AchievementsSnapshotCreate) sqlSave(ctx context.Context) (*AchievementsSnapshot, error) { + if err := asc.check(); err != nil { + return nil, err + } + _node, _spec := asc.createSpec() + if err := sqlgraph.CreateNode(ctx, asc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected AchievementsSnapshot.ID type: %T", _spec.ID.Value) + } + } + asc.mutation.id = &_node.ID + asc.mutation.done = true + return _node, nil +} + +func (asc *AchievementsSnapshotCreate) createSpec() (*AchievementsSnapshot, *sqlgraph.CreateSpec) { + var ( + _node = &AchievementsSnapshot{config: asc.config} + _spec = sqlgraph.NewCreateSpec(achievementssnapshot.Table, sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString)) + ) + if id, ok := asc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := asc.mutation.CreatedAt(); ok { + _spec.SetField(achievementssnapshot.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := asc.mutation.UpdatedAt(); ok { + _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := asc.mutation.GetType(); ok { + _spec.SetField(achievementssnapshot.FieldType, field.TypeEnum, value) + _node.Type = value + } + if value, ok := asc.mutation.ReferenceID(); ok { + _spec.SetField(achievementssnapshot.FieldReferenceID, field.TypeString, value) + _node.ReferenceID = value + } + if value, ok := asc.mutation.Battles(); ok { + _spec.SetField(achievementssnapshot.FieldBattles, field.TypeInt, value) + _node.Battles = value + } + if value, ok := asc.mutation.LastBattleTime(); ok { + _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + _node.LastBattleTime = value + } + if value, ok := asc.mutation.Data(); ok { + _spec.SetField(achievementssnapshot.FieldData, field.TypeJSON, value) + _node.Data = value + } + if nodes := asc.mutation.AccountIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: achievementssnapshot.AccountTable, + Columns: []string{achievementssnapshot.AccountColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.AccountID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// AchievementsSnapshotCreateBulk is the builder for creating many AchievementsSnapshot entities in bulk. +type AchievementsSnapshotCreateBulk struct { + config + err error + builders []*AchievementsSnapshotCreate +} + +// Save creates the AchievementsSnapshot entities in the database. +func (ascb *AchievementsSnapshotCreateBulk) Save(ctx context.Context) ([]*AchievementsSnapshot, error) { + if ascb.err != nil { + return nil, ascb.err + } + specs := make([]*sqlgraph.CreateSpec, len(ascb.builders)) + nodes := make([]*AchievementsSnapshot, len(ascb.builders)) + mutators := make([]Mutator, len(ascb.builders)) + for i := range ascb.builders { + func(i int, root context.Context) { + builder := ascb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AchievementsSnapshotMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, ascb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, ascb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, ascb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (ascb *AchievementsSnapshotCreateBulk) SaveX(ctx context.Context) []*AchievementsSnapshot { + v, err := ascb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ascb *AchievementsSnapshotCreateBulk) Exec(ctx context.Context) error { + _, err := ascb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ascb *AchievementsSnapshotCreateBulk) ExecX(ctx context.Context) { + if err := ascb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/achievementssnapshot_delete.go b/internal/database/ent/db/achievementssnapshot_delete.go new file mode 100644 index 00000000..e79eb3c4 --- /dev/null +++ b/internal/database/ent/db/achievementssnapshot_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AchievementsSnapshotDelete is the builder for deleting a AchievementsSnapshot entity. +type AchievementsSnapshotDelete struct { + config + hooks []Hook + mutation *AchievementsSnapshotMutation +} + +// Where appends a list predicates to the AchievementsSnapshotDelete builder. +func (asd *AchievementsSnapshotDelete) Where(ps ...predicate.AchievementsSnapshot) *AchievementsSnapshotDelete { + asd.mutation.Where(ps...) + return asd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (asd *AchievementsSnapshotDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, asd.sqlExec, asd.mutation, asd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (asd *AchievementsSnapshotDelete) ExecX(ctx context.Context) int { + n, err := asd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (asd *AchievementsSnapshotDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(achievementssnapshot.Table, sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString)) + if ps := asd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, asd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + asd.mutation.done = true + return affected, err +} + +// AchievementsSnapshotDeleteOne is the builder for deleting a single AchievementsSnapshot entity. +type AchievementsSnapshotDeleteOne struct { + asd *AchievementsSnapshotDelete +} + +// Where appends a list predicates to the AchievementsSnapshotDelete builder. +func (asdo *AchievementsSnapshotDeleteOne) Where(ps ...predicate.AchievementsSnapshot) *AchievementsSnapshotDeleteOne { + asdo.asd.mutation.Where(ps...) + return asdo +} + +// Exec executes the deletion query. +func (asdo *AchievementsSnapshotDeleteOne) Exec(ctx context.Context) error { + n, err := asdo.asd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{achievementssnapshot.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (asdo *AchievementsSnapshotDeleteOne) ExecX(ctx context.Context) { + if err := asdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/achievementssnapshot_query.go b/internal/database/ent/db/achievementssnapshot_query.go new file mode 100644 index 00000000..fed3275c --- /dev/null +++ b/internal/database/ent/db/achievementssnapshot_query.go @@ -0,0 +1,605 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AchievementsSnapshotQuery is the builder for querying AchievementsSnapshot entities. +type AchievementsSnapshotQuery struct { + config + ctx *QueryContext + order []achievementssnapshot.OrderOption + inters []Interceptor + predicates []predicate.AchievementsSnapshot + withAccount *AccountQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AchievementsSnapshotQuery builder. +func (asq *AchievementsSnapshotQuery) Where(ps ...predicate.AchievementsSnapshot) *AchievementsSnapshotQuery { + asq.predicates = append(asq.predicates, ps...) + return asq +} + +// Limit the number of records to be returned by this query. +func (asq *AchievementsSnapshotQuery) Limit(limit int) *AchievementsSnapshotQuery { + asq.ctx.Limit = &limit + return asq +} + +// Offset to start from. +func (asq *AchievementsSnapshotQuery) Offset(offset int) *AchievementsSnapshotQuery { + asq.ctx.Offset = &offset + return asq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (asq *AchievementsSnapshotQuery) Unique(unique bool) *AchievementsSnapshotQuery { + asq.ctx.Unique = &unique + return asq +} + +// Order specifies how the records should be ordered. +func (asq *AchievementsSnapshotQuery) Order(o ...achievementssnapshot.OrderOption) *AchievementsSnapshotQuery { + asq.order = append(asq.order, o...) + return asq +} + +// QueryAccount chains the current query on the "account" edge. +func (asq *AchievementsSnapshotQuery) QueryAccount() *AccountQuery { + query := (&AccountClient{config: asq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := asq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := asq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(achievementssnapshot.Table, achievementssnapshot.FieldID, selector), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, achievementssnapshot.AccountTable, achievementssnapshot.AccountColumn), + ) + fromU = sqlgraph.SetNeighbors(asq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first AchievementsSnapshot entity from the query. +// Returns a *NotFoundError when no AchievementsSnapshot was found. +func (asq *AchievementsSnapshotQuery) First(ctx context.Context) (*AchievementsSnapshot, error) { + nodes, err := asq.Limit(1).All(setContextOp(ctx, asq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{achievementssnapshot.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (asq *AchievementsSnapshotQuery) FirstX(ctx context.Context) *AchievementsSnapshot { + node, err := asq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first AchievementsSnapshot ID from the query. +// Returns a *NotFoundError when no AchievementsSnapshot ID was found. +func (asq *AchievementsSnapshotQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = asq.Limit(1).IDs(setContextOp(ctx, asq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{achievementssnapshot.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (asq *AchievementsSnapshotQuery) FirstIDX(ctx context.Context) string { + id, err := asq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single AchievementsSnapshot entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one AchievementsSnapshot entity is found. +// Returns a *NotFoundError when no AchievementsSnapshot entities are found. +func (asq *AchievementsSnapshotQuery) Only(ctx context.Context) (*AchievementsSnapshot, error) { + nodes, err := asq.Limit(2).All(setContextOp(ctx, asq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{achievementssnapshot.Label} + default: + return nil, &NotSingularError{achievementssnapshot.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (asq *AchievementsSnapshotQuery) OnlyX(ctx context.Context) *AchievementsSnapshot { + node, err := asq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only AchievementsSnapshot ID in the query. +// Returns a *NotSingularError when more than one AchievementsSnapshot ID is found. +// Returns a *NotFoundError when no entities are found. +func (asq *AchievementsSnapshotQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = asq.Limit(2).IDs(setContextOp(ctx, asq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{achievementssnapshot.Label} + default: + err = &NotSingularError{achievementssnapshot.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (asq *AchievementsSnapshotQuery) OnlyIDX(ctx context.Context) string { + id, err := asq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of AchievementsSnapshots. +func (asq *AchievementsSnapshotQuery) All(ctx context.Context) ([]*AchievementsSnapshot, error) { + ctx = setContextOp(ctx, asq.ctx, "All") + if err := asq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*AchievementsSnapshot, *AchievementsSnapshotQuery]() + return withInterceptors[[]*AchievementsSnapshot](ctx, asq, qr, asq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (asq *AchievementsSnapshotQuery) AllX(ctx context.Context) []*AchievementsSnapshot { + nodes, err := asq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of AchievementsSnapshot IDs. +func (asq *AchievementsSnapshotQuery) IDs(ctx context.Context) (ids []string, err error) { + if asq.ctx.Unique == nil && asq.path != nil { + asq.Unique(true) + } + ctx = setContextOp(ctx, asq.ctx, "IDs") + if err = asq.Select(achievementssnapshot.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (asq *AchievementsSnapshotQuery) IDsX(ctx context.Context) []string { + ids, err := asq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (asq *AchievementsSnapshotQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, asq.ctx, "Count") + if err := asq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, asq, querierCount[*AchievementsSnapshotQuery](), asq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (asq *AchievementsSnapshotQuery) CountX(ctx context.Context) int { + count, err := asq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (asq *AchievementsSnapshotQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, asq.ctx, "Exist") + switch _, err := asq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (asq *AchievementsSnapshotQuery) ExistX(ctx context.Context) bool { + exist, err := asq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AchievementsSnapshotQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (asq *AchievementsSnapshotQuery) Clone() *AchievementsSnapshotQuery { + if asq == nil { + return nil + } + return &AchievementsSnapshotQuery{ + config: asq.config, + ctx: asq.ctx.Clone(), + order: append([]achievementssnapshot.OrderOption{}, asq.order...), + inters: append([]Interceptor{}, asq.inters...), + predicates: append([]predicate.AchievementsSnapshot{}, asq.predicates...), + withAccount: asq.withAccount.Clone(), + // clone intermediate query. + sql: asq.sql.Clone(), + path: asq.path, + } +} + +// WithAccount tells the query-builder to eager-load the nodes that are connected to +// the "account" edge. The optional arguments are used to configure the query builder of the edge. +func (asq *AchievementsSnapshotQuery) WithAccount(opts ...func(*AccountQuery)) *AchievementsSnapshotQuery { + query := (&AccountClient{config: asq.config}).Query() + for _, opt := range opts { + opt(query) + } + asq.withAccount = query + return asq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.AchievementsSnapshot.Query(). +// GroupBy(achievementssnapshot.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (asq *AchievementsSnapshotQuery) GroupBy(field string, fields ...string) *AchievementsSnapshotGroupBy { + asq.ctx.Fields = append([]string{field}, fields...) + grbuild := &AchievementsSnapshotGroupBy{build: asq} + grbuild.flds = &asq.ctx.Fields + grbuild.label = achievementssnapshot.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.AchievementsSnapshot.Query(). +// Select(achievementssnapshot.FieldCreatedAt). +// Scan(ctx, &v) +func (asq *AchievementsSnapshotQuery) Select(fields ...string) *AchievementsSnapshotSelect { + asq.ctx.Fields = append(asq.ctx.Fields, fields...) + sbuild := &AchievementsSnapshotSelect{AchievementsSnapshotQuery: asq} + sbuild.label = achievementssnapshot.Label + sbuild.flds, sbuild.scan = &asq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a AchievementsSnapshotSelect configured with the given aggregations. +func (asq *AchievementsSnapshotQuery) Aggregate(fns ...AggregateFunc) *AchievementsSnapshotSelect { + return asq.Select().Aggregate(fns...) +} + +func (asq *AchievementsSnapshotQuery) prepareQuery(ctx context.Context) error { + for _, inter := range asq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, asq); err != nil { + return err + } + } + } + for _, f := range asq.ctx.Fields { + if !achievementssnapshot.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if asq.path != nil { + prev, err := asq.path(ctx) + if err != nil { + return err + } + asq.sql = prev + } + return nil +} + +func (asq *AchievementsSnapshotQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AchievementsSnapshot, error) { + var ( + nodes = []*AchievementsSnapshot{} + _spec = asq.querySpec() + loadedTypes = [1]bool{ + asq.withAccount != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*AchievementsSnapshot).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &AchievementsSnapshot{config: asq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, asq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := asq.withAccount; query != nil { + if err := asq.loadAccount(ctx, query, nodes, nil, + func(n *AchievementsSnapshot, e *Account) { n.Edges.Account = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (asq *AchievementsSnapshotQuery) loadAccount(ctx context.Context, query *AccountQuery, nodes []*AchievementsSnapshot, init func(*AchievementsSnapshot), assign func(*AchievementsSnapshot, *Account)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*AchievementsSnapshot) + for i := range nodes { + fk := nodes[i].AccountID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(account.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "account_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (asq *AchievementsSnapshotQuery) sqlCount(ctx context.Context) (int, error) { + _spec := asq.querySpec() + _spec.Node.Columns = asq.ctx.Fields + if len(asq.ctx.Fields) > 0 { + _spec.Unique = asq.ctx.Unique != nil && *asq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, asq.driver, _spec) +} + +func (asq *AchievementsSnapshotQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(achievementssnapshot.Table, achievementssnapshot.Columns, sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString)) + _spec.From = asq.sql + if unique := asq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if asq.path != nil { + _spec.Unique = true + } + if fields := asq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, achievementssnapshot.FieldID) + for i := range fields { + if fields[i] != achievementssnapshot.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if asq.withAccount != nil { + _spec.Node.AddColumnOnce(achievementssnapshot.FieldAccountID) + } + } + if ps := asq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := asq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := asq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := asq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (asq *AchievementsSnapshotQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(asq.driver.Dialect()) + t1 := builder.Table(achievementssnapshot.Table) + columns := asq.ctx.Fields + if len(columns) == 0 { + columns = achievementssnapshot.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if asq.sql != nil { + selector = asq.sql + selector.Select(selector.Columns(columns...)...) + } + if asq.ctx.Unique != nil && *asq.ctx.Unique { + selector.Distinct() + } + for _, p := range asq.predicates { + p(selector) + } + for _, p := range asq.order { + p(selector) + } + if offset := asq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := asq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// AchievementsSnapshotGroupBy is the group-by builder for AchievementsSnapshot entities. +type AchievementsSnapshotGroupBy struct { + selector + build *AchievementsSnapshotQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (asgb *AchievementsSnapshotGroupBy) Aggregate(fns ...AggregateFunc) *AchievementsSnapshotGroupBy { + asgb.fns = append(asgb.fns, fns...) + return asgb +} + +// Scan applies the selector query and scans the result into the given value. +func (asgb *AchievementsSnapshotGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, asgb.build.ctx, "GroupBy") + if err := asgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AchievementsSnapshotQuery, *AchievementsSnapshotGroupBy](ctx, asgb.build, asgb, asgb.build.inters, v) +} + +func (asgb *AchievementsSnapshotGroupBy) sqlScan(ctx context.Context, root *AchievementsSnapshotQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(asgb.fns)) + for _, fn := range asgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*asgb.flds)+len(asgb.fns)) + for _, f := range *asgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*asgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := asgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// AchievementsSnapshotSelect is the builder for selecting fields of AchievementsSnapshot entities. +type AchievementsSnapshotSelect struct { + *AchievementsSnapshotQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ass *AchievementsSnapshotSelect) Aggregate(fns ...AggregateFunc) *AchievementsSnapshotSelect { + ass.fns = append(ass.fns, fns...) + return ass +} + +// Scan applies the selector query and scans the result into the given value. +func (ass *AchievementsSnapshotSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ass.ctx, "Select") + if err := ass.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AchievementsSnapshotQuery, *AchievementsSnapshotSelect](ctx, ass.AchievementsSnapshotQuery, ass, ass.inters, v) +} + +func (ass *AchievementsSnapshotSelect) sqlScan(ctx context.Context, root *AchievementsSnapshotQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(ass.fns)) + for _, fn := range ass.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*ass.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ass.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/achievementssnapshot_update.go b/internal/database/ent/db/achievementssnapshot_update.go new file mode 100644 index 00000000..10f05608 --- /dev/null +++ b/internal/database/ent/db/achievementssnapshot_update.go @@ -0,0 +1,485 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/am-wg-proxy-next/v2/types" +) + +// AchievementsSnapshotUpdate is the builder for updating AchievementsSnapshot entities. +type AchievementsSnapshotUpdate struct { + config + hooks []Hook + mutation *AchievementsSnapshotMutation +} + +// Where appends a list predicates to the AchievementsSnapshotUpdate builder. +func (asu *AchievementsSnapshotUpdate) Where(ps ...predicate.AchievementsSnapshot) *AchievementsSnapshotUpdate { + asu.mutation.Where(ps...) + return asu +} + +// SetUpdatedAt sets the "updated_at" field. +func (asu *AchievementsSnapshotUpdate) SetUpdatedAt(i int) *AchievementsSnapshotUpdate { + asu.mutation.ResetUpdatedAt() + asu.mutation.SetUpdatedAt(i) + return asu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (asu *AchievementsSnapshotUpdate) AddUpdatedAt(i int) *AchievementsSnapshotUpdate { + asu.mutation.AddUpdatedAt(i) + return asu +} + +// SetType sets the "type" field. +func (asu *AchievementsSnapshotUpdate) SetType(mt models.SnapshotType) *AchievementsSnapshotUpdate { + asu.mutation.SetType(mt) + return asu +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (asu *AchievementsSnapshotUpdate) SetNillableType(mt *models.SnapshotType) *AchievementsSnapshotUpdate { + if mt != nil { + asu.SetType(*mt) + } + return asu +} + +// SetReferenceID sets the "reference_id" field. +func (asu *AchievementsSnapshotUpdate) SetReferenceID(s string) *AchievementsSnapshotUpdate { + asu.mutation.SetReferenceID(s) + return asu +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (asu *AchievementsSnapshotUpdate) SetNillableReferenceID(s *string) *AchievementsSnapshotUpdate { + if s != nil { + asu.SetReferenceID(*s) + } + return asu +} + +// SetBattles sets the "battles" field. +func (asu *AchievementsSnapshotUpdate) SetBattles(i int) *AchievementsSnapshotUpdate { + asu.mutation.ResetBattles() + asu.mutation.SetBattles(i) + return asu +} + +// SetNillableBattles sets the "battles" field if the given value is not nil. +func (asu *AchievementsSnapshotUpdate) SetNillableBattles(i *int) *AchievementsSnapshotUpdate { + if i != nil { + asu.SetBattles(*i) + } + return asu +} + +// AddBattles adds i to the "battles" field. +func (asu *AchievementsSnapshotUpdate) AddBattles(i int) *AchievementsSnapshotUpdate { + asu.mutation.AddBattles(i) + return asu +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (asu *AchievementsSnapshotUpdate) SetLastBattleTime(i int) *AchievementsSnapshotUpdate { + asu.mutation.ResetLastBattleTime() + asu.mutation.SetLastBattleTime(i) + return asu +} + +// SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. +func (asu *AchievementsSnapshotUpdate) SetNillableLastBattleTime(i *int) *AchievementsSnapshotUpdate { + if i != nil { + asu.SetLastBattleTime(*i) + } + return asu +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (asu *AchievementsSnapshotUpdate) AddLastBattleTime(i int) *AchievementsSnapshotUpdate { + asu.mutation.AddLastBattleTime(i) + return asu +} + +// SetData sets the "data" field. +func (asu *AchievementsSnapshotUpdate) SetData(tf types.AchievementsFrame) *AchievementsSnapshotUpdate { + asu.mutation.SetData(tf) + return asu +} + +// SetNillableData sets the "data" field if the given value is not nil. +func (asu *AchievementsSnapshotUpdate) SetNillableData(tf *types.AchievementsFrame) *AchievementsSnapshotUpdate { + if tf != nil { + asu.SetData(*tf) + } + return asu +} + +// Mutation returns the AchievementsSnapshotMutation object of the builder. +func (asu *AchievementsSnapshotUpdate) Mutation() *AchievementsSnapshotMutation { + return asu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (asu *AchievementsSnapshotUpdate) Save(ctx context.Context) (int, error) { + asu.defaults() + return withHooks(ctx, asu.sqlSave, asu.mutation, asu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (asu *AchievementsSnapshotUpdate) SaveX(ctx context.Context) int { + affected, err := asu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (asu *AchievementsSnapshotUpdate) Exec(ctx context.Context) error { + _, err := asu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (asu *AchievementsSnapshotUpdate) ExecX(ctx context.Context) { + if err := asu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (asu *AchievementsSnapshotUpdate) defaults() { + if _, ok := asu.mutation.UpdatedAt(); !ok { + v := achievementssnapshot.UpdateDefaultUpdatedAt() + asu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (asu *AchievementsSnapshotUpdate) check() error { + if v, ok := asu.mutation.GetType(); ok { + if err := achievementssnapshot.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "AchievementsSnapshot.type": %w`, err)} + } + } + if v, ok := asu.mutation.ReferenceID(); ok { + if err := achievementssnapshot.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "AchievementsSnapshot.reference_id": %w`, err)} + } + } + if _, ok := asu.mutation.AccountID(); asu.mutation.AccountCleared() && !ok { + return errors.New(`db: clearing a required unique edge "AchievementsSnapshot.account"`) + } + return nil +} + +func (asu *AchievementsSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := asu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(achievementssnapshot.Table, achievementssnapshot.Columns, sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString)) + if ps := asu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := asu.mutation.UpdatedAt(); ok { + _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := asu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := asu.mutation.GetType(); ok { + _spec.SetField(achievementssnapshot.FieldType, field.TypeEnum, value) + } + if value, ok := asu.mutation.ReferenceID(); ok { + _spec.SetField(achievementssnapshot.FieldReferenceID, field.TypeString, value) + } + if value, ok := asu.mutation.Battles(); ok { + _spec.SetField(achievementssnapshot.FieldBattles, field.TypeInt, value) + } + if value, ok := asu.mutation.AddedBattles(); ok { + _spec.AddField(achievementssnapshot.FieldBattles, field.TypeInt, value) + } + if value, ok := asu.mutation.LastBattleTime(); ok { + _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := asu.mutation.AddedLastBattleTime(); ok { + _spec.AddField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := asu.mutation.Data(); ok { + _spec.SetField(achievementssnapshot.FieldData, field.TypeJSON, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, asu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{achievementssnapshot.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + asu.mutation.done = true + return n, nil +} + +// AchievementsSnapshotUpdateOne is the builder for updating a single AchievementsSnapshot entity. +type AchievementsSnapshotUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AchievementsSnapshotMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (asuo *AchievementsSnapshotUpdateOne) SetUpdatedAt(i int) *AchievementsSnapshotUpdateOne { + asuo.mutation.ResetUpdatedAt() + asuo.mutation.SetUpdatedAt(i) + return asuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (asuo *AchievementsSnapshotUpdateOne) AddUpdatedAt(i int) *AchievementsSnapshotUpdateOne { + asuo.mutation.AddUpdatedAt(i) + return asuo +} + +// SetType sets the "type" field. +func (asuo *AchievementsSnapshotUpdateOne) SetType(mt models.SnapshotType) *AchievementsSnapshotUpdateOne { + asuo.mutation.SetType(mt) + return asuo +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (asuo *AchievementsSnapshotUpdateOne) SetNillableType(mt *models.SnapshotType) *AchievementsSnapshotUpdateOne { + if mt != nil { + asuo.SetType(*mt) + } + return asuo +} + +// SetReferenceID sets the "reference_id" field. +func (asuo *AchievementsSnapshotUpdateOne) SetReferenceID(s string) *AchievementsSnapshotUpdateOne { + asuo.mutation.SetReferenceID(s) + return asuo +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (asuo *AchievementsSnapshotUpdateOne) SetNillableReferenceID(s *string) *AchievementsSnapshotUpdateOne { + if s != nil { + asuo.SetReferenceID(*s) + } + return asuo +} + +// SetBattles sets the "battles" field. +func (asuo *AchievementsSnapshotUpdateOne) SetBattles(i int) *AchievementsSnapshotUpdateOne { + asuo.mutation.ResetBattles() + asuo.mutation.SetBattles(i) + return asuo +} + +// SetNillableBattles sets the "battles" field if the given value is not nil. +func (asuo *AchievementsSnapshotUpdateOne) SetNillableBattles(i *int) *AchievementsSnapshotUpdateOne { + if i != nil { + asuo.SetBattles(*i) + } + return asuo +} + +// AddBattles adds i to the "battles" field. +func (asuo *AchievementsSnapshotUpdateOne) AddBattles(i int) *AchievementsSnapshotUpdateOne { + asuo.mutation.AddBattles(i) + return asuo +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (asuo *AchievementsSnapshotUpdateOne) SetLastBattleTime(i int) *AchievementsSnapshotUpdateOne { + asuo.mutation.ResetLastBattleTime() + asuo.mutation.SetLastBattleTime(i) + return asuo +} + +// SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. +func (asuo *AchievementsSnapshotUpdateOne) SetNillableLastBattleTime(i *int) *AchievementsSnapshotUpdateOne { + if i != nil { + asuo.SetLastBattleTime(*i) + } + return asuo +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (asuo *AchievementsSnapshotUpdateOne) AddLastBattleTime(i int) *AchievementsSnapshotUpdateOne { + asuo.mutation.AddLastBattleTime(i) + return asuo +} + +// SetData sets the "data" field. +func (asuo *AchievementsSnapshotUpdateOne) SetData(tf types.AchievementsFrame) *AchievementsSnapshotUpdateOne { + asuo.mutation.SetData(tf) + return asuo +} + +// SetNillableData sets the "data" field if the given value is not nil. +func (asuo *AchievementsSnapshotUpdateOne) SetNillableData(tf *types.AchievementsFrame) *AchievementsSnapshotUpdateOne { + if tf != nil { + asuo.SetData(*tf) + } + return asuo +} + +// Mutation returns the AchievementsSnapshotMutation object of the builder. +func (asuo *AchievementsSnapshotUpdateOne) Mutation() *AchievementsSnapshotMutation { + return asuo.mutation +} + +// Where appends a list predicates to the AchievementsSnapshotUpdate builder. +func (asuo *AchievementsSnapshotUpdateOne) Where(ps ...predicate.AchievementsSnapshot) *AchievementsSnapshotUpdateOne { + asuo.mutation.Where(ps...) + return asuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (asuo *AchievementsSnapshotUpdateOne) Select(field string, fields ...string) *AchievementsSnapshotUpdateOne { + asuo.fields = append([]string{field}, fields...) + return asuo +} + +// Save executes the query and returns the updated AchievementsSnapshot entity. +func (asuo *AchievementsSnapshotUpdateOne) Save(ctx context.Context) (*AchievementsSnapshot, error) { + asuo.defaults() + return withHooks(ctx, asuo.sqlSave, asuo.mutation, asuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (asuo *AchievementsSnapshotUpdateOne) SaveX(ctx context.Context) *AchievementsSnapshot { + node, err := asuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (asuo *AchievementsSnapshotUpdateOne) Exec(ctx context.Context) error { + _, err := asuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (asuo *AchievementsSnapshotUpdateOne) ExecX(ctx context.Context) { + if err := asuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (asuo *AchievementsSnapshotUpdateOne) defaults() { + if _, ok := asuo.mutation.UpdatedAt(); !ok { + v := achievementssnapshot.UpdateDefaultUpdatedAt() + asuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (asuo *AchievementsSnapshotUpdateOne) check() error { + if v, ok := asuo.mutation.GetType(); ok { + if err := achievementssnapshot.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "AchievementsSnapshot.type": %w`, err)} + } + } + if v, ok := asuo.mutation.ReferenceID(); ok { + if err := achievementssnapshot.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "AchievementsSnapshot.reference_id": %w`, err)} + } + } + if _, ok := asuo.mutation.AccountID(); asuo.mutation.AccountCleared() && !ok { + return errors.New(`db: clearing a required unique edge "AchievementsSnapshot.account"`) + } + return nil +} + +func (asuo *AchievementsSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *AchievementsSnapshot, err error) { + if err := asuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(achievementssnapshot.Table, achievementssnapshot.Columns, sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString)) + id, ok := asuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "AchievementsSnapshot.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := asuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, achievementssnapshot.FieldID) + for _, f := range fields { + if !achievementssnapshot.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != achievementssnapshot.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := asuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := asuo.mutation.UpdatedAt(); ok { + _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := asuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := asuo.mutation.GetType(); ok { + _spec.SetField(achievementssnapshot.FieldType, field.TypeEnum, value) + } + if value, ok := asuo.mutation.ReferenceID(); ok { + _spec.SetField(achievementssnapshot.FieldReferenceID, field.TypeString, value) + } + if value, ok := asuo.mutation.Battles(); ok { + _spec.SetField(achievementssnapshot.FieldBattles, field.TypeInt, value) + } + if value, ok := asuo.mutation.AddedBattles(); ok { + _spec.AddField(achievementssnapshot.FieldBattles, field.TypeInt, value) + } + if value, ok := asuo.mutation.LastBattleTime(); ok { + _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := asuo.mutation.AddedLastBattleTime(); ok { + _spec.AddField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := asuo.mutation.Data(); ok { + _spec.SetField(achievementssnapshot.FieldData, field.TypeJSON, value) + } + _node = &AchievementsSnapshot{config: asuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, asuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{achievementssnapshot.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + asuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/appconfiguration.go b/internal/database/ent/db/appconfiguration.go new file mode 100644 index 00000000..8dbdef16 --- /dev/null +++ b/internal/database/ent/db/appconfiguration.go @@ -0,0 +1,154 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" +) + +// AppConfiguration is the model entity for the AppConfiguration schema. +type AppConfiguration struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Key holds the value of the "key" field. + Key string `json:"key,omitempty"` + // Value holds the value of the "value" field. + Value any `json:"value,omitempty"` + // Metadata holds the value of the "metadata" field. + Metadata map[string]interface{} `json:"metadata,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*AppConfiguration) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case appconfiguration.FieldValue, appconfiguration.FieldMetadata: + values[i] = new([]byte) + case appconfiguration.FieldCreatedAt, appconfiguration.FieldUpdatedAt: + values[i] = new(sql.NullInt64) + case appconfiguration.FieldID, appconfiguration.FieldKey: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the AppConfiguration fields. +func (ac *AppConfiguration) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case appconfiguration.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + ac.ID = value.String + } + case appconfiguration.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + ac.CreatedAt = int(value.Int64) + } + case appconfiguration.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + ac.UpdatedAt = int(value.Int64) + } + case appconfiguration.FieldKey: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field key", values[i]) + } else if value.Valid { + ac.Key = value.String + } + case appconfiguration.FieldValue: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field value", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &ac.Value); err != nil { + return fmt.Errorf("unmarshal field value: %w", err) + } + } + case appconfiguration.FieldMetadata: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field metadata", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &ac.Metadata); err != nil { + return fmt.Errorf("unmarshal field metadata: %w", err) + } + } + default: + ac.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// GetValue returns the ent.Value that was dynamically selected and assigned to the AppConfiguration. +// This includes values selected through modifiers, order, etc. +func (ac *AppConfiguration) GetValue(name string) (ent.Value, error) { + return ac.selectValues.Get(name) +} + +// Update returns a builder for updating this AppConfiguration. +// Note that you need to call AppConfiguration.Unwrap() before calling this method if this AppConfiguration +// was returned from a transaction, and the transaction was committed or rolled back. +func (ac *AppConfiguration) Update() *AppConfigurationUpdateOne { + return NewAppConfigurationClient(ac.config).UpdateOne(ac) +} + +// Unwrap unwraps the AppConfiguration entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (ac *AppConfiguration) Unwrap() *AppConfiguration { + _tx, ok := ac.config.driver.(*txDriver) + if !ok { + panic("db: AppConfiguration is not a transactional entity") + } + ac.config.driver = _tx.drv + return ac +} + +// String implements the fmt.Stringer. +func (ac *AppConfiguration) String() string { + var builder strings.Builder + builder.WriteString("AppConfiguration(") + builder.WriteString(fmt.Sprintf("id=%v, ", ac.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", ac.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", ac.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("key=") + builder.WriteString(ac.Key) + builder.WriteString(", ") + builder.WriteString("value=") + builder.WriteString(fmt.Sprintf("%v", ac.Value)) + builder.WriteString(", ") + builder.WriteString("metadata=") + builder.WriteString(fmt.Sprintf("%v", ac.Metadata)) + builder.WriteByte(')') + return builder.String() +} + +// AppConfigurations is a parsable slice of AppConfiguration. +type AppConfigurations []*AppConfiguration diff --git a/internal/database/ent/db/appconfiguration/appconfiguration.go b/internal/database/ent/db/appconfiguration/appconfiguration.go new file mode 100644 index 00000000..02f8ba48 --- /dev/null +++ b/internal/database/ent/db/appconfiguration/appconfiguration.go @@ -0,0 +1,82 @@ +// Code generated by ent, DO NOT EDIT. + +package appconfiguration + +import ( + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the appconfiguration type in the database. + Label = "app_configuration" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldKey holds the string denoting the key field in the database. + FieldKey = "key" + // FieldValue holds the string denoting the value field in the database. + FieldValue = "value" + // FieldMetadata holds the string denoting the metadata field in the database. + FieldMetadata = "metadata" + // Table holds the table name of the appconfiguration in the database. + Table = "app_configurations" +) + +// Columns holds all SQL columns for appconfiguration fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldKey, + FieldValue, + FieldMetadata, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // KeyValidator is a validator for the "key" field. It is called by the builders before save. + KeyValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// OrderOption defines the ordering options for the AppConfiguration queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByKey orders the results by the key field. +func ByKey(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldKey, opts...).ToFunc() +} diff --git a/internal/database/ent/db/appconfiguration/where.go b/internal/database/ent/db/appconfiguration/where.go new file mode 100644 index 00000000..ffb17f2d --- /dev/null +++ b/internal/database/ent/db/appconfiguration/where.go @@ -0,0 +1,248 @@ +// Code generated by ent, DO NOT EDIT. + +package appconfiguration + +import ( + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Key applies equality check predicate on the "key" field. It's identical to KeyEQ. +func Key(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEQ(FieldKey, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// KeyEQ applies the EQ predicate on the "key" field. +func KeyEQ(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEQ(FieldKey, v)) +} + +// KeyNEQ applies the NEQ predicate on the "key" field. +func KeyNEQ(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldNEQ(FieldKey, v)) +} + +// KeyIn applies the In predicate on the "key" field. +func KeyIn(vs ...string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldIn(FieldKey, vs...)) +} + +// KeyNotIn applies the NotIn predicate on the "key" field. +func KeyNotIn(vs ...string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldNotIn(FieldKey, vs...)) +} + +// KeyGT applies the GT predicate on the "key" field. +func KeyGT(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldGT(FieldKey, v)) +} + +// KeyGTE applies the GTE predicate on the "key" field. +func KeyGTE(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldGTE(FieldKey, v)) +} + +// KeyLT applies the LT predicate on the "key" field. +func KeyLT(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldLT(FieldKey, v)) +} + +// KeyLTE applies the LTE predicate on the "key" field. +func KeyLTE(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldLTE(FieldKey, v)) +} + +// KeyContains applies the Contains predicate on the "key" field. +func KeyContains(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldContains(FieldKey, v)) +} + +// KeyHasPrefix applies the HasPrefix predicate on the "key" field. +func KeyHasPrefix(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldHasPrefix(FieldKey, v)) +} + +// KeyHasSuffix applies the HasSuffix predicate on the "key" field. +func KeyHasSuffix(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldHasSuffix(FieldKey, v)) +} + +// KeyEqualFold applies the EqualFold predicate on the "key" field. +func KeyEqualFold(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldEqualFold(FieldKey, v)) +} + +// KeyContainsFold applies the ContainsFold predicate on the "key" field. +func KeyContainsFold(v string) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldContainsFold(FieldKey, v)) +} + +// MetadataIsNil applies the IsNil predicate on the "metadata" field. +func MetadataIsNil() predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldIsNull(FieldMetadata)) +} + +// MetadataNotNil applies the NotNil predicate on the "metadata" field. +func MetadataNotNil() predicate.AppConfiguration { + return predicate.AppConfiguration(sql.FieldNotNull(FieldMetadata)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.AppConfiguration) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.AppConfiguration) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.AppConfiguration) predicate.AppConfiguration { + return predicate.AppConfiguration(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/appconfiguration_create.go b/internal/database/ent/db/appconfiguration_create.go new file mode 100644 index 00000000..2cf22a7b --- /dev/null +++ b/internal/database/ent/db/appconfiguration_create.go @@ -0,0 +1,290 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" +) + +// AppConfigurationCreate is the builder for creating a AppConfiguration entity. +type AppConfigurationCreate struct { + config + mutation *AppConfigurationMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (acc *AppConfigurationCreate) SetCreatedAt(i int) *AppConfigurationCreate { + acc.mutation.SetCreatedAt(i) + return acc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (acc *AppConfigurationCreate) SetNillableCreatedAt(i *int) *AppConfigurationCreate { + if i != nil { + acc.SetCreatedAt(*i) + } + return acc +} + +// SetUpdatedAt sets the "updated_at" field. +func (acc *AppConfigurationCreate) SetUpdatedAt(i int) *AppConfigurationCreate { + acc.mutation.SetUpdatedAt(i) + return acc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (acc *AppConfigurationCreate) SetNillableUpdatedAt(i *int) *AppConfigurationCreate { + if i != nil { + acc.SetUpdatedAt(*i) + } + return acc +} + +// SetKey sets the "key" field. +func (acc *AppConfigurationCreate) SetKey(s string) *AppConfigurationCreate { + acc.mutation.SetKey(s) + return acc +} + +// SetValue sets the "value" field. +func (acc *AppConfigurationCreate) SetValue(a any) *AppConfigurationCreate { + acc.mutation.SetValue(a) + return acc +} + +// SetMetadata sets the "metadata" field. +func (acc *AppConfigurationCreate) SetMetadata(m map[string]interface{}) *AppConfigurationCreate { + acc.mutation.SetMetadata(m) + return acc +} + +// SetID sets the "id" field. +func (acc *AppConfigurationCreate) SetID(s string) *AppConfigurationCreate { + acc.mutation.SetID(s) + return acc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (acc *AppConfigurationCreate) SetNillableID(s *string) *AppConfigurationCreate { + if s != nil { + acc.SetID(*s) + } + return acc +} + +// Mutation returns the AppConfigurationMutation object of the builder. +func (acc *AppConfigurationCreate) Mutation() *AppConfigurationMutation { + return acc.mutation +} + +// Save creates the AppConfiguration in the database. +func (acc *AppConfigurationCreate) Save(ctx context.Context) (*AppConfiguration, error) { + acc.defaults() + return withHooks(ctx, acc.sqlSave, acc.mutation, acc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (acc *AppConfigurationCreate) SaveX(ctx context.Context) *AppConfiguration { + v, err := acc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (acc *AppConfigurationCreate) Exec(ctx context.Context) error { + _, err := acc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (acc *AppConfigurationCreate) ExecX(ctx context.Context) { + if err := acc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (acc *AppConfigurationCreate) defaults() { + if _, ok := acc.mutation.CreatedAt(); !ok { + v := appconfiguration.DefaultCreatedAt() + acc.mutation.SetCreatedAt(v) + } + if _, ok := acc.mutation.UpdatedAt(); !ok { + v := appconfiguration.DefaultUpdatedAt() + acc.mutation.SetUpdatedAt(v) + } + if _, ok := acc.mutation.ID(); !ok { + v := appconfiguration.DefaultID() + acc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (acc *AppConfigurationCreate) check() error { + if _, ok := acc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "AppConfiguration.created_at"`)} + } + if _, ok := acc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "AppConfiguration.updated_at"`)} + } + if _, ok := acc.mutation.Key(); !ok { + return &ValidationError{Name: "key", err: errors.New(`db: missing required field "AppConfiguration.key"`)} + } + if v, ok := acc.mutation.Key(); ok { + if err := appconfiguration.KeyValidator(v); err != nil { + return &ValidationError{Name: "key", err: fmt.Errorf(`db: validator failed for field "AppConfiguration.key": %w`, err)} + } + } + if _, ok := acc.mutation.Value(); !ok { + return &ValidationError{Name: "value", err: errors.New(`db: missing required field "AppConfiguration.value"`)} + } + return nil +} + +func (acc *AppConfigurationCreate) sqlSave(ctx context.Context) (*AppConfiguration, error) { + if err := acc.check(); err != nil { + return nil, err + } + _node, _spec := acc.createSpec() + if err := sqlgraph.CreateNode(ctx, acc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected AppConfiguration.ID type: %T", _spec.ID.Value) + } + } + acc.mutation.id = &_node.ID + acc.mutation.done = true + return _node, nil +} + +func (acc *AppConfigurationCreate) createSpec() (*AppConfiguration, *sqlgraph.CreateSpec) { + var ( + _node = &AppConfiguration{config: acc.config} + _spec = sqlgraph.NewCreateSpec(appconfiguration.Table, sqlgraph.NewFieldSpec(appconfiguration.FieldID, field.TypeString)) + ) + if id, ok := acc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := acc.mutation.CreatedAt(); ok { + _spec.SetField(appconfiguration.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := acc.mutation.UpdatedAt(); ok { + _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := acc.mutation.Key(); ok { + _spec.SetField(appconfiguration.FieldKey, field.TypeString, value) + _node.Key = value + } + if value, ok := acc.mutation.Value(); ok { + _spec.SetField(appconfiguration.FieldValue, field.TypeJSON, value) + _node.Value = value + } + if value, ok := acc.mutation.Metadata(); ok { + _spec.SetField(appconfiguration.FieldMetadata, field.TypeJSON, value) + _node.Metadata = value + } + return _node, _spec +} + +// AppConfigurationCreateBulk is the builder for creating many AppConfiguration entities in bulk. +type AppConfigurationCreateBulk struct { + config + err error + builders []*AppConfigurationCreate +} + +// Save creates the AppConfiguration entities in the database. +func (accb *AppConfigurationCreateBulk) Save(ctx context.Context) ([]*AppConfiguration, error) { + if accb.err != nil { + return nil, accb.err + } + specs := make([]*sqlgraph.CreateSpec, len(accb.builders)) + nodes := make([]*AppConfiguration, len(accb.builders)) + mutators := make([]Mutator, len(accb.builders)) + for i := range accb.builders { + func(i int, root context.Context) { + builder := accb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AppConfigurationMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, accb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, accb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, accb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (accb *AppConfigurationCreateBulk) SaveX(ctx context.Context) []*AppConfiguration { + v, err := accb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (accb *AppConfigurationCreateBulk) Exec(ctx context.Context) error { + _, err := accb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (accb *AppConfigurationCreateBulk) ExecX(ctx context.Context) { + if err := accb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/appconfiguration_delete.go b/internal/database/ent/db/appconfiguration_delete.go new file mode 100644 index 00000000..f93b6397 --- /dev/null +++ b/internal/database/ent/db/appconfiguration_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AppConfigurationDelete is the builder for deleting a AppConfiguration entity. +type AppConfigurationDelete struct { + config + hooks []Hook + mutation *AppConfigurationMutation +} + +// Where appends a list predicates to the AppConfigurationDelete builder. +func (acd *AppConfigurationDelete) Where(ps ...predicate.AppConfiguration) *AppConfigurationDelete { + acd.mutation.Where(ps...) + return acd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (acd *AppConfigurationDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, acd.sqlExec, acd.mutation, acd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (acd *AppConfigurationDelete) ExecX(ctx context.Context) int { + n, err := acd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (acd *AppConfigurationDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(appconfiguration.Table, sqlgraph.NewFieldSpec(appconfiguration.FieldID, field.TypeString)) + if ps := acd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, acd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + acd.mutation.done = true + return affected, err +} + +// AppConfigurationDeleteOne is the builder for deleting a single AppConfiguration entity. +type AppConfigurationDeleteOne struct { + acd *AppConfigurationDelete +} + +// Where appends a list predicates to the AppConfigurationDelete builder. +func (acdo *AppConfigurationDeleteOne) Where(ps ...predicate.AppConfiguration) *AppConfigurationDeleteOne { + acdo.acd.mutation.Where(ps...) + return acdo +} + +// Exec executes the deletion query. +func (acdo *AppConfigurationDeleteOne) Exec(ctx context.Context) error { + n, err := acdo.acd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{appconfiguration.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (acdo *AppConfigurationDeleteOne) ExecX(ctx context.Context) { + if err := acdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/appconfiguration_query.go b/internal/database/ent/db/appconfiguration_query.go new file mode 100644 index 00000000..549a21e4 --- /dev/null +++ b/internal/database/ent/db/appconfiguration_query.go @@ -0,0 +1,526 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AppConfigurationQuery is the builder for querying AppConfiguration entities. +type AppConfigurationQuery struct { + config + ctx *QueryContext + order []appconfiguration.OrderOption + inters []Interceptor + predicates []predicate.AppConfiguration + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AppConfigurationQuery builder. +func (acq *AppConfigurationQuery) Where(ps ...predicate.AppConfiguration) *AppConfigurationQuery { + acq.predicates = append(acq.predicates, ps...) + return acq +} + +// Limit the number of records to be returned by this query. +func (acq *AppConfigurationQuery) Limit(limit int) *AppConfigurationQuery { + acq.ctx.Limit = &limit + return acq +} + +// Offset to start from. +func (acq *AppConfigurationQuery) Offset(offset int) *AppConfigurationQuery { + acq.ctx.Offset = &offset + return acq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (acq *AppConfigurationQuery) Unique(unique bool) *AppConfigurationQuery { + acq.ctx.Unique = &unique + return acq +} + +// Order specifies how the records should be ordered. +func (acq *AppConfigurationQuery) Order(o ...appconfiguration.OrderOption) *AppConfigurationQuery { + acq.order = append(acq.order, o...) + return acq +} + +// First returns the first AppConfiguration entity from the query. +// Returns a *NotFoundError when no AppConfiguration was found. +func (acq *AppConfigurationQuery) First(ctx context.Context) (*AppConfiguration, error) { + nodes, err := acq.Limit(1).All(setContextOp(ctx, acq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{appconfiguration.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (acq *AppConfigurationQuery) FirstX(ctx context.Context) *AppConfiguration { + node, err := acq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first AppConfiguration ID from the query. +// Returns a *NotFoundError when no AppConfiguration ID was found. +func (acq *AppConfigurationQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = acq.Limit(1).IDs(setContextOp(ctx, acq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{appconfiguration.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (acq *AppConfigurationQuery) FirstIDX(ctx context.Context) string { + id, err := acq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single AppConfiguration entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one AppConfiguration entity is found. +// Returns a *NotFoundError when no AppConfiguration entities are found. +func (acq *AppConfigurationQuery) Only(ctx context.Context) (*AppConfiguration, error) { + nodes, err := acq.Limit(2).All(setContextOp(ctx, acq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{appconfiguration.Label} + default: + return nil, &NotSingularError{appconfiguration.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (acq *AppConfigurationQuery) OnlyX(ctx context.Context) *AppConfiguration { + node, err := acq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only AppConfiguration ID in the query. +// Returns a *NotSingularError when more than one AppConfiguration ID is found. +// Returns a *NotFoundError when no entities are found. +func (acq *AppConfigurationQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = acq.Limit(2).IDs(setContextOp(ctx, acq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{appconfiguration.Label} + default: + err = &NotSingularError{appconfiguration.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (acq *AppConfigurationQuery) OnlyIDX(ctx context.Context) string { + id, err := acq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of AppConfigurations. +func (acq *AppConfigurationQuery) All(ctx context.Context) ([]*AppConfiguration, error) { + ctx = setContextOp(ctx, acq.ctx, "All") + if err := acq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*AppConfiguration, *AppConfigurationQuery]() + return withInterceptors[[]*AppConfiguration](ctx, acq, qr, acq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (acq *AppConfigurationQuery) AllX(ctx context.Context) []*AppConfiguration { + nodes, err := acq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of AppConfiguration IDs. +func (acq *AppConfigurationQuery) IDs(ctx context.Context) (ids []string, err error) { + if acq.ctx.Unique == nil && acq.path != nil { + acq.Unique(true) + } + ctx = setContextOp(ctx, acq.ctx, "IDs") + if err = acq.Select(appconfiguration.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (acq *AppConfigurationQuery) IDsX(ctx context.Context) []string { + ids, err := acq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (acq *AppConfigurationQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, acq.ctx, "Count") + if err := acq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, acq, querierCount[*AppConfigurationQuery](), acq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (acq *AppConfigurationQuery) CountX(ctx context.Context) int { + count, err := acq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (acq *AppConfigurationQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, acq.ctx, "Exist") + switch _, err := acq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (acq *AppConfigurationQuery) ExistX(ctx context.Context) bool { + exist, err := acq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AppConfigurationQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (acq *AppConfigurationQuery) Clone() *AppConfigurationQuery { + if acq == nil { + return nil + } + return &AppConfigurationQuery{ + config: acq.config, + ctx: acq.ctx.Clone(), + order: append([]appconfiguration.OrderOption{}, acq.order...), + inters: append([]Interceptor{}, acq.inters...), + predicates: append([]predicate.AppConfiguration{}, acq.predicates...), + // clone intermediate query. + sql: acq.sql.Clone(), + path: acq.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.AppConfiguration.Query(). +// GroupBy(appconfiguration.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (acq *AppConfigurationQuery) GroupBy(field string, fields ...string) *AppConfigurationGroupBy { + acq.ctx.Fields = append([]string{field}, fields...) + grbuild := &AppConfigurationGroupBy{build: acq} + grbuild.flds = &acq.ctx.Fields + grbuild.label = appconfiguration.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.AppConfiguration.Query(). +// Select(appconfiguration.FieldCreatedAt). +// Scan(ctx, &v) +func (acq *AppConfigurationQuery) Select(fields ...string) *AppConfigurationSelect { + acq.ctx.Fields = append(acq.ctx.Fields, fields...) + sbuild := &AppConfigurationSelect{AppConfigurationQuery: acq} + sbuild.label = appconfiguration.Label + sbuild.flds, sbuild.scan = &acq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a AppConfigurationSelect configured with the given aggregations. +func (acq *AppConfigurationQuery) Aggregate(fns ...AggregateFunc) *AppConfigurationSelect { + return acq.Select().Aggregate(fns...) +} + +func (acq *AppConfigurationQuery) prepareQuery(ctx context.Context) error { + for _, inter := range acq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, acq); err != nil { + return err + } + } + } + for _, f := range acq.ctx.Fields { + if !appconfiguration.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if acq.path != nil { + prev, err := acq.path(ctx) + if err != nil { + return err + } + acq.sql = prev + } + return nil +} + +func (acq *AppConfigurationQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AppConfiguration, error) { + var ( + nodes = []*AppConfiguration{} + _spec = acq.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*AppConfiguration).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &AppConfiguration{config: acq.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, acq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (acq *AppConfigurationQuery) sqlCount(ctx context.Context) (int, error) { + _spec := acq.querySpec() + _spec.Node.Columns = acq.ctx.Fields + if len(acq.ctx.Fields) > 0 { + _spec.Unique = acq.ctx.Unique != nil && *acq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, acq.driver, _spec) +} + +func (acq *AppConfigurationQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(appconfiguration.Table, appconfiguration.Columns, sqlgraph.NewFieldSpec(appconfiguration.FieldID, field.TypeString)) + _spec.From = acq.sql + if unique := acq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if acq.path != nil { + _spec.Unique = true + } + if fields := acq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, appconfiguration.FieldID) + for i := range fields { + if fields[i] != appconfiguration.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := acq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := acq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := acq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := acq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (acq *AppConfigurationQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(acq.driver.Dialect()) + t1 := builder.Table(appconfiguration.Table) + columns := acq.ctx.Fields + if len(columns) == 0 { + columns = appconfiguration.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if acq.sql != nil { + selector = acq.sql + selector.Select(selector.Columns(columns...)...) + } + if acq.ctx.Unique != nil && *acq.ctx.Unique { + selector.Distinct() + } + for _, p := range acq.predicates { + p(selector) + } + for _, p := range acq.order { + p(selector) + } + if offset := acq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := acq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// AppConfigurationGroupBy is the group-by builder for AppConfiguration entities. +type AppConfigurationGroupBy struct { + selector + build *AppConfigurationQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (acgb *AppConfigurationGroupBy) Aggregate(fns ...AggregateFunc) *AppConfigurationGroupBy { + acgb.fns = append(acgb.fns, fns...) + return acgb +} + +// Scan applies the selector query and scans the result into the given value. +func (acgb *AppConfigurationGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, acgb.build.ctx, "GroupBy") + if err := acgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AppConfigurationQuery, *AppConfigurationGroupBy](ctx, acgb.build, acgb, acgb.build.inters, v) +} + +func (acgb *AppConfigurationGroupBy) sqlScan(ctx context.Context, root *AppConfigurationQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(acgb.fns)) + for _, fn := range acgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*acgb.flds)+len(acgb.fns)) + for _, f := range *acgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*acgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := acgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// AppConfigurationSelect is the builder for selecting fields of AppConfiguration entities. +type AppConfigurationSelect struct { + *AppConfigurationQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (acs *AppConfigurationSelect) Aggregate(fns ...AggregateFunc) *AppConfigurationSelect { + acs.fns = append(acs.fns, fns...) + return acs +} + +// Scan applies the selector query and scans the result into the given value. +func (acs *AppConfigurationSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, acs.ctx, "Select") + if err := acs.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AppConfigurationQuery, *AppConfigurationSelect](ctx, acs.AppConfigurationQuery, acs, acs.inters, v) +} + +func (acs *AppConfigurationSelect) sqlScan(ctx context.Context, root *AppConfigurationQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(acs.fns)) + for _, fn := range acs.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*acs.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := acs.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/appconfiguration_update.go b/internal/database/ent/db/appconfiguration_update.go new file mode 100644 index 00000000..168e52c4 --- /dev/null +++ b/internal/database/ent/db/appconfiguration_update.go @@ -0,0 +1,345 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AppConfigurationUpdate is the builder for updating AppConfiguration entities. +type AppConfigurationUpdate struct { + config + hooks []Hook + mutation *AppConfigurationMutation +} + +// Where appends a list predicates to the AppConfigurationUpdate builder. +func (acu *AppConfigurationUpdate) Where(ps ...predicate.AppConfiguration) *AppConfigurationUpdate { + acu.mutation.Where(ps...) + return acu +} + +// SetUpdatedAt sets the "updated_at" field. +func (acu *AppConfigurationUpdate) SetUpdatedAt(i int) *AppConfigurationUpdate { + acu.mutation.ResetUpdatedAt() + acu.mutation.SetUpdatedAt(i) + return acu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (acu *AppConfigurationUpdate) AddUpdatedAt(i int) *AppConfigurationUpdate { + acu.mutation.AddUpdatedAt(i) + return acu +} + +// SetKey sets the "key" field. +func (acu *AppConfigurationUpdate) SetKey(s string) *AppConfigurationUpdate { + acu.mutation.SetKey(s) + return acu +} + +// SetNillableKey sets the "key" field if the given value is not nil. +func (acu *AppConfigurationUpdate) SetNillableKey(s *string) *AppConfigurationUpdate { + if s != nil { + acu.SetKey(*s) + } + return acu +} + +// SetValue sets the "value" field. +func (acu *AppConfigurationUpdate) SetValue(a any) *AppConfigurationUpdate { + acu.mutation.SetValue(a) + return acu +} + +// SetMetadata sets the "metadata" field. +func (acu *AppConfigurationUpdate) SetMetadata(m map[string]interface{}) *AppConfigurationUpdate { + acu.mutation.SetMetadata(m) + return acu +} + +// ClearMetadata clears the value of the "metadata" field. +func (acu *AppConfigurationUpdate) ClearMetadata() *AppConfigurationUpdate { + acu.mutation.ClearMetadata() + return acu +} + +// Mutation returns the AppConfigurationMutation object of the builder. +func (acu *AppConfigurationUpdate) Mutation() *AppConfigurationMutation { + return acu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (acu *AppConfigurationUpdate) Save(ctx context.Context) (int, error) { + acu.defaults() + return withHooks(ctx, acu.sqlSave, acu.mutation, acu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (acu *AppConfigurationUpdate) SaveX(ctx context.Context) int { + affected, err := acu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (acu *AppConfigurationUpdate) Exec(ctx context.Context) error { + _, err := acu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (acu *AppConfigurationUpdate) ExecX(ctx context.Context) { + if err := acu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (acu *AppConfigurationUpdate) defaults() { + if _, ok := acu.mutation.UpdatedAt(); !ok { + v := appconfiguration.UpdateDefaultUpdatedAt() + acu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (acu *AppConfigurationUpdate) check() error { + if v, ok := acu.mutation.Key(); ok { + if err := appconfiguration.KeyValidator(v); err != nil { + return &ValidationError{Name: "key", err: fmt.Errorf(`db: validator failed for field "AppConfiguration.key": %w`, err)} + } + } + return nil +} + +func (acu *AppConfigurationUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := acu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(appconfiguration.Table, appconfiguration.Columns, sqlgraph.NewFieldSpec(appconfiguration.FieldID, field.TypeString)) + if ps := acu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := acu.mutation.UpdatedAt(); ok { + _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := acu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := acu.mutation.Key(); ok { + _spec.SetField(appconfiguration.FieldKey, field.TypeString, value) + } + if value, ok := acu.mutation.Value(); ok { + _spec.SetField(appconfiguration.FieldValue, field.TypeJSON, value) + } + if value, ok := acu.mutation.Metadata(); ok { + _spec.SetField(appconfiguration.FieldMetadata, field.TypeJSON, value) + } + if acu.mutation.MetadataCleared() { + _spec.ClearField(appconfiguration.FieldMetadata, field.TypeJSON) + } + if n, err = sqlgraph.UpdateNodes(ctx, acu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{appconfiguration.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + acu.mutation.done = true + return n, nil +} + +// AppConfigurationUpdateOne is the builder for updating a single AppConfiguration entity. +type AppConfigurationUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AppConfigurationMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (acuo *AppConfigurationUpdateOne) SetUpdatedAt(i int) *AppConfigurationUpdateOne { + acuo.mutation.ResetUpdatedAt() + acuo.mutation.SetUpdatedAt(i) + return acuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (acuo *AppConfigurationUpdateOne) AddUpdatedAt(i int) *AppConfigurationUpdateOne { + acuo.mutation.AddUpdatedAt(i) + return acuo +} + +// SetKey sets the "key" field. +func (acuo *AppConfigurationUpdateOne) SetKey(s string) *AppConfigurationUpdateOne { + acuo.mutation.SetKey(s) + return acuo +} + +// SetNillableKey sets the "key" field if the given value is not nil. +func (acuo *AppConfigurationUpdateOne) SetNillableKey(s *string) *AppConfigurationUpdateOne { + if s != nil { + acuo.SetKey(*s) + } + return acuo +} + +// SetValue sets the "value" field. +func (acuo *AppConfigurationUpdateOne) SetValue(a any) *AppConfigurationUpdateOne { + acuo.mutation.SetValue(a) + return acuo +} + +// SetMetadata sets the "metadata" field. +func (acuo *AppConfigurationUpdateOne) SetMetadata(m map[string]interface{}) *AppConfigurationUpdateOne { + acuo.mutation.SetMetadata(m) + return acuo +} + +// ClearMetadata clears the value of the "metadata" field. +func (acuo *AppConfigurationUpdateOne) ClearMetadata() *AppConfigurationUpdateOne { + acuo.mutation.ClearMetadata() + return acuo +} + +// Mutation returns the AppConfigurationMutation object of the builder. +func (acuo *AppConfigurationUpdateOne) Mutation() *AppConfigurationMutation { + return acuo.mutation +} + +// Where appends a list predicates to the AppConfigurationUpdate builder. +func (acuo *AppConfigurationUpdateOne) Where(ps ...predicate.AppConfiguration) *AppConfigurationUpdateOne { + acuo.mutation.Where(ps...) + return acuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (acuo *AppConfigurationUpdateOne) Select(field string, fields ...string) *AppConfigurationUpdateOne { + acuo.fields = append([]string{field}, fields...) + return acuo +} + +// Save executes the query and returns the updated AppConfiguration entity. +func (acuo *AppConfigurationUpdateOne) Save(ctx context.Context) (*AppConfiguration, error) { + acuo.defaults() + return withHooks(ctx, acuo.sqlSave, acuo.mutation, acuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (acuo *AppConfigurationUpdateOne) SaveX(ctx context.Context) *AppConfiguration { + node, err := acuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (acuo *AppConfigurationUpdateOne) Exec(ctx context.Context) error { + _, err := acuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (acuo *AppConfigurationUpdateOne) ExecX(ctx context.Context) { + if err := acuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (acuo *AppConfigurationUpdateOne) defaults() { + if _, ok := acuo.mutation.UpdatedAt(); !ok { + v := appconfiguration.UpdateDefaultUpdatedAt() + acuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (acuo *AppConfigurationUpdateOne) check() error { + if v, ok := acuo.mutation.Key(); ok { + if err := appconfiguration.KeyValidator(v); err != nil { + return &ValidationError{Name: "key", err: fmt.Errorf(`db: validator failed for field "AppConfiguration.key": %w`, err)} + } + } + return nil +} + +func (acuo *AppConfigurationUpdateOne) sqlSave(ctx context.Context) (_node *AppConfiguration, err error) { + if err := acuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(appconfiguration.Table, appconfiguration.Columns, sqlgraph.NewFieldSpec(appconfiguration.FieldID, field.TypeString)) + id, ok := acuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "AppConfiguration.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := acuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, appconfiguration.FieldID) + for _, f := range fields { + if !appconfiguration.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != appconfiguration.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := acuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := acuo.mutation.UpdatedAt(); ok { + _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := acuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := acuo.mutation.Key(); ok { + _spec.SetField(appconfiguration.FieldKey, field.TypeString, value) + } + if value, ok := acuo.mutation.Value(); ok { + _spec.SetField(appconfiguration.FieldValue, field.TypeJSON, value) + } + if value, ok := acuo.mutation.Metadata(); ok { + _spec.SetField(appconfiguration.FieldMetadata, field.TypeJSON, value) + } + if acuo.mutation.MetadataCleared() { + _spec.ClearField(appconfiguration.FieldMetadata, field.TypeJSON) + } + _node = &AppConfiguration{config: acuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, acuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{appconfiguration.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + acuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/applicationcommand.go b/internal/database/ent/db/applicationcommand.go new file mode 100644 index 00000000..ea3626b9 --- /dev/null +++ b/internal/database/ent/db/applicationcommand.go @@ -0,0 +1,147 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" +) + +// ApplicationCommand is the model entity for the ApplicationCommand schema. +type ApplicationCommand struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // Version holds the value of the "version" field. + Version string `json:"version,omitempty"` + // OptionsHash holds the value of the "options_hash" field. + OptionsHash string `json:"options_hash,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*ApplicationCommand) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case applicationcommand.FieldCreatedAt, applicationcommand.FieldUpdatedAt: + values[i] = new(sql.NullInt64) + case applicationcommand.FieldID, applicationcommand.FieldName, applicationcommand.FieldVersion, applicationcommand.FieldOptionsHash: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the ApplicationCommand fields. +func (ac *ApplicationCommand) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case applicationcommand.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + ac.ID = value.String + } + case applicationcommand.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + ac.CreatedAt = int(value.Int64) + } + case applicationcommand.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + ac.UpdatedAt = int(value.Int64) + } + case applicationcommand.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + ac.Name = value.String + } + case applicationcommand.FieldVersion: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field version", values[i]) + } else if value.Valid { + ac.Version = value.String + } + case applicationcommand.FieldOptionsHash: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field options_hash", values[i]) + } else if value.Valid { + ac.OptionsHash = value.String + } + default: + ac.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the ApplicationCommand. +// This includes values selected through modifiers, order, etc. +func (ac *ApplicationCommand) Value(name string) (ent.Value, error) { + return ac.selectValues.Get(name) +} + +// Update returns a builder for updating this ApplicationCommand. +// Note that you need to call ApplicationCommand.Unwrap() before calling this method if this ApplicationCommand +// was returned from a transaction, and the transaction was committed or rolled back. +func (ac *ApplicationCommand) Update() *ApplicationCommandUpdateOne { + return NewApplicationCommandClient(ac.config).UpdateOne(ac) +} + +// Unwrap unwraps the ApplicationCommand entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (ac *ApplicationCommand) Unwrap() *ApplicationCommand { + _tx, ok := ac.config.driver.(*txDriver) + if !ok { + panic("db: ApplicationCommand is not a transactional entity") + } + ac.config.driver = _tx.drv + return ac +} + +// String implements the fmt.Stringer. +func (ac *ApplicationCommand) String() string { + var builder strings.Builder + builder.WriteString("ApplicationCommand(") + builder.WriteString(fmt.Sprintf("id=%v, ", ac.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", ac.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", ac.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("name=") + builder.WriteString(ac.Name) + builder.WriteString(", ") + builder.WriteString("version=") + builder.WriteString(ac.Version) + builder.WriteString(", ") + builder.WriteString("options_hash=") + builder.WriteString(ac.OptionsHash) + builder.WriteByte(')') + return builder.String() +} + +// ApplicationCommands is a parsable slice of ApplicationCommand. +type ApplicationCommands []*ApplicationCommand diff --git a/internal/database/ent/db/applicationcommand/applicationcommand.go b/internal/database/ent/db/applicationcommand/applicationcommand.go new file mode 100644 index 00000000..6860891e --- /dev/null +++ b/internal/database/ent/db/applicationcommand/applicationcommand.go @@ -0,0 +1,96 @@ +// Code generated by ent, DO NOT EDIT. + +package applicationcommand + +import ( + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the applicationcommand type in the database. + Label = "application_command" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldVersion holds the string denoting the version field in the database. + FieldVersion = "version" + // FieldOptionsHash holds the string denoting the options_hash field in the database. + FieldOptionsHash = "options_hash" + // Table holds the table name of the applicationcommand in the database. + Table = "application_commands" +) + +// Columns holds all SQL columns for applicationcommand fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldName, + FieldVersion, + FieldOptionsHash, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // NameValidator is a validator for the "name" field. It is called by the builders before save. + NameValidator func(string) error + // VersionValidator is a validator for the "version" field. It is called by the builders before save. + VersionValidator func(string) error + // OptionsHashValidator is a validator for the "options_hash" field. It is called by the builders before save. + OptionsHashValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// OrderOption defines the ordering options for the ApplicationCommand queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByVersion orders the results by the version field. +func ByVersion(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldVersion, opts...).ToFunc() +} + +// ByOptionsHash orders the results by the options_hash field. +func ByOptionsHash(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldOptionsHash, opts...).ToFunc() +} diff --git a/internal/database/ent/db/applicationcommand/where.go b/internal/database/ent/db/applicationcommand/where.go new file mode 100644 index 00000000..d281b98f --- /dev/null +++ b/internal/database/ent/db/applicationcommand/where.go @@ -0,0 +1,378 @@ +// Code generated by ent, DO NOT EDIT. + +package applicationcommand + +import ( + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldName, v)) +} + +// Version applies equality check predicate on the "version" field. It's identical to VersionEQ. +func Version(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldVersion, v)) +} + +// OptionsHash applies equality check predicate on the "options_hash" field. It's identical to OptionsHashEQ. +func OptionsHash(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldOptionsHash, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldContainsFold(FieldName, v)) +} + +// VersionEQ applies the EQ predicate on the "version" field. +func VersionEQ(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldVersion, v)) +} + +// VersionNEQ applies the NEQ predicate on the "version" field. +func VersionNEQ(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNEQ(FieldVersion, v)) +} + +// VersionIn applies the In predicate on the "version" field. +func VersionIn(vs ...string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldIn(FieldVersion, vs...)) +} + +// VersionNotIn applies the NotIn predicate on the "version" field. +func VersionNotIn(vs ...string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNotIn(FieldVersion, vs...)) +} + +// VersionGT applies the GT predicate on the "version" field. +func VersionGT(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGT(FieldVersion, v)) +} + +// VersionGTE applies the GTE predicate on the "version" field. +func VersionGTE(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGTE(FieldVersion, v)) +} + +// VersionLT applies the LT predicate on the "version" field. +func VersionLT(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLT(FieldVersion, v)) +} + +// VersionLTE applies the LTE predicate on the "version" field. +func VersionLTE(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLTE(FieldVersion, v)) +} + +// VersionContains applies the Contains predicate on the "version" field. +func VersionContains(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldContains(FieldVersion, v)) +} + +// VersionHasPrefix applies the HasPrefix predicate on the "version" field. +func VersionHasPrefix(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldHasPrefix(FieldVersion, v)) +} + +// VersionHasSuffix applies the HasSuffix predicate on the "version" field. +func VersionHasSuffix(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldHasSuffix(FieldVersion, v)) +} + +// VersionEqualFold applies the EqualFold predicate on the "version" field. +func VersionEqualFold(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEqualFold(FieldVersion, v)) +} + +// VersionContainsFold applies the ContainsFold predicate on the "version" field. +func VersionContainsFold(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldContainsFold(FieldVersion, v)) +} + +// OptionsHashEQ applies the EQ predicate on the "options_hash" field. +func OptionsHashEQ(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEQ(FieldOptionsHash, v)) +} + +// OptionsHashNEQ applies the NEQ predicate on the "options_hash" field. +func OptionsHashNEQ(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNEQ(FieldOptionsHash, v)) +} + +// OptionsHashIn applies the In predicate on the "options_hash" field. +func OptionsHashIn(vs ...string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldIn(FieldOptionsHash, vs...)) +} + +// OptionsHashNotIn applies the NotIn predicate on the "options_hash" field. +func OptionsHashNotIn(vs ...string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldNotIn(FieldOptionsHash, vs...)) +} + +// OptionsHashGT applies the GT predicate on the "options_hash" field. +func OptionsHashGT(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGT(FieldOptionsHash, v)) +} + +// OptionsHashGTE applies the GTE predicate on the "options_hash" field. +func OptionsHashGTE(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldGTE(FieldOptionsHash, v)) +} + +// OptionsHashLT applies the LT predicate on the "options_hash" field. +func OptionsHashLT(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLT(FieldOptionsHash, v)) +} + +// OptionsHashLTE applies the LTE predicate on the "options_hash" field. +func OptionsHashLTE(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldLTE(FieldOptionsHash, v)) +} + +// OptionsHashContains applies the Contains predicate on the "options_hash" field. +func OptionsHashContains(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldContains(FieldOptionsHash, v)) +} + +// OptionsHashHasPrefix applies the HasPrefix predicate on the "options_hash" field. +func OptionsHashHasPrefix(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldHasPrefix(FieldOptionsHash, v)) +} + +// OptionsHashHasSuffix applies the HasSuffix predicate on the "options_hash" field. +func OptionsHashHasSuffix(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldHasSuffix(FieldOptionsHash, v)) +} + +// OptionsHashEqualFold applies the EqualFold predicate on the "options_hash" field. +func OptionsHashEqualFold(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldEqualFold(FieldOptionsHash, v)) +} + +// OptionsHashContainsFold applies the ContainsFold predicate on the "options_hash" field. +func OptionsHashContainsFold(v string) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.FieldContainsFold(FieldOptionsHash, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.ApplicationCommand) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.ApplicationCommand) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.ApplicationCommand) predicate.ApplicationCommand { + return predicate.ApplicationCommand(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/applicationcommand_create.go b/internal/database/ent/db/applicationcommand_create.go new file mode 100644 index 00000000..14f3abb9 --- /dev/null +++ b/internal/database/ent/db/applicationcommand_create.go @@ -0,0 +1,303 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" +) + +// ApplicationCommandCreate is the builder for creating a ApplicationCommand entity. +type ApplicationCommandCreate struct { + config + mutation *ApplicationCommandMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (acc *ApplicationCommandCreate) SetCreatedAt(i int) *ApplicationCommandCreate { + acc.mutation.SetCreatedAt(i) + return acc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (acc *ApplicationCommandCreate) SetNillableCreatedAt(i *int) *ApplicationCommandCreate { + if i != nil { + acc.SetCreatedAt(*i) + } + return acc +} + +// SetUpdatedAt sets the "updated_at" field. +func (acc *ApplicationCommandCreate) SetUpdatedAt(i int) *ApplicationCommandCreate { + acc.mutation.SetUpdatedAt(i) + return acc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (acc *ApplicationCommandCreate) SetNillableUpdatedAt(i *int) *ApplicationCommandCreate { + if i != nil { + acc.SetUpdatedAt(*i) + } + return acc +} + +// SetName sets the "name" field. +func (acc *ApplicationCommandCreate) SetName(s string) *ApplicationCommandCreate { + acc.mutation.SetName(s) + return acc +} + +// SetVersion sets the "version" field. +func (acc *ApplicationCommandCreate) SetVersion(s string) *ApplicationCommandCreate { + acc.mutation.SetVersion(s) + return acc +} + +// SetOptionsHash sets the "options_hash" field. +func (acc *ApplicationCommandCreate) SetOptionsHash(s string) *ApplicationCommandCreate { + acc.mutation.SetOptionsHash(s) + return acc +} + +// SetID sets the "id" field. +func (acc *ApplicationCommandCreate) SetID(s string) *ApplicationCommandCreate { + acc.mutation.SetID(s) + return acc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (acc *ApplicationCommandCreate) SetNillableID(s *string) *ApplicationCommandCreate { + if s != nil { + acc.SetID(*s) + } + return acc +} + +// Mutation returns the ApplicationCommandMutation object of the builder. +func (acc *ApplicationCommandCreate) Mutation() *ApplicationCommandMutation { + return acc.mutation +} + +// Save creates the ApplicationCommand in the database. +func (acc *ApplicationCommandCreate) Save(ctx context.Context) (*ApplicationCommand, error) { + acc.defaults() + return withHooks(ctx, acc.sqlSave, acc.mutation, acc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (acc *ApplicationCommandCreate) SaveX(ctx context.Context) *ApplicationCommand { + v, err := acc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (acc *ApplicationCommandCreate) Exec(ctx context.Context) error { + _, err := acc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (acc *ApplicationCommandCreate) ExecX(ctx context.Context) { + if err := acc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (acc *ApplicationCommandCreate) defaults() { + if _, ok := acc.mutation.CreatedAt(); !ok { + v := applicationcommand.DefaultCreatedAt() + acc.mutation.SetCreatedAt(v) + } + if _, ok := acc.mutation.UpdatedAt(); !ok { + v := applicationcommand.DefaultUpdatedAt() + acc.mutation.SetUpdatedAt(v) + } + if _, ok := acc.mutation.ID(); !ok { + v := applicationcommand.DefaultID() + acc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (acc *ApplicationCommandCreate) check() error { + if _, ok := acc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "ApplicationCommand.created_at"`)} + } + if _, ok := acc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "ApplicationCommand.updated_at"`)} + } + if _, ok := acc.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`db: missing required field "ApplicationCommand.name"`)} + } + if v, ok := acc.mutation.Name(); ok { + if err := applicationcommand.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "ApplicationCommand.name": %w`, err)} + } + } + if _, ok := acc.mutation.Version(); !ok { + return &ValidationError{Name: "version", err: errors.New(`db: missing required field "ApplicationCommand.version"`)} + } + if v, ok := acc.mutation.Version(); ok { + if err := applicationcommand.VersionValidator(v); err != nil { + return &ValidationError{Name: "version", err: fmt.Errorf(`db: validator failed for field "ApplicationCommand.version": %w`, err)} + } + } + if _, ok := acc.mutation.OptionsHash(); !ok { + return &ValidationError{Name: "options_hash", err: errors.New(`db: missing required field "ApplicationCommand.options_hash"`)} + } + if v, ok := acc.mutation.OptionsHash(); ok { + if err := applicationcommand.OptionsHashValidator(v); err != nil { + return &ValidationError{Name: "options_hash", err: fmt.Errorf(`db: validator failed for field "ApplicationCommand.options_hash": %w`, err)} + } + } + return nil +} + +func (acc *ApplicationCommandCreate) sqlSave(ctx context.Context) (*ApplicationCommand, error) { + if err := acc.check(); err != nil { + return nil, err + } + _node, _spec := acc.createSpec() + if err := sqlgraph.CreateNode(ctx, acc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected ApplicationCommand.ID type: %T", _spec.ID.Value) + } + } + acc.mutation.id = &_node.ID + acc.mutation.done = true + return _node, nil +} + +func (acc *ApplicationCommandCreate) createSpec() (*ApplicationCommand, *sqlgraph.CreateSpec) { + var ( + _node = &ApplicationCommand{config: acc.config} + _spec = sqlgraph.NewCreateSpec(applicationcommand.Table, sqlgraph.NewFieldSpec(applicationcommand.FieldID, field.TypeString)) + ) + if id, ok := acc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := acc.mutation.CreatedAt(); ok { + _spec.SetField(applicationcommand.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := acc.mutation.UpdatedAt(); ok { + _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := acc.mutation.Name(); ok { + _spec.SetField(applicationcommand.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := acc.mutation.Version(); ok { + _spec.SetField(applicationcommand.FieldVersion, field.TypeString, value) + _node.Version = value + } + if value, ok := acc.mutation.OptionsHash(); ok { + _spec.SetField(applicationcommand.FieldOptionsHash, field.TypeString, value) + _node.OptionsHash = value + } + return _node, _spec +} + +// ApplicationCommandCreateBulk is the builder for creating many ApplicationCommand entities in bulk. +type ApplicationCommandCreateBulk struct { + config + err error + builders []*ApplicationCommandCreate +} + +// Save creates the ApplicationCommand entities in the database. +func (accb *ApplicationCommandCreateBulk) Save(ctx context.Context) ([]*ApplicationCommand, error) { + if accb.err != nil { + return nil, accb.err + } + specs := make([]*sqlgraph.CreateSpec, len(accb.builders)) + nodes := make([]*ApplicationCommand, len(accb.builders)) + mutators := make([]Mutator, len(accb.builders)) + for i := range accb.builders { + func(i int, root context.Context) { + builder := accb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*ApplicationCommandMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, accb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, accb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, accb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (accb *ApplicationCommandCreateBulk) SaveX(ctx context.Context) []*ApplicationCommand { + v, err := accb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (accb *ApplicationCommandCreateBulk) Exec(ctx context.Context) error { + _, err := accb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (accb *ApplicationCommandCreateBulk) ExecX(ctx context.Context) { + if err := accb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/applicationcommand_delete.go b/internal/database/ent/db/applicationcommand_delete.go new file mode 100644 index 00000000..547e4399 --- /dev/null +++ b/internal/database/ent/db/applicationcommand_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ApplicationCommandDelete is the builder for deleting a ApplicationCommand entity. +type ApplicationCommandDelete struct { + config + hooks []Hook + mutation *ApplicationCommandMutation +} + +// Where appends a list predicates to the ApplicationCommandDelete builder. +func (acd *ApplicationCommandDelete) Where(ps ...predicate.ApplicationCommand) *ApplicationCommandDelete { + acd.mutation.Where(ps...) + return acd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (acd *ApplicationCommandDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, acd.sqlExec, acd.mutation, acd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (acd *ApplicationCommandDelete) ExecX(ctx context.Context) int { + n, err := acd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (acd *ApplicationCommandDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(applicationcommand.Table, sqlgraph.NewFieldSpec(applicationcommand.FieldID, field.TypeString)) + if ps := acd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, acd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + acd.mutation.done = true + return affected, err +} + +// ApplicationCommandDeleteOne is the builder for deleting a single ApplicationCommand entity. +type ApplicationCommandDeleteOne struct { + acd *ApplicationCommandDelete +} + +// Where appends a list predicates to the ApplicationCommandDelete builder. +func (acdo *ApplicationCommandDeleteOne) Where(ps ...predicate.ApplicationCommand) *ApplicationCommandDeleteOne { + acdo.acd.mutation.Where(ps...) + return acdo +} + +// Exec executes the deletion query. +func (acdo *ApplicationCommandDeleteOne) Exec(ctx context.Context) error { + n, err := acdo.acd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{applicationcommand.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (acdo *ApplicationCommandDeleteOne) ExecX(ctx context.Context) { + if err := acdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/applicationcommand_query.go b/internal/database/ent/db/applicationcommand_query.go new file mode 100644 index 00000000..5c45967e --- /dev/null +++ b/internal/database/ent/db/applicationcommand_query.go @@ -0,0 +1,526 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ApplicationCommandQuery is the builder for querying ApplicationCommand entities. +type ApplicationCommandQuery struct { + config + ctx *QueryContext + order []applicationcommand.OrderOption + inters []Interceptor + predicates []predicate.ApplicationCommand + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the ApplicationCommandQuery builder. +func (acq *ApplicationCommandQuery) Where(ps ...predicate.ApplicationCommand) *ApplicationCommandQuery { + acq.predicates = append(acq.predicates, ps...) + return acq +} + +// Limit the number of records to be returned by this query. +func (acq *ApplicationCommandQuery) Limit(limit int) *ApplicationCommandQuery { + acq.ctx.Limit = &limit + return acq +} + +// Offset to start from. +func (acq *ApplicationCommandQuery) Offset(offset int) *ApplicationCommandQuery { + acq.ctx.Offset = &offset + return acq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (acq *ApplicationCommandQuery) Unique(unique bool) *ApplicationCommandQuery { + acq.ctx.Unique = &unique + return acq +} + +// Order specifies how the records should be ordered. +func (acq *ApplicationCommandQuery) Order(o ...applicationcommand.OrderOption) *ApplicationCommandQuery { + acq.order = append(acq.order, o...) + return acq +} + +// First returns the first ApplicationCommand entity from the query. +// Returns a *NotFoundError when no ApplicationCommand was found. +func (acq *ApplicationCommandQuery) First(ctx context.Context) (*ApplicationCommand, error) { + nodes, err := acq.Limit(1).All(setContextOp(ctx, acq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{applicationcommand.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (acq *ApplicationCommandQuery) FirstX(ctx context.Context) *ApplicationCommand { + node, err := acq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first ApplicationCommand ID from the query. +// Returns a *NotFoundError when no ApplicationCommand ID was found. +func (acq *ApplicationCommandQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = acq.Limit(1).IDs(setContextOp(ctx, acq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{applicationcommand.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (acq *ApplicationCommandQuery) FirstIDX(ctx context.Context) string { + id, err := acq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single ApplicationCommand entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one ApplicationCommand entity is found. +// Returns a *NotFoundError when no ApplicationCommand entities are found. +func (acq *ApplicationCommandQuery) Only(ctx context.Context) (*ApplicationCommand, error) { + nodes, err := acq.Limit(2).All(setContextOp(ctx, acq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{applicationcommand.Label} + default: + return nil, &NotSingularError{applicationcommand.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (acq *ApplicationCommandQuery) OnlyX(ctx context.Context) *ApplicationCommand { + node, err := acq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only ApplicationCommand ID in the query. +// Returns a *NotSingularError when more than one ApplicationCommand ID is found. +// Returns a *NotFoundError when no entities are found. +func (acq *ApplicationCommandQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = acq.Limit(2).IDs(setContextOp(ctx, acq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{applicationcommand.Label} + default: + err = &NotSingularError{applicationcommand.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (acq *ApplicationCommandQuery) OnlyIDX(ctx context.Context) string { + id, err := acq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of ApplicationCommands. +func (acq *ApplicationCommandQuery) All(ctx context.Context) ([]*ApplicationCommand, error) { + ctx = setContextOp(ctx, acq.ctx, "All") + if err := acq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*ApplicationCommand, *ApplicationCommandQuery]() + return withInterceptors[[]*ApplicationCommand](ctx, acq, qr, acq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (acq *ApplicationCommandQuery) AllX(ctx context.Context) []*ApplicationCommand { + nodes, err := acq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of ApplicationCommand IDs. +func (acq *ApplicationCommandQuery) IDs(ctx context.Context) (ids []string, err error) { + if acq.ctx.Unique == nil && acq.path != nil { + acq.Unique(true) + } + ctx = setContextOp(ctx, acq.ctx, "IDs") + if err = acq.Select(applicationcommand.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (acq *ApplicationCommandQuery) IDsX(ctx context.Context) []string { + ids, err := acq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (acq *ApplicationCommandQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, acq.ctx, "Count") + if err := acq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, acq, querierCount[*ApplicationCommandQuery](), acq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (acq *ApplicationCommandQuery) CountX(ctx context.Context) int { + count, err := acq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (acq *ApplicationCommandQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, acq.ctx, "Exist") + switch _, err := acq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (acq *ApplicationCommandQuery) ExistX(ctx context.Context) bool { + exist, err := acq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the ApplicationCommandQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (acq *ApplicationCommandQuery) Clone() *ApplicationCommandQuery { + if acq == nil { + return nil + } + return &ApplicationCommandQuery{ + config: acq.config, + ctx: acq.ctx.Clone(), + order: append([]applicationcommand.OrderOption{}, acq.order...), + inters: append([]Interceptor{}, acq.inters...), + predicates: append([]predicate.ApplicationCommand{}, acq.predicates...), + // clone intermediate query. + sql: acq.sql.Clone(), + path: acq.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.ApplicationCommand.Query(). +// GroupBy(applicationcommand.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (acq *ApplicationCommandQuery) GroupBy(field string, fields ...string) *ApplicationCommandGroupBy { + acq.ctx.Fields = append([]string{field}, fields...) + grbuild := &ApplicationCommandGroupBy{build: acq} + grbuild.flds = &acq.ctx.Fields + grbuild.label = applicationcommand.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.ApplicationCommand.Query(). +// Select(applicationcommand.FieldCreatedAt). +// Scan(ctx, &v) +func (acq *ApplicationCommandQuery) Select(fields ...string) *ApplicationCommandSelect { + acq.ctx.Fields = append(acq.ctx.Fields, fields...) + sbuild := &ApplicationCommandSelect{ApplicationCommandQuery: acq} + sbuild.label = applicationcommand.Label + sbuild.flds, sbuild.scan = &acq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a ApplicationCommandSelect configured with the given aggregations. +func (acq *ApplicationCommandQuery) Aggregate(fns ...AggregateFunc) *ApplicationCommandSelect { + return acq.Select().Aggregate(fns...) +} + +func (acq *ApplicationCommandQuery) prepareQuery(ctx context.Context) error { + for _, inter := range acq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, acq); err != nil { + return err + } + } + } + for _, f := range acq.ctx.Fields { + if !applicationcommand.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if acq.path != nil { + prev, err := acq.path(ctx) + if err != nil { + return err + } + acq.sql = prev + } + return nil +} + +func (acq *ApplicationCommandQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ApplicationCommand, error) { + var ( + nodes = []*ApplicationCommand{} + _spec = acq.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*ApplicationCommand).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &ApplicationCommand{config: acq.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, acq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (acq *ApplicationCommandQuery) sqlCount(ctx context.Context) (int, error) { + _spec := acq.querySpec() + _spec.Node.Columns = acq.ctx.Fields + if len(acq.ctx.Fields) > 0 { + _spec.Unique = acq.ctx.Unique != nil && *acq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, acq.driver, _spec) +} + +func (acq *ApplicationCommandQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(applicationcommand.Table, applicationcommand.Columns, sqlgraph.NewFieldSpec(applicationcommand.FieldID, field.TypeString)) + _spec.From = acq.sql + if unique := acq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if acq.path != nil { + _spec.Unique = true + } + if fields := acq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, applicationcommand.FieldID) + for i := range fields { + if fields[i] != applicationcommand.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := acq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := acq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := acq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := acq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (acq *ApplicationCommandQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(acq.driver.Dialect()) + t1 := builder.Table(applicationcommand.Table) + columns := acq.ctx.Fields + if len(columns) == 0 { + columns = applicationcommand.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if acq.sql != nil { + selector = acq.sql + selector.Select(selector.Columns(columns...)...) + } + if acq.ctx.Unique != nil && *acq.ctx.Unique { + selector.Distinct() + } + for _, p := range acq.predicates { + p(selector) + } + for _, p := range acq.order { + p(selector) + } + if offset := acq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := acq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ApplicationCommandGroupBy is the group-by builder for ApplicationCommand entities. +type ApplicationCommandGroupBy struct { + selector + build *ApplicationCommandQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (acgb *ApplicationCommandGroupBy) Aggregate(fns ...AggregateFunc) *ApplicationCommandGroupBy { + acgb.fns = append(acgb.fns, fns...) + return acgb +} + +// Scan applies the selector query and scans the result into the given value. +func (acgb *ApplicationCommandGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, acgb.build.ctx, "GroupBy") + if err := acgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ApplicationCommandQuery, *ApplicationCommandGroupBy](ctx, acgb.build, acgb, acgb.build.inters, v) +} + +func (acgb *ApplicationCommandGroupBy) sqlScan(ctx context.Context, root *ApplicationCommandQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(acgb.fns)) + for _, fn := range acgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*acgb.flds)+len(acgb.fns)) + for _, f := range *acgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*acgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := acgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// ApplicationCommandSelect is the builder for selecting fields of ApplicationCommand entities. +type ApplicationCommandSelect struct { + *ApplicationCommandQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (acs *ApplicationCommandSelect) Aggregate(fns ...AggregateFunc) *ApplicationCommandSelect { + acs.fns = append(acs.fns, fns...) + return acs +} + +// Scan applies the selector query and scans the result into the given value. +func (acs *ApplicationCommandSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, acs.ctx, "Select") + if err := acs.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ApplicationCommandQuery, *ApplicationCommandSelect](ctx, acs.ApplicationCommandQuery, acs, acs.inters, v) +} + +func (acs *ApplicationCommandSelect) sqlScan(ctx context.Context, root *ApplicationCommandQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(acs.fns)) + for _, fn := range acs.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*acs.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := acs.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/applicationcommand_update.go b/internal/database/ent/db/applicationcommand_update.go new file mode 100644 index 00000000..ebdf0db2 --- /dev/null +++ b/internal/database/ent/db/applicationcommand_update.go @@ -0,0 +1,379 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ApplicationCommandUpdate is the builder for updating ApplicationCommand entities. +type ApplicationCommandUpdate struct { + config + hooks []Hook + mutation *ApplicationCommandMutation +} + +// Where appends a list predicates to the ApplicationCommandUpdate builder. +func (acu *ApplicationCommandUpdate) Where(ps ...predicate.ApplicationCommand) *ApplicationCommandUpdate { + acu.mutation.Where(ps...) + return acu +} + +// SetUpdatedAt sets the "updated_at" field. +func (acu *ApplicationCommandUpdate) SetUpdatedAt(i int) *ApplicationCommandUpdate { + acu.mutation.ResetUpdatedAt() + acu.mutation.SetUpdatedAt(i) + return acu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (acu *ApplicationCommandUpdate) AddUpdatedAt(i int) *ApplicationCommandUpdate { + acu.mutation.AddUpdatedAt(i) + return acu +} + +// SetName sets the "name" field. +func (acu *ApplicationCommandUpdate) SetName(s string) *ApplicationCommandUpdate { + acu.mutation.SetName(s) + return acu +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (acu *ApplicationCommandUpdate) SetNillableName(s *string) *ApplicationCommandUpdate { + if s != nil { + acu.SetName(*s) + } + return acu +} + +// SetVersion sets the "version" field. +func (acu *ApplicationCommandUpdate) SetVersion(s string) *ApplicationCommandUpdate { + acu.mutation.SetVersion(s) + return acu +} + +// SetNillableVersion sets the "version" field if the given value is not nil. +func (acu *ApplicationCommandUpdate) SetNillableVersion(s *string) *ApplicationCommandUpdate { + if s != nil { + acu.SetVersion(*s) + } + return acu +} + +// SetOptionsHash sets the "options_hash" field. +func (acu *ApplicationCommandUpdate) SetOptionsHash(s string) *ApplicationCommandUpdate { + acu.mutation.SetOptionsHash(s) + return acu +} + +// SetNillableOptionsHash sets the "options_hash" field if the given value is not nil. +func (acu *ApplicationCommandUpdate) SetNillableOptionsHash(s *string) *ApplicationCommandUpdate { + if s != nil { + acu.SetOptionsHash(*s) + } + return acu +} + +// Mutation returns the ApplicationCommandMutation object of the builder. +func (acu *ApplicationCommandUpdate) Mutation() *ApplicationCommandMutation { + return acu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (acu *ApplicationCommandUpdate) Save(ctx context.Context) (int, error) { + acu.defaults() + return withHooks(ctx, acu.sqlSave, acu.mutation, acu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (acu *ApplicationCommandUpdate) SaveX(ctx context.Context) int { + affected, err := acu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (acu *ApplicationCommandUpdate) Exec(ctx context.Context) error { + _, err := acu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (acu *ApplicationCommandUpdate) ExecX(ctx context.Context) { + if err := acu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (acu *ApplicationCommandUpdate) defaults() { + if _, ok := acu.mutation.UpdatedAt(); !ok { + v := applicationcommand.UpdateDefaultUpdatedAt() + acu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (acu *ApplicationCommandUpdate) check() error { + if v, ok := acu.mutation.Name(); ok { + if err := applicationcommand.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "ApplicationCommand.name": %w`, err)} + } + } + if v, ok := acu.mutation.Version(); ok { + if err := applicationcommand.VersionValidator(v); err != nil { + return &ValidationError{Name: "version", err: fmt.Errorf(`db: validator failed for field "ApplicationCommand.version": %w`, err)} + } + } + if v, ok := acu.mutation.OptionsHash(); ok { + if err := applicationcommand.OptionsHashValidator(v); err != nil { + return &ValidationError{Name: "options_hash", err: fmt.Errorf(`db: validator failed for field "ApplicationCommand.options_hash": %w`, err)} + } + } + return nil +} + +func (acu *ApplicationCommandUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := acu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(applicationcommand.Table, applicationcommand.Columns, sqlgraph.NewFieldSpec(applicationcommand.FieldID, field.TypeString)) + if ps := acu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := acu.mutation.UpdatedAt(); ok { + _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := acu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := acu.mutation.Name(); ok { + _spec.SetField(applicationcommand.FieldName, field.TypeString, value) + } + if value, ok := acu.mutation.Version(); ok { + _spec.SetField(applicationcommand.FieldVersion, field.TypeString, value) + } + if value, ok := acu.mutation.OptionsHash(); ok { + _spec.SetField(applicationcommand.FieldOptionsHash, field.TypeString, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, acu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{applicationcommand.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + acu.mutation.done = true + return n, nil +} + +// ApplicationCommandUpdateOne is the builder for updating a single ApplicationCommand entity. +type ApplicationCommandUpdateOne struct { + config + fields []string + hooks []Hook + mutation *ApplicationCommandMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (acuo *ApplicationCommandUpdateOne) SetUpdatedAt(i int) *ApplicationCommandUpdateOne { + acuo.mutation.ResetUpdatedAt() + acuo.mutation.SetUpdatedAt(i) + return acuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (acuo *ApplicationCommandUpdateOne) AddUpdatedAt(i int) *ApplicationCommandUpdateOne { + acuo.mutation.AddUpdatedAt(i) + return acuo +} + +// SetName sets the "name" field. +func (acuo *ApplicationCommandUpdateOne) SetName(s string) *ApplicationCommandUpdateOne { + acuo.mutation.SetName(s) + return acuo +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (acuo *ApplicationCommandUpdateOne) SetNillableName(s *string) *ApplicationCommandUpdateOne { + if s != nil { + acuo.SetName(*s) + } + return acuo +} + +// SetVersion sets the "version" field. +func (acuo *ApplicationCommandUpdateOne) SetVersion(s string) *ApplicationCommandUpdateOne { + acuo.mutation.SetVersion(s) + return acuo +} + +// SetNillableVersion sets the "version" field if the given value is not nil. +func (acuo *ApplicationCommandUpdateOne) SetNillableVersion(s *string) *ApplicationCommandUpdateOne { + if s != nil { + acuo.SetVersion(*s) + } + return acuo +} + +// SetOptionsHash sets the "options_hash" field. +func (acuo *ApplicationCommandUpdateOne) SetOptionsHash(s string) *ApplicationCommandUpdateOne { + acuo.mutation.SetOptionsHash(s) + return acuo +} + +// SetNillableOptionsHash sets the "options_hash" field if the given value is not nil. +func (acuo *ApplicationCommandUpdateOne) SetNillableOptionsHash(s *string) *ApplicationCommandUpdateOne { + if s != nil { + acuo.SetOptionsHash(*s) + } + return acuo +} + +// Mutation returns the ApplicationCommandMutation object of the builder. +func (acuo *ApplicationCommandUpdateOne) Mutation() *ApplicationCommandMutation { + return acuo.mutation +} + +// Where appends a list predicates to the ApplicationCommandUpdate builder. +func (acuo *ApplicationCommandUpdateOne) Where(ps ...predicate.ApplicationCommand) *ApplicationCommandUpdateOne { + acuo.mutation.Where(ps...) + return acuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (acuo *ApplicationCommandUpdateOne) Select(field string, fields ...string) *ApplicationCommandUpdateOne { + acuo.fields = append([]string{field}, fields...) + return acuo +} + +// Save executes the query and returns the updated ApplicationCommand entity. +func (acuo *ApplicationCommandUpdateOne) Save(ctx context.Context) (*ApplicationCommand, error) { + acuo.defaults() + return withHooks(ctx, acuo.sqlSave, acuo.mutation, acuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (acuo *ApplicationCommandUpdateOne) SaveX(ctx context.Context) *ApplicationCommand { + node, err := acuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (acuo *ApplicationCommandUpdateOne) Exec(ctx context.Context) error { + _, err := acuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (acuo *ApplicationCommandUpdateOne) ExecX(ctx context.Context) { + if err := acuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (acuo *ApplicationCommandUpdateOne) defaults() { + if _, ok := acuo.mutation.UpdatedAt(); !ok { + v := applicationcommand.UpdateDefaultUpdatedAt() + acuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (acuo *ApplicationCommandUpdateOne) check() error { + if v, ok := acuo.mutation.Name(); ok { + if err := applicationcommand.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "ApplicationCommand.name": %w`, err)} + } + } + if v, ok := acuo.mutation.Version(); ok { + if err := applicationcommand.VersionValidator(v); err != nil { + return &ValidationError{Name: "version", err: fmt.Errorf(`db: validator failed for field "ApplicationCommand.version": %w`, err)} + } + } + if v, ok := acuo.mutation.OptionsHash(); ok { + if err := applicationcommand.OptionsHashValidator(v); err != nil { + return &ValidationError{Name: "options_hash", err: fmt.Errorf(`db: validator failed for field "ApplicationCommand.options_hash": %w`, err)} + } + } + return nil +} + +func (acuo *ApplicationCommandUpdateOne) sqlSave(ctx context.Context) (_node *ApplicationCommand, err error) { + if err := acuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(applicationcommand.Table, applicationcommand.Columns, sqlgraph.NewFieldSpec(applicationcommand.FieldID, field.TypeString)) + id, ok := acuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "ApplicationCommand.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := acuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, applicationcommand.FieldID) + for _, f := range fields { + if !applicationcommand.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != applicationcommand.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := acuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := acuo.mutation.UpdatedAt(); ok { + _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := acuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := acuo.mutation.Name(); ok { + _spec.SetField(applicationcommand.FieldName, field.TypeString, value) + } + if value, ok := acuo.mutation.Version(); ok { + _spec.SetField(applicationcommand.FieldVersion, field.TypeString, value) + } + if value, ok := acuo.mutation.OptionsHash(); ok { + _spec.SetField(applicationcommand.FieldOptionsHash, field.TypeString, value) + } + _node = &ApplicationCommand{config: acuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, acuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{applicationcommand.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + acuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/clan.go b/internal/database/ent/db/clan.go new file mode 100644 index 00000000..98645aef --- /dev/null +++ b/internal/database/ent/db/clan.go @@ -0,0 +1,189 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/clan" +) + +// Clan is the model entity for the Clan schema. +type Clan struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Tag holds the value of the "tag" field. + Tag string `json:"tag,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // EmblemID holds the value of the "emblem_id" field. + EmblemID string `json:"emblem_id,omitempty"` + // Members holds the value of the "members" field. + Members []string `json:"members,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the ClanQuery when eager-loading is set. + Edges ClanEdges `json:"edges"` + selectValues sql.SelectValues +} + +// ClanEdges holds the relations/edges for other nodes in the graph. +type ClanEdges struct { + // Accounts holds the value of the accounts edge. + Accounts []*Account `json:"accounts,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// AccountsOrErr returns the Accounts value or an error if the edge +// was not loaded in eager-loading. +func (e ClanEdges) AccountsOrErr() ([]*Account, error) { + if e.loadedTypes[0] { + return e.Accounts, nil + } + return nil, &NotLoadedError{edge: "accounts"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Clan) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case clan.FieldMembers: + values[i] = new([]byte) + case clan.FieldCreatedAt, clan.FieldUpdatedAt: + values[i] = new(sql.NullInt64) + case clan.FieldID, clan.FieldTag, clan.FieldName, clan.FieldEmblemID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Clan fields. +func (c *Clan) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case clan.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + c.ID = value.String + } + case clan.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + c.CreatedAt = int(value.Int64) + } + case clan.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + c.UpdatedAt = int(value.Int64) + } + case clan.FieldTag: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field tag", values[i]) + } else if value.Valid { + c.Tag = value.String + } + case clan.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + c.Name = value.String + } + case clan.FieldEmblemID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field emblem_id", values[i]) + } else if value.Valid { + c.EmblemID = value.String + } + case clan.FieldMembers: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field members", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &c.Members); err != nil { + return fmt.Errorf("unmarshal field members: %w", err) + } + } + default: + c.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the Clan. +// This includes values selected through modifiers, order, etc. +func (c *Clan) Value(name string) (ent.Value, error) { + return c.selectValues.Get(name) +} + +// QueryAccounts queries the "accounts" edge of the Clan entity. +func (c *Clan) QueryAccounts() *AccountQuery { + return NewClanClient(c.config).QueryAccounts(c) +} + +// Update returns a builder for updating this Clan. +// Note that you need to call Clan.Unwrap() before calling this method if this Clan +// was returned from a transaction, and the transaction was committed or rolled back. +func (c *Clan) Update() *ClanUpdateOne { + return NewClanClient(c.config).UpdateOne(c) +} + +// Unwrap unwraps the Clan entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (c *Clan) Unwrap() *Clan { + _tx, ok := c.config.driver.(*txDriver) + if !ok { + panic("db: Clan is not a transactional entity") + } + c.config.driver = _tx.drv + return c +} + +// String implements the fmt.Stringer. +func (c *Clan) String() string { + var builder strings.Builder + builder.WriteString("Clan(") + builder.WriteString(fmt.Sprintf("id=%v, ", c.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", c.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", c.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("tag=") + builder.WriteString(c.Tag) + builder.WriteString(", ") + builder.WriteString("name=") + builder.WriteString(c.Name) + builder.WriteString(", ") + builder.WriteString("emblem_id=") + builder.WriteString(c.EmblemID) + builder.WriteString(", ") + builder.WriteString("members=") + builder.WriteString(fmt.Sprintf("%v", c.Members)) + builder.WriteByte(')') + return builder.String() +} + +// Clans is a parsable slice of Clan. +type Clans []*Clan diff --git a/internal/database/ent/db/clan/clan.go b/internal/database/ent/db/clan/clan.go new file mode 100644 index 00000000..df17d622 --- /dev/null +++ b/internal/database/ent/db/clan/clan.go @@ -0,0 +1,128 @@ +// Code generated by ent, DO NOT EDIT. + +package clan + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the clan type in the database. + Label = "clan" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldTag holds the string denoting the tag field in the database. + FieldTag = "tag" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldEmblemID holds the string denoting the emblem_id field in the database. + FieldEmblemID = "emblem_id" + // FieldMembers holds the string denoting the members field in the database. + FieldMembers = "members" + // EdgeAccounts holds the string denoting the accounts edge name in mutations. + EdgeAccounts = "accounts" + // Table holds the table name of the clan in the database. + Table = "clans" + // AccountsTable is the table that holds the accounts relation/edge. + AccountsTable = "accounts" + // AccountsInverseTable is the table name for the Account entity. + // It exists in this package in order to avoid circular dependency with the "account" package. + AccountsInverseTable = "accounts" + // AccountsColumn is the table column denoting the accounts relation/edge. + AccountsColumn = "clan_id" +) + +// Columns holds all SQL columns for clan fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldTag, + FieldName, + FieldEmblemID, + FieldMembers, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // TagValidator is a validator for the "tag" field. It is called by the builders before save. + TagValidator func(string) error + // NameValidator is a validator for the "name" field. It is called by the builders before save. + NameValidator func(string) error + // DefaultEmblemID holds the default value on creation for the "emblem_id" field. + DefaultEmblemID string +) + +// OrderOption defines the ordering options for the Clan queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByTag orders the results by the tag field. +func ByTag(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTag, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByEmblemID orders the results by the emblem_id field. +func ByEmblemID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldEmblemID, opts...).ToFunc() +} + +// ByAccountsCount orders the results by accounts count. +func ByAccountsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newAccountsStep(), opts...) + } +} + +// ByAccounts orders the results by accounts terms. +func ByAccounts(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newAccountsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newAccountsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(AccountsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AccountsTable, AccountsColumn), + ) +} diff --git a/internal/database/ent/db/clan/where.go b/internal/database/ent/db/clan/where.go new file mode 100644 index 00000000..c860dc4d --- /dev/null +++ b/internal/database/ent/db/clan/where.go @@ -0,0 +1,412 @@ +// Code generated by ent, DO NOT EDIT. + +package clan + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.Clan { + return predicate.Clan(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.Clan { + return predicate.Clan(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.Clan { + return predicate.Clan(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.Clan { + return predicate.Clan(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.Clan { + return predicate.Clan(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.Clan { + return predicate.Clan(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.Clan { + return predicate.Clan(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.Clan { + return predicate.Clan(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.Clan { + return predicate.Clan(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Tag applies equality check predicate on the "tag" field. It's identical to TagEQ. +func Tag(v string) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldTag, v)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldName, v)) +} + +// EmblemID applies equality check predicate on the "emblem_id" field. It's identical to EmblemIDEQ. +func EmblemID(v string) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldEmblemID, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.Clan { + return predicate.Clan(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.Clan { + return predicate.Clan(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.Clan { + return predicate.Clan(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.Clan { + return predicate.Clan(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.Clan { + return predicate.Clan(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.Clan { + return predicate.Clan(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.Clan { + return predicate.Clan(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.Clan { + return predicate.Clan(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.Clan { + return predicate.Clan(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.Clan { + return predicate.Clan(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.Clan { + return predicate.Clan(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.Clan { + return predicate.Clan(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.Clan { + return predicate.Clan(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.Clan { + return predicate.Clan(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// TagEQ applies the EQ predicate on the "tag" field. +func TagEQ(v string) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldTag, v)) +} + +// TagNEQ applies the NEQ predicate on the "tag" field. +func TagNEQ(v string) predicate.Clan { + return predicate.Clan(sql.FieldNEQ(FieldTag, v)) +} + +// TagIn applies the In predicate on the "tag" field. +func TagIn(vs ...string) predicate.Clan { + return predicate.Clan(sql.FieldIn(FieldTag, vs...)) +} + +// TagNotIn applies the NotIn predicate on the "tag" field. +func TagNotIn(vs ...string) predicate.Clan { + return predicate.Clan(sql.FieldNotIn(FieldTag, vs...)) +} + +// TagGT applies the GT predicate on the "tag" field. +func TagGT(v string) predicate.Clan { + return predicate.Clan(sql.FieldGT(FieldTag, v)) +} + +// TagGTE applies the GTE predicate on the "tag" field. +func TagGTE(v string) predicate.Clan { + return predicate.Clan(sql.FieldGTE(FieldTag, v)) +} + +// TagLT applies the LT predicate on the "tag" field. +func TagLT(v string) predicate.Clan { + return predicate.Clan(sql.FieldLT(FieldTag, v)) +} + +// TagLTE applies the LTE predicate on the "tag" field. +func TagLTE(v string) predicate.Clan { + return predicate.Clan(sql.FieldLTE(FieldTag, v)) +} + +// TagContains applies the Contains predicate on the "tag" field. +func TagContains(v string) predicate.Clan { + return predicate.Clan(sql.FieldContains(FieldTag, v)) +} + +// TagHasPrefix applies the HasPrefix predicate on the "tag" field. +func TagHasPrefix(v string) predicate.Clan { + return predicate.Clan(sql.FieldHasPrefix(FieldTag, v)) +} + +// TagHasSuffix applies the HasSuffix predicate on the "tag" field. +func TagHasSuffix(v string) predicate.Clan { + return predicate.Clan(sql.FieldHasSuffix(FieldTag, v)) +} + +// TagEqualFold applies the EqualFold predicate on the "tag" field. +func TagEqualFold(v string) predicate.Clan { + return predicate.Clan(sql.FieldEqualFold(FieldTag, v)) +} + +// TagContainsFold applies the ContainsFold predicate on the "tag" field. +func TagContainsFold(v string) predicate.Clan { + return predicate.Clan(sql.FieldContainsFold(FieldTag, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.Clan { + return predicate.Clan(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.Clan { + return predicate.Clan(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.Clan { + return predicate.Clan(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.Clan { + return predicate.Clan(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.Clan { + return predicate.Clan(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.Clan { + return predicate.Clan(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.Clan { + return predicate.Clan(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.Clan { + return predicate.Clan(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.Clan { + return predicate.Clan(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.Clan { + return predicate.Clan(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.Clan { + return predicate.Clan(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.Clan { + return predicate.Clan(sql.FieldContainsFold(FieldName, v)) +} + +// EmblemIDEQ applies the EQ predicate on the "emblem_id" field. +func EmblemIDEQ(v string) predicate.Clan { + return predicate.Clan(sql.FieldEQ(FieldEmblemID, v)) +} + +// EmblemIDNEQ applies the NEQ predicate on the "emblem_id" field. +func EmblemIDNEQ(v string) predicate.Clan { + return predicate.Clan(sql.FieldNEQ(FieldEmblemID, v)) +} + +// EmblemIDIn applies the In predicate on the "emblem_id" field. +func EmblemIDIn(vs ...string) predicate.Clan { + return predicate.Clan(sql.FieldIn(FieldEmblemID, vs...)) +} + +// EmblemIDNotIn applies the NotIn predicate on the "emblem_id" field. +func EmblemIDNotIn(vs ...string) predicate.Clan { + return predicate.Clan(sql.FieldNotIn(FieldEmblemID, vs...)) +} + +// EmblemIDGT applies the GT predicate on the "emblem_id" field. +func EmblemIDGT(v string) predicate.Clan { + return predicate.Clan(sql.FieldGT(FieldEmblemID, v)) +} + +// EmblemIDGTE applies the GTE predicate on the "emblem_id" field. +func EmblemIDGTE(v string) predicate.Clan { + return predicate.Clan(sql.FieldGTE(FieldEmblemID, v)) +} + +// EmblemIDLT applies the LT predicate on the "emblem_id" field. +func EmblemIDLT(v string) predicate.Clan { + return predicate.Clan(sql.FieldLT(FieldEmblemID, v)) +} + +// EmblemIDLTE applies the LTE predicate on the "emblem_id" field. +func EmblemIDLTE(v string) predicate.Clan { + return predicate.Clan(sql.FieldLTE(FieldEmblemID, v)) +} + +// EmblemIDContains applies the Contains predicate on the "emblem_id" field. +func EmblemIDContains(v string) predicate.Clan { + return predicate.Clan(sql.FieldContains(FieldEmblemID, v)) +} + +// EmblemIDHasPrefix applies the HasPrefix predicate on the "emblem_id" field. +func EmblemIDHasPrefix(v string) predicate.Clan { + return predicate.Clan(sql.FieldHasPrefix(FieldEmblemID, v)) +} + +// EmblemIDHasSuffix applies the HasSuffix predicate on the "emblem_id" field. +func EmblemIDHasSuffix(v string) predicate.Clan { + return predicate.Clan(sql.FieldHasSuffix(FieldEmblemID, v)) +} + +// EmblemIDIsNil applies the IsNil predicate on the "emblem_id" field. +func EmblemIDIsNil() predicate.Clan { + return predicate.Clan(sql.FieldIsNull(FieldEmblemID)) +} + +// EmblemIDNotNil applies the NotNil predicate on the "emblem_id" field. +func EmblemIDNotNil() predicate.Clan { + return predicate.Clan(sql.FieldNotNull(FieldEmblemID)) +} + +// EmblemIDEqualFold applies the EqualFold predicate on the "emblem_id" field. +func EmblemIDEqualFold(v string) predicate.Clan { + return predicate.Clan(sql.FieldEqualFold(FieldEmblemID, v)) +} + +// EmblemIDContainsFold applies the ContainsFold predicate on the "emblem_id" field. +func EmblemIDContainsFold(v string) predicate.Clan { + return predicate.Clan(sql.FieldContainsFold(FieldEmblemID, v)) +} + +// HasAccounts applies the HasEdge predicate on the "accounts" edge. +func HasAccounts() predicate.Clan { + return predicate.Clan(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AccountsTable, AccountsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasAccountsWith applies the HasEdge predicate on the "accounts" edge with a given conditions (other predicates). +func HasAccountsWith(preds ...predicate.Account) predicate.Clan { + return predicate.Clan(func(s *sql.Selector) { + step := newAccountsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Clan) predicate.Clan { + return predicate.Clan(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Clan) predicate.Clan { + return predicate.Clan(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Clan) predicate.Clan { + return predicate.Clan(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/clan_create.go b/internal/database/ent/db/clan_create.go new file mode 100644 index 00000000..fb96c54d --- /dev/null +++ b/internal/database/ent/db/clan_create.go @@ -0,0 +1,340 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/clan" +) + +// ClanCreate is the builder for creating a Clan entity. +type ClanCreate struct { + config + mutation *ClanMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (cc *ClanCreate) SetCreatedAt(i int) *ClanCreate { + cc.mutation.SetCreatedAt(i) + return cc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (cc *ClanCreate) SetNillableCreatedAt(i *int) *ClanCreate { + if i != nil { + cc.SetCreatedAt(*i) + } + return cc +} + +// SetUpdatedAt sets the "updated_at" field. +func (cc *ClanCreate) SetUpdatedAt(i int) *ClanCreate { + cc.mutation.SetUpdatedAt(i) + return cc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (cc *ClanCreate) SetNillableUpdatedAt(i *int) *ClanCreate { + if i != nil { + cc.SetUpdatedAt(*i) + } + return cc +} + +// SetTag sets the "tag" field. +func (cc *ClanCreate) SetTag(s string) *ClanCreate { + cc.mutation.SetTag(s) + return cc +} + +// SetName sets the "name" field. +func (cc *ClanCreate) SetName(s string) *ClanCreate { + cc.mutation.SetName(s) + return cc +} + +// SetEmblemID sets the "emblem_id" field. +func (cc *ClanCreate) SetEmblemID(s string) *ClanCreate { + cc.mutation.SetEmblemID(s) + return cc +} + +// SetNillableEmblemID sets the "emblem_id" field if the given value is not nil. +func (cc *ClanCreate) SetNillableEmblemID(s *string) *ClanCreate { + if s != nil { + cc.SetEmblemID(*s) + } + return cc +} + +// SetMembers sets the "members" field. +func (cc *ClanCreate) SetMembers(s []string) *ClanCreate { + cc.mutation.SetMembers(s) + return cc +} + +// SetID sets the "id" field. +func (cc *ClanCreate) SetID(s string) *ClanCreate { + cc.mutation.SetID(s) + return cc +} + +// AddAccountIDs adds the "accounts" edge to the Account entity by IDs. +func (cc *ClanCreate) AddAccountIDs(ids ...string) *ClanCreate { + cc.mutation.AddAccountIDs(ids...) + return cc +} + +// AddAccounts adds the "accounts" edges to the Account entity. +func (cc *ClanCreate) AddAccounts(a ...*Account) *ClanCreate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return cc.AddAccountIDs(ids...) +} + +// Mutation returns the ClanMutation object of the builder. +func (cc *ClanCreate) Mutation() *ClanMutation { + return cc.mutation +} + +// Save creates the Clan in the database. +func (cc *ClanCreate) Save(ctx context.Context) (*Clan, error) { + cc.defaults() + return withHooks(ctx, cc.sqlSave, cc.mutation, cc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (cc *ClanCreate) SaveX(ctx context.Context) *Clan { + v, err := cc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (cc *ClanCreate) Exec(ctx context.Context) error { + _, err := cc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (cc *ClanCreate) ExecX(ctx context.Context) { + if err := cc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (cc *ClanCreate) defaults() { + if _, ok := cc.mutation.CreatedAt(); !ok { + v := clan.DefaultCreatedAt() + cc.mutation.SetCreatedAt(v) + } + if _, ok := cc.mutation.UpdatedAt(); !ok { + v := clan.DefaultUpdatedAt() + cc.mutation.SetUpdatedAt(v) + } + if _, ok := cc.mutation.EmblemID(); !ok { + v := clan.DefaultEmblemID + cc.mutation.SetEmblemID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (cc *ClanCreate) check() error { + if _, ok := cc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "Clan.created_at"`)} + } + if _, ok := cc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "Clan.updated_at"`)} + } + if _, ok := cc.mutation.Tag(); !ok { + return &ValidationError{Name: "tag", err: errors.New(`db: missing required field "Clan.tag"`)} + } + if v, ok := cc.mutation.Tag(); ok { + if err := clan.TagValidator(v); err != nil { + return &ValidationError{Name: "tag", err: fmt.Errorf(`db: validator failed for field "Clan.tag": %w`, err)} + } + } + if _, ok := cc.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`db: missing required field "Clan.name"`)} + } + if v, ok := cc.mutation.Name(); ok { + if err := clan.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "Clan.name": %w`, err)} + } + } + if _, ok := cc.mutation.Members(); !ok { + return &ValidationError{Name: "members", err: errors.New(`db: missing required field "Clan.members"`)} + } + return nil +} + +func (cc *ClanCreate) sqlSave(ctx context.Context) (*Clan, error) { + if err := cc.check(); err != nil { + return nil, err + } + _node, _spec := cc.createSpec() + if err := sqlgraph.CreateNode(ctx, cc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected Clan.ID type: %T", _spec.ID.Value) + } + } + cc.mutation.id = &_node.ID + cc.mutation.done = true + return _node, nil +} + +func (cc *ClanCreate) createSpec() (*Clan, *sqlgraph.CreateSpec) { + var ( + _node = &Clan{config: cc.config} + _spec = sqlgraph.NewCreateSpec(clan.Table, sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString)) + ) + if id, ok := cc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := cc.mutation.CreatedAt(); ok { + _spec.SetField(clan.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := cc.mutation.UpdatedAt(); ok { + _spec.SetField(clan.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := cc.mutation.Tag(); ok { + _spec.SetField(clan.FieldTag, field.TypeString, value) + _node.Tag = value + } + if value, ok := cc.mutation.Name(); ok { + _spec.SetField(clan.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := cc.mutation.EmblemID(); ok { + _spec.SetField(clan.FieldEmblemID, field.TypeString, value) + _node.EmblemID = value + } + if value, ok := cc.mutation.Members(); ok { + _spec.SetField(clan.FieldMembers, field.TypeJSON, value) + _node.Members = value + } + if nodes := cc.mutation.AccountsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: clan.AccountsTable, + Columns: []string{clan.AccountsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// ClanCreateBulk is the builder for creating many Clan entities in bulk. +type ClanCreateBulk struct { + config + err error + builders []*ClanCreate +} + +// Save creates the Clan entities in the database. +func (ccb *ClanCreateBulk) Save(ctx context.Context) ([]*Clan, error) { + if ccb.err != nil { + return nil, ccb.err + } + specs := make([]*sqlgraph.CreateSpec, len(ccb.builders)) + nodes := make([]*Clan, len(ccb.builders)) + mutators := make([]Mutator, len(ccb.builders)) + for i := range ccb.builders { + func(i int, root context.Context) { + builder := ccb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*ClanMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, ccb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, ccb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, ccb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (ccb *ClanCreateBulk) SaveX(ctx context.Context) []*Clan { + v, err := ccb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ccb *ClanCreateBulk) Exec(ctx context.Context) error { + _, err := ccb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ccb *ClanCreateBulk) ExecX(ctx context.Context) { + if err := ccb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/clan_delete.go b/internal/database/ent/db/clan_delete.go new file mode 100644 index 00000000..23f8393b --- /dev/null +++ b/internal/database/ent/db/clan_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ClanDelete is the builder for deleting a Clan entity. +type ClanDelete struct { + config + hooks []Hook + mutation *ClanMutation +} + +// Where appends a list predicates to the ClanDelete builder. +func (cd *ClanDelete) Where(ps ...predicate.Clan) *ClanDelete { + cd.mutation.Where(ps...) + return cd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (cd *ClanDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, cd.sqlExec, cd.mutation, cd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (cd *ClanDelete) ExecX(ctx context.Context) int { + n, err := cd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (cd *ClanDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(clan.Table, sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString)) + if ps := cd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, cd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + cd.mutation.done = true + return affected, err +} + +// ClanDeleteOne is the builder for deleting a single Clan entity. +type ClanDeleteOne struct { + cd *ClanDelete +} + +// Where appends a list predicates to the ClanDelete builder. +func (cdo *ClanDeleteOne) Where(ps ...predicate.Clan) *ClanDeleteOne { + cdo.cd.mutation.Where(ps...) + return cdo +} + +// Exec executes the deletion query. +func (cdo *ClanDeleteOne) Exec(ctx context.Context) error { + n, err := cdo.cd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{clan.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (cdo *ClanDeleteOne) ExecX(ctx context.Context) { + if err := cdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/clan_query.go b/internal/database/ent/db/clan_query.go new file mode 100644 index 00000000..274928c5 --- /dev/null +++ b/internal/database/ent/db/clan_query.go @@ -0,0 +1,605 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ClanQuery is the builder for querying Clan entities. +type ClanQuery struct { + config + ctx *QueryContext + order []clan.OrderOption + inters []Interceptor + predicates []predicate.Clan + withAccounts *AccountQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the ClanQuery builder. +func (cq *ClanQuery) Where(ps ...predicate.Clan) *ClanQuery { + cq.predicates = append(cq.predicates, ps...) + return cq +} + +// Limit the number of records to be returned by this query. +func (cq *ClanQuery) Limit(limit int) *ClanQuery { + cq.ctx.Limit = &limit + return cq +} + +// Offset to start from. +func (cq *ClanQuery) Offset(offset int) *ClanQuery { + cq.ctx.Offset = &offset + return cq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (cq *ClanQuery) Unique(unique bool) *ClanQuery { + cq.ctx.Unique = &unique + return cq +} + +// Order specifies how the records should be ordered. +func (cq *ClanQuery) Order(o ...clan.OrderOption) *ClanQuery { + cq.order = append(cq.order, o...) + return cq +} + +// QueryAccounts chains the current query on the "accounts" edge. +func (cq *ClanQuery) QueryAccounts() *AccountQuery { + query := (&AccountClient{config: cq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := cq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := cq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(clan.Table, clan.FieldID, selector), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, clan.AccountsTable, clan.AccountsColumn), + ) + fromU = sqlgraph.SetNeighbors(cq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first Clan entity from the query. +// Returns a *NotFoundError when no Clan was found. +func (cq *ClanQuery) First(ctx context.Context) (*Clan, error) { + nodes, err := cq.Limit(1).All(setContextOp(ctx, cq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{clan.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (cq *ClanQuery) FirstX(ctx context.Context) *Clan { + node, err := cq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Clan ID from the query. +// Returns a *NotFoundError when no Clan ID was found. +func (cq *ClanQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = cq.Limit(1).IDs(setContextOp(ctx, cq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{clan.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (cq *ClanQuery) FirstIDX(ctx context.Context) string { + id, err := cq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Clan entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Clan entity is found. +// Returns a *NotFoundError when no Clan entities are found. +func (cq *ClanQuery) Only(ctx context.Context) (*Clan, error) { + nodes, err := cq.Limit(2).All(setContextOp(ctx, cq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{clan.Label} + default: + return nil, &NotSingularError{clan.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (cq *ClanQuery) OnlyX(ctx context.Context) *Clan { + node, err := cq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Clan ID in the query. +// Returns a *NotSingularError when more than one Clan ID is found. +// Returns a *NotFoundError when no entities are found. +func (cq *ClanQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = cq.Limit(2).IDs(setContextOp(ctx, cq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{clan.Label} + default: + err = &NotSingularError{clan.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (cq *ClanQuery) OnlyIDX(ctx context.Context) string { + id, err := cq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Clans. +func (cq *ClanQuery) All(ctx context.Context) ([]*Clan, error) { + ctx = setContextOp(ctx, cq.ctx, "All") + if err := cq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Clan, *ClanQuery]() + return withInterceptors[[]*Clan](ctx, cq, qr, cq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (cq *ClanQuery) AllX(ctx context.Context) []*Clan { + nodes, err := cq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Clan IDs. +func (cq *ClanQuery) IDs(ctx context.Context) (ids []string, err error) { + if cq.ctx.Unique == nil && cq.path != nil { + cq.Unique(true) + } + ctx = setContextOp(ctx, cq.ctx, "IDs") + if err = cq.Select(clan.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (cq *ClanQuery) IDsX(ctx context.Context) []string { + ids, err := cq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (cq *ClanQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, cq.ctx, "Count") + if err := cq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, cq, querierCount[*ClanQuery](), cq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (cq *ClanQuery) CountX(ctx context.Context) int { + count, err := cq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (cq *ClanQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, cq.ctx, "Exist") + switch _, err := cq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (cq *ClanQuery) ExistX(ctx context.Context) bool { + exist, err := cq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the ClanQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (cq *ClanQuery) Clone() *ClanQuery { + if cq == nil { + return nil + } + return &ClanQuery{ + config: cq.config, + ctx: cq.ctx.Clone(), + order: append([]clan.OrderOption{}, cq.order...), + inters: append([]Interceptor{}, cq.inters...), + predicates: append([]predicate.Clan{}, cq.predicates...), + withAccounts: cq.withAccounts.Clone(), + // clone intermediate query. + sql: cq.sql.Clone(), + path: cq.path, + } +} + +// WithAccounts tells the query-builder to eager-load the nodes that are connected to +// the "accounts" edge. The optional arguments are used to configure the query builder of the edge. +func (cq *ClanQuery) WithAccounts(opts ...func(*AccountQuery)) *ClanQuery { + query := (&AccountClient{config: cq.config}).Query() + for _, opt := range opts { + opt(query) + } + cq.withAccounts = query + return cq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Clan.Query(). +// GroupBy(clan.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (cq *ClanQuery) GroupBy(field string, fields ...string) *ClanGroupBy { + cq.ctx.Fields = append([]string{field}, fields...) + grbuild := &ClanGroupBy{build: cq} + grbuild.flds = &cq.ctx.Fields + grbuild.label = clan.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.Clan.Query(). +// Select(clan.FieldCreatedAt). +// Scan(ctx, &v) +func (cq *ClanQuery) Select(fields ...string) *ClanSelect { + cq.ctx.Fields = append(cq.ctx.Fields, fields...) + sbuild := &ClanSelect{ClanQuery: cq} + sbuild.label = clan.Label + sbuild.flds, sbuild.scan = &cq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a ClanSelect configured with the given aggregations. +func (cq *ClanQuery) Aggregate(fns ...AggregateFunc) *ClanSelect { + return cq.Select().Aggregate(fns...) +} + +func (cq *ClanQuery) prepareQuery(ctx context.Context) error { + for _, inter := range cq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, cq); err != nil { + return err + } + } + } + for _, f := range cq.ctx.Fields { + if !clan.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if cq.path != nil { + prev, err := cq.path(ctx) + if err != nil { + return err + } + cq.sql = prev + } + return nil +} + +func (cq *ClanQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Clan, error) { + var ( + nodes = []*Clan{} + _spec = cq.querySpec() + loadedTypes = [1]bool{ + cq.withAccounts != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*Clan).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &Clan{config: cq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, cq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := cq.withAccounts; query != nil { + if err := cq.loadAccounts(ctx, query, nodes, + func(n *Clan) { n.Edges.Accounts = []*Account{} }, + func(n *Clan, e *Account) { n.Edges.Accounts = append(n.Edges.Accounts, e) }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (cq *ClanQuery) loadAccounts(ctx context.Context, query *AccountQuery, nodes []*Clan, init func(*Clan), assign func(*Clan, *Account)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*Clan) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(account.FieldClanID) + } + query.Where(predicate.Account(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(clan.AccountsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.ClanID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "clan_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} + +func (cq *ClanQuery) sqlCount(ctx context.Context) (int, error) { + _spec := cq.querySpec() + _spec.Node.Columns = cq.ctx.Fields + if len(cq.ctx.Fields) > 0 { + _spec.Unique = cq.ctx.Unique != nil && *cq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, cq.driver, _spec) +} + +func (cq *ClanQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(clan.Table, clan.Columns, sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString)) + _spec.From = cq.sql + if unique := cq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if cq.path != nil { + _spec.Unique = true + } + if fields := cq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, clan.FieldID) + for i := range fields { + if fields[i] != clan.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := cq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := cq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := cq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := cq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (cq *ClanQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(cq.driver.Dialect()) + t1 := builder.Table(clan.Table) + columns := cq.ctx.Fields + if len(columns) == 0 { + columns = clan.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if cq.sql != nil { + selector = cq.sql + selector.Select(selector.Columns(columns...)...) + } + if cq.ctx.Unique != nil && *cq.ctx.Unique { + selector.Distinct() + } + for _, p := range cq.predicates { + p(selector) + } + for _, p := range cq.order { + p(selector) + } + if offset := cq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := cq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ClanGroupBy is the group-by builder for Clan entities. +type ClanGroupBy struct { + selector + build *ClanQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (cgb *ClanGroupBy) Aggregate(fns ...AggregateFunc) *ClanGroupBy { + cgb.fns = append(cgb.fns, fns...) + return cgb +} + +// Scan applies the selector query and scans the result into the given value. +func (cgb *ClanGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, cgb.build.ctx, "GroupBy") + if err := cgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ClanQuery, *ClanGroupBy](ctx, cgb.build, cgb, cgb.build.inters, v) +} + +func (cgb *ClanGroupBy) sqlScan(ctx context.Context, root *ClanQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(cgb.fns)) + for _, fn := range cgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*cgb.flds)+len(cgb.fns)) + for _, f := range *cgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*cgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := cgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// ClanSelect is the builder for selecting fields of Clan entities. +type ClanSelect struct { + *ClanQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (cs *ClanSelect) Aggregate(fns ...AggregateFunc) *ClanSelect { + cs.fns = append(cs.fns, fns...) + return cs +} + +// Scan applies the selector query and scans the result into the given value. +func (cs *ClanSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, cs.ctx, "Select") + if err := cs.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ClanQuery, *ClanSelect](ctx, cs.ClanQuery, cs, cs.inters, v) +} + +func (cs *ClanSelect) sqlScan(ctx context.Context, root *ClanQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(cs.fns)) + for _, fn := range cs.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*cs.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := cs.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/clan_update.go b/internal/database/ent/db/clan_update.go new file mode 100644 index 00000000..85b62c7a --- /dev/null +++ b/internal/database/ent/db/clan_update.go @@ -0,0 +1,591 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/dialect/sql/sqljson" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ClanUpdate is the builder for updating Clan entities. +type ClanUpdate struct { + config + hooks []Hook + mutation *ClanMutation +} + +// Where appends a list predicates to the ClanUpdate builder. +func (cu *ClanUpdate) Where(ps ...predicate.Clan) *ClanUpdate { + cu.mutation.Where(ps...) + return cu +} + +// SetUpdatedAt sets the "updated_at" field. +func (cu *ClanUpdate) SetUpdatedAt(i int) *ClanUpdate { + cu.mutation.ResetUpdatedAt() + cu.mutation.SetUpdatedAt(i) + return cu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (cu *ClanUpdate) AddUpdatedAt(i int) *ClanUpdate { + cu.mutation.AddUpdatedAt(i) + return cu +} + +// SetTag sets the "tag" field. +func (cu *ClanUpdate) SetTag(s string) *ClanUpdate { + cu.mutation.SetTag(s) + return cu +} + +// SetNillableTag sets the "tag" field if the given value is not nil. +func (cu *ClanUpdate) SetNillableTag(s *string) *ClanUpdate { + if s != nil { + cu.SetTag(*s) + } + return cu +} + +// SetName sets the "name" field. +func (cu *ClanUpdate) SetName(s string) *ClanUpdate { + cu.mutation.SetName(s) + return cu +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (cu *ClanUpdate) SetNillableName(s *string) *ClanUpdate { + if s != nil { + cu.SetName(*s) + } + return cu +} + +// SetEmblemID sets the "emblem_id" field. +func (cu *ClanUpdate) SetEmblemID(s string) *ClanUpdate { + cu.mutation.SetEmblemID(s) + return cu +} + +// SetNillableEmblemID sets the "emblem_id" field if the given value is not nil. +func (cu *ClanUpdate) SetNillableEmblemID(s *string) *ClanUpdate { + if s != nil { + cu.SetEmblemID(*s) + } + return cu +} + +// ClearEmblemID clears the value of the "emblem_id" field. +func (cu *ClanUpdate) ClearEmblemID() *ClanUpdate { + cu.mutation.ClearEmblemID() + return cu +} + +// SetMembers sets the "members" field. +func (cu *ClanUpdate) SetMembers(s []string) *ClanUpdate { + cu.mutation.SetMembers(s) + return cu +} + +// AppendMembers appends s to the "members" field. +func (cu *ClanUpdate) AppendMembers(s []string) *ClanUpdate { + cu.mutation.AppendMembers(s) + return cu +} + +// AddAccountIDs adds the "accounts" edge to the Account entity by IDs. +func (cu *ClanUpdate) AddAccountIDs(ids ...string) *ClanUpdate { + cu.mutation.AddAccountIDs(ids...) + return cu +} + +// AddAccounts adds the "accounts" edges to the Account entity. +func (cu *ClanUpdate) AddAccounts(a ...*Account) *ClanUpdate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return cu.AddAccountIDs(ids...) +} + +// Mutation returns the ClanMutation object of the builder. +func (cu *ClanUpdate) Mutation() *ClanMutation { + return cu.mutation +} + +// ClearAccounts clears all "accounts" edges to the Account entity. +func (cu *ClanUpdate) ClearAccounts() *ClanUpdate { + cu.mutation.ClearAccounts() + return cu +} + +// RemoveAccountIDs removes the "accounts" edge to Account entities by IDs. +func (cu *ClanUpdate) RemoveAccountIDs(ids ...string) *ClanUpdate { + cu.mutation.RemoveAccountIDs(ids...) + return cu +} + +// RemoveAccounts removes "accounts" edges to Account entities. +func (cu *ClanUpdate) RemoveAccounts(a ...*Account) *ClanUpdate { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return cu.RemoveAccountIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (cu *ClanUpdate) Save(ctx context.Context) (int, error) { + cu.defaults() + return withHooks(ctx, cu.sqlSave, cu.mutation, cu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (cu *ClanUpdate) SaveX(ctx context.Context) int { + affected, err := cu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (cu *ClanUpdate) Exec(ctx context.Context) error { + _, err := cu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (cu *ClanUpdate) ExecX(ctx context.Context) { + if err := cu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (cu *ClanUpdate) defaults() { + if _, ok := cu.mutation.UpdatedAt(); !ok { + v := clan.UpdateDefaultUpdatedAt() + cu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (cu *ClanUpdate) check() error { + if v, ok := cu.mutation.Tag(); ok { + if err := clan.TagValidator(v); err != nil { + return &ValidationError{Name: "tag", err: fmt.Errorf(`db: validator failed for field "Clan.tag": %w`, err)} + } + } + if v, ok := cu.mutation.Name(); ok { + if err := clan.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "Clan.name": %w`, err)} + } + } + return nil +} + +func (cu *ClanUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := cu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(clan.Table, clan.Columns, sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString)) + if ps := cu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := cu.mutation.UpdatedAt(); ok { + _spec.SetField(clan.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := cu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(clan.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := cu.mutation.Tag(); ok { + _spec.SetField(clan.FieldTag, field.TypeString, value) + } + if value, ok := cu.mutation.Name(); ok { + _spec.SetField(clan.FieldName, field.TypeString, value) + } + if value, ok := cu.mutation.EmblemID(); ok { + _spec.SetField(clan.FieldEmblemID, field.TypeString, value) + } + if cu.mutation.EmblemIDCleared() { + _spec.ClearField(clan.FieldEmblemID, field.TypeString) + } + if value, ok := cu.mutation.Members(); ok { + _spec.SetField(clan.FieldMembers, field.TypeJSON, value) + } + if value, ok := cu.mutation.AppendedMembers(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, clan.FieldMembers, value) + }) + } + if cu.mutation.AccountsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: clan.AccountsTable, + Columns: []string{clan.AccountsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := cu.mutation.RemovedAccountsIDs(); len(nodes) > 0 && !cu.mutation.AccountsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: clan.AccountsTable, + Columns: []string{clan.AccountsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := cu.mutation.AccountsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: clan.AccountsTable, + Columns: []string{clan.AccountsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, cu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{clan.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + cu.mutation.done = true + return n, nil +} + +// ClanUpdateOne is the builder for updating a single Clan entity. +type ClanUpdateOne struct { + config + fields []string + hooks []Hook + mutation *ClanMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (cuo *ClanUpdateOne) SetUpdatedAt(i int) *ClanUpdateOne { + cuo.mutation.ResetUpdatedAt() + cuo.mutation.SetUpdatedAt(i) + return cuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (cuo *ClanUpdateOne) AddUpdatedAt(i int) *ClanUpdateOne { + cuo.mutation.AddUpdatedAt(i) + return cuo +} + +// SetTag sets the "tag" field. +func (cuo *ClanUpdateOne) SetTag(s string) *ClanUpdateOne { + cuo.mutation.SetTag(s) + return cuo +} + +// SetNillableTag sets the "tag" field if the given value is not nil. +func (cuo *ClanUpdateOne) SetNillableTag(s *string) *ClanUpdateOne { + if s != nil { + cuo.SetTag(*s) + } + return cuo +} + +// SetName sets the "name" field. +func (cuo *ClanUpdateOne) SetName(s string) *ClanUpdateOne { + cuo.mutation.SetName(s) + return cuo +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (cuo *ClanUpdateOne) SetNillableName(s *string) *ClanUpdateOne { + if s != nil { + cuo.SetName(*s) + } + return cuo +} + +// SetEmblemID sets the "emblem_id" field. +func (cuo *ClanUpdateOne) SetEmblemID(s string) *ClanUpdateOne { + cuo.mutation.SetEmblemID(s) + return cuo +} + +// SetNillableEmblemID sets the "emblem_id" field if the given value is not nil. +func (cuo *ClanUpdateOne) SetNillableEmblemID(s *string) *ClanUpdateOne { + if s != nil { + cuo.SetEmblemID(*s) + } + return cuo +} + +// ClearEmblemID clears the value of the "emblem_id" field. +func (cuo *ClanUpdateOne) ClearEmblemID() *ClanUpdateOne { + cuo.mutation.ClearEmblemID() + return cuo +} + +// SetMembers sets the "members" field. +func (cuo *ClanUpdateOne) SetMembers(s []string) *ClanUpdateOne { + cuo.mutation.SetMembers(s) + return cuo +} + +// AppendMembers appends s to the "members" field. +func (cuo *ClanUpdateOne) AppendMembers(s []string) *ClanUpdateOne { + cuo.mutation.AppendMembers(s) + return cuo +} + +// AddAccountIDs adds the "accounts" edge to the Account entity by IDs. +func (cuo *ClanUpdateOne) AddAccountIDs(ids ...string) *ClanUpdateOne { + cuo.mutation.AddAccountIDs(ids...) + return cuo +} + +// AddAccounts adds the "accounts" edges to the Account entity. +func (cuo *ClanUpdateOne) AddAccounts(a ...*Account) *ClanUpdateOne { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return cuo.AddAccountIDs(ids...) +} + +// Mutation returns the ClanMutation object of the builder. +func (cuo *ClanUpdateOne) Mutation() *ClanMutation { + return cuo.mutation +} + +// ClearAccounts clears all "accounts" edges to the Account entity. +func (cuo *ClanUpdateOne) ClearAccounts() *ClanUpdateOne { + cuo.mutation.ClearAccounts() + return cuo +} + +// RemoveAccountIDs removes the "accounts" edge to Account entities by IDs. +func (cuo *ClanUpdateOne) RemoveAccountIDs(ids ...string) *ClanUpdateOne { + cuo.mutation.RemoveAccountIDs(ids...) + return cuo +} + +// RemoveAccounts removes "accounts" edges to Account entities. +func (cuo *ClanUpdateOne) RemoveAccounts(a ...*Account) *ClanUpdateOne { + ids := make([]string, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return cuo.RemoveAccountIDs(ids...) +} + +// Where appends a list predicates to the ClanUpdate builder. +func (cuo *ClanUpdateOne) Where(ps ...predicate.Clan) *ClanUpdateOne { + cuo.mutation.Where(ps...) + return cuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (cuo *ClanUpdateOne) Select(field string, fields ...string) *ClanUpdateOne { + cuo.fields = append([]string{field}, fields...) + return cuo +} + +// Save executes the query and returns the updated Clan entity. +func (cuo *ClanUpdateOne) Save(ctx context.Context) (*Clan, error) { + cuo.defaults() + return withHooks(ctx, cuo.sqlSave, cuo.mutation, cuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (cuo *ClanUpdateOne) SaveX(ctx context.Context) *Clan { + node, err := cuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (cuo *ClanUpdateOne) Exec(ctx context.Context) error { + _, err := cuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (cuo *ClanUpdateOne) ExecX(ctx context.Context) { + if err := cuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (cuo *ClanUpdateOne) defaults() { + if _, ok := cuo.mutation.UpdatedAt(); !ok { + v := clan.UpdateDefaultUpdatedAt() + cuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (cuo *ClanUpdateOne) check() error { + if v, ok := cuo.mutation.Tag(); ok { + if err := clan.TagValidator(v); err != nil { + return &ValidationError{Name: "tag", err: fmt.Errorf(`db: validator failed for field "Clan.tag": %w`, err)} + } + } + if v, ok := cuo.mutation.Name(); ok { + if err := clan.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "Clan.name": %w`, err)} + } + } + return nil +} + +func (cuo *ClanUpdateOne) sqlSave(ctx context.Context) (_node *Clan, err error) { + if err := cuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(clan.Table, clan.Columns, sqlgraph.NewFieldSpec(clan.FieldID, field.TypeString)) + id, ok := cuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "Clan.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := cuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, clan.FieldID) + for _, f := range fields { + if !clan.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != clan.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := cuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := cuo.mutation.UpdatedAt(); ok { + _spec.SetField(clan.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := cuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(clan.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := cuo.mutation.Tag(); ok { + _spec.SetField(clan.FieldTag, field.TypeString, value) + } + if value, ok := cuo.mutation.Name(); ok { + _spec.SetField(clan.FieldName, field.TypeString, value) + } + if value, ok := cuo.mutation.EmblemID(); ok { + _spec.SetField(clan.FieldEmblemID, field.TypeString, value) + } + if cuo.mutation.EmblemIDCleared() { + _spec.ClearField(clan.FieldEmblemID, field.TypeString) + } + if value, ok := cuo.mutation.Members(); ok { + _spec.SetField(clan.FieldMembers, field.TypeJSON, value) + } + if value, ok := cuo.mutation.AppendedMembers(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, clan.FieldMembers, value) + }) + } + if cuo.mutation.AccountsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: clan.AccountsTable, + Columns: []string{clan.AccountsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := cuo.mutation.RemovedAccountsIDs(); len(nodes) > 0 && !cuo.mutation.AccountsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: clan.AccountsTable, + Columns: []string{clan.AccountsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := cuo.mutation.AccountsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: clan.AccountsTable, + Columns: []string{clan.AccountsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &Clan{config: cuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, cuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{clan.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + cuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/client.go b/internal/database/ent/db/client.go new file mode 100644 index 00000000..4b53ddbc --- /dev/null +++ b/internal/database/ent/db/client.go @@ -0,0 +1,2416 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + + "github.com/cufee/aftermath/internal/database/ent/db/migrate" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" +) + +// Client is the client that holds all ent builders. +type Client struct { + config + // Schema is the client for creating, migrating and dropping schema. + Schema *migrate.Schema + // Account is the client for interacting with the Account builders. + Account *AccountClient + // AccountSnapshot is the client for interacting with the AccountSnapshot builders. + AccountSnapshot *AccountSnapshotClient + // AchievementsSnapshot is the client for interacting with the AchievementsSnapshot builders. + AchievementsSnapshot *AchievementsSnapshotClient + // AppConfiguration is the client for interacting with the AppConfiguration builders. + AppConfiguration *AppConfigurationClient + // ApplicationCommand is the client for interacting with the ApplicationCommand builders. + ApplicationCommand *ApplicationCommandClient + // Clan is the client for interacting with the Clan builders. + Clan *ClanClient + // CronTask is the client for interacting with the CronTask builders. + CronTask *CronTaskClient + // User is the client for interacting with the User builders. + User *UserClient + // UserConnection is the client for interacting with the UserConnection builders. + UserConnection *UserConnectionClient + // UserContent is the client for interacting with the UserContent builders. + UserContent *UserContentClient + // UserSubscription is the client for interacting with the UserSubscription builders. + UserSubscription *UserSubscriptionClient + // Vehicle is the client for interacting with the Vehicle builders. + Vehicle *VehicleClient + // VehicleAverage is the client for interacting with the VehicleAverage builders. + VehicleAverage *VehicleAverageClient + // VehicleSnapshot is the client for interacting with the VehicleSnapshot builders. + VehicleSnapshot *VehicleSnapshotClient +} + +// NewClient creates a new client configured with the given options. +func NewClient(opts ...Option) *Client { + client := &Client{config: newConfig(opts...)} + client.init() + return client +} + +func (c *Client) init() { + c.Schema = migrate.NewSchema(c.driver) + c.Account = NewAccountClient(c.config) + c.AccountSnapshot = NewAccountSnapshotClient(c.config) + c.AchievementsSnapshot = NewAchievementsSnapshotClient(c.config) + c.AppConfiguration = NewAppConfigurationClient(c.config) + c.ApplicationCommand = NewApplicationCommandClient(c.config) + c.Clan = NewClanClient(c.config) + c.CronTask = NewCronTaskClient(c.config) + c.User = NewUserClient(c.config) + c.UserConnection = NewUserConnectionClient(c.config) + c.UserContent = NewUserContentClient(c.config) + c.UserSubscription = NewUserSubscriptionClient(c.config) + c.Vehicle = NewVehicleClient(c.config) + c.VehicleAverage = NewVehicleAverageClient(c.config) + c.VehicleSnapshot = NewVehicleSnapshotClient(c.config) +} + +type ( + // config is the configuration for the client and its builder. + config struct { + // driver used for executing database requests. + driver dialect.Driver + // debug enable a debug logging. + debug bool + // log used for logging on debug mode. + log func(...any) + // hooks to execute on mutations. + hooks *hooks + // interceptors to execute on queries. + inters *inters + } + // Option function to configure the client. + Option func(*config) +) + +// newConfig creates a new config for the client. +func newConfig(opts ...Option) config { + cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}} + cfg.options(opts...) + return cfg +} + +// options applies the options on the config object. +func (c *config) options(opts ...Option) { + for _, opt := range opts { + opt(c) + } + if c.debug { + c.driver = dialect.Debug(c.driver, c.log) + } +} + +// Debug enables debug logging on the ent.Driver. +func Debug() Option { + return func(c *config) { + c.debug = true + } +} + +// Log sets the logging function for debug mode. +func Log(fn func(...any)) Option { + return func(c *config) { + c.log = fn + } +} + +// Driver configures the client driver. +func Driver(driver dialect.Driver) Option { + return func(c *config) { + c.driver = driver + } +} + +// Open opens a database/sql.DB specified by the driver name and +// the data source name, and returns a new client attached to it. +// Optional parameters can be added for configuring the client. +func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { + switch driverName { + case dialect.MySQL, dialect.Postgres, dialect.SQLite: + drv, err := sql.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + return NewClient(append(options, Driver(drv))...), nil + default: + return nil, fmt.Errorf("unsupported driver: %q", driverName) + } +} + +// ErrTxStarted is returned when trying to start a new transaction from a transactional client. +var ErrTxStarted = errors.New("db: cannot start a transaction within a transaction") + +// Tx returns a new transactional client. The provided context +// is used until the transaction is committed or rolled back. +func (c *Client) Tx(ctx context.Context) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, ErrTxStarted + } + tx, err := newTx(ctx, c.driver) + if err != nil { + return nil, fmt.Errorf("db: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = tx + return &Tx{ + ctx: ctx, + config: cfg, + Account: NewAccountClient(cfg), + AccountSnapshot: NewAccountSnapshotClient(cfg), + AchievementsSnapshot: NewAchievementsSnapshotClient(cfg), + AppConfiguration: NewAppConfigurationClient(cfg), + ApplicationCommand: NewApplicationCommandClient(cfg), + Clan: NewClanClient(cfg), + CronTask: NewCronTaskClient(cfg), + User: NewUserClient(cfg), + UserConnection: NewUserConnectionClient(cfg), + UserContent: NewUserContentClient(cfg), + UserSubscription: NewUserSubscriptionClient(cfg), + Vehicle: NewVehicleClient(cfg), + VehicleAverage: NewVehicleAverageClient(cfg), + VehicleSnapshot: NewVehicleSnapshotClient(cfg), + }, nil +} + +// BeginTx returns a transactional client with specified options. +func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, errors.New("ent: cannot start a transaction within a transaction") + } + tx, err := c.driver.(interface { + BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) + }).BeginTx(ctx, opts) + if err != nil { + return nil, fmt.Errorf("ent: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = &txDriver{tx: tx, drv: c.driver} + return &Tx{ + ctx: ctx, + config: cfg, + Account: NewAccountClient(cfg), + AccountSnapshot: NewAccountSnapshotClient(cfg), + AchievementsSnapshot: NewAchievementsSnapshotClient(cfg), + AppConfiguration: NewAppConfigurationClient(cfg), + ApplicationCommand: NewApplicationCommandClient(cfg), + Clan: NewClanClient(cfg), + CronTask: NewCronTaskClient(cfg), + User: NewUserClient(cfg), + UserConnection: NewUserConnectionClient(cfg), + UserContent: NewUserContentClient(cfg), + UserSubscription: NewUserSubscriptionClient(cfg), + Vehicle: NewVehicleClient(cfg), + VehicleAverage: NewVehicleAverageClient(cfg), + VehicleSnapshot: NewVehicleSnapshotClient(cfg), + }, nil +} + +// Debug returns a new debug-client. It's used to get verbose logging on specific operations. +// +// client.Debug(). +// Account. +// Query(). +// Count(ctx) +func (c *Client) Debug() *Client { + if c.debug { + return c + } + cfg := c.config + cfg.driver = dialect.Debug(c.driver, c.log) + client := &Client{config: cfg} + client.init() + return client +} + +// Close closes the database connection and prevents new queries from starting. +func (c *Client) Close() error { + return c.driver.Close() +} + +// Use adds the mutation hooks to all the entity clients. +// In order to add hooks to a specific client, call: `client.Node.Use(...)`. +func (c *Client) Use(hooks ...Hook) { + for _, n := range []interface{ Use(...Hook) }{ + c.Account, c.AccountSnapshot, c.AchievementsSnapshot, c.AppConfiguration, + c.ApplicationCommand, c.Clan, c.CronTask, c.User, c.UserConnection, + c.UserContent, c.UserSubscription, c.Vehicle, c.VehicleAverage, + c.VehicleSnapshot, + } { + n.Use(hooks...) + } +} + +// Intercept adds the query interceptors to all the entity clients. +// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. +func (c *Client) Intercept(interceptors ...Interceptor) { + for _, n := range []interface{ Intercept(...Interceptor) }{ + c.Account, c.AccountSnapshot, c.AchievementsSnapshot, c.AppConfiguration, + c.ApplicationCommand, c.Clan, c.CronTask, c.User, c.UserConnection, + c.UserContent, c.UserSubscription, c.Vehicle, c.VehicleAverage, + c.VehicleSnapshot, + } { + n.Intercept(interceptors...) + } +} + +// Mutate implements the ent.Mutator interface. +func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { + switch m := m.(type) { + case *AccountMutation: + return c.Account.mutate(ctx, m) + case *AccountSnapshotMutation: + return c.AccountSnapshot.mutate(ctx, m) + case *AchievementsSnapshotMutation: + return c.AchievementsSnapshot.mutate(ctx, m) + case *AppConfigurationMutation: + return c.AppConfiguration.mutate(ctx, m) + case *ApplicationCommandMutation: + return c.ApplicationCommand.mutate(ctx, m) + case *ClanMutation: + return c.Clan.mutate(ctx, m) + case *CronTaskMutation: + return c.CronTask.mutate(ctx, m) + case *UserMutation: + return c.User.mutate(ctx, m) + case *UserConnectionMutation: + return c.UserConnection.mutate(ctx, m) + case *UserContentMutation: + return c.UserContent.mutate(ctx, m) + case *UserSubscriptionMutation: + return c.UserSubscription.mutate(ctx, m) + case *VehicleMutation: + return c.Vehicle.mutate(ctx, m) + case *VehicleAverageMutation: + return c.VehicleAverage.mutate(ctx, m) + case *VehicleSnapshotMutation: + return c.VehicleSnapshot.mutate(ctx, m) + default: + return nil, fmt.Errorf("db: unknown mutation type %T", m) + } +} + +// AccountClient is a client for the Account schema. +type AccountClient struct { + config +} + +// NewAccountClient returns a client for the Account from the given config. +func NewAccountClient(c config) *AccountClient { + return &AccountClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `account.Hooks(f(g(h())))`. +func (c *AccountClient) Use(hooks ...Hook) { + c.hooks.Account = append(c.hooks.Account, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `account.Intercept(f(g(h())))`. +func (c *AccountClient) Intercept(interceptors ...Interceptor) { + c.inters.Account = append(c.inters.Account, interceptors...) +} + +// Create returns a builder for creating a Account entity. +func (c *AccountClient) Create() *AccountCreate { + mutation := newAccountMutation(c.config, OpCreate) + return &AccountCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Account entities. +func (c *AccountClient) CreateBulk(builders ...*AccountCreate) *AccountCreateBulk { + return &AccountCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *AccountClient) MapCreateBulk(slice any, setFunc func(*AccountCreate, int)) *AccountCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &AccountCreateBulk{err: fmt.Errorf("calling to AccountClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*AccountCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &AccountCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Account. +func (c *AccountClient) Update() *AccountUpdate { + mutation := newAccountMutation(c.config, OpUpdate) + return &AccountUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AccountClient) UpdateOne(a *Account) *AccountUpdateOne { + mutation := newAccountMutation(c.config, OpUpdateOne, withAccount(a)) + return &AccountUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AccountClient) UpdateOneID(id string) *AccountUpdateOne { + mutation := newAccountMutation(c.config, OpUpdateOne, withAccountID(id)) + return &AccountUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Account. +func (c *AccountClient) Delete() *AccountDelete { + mutation := newAccountMutation(c.config, OpDelete) + return &AccountDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AccountClient) DeleteOne(a *Account) *AccountDeleteOne { + return c.DeleteOneID(a.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AccountClient) DeleteOneID(id string) *AccountDeleteOne { + builder := c.Delete().Where(account.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AccountDeleteOne{builder} +} + +// Query returns a query builder for Account. +func (c *AccountClient) Query() *AccountQuery { + return &AccountQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeAccount}, + inters: c.Interceptors(), + } +} + +// Get returns a Account entity by its id. +func (c *AccountClient) Get(ctx context.Context, id string) (*Account, error) { + return c.Query().Where(account.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AccountClient) GetX(ctx context.Context, id string) *Account { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryClan queries the clan edge of a Account. +func (c *AccountClient) QueryClan(a *Account) *ClanQuery { + query := (&ClanClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := a.ID + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, id), + sqlgraph.To(clan.Table, clan.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, account.ClanTable, account.ClanColumn), + ) + fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QuerySnapshots queries the snapshots edge of a Account. +func (c *AccountClient) QuerySnapshots(a *Account) *AccountSnapshotQuery { + query := (&AccountSnapshotClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := a.ID + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, id), + sqlgraph.To(accountsnapshot.Table, accountsnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.SnapshotsTable, account.SnapshotsColumn), + ) + fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryVehicleSnapshots queries the vehicle_snapshots edge of a Account. +func (c *AccountClient) QueryVehicleSnapshots(a *Account) *VehicleSnapshotQuery { + query := (&VehicleSnapshotClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := a.ID + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, id), + sqlgraph.To(vehiclesnapshot.Table, vehiclesnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.VehicleSnapshotsTable, account.VehicleSnapshotsColumn), + ) + fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryAchievementSnapshots queries the achievement_snapshots edge of a Account. +func (c *AccountClient) QueryAchievementSnapshots(a *Account) *AchievementsSnapshotQuery { + query := (&AchievementsSnapshotClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := a.ID + step := sqlgraph.NewStep( + sqlgraph.From(account.Table, account.FieldID, id), + sqlgraph.To(achievementssnapshot.Table, achievementssnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.AchievementSnapshotsTable, account.AchievementSnapshotsColumn), + ) + fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *AccountClient) Hooks() []Hook { + return c.hooks.Account +} + +// Interceptors returns the client interceptors. +func (c *AccountClient) Interceptors() []Interceptor { + return c.inters.Account +} + +func (c *AccountClient) mutate(ctx context.Context, m *AccountMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&AccountCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&AccountUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&AccountUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&AccountDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown Account mutation op: %q", m.Op()) + } +} + +// AccountSnapshotClient is a client for the AccountSnapshot schema. +type AccountSnapshotClient struct { + config +} + +// NewAccountSnapshotClient returns a client for the AccountSnapshot from the given config. +func NewAccountSnapshotClient(c config) *AccountSnapshotClient { + return &AccountSnapshotClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `accountsnapshot.Hooks(f(g(h())))`. +func (c *AccountSnapshotClient) Use(hooks ...Hook) { + c.hooks.AccountSnapshot = append(c.hooks.AccountSnapshot, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `accountsnapshot.Intercept(f(g(h())))`. +func (c *AccountSnapshotClient) Intercept(interceptors ...Interceptor) { + c.inters.AccountSnapshot = append(c.inters.AccountSnapshot, interceptors...) +} + +// Create returns a builder for creating a AccountSnapshot entity. +func (c *AccountSnapshotClient) Create() *AccountSnapshotCreate { + mutation := newAccountSnapshotMutation(c.config, OpCreate) + return &AccountSnapshotCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of AccountSnapshot entities. +func (c *AccountSnapshotClient) CreateBulk(builders ...*AccountSnapshotCreate) *AccountSnapshotCreateBulk { + return &AccountSnapshotCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *AccountSnapshotClient) MapCreateBulk(slice any, setFunc func(*AccountSnapshotCreate, int)) *AccountSnapshotCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &AccountSnapshotCreateBulk{err: fmt.Errorf("calling to AccountSnapshotClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*AccountSnapshotCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &AccountSnapshotCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for AccountSnapshot. +func (c *AccountSnapshotClient) Update() *AccountSnapshotUpdate { + mutation := newAccountSnapshotMutation(c.config, OpUpdate) + return &AccountSnapshotUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AccountSnapshotClient) UpdateOne(as *AccountSnapshot) *AccountSnapshotUpdateOne { + mutation := newAccountSnapshotMutation(c.config, OpUpdateOne, withAccountSnapshot(as)) + return &AccountSnapshotUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AccountSnapshotClient) UpdateOneID(id string) *AccountSnapshotUpdateOne { + mutation := newAccountSnapshotMutation(c.config, OpUpdateOne, withAccountSnapshotID(id)) + return &AccountSnapshotUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for AccountSnapshot. +func (c *AccountSnapshotClient) Delete() *AccountSnapshotDelete { + mutation := newAccountSnapshotMutation(c.config, OpDelete) + return &AccountSnapshotDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AccountSnapshotClient) DeleteOne(as *AccountSnapshot) *AccountSnapshotDeleteOne { + return c.DeleteOneID(as.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AccountSnapshotClient) DeleteOneID(id string) *AccountSnapshotDeleteOne { + builder := c.Delete().Where(accountsnapshot.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AccountSnapshotDeleteOne{builder} +} + +// Query returns a query builder for AccountSnapshot. +func (c *AccountSnapshotClient) Query() *AccountSnapshotQuery { + return &AccountSnapshotQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeAccountSnapshot}, + inters: c.Interceptors(), + } +} + +// Get returns a AccountSnapshot entity by its id. +func (c *AccountSnapshotClient) Get(ctx context.Context, id string) (*AccountSnapshot, error) { + return c.Query().Where(accountsnapshot.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AccountSnapshotClient) GetX(ctx context.Context, id string) *AccountSnapshot { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryAccount queries the account edge of a AccountSnapshot. +func (c *AccountSnapshotClient) QueryAccount(as *AccountSnapshot) *AccountQuery { + query := (&AccountClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := as.ID + step := sqlgraph.NewStep( + sqlgraph.From(accountsnapshot.Table, accountsnapshot.FieldID, id), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, accountsnapshot.AccountTable, accountsnapshot.AccountColumn), + ) + fromV = sqlgraph.Neighbors(as.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *AccountSnapshotClient) Hooks() []Hook { + return c.hooks.AccountSnapshot +} + +// Interceptors returns the client interceptors. +func (c *AccountSnapshotClient) Interceptors() []Interceptor { + return c.inters.AccountSnapshot +} + +func (c *AccountSnapshotClient) mutate(ctx context.Context, m *AccountSnapshotMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&AccountSnapshotCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&AccountSnapshotUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&AccountSnapshotUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&AccountSnapshotDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown AccountSnapshot mutation op: %q", m.Op()) + } +} + +// AchievementsSnapshotClient is a client for the AchievementsSnapshot schema. +type AchievementsSnapshotClient struct { + config +} + +// NewAchievementsSnapshotClient returns a client for the AchievementsSnapshot from the given config. +func NewAchievementsSnapshotClient(c config) *AchievementsSnapshotClient { + return &AchievementsSnapshotClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `achievementssnapshot.Hooks(f(g(h())))`. +func (c *AchievementsSnapshotClient) Use(hooks ...Hook) { + c.hooks.AchievementsSnapshot = append(c.hooks.AchievementsSnapshot, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `achievementssnapshot.Intercept(f(g(h())))`. +func (c *AchievementsSnapshotClient) Intercept(interceptors ...Interceptor) { + c.inters.AchievementsSnapshot = append(c.inters.AchievementsSnapshot, interceptors...) +} + +// Create returns a builder for creating a AchievementsSnapshot entity. +func (c *AchievementsSnapshotClient) Create() *AchievementsSnapshotCreate { + mutation := newAchievementsSnapshotMutation(c.config, OpCreate) + return &AchievementsSnapshotCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of AchievementsSnapshot entities. +func (c *AchievementsSnapshotClient) CreateBulk(builders ...*AchievementsSnapshotCreate) *AchievementsSnapshotCreateBulk { + return &AchievementsSnapshotCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *AchievementsSnapshotClient) MapCreateBulk(slice any, setFunc func(*AchievementsSnapshotCreate, int)) *AchievementsSnapshotCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &AchievementsSnapshotCreateBulk{err: fmt.Errorf("calling to AchievementsSnapshotClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*AchievementsSnapshotCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &AchievementsSnapshotCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for AchievementsSnapshot. +func (c *AchievementsSnapshotClient) Update() *AchievementsSnapshotUpdate { + mutation := newAchievementsSnapshotMutation(c.config, OpUpdate) + return &AchievementsSnapshotUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AchievementsSnapshotClient) UpdateOne(as *AchievementsSnapshot) *AchievementsSnapshotUpdateOne { + mutation := newAchievementsSnapshotMutation(c.config, OpUpdateOne, withAchievementsSnapshot(as)) + return &AchievementsSnapshotUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AchievementsSnapshotClient) UpdateOneID(id string) *AchievementsSnapshotUpdateOne { + mutation := newAchievementsSnapshotMutation(c.config, OpUpdateOne, withAchievementsSnapshotID(id)) + return &AchievementsSnapshotUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for AchievementsSnapshot. +func (c *AchievementsSnapshotClient) Delete() *AchievementsSnapshotDelete { + mutation := newAchievementsSnapshotMutation(c.config, OpDelete) + return &AchievementsSnapshotDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AchievementsSnapshotClient) DeleteOne(as *AchievementsSnapshot) *AchievementsSnapshotDeleteOne { + return c.DeleteOneID(as.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AchievementsSnapshotClient) DeleteOneID(id string) *AchievementsSnapshotDeleteOne { + builder := c.Delete().Where(achievementssnapshot.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AchievementsSnapshotDeleteOne{builder} +} + +// Query returns a query builder for AchievementsSnapshot. +func (c *AchievementsSnapshotClient) Query() *AchievementsSnapshotQuery { + return &AchievementsSnapshotQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeAchievementsSnapshot}, + inters: c.Interceptors(), + } +} + +// Get returns a AchievementsSnapshot entity by its id. +func (c *AchievementsSnapshotClient) Get(ctx context.Context, id string) (*AchievementsSnapshot, error) { + return c.Query().Where(achievementssnapshot.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AchievementsSnapshotClient) GetX(ctx context.Context, id string) *AchievementsSnapshot { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryAccount queries the account edge of a AchievementsSnapshot. +func (c *AchievementsSnapshotClient) QueryAccount(as *AchievementsSnapshot) *AccountQuery { + query := (&AccountClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := as.ID + step := sqlgraph.NewStep( + sqlgraph.From(achievementssnapshot.Table, achievementssnapshot.FieldID, id), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, achievementssnapshot.AccountTable, achievementssnapshot.AccountColumn), + ) + fromV = sqlgraph.Neighbors(as.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *AchievementsSnapshotClient) Hooks() []Hook { + return c.hooks.AchievementsSnapshot +} + +// Interceptors returns the client interceptors. +func (c *AchievementsSnapshotClient) Interceptors() []Interceptor { + return c.inters.AchievementsSnapshot +} + +func (c *AchievementsSnapshotClient) mutate(ctx context.Context, m *AchievementsSnapshotMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&AchievementsSnapshotCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&AchievementsSnapshotUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&AchievementsSnapshotUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&AchievementsSnapshotDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown AchievementsSnapshot mutation op: %q", m.Op()) + } +} + +// AppConfigurationClient is a client for the AppConfiguration schema. +type AppConfigurationClient struct { + config +} + +// NewAppConfigurationClient returns a client for the AppConfiguration from the given config. +func NewAppConfigurationClient(c config) *AppConfigurationClient { + return &AppConfigurationClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `appconfiguration.Hooks(f(g(h())))`. +func (c *AppConfigurationClient) Use(hooks ...Hook) { + c.hooks.AppConfiguration = append(c.hooks.AppConfiguration, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `appconfiguration.Intercept(f(g(h())))`. +func (c *AppConfigurationClient) Intercept(interceptors ...Interceptor) { + c.inters.AppConfiguration = append(c.inters.AppConfiguration, interceptors...) +} + +// Create returns a builder for creating a AppConfiguration entity. +func (c *AppConfigurationClient) Create() *AppConfigurationCreate { + mutation := newAppConfigurationMutation(c.config, OpCreate) + return &AppConfigurationCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of AppConfiguration entities. +func (c *AppConfigurationClient) CreateBulk(builders ...*AppConfigurationCreate) *AppConfigurationCreateBulk { + return &AppConfigurationCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *AppConfigurationClient) MapCreateBulk(slice any, setFunc func(*AppConfigurationCreate, int)) *AppConfigurationCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &AppConfigurationCreateBulk{err: fmt.Errorf("calling to AppConfigurationClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*AppConfigurationCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &AppConfigurationCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for AppConfiguration. +func (c *AppConfigurationClient) Update() *AppConfigurationUpdate { + mutation := newAppConfigurationMutation(c.config, OpUpdate) + return &AppConfigurationUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AppConfigurationClient) UpdateOne(ac *AppConfiguration) *AppConfigurationUpdateOne { + mutation := newAppConfigurationMutation(c.config, OpUpdateOne, withAppConfiguration(ac)) + return &AppConfigurationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AppConfigurationClient) UpdateOneID(id string) *AppConfigurationUpdateOne { + mutation := newAppConfigurationMutation(c.config, OpUpdateOne, withAppConfigurationID(id)) + return &AppConfigurationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for AppConfiguration. +func (c *AppConfigurationClient) Delete() *AppConfigurationDelete { + mutation := newAppConfigurationMutation(c.config, OpDelete) + return &AppConfigurationDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AppConfigurationClient) DeleteOne(ac *AppConfiguration) *AppConfigurationDeleteOne { + return c.DeleteOneID(ac.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AppConfigurationClient) DeleteOneID(id string) *AppConfigurationDeleteOne { + builder := c.Delete().Where(appconfiguration.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AppConfigurationDeleteOne{builder} +} + +// Query returns a query builder for AppConfiguration. +func (c *AppConfigurationClient) Query() *AppConfigurationQuery { + return &AppConfigurationQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeAppConfiguration}, + inters: c.Interceptors(), + } +} + +// Get returns a AppConfiguration entity by its id. +func (c *AppConfigurationClient) Get(ctx context.Context, id string) (*AppConfiguration, error) { + return c.Query().Where(appconfiguration.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AppConfigurationClient) GetX(ctx context.Context, id string) *AppConfiguration { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *AppConfigurationClient) Hooks() []Hook { + return c.hooks.AppConfiguration +} + +// Interceptors returns the client interceptors. +func (c *AppConfigurationClient) Interceptors() []Interceptor { + return c.inters.AppConfiguration +} + +func (c *AppConfigurationClient) mutate(ctx context.Context, m *AppConfigurationMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&AppConfigurationCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&AppConfigurationUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&AppConfigurationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&AppConfigurationDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown AppConfiguration mutation op: %q", m.Op()) + } +} + +// ApplicationCommandClient is a client for the ApplicationCommand schema. +type ApplicationCommandClient struct { + config +} + +// NewApplicationCommandClient returns a client for the ApplicationCommand from the given config. +func NewApplicationCommandClient(c config) *ApplicationCommandClient { + return &ApplicationCommandClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `applicationcommand.Hooks(f(g(h())))`. +func (c *ApplicationCommandClient) Use(hooks ...Hook) { + c.hooks.ApplicationCommand = append(c.hooks.ApplicationCommand, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `applicationcommand.Intercept(f(g(h())))`. +func (c *ApplicationCommandClient) Intercept(interceptors ...Interceptor) { + c.inters.ApplicationCommand = append(c.inters.ApplicationCommand, interceptors...) +} + +// Create returns a builder for creating a ApplicationCommand entity. +func (c *ApplicationCommandClient) Create() *ApplicationCommandCreate { + mutation := newApplicationCommandMutation(c.config, OpCreate) + return &ApplicationCommandCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of ApplicationCommand entities. +func (c *ApplicationCommandClient) CreateBulk(builders ...*ApplicationCommandCreate) *ApplicationCommandCreateBulk { + return &ApplicationCommandCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ApplicationCommandClient) MapCreateBulk(slice any, setFunc func(*ApplicationCommandCreate, int)) *ApplicationCommandCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ApplicationCommandCreateBulk{err: fmt.Errorf("calling to ApplicationCommandClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ApplicationCommandCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ApplicationCommandCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for ApplicationCommand. +func (c *ApplicationCommandClient) Update() *ApplicationCommandUpdate { + mutation := newApplicationCommandMutation(c.config, OpUpdate) + return &ApplicationCommandUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *ApplicationCommandClient) UpdateOne(ac *ApplicationCommand) *ApplicationCommandUpdateOne { + mutation := newApplicationCommandMutation(c.config, OpUpdateOne, withApplicationCommand(ac)) + return &ApplicationCommandUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *ApplicationCommandClient) UpdateOneID(id string) *ApplicationCommandUpdateOne { + mutation := newApplicationCommandMutation(c.config, OpUpdateOne, withApplicationCommandID(id)) + return &ApplicationCommandUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for ApplicationCommand. +func (c *ApplicationCommandClient) Delete() *ApplicationCommandDelete { + mutation := newApplicationCommandMutation(c.config, OpDelete) + return &ApplicationCommandDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *ApplicationCommandClient) DeleteOne(ac *ApplicationCommand) *ApplicationCommandDeleteOne { + return c.DeleteOneID(ac.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *ApplicationCommandClient) DeleteOneID(id string) *ApplicationCommandDeleteOne { + builder := c.Delete().Where(applicationcommand.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &ApplicationCommandDeleteOne{builder} +} + +// Query returns a query builder for ApplicationCommand. +func (c *ApplicationCommandClient) Query() *ApplicationCommandQuery { + return &ApplicationCommandQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeApplicationCommand}, + inters: c.Interceptors(), + } +} + +// Get returns a ApplicationCommand entity by its id. +func (c *ApplicationCommandClient) Get(ctx context.Context, id string) (*ApplicationCommand, error) { + return c.Query().Where(applicationcommand.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *ApplicationCommandClient) GetX(ctx context.Context, id string) *ApplicationCommand { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *ApplicationCommandClient) Hooks() []Hook { + return c.hooks.ApplicationCommand +} + +// Interceptors returns the client interceptors. +func (c *ApplicationCommandClient) Interceptors() []Interceptor { + return c.inters.ApplicationCommand +} + +func (c *ApplicationCommandClient) mutate(ctx context.Context, m *ApplicationCommandMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&ApplicationCommandCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&ApplicationCommandUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&ApplicationCommandUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&ApplicationCommandDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown ApplicationCommand mutation op: %q", m.Op()) + } +} + +// ClanClient is a client for the Clan schema. +type ClanClient struct { + config +} + +// NewClanClient returns a client for the Clan from the given config. +func NewClanClient(c config) *ClanClient { + return &ClanClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `clan.Hooks(f(g(h())))`. +func (c *ClanClient) Use(hooks ...Hook) { + c.hooks.Clan = append(c.hooks.Clan, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `clan.Intercept(f(g(h())))`. +func (c *ClanClient) Intercept(interceptors ...Interceptor) { + c.inters.Clan = append(c.inters.Clan, interceptors...) +} + +// Create returns a builder for creating a Clan entity. +func (c *ClanClient) Create() *ClanCreate { + mutation := newClanMutation(c.config, OpCreate) + return &ClanCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Clan entities. +func (c *ClanClient) CreateBulk(builders ...*ClanCreate) *ClanCreateBulk { + return &ClanCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ClanClient) MapCreateBulk(slice any, setFunc func(*ClanCreate, int)) *ClanCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ClanCreateBulk{err: fmt.Errorf("calling to ClanClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ClanCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ClanCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Clan. +func (c *ClanClient) Update() *ClanUpdate { + mutation := newClanMutation(c.config, OpUpdate) + return &ClanUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *ClanClient) UpdateOne(cl *Clan) *ClanUpdateOne { + mutation := newClanMutation(c.config, OpUpdateOne, withClan(cl)) + return &ClanUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *ClanClient) UpdateOneID(id string) *ClanUpdateOne { + mutation := newClanMutation(c.config, OpUpdateOne, withClanID(id)) + return &ClanUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Clan. +func (c *ClanClient) Delete() *ClanDelete { + mutation := newClanMutation(c.config, OpDelete) + return &ClanDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *ClanClient) DeleteOne(cl *Clan) *ClanDeleteOne { + return c.DeleteOneID(cl.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *ClanClient) DeleteOneID(id string) *ClanDeleteOne { + builder := c.Delete().Where(clan.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &ClanDeleteOne{builder} +} + +// Query returns a query builder for Clan. +func (c *ClanClient) Query() *ClanQuery { + return &ClanQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeClan}, + inters: c.Interceptors(), + } +} + +// Get returns a Clan entity by its id. +func (c *ClanClient) Get(ctx context.Context, id string) (*Clan, error) { + return c.Query().Where(clan.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *ClanClient) GetX(ctx context.Context, id string) *Clan { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryAccounts queries the accounts edge of a Clan. +func (c *ClanClient) QueryAccounts(cl *Clan) *AccountQuery { + query := (&AccountClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := cl.ID + step := sqlgraph.NewStep( + sqlgraph.From(clan.Table, clan.FieldID, id), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, clan.AccountsTable, clan.AccountsColumn), + ) + fromV = sqlgraph.Neighbors(cl.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *ClanClient) Hooks() []Hook { + return c.hooks.Clan +} + +// Interceptors returns the client interceptors. +func (c *ClanClient) Interceptors() []Interceptor { + return c.inters.Clan +} + +func (c *ClanClient) mutate(ctx context.Context, m *ClanMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&ClanCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&ClanUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&ClanUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&ClanDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown Clan mutation op: %q", m.Op()) + } +} + +// CronTaskClient is a client for the CronTask schema. +type CronTaskClient struct { + config +} + +// NewCronTaskClient returns a client for the CronTask from the given config. +func NewCronTaskClient(c config) *CronTaskClient { + return &CronTaskClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `crontask.Hooks(f(g(h())))`. +func (c *CronTaskClient) Use(hooks ...Hook) { + c.hooks.CronTask = append(c.hooks.CronTask, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `crontask.Intercept(f(g(h())))`. +func (c *CronTaskClient) Intercept(interceptors ...Interceptor) { + c.inters.CronTask = append(c.inters.CronTask, interceptors...) +} + +// Create returns a builder for creating a CronTask entity. +func (c *CronTaskClient) Create() *CronTaskCreate { + mutation := newCronTaskMutation(c.config, OpCreate) + return &CronTaskCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of CronTask entities. +func (c *CronTaskClient) CreateBulk(builders ...*CronTaskCreate) *CronTaskCreateBulk { + return &CronTaskCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *CronTaskClient) MapCreateBulk(slice any, setFunc func(*CronTaskCreate, int)) *CronTaskCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &CronTaskCreateBulk{err: fmt.Errorf("calling to CronTaskClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*CronTaskCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &CronTaskCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for CronTask. +func (c *CronTaskClient) Update() *CronTaskUpdate { + mutation := newCronTaskMutation(c.config, OpUpdate) + return &CronTaskUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *CronTaskClient) UpdateOne(ct *CronTask) *CronTaskUpdateOne { + mutation := newCronTaskMutation(c.config, OpUpdateOne, withCronTask(ct)) + return &CronTaskUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *CronTaskClient) UpdateOneID(id string) *CronTaskUpdateOne { + mutation := newCronTaskMutation(c.config, OpUpdateOne, withCronTaskID(id)) + return &CronTaskUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for CronTask. +func (c *CronTaskClient) Delete() *CronTaskDelete { + mutation := newCronTaskMutation(c.config, OpDelete) + return &CronTaskDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *CronTaskClient) DeleteOne(ct *CronTask) *CronTaskDeleteOne { + return c.DeleteOneID(ct.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *CronTaskClient) DeleteOneID(id string) *CronTaskDeleteOne { + builder := c.Delete().Where(crontask.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &CronTaskDeleteOne{builder} +} + +// Query returns a query builder for CronTask. +func (c *CronTaskClient) Query() *CronTaskQuery { + return &CronTaskQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeCronTask}, + inters: c.Interceptors(), + } +} + +// Get returns a CronTask entity by its id. +func (c *CronTaskClient) Get(ctx context.Context, id string) (*CronTask, error) { + return c.Query().Where(crontask.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *CronTaskClient) GetX(ctx context.Context, id string) *CronTask { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *CronTaskClient) Hooks() []Hook { + return c.hooks.CronTask +} + +// Interceptors returns the client interceptors. +func (c *CronTaskClient) Interceptors() []Interceptor { + return c.inters.CronTask +} + +func (c *CronTaskClient) mutate(ctx context.Context, m *CronTaskMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&CronTaskCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&CronTaskUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&CronTaskUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&CronTaskDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown CronTask mutation op: %q", m.Op()) + } +} + +// UserClient is a client for the User schema. +type UserClient struct { + config +} + +// NewUserClient returns a client for the User from the given config. +func NewUserClient(c config) *UserClient { + return &UserClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `user.Hooks(f(g(h())))`. +func (c *UserClient) Use(hooks ...Hook) { + c.hooks.User = append(c.hooks.User, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `user.Intercept(f(g(h())))`. +func (c *UserClient) Intercept(interceptors ...Interceptor) { + c.inters.User = append(c.inters.User, interceptors...) +} + +// Create returns a builder for creating a User entity. +func (c *UserClient) Create() *UserCreate { + mutation := newUserMutation(c.config, OpCreate) + return &UserCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of User entities. +func (c *UserClient) CreateBulk(builders ...*UserCreate) *UserCreateBulk { + return &UserCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *UserClient) MapCreateBulk(slice any, setFunc func(*UserCreate, int)) *UserCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &UserCreateBulk{err: fmt.Errorf("calling to UserClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*UserCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &UserCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for User. +func (c *UserClient) Update() *UserUpdate { + mutation := newUserMutation(c.config, OpUpdate) + return &UserUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserClient) UpdateOne(u *User) *UserUpdateOne { + mutation := newUserMutation(c.config, OpUpdateOne, withUser(u)) + return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserClient) UpdateOneID(id string) *UserUpdateOne { + mutation := newUserMutation(c.config, OpUpdateOne, withUserID(id)) + return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for User. +func (c *UserClient) Delete() *UserDelete { + mutation := newUserMutation(c.config, OpDelete) + return &UserDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserClient) DeleteOne(u *User) *UserDeleteOne { + return c.DeleteOneID(u.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserClient) DeleteOneID(id string) *UserDeleteOne { + builder := c.Delete().Where(user.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserDeleteOne{builder} +} + +// Query returns a query builder for User. +func (c *UserClient) Query() *UserQuery { + return &UserQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUser}, + inters: c.Interceptors(), + } +} + +// Get returns a User entity by its id. +func (c *UserClient) Get(ctx context.Context, id string) (*User, error) { + return c.Query().Where(user.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserClient) GetX(ctx context.Context, id string) *User { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QuerySubscriptions queries the subscriptions edge of a User. +func (c *UserClient) QuerySubscriptions(u *User) *UserSubscriptionQuery { + query := (&UserSubscriptionClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(usersubscription.Table, usersubscription.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.SubscriptionsTable, user.SubscriptionsColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryConnections queries the connections edge of a User. +func (c *UserClient) QueryConnections(u *User) *UserConnectionQuery { + query := (&UserConnectionClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(userconnection.Table, userconnection.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.ConnectionsTable, user.ConnectionsColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryContent queries the content edge of a User. +func (c *UserClient) QueryContent(u *User) *UserContentQuery { + query := (&UserContentClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(usercontent.Table, usercontent.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.ContentTable, user.ContentColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *UserClient) Hooks() []Hook { + return c.hooks.User +} + +// Interceptors returns the client interceptors. +func (c *UserClient) Interceptors() []Interceptor { + return c.inters.User +} + +func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown User mutation op: %q", m.Op()) + } +} + +// UserConnectionClient is a client for the UserConnection schema. +type UserConnectionClient struct { + config +} + +// NewUserConnectionClient returns a client for the UserConnection from the given config. +func NewUserConnectionClient(c config) *UserConnectionClient { + return &UserConnectionClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `userconnection.Hooks(f(g(h())))`. +func (c *UserConnectionClient) Use(hooks ...Hook) { + c.hooks.UserConnection = append(c.hooks.UserConnection, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `userconnection.Intercept(f(g(h())))`. +func (c *UserConnectionClient) Intercept(interceptors ...Interceptor) { + c.inters.UserConnection = append(c.inters.UserConnection, interceptors...) +} + +// Create returns a builder for creating a UserConnection entity. +func (c *UserConnectionClient) Create() *UserConnectionCreate { + mutation := newUserConnectionMutation(c.config, OpCreate) + return &UserConnectionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of UserConnection entities. +func (c *UserConnectionClient) CreateBulk(builders ...*UserConnectionCreate) *UserConnectionCreateBulk { + return &UserConnectionCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *UserConnectionClient) MapCreateBulk(slice any, setFunc func(*UserConnectionCreate, int)) *UserConnectionCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &UserConnectionCreateBulk{err: fmt.Errorf("calling to UserConnectionClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*UserConnectionCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &UserConnectionCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for UserConnection. +func (c *UserConnectionClient) Update() *UserConnectionUpdate { + mutation := newUserConnectionMutation(c.config, OpUpdate) + return &UserConnectionUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserConnectionClient) UpdateOne(uc *UserConnection) *UserConnectionUpdateOne { + mutation := newUserConnectionMutation(c.config, OpUpdateOne, withUserConnection(uc)) + return &UserConnectionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserConnectionClient) UpdateOneID(id string) *UserConnectionUpdateOne { + mutation := newUserConnectionMutation(c.config, OpUpdateOne, withUserConnectionID(id)) + return &UserConnectionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for UserConnection. +func (c *UserConnectionClient) Delete() *UserConnectionDelete { + mutation := newUserConnectionMutation(c.config, OpDelete) + return &UserConnectionDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserConnectionClient) DeleteOne(uc *UserConnection) *UserConnectionDeleteOne { + return c.DeleteOneID(uc.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserConnectionClient) DeleteOneID(id string) *UserConnectionDeleteOne { + builder := c.Delete().Where(userconnection.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserConnectionDeleteOne{builder} +} + +// Query returns a query builder for UserConnection. +func (c *UserConnectionClient) Query() *UserConnectionQuery { + return &UserConnectionQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUserConnection}, + inters: c.Interceptors(), + } +} + +// Get returns a UserConnection entity by its id. +func (c *UserConnectionClient) Get(ctx context.Context, id string) (*UserConnection, error) { + return c.Query().Where(userconnection.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserConnectionClient) GetX(ctx context.Context, id string) *UserConnection { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a UserConnection. +func (c *UserConnectionClient) QueryUser(uc *UserConnection) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := uc.ID + step := sqlgraph.NewStep( + sqlgraph.From(userconnection.Table, userconnection.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, userconnection.UserTable, userconnection.UserColumn), + ) + fromV = sqlgraph.Neighbors(uc.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *UserConnectionClient) Hooks() []Hook { + return c.hooks.UserConnection +} + +// Interceptors returns the client interceptors. +func (c *UserConnectionClient) Interceptors() []Interceptor { + return c.inters.UserConnection +} + +func (c *UserConnectionClient) mutate(ctx context.Context, m *UserConnectionMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserConnectionCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserConnectionUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserConnectionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserConnectionDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown UserConnection mutation op: %q", m.Op()) + } +} + +// UserContentClient is a client for the UserContent schema. +type UserContentClient struct { + config +} + +// NewUserContentClient returns a client for the UserContent from the given config. +func NewUserContentClient(c config) *UserContentClient { + return &UserContentClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `usercontent.Hooks(f(g(h())))`. +func (c *UserContentClient) Use(hooks ...Hook) { + c.hooks.UserContent = append(c.hooks.UserContent, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `usercontent.Intercept(f(g(h())))`. +func (c *UserContentClient) Intercept(interceptors ...Interceptor) { + c.inters.UserContent = append(c.inters.UserContent, interceptors...) +} + +// Create returns a builder for creating a UserContent entity. +func (c *UserContentClient) Create() *UserContentCreate { + mutation := newUserContentMutation(c.config, OpCreate) + return &UserContentCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of UserContent entities. +func (c *UserContentClient) CreateBulk(builders ...*UserContentCreate) *UserContentCreateBulk { + return &UserContentCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *UserContentClient) MapCreateBulk(slice any, setFunc func(*UserContentCreate, int)) *UserContentCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &UserContentCreateBulk{err: fmt.Errorf("calling to UserContentClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*UserContentCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &UserContentCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for UserContent. +func (c *UserContentClient) Update() *UserContentUpdate { + mutation := newUserContentMutation(c.config, OpUpdate) + return &UserContentUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserContentClient) UpdateOne(uc *UserContent) *UserContentUpdateOne { + mutation := newUserContentMutation(c.config, OpUpdateOne, withUserContent(uc)) + return &UserContentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserContentClient) UpdateOneID(id string) *UserContentUpdateOne { + mutation := newUserContentMutation(c.config, OpUpdateOne, withUserContentID(id)) + return &UserContentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for UserContent. +func (c *UserContentClient) Delete() *UserContentDelete { + mutation := newUserContentMutation(c.config, OpDelete) + return &UserContentDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserContentClient) DeleteOne(uc *UserContent) *UserContentDeleteOne { + return c.DeleteOneID(uc.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserContentClient) DeleteOneID(id string) *UserContentDeleteOne { + builder := c.Delete().Where(usercontent.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserContentDeleteOne{builder} +} + +// Query returns a query builder for UserContent. +func (c *UserContentClient) Query() *UserContentQuery { + return &UserContentQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUserContent}, + inters: c.Interceptors(), + } +} + +// Get returns a UserContent entity by its id. +func (c *UserContentClient) Get(ctx context.Context, id string) (*UserContent, error) { + return c.Query().Where(usercontent.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserContentClient) GetX(ctx context.Context, id string) *UserContent { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a UserContent. +func (c *UserContentClient) QueryUser(uc *UserContent) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := uc.ID + step := sqlgraph.NewStep( + sqlgraph.From(usercontent.Table, usercontent.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, usercontent.UserTable, usercontent.UserColumn), + ) + fromV = sqlgraph.Neighbors(uc.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *UserContentClient) Hooks() []Hook { + return c.hooks.UserContent +} + +// Interceptors returns the client interceptors. +func (c *UserContentClient) Interceptors() []Interceptor { + return c.inters.UserContent +} + +func (c *UserContentClient) mutate(ctx context.Context, m *UserContentMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserContentCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserContentUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserContentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserContentDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown UserContent mutation op: %q", m.Op()) + } +} + +// UserSubscriptionClient is a client for the UserSubscription schema. +type UserSubscriptionClient struct { + config +} + +// NewUserSubscriptionClient returns a client for the UserSubscription from the given config. +func NewUserSubscriptionClient(c config) *UserSubscriptionClient { + return &UserSubscriptionClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `usersubscription.Hooks(f(g(h())))`. +func (c *UserSubscriptionClient) Use(hooks ...Hook) { + c.hooks.UserSubscription = append(c.hooks.UserSubscription, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `usersubscription.Intercept(f(g(h())))`. +func (c *UserSubscriptionClient) Intercept(interceptors ...Interceptor) { + c.inters.UserSubscription = append(c.inters.UserSubscription, interceptors...) +} + +// Create returns a builder for creating a UserSubscription entity. +func (c *UserSubscriptionClient) Create() *UserSubscriptionCreate { + mutation := newUserSubscriptionMutation(c.config, OpCreate) + return &UserSubscriptionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of UserSubscription entities. +func (c *UserSubscriptionClient) CreateBulk(builders ...*UserSubscriptionCreate) *UserSubscriptionCreateBulk { + return &UserSubscriptionCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *UserSubscriptionClient) MapCreateBulk(slice any, setFunc func(*UserSubscriptionCreate, int)) *UserSubscriptionCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &UserSubscriptionCreateBulk{err: fmt.Errorf("calling to UserSubscriptionClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*UserSubscriptionCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &UserSubscriptionCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for UserSubscription. +func (c *UserSubscriptionClient) Update() *UserSubscriptionUpdate { + mutation := newUserSubscriptionMutation(c.config, OpUpdate) + return &UserSubscriptionUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserSubscriptionClient) UpdateOne(us *UserSubscription) *UserSubscriptionUpdateOne { + mutation := newUserSubscriptionMutation(c.config, OpUpdateOne, withUserSubscription(us)) + return &UserSubscriptionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserSubscriptionClient) UpdateOneID(id string) *UserSubscriptionUpdateOne { + mutation := newUserSubscriptionMutation(c.config, OpUpdateOne, withUserSubscriptionID(id)) + return &UserSubscriptionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for UserSubscription. +func (c *UserSubscriptionClient) Delete() *UserSubscriptionDelete { + mutation := newUserSubscriptionMutation(c.config, OpDelete) + return &UserSubscriptionDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserSubscriptionClient) DeleteOne(us *UserSubscription) *UserSubscriptionDeleteOne { + return c.DeleteOneID(us.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserSubscriptionClient) DeleteOneID(id string) *UserSubscriptionDeleteOne { + builder := c.Delete().Where(usersubscription.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserSubscriptionDeleteOne{builder} +} + +// Query returns a query builder for UserSubscription. +func (c *UserSubscriptionClient) Query() *UserSubscriptionQuery { + return &UserSubscriptionQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUserSubscription}, + inters: c.Interceptors(), + } +} + +// Get returns a UserSubscription entity by its id. +func (c *UserSubscriptionClient) Get(ctx context.Context, id string) (*UserSubscription, error) { + return c.Query().Where(usersubscription.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserSubscriptionClient) GetX(ctx context.Context, id string) *UserSubscription { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a UserSubscription. +func (c *UserSubscriptionClient) QueryUser(us *UserSubscription) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := us.ID + step := sqlgraph.NewStep( + sqlgraph.From(usersubscription.Table, usersubscription.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, usersubscription.UserTable, usersubscription.UserColumn), + ) + fromV = sqlgraph.Neighbors(us.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *UserSubscriptionClient) Hooks() []Hook { + return c.hooks.UserSubscription +} + +// Interceptors returns the client interceptors. +func (c *UserSubscriptionClient) Interceptors() []Interceptor { + return c.inters.UserSubscription +} + +func (c *UserSubscriptionClient) mutate(ctx context.Context, m *UserSubscriptionMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserSubscriptionCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserSubscriptionUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserSubscriptionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserSubscriptionDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown UserSubscription mutation op: %q", m.Op()) + } +} + +// VehicleClient is a client for the Vehicle schema. +type VehicleClient struct { + config +} + +// NewVehicleClient returns a client for the Vehicle from the given config. +func NewVehicleClient(c config) *VehicleClient { + return &VehicleClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `vehicle.Hooks(f(g(h())))`. +func (c *VehicleClient) Use(hooks ...Hook) { + c.hooks.Vehicle = append(c.hooks.Vehicle, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `vehicle.Intercept(f(g(h())))`. +func (c *VehicleClient) Intercept(interceptors ...Interceptor) { + c.inters.Vehicle = append(c.inters.Vehicle, interceptors...) +} + +// Create returns a builder for creating a Vehicle entity. +func (c *VehicleClient) Create() *VehicleCreate { + mutation := newVehicleMutation(c.config, OpCreate) + return &VehicleCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Vehicle entities. +func (c *VehicleClient) CreateBulk(builders ...*VehicleCreate) *VehicleCreateBulk { + return &VehicleCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *VehicleClient) MapCreateBulk(slice any, setFunc func(*VehicleCreate, int)) *VehicleCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &VehicleCreateBulk{err: fmt.Errorf("calling to VehicleClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*VehicleCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &VehicleCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Vehicle. +func (c *VehicleClient) Update() *VehicleUpdate { + mutation := newVehicleMutation(c.config, OpUpdate) + return &VehicleUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *VehicleClient) UpdateOne(v *Vehicle) *VehicleUpdateOne { + mutation := newVehicleMutation(c.config, OpUpdateOne, withVehicle(v)) + return &VehicleUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *VehicleClient) UpdateOneID(id string) *VehicleUpdateOne { + mutation := newVehicleMutation(c.config, OpUpdateOne, withVehicleID(id)) + return &VehicleUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Vehicle. +func (c *VehicleClient) Delete() *VehicleDelete { + mutation := newVehicleMutation(c.config, OpDelete) + return &VehicleDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *VehicleClient) DeleteOne(v *Vehicle) *VehicleDeleteOne { + return c.DeleteOneID(v.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *VehicleClient) DeleteOneID(id string) *VehicleDeleteOne { + builder := c.Delete().Where(vehicle.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &VehicleDeleteOne{builder} +} + +// Query returns a query builder for Vehicle. +func (c *VehicleClient) Query() *VehicleQuery { + return &VehicleQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeVehicle}, + inters: c.Interceptors(), + } +} + +// Get returns a Vehicle entity by its id. +func (c *VehicleClient) Get(ctx context.Context, id string) (*Vehicle, error) { + return c.Query().Where(vehicle.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *VehicleClient) GetX(ctx context.Context, id string) *Vehicle { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *VehicleClient) Hooks() []Hook { + return c.hooks.Vehicle +} + +// Interceptors returns the client interceptors. +func (c *VehicleClient) Interceptors() []Interceptor { + return c.inters.Vehicle +} + +func (c *VehicleClient) mutate(ctx context.Context, m *VehicleMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&VehicleCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&VehicleUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&VehicleUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&VehicleDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown Vehicle mutation op: %q", m.Op()) + } +} + +// VehicleAverageClient is a client for the VehicleAverage schema. +type VehicleAverageClient struct { + config +} + +// NewVehicleAverageClient returns a client for the VehicleAverage from the given config. +func NewVehicleAverageClient(c config) *VehicleAverageClient { + return &VehicleAverageClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `vehicleaverage.Hooks(f(g(h())))`. +func (c *VehicleAverageClient) Use(hooks ...Hook) { + c.hooks.VehicleAverage = append(c.hooks.VehicleAverage, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `vehicleaverage.Intercept(f(g(h())))`. +func (c *VehicleAverageClient) Intercept(interceptors ...Interceptor) { + c.inters.VehicleAverage = append(c.inters.VehicleAverage, interceptors...) +} + +// Create returns a builder for creating a VehicleAverage entity. +func (c *VehicleAverageClient) Create() *VehicleAverageCreate { + mutation := newVehicleAverageMutation(c.config, OpCreate) + return &VehicleAverageCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of VehicleAverage entities. +func (c *VehicleAverageClient) CreateBulk(builders ...*VehicleAverageCreate) *VehicleAverageCreateBulk { + return &VehicleAverageCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *VehicleAverageClient) MapCreateBulk(slice any, setFunc func(*VehicleAverageCreate, int)) *VehicleAverageCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &VehicleAverageCreateBulk{err: fmt.Errorf("calling to VehicleAverageClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*VehicleAverageCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &VehicleAverageCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for VehicleAverage. +func (c *VehicleAverageClient) Update() *VehicleAverageUpdate { + mutation := newVehicleAverageMutation(c.config, OpUpdate) + return &VehicleAverageUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *VehicleAverageClient) UpdateOne(va *VehicleAverage) *VehicleAverageUpdateOne { + mutation := newVehicleAverageMutation(c.config, OpUpdateOne, withVehicleAverage(va)) + return &VehicleAverageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *VehicleAverageClient) UpdateOneID(id string) *VehicleAverageUpdateOne { + mutation := newVehicleAverageMutation(c.config, OpUpdateOne, withVehicleAverageID(id)) + return &VehicleAverageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for VehicleAverage. +func (c *VehicleAverageClient) Delete() *VehicleAverageDelete { + mutation := newVehicleAverageMutation(c.config, OpDelete) + return &VehicleAverageDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *VehicleAverageClient) DeleteOne(va *VehicleAverage) *VehicleAverageDeleteOne { + return c.DeleteOneID(va.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *VehicleAverageClient) DeleteOneID(id string) *VehicleAverageDeleteOne { + builder := c.Delete().Where(vehicleaverage.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &VehicleAverageDeleteOne{builder} +} + +// Query returns a query builder for VehicleAverage. +func (c *VehicleAverageClient) Query() *VehicleAverageQuery { + return &VehicleAverageQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeVehicleAverage}, + inters: c.Interceptors(), + } +} + +// Get returns a VehicleAverage entity by its id. +func (c *VehicleAverageClient) Get(ctx context.Context, id string) (*VehicleAverage, error) { + return c.Query().Where(vehicleaverage.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *VehicleAverageClient) GetX(ctx context.Context, id string) *VehicleAverage { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *VehicleAverageClient) Hooks() []Hook { + return c.hooks.VehicleAverage +} + +// Interceptors returns the client interceptors. +func (c *VehicleAverageClient) Interceptors() []Interceptor { + return c.inters.VehicleAverage +} + +func (c *VehicleAverageClient) mutate(ctx context.Context, m *VehicleAverageMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&VehicleAverageCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&VehicleAverageUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&VehicleAverageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&VehicleAverageDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown VehicleAverage mutation op: %q", m.Op()) + } +} + +// VehicleSnapshotClient is a client for the VehicleSnapshot schema. +type VehicleSnapshotClient struct { + config +} + +// NewVehicleSnapshotClient returns a client for the VehicleSnapshot from the given config. +func NewVehicleSnapshotClient(c config) *VehicleSnapshotClient { + return &VehicleSnapshotClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `vehiclesnapshot.Hooks(f(g(h())))`. +func (c *VehicleSnapshotClient) Use(hooks ...Hook) { + c.hooks.VehicleSnapshot = append(c.hooks.VehicleSnapshot, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `vehiclesnapshot.Intercept(f(g(h())))`. +func (c *VehicleSnapshotClient) Intercept(interceptors ...Interceptor) { + c.inters.VehicleSnapshot = append(c.inters.VehicleSnapshot, interceptors...) +} + +// Create returns a builder for creating a VehicleSnapshot entity. +func (c *VehicleSnapshotClient) Create() *VehicleSnapshotCreate { + mutation := newVehicleSnapshotMutation(c.config, OpCreate) + return &VehicleSnapshotCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of VehicleSnapshot entities. +func (c *VehicleSnapshotClient) CreateBulk(builders ...*VehicleSnapshotCreate) *VehicleSnapshotCreateBulk { + return &VehicleSnapshotCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *VehicleSnapshotClient) MapCreateBulk(slice any, setFunc func(*VehicleSnapshotCreate, int)) *VehicleSnapshotCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &VehicleSnapshotCreateBulk{err: fmt.Errorf("calling to VehicleSnapshotClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*VehicleSnapshotCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &VehicleSnapshotCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for VehicleSnapshot. +func (c *VehicleSnapshotClient) Update() *VehicleSnapshotUpdate { + mutation := newVehicleSnapshotMutation(c.config, OpUpdate) + return &VehicleSnapshotUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *VehicleSnapshotClient) UpdateOne(vs *VehicleSnapshot) *VehicleSnapshotUpdateOne { + mutation := newVehicleSnapshotMutation(c.config, OpUpdateOne, withVehicleSnapshot(vs)) + return &VehicleSnapshotUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *VehicleSnapshotClient) UpdateOneID(id string) *VehicleSnapshotUpdateOne { + mutation := newVehicleSnapshotMutation(c.config, OpUpdateOne, withVehicleSnapshotID(id)) + return &VehicleSnapshotUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for VehicleSnapshot. +func (c *VehicleSnapshotClient) Delete() *VehicleSnapshotDelete { + mutation := newVehicleSnapshotMutation(c.config, OpDelete) + return &VehicleSnapshotDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *VehicleSnapshotClient) DeleteOne(vs *VehicleSnapshot) *VehicleSnapshotDeleteOne { + return c.DeleteOneID(vs.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *VehicleSnapshotClient) DeleteOneID(id string) *VehicleSnapshotDeleteOne { + builder := c.Delete().Where(vehiclesnapshot.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &VehicleSnapshotDeleteOne{builder} +} + +// Query returns a query builder for VehicleSnapshot. +func (c *VehicleSnapshotClient) Query() *VehicleSnapshotQuery { + return &VehicleSnapshotQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeVehicleSnapshot}, + inters: c.Interceptors(), + } +} + +// Get returns a VehicleSnapshot entity by its id. +func (c *VehicleSnapshotClient) Get(ctx context.Context, id string) (*VehicleSnapshot, error) { + return c.Query().Where(vehiclesnapshot.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *VehicleSnapshotClient) GetX(ctx context.Context, id string) *VehicleSnapshot { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryAccount queries the account edge of a VehicleSnapshot. +func (c *VehicleSnapshotClient) QueryAccount(vs *VehicleSnapshot) *AccountQuery { + query := (&AccountClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := vs.ID + step := sqlgraph.NewStep( + sqlgraph.From(vehiclesnapshot.Table, vehiclesnapshot.FieldID, id), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, vehiclesnapshot.AccountTable, vehiclesnapshot.AccountColumn), + ) + fromV = sqlgraph.Neighbors(vs.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *VehicleSnapshotClient) Hooks() []Hook { + return c.hooks.VehicleSnapshot +} + +// Interceptors returns the client interceptors. +func (c *VehicleSnapshotClient) Interceptors() []Interceptor { + return c.inters.VehicleSnapshot +} + +func (c *VehicleSnapshotClient) mutate(ctx context.Context, m *VehicleSnapshotMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&VehicleSnapshotCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&VehicleSnapshotUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&VehicleSnapshotUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&VehicleSnapshotDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown VehicleSnapshot mutation op: %q", m.Op()) + } +} + +// hooks and interceptors per client, for fast access. +type ( + hooks struct { + Account, AccountSnapshot, AchievementsSnapshot, AppConfiguration, + ApplicationCommand, Clan, CronTask, User, UserConnection, UserContent, + UserSubscription, Vehicle, VehicleAverage, VehicleSnapshot []ent.Hook + } + inters struct { + Account, AccountSnapshot, AchievementsSnapshot, AppConfiguration, + ApplicationCommand, Clan, CronTask, User, UserConnection, UserContent, + UserSubscription, Vehicle, VehicleAverage, VehicleSnapshot []ent.Interceptor + } +) diff --git a/internal/database/ent/db/crontask.go b/internal/database/ent/db/crontask.go new file mode 100644 index 00000000..c7580c3c --- /dev/null +++ b/internal/database/ent/db/crontask.go @@ -0,0 +1,212 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/models" +) + +// CronTask is the model entity for the CronTask schema. +type CronTask struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Type holds the value of the "type" field. + Type string `json:"type,omitempty"` + // ReferenceID holds the value of the "reference_id" field. + ReferenceID string `json:"reference_id,omitempty"` + // Targets holds the value of the "targets" field. + Targets []string `json:"targets,omitempty"` + // Status holds the value of the "status" field. + Status string `json:"status,omitempty"` + // ScheduledAfter holds the value of the "scheduled_after" field. + ScheduledAfter int `json:"scheduled_after,omitempty"` + // LastRun holds the value of the "last_run" field. + LastRun int `json:"last_run,omitempty"` + // Logs holds the value of the "logs" field. + Logs []models.TaskLog `json:"logs,omitempty"` + // Data holds the value of the "data" field. + Data map[string]interface{} `json:"data,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*CronTask) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case crontask.FieldTargets, crontask.FieldLogs, crontask.FieldData: + values[i] = new([]byte) + case crontask.FieldCreatedAt, crontask.FieldUpdatedAt, crontask.FieldScheduledAfter, crontask.FieldLastRun: + values[i] = new(sql.NullInt64) + case crontask.FieldID, crontask.FieldType, crontask.FieldReferenceID, crontask.FieldStatus: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the CronTask fields. +func (ct *CronTask) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case crontask.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + ct.ID = value.String + } + case crontask.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + ct.CreatedAt = int(value.Int64) + } + case crontask.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + ct.UpdatedAt = int(value.Int64) + } + case crontask.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + ct.Type = value.String + } + case crontask.FieldReferenceID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reference_id", values[i]) + } else if value.Valid { + ct.ReferenceID = value.String + } + case crontask.FieldTargets: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field targets", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &ct.Targets); err != nil { + return fmt.Errorf("unmarshal field targets: %w", err) + } + } + case crontask.FieldStatus: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field status", values[i]) + } else if value.Valid { + ct.Status = value.String + } + case crontask.FieldScheduledAfter: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field scheduled_after", values[i]) + } else if value.Valid { + ct.ScheduledAfter = int(value.Int64) + } + case crontask.FieldLastRun: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field last_run", values[i]) + } else if value.Valid { + ct.LastRun = int(value.Int64) + } + case crontask.FieldLogs: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field logs", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &ct.Logs); err != nil { + return fmt.Errorf("unmarshal field logs: %w", err) + } + } + case crontask.FieldData: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field data", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &ct.Data); err != nil { + return fmt.Errorf("unmarshal field data: %w", err) + } + } + default: + ct.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the CronTask. +// This includes values selected through modifiers, order, etc. +func (ct *CronTask) Value(name string) (ent.Value, error) { + return ct.selectValues.Get(name) +} + +// Update returns a builder for updating this CronTask. +// Note that you need to call CronTask.Unwrap() before calling this method if this CronTask +// was returned from a transaction, and the transaction was committed or rolled back. +func (ct *CronTask) Update() *CronTaskUpdateOne { + return NewCronTaskClient(ct.config).UpdateOne(ct) +} + +// Unwrap unwraps the CronTask entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (ct *CronTask) Unwrap() *CronTask { + _tx, ok := ct.config.driver.(*txDriver) + if !ok { + panic("db: CronTask is not a transactional entity") + } + ct.config.driver = _tx.drv + return ct +} + +// String implements the fmt.Stringer. +func (ct *CronTask) String() string { + var builder strings.Builder + builder.WriteString("CronTask(") + builder.WriteString(fmt.Sprintf("id=%v, ", ct.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", ct.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", ct.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("type=") + builder.WriteString(ct.Type) + builder.WriteString(", ") + builder.WriteString("reference_id=") + builder.WriteString(ct.ReferenceID) + builder.WriteString(", ") + builder.WriteString("targets=") + builder.WriteString(fmt.Sprintf("%v", ct.Targets)) + builder.WriteString(", ") + builder.WriteString("status=") + builder.WriteString(ct.Status) + builder.WriteString(", ") + builder.WriteString("scheduled_after=") + builder.WriteString(fmt.Sprintf("%v", ct.ScheduledAfter)) + builder.WriteString(", ") + builder.WriteString("last_run=") + builder.WriteString(fmt.Sprintf("%v", ct.LastRun)) + builder.WriteString(", ") + builder.WriteString("logs=") + builder.WriteString(fmt.Sprintf("%v", ct.Logs)) + builder.WriteString(", ") + builder.WriteString("data=") + builder.WriteString(fmt.Sprintf("%v", ct.Data)) + builder.WriteByte(')') + return builder.String() +} + +// CronTasks is a parsable slice of CronTask. +type CronTasks []*CronTask diff --git a/internal/database/ent/db/crontask/crontask.go b/internal/database/ent/db/crontask/crontask.go new file mode 100644 index 00000000..eec007e1 --- /dev/null +++ b/internal/database/ent/db/crontask/crontask.go @@ -0,0 +1,121 @@ +// Code generated by ent, DO NOT EDIT. + +package crontask + +import ( + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the crontask type in the database. + Label = "cron_task" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldReferenceID holds the string denoting the reference_id field in the database. + FieldReferenceID = "reference_id" + // FieldTargets holds the string denoting the targets field in the database. + FieldTargets = "targets" + // FieldStatus holds the string denoting the status field in the database. + FieldStatus = "status" + // FieldScheduledAfter holds the string denoting the scheduled_after field in the database. + FieldScheduledAfter = "scheduled_after" + // FieldLastRun holds the string denoting the last_run field in the database. + FieldLastRun = "last_run" + // FieldLogs holds the string denoting the logs field in the database. + FieldLogs = "logs" + // FieldData holds the string denoting the data field in the database. + FieldData = "data" + // Table holds the table name of the crontask in the database. + Table = "cron_tasks" +) + +// Columns holds all SQL columns for crontask fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldType, + FieldReferenceID, + FieldTargets, + FieldStatus, + FieldScheduledAfter, + FieldLastRun, + FieldLogs, + FieldData, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // TypeValidator is a validator for the "type" field. It is called by the builders before save. + TypeValidator func(string) error + // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + ReferenceIDValidator func(string) error + // StatusValidator is a validator for the "status" field. It is called by the builders before save. + StatusValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// OrderOption defines the ordering options for the CronTask queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByReferenceID orders the results by the reference_id field. +func ByReferenceID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReferenceID, opts...).ToFunc() +} + +// ByStatus orders the results by the status field. +func ByStatus(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldStatus, opts...).ToFunc() +} + +// ByScheduledAfter orders the results by the scheduled_after field. +func ByScheduledAfter(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldScheduledAfter, opts...).ToFunc() +} + +// ByLastRun orders the results by the last_run field. +func ByLastRun(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLastRun, opts...).ToFunc() +} diff --git a/internal/database/ent/db/crontask/where.go b/internal/database/ent/db/crontask/where.go new file mode 100644 index 00000000..a3da3987 --- /dev/null +++ b/internal/database/ent/db/crontask/where.go @@ -0,0 +1,468 @@ +// Code generated by ent, DO NOT EDIT. + +package crontask + +import ( + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.CronTask { + return predicate.CronTask(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.CronTask { + return predicate.CronTask(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.CronTask { + return predicate.CronTask(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.CronTask { + return predicate.CronTask(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.CronTask { + return predicate.CronTask(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.CronTask { + return predicate.CronTask(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.CronTask { + return predicate.CronTask(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.CronTask { + return predicate.CronTask(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.CronTask { + return predicate.CronTask(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Type applies equality check predicate on the "type" field. It's identical to TypeEQ. +func Type(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldType, v)) +} + +// ReferenceID applies equality check predicate on the "reference_id" field. It's identical to ReferenceIDEQ. +func ReferenceID(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldReferenceID, v)) +} + +// Status applies equality check predicate on the "status" field. It's identical to StatusEQ. +func Status(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldStatus, v)) +} + +// ScheduledAfter applies equality check predicate on the "scheduled_after" field. It's identical to ScheduledAfterEQ. +func ScheduledAfter(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldScheduledAfter, v)) +} + +// LastRun applies equality check predicate on the "last_run" field. It's identical to LastRunEQ. +func LastRun(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldLastRun, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldType, v)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldNEQ(FieldType, v)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...string) predicate.CronTask { + return predicate.CronTask(sql.FieldIn(FieldType, vs...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...string) predicate.CronTask { + return predicate.CronTask(sql.FieldNotIn(FieldType, vs...)) +} + +// TypeGT applies the GT predicate on the "type" field. +func TypeGT(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldGT(FieldType, v)) +} + +// TypeGTE applies the GTE predicate on the "type" field. +func TypeGTE(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldGTE(FieldType, v)) +} + +// TypeLT applies the LT predicate on the "type" field. +func TypeLT(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldLT(FieldType, v)) +} + +// TypeLTE applies the LTE predicate on the "type" field. +func TypeLTE(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldLTE(FieldType, v)) +} + +// TypeContains applies the Contains predicate on the "type" field. +func TypeContains(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldContains(FieldType, v)) +} + +// TypeHasPrefix applies the HasPrefix predicate on the "type" field. +func TypeHasPrefix(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldHasPrefix(FieldType, v)) +} + +// TypeHasSuffix applies the HasSuffix predicate on the "type" field. +func TypeHasSuffix(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldHasSuffix(FieldType, v)) +} + +// TypeEqualFold applies the EqualFold predicate on the "type" field. +func TypeEqualFold(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldEqualFold(FieldType, v)) +} + +// TypeContainsFold applies the ContainsFold predicate on the "type" field. +func TypeContainsFold(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldContainsFold(FieldType, v)) +} + +// ReferenceIDEQ applies the EQ predicate on the "reference_id" field. +func ReferenceIDEQ(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldReferenceID, v)) +} + +// ReferenceIDNEQ applies the NEQ predicate on the "reference_id" field. +func ReferenceIDNEQ(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldNEQ(FieldReferenceID, v)) +} + +// ReferenceIDIn applies the In predicate on the "reference_id" field. +func ReferenceIDIn(vs ...string) predicate.CronTask { + return predicate.CronTask(sql.FieldIn(FieldReferenceID, vs...)) +} + +// ReferenceIDNotIn applies the NotIn predicate on the "reference_id" field. +func ReferenceIDNotIn(vs ...string) predicate.CronTask { + return predicate.CronTask(sql.FieldNotIn(FieldReferenceID, vs...)) +} + +// ReferenceIDGT applies the GT predicate on the "reference_id" field. +func ReferenceIDGT(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldGT(FieldReferenceID, v)) +} + +// ReferenceIDGTE applies the GTE predicate on the "reference_id" field. +func ReferenceIDGTE(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldGTE(FieldReferenceID, v)) +} + +// ReferenceIDLT applies the LT predicate on the "reference_id" field. +func ReferenceIDLT(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldLT(FieldReferenceID, v)) +} + +// ReferenceIDLTE applies the LTE predicate on the "reference_id" field. +func ReferenceIDLTE(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldLTE(FieldReferenceID, v)) +} + +// ReferenceIDContains applies the Contains predicate on the "reference_id" field. +func ReferenceIDContains(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldContains(FieldReferenceID, v)) +} + +// ReferenceIDHasPrefix applies the HasPrefix predicate on the "reference_id" field. +func ReferenceIDHasPrefix(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldHasPrefix(FieldReferenceID, v)) +} + +// ReferenceIDHasSuffix applies the HasSuffix predicate on the "reference_id" field. +func ReferenceIDHasSuffix(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldHasSuffix(FieldReferenceID, v)) +} + +// ReferenceIDEqualFold applies the EqualFold predicate on the "reference_id" field. +func ReferenceIDEqualFold(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldEqualFold(FieldReferenceID, v)) +} + +// ReferenceIDContainsFold applies the ContainsFold predicate on the "reference_id" field. +func ReferenceIDContainsFold(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldContainsFold(FieldReferenceID, v)) +} + +// StatusEQ applies the EQ predicate on the "status" field. +func StatusEQ(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldStatus, v)) +} + +// StatusNEQ applies the NEQ predicate on the "status" field. +func StatusNEQ(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldNEQ(FieldStatus, v)) +} + +// StatusIn applies the In predicate on the "status" field. +func StatusIn(vs ...string) predicate.CronTask { + return predicate.CronTask(sql.FieldIn(FieldStatus, vs...)) +} + +// StatusNotIn applies the NotIn predicate on the "status" field. +func StatusNotIn(vs ...string) predicate.CronTask { + return predicate.CronTask(sql.FieldNotIn(FieldStatus, vs...)) +} + +// StatusGT applies the GT predicate on the "status" field. +func StatusGT(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldGT(FieldStatus, v)) +} + +// StatusGTE applies the GTE predicate on the "status" field. +func StatusGTE(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldGTE(FieldStatus, v)) +} + +// StatusLT applies the LT predicate on the "status" field. +func StatusLT(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldLT(FieldStatus, v)) +} + +// StatusLTE applies the LTE predicate on the "status" field. +func StatusLTE(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldLTE(FieldStatus, v)) +} + +// StatusContains applies the Contains predicate on the "status" field. +func StatusContains(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldContains(FieldStatus, v)) +} + +// StatusHasPrefix applies the HasPrefix predicate on the "status" field. +func StatusHasPrefix(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldHasPrefix(FieldStatus, v)) +} + +// StatusHasSuffix applies the HasSuffix predicate on the "status" field. +func StatusHasSuffix(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldHasSuffix(FieldStatus, v)) +} + +// StatusEqualFold applies the EqualFold predicate on the "status" field. +func StatusEqualFold(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldEqualFold(FieldStatus, v)) +} + +// StatusContainsFold applies the ContainsFold predicate on the "status" field. +func StatusContainsFold(v string) predicate.CronTask { + return predicate.CronTask(sql.FieldContainsFold(FieldStatus, v)) +} + +// ScheduledAfterEQ applies the EQ predicate on the "scheduled_after" field. +func ScheduledAfterEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldScheduledAfter, v)) +} + +// ScheduledAfterNEQ applies the NEQ predicate on the "scheduled_after" field. +func ScheduledAfterNEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldNEQ(FieldScheduledAfter, v)) +} + +// ScheduledAfterIn applies the In predicate on the "scheduled_after" field. +func ScheduledAfterIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldIn(FieldScheduledAfter, vs...)) +} + +// ScheduledAfterNotIn applies the NotIn predicate on the "scheduled_after" field. +func ScheduledAfterNotIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldNotIn(FieldScheduledAfter, vs...)) +} + +// ScheduledAfterGT applies the GT predicate on the "scheduled_after" field. +func ScheduledAfterGT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGT(FieldScheduledAfter, v)) +} + +// ScheduledAfterGTE applies the GTE predicate on the "scheduled_after" field. +func ScheduledAfterGTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGTE(FieldScheduledAfter, v)) +} + +// ScheduledAfterLT applies the LT predicate on the "scheduled_after" field. +func ScheduledAfterLT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLT(FieldScheduledAfter, v)) +} + +// ScheduledAfterLTE applies the LTE predicate on the "scheduled_after" field. +func ScheduledAfterLTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLTE(FieldScheduledAfter, v)) +} + +// LastRunEQ applies the EQ predicate on the "last_run" field. +func LastRunEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldLastRun, v)) +} + +// LastRunNEQ applies the NEQ predicate on the "last_run" field. +func LastRunNEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldNEQ(FieldLastRun, v)) +} + +// LastRunIn applies the In predicate on the "last_run" field. +func LastRunIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldIn(FieldLastRun, vs...)) +} + +// LastRunNotIn applies the NotIn predicate on the "last_run" field. +func LastRunNotIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldNotIn(FieldLastRun, vs...)) +} + +// LastRunGT applies the GT predicate on the "last_run" field. +func LastRunGT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGT(FieldLastRun, v)) +} + +// LastRunGTE applies the GTE predicate on the "last_run" field. +func LastRunGTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGTE(FieldLastRun, v)) +} + +// LastRunLT applies the LT predicate on the "last_run" field. +func LastRunLT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLT(FieldLastRun, v)) +} + +// LastRunLTE applies the LTE predicate on the "last_run" field. +func LastRunLTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLTE(FieldLastRun, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.CronTask) predicate.CronTask { + return predicate.CronTask(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.CronTask) predicate.CronTask { + return predicate.CronTask(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.CronTask) predicate.CronTask { + return predicate.CronTask(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/crontask_create.go b/internal/database/ent/db/crontask_create.go new file mode 100644 index 00000000..40345b5d --- /dev/null +++ b/internal/database/ent/db/crontask_create.go @@ -0,0 +1,369 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/models" +) + +// CronTaskCreate is the builder for creating a CronTask entity. +type CronTaskCreate struct { + config + mutation *CronTaskMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (ctc *CronTaskCreate) SetCreatedAt(i int) *CronTaskCreate { + ctc.mutation.SetCreatedAt(i) + return ctc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (ctc *CronTaskCreate) SetNillableCreatedAt(i *int) *CronTaskCreate { + if i != nil { + ctc.SetCreatedAt(*i) + } + return ctc +} + +// SetUpdatedAt sets the "updated_at" field. +func (ctc *CronTaskCreate) SetUpdatedAt(i int) *CronTaskCreate { + ctc.mutation.SetUpdatedAt(i) + return ctc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (ctc *CronTaskCreate) SetNillableUpdatedAt(i *int) *CronTaskCreate { + if i != nil { + ctc.SetUpdatedAt(*i) + } + return ctc +} + +// SetType sets the "type" field. +func (ctc *CronTaskCreate) SetType(s string) *CronTaskCreate { + ctc.mutation.SetType(s) + return ctc +} + +// SetReferenceID sets the "reference_id" field. +func (ctc *CronTaskCreate) SetReferenceID(s string) *CronTaskCreate { + ctc.mutation.SetReferenceID(s) + return ctc +} + +// SetTargets sets the "targets" field. +func (ctc *CronTaskCreate) SetTargets(s []string) *CronTaskCreate { + ctc.mutation.SetTargets(s) + return ctc +} + +// SetStatus sets the "status" field. +func (ctc *CronTaskCreate) SetStatus(s string) *CronTaskCreate { + ctc.mutation.SetStatus(s) + return ctc +} + +// SetScheduledAfter sets the "scheduled_after" field. +func (ctc *CronTaskCreate) SetScheduledAfter(i int) *CronTaskCreate { + ctc.mutation.SetScheduledAfter(i) + return ctc +} + +// SetLastRun sets the "last_run" field. +func (ctc *CronTaskCreate) SetLastRun(i int) *CronTaskCreate { + ctc.mutation.SetLastRun(i) + return ctc +} + +// SetLogs sets the "logs" field. +func (ctc *CronTaskCreate) SetLogs(ml []models.TaskLog) *CronTaskCreate { + ctc.mutation.SetLogs(ml) + return ctc +} + +// SetData sets the "data" field. +func (ctc *CronTaskCreate) SetData(m map[string]interface{}) *CronTaskCreate { + ctc.mutation.SetData(m) + return ctc +} + +// SetID sets the "id" field. +func (ctc *CronTaskCreate) SetID(s string) *CronTaskCreate { + ctc.mutation.SetID(s) + return ctc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (ctc *CronTaskCreate) SetNillableID(s *string) *CronTaskCreate { + if s != nil { + ctc.SetID(*s) + } + return ctc +} + +// Mutation returns the CronTaskMutation object of the builder. +func (ctc *CronTaskCreate) Mutation() *CronTaskMutation { + return ctc.mutation +} + +// Save creates the CronTask in the database. +func (ctc *CronTaskCreate) Save(ctx context.Context) (*CronTask, error) { + ctc.defaults() + return withHooks(ctx, ctc.sqlSave, ctc.mutation, ctc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (ctc *CronTaskCreate) SaveX(ctx context.Context) *CronTask { + v, err := ctc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ctc *CronTaskCreate) Exec(ctx context.Context) error { + _, err := ctc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ctc *CronTaskCreate) ExecX(ctx context.Context) { + if err := ctc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ctc *CronTaskCreate) defaults() { + if _, ok := ctc.mutation.CreatedAt(); !ok { + v := crontask.DefaultCreatedAt() + ctc.mutation.SetCreatedAt(v) + } + if _, ok := ctc.mutation.UpdatedAt(); !ok { + v := crontask.DefaultUpdatedAt() + ctc.mutation.SetUpdatedAt(v) + } + if _, ok := ctc.mutation.ID(); !ok { + v := crontask.DefaultID() + ctc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ctc *CronTaskCreate) check() error { + if _, ok := ctc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "CronTask.created_at"`)} + } + if _, ok := ctc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "CronTask.updated_at"`)} + } + if _, ok := ctc.mutation.GetType(); !ok { + return &ValidationError{Name: "type", err: errors.New(`db: missing required field "CronTask.type"`)} + } + if v, ok := ctc.mutation.GetType(); ok { + if err := crontask.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "CronTask.type": %w`, err)} + } + } + if _, ok := ctc.mutation.ReferenceID(); !ok { + return &ValidationError{Name: "reference_id", err: errors.New(`db: missing required field "CronTask.reference_id"`)} + } + if v, ok := ctc.mutation.ReferenceID(); ok { + if err := crontask.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "CronTask.reference_id": %w`, err)} + } + } + if _, ok := ctc.mutation.Targets(); !ok { + return &ValidationError{Name: "targets", err: errors.New(`db: missing required field "CronTask.targets"`)} + } + if _, ok := ctc.mutation.Status(); !ok { + return &ValidationError{Name: "status", err: errors.New(`db: missing required field "CronTask.status"`)} + } + if v, ok := ctc.mutation.Status(); ok { + if err := crontask.StatusValidator(v); err != nil { + return &ValidationError{Name: "status", err: fmt.Errorf(`db: validator failed for field "CronTask.status": %w`, err)} + } + } + if _, ok := ctc.mutation.ScheduledAfter(); !ok { + return &ValidationError{Name: "scheduled_after", err: errors.New(`db: missing required field "CronTask.scheduled_after"`)} + } + if _, ok := ctc.mutation.LastRun(); !ok { + return &ValidationError{Name: "last_run", err: errors.New(`db: missing required field "CronTask.last_run"`)} + } + if _, ok := ctc.mutation.Logs(); !ok { + return &ValidationError{Name: "logs", err: errors.New(`db: missing required field "CronTask.logs"`)} + } + if _, ok := ctc.mutation.Data(); !ok { + return &ValidationError{Name: "data", err: errors.New(`db: missing required field "CronTask.data"`)} + } + return nil +} + +func (ctc *CronTaskCreate) sqlSave(ctx context.Context) (*CronTask, error) { + if err := ctc.check(); err != nil { + return nil, err + } + _node, _spec := ctc.createSpec() + if err := sqlgraph.CreateNode(ctx, ctc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected CronTask.ID type: %T", _spec.ID.Value) + } + } + ctc.mutation.id = &_node.ID + ctc.mutation.done = true + return _node, nil +} + +func (ctc *CronTaskCreate) createSpec() (*CronTask, *sqlgraph.CreateSpec) { + var ( + _node = &CronTask{config: ctc.config} + _spec = sqlgraph.NewCreateSpec(crontask.Table, sqlgraph.NewFieldSpec(crontask.FieldID, field.TypeString)) + ) + if id, ok := ctc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := ctc.mutation.CreatedAt(); ok { + _spec.SetField(crontask.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := ctc.mutation.UpdatedAt(); ok { + _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := ctc.mutation.GetType(); ok { + _spec.SetField(crontask.FieldType, field.TypeString, value) + _node.Type = value + } + if value, ok := ctc.mutation.ReferenceID(); ok { + _spec.SetField(crontask.FieldReferenceID, field.TypeString, value) + _node.ReferenceID = value + } + if value, ok := ctc.mutation.Targets(); ok { + _spec.SetField(crontask.FieldTargets, field.TypeJSON, value) + _node.Targets = value + } + if value, ok := ctc.mutation.Status(); ok { + _spec.SetField(crontask.FieldStatus, field.TypeString, value) + _node.Status = value + } + if value, ok := ctc.mutation.ScheduledAfter(); ok { + _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt, value) + _node.ScheduledAfter = value + } + if value, ok := ctc.mutation.LastRun(); ok { + _spec.SetField(crontask.FieldLastRun, field.TypeInt, value) + _node.LastRun = value + } + if value, ok := ctc.mutation.Logs(); ok { + _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) + _node.Logs = value + } + if value, ok := ctc.mutation.Data(); ok { + _spec.SetField(crontask.FieldData, field.TypeJSON, value) + _node.Data = value + } + return _node, _spec +} + +// CronTaskCreateBulk is the builder for creating many CronTask entities in bulk. +type CronTaskCreateBulk struct { + config + err error + builders []*CronTaskCreate +} + +// Save creates the CronTask entities in the database. +func (ctcb *CronTaskCreateBulk) Save(ctx context.Context) ([]*CronTask, error) { + if ctcb.err != nil { + return nil, ctcb.err + } + specs := make([]*sqlgraph.CreateSpec, len(ctcb.builders)) + nodes := make([]*CronTask, len(ctcb.builders)) + mutators := make([]Mutator, len(ctcb.builders)) + for i := range ctcb.builders { + func(i int, root context.Context) { + builder := ctcb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*CronTaskMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, ctcb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, ctcb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, ctcb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (ctcb *CronTaskCreateBulk) SaveX(ctx context.Context) []*CronTask { + v, err := ctcb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ctcb *CronTaskCreateBulk) Exec(ctx context.Context) error { + _, err := ctcb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ctcb *CronTaskCreateBulk) ExecX(ctx context.Context) { + if err := ctcb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/crontask_delete.go b/internal/database/ent/db/crontask_delete.go new file mode 100644 index 00000000..31943730 --- /dev/null +++ b/internal/database/ent/db/crontask_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// CronTaskDelete is the builder for deleting a CronTask entity. +type CronTaskDelete struct { + config + hooks []Hook + mutation *CronTaskMutation +} + +// Where appends a list predicates to the CronTaskDelete builder. +func (ctd *CronTaskDelete) Where(ps ...predicate.CronTask) *CronTaskDelete { + ctd.mutation.Where(ps...) + return ctd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (ctd *CronTaskDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, ctd.sqlExec, ctd.mutation, ctd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (ctd *CronTaskDelete) ExecX(ctx context.Context) int { + n, err := ctd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (ctd *CronTaskDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(crontask.Table, sqlgraph.NewFieldSpec(crontask.FieldID, field.TypeString)) + if ps := ctd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, ctd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + ctd.mutation.done = true + return affected, err +} + +// CronTaskDeleteOne is the builder for deleting a single CronTask entity. +type CronTaskDeleteOne struct { + ctd *CronTaskDelete +} + +// Where appends a list predicates to the CronTaskDelete builder. +func (ctdo *CronTaskDeleteOne) Where(ps ...predicate.CronTask) *CronTaskDeleteOne { + ctdo.ctd.mutation.Where(ps...) + return ctdo +} + +// Exec executes the deletion query. +func (ctdo *CronTaskDeleteOne) Exec(ctx context.Context) error { + n, err := ctdo.ctd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{crontask.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (ctdo *CronTaskDeleteOne) ExecX(ctx context.Context) { + if err := ctdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/crontask_query.go b/internal/database/ent/db/crontask_query.go new file mode 100644 index 00000000..2ff7de14 --- /dev/null +++ b/internal/database/ent/db/crontask_query.go @@ -0,0 +1,526 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// CronTaskQuery is the builder for querying CronTask entities. +type CronTaskQuery struct { + config + ctx *QueryContext + order []crontask.OrderOption + inters []Interceptor + predicates []predicate.CronTask + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the CronTaskQuery builder. +func (ctq *CronTaskQuery) Where(ps ...predicate.CronTask) *CronTaskQuery { + ctq.predicates = append(ctq.predicates, ps...) + return ctq +} + +// Limit the number of records to be returned by this query. +func (ctq *CronTaskQuery) Limit(limit int) *CronTaskQuery { + ctq.ctx.Limit = &limit + return ctq +} + +// Offset to start from. +func (ctq *CronTaskQuery) Offset(offset int) *CronTaskQuery { + ctq.ctx.Offset = &offset + return ctq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (ctq *CronTaskQuery) Unique(unique bool) *CronTaskQuery { + ctq.ctx.Unique = &unique + return ctq +} + +// Order specifies how the records should be ordered. +func (ctq *CronTaskQuery) Order(o ...crontask.OrderOption) *CronTaskQuery { + ctq.order = append(ctq.order, o...) + return ctq +} + +// First returns the first CronTask entity from the query. +// Returns a *NotFoundError when no CronTask was found. +func (ctq *CronTaskQuery) First(ctx context.Context) (*CronTask, error) { + nodes, err := ctq.Limit(1).All(setContextOp(ctx, ctq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{crontask.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (ctq *CronTaskQuery) FirstX(ctx context.Context) *CronTask { + node, err := ctq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first CronTask ID from the query. +// Returns a *NotFoundError when no CronTask ID was found. +func (ctq *CronTaskQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = ctq.Limit(1).IDs(setContextOp(ctx, ctq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{crontask.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (ctq *CronTaskQuery) FirstIDX(ctx context.Context) string { + id, err := ctq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single CronTask entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one CronTask entity is found. +// Returns a *NotFoundError when no CronTask entities are found. +func (ctq *CronTaskQuery) Only(ctx context.Context) (*CronTask, error) { + nodes, err := ctq.Limit(2).All(setContextOp(ctx, ctq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{crontask.Label} + default: + return nil, &NotSingularError{crontask.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (ctq *CronTaskQuery) OnlyX(ctx context.Context) *CronTask { + node, err := ctq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only CronTask ID in the query. +// Returns a *NotSingularError when more than one CronTask ID is found. +// Returns a *NotFoundError when no entities are found. +func (ctq *CronTaskQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = ctq.Limit(2).IDs(setContextOp(ctx, ctq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{crontask.Label} + default: + err = &NotSingularError{crontask.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (ctq *CronTaskQuery) OnlyIDX(ctx context.Context) string { + id, err := ctq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of CronTasks. +func (ctq *CronTaskQuery) All(ctx context.Context) ([]*CronTask, error) { + ctx = setContextOp(ctx, ctq.ctx, "All") + if err := ctq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*CronTask, *CronTaskQuery]() + return withInterceptors[[]*CronTask](ctx, ctq, qr, ctq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (ctq *CronTaskQuery) AllX(ctx context.Context) []*CronTask { + nodes, err := ctq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of CronTask IDs. +func (ctq *CronTaskQuery) IDs(ctx context.Context) (ids []string, err error) { + if ctq.ctx.Unique == nil && ctq.path != nil { + ctq.Unique(true) + } + ctx = setContextOp(ctx, ctq.ctx, "IDs") + if err = ctq.Select(crontask.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (ctq *CronTaskQuery) IDsX(ctx context.Context) []string { + ids, err := ctq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (ctq *CronTaskQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, ctq.ctx, "Count") + if err := ctq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, ctq, querierCount[*CronTaskQuery](), ctq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (ctq *CronTaskQuery) CountX(ctx context.Context) int { + count, err := ctq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (ctq *CronTaskQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, ctq.ctx, "Exist") + switch _, err := ctq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (ctq *CronTaskQuery) ExistX(ctx context.Context) bool { + exist, err := ctq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the CronTaskQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (ctq *CronTaskQuery) Clone() *CronTaskQuery { + if ctq == nil { + return nil + } + return &CronTaskQuery{ + config: ctq.config, + ctx: ctq.ctx.Clone(), + order: append([]crontask.OrderOption{}, ctq.order...), + inters: append([]Interceptor{}, ctq.inters...), + predicates: append([]predicate.CronTask{}, ctq.predicates...), + // clone intermediate query. + sql: ctq.sql.Clone(), + path: ctq.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.CronTask.Query(). +// GroupBy(crontask.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (ctq *CronTaskQuery) GroupBy(field string, fields ...string) *CronTaskGroupBy { + ctq.ctx.Fields = append([]string{field}, fields...) + grbuild := &CronTaskGroupBy{build: ctq} + grbuild.flds = &ctq.ctx.Fields + grbuild.label = crontask.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.CronTask.Query(). +// Select(crontask.FieldCreatedAt). +// Scan(ctx, &v) +func (ctq *CronTaskQuery) Select(fields ...string) *CronTaskSelect { + ctq.ctx.Fields = append(ctq.ctx.Fields, fields...) + sbuild := &CronTaskSelect{CronTaskQuery: ctq} + sbuild.label = crontask.Label + sbuild.flds, sbuild.scan = &ctq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a CronTaskSelect configured with the given aggregations. +func (ctq *CronTaskQuery) Aggregate(fns ...AggregateFunc) *CronTaskSelect { + return ctq.Select().Aggregate(fns...) +} + +func (ctq *CronTaskQuery) prepareQuery(ctx context.Context) error { + for _, inter := range ctq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, ctq); err != nil { + return err + } + } + } + for _, f := range ctq.ctx.Fields { + if !crontask.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if ctq.path != nil { + prev, err := ctq.path(ctx) + if err != nil { + return err + } + ctq.sql = prev + } + return nil +} + +func (ctq *CronTaskQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*CronTask, error) { + var ( + nodes = []*CronTask{} + _spec = ctq.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*CronTask).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &CronTask{config: ctq.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, ctq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (ctq *CronTaskQuery) sqlCount(ctx context.Context) (int, error) { + _spec := ctq.querySpec() + _spec.Node.Columns = ctq.ctx.Fields + if len(ctq.ctx.Fields) > 0 { + _spec.Unique = ctq.ctx.Unique != nil && *ctq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, ctq.driver, _spec) +} + +func (ctq *CronTaskQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(crontask.Table, crontask.Columns, sqlgraph.NewFieldSpec(crontask.FieldID, field.TypeString)) + _spec.From = ctq.sql + if unique := ctq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if ctq.path != nil { + _spec.Unique = true + } + if fields := ctq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, crontask.FieldID) + for i := range fields { + if fields[i] != crontask.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := ctq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := ctq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := ctq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := ctq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (ctq *CronTaskQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(ctq.driver.Dialect()) + t1 := builder.Table(crontask.Table) + columns := ctq.ctx.Fields + if len(columns) == 0 { + columns = crontask.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if ctq.sql != nil { + selector = ctq.sql + selector.Select(selector.Columns(columns...)...) + } + if ctq.ctx.Unique != nil && *ctq.ctx.Unique { + selector.Distinct() + } + for _, p := range ctq.predicates { + p(selector) + } + for _, p := range ctq.order { + p(selector) + } + if offset := ctq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := ctq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// CronTaskGroupBy is the group-by builder for CronTask entities. +type CronTaskGroupBy struct { + selector + build *CronTaskQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (ctgb *CronTaskGroupBy) Aggregate(fns ...AggregateFunc) *CronTaskGroupBy { + ctgb.fns = append(ctgb.fns, fns...) + return ctgb +} + +// Scan applies the selector query and scans the result into the given value. +func (ctgb *CronTaskGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ctgb.build.ctx, "GroupBy") + if err := ctgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*CronTaskQuery, *CronTaskGroupBy](ctx, ctgb.build, ctgb, ctgb.build.inters, v) +} + +func (ctgb *CronTaskGroupBy) sqlScan(ctx context.Context, root *CronTaskQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(ctgb.fns)) + for _, fn := range ctgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*ctgb.flds)+len(ctgb.fns)) + for _, f := range *ctgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*ctgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ctgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// CronTaskSelect is the builder for selecting fields of CronTask entities. +type CronTaskSelect struct { + *CronTaskQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (cts *CronTaskSelect) Aggregate(fns ...AggregateFunc) *CronTaskSelect { + cts.fns = append(cts.fns, fns...) + return cts +} + +// Scan applies the selector query and scans the result into the given value. +func (cts *CronTaskSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, cts.ctx, "Select") + if err := cts.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*CronTaskQuery, *CronTaskSelect](ctx, cts.CronTaskQuery, cts, cts.inters, v) +} + +func (cts *CronTaskSelect) sqlScan(ctx context.Context, root *CronTaskQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(cts.fns)) + for _, fn := range cts.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*cts.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := cts.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/crontask_update.go b/internal/database/ent/db/crontask_update.go new file mode 100644 index 00000000..1d3bd10f --- /dev/null +++ b/internal/database/ent/db/crontask_update.go @@ -0,0 +1,587 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/dialect/sql/sqljson" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" +) + +// CronTaskUpdate is the builder for updating CronTask entities. +type CronTaskUpdate struct { + config + hooks []Hook + mutation *CronTaskMutation +} + +// Where appends a list predicates to the CronTaskUpdate builder. +func (ctu *CronTaskUpdate) Where(ps ...predicate.CronTask) *CronTaskUpdate { + ctu.mutation.Where(ps...) + return ctu +} + +// SetUpdatedAt sets the "updated_at" field. +func (ctu *CronTaskUpdate) SetUpdatedAt(i int) *CronTaskUpdate { + ctu.mutation.ResetUpdatedAt() + ctu.mutation.SetUpdatedAt(i) + return ctu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (ctu *CronTaskUpdate) AddUpdatedAt(i int) *CronTaskUpdate { + ctu.mutation.AddUpdatedAt(i) + return ctu +} + +// SetType sets the "type" field. +func (ctu *CronTaskUpdate) SetType(s string) *CronTaskUpdate { + ctu.mutation.SetType(s) + return ctu +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (ctu *CronTaskUpdate) SetNillableType(s *string) *CronTaskUpdate { + if s != nil { + ctu.SetType(*s) + } + return ctu +} + +// SetReferenceID sets the "reference_id" field. +func (ctu *CronTaskUpdate) SetReferenceID(s string) *CronTaskUpdate { + ctu.mutation.SetReferenceID(s) + return ctu +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (ctu *CronTaskUpdate) SetNillableReferenceID(s *string) *CronTaskUpdate { + if s != nil { + ctu.SetReferenceID(*s) + } + return ctu +} + +// SetTargets sets the "targets" field. +func (ctu *CronTaskUpdate) SetTargets(s []string) *CronTaskUpdate { + ctu.mutation.SetTargets(s) + return ctu +} + +// AppendTargets appends s to the "targets" field. +func (ctu *CronTaskUpdate) AppendTargets(s []string) *CronTaskUpdate { + ctu.mutation.AppendTargets(s) + return ctu +} + +// SetStatus sets the "status" field. +func (ctu *CronTaskUpdate) SetStatus(s string) *CronTaskUpdate { + ctu.mutation.SetStatus(s) + return ctu +} + +// SetNillableStatus sets the "status" field if the given value is not nil. +func (ctu *CronTaskUpdate) SetNillableStatus(s *string) *CronTaskUpdate { + if s != nil { + ctu.SetStatus(*s) + } + return ctu +} + +// SetScheduledAfter sets the "scheduled_after" field. +func (ctu *CronTaskUpdate) SetScheduledAfter(i int) *CronTaskUpdate { + ctu.mutation.ResetScheduledAfter() + ctu.mutation.SetScheduledAfter(i) + return ctu +} + +// SetNillableScheduledAfter sets the "scheduled_after" field if the given value is not nil. +func (ctu *CronTaskUpdate) SetNillableScheduledAfter(i *int) *CronTaskUpdate { + if i != nil { + ctu.SetScheduledAfter(*i) + } + return ctu +} + +// AddScheduledAfter adds i to the "scheduled_after" field. +func (ctu *CronTaskUpdate) AddScheduledAfter(i int) *CronTaskUpdate { + ctu.mutation.AddScheduledAfter(i) + return ctu +} + +// SetLastRun sets the "last_run" field. +func (ctu *CronTaskUpdate) SetLastRun(i int) *CronTaskUpdate { + ctu.mutation.ResetLastRun() + ctu.mutation.SetLastRun(i) + return ctu +} + +// SetNillableLastRun sets the "last_run" field if the given value is not nil. +func (ctu *CronTaskUpdate) SetNillableLastRun(i *int) *CronTaskUpdate { + if i != nil { + ctu.SetLastRun(*i) + } + return ctu +} + +// AddLastRun adds i to the "last_run" field. +func (ctu *CronTaskUpdate) AddLastRun(i int) *CronTaskUpdate { + ctu.mutation.AddLastRun(i) + return ctu +} + +// SetLogs sets the "logs" field. +func (ctu *CronTaskUpdate) SetLogs(ml []models.TaskLog) *CronTaskUpdate { + ctu.mutation.SetLogs(ml) + return ctu +} + +// AppendLogs appends ml to the "logs" field. +func (ctu *CronTaskUpdate) AppendLogs(ml []models.TaskLog) *CronTaskUpdate { + ctu.mutation.AppendLogs(ml) + return ctu +} + +// SetData sets the "data" field. +func (ctu *CronTaskUpdate) SetData(m map[string]interface{}) *CronTaskUpdate { + ctu.mutation.SetData(m) + return ctu +} + +// Mutation returns the CronTaskMutation object of the builder. +func (ctu *CronTaskUpdate) Mutation() *CronTaskMutation { + return ctu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (ctu *CronTaskUpdate) Save(ctx context.Context) (int, error) { + ctu.defaults() + return withHooks(ctx, ctu.sqlSave, ctu.mutation, ctu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (ctu *CronTaskUpdate) SaveX(ctx context.Context) int { + affected, err := ctu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (ctu *CronTaskUpdate) Exec(ctx context.Context) error { + _, err := ctu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ctu *CronTaskUpdate) ExecX(ctx context.Context) { + if err := ctu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ctu *CronTaskUpdate) defaults() { + if _, ok := ctu.mutation.UpdatedAt(); !ok { + v := crontask.UpdateDefaultUpdatedAt() + ctu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ctu *CronTaskUpdate) check() error { + if v, ok := ctu.mutation.GetType(); ok { + if err := crontask.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "CronTask.type": %w`, err)} + } + } + if v, ok := ctu.mutation.ReferenceID(); ok { + if err := crontask.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "CronTask.reference_id": %w`, err)} + } + } + if v, ok := ctu.mutation.Status(); ok { + if err := crontask.StatusValidator(v); err != nil { + return &ValidationError{Name: "status", err: fmt.Errorf(`db: validator failed for field "CronTask.status": %w`, err)} + } + } + return nil +} + +func (ctu *CronTaskUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := ctu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(crontask.Table, crontask.Columns, sqlgraph.NewFieldSpec(crontask.FieldID, field.TypeString)) + if ps := ctu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := ctu.mutation.UpdatedAt(); ok { + _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ctu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(crontask.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ctu.mutation.GetType(); ok { + _spec.SetField(crontask.FieldType, field.TypeString, value) + } + if value, ok := ctu.mutation.ReferenceID(); ok { + _spec.SetField(crontask.FieldReferenceID, field.TypeString, value) + } + if value, ok := ctu.mutation.Targets(); ok { + _spec.SetField(crontask.FieldTargets, field.TypeJSON, value) + } + if value, ok := ctu.mutation.AppendedTargets(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, crontask.FieldTargets, value) + }) + } + if value, ok := ctu.mutation.Status(); ok { + _spec.SetField(crontask.FieldStatus, field.TypeString, value) + } + if value, ok := ctu.mutation.ScheduledAfter(); ok { + _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt, value) + } + if value, ok := ctu.mutation.AddedScheduledAfter(); ok { + _spec.AddField(crontask.FieldScheduledAfter, field.TypeInt, value) + } + if value, ok := ctu.mutation.LastRun(); ok { + _spec.SetField(crontask.FieldLastRun, field.TypeInt, value) + } + if value, ok := ctu.mutation.AddedLastRun(); ok { + _spec.AddField(crontask.FieldLastRun, field.TypeInt, value) + } + if value, ok := ctu.mutation.Logs(); ok { + _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) + } + if value, ok := ctu.mutation.AppendedLogs(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, crontask.FieldLogs, value) + }) + } + if value, ok := ctu.mutation.Data(); ok { + _spec.SetField(crontask.FieldData, field.TypeJSON, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, ctu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{crontask.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + ctu.mutation.done = true + return n, nil +} + +// CronTaskUpdateOne is the builder for updating a single CronTask entity. +type CronTaskUpdateOne struct { + config + fields []string + hooks []Hook + mutation *CronTaskMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (ctuo *CronTaskUpdateOne) SetUpdatedAt(i int) *CronTaskUpdateOne { + ctuo.mutation.ResetUpdatedAt() + ctuo.mutation.SetUpdatedAt(i) + return ctuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (ctuo *CronTaskUpdateOne) AddUpdatedAt(i int) *CronTaskUpdateOne { + ctuo.mutation.AddUpdatedAt(i) + return ctuo +} + +// SetType sets the "type" field. +func (ctuo *CronTaskUpdateOne) SetType(s string) *CronTaskUpdateOne { + ctuo.mutation.SetType(s) + return ctuo +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (ctuo *CronTaskUpdateOne) SetNillableType(s *string) *CronTaskUpdateOne { + if s != nil { + ctuo.SetType(*s) + } + return ctuo +} + +// SetReferenceID sets the "reference_id" field. +func (ctuo *CronTaskUpdateOne) SetReferenceID(s string) *CronTaskUpdateOne { + ctuo.mutation.SetReferenceID(s) + return ctuo +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (ctuo *CronTaskUpdateOne) SetNillableReferenceID(s *string) *CronTaskUpdateOne { + if s != nil { + ctuo.SetReferenceID(*s) + } + return ctuo +} + +// SetTargets sets the "targets" field. +func (ctuo *CronTaskUpdateOne) SetTargets(s []string) *CronTaskUpdateOne { + ctuo.mutation.SetTargets(s) + return ctuo +} + +// AppendTargets appends s to the "targets" field. +func (ctuo *CronTaskUpdateOne) AppendTargets(s []string) *CronTaskUpdateOne { + ctuo.mutation.AppendTargets(s) + return ctuo +} + +// SetStatus sets the "status" field. +func (ctuo *CronTaskUpdateOne) SetStatus(s string) *CronTaskUpdateOne { + ctuo.mutation.SetStatus(s) + return ctuo +} + +// SetNillableStatus sets the "status" field if the given value is not nil. +func (ctuo *CronTaskUpdateOne) SetNillableStatus(s *string) *CronTaskUpdateOne { + if s != nil { + ctuo.SetStatus(*s) + } + return ctuo +} + +// SetScheduledAfter sets the "scheduled_after" field. +func (ctuo *CronTaskUpdateOne) SetScheduledAfter(i int) *CronTaskUpdateOne { + ctuo.mutation.ResetScheduledAfter() + ctuo.mutation.SetScheduledAfter(i) + return ctuo +} + +// SetNillableScheduledAfter sets the "scheduled_after" field if the given value is not nil. +func (ctuo *CronTaskUpdateOne) SetNillableScheduledAfter(i *int) *CronTaskUpdateOne { + if i != nil { + ctuo.SetScheduledAfter(*i) + } + return ctuo +} + +// AddScheduledAfter adds i to the "scheduled_after" field. +func (ctuo *CronTaskUpdateOne) AddScheduledAfter(i int) *CronTaskUpdateOne { + ctuo.mutation.AddScheduledAfter(i) + return ctuo +} + +// SetLastRun sets the "last_run" field. +func (ctuo *CronTaskUpdateOne) SetLastRun(i int) *CronTaskUpdateOne { + ctuo.mutation.ResetLastRun() + ctuo.mutation.SetLastRun(i) + return ctuo +} + +// SetNillableLastRun sets the "last_run" field if the given value is not nil. +func (ctuo *CronTaskUpdateOne) SetNillableLastRun(i *int) *CronTaskUpdateOne { + if i != nil { + ctuo.SetLastRun(*i) + } + return ctuo +} + +// AddLastRun adds i to the "last_run" field. +func (ctuo *CronTaskUpdateOne) AddLastRun(i int) *CronTaskUpdateOne { + ctuo.mutation.AddLastRun(i) + return ctuo +} + +// SetLogs sets the "logs" field. +func (ctuo *CronTaskUpdateOne) SetLogs(ml []models.TaskLog) *CronTaskUpdateOne { + ctuo.mutation.SetLogs(ml) + return ctuo +} + +// AppendLogs appends ml to the "logs" field. +func (ctuo *CronTaskUpdateOne) AppendLogs(ml []models.TaskLog) *CronTaskUpdateOne { + ctuo.mutation.AppendLogs(ml) + return ctuo +} + +// SetData sets the "data" field. +func (ctuo *CronTaskUpdateOne) SetData(m map[string]interface{}) *CronTaskUpdateOne { + ctuo.mutation.SetData(m) + return ctuo +} + +// Mutation returns the CronTaskMutation object of the builder. +func (ctuo *CronTaskUpdateOne) Mutation() *CronTaskMutation { + return ctuo.mutation +} + +// Where appends a list predicates to the CronTaskUpdate builder. +func (ctuo *CronTaskUpdateOne) Where(ps ...predicate.CronTask) *CronTaskUpdateOne { + ctuo.mutation.Where(ps...) + return ctuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (ctuo *CronTaskUpdateOne) Select(field string, fields ...string) *CronTaskUpdateOne { + ctuo.fields = append([]string{field}, fields...) + return ctuo +} + +// Save executes the query and returns the updated CronTask entity. +func (ctuo *CronTaskUpdateOne) Save(ctx context.Context) (*CronTask, error) { + ctuo.defaults() + return withHooks(ctx, ctuo.sqlSave, ctuo.mutation, ctuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (ctuo *CronTaskUpdateOne) SaveX(ctx context.Context) *CronTask { + node, err := ctuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (ctuo *CronTaskUpdateOne) Exec(ctx context.Context) error { + _, err := ctuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ctuo *CronTaskUpdateOne) ExecX(ctx context.Context) { + if err := ctuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ctuo *CronTaskUpdateOne) defaults() { + if _, ok := ctuo.mutation.UpdatedAt(); !ok { + v := crontask.UpdateDefaultUpdatedAt() + ctuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ctuo *CronTaskUpdateOne) check() error { + if v, ok := ctuo.mutation.GetType(); ok { + if err := crontask.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "CronTask.type": %w`, err)} + } + } + if v, ok := ctuo.mutation.ReferenceID(); ok { + if err := crontask.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "CronTask.reference_id": %w`, err)} + } + } + if v, ok := ctuo.mutation.Status(); ok { + if err := crontask.StatusValidator(v); err != nil { + return &ValidationError{Name: "status", err: fmt.Errorf(`db: validator failed for field "CronTask.status": %w`, err)} + } + } + return nil +} + +func (ctuo *CronTaskUpdateOne) sqlSave(ctx context.Context) (_node *CronTask, err error) { + if err := ctuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(crontask.Table, crontask.Columns, sqlgraph.NewFieldSpec(crontask.FieldID, field.TypeString)) + id, ok := ctuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "CronTask.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := ctuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, crontask.FieldID) + for _, f := range fields { + if !crontask.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != crontask.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := ctuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := ctuo.mutation.UpdatedAt(); ok { + _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ctuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(crontask.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ctuo.mutation.GetType(); ok { + _spec.SetField(crontask.FieldType, field.TypeString, value) + } + if value, ok := ctuo.mutation.ReferenceID(); ok { + _spec.SetField(crontask.FieldReferenceID, field.TypeString, value) + } + if value, ok := ctuo.mutation.Targets(); ok { + _spec.SetField(crontask.FieldTargets, field.TypeJSON, value) + } + if value, ok := ctuo.mutation.AppendedTargets(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, crontask.FieldTargets, value) + }) + } + if value, ok := ctuo.mutation.Status(); ok { + _spec.SetField(crontask.FieldStatus, field.TypeString, value) + } + if value, ok := ctuo.mutation.ScheduledAfter(); ok { + _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt, value) + } + if value, ok := ctuo.mutation.AddedScheduledAfter(); ok { + _spec.AddField(crontask.FieldScheduledAfter, field.TypeInt, value) + } + if value, ok := ctuo.mutation.LastRun(); ok { + _spec.SetField(crontask.FieldLastRun, field.TypeInt, value) + } + if value, ok := ctuo.mutation.AddedLastRun(); ok { + _spec.AddField(crontask.FieldLastRun, field.TypeInt, value) + } + if value, ok := ctuo.mutation.Logs(); ok { + _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) + } + if value, ok := ctuo.mutation.AppendedLogs(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, crontask.FieldLogs, value) + }) + } + if value, ok := ctuo.mutation.Data(); ok { + _spec.SetField(crontask.FieldData, field.TypeJSON, value) + } + _node = &CronTask{config: ctuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, ctuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{crontask.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + ctuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/ent.go b/internal/database/ent/db/ent.go new file mode 100644 index 00000000..bcaa9136 --- /dev/null +++ b/internal/database/ent/db/ent.go @@ -0,0 +1,634 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + "reflect" + "sync" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" +) + +// ent aliases to avoid import conflicts in user's code. +type ( + Op = ent.Op + Hook = ent.Hook + Value = ent.Value + Query = ent.Query + QueryContext = ent.QueryContext + Querier = ent.Querier + QuerierFunc = ent.QuerierFunc + Interceptor = ent.Interceptor + InterceptFunc = ent.InterceptFunc + Traverser = ent.Traverser + TraverseFunc = ent.TraverseFunc + Policy = ent.Policy + Mutator = ent.Mutator + Mutation = ent.Mutation + MutateFunc = ent.MutateFunc +) + +type clientCtxKey struct{} + +// FromContext returns a Client stored inside a context, or nil if there isn't one. +func FromContext(ctx context.Context) *Client { + c, _ := ctx.Value(clientCtxKey{}).(*Client) + return c +} + +// NewContext returns a new context with the given Client attached. +func NewContext(parent context.Context, c *Client) context.Context { + return context.WithValue(parent, clientCtxKey{}, c) +} + +type txCtxKey struct{} + +// TxFromContext returns a Tx stored inside a context, or nil if there isn't one. +func TxFromContext(ctx context.Context) *Tx { + tx, _ := ctx.Value(txCtxKey{}).(*Tx) + return tx +} + +// NewTxContext returns a new context with the given Tx attached. +func NewTxContext(parent context.Context, tx *Tx) context.Context { + return context.WithValue(parent, txCtxKey{}, tx) +} + +// OrderFunc applies an ordering on the sql selector. +// Deprecated: Use Asc/Desc functions or the package builders instead. +type OrderFunc func(*sql.Selector) + +var ( + initCheck sync.Once + columnCheck sql.ColumnCheck +) + +// columnChecker checks if the column exists in the given table. +func checkColumn(table, column string) error { + initCheck.Do(func() { + columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ + account.Table: account.ValidColumn, + accountsnapshot.Table: accountsnapshot.ValidColumn, + achievementssnapshot.Table: achievementssnapshot.ValidColumn, + appconfiguration.Table: appconfiguration.ValidColumn, + applicationcommand.Table: applicationcommand.ValidColumn, + clan.Table: clan.ValidColumn, + crontask.Table: crontask.ValidColumn, + user.Table: user.ValidColumn, + userconnection.Table: userconnection.ValidColumn, + usercontent.Table: usercontent.ValidColumn, + usersubscription.Table: usersubscription.ValidColumn, + vehicle.Table: vehicle.ValidColumn, + vehicleaverage.Table: vehicleaverage.ValidColumn, + vehiclesnapshot.Table: vehiclesnapshot.ValidColumn, + }) + }) + return columnCheck(table, column) +} + +// Asc applies the given fields in ASC order. +func Asc(fields ...string) func(*sql.Selector) { + return func(s *sql.Selector) { + for _, f := range fields { + if err := checkColumn(s.TableName(), f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("db: %w", err)}) + } + s.OrderBy(sql.Asc(s.C(f))) + } + } +} + +// Desc applies the given fields in DESC order. +func Desc(fields ...string) func(*sql.Selector) { + return func(s *sql.Selector) { + for _, f := range fields { + if err := checkColumn(s.TableName(), f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("db: %w", err)}) + } + s.OrderBy(sql.Desc(s.C(f))) + } + } +} + +// AggregateFunc applies an aggregation step on the group-by traversal/selector. +type AggregateFunc func(*sql.Selector) string + +// As is a pseudo aggregation function for renaming another other functions with custom names. For example: +// +// GroupBy(field1, field2). +// Aggregate(db.As(db.Sum(field1), "sum_field1"), (db.As(db.Sum(field2), "sum_field2")). +// Scan(ctx, &v) +func As(fn AggregateFunc, end string) AggregateFunc { + return func(s *sql.Selector) string { + return sql.As(fn(s), end) + } +} + +// Count applies the "count" aggregation function on each group. +func Count() AggregateFunc { + return func(s *sql.Selector) string { + return sql.Count("*") + } +} + +// Max applies the "max" aggregation function on the given field of each group. +func Max(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("db: %w", err)}) + return "" + } + return sql.Max(s.C(field)) + } +} + +// Mean applies the "mean" aggregation function on the given field of each group. +func Mean(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("db: %w", err)}) + return "" + } + return sql.Avg(s.C(field)) + } +} + +// Min applies the "min" aggregation function on the given field of each group. +func Min(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("db: %w", err)}) + return "" + } + return sql.Min(s.C(field)) + } +} + +// Sum applies the "sum" aggregation function on the given field of each group. +func Sum(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("db: %w", err)}) + return "" + } + return sql.Sum(s.C(field)) + } +} + +// ValidationError returns when validating a field or edge fails. +type ValidationError struct { + Name string // Field or edge name. + err error +} + +// Error implements the error interface. +func (e *ValidationError) Error() string { + return e.err.Error() +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ValidationError) Unwrap() error { + return e.err +} + +// IsValidationError returns a boolean indicating whether the error is a validation error. +func IsValidationError(err error) bool { + if err == nil { + return false + } + var e *ValidationError + return errors.As(err, &e) +} + +// NotFoundError returns when trying to fetch a specific entity and it was not found in the database. +type NotFoundError struct { + label string +} + +// Error implements the error interface. +func (e *NotFoundError) Error() string { + return "db: " + e.label + " not found" +} + +// IsNotFound returns a boolean indicating whether the error is a not found error. +func IsNotFound(err error) bool { + if err == nil { + return false + } + var e *NotFoundError + return errors.As(err, &e) +} + +// MaskNotFound masks not found error. +func MaskNotFound(err error) error { + if IsNotFound(err) { + return nil + } + return err +} + +// NotSingularError returns when trying to fetch a singular entity and more then one was found in the database. +type NotSingularError struct { + label string +} + +// Error implements the error interface. +func (e *NotSingularError) Error() string { + return "db: " + e.label + " not singular" +} + +// IsNotSingular returns a boolean indicating whether the error is a not singular error. +func IsNotSingular(err error) bool { + if err == nil { + return false + } + var e *NotSingularError + return errors.As(err, &e) +} + +// NotLoadedError returns when trying to get a node that was not loaded by the query. +type NotLoadedError struct { + edge string +} + +// Error implements the error interface. +func (e *NotLoadedError) Error() string { + return "db: " + e.edge + " edge was not loaded" +} + +// IsNotLoaded returns a boolean indicating whether the error is a not loaded error. +func IsNotLoaded(err error) bool { + if err == nil { + return false + } + var e *NotLoadedError + return errors.As(err, &e) +} + +// ConstraintError returns when trying to create/update one or more entities and +// one or more of their constraints failed. For example, violation of edge or +// field uniqueness. +type ConstraintError struct { + msg string + wrap error +} + +// Error implements the error interface. +func (e ConstraintError) Error() string { + return "db: constraint failed: " + e.msg +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ConstraintError) Unwrap() error { + return e.wrap +} + +// IsConstraintError returns a boolean indicating whether the error is a constraint failure. +func IsConstraintError(err error) bool { + if err == nil { + return false + } + var e *ConstraintError + return errors.As(err, &e) +} + +// selector embedded by the different Select/GroupBy builders. +type selector struct { + label string + flds *[]string + fns []AggregateFunc + scan func(context.Context, any) error +} + +// ScanX is like Scan, but panics if an error occurs. +func (s *selector) ScanX(ctx context.Context, v any) { + if err := s.scan(ctx, v); err != nil { + panic(err) + } +} + +// Strings returns list of strings from a selector. It is only allowed when selecting one field. +func (s *selector) Strings(ctx context.Context) ([]string, error) { + if len(*s.flds) > 1 { + return nil, errors.New("db: Strings is not achievable when selecting more than 1 field") + } + var v []string + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// StringsX is like Strings, but panics if an error occurs. +func (s *selector) StringsX(ctx context.Context) []string { + v, err := s.Strings(ctx) + if err != nil { + panic(err) + } + return v +} + +// String returns a single string from a selector. It is only allowed when selecting one field. +func (s *selector) String(ctx context.Context) (_ string, err error) { + var v []string + if v, err = s.Strings(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("db: Strings returned %d results when one was expected", len(v)) + } + return +} + +// StringX is like String, but panics if an error occurs. +func (s *selector) StringX(ctx context.Context) string { + v, err := s.String(ctx) + if err != nil { + panic(err) + } + return v +} + +// Ints returns list of ints from a selector. It is only allowed when selecting one field. +func (s *selector) Ints(ctx context.Context) ([]int, error) { + if len(*s.flds) > 1 { + return nil, errors.New("db: Ints is not achievable when selecting more than 1 field") + } + var v []int + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// IntsX is like Ints, but panics if an error occurs. +func (s *selector) IntsX(ctx context.Context) []int { + v, err := s.Ints(ctx) + if err != nil { + panic(err) + } + return v +} + +// Int returns a single int from a selector. It is only allowed when selecting one field. +func (s *selector) Int(ctx context.Context) (_ int, err error) { + var v []int + if v, err = s.Ints(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("db: Ints returned %d results when one was expected", len(v)) + } + return +} + +// IntX is like Int, but panics if an error occurs. +func (s *selector) IntX(ctx context.Context) int { + v, err := s.Int(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64s returns list of float64s from a selector. It is only allowed when selecting one field. +func (s *selector) Float64s(ctx context.Context) ([]float64, error) { + if len(*s.flds) > 1 { + return nil, errors.New("db: Float64s is not achievable when selecting more than 1 field") + } + var v []float64 + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// Float64sX is like Float64s, but panics if an error occurs. +func (s *selector) Float64sX(ctx context.Context) []float64 { + v, err := s.Float64s(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64 returns a single float64 from a selector. It is only allowed when selecting one field. +func (s *selector) Float64(ctx context.Context) (_ float64, err error) { + var v []float64 + if v, err = s.Float64s(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("db: Float64s returned %d results when one was expected", len(v)) + } + return +} + +// Float64X is like Float64, but panics if an error occurs. +func (s *selector) Float64X(ctx context.Context) float64 { + v, err := s.Float64(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bools returns list of bools from a selector. It is only allowed when selecting one field. +func (s *selector) Bools(ctx context.Context) ([]bool, error) { + if len(*s.flds) > 1 { + return nil, errors.New("db: Bools is not achievable when selecting more than 1 field") + } + var v []bool + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// BoolsX is like Bools, but panics if an error occurs. +func (s *selector) BoolsX(ctx context.Context) []bool { + v, err := s.Bools(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bool returns a single bool from a selector. It is only allowed when selecting one field. +func (s *selector) Bool(ctx context.Context) (_ bool, err error) { + var v []bool + if v, err = s.Bools(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("db: Bools returned %d results when one was expected", len(v)) + } + return +} + +// BoolX is like Bool, but panics if an error occurs. +func (s *selector) BoolX(ctx context.Context) bool { + v, err := s.Bool(ctx) + if err != nil { + panic(err) + } + return v +} + +// withHooks invokes the builder operation with the given hooks, if any. +func withHooks[V Value, M any, PM interface { + *M + Mutation +}](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) { + if len(hooks) == 0 { + return exec(ctx) + } + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutationT, ok := any(m).(PM) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + // Set the mutation to the builder. + *mutation = *mutationT + return exec(ctx) + }) + for i := len(hooks) - 1; i >= 0; i-- { + if hooks[i] == nil { + return value, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = hooks[i](mut) + } + v, err := mut.Mutate(ctx, mutation) + if err != nil { + return value, err + } + nv, ok := v.(V) + if !ok { + return value, fmt.Errorf("unexpected node type %T returned from %T", v, mutation) + } + return nv, nil +} + +// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist. +func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context { + if ent.QueryFromContext(ctx) == nil { + qc.Op = op + ctx = ent.NewQueryContext(ctx, qc) + } + return ctx +} + +func querierAll[V Value, Q interface { + sqlAll(context.Context, ...queryHook) (V, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlAll(ctx) + }) +} + +func querierCount[Q interface { + sqlCount(context.Context) (int, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlCount(ctx) + }) +} + +func withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) { + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + rv, err := qr.Query(ctx, q) + if err != nil { + return v, err + } + vt, ok := rv.(V) + if !ok { + return v, fmt.Errorf("unexpected type %T returned from %T. expected type: %T", vt, q, v) + } + return vt, nil +} + +func scanWithInterceptors[Q1 ent.Query, Q2 interface { + sqlScan(context.Context, Q1, any) error +}](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error { + rv := reflect.ValueOf(v) + var qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q1) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + if err := selectOrGroup.sqlScan(ctx, query, v); err != nil { + return nil, err + } + if k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() { + return rv.Elem().Interface(), nil + } + return v, nil + }) + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + vv, err := qr.Query(ctx, rootQuery) + if err != nil { + return err + } + switch rv2 := reflect.ValueOf(vv); { + case rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer: + case rv.Type() == rv2.Type(): + rv.Elem().Set(rv2.Elem()) + case rv.Elem().Type() == rv2.Type(): + rv.Elem().Set(rv2) + } + return nil +} + +// queryHook describes an internal hook for the different sqlAll methods. +type queryHook func(context.Context, *sqlgraph.QuerySpec) diff --git a/internal/database/ent/db/enttest/enttest.go b/internal/database/ent/db/enttest/enttest.go new file mode 100644 index 00000000..4267b558 --- /dev/null +++ b/internal/database/ent/db/enttest/enttest.go @@ -0,0 +1,84 @@ +// Code generated by ent, DO NOT EDIT. + +package enttest + +import ( + "context" + + "github.com/cufee/aftermath/internal/database/ent/db" + // required by schema hooks. + _ "github.com/cufee/aftermath/internal/database/ent/db/runtime" + + "entgo.io/ent/dialect/sql/schema" + "github.com/cufee/aftermath/internal/database/ent/db/migrate" +) + +type ( + // TestingT is the interface that is shared between + // testing.T and testing.B and used by enttest. + TestingT interface { + FailNow() + Error(...any) + } + + // Option configures client creation. + Option func(*options) + + options struct { + opts []db.Option + migrateOpts []schema.MigrateOption + } +) + +// WithOptions forwards options to client creation. +func WithOptions(opts ...db.Option) Option { + return func(o *options) { + o.opts = append(o.opts, opts...) + } +} + +// WithMigrateOptions forwards options to auto migration. +func WithMigrateOptions(opts ...schema.MigrateOption) Option { + return func(o *options) { + o.migrateOpts = append(o.migrateOpts, opts...) + } +} + +func newOptions(opts []Option) *options { + o := &options{} + for _, opt := range opts { + opt(o) + } + return o +} + +// Open calls db.Open and auto-run migration. +func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *db.Client { + o := newOptions(opts) + c, err := db.Open(driverName, dataSourceName, o.opts...) + if err != nil { + t.Error(err) + t.FailNow() + } + migrateSchema(t, c, o) + return c +} + +// NewClient calls db.NewClient and auto-run migration. +func NewClient(t TestingT, opts ...Option) *db.Client { + o := newOptions(opts) + c := db.NewClient(o.opts...) + migrateSchema(t, c, o) + return c +} +func migrateSchema(t TestingT, c *db.Client, o *options) { + tables, err := schema.CopyTables(migrate.Tables) + if err != nil { + t.Error(err) + t.FailNow() + } + if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { + t.Error(err) + t.FailNow() + } +} diff --git a/internal/database/ent/db/hook/hook.go b/internal/database/ent/db/hook/hook.go new file mode 100644 index 00000000..fe0ba69f --- /dev/null +++ b/internal/database/ent/db/hook/hook.go @@ -0,0 +1,355 @@ +// Code generated by ent, DO NOT EDIT. + +package hook + +import ( + "context" + "fmt" + + "github.com/cufee/aftermath/internal/database/ent/db" +) + +// The AccountFunc type is an adapter to allow the use of ordinary +// function as Account mutator. +type AccountFunc func(context.Context, *db.AccountMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f AccountFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.AccountMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.AccountMutation", m) +} + +// The AccountSnapshotFunc type is an adapter to allow the use of ordinary +// function as AccountSnapshot mutator. +type AccountSnapshotFunc func(context.Context, *db.AccountSnapshotMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f AccountSnapshotFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.AccountSnapshotMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.AccountSnapshotMutation", m) +} + +// The AchievementsSnapshotFunc type is an adapter to allow the use of ordinary +// function as AchievementsSnapshot mutator. +type AchievementsSnapshotFunc func(context.Context, *db.AchievementsSnapshotMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f AchievementsSnapshotFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.AchievementsSnapshotMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.AchievementsSnapshotMutation", m) +} + +// The AppConfigurationFunc type is an adapter to allow the use of ordinary +// function as AppConfiguration mutator. +type AppConfigurationFunc func(context.Context, *db.AppConfigurationMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f AppConfigurationFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.AppConfigurationMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.AppConfigurationMutation", m) +} + +// The ApplicationCommandFunc type is an adapter to allow the use of ordinary +// function as ApplicationCommand mutator. +type ApplicationCommandFunc func(context.Context, *db.ApplicationCommandMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f ApplicationCommandFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.ApplicationCommandMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.ApplicationCommandMutation", m) +} + +// The ClanFunc type is an adapter to allow the use of ordinary +// function as Clan mutator. +type ClanFunc func(context.Context, *db.ClanMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f ClanFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.ClanMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.ClanMutation", m) +} + +// The CronTaskFunc type is an adapter to allow the use of ordinary +// function as CronTask mutator. +type CronTaskFunc func(context.Context, *db.CronTaskMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f CronTaskFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.CronTaskMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.CronTaskMutation", m) +} + +// The UserFunc type is an adapter to allow the use of ordinary +// function as User mutator. +type UserFunc func(context.Context, *db.UserMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f UserFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.UserMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.UserMutation", m) +} + +// The UserConnectionFunc type is an adapter to allow the use of ordinary +// function as UserConnection mutator. +type UserConnectionFunc func(context.Context, *db.UserConnectionMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f UserConnectionFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.UserConnectionMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.UserConnectionMutation", m) +} + +// The UserContentFunc type is an adapter to allow the use of ordinary +// function as UserContent mutator. +type UserContentFunc func(context.Context, *db.UserContentMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f UserContentFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.UserContentMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.UserContentMutation", m) +} + +// The UserSubscriptionFunc type is an adapter to allow the use of ordinary +// function as UserSubscription mutator. +type UserSubscriptionFunc func(context.Context, *db.UserSubscriptionMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f UserSubscriptionFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.UserSubscriptionMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.UserSubscriptionMutation", m) +} + +// The VehicleFunc type is an adapter to allow the use of ordinary +// function as Vehicle mutator. +type VehicleFunc func(context.Context, *db.VehicleMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f VehicleFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.VehicleMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.VehicleMutation", m) +} + +// The VehicleAverageFunc type is an adapter to allow the use of ordinary +// function as VehicleAverage mutator. +type VehicleAverageFunc func(context.Context, *db.VehicleAverageMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f VehicleAverageFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.VehicleAverageMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.VehicleAverageMutation", m) +} + +// The VehicleSnapshotFunc type is an adapter to allow the use of ordinary +// function as VehicleSnapshot mutator. +type VehicleSnapshotFunc func(context.Context, *db.VehicleSnapshotMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f VehicleSnapshotFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.VehicleSnapshotMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.VehicleSnapshotMutation", m) +} + +// Condition is a hook condition function. +type Condition func(context.Context, db.Mutation) bool + +// And groups conditions with the AND operator. +func And(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m db.Mutation) bool { + if !first(ctx, m) || !second(ctx, m) { + return false + } + for _, cond := range rest { + if !cond(ctx, m) { + return false + } + } + return true + } +} + +// Or groups conditions with the OR operator. +func Or(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m db.Mutation) bool { + if first(ctx, m) || second(ctx, m) { + return true + } + for _, cond := range rest { + if cond(ctx, m) { + return true + } + } + return false + } +} + +// Not negates a given condition. +func Not(cond Condition) Condition { + return func(ctx context.Context, m db.Mutation) bool { + return !cond(ctx, m) + } +} + +// HasOp is a condition testing mutation operation. +func HasOp(op db.Op) Condition { + return func(_ context.Context, m db.Mutation) bool { + return m.Op().Is(op) + } +} + +// HasAddedFields is a condition validating `.AddedField` on fields. +func HasAddedFields(field string, fields ...string) Condition { + return func(_ context.Context, m db.Mutation) bool { + if _, exists := m.AddedField(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.AddedField(field); !exists { + return false + } + } + return true + } +} + +// HasClearedFields is a condition validating `.FieldCleared` on fields. +func HasClearedFields(field string, fields ...string) Condition { + return func(_ context.Context, m db.Mutation) bool { + if exists := m.FieldCleared(field); !exists { + return false + } + for _, field := range fields { + if exists := m.FieldCleared(field); !exists { + return false + } + } + return true + } +} + +// HasFields is a condition validating `.Field` on fields. +func HasFields(field string, fields ...string) Condition { + return func(_ context.Context, m db.Mutation) bool { + if _, exists := m.Field(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.Field(field); !exists { + return false + } + } + return true + } +} + +// If executes the given hook under condition. +// +// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) +func If(hk db.Hook, cond Condition) db.Hook { + return func(next db.Mutator) db.Mutator { + return db.MutateFunc(func(ctx context.Context, m db.Mutation) (db.Value, error) { + if cond(ctx, m) { + return hk(next).Mutate(ctx, m) + } + return next.Mutate(ctx, m) + }) + } +} + +// On executes the given hook only for the given operation. +// +// hook.On(Log, db.Delete|db.Create) +func On(hk db.Hook, op db.Op) db.Hook { + return If(hk, HasOp(op)) +} + +// Unless skips the given hook only for the given operation. +// +// hook.Unless(Log, db.Update|db.UpdateOne) +func Unless(hk db.Hook, op db.Op) db.Hook { + return If(hk, Not(HasOp(op))) +} + +// FixedError is a hook returning a fixed error. +func FixedError(err error) db.Hook { + return func(db.Mutator) db.Mutator { + return db.MutateFunc(func(context.Context, db.Mutation) (db.Value, error) { + return nil, err + }) + } +} + +// Reject returns a hook that rejects all operations that match op. +// +// func (T) Hooks() []db.Hook { +// return []db.Hook{ +// Reject(db.Delete|db.Update), +// } +// } +func Reject(op db.Op) db.Hook { + hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) + return On(hk, op) +} + +// Chain acts as a list of hooks and is effectively immutable. +// Once created, it will always hold the same set of hooks in the same order. +type Chain struct { + hooks []db.Hook +} + +// NewChain creates a new chain of hooks. +func NewChain(hooks ...db.Hook) Chain { + return Chain{append([]db.Hook(nil), hooks...)} +} + +// Hook chains the list of hooks and returns the final hook. +func (c Chain) Hook() db.Hook { + return func(mutator db.Mutator) db.Mutator { + for i := len(c.hooks) - 1; i >= 0; i-- { + mutator = c.hooks[i](mutator) + } + return mutator + } +} + +// Append extends a chain, adding the specified hook +// as the last ones in the mutation flow. +func (c Chain) Append(hooks ...db.Hook) Chain { + newHooks := make([]db.Hook, 0, len(c.hooks)+len(hooks)) + newHooks = append(newHooks, c.hooks...) + newHooks = append(newHooks, hooks...) + return Chain{newHooks} +} + +// Extend extends a chain, adding the specified chain +// as the last ones in the mutation flow. +func (c Chain) Extend(chain Chain) Chain { + return c.Append(chain.hooks...) +} diff --git a/internal/database/ent/db/migrate/migrate.go b/internal/database/ent/db/migrate/migrate.go new file mode 100644 index 00000000..d8d3bcb8 --- /dev/null +++ b/internal/database/ent/db/migrate/migrate.go @@ -0,0 +1,96 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "context" + "fmt" + "io" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql/schema" +) + +var ( + // WithGlobalUniqueID sets the universal ids options to the migration. + // If this option is enabled, ent migration will allocate a 1<<32 range + // for the ids of each entity (table). + // Note that this option cannot be applied on tables that already exist. + WithGlobalUniqueID = schema.WithGlobalUniqueID + // WithDropColumn sets the drop column option to the migration. + // If this option is enabled, ent migration will drop old columns + // that were used for both fields and edges. This defaults to false. + WithDropColumn = schema.WithDropColumn + // WithDropIndex sets the drop index option to the migration. + // If this option is enabled, ent migration will drop old indexes + // that were defined in the schema. This defaults to false. + // Note that unique constraints are defined using `UNIQUE INDEX`, + // and therefore, it's recommended to enable this option to get more + // flexibility in the schema changes. + WithDropIndex = schema.WithDropIndex + // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. + WithForeignKeys = schema.WithForeignKeys +) + +// Schema is the API for creating, migrating and dropping a schema. +type Schema struct { + drv dialect.Driver +} + +// NewSchema creates a new schema client. +func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } + +// Create creates all schema resources. +func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { + return Create(ctx, s, Tables, opts...) +} + +// Create creates all table resources using the given schema driver. +func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %w", err) + } + return migrate.Create(ctx, tables...) +} + +// Diff compares the state read from a database connection or migration directory with +// the state defined by the Ent schema. Changes will be written to new migration files. +func Diff(ctx context.Context, url string, opts ...schema.MigrateOption) error { + return NamedDiff(ctx, url, "changes", opts...) +} + +// NamedDiff compares the state read from a database connection or migration directory with +// the state defined by the Ent schema. Changes will be written to new named migration files. +func NamedDiff(ctx context.Context, url, name string, opts ...schema.MigrateOption) error { + return schema.Diff(ctx, url, name, Tables, opts...) +} + +// Diff creates a migration file containing the statements to resolve the diff +// between the Ent schema and the connected database. +func (s *Schema) Diff(ctx context.Context, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %w", err) + } + return migrate.Diff(ctx, Tables...) +} + +// NamedDiff creates a named migration file containing the statements to resolve the diff +// between the Ent schema and the connected database. +func (s *Schema) NamedDiff(ctx context.Context, name string, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %w", err) + } + return migrate.NamedDiff(ctx, name, Tables...) +} + +// WriteTo writes the schema changes to w instead of running them against the database. +// +// if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { +// log.Fatal(err) +// } +func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { + return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) +} diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go new file mode 100644 index 00000000..6496f121 --- /dev/null +++ b/internal/database/ent/db/migrate/schema.go @@ -0,0 +1,524 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "entgo.io/ent/dialect/sql/schema" + "entgo.io/ent/schema/field" +) + +var ( + // AccountsColumns holds the columns for the "accounts" table. + AccountsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "last_battle_time", Type: field.TypeInt}, + {Name: "account_created_at", Type: field.TypeInt}, + {Name: "realm", Type: field.TypeString, Size: 5}, + {Name: "nickname", Type: field.TypeString}, + {Name: "private", Type: field.TypeBool, Default: false}, + {Name: "clan_id", Type: field.TypeString, Nullable: true}, + } + // AccountsTable holds the schema information for the "accounts" table. + AccountsTable = &schema.Table{ + Name: "accounts", + Columns: AccountsColumns, + PrimaryKey: []*schema.Column{AccountsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "accounts_clans_accounts", + Columns: []*schema.Column{AccountsColumns[8]}, + RefColumns: []*schema.Column{ClansColumns[0]}, + OnDelete: schema.SetNull, + }, + }, + Indexes: []*schema.Index{ + { + Name: "account_id_last_battle_time", + Unique: false, + Columns: []*schema.Column{AccountsColumns[0], AccountsColumns[3]}, + }, + { + Name: "account_realm", + Unique: false, + Columns: []*schema.Column{AccountsColumns[5]}, + }, + { + Name: "account_realm_last_battle_time", + Unique: false, + Columns: []*schema.Column{AccountsColumns[5], AccountsColumns[3]}, + }, + { + Name: "account_clan_id", + Unique: false, + Columns: []*schema.Column{AccountsColumns[8]}, + }, + }, + } + // AccountSnapshotsColumns holds the columns for the "account_snapshots" table. + AccountSnapshotsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "type", Type: field.TypeEnum, Enums: []string{"live", "daily"}}, + {Name: "last_battle_time", Type: field.TypeInt}, + {Name: "reference_id", Type: field.TypeString}, + {Name: "rating_battles", Type: field.TypeInt}, + {Name: "rating_frame", Type: field.TypeJSON}, + {Name: "regular_battles", Type: field.TypeInt}, + {Name: "regular_frame", Type: field.TypeJSON}, + {Name: "account_id", Type: field.TypeString}, + } + // AccountSnapshotsTable holds the schema information for the "account_snapshots" table. + AccountSnapshotsTable = &schema.Table{ + Name: "account_snapshots", + Columns: AccountSnapshotsColumns, + PrimaryKey: []*schema.Column{AccountSnapshotsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "account_snapshots_accounts_snapshots", + Columns: []*schema.Column{AccountSnapshotsColumns[10]}, + RefColumns: []*schema.Column{AccountsColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "accountsnapshot_created_at", + Unique: false, + Columns: []*schema.Column{AccountSnapshotsColumns[1]}, + }, + { + Name: "accountsnapshot_type_account_id_created_at", + Unique: false, + Columns: []*schema.Column{AccountSnapshotsColumns[3], AccountSnapshotsColumns[10], AccountSnapshotsColumns[1]}, + }, + { + Name: "accountsnapshot_type_account_id_reference_id", + Unique: false, + Columns: []*schema.Column{AccountSnapshotsColumns[3], AccountSnapshotsColumns[10], AccountSnapshotsColumns[5]}, + }, + { + Name: "accountsnapshot_type_account_id_reference_id_created_at", + Unique: false, + Columns: []*schema.Column{AccountSnapshotsColumns[3], AccountSnapshotsColumns[10], AccountSnapshotsColumns[5], AccountSnapshotsColumns[1]}, + }, + }, + } + // AchievementsSnapshotsColumns holds the columns for the "achievements_snapshots" table. + AchievementsSnapshotsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "type", Type: field.TypeEnum, Enums: []string{"live", "daily"}}, + {Name: "reference_id", Type: field.TypeString}, + {Name: "battles", Type: field.TypeInt}, + {Name: "last_battle_time", Type: field.TypeInt}, + {Name: "data", Type: field.TypeJSON}, + {Name: "account_id", Type: field.TypeString}, + } + // AchievementsSnapshotsTable holds the schema information for the "achievements_snapshots" table. + AchievementsSnapshotsTable = &schema.Table{ + Name: "achievements_snapshots", + Columns: AchievementsSnapshotsColumns, + PrimaryKey: []*schema.Column{AchievementsSnapshotsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "achievements_snapshots_accounts_achievement_snapshots", + Columns: []*schema.Column{AchievementsSnapshotsColumns[8]}, + RefColumns: []*schema.Column{AccountsColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "achievementssnapshot_created_at", + Unique: false, + Columns: []*schema.Column{AchievementsSnapshotsColumns[1]}, + }, + { + Name: "achievementssnapshot_account_id_reference_id", + Unique: false, + Columns: []*schema.Column{AchievementsSnapshotsColumns[8], AchievementsSnapshotsColumns[4]}, + }, + { + Name: "achievementssnapshot_account_id_reference_id_created_at", + Unique: false, + Columns: []*schema.Column{AchievementsSnapshotsColumns[8], AchievementsSnapshotsColumns[4], AchievementsSnapshotsColumns[1]}, + }, + }, + } + // AppConfigurationsColumns holds the columns for the "app_configurations" table. + AppConfigurationsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "key", Type: field.TypeString, Unique: true}, + {Name: "value", Type: field.TypeJSON}, + {Name: "metadata", Type: field.TypeJSON, Nullable: true}, + } + // AppConfigurationsTable holds the schema information for the "app_configurations" table. + AppConfigurationsTable = &schema.Table{ + Name: "app_configurations", + Columns: AppConfigurationsColumns, + PrimaryKey: []*schema.Column{AppConfigurationsColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "appconfiguration_key", + Unique: false, + Columns: []*schema.Column{AppConfigurationsColumns[3]}, + }, + }, + } + // ApplicationCommandsColumns holds the columns for the "application_commands" table. + ApplicationCommandsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "name", Type: field.TypeString, Unique: true}, + {Name: "version", Type: field.TypeString}, + {Name: "options_hash", Type: field.TypeString}, + } + // ApplicationCommandsTable holds the schema information for the "application_commands" table. + ApplicationCommandsTable = &schema.Table{ + Name: "application_commands", + Columns: ApplicationCommandsColumns, + PrimaryKey: []*schema.Column{ApplicationCommandsColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "applicationcommand_options_hash", + Unique: false, + Columns: []*schema.Column{ApplicationCommandsColumns[5]}, + }, + }, + } + // ClansColumns holds the columns for the "clans" table. + ClansColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "tag", Type: field.TypeString}, + {Name: "name", Type: field.TypeString}, + {Name: "emblem_id", Type: field.TypeString, Nullable: true, Default: ""}, + {Name: "members", Type: field.TypeJSON}, + } + // ClansTable holds the schema information for the "clans" table. + ClansTable = &schema.Table{ + Name: "clans", + Columns: ClansColumns, + PrimaryKey: []*schema.Column{ClansColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "clan_tag", + Unique: false, + Columns: []*schema.Column{ClansColumns[3]}, + }, + { + Name: "clan_name", + Unique: false, + Columns: []*schema.Column{ClansColumns[4]}, + }, + }, + } + // CronTasksColumns holds the columns for the "cron_tasks" table. + CronTasksColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "type", Type: field.TypeString}, + {Name: "reference_id", Type: field.TypeString}, + {Name: "targets", Type: field.TypeJSON}, + {Name: "status", Type: field.TypeString}, + {Name: "scheduled_after", Type: field.TypeInt}, + {Name: "last_run", Type: field.TypeInt}, + {Name: "logs", Type: field.TypeJSON}, + {Name: "data", Type: field.TypeJSON}, + } + // CronTasksTable holds the schema information for the "cron_tasks" table. + CronTasksTable = &schema.Table{ + Name: "cron_tasks", + Columns: CronTasksColumns, + PrimaryKey: []*schema.Column{CronTasksColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "crontask_reference_id", + Unique: false, + Columns: []*schema.Column{CronTasksColumns[4]}, + }, + { + Name: "crontask_status_last_run", + Unique: false, + Columns: []*schema.Column{CronTasksColumns[6], CronTasksColumns[8]}, + }, + { + Name: "crontask_status_created_at", + Unique: false, + Columns: []*schema.Column{CronTasksColumns[6], CronTasksColumns[1]}, + }, + { + Name: "crontask_status_scheduled_after", + Unique: false, + Columns: []*schema.Column{CronTasksColumns[6], CronTasksColumns[7]}, + }, + }, + } + // UsersColumns holds the columns for the "users" table. + UsersColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "permissions", Type: field.TypeString, Default: ""}, + {Name: "feature_flags", Type: field.TypeJSON, Nullable: true}, + } + // UsersTable holds the schema information for the "users" table. + UsersTable = &schema.Table{ + Name: "users", + Columns: UsersColumns, + PrimaryKey: []*schema.Column{UsersColumns[0]}, + } + // UserConnectionsColumns holds the columns for the "user_connections" table. + UserConnectionsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "type", Type: field.TypeEnum, Enums: []string{"wargaming"}}, + {Name: "reference_id", Type: field.TypeString}, + {Name: "permissions", Type: field.TypeString, Nullable: true, Default: ""}, + {Name: "metadata", Type: field.TypeJSON, Nullable: true}, + {Name: "user_id", Type: field.TypeString}, + } + // UserConnectionsTable holds the schema information for the "user_connections" table. + UserConnectionsTable = &schema.Table{ + Name: "user_connections", + Columns: UserConnectionsColumns, + PrimaryKey: []*schema.Column{UserConnectionsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "user_connections_users_connections", + Columns: []*schema.Column{UserConnectionsColumns[7]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "userconnection_user_id", + Unique: false, + Columns: []*schema.Column{UserConnectionsColumns[7]}, + }, + { + Name: "userconnection_type_user_id", + Unique: false, + Columns: []*schema.Column{UserConnectionsColumns[3], UserConnectionsColumns[7]}, + }, + { + Name: "userconnection_reference_id", + Unique: false, + Columns: []*schema.Column{UserConnectionsColumns[4]}, + }, + { + Name: "userconnection_type_reference_id", + Unique: false, + Columns: []*schema.Column{UserConnectionsColumns[3], UserConnectionsColumns[4]}, + }, + }, + } + // UserContentsColumns holds the columns for the "user_contents" table. + UserContentsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "type", Type: field.TypeEnum, Enums: []string{"clan-background-image", "personal-background-image"}}, + {Name: "reference_id", Type: field.TypeString}, + {Name: "value", Type: field.TypeJSON}, + {Name: "metadata", Type: field.TypeJSON}, + {Name: "user_id", Type: field.TypeString}, + } + // UserContentsTable holds the schema information for the "user_contents" table. + UserContentsTable = &schema.Table{ + Name: "user_contents", + Columns: UserContentsColumns, + PrimaryKey: []*schema.Column{UserContentsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "user_contents_users_content", + Columns: []*schema.Column{UserContentsColumns[7]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "usercontent_user_id", + Unique: false, + Columns: []*schema.Column{UserContentsColumns[7]}, + }, + { + Name: "usercontent_type_user_id", + Unique: false, + Columns: []*schema.Column{UserContentsColumns[3], UserContentsColumns[7]}, + }, + { + Name: "usercontent_reference_id", + Unique: false, + Columns: []*schema.Column{UserContentsColumns[4]}, + }, + { + Name: "usercontent_type_reference_id", + Unique: false, + Columns: []*schema.Column{UserContentsColumns[3], UserContentsColumns[4]}, + }, + }, + } + // UserSubscriptionsColumns holds the columns for the "user_subscriptions" table. + UserSubscriptionsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "type", Type: field.TypeEnum, Enums: []string{"aftermath-pro", "aftermath-pro-clan", "aftermath-plus", "supporter", "verified-clan", "server-moderator", "content-moderator", "developer", "server-booster", "content-translator"}}, + {Name: "expires_at", Type: field.TypeInt}, + {Name: "permissions", Type: field.TypeString}, + {Name: "reference_id", Type: field.TypeString}, + {Name: "user_id", Type: field.TypeString}, + } + // UserSubscriptionsTable holds the schema information for the "user_subscriptions" table. + UserSubscriptionsTable = &schema.Table{ + Name: "user_subscriptions", + Columns: UserSubscriptionsColumns, + PrimaryKey: []*schema.Column{UserSubscriptionsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "user_subscriptions_users_subscriptions", + Columns: []*schema.Column{UserSubscriptionsColumns[7]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "usersubscription_user_id", + Unique: false, + Columns: []*schema.Column{UserSubscriptionsColumns[7]}, + }, + { + Name: "usersubscription_type_user_id", + Unique: false, + Columns: []*schema.Column{UserSubscriptionsColumns[3], UserSubscriptionsColumns[7]}, + }, + { + Name: "usersubscription_expires_at", + Unique: false, + Columns: []*schema.Column{UserSubscriptionsColumns[4]}, + }, + { + Name: "usersubscription_expires_at_user_id", + Unique: false, + Columns: []*schema.Column{UserSubscriptionsColumns[4], UserSubscriptionsColumns[7]}, + }, + }, + } + // VehiclesColumns holds the columns for the "vehicles" table. + VehiclesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "tier", Type: field.TypeInt}, + {Name: "localized_names", Type: field.TypeJSON}, + } + // VehiclesTable holds the schema information for the "vehicles" table. + VehiclesTable = &schema.Table{ + Name: "vehicles", + Columns: VehiclesColumns, + PrimaryKey: []*schema.Column{VehiclesColumns[0]}, + } + // VehicleAveragesColumns holds the columns for the "vehicle_averages" table. + VehicleAveragesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "data", Type: field.TypeJSON}, + } + // VehicleAveragesTable holds the schema information for the "vehicle_averages" table. + VehicleAveragesTable = &schema.Table{ + Name: "vehicle_averages", + Columns: VehicleAveragesColumns, + PrimaryKey: []*schema.Column{VehicleAveragesColumns[0]}, + } + // VehicleSnapshotsColumns holds the columns for the "vehicle_snapshots" table. + VehicleSnapshotsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt}, + {Name: "updated_at", Type: field.TypeInt}, + {Name: "type", Type: field.TypeEnum, Enums: []string{"live", "daily"}}, + {Name: "vehicle_id", Type: field.TypeString}, + {Name: "reference_id", Type: field.TypeString}, + {Name: "battles", Type: field.TypeInt}, + {Name: "last_battle_time", Type: field.TypeInt}, + {Name: "frame", Type: field.TypeJSON}, + {Name: "account_id", Type: field.TypeString}, + } + // VehicleSnapshotsTable holds the schema information for the "vehicle_snapshots" table. + VehicleSnapshotsTable = &schema.Table{ + Name: "vehicle_snapshots", + Columns: VehicleSnapshotsColumns, + PrimaryKey: []*schema.Column{VehicleSnapshotsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "vehicle_snapshots_accounts_vehicle_snapshots", + Columns: []*schema.Column{VehicleSnapshotsColumns[9]}, + RefColumns: []*schema.Column{AccountsColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "vehiclesnapshot_created_at", + Unique: false, + Columns: []*schema.Column{VehicleSnapshotsColumns[1]}, + }, + { + Name: "vehiclesnapshot_vehicle_id_created_at", + Unique: false, + Columns: []*schema.Column{VehicleSnapshotsColumns[4], VehicleSnapshotsColumns[1]}, + }, + { + Name: "vehiclesnapshot_account_id_created_at", + Unique: false, + Columns: []*schema.Column{VehicleSnapshotsColumns[9], VehicleSnapshotsColumns[1]}, + }, + { + Name: "vehiclesnapshot_account_id_type_created_at", + Unique: false, + Columns: []*schema.Column{VehicleSnapshotsColumns[9], VehicleSnapshotsColumns[3], VehicleSnapshotsColumns[1]}, + }, + }, + } + // Tables holds all the tables in the schema. + Tables = []*schema.Table{ + AccountsTable, + AccountSnapshotsTable, + AchievementsSnapshotsTable, + AppConfigurationsTable, + ApplicationCommandsTable, + ClansTable, + CronTasksTable, + UsersTable, + UserConnectionsTable, + UserContentsTable, + UserSubscriptionsTable, + VehiclesTable, + VehicleAveragesTable, + VehicleSnapshotsTable, + } +) + +func init() { + AccountsTable.ForeignKeys[0].RefTable = ClansTable + AccountSnapshotsTable.ForeignKeys[0].RefTable = AccountsTable + AchievementsSnapshotsTable.ForeignKeys[0].RefTable = AccountsTable + UserConnectionsTable.ForeignKeys[0].RefTable = UsersTable + UserContentsTable.ForeignKeys[0].RefTable = UsersTable + UserSubscriptionsTable.ForeignKeys[0].RefTable = UsersTable + VehicleSnapshotsTable.ForeignKeys[0].RefTable = AccountsTable +} diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go new file mode 100644 index 00000000..dbf6c4bc --- /dev/null +++ b/internal/database/ent/db/mutation.go @@ -0,0 +1,11540 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + "sync" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" + "github.com/cufee/am-wg-proxy-next/v2/types" +) + +const ( + // Operation types. + OpCreate = ent.OpCreate + OpDelete = ent.OpDelete + OpDeleteOne = ent.OpDeleteOne + OpUpdate = ent.OpUpdate + OpUpdateOne = ent.OpUpdateOne + + // Node types. + TypeAccount = "Account" + TypeAccountSnapshot = "AccountSnapshot" + TypeAchievementsSnapshot = "AchievementsSnapshot" + TypeAppConfiguration = "AppConfiguration" + TypeApplicationCommand = "ApplicationCommand" + TypeClan = "Clan" + TypeCronTask = "CronTask" + TypeUser = "User" + TypeUserConnection = "UserConnection" + TypeUserContent = "UserContent" + TypeUserSubscription = "UserSubscription" + TypeVehicle = "Vehicle" + TypeVehicleAverage = "VehicleAverage" + TypeVehicleSnapshot = "VehicleSnapshot" +) + +// AccountMutation represents an operation that mutates the Account nodes in the graph. +type AccountMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + last_battle_time *int + addlast_battle_time *int + account_created_at *int + addaccount_created_at *int + realm *string + nickname *string + private *bool + clearedFields map[string]struct{} + clan *string + clearedclan bool + snapshots map[string]struct{} + removedsnapshots map[string]struct{} + clearedsnapshots bool + vehicle_snapshots map[string]struct{} + removedvehicle_snapshots map[string]struct{} + clearedvehicle_snapshots bool + achievement_snapshots map[string]struct{} + removedachievement_snapshots map[string]struct{} + clearedachievement_snapshots bool + done bool + oldValue func(context.Context) (*Account, error) + predicates []predicate.Account +} + +var _ ent.Mutation = (*AccountMutation)(nil) + +// accountOption allows management of the mutation configuration using functional options. +type accountOption func(*AccountMutation) + +// newAccountMutation creates new mutation for the Account entity. +func newAccountMutation(c config, op Op, opts ...accountOption) *AccountMutation { + m := &AccountMutation{ + config: c, + op: op, + typ: TypeAccount, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withAccountID sets the ID field of the mutation. +func withAccountID(id string) accountOption { + return func(m *AccountMutation) { + var ( + err error + once sync.Once + value *Account + ) + m.oldValue = func(ctx context.Context) (*Account, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Account.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withAccount sets the old Account of the mutation. +func withAccount(node *Account) accountOption { + return func(m *AccountMutation) { + m.oldValue = func(context.Context) (*Account, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m AccountMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m AccountMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of Account entities. +func (m *AccountMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *AccountMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *AccountMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().Account.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *AccountMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *AccountMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the Account entity. +// If the Account object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *AccountMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *AccountMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *AccountMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *AccountMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *AccountMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the Account entity. +// If the Account object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *AccountMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *AccountMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *AccountMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (m *AccountMutation) SetLastBattleTime(i int) { + m.last_battle_time = &i + m.addlast_battle_time = nil +} + +// LastBattleTime returns the value of the "last_battle_time" field in the mutation. +func (m *AccountMutation) LastBattleTime() (r int, exists bool) { + v := m.last_battle_time + if v == nil { + return + } + return *v, true +} + +// OldLastBattleTime returns the old "last_battle_time" field's value of the Account entity. +// If the Account object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountMutation) OldLastBattleTime(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLastBattleTime requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLastBattleTime: %w", err) + } + return oldValue.LastBattleTime, nil +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (m *AccountMutation) AddLastBattleTime(i int) { + if m.addlast_battle_time != nil { + *m.addlast_battle_time += i + } else { + m.addlast_battle_time = &i + } +} + +// AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. +func (m *AccountMutation) AddedLastBattleTime() (r int, exists bool) { + v := m.addlast_battle_time + if v == nil { + return + } + return *v, true +} + +// ResetLastBattleTime resets all changes to the "last_battle_time" field. +func (m *AccountMutation) ResetLastBattleTime() { + m.last_battle_time = nil + m.addlast_battle_time = nil +} + +// SetAccountCreatedAt sets the "account_created_at" field. +func (m *AccountMutation) SetAccountCreatedAt(i int) { + m.account_created_at = &i + m.addaccount_created_at = nil +} + +// AccountCreatedAt returns the value of the "account_created_at" field in the mutation. +func (m *AccountMutation) AccountCreatedAt() (r int, exists bool) { + v := m.account_created_at + if v == nil { + return + } + return *v, true +} + +// OldAccountCreatedAt returns the old "account_created_at" field's value of the Account entity. +// If the Account object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountMutation) OldAccountCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAccountCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAccountCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAccountCreatedAt: %w", err) + } + return oldValue.AccountCreatedAt, nil +} + +// AddAccountCreatedAt adds i to the "account_created_at" field. +func (m *AccountMutation) AddAccountCreatedAt(i int) { + if m.addaccount_created_at != nil { + *m.addaccount_created_at += i + } else { + m.addaccount_created_at = &i + } +} + +// AddedAccountCreatedAt returns the value that was added to the "account_created_at" field in this mutation. +func (m *AccountMutation) AddedAccountCreatedAt() (r int, exists bool) { + v := m.addaccount_created_at + if v == nil { + return + } + return *v, true +} + +// ResetAccountCreatedAt resets all changes to the "account_created_at" field. +func (m *AccountMutation) ResetAccountCreatedAt() { + m.account_created_at = nil + m.addaccount_created_at = nil +} + +// SetRealm sets the "realm" field. +func (m *AccountMutation) SetRealm(s string) { + m.realm = &s +} + +// Realm returns the value of the "realm" field in the mutation. +func (m *AccountMutation) Realm() (r string, exists bool) { + v := m.realm + if v == nil { + return + } + return *v, true +} + +// OldRealm returns the old "realm" field's value of the Account entity. +// If the Account object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountMutation) OldRealm(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRealm is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRealm requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRealm: %w", err) + } + return oldValue.Realm, nil +} + +// ResetRealm resets all changes to the "realm" field. +func (m *AccountMutation) ResetRealm() { + m.realm = nil +} + +// SetNickname sets the "nickname" field. +func (m *AccountMutation) SetNickname(s string) { + m.nickname = &s +} + +// Nickname returns the value of the "nickname" field in the mutation. +func (m *AccountMutation) Nickname() (r string, exists bool) { + v := m.nickname + if v == nil { + return + } + return *v, true +} + +// OldNickname returns the old "nickname" field's value of the Account entity. +// If the Account object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountMutation) OldNickname(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldNickname is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldNickname requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldNickname: %w", err) + } + return oldValue.Nickname, nil +} + +// ResetNickname resets all changes to the "nickname" field. +func (m *AccountMutation) ResetNickname() { + m.nickname = nil +} + +// SetPrivate sets the "private" field. +func (m *AccountMutation) SetPrivate(b bool) { + m.private = &b +} + +// Private returns the value of the "private" field in the mutation. +func (m *AccountMutation) Private() (r bool, exists bool) { + v := m.private + if v == nil { + return + } + return *v, true +} + +// OldPrivate returns the old "private" field's value of the Account entity. +// If the Account object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountMutation) OldPrivate(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPrivate is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPrivate requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPrivate: %w", err) + } + return oldValue.Private, nil +} + +// ResetPrivate resets all changes to the "private" field. +func (m *AccountMutation) ResetPrivate() { + m.private = nil +} + +// SetClanID sets the "clan_id" field. +func (m *AccountMutation) SetClanID(s string) { + m.clan = &s +} + +// ClanID returns the value of the "clan_id" field in the mutation. +func (m *AccountMutation) ClanID() (r string, exists bool) { + v := m.clan + if v == nil { + return + } + return *v, true +} + +// OldClanID returns the old "clan_id" field's value of the Account entity. +// If the Account object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountMutation) OldClanID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldClanID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldClanID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldClanID: %w", err) + } + return oldValue.ClanID, nil +} + +// ClearClanID clears the value of the "clan_id" field. +func (m *AccountMutation) ClearClanID() { + m.clan = nil + m.clearedFields[account.FieldClanID] = struct{}{} +} + +// ClanIDCleared returns if the "clan_id" field was cleared in this mutation. +func (m *AccountMutation) ClanIDCleared() bool { + _, ok := m.clearedFields[account.FieldClanID] + return ok +} + +// ResetClanID resets all changes to the "clan_id" field. +func (m *AccountMutation) ResetClanID() { + m.clan = nil + delete(m.clearedFields, account.FieldClanID) +} + +// ClearClan clears the "clan" edge to the Clan entity. +func (m *AccountMutation) ClearClan() { + m.clearedclan = true + m.clearedFields[account.FieldClanID] = struct{}{} +} + +// ClanCleared reports if the "clan" edge to the Clan entity was cleared. +func (m *AccountMutation) ClanCleared() bool { + return m.ClanIDCleared() || m.clearedclan +} + +// ClanIDs returns the "clan" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// ClanID instead. It exists only for internal usage by the builders. +func (m *AccountMutation) ClanIDs() (ids []string) { + if id := m.clan; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetClan resets all changes to the "clan" edge. +func (m *AccountMutation) ResetClan() { + m.clan = nil + m.clearedclan = false +} + +// AddSnapshotIDs adds the "snapshots" edge to the AccountSnapshot entity by ids. +func (m *AccountMutation) AddSnapshotIDs(ids ...string) { + if m.snapshots == nil { + m.snapshots = make(map[string]struct{}) + } + for i := range ids { + m.snapshots[ids[i]] = struct{}{} + } +} + +// ClearSnapshots clears the "snapshots" edge to the AccountSnapshot entity. +func (m *AccountMutation) ClearSnapshots() { + m.clearedsnapshots = true +} + +// SnapshotsCleared reports if the "snapshots" edge to the AccountSnapshot entity was cleared. +func (m *AccountMutation) SnapshotsCleared() bool { + return m.clearedsnapshots +} + +// RemoveSnapshotIDs removes the "snapshots" edge to the AccountSnapshot entity by IDs. +func (m *AccountMutation) RemoveSnapshotIDs(ids ...string) { + if m.removedsnapshots == nil { + m.removedsnapshots = make(map[string]struct{}) + } + for i := range ids { + delete(m.snapshots, ids[i]) + m.removedsnapshots[ids[i]] = struct{}{} + } +} + +// RemovedSnapshots returns the removed IDs of the "snapshots" edge to the AccountSnapshot entity. +func (m *AccountMutation) RemovedSnapshotsIDs() (ids []string) { + for id := range m.removedsnapshots { + ids = append(ids, id) + } + return +} + +// SnapshotsIDs returns the "snapshots" edge IDs in the mutation. +func (m *AccountMutation) SnapshotsIDs() (ids []string) { + for id := range m.snapshots { + ids = append(ids, id) + } + return +} + +// ResetSnapshots resets all changes to the "snapshots" edge. +func (m *AccountMutation) ResetSnapshots() { + m.snapshots = nil + m.clearedsnapshots = false + m.removedsnapshots = nil +} + +// AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by ids. +func (m *AccountMutation) AddVehicleSnapshotIDs(ids ...string) { + if m.vehicle_snapshots == nil { + m.vehicle_snapshots = make(map[string]struct{}) + } + for i := range ids { + m.vehicle_snapshots[ids[i]] = struct{}{} + } +} + +// ClearVehicleSnapshots clears the "vehicle_snapshots" edge to the VehicleSnapshot entity. +func (m *AccountMutation) ClearVehicleSnapshots() { + m.clearedvehicle_snapshots = true +} + +// VehicleSnapshotsCleared reports if the "vehicle_snapshots" edge to the VehicleSnapshot entity was cleared. +func (m *AccountMutation) VehicleSnapshotsCleared() bool { + return m.clearedvehicle_snapshots +} + +// RemoveVehicleSnapshotIDs removes the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. +func (m *AccountMutation) RemoveVehicleSnapshotIDs(ids ...string) { + if m.removedvehicle_snapshots == nil { + m.removedvehicle_snapshots = make(map[string]struct{}) + } + for i := range ids { + delete(m.vehicle_snapshots, ids[i]) + m.removedvehicle_snapshots[ids[i]] = struct{}{} + } +} + +// RemovedVehicleSnapshots returns the removed IDs of the "vehicle_snapshots" edge to the VehicleSnapshot entity. +func (m *AccountMutation) RemovedVehicleSnapshotsIDs() (ids []string) { + for id := range m.removedvehicle_snapshots { + ids = append(ids, id) + } + return +} + +// VehicleSnapshotsIDs returns the "vehicle_snapshots" edge IDs in the mutation. +func (m *AccountMutation) VehicleSnapshotsIDs() (ids []string) { + for id := range m.vehicle_snapshots { + ids = append(ids, id) + } + return +} + +// ResetVehicleSnapshots resets all changes to the "vehicle_snapshots" edge. +func (m *AccountMutation) ResetVehicleSnapshots() { + m.vehicle_snapshots = nil + m.clearedvehicle_snapshots = false + m.removedvehicle_snapshots = nil +} + +// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by ids. +func (m *AccountMutation) AddAchievementSnapshotIDs(ids ...string) { + if m.achievement_snapshots == nil { + m.achievement_snapshots = make(map[string]struct{}) + } + for i := range ids { + m.achievement_snapshots[ids[i]] = struct{}{} + } +} + +// ClearAchievementSnapshots clears the "achievement_snapshots" edge to the AchievementsSnapshot entity. +func (m *AccountMutation) ClearAchievementSnapshots() { + m.clearedachievement_snapshots = true +} + +// AchievementSnapshotsCleared reports if the "achievement_snapshots" edge to the AchievementsSnapshot entity was cleared. +func (m *AccountMutation) AchievementSnapshotsCleared() bool { + return m.clearedachievement_snapshots +} + +// RemoveAchievementSnapshotIDs removes the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. +func (m *AccountMutation) RemoveAchievementSnapshotIDs(ids ...string) { + if m.removedachievement_snapshots == nil { + m.removedachievement_snapshots = make(map[string]struct{}) + } + for i := range ids { + delete(m.achievement_snapshots, ids[i]) + m.removedachievement_snapshots[ids[i]] = struct{}{} + } +} + +// RemovedAchievementSnapshots returns the removed IDs of the "achievement_snapshots" edge to the AchievementsSnapshot entity. +func (m *AccountMutation) RemovedAchievementSnapshotsIDs() (ids []string) { + for id := range m.removedachievement_snapshots { + ids = append(ids, id) + } + return +} + +// AchievementSnapshotsIDs returns the "achievement_snapshots" edge IDs in the mutation. +func (m *AccountMutation) AchievementSnapshotsIDs() (ids []string) { + for id := range m.achievement_snapshots { + ids = append(ids, id) + } + return +} + +// ResetAchievementSnapshots resets all changes to the "achievement_snapshots" edge. +func (m *AccountMutation) ResetAchievementSnapshots() { + m.achievement_snapshots = nil + m.clearedachievement_snapshots = false + m.removedachievement_snapshots = nil +} + +// Where appends a list predicates to the AccountMutation builder. +func (m *AccountMutation) Where(ps ...predicate.Account) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the AccountMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *AccountMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Account, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *AccountMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *AccountMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Account). +func (m *AccountMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *AccountMutation) Fields() []string { + fields := make([]string, 0, 8) + if m.created_at != nil { + fields = append(fields, account.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, account.FieldUpdatedAt) + } + if m.last_battle_time != nil { + fields = append(fields, account.FieldLastBattleTime) + } + if m.account_created_at != nil { + fields = append(fields, account.FieldAccountCreatedAt) + } + if m.realm != nil { + fields = append(fields, account.FieldRealm) + } + if m.nickname != nil { + fields = append(fields, account.FieldNickname) + } + if m.private != nil { + fields = append(fields, account.FieldPrivate) + } + if m.clan != nil { + fields = append(fields, account.FieldClanID) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *AccountMutation) Field(name string) (ent.Value, bool) { + switch name { + case account.FieldCreatedAt: + return m.CreatedAt() + case account.FieldUpdatedAt: + return m.UpdatedAt() + case account.FieldLastBattleTime: + return m.LastBattleTime() + case account.FieldAccountCreatedAt: + return m.AccountCreatedAt() + case account.FieldRealm: + return m.Realm() + case account.FieldNickname: + return m.Nickname() + case account.FieldPrivate: + return m.Private() + case account.FieldClanID: + return m.ClanID() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *AccountMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case account.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case account.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case account.FieldLastBattleTime: + return m.OldLastBattleTime(ctx) + case account.FieldAccountCreatedAt: + return m.OldAccountCreatedAt(ctx) + case account.FieldRealm: + return m.OldRealm(ctx) + case account.FieldNickname: + return m.OldNickname(ctx) + case account.FieldPrivate: + return m.OldPrivate(ctx) + case account.FieldClanID: + return m.OldClanID(ctx) + } + return nil, fmt.Errorf("unknown Account field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AccountMutation) SetField(name string, value ent.Value) error { + switch name { + case account.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case account.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case account.FieldLastBattleTime: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLastBattleTime(v) + return nil + case account.FieldAccountCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAccountCreatedAt(v) + return nil + case account.FieldRealm: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRealm(v) + return nil + case account.FieldNickname: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetNickname(v) + return nil + case account.FieldPrivate: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPrivate(v) + return nil + case account.FieldClanID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetClanID(v) + return nil + } + return fmt.Errorf("unknown Account field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *AccountMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, account.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, account.FieldUpdatedAt) + } + if m.addlast_battle_time != nil { + fields = append(fields, account.FieldLastBattleTime) + } + if m.addaccount_created_at != nil { + fields = append(fields, account.FieldAccountCreatedAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *AccountMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case account.FieldCreatedAt: + return m.AddedCreatedAt() + case account.FieldUpdatedAt: + return m.AddedUpdatedAt() + case account.FieldLastBattleTime: + return m.AddedLastBattleTime() + case account.FieldAccountCreatedAt: + return m.AddedAccountCreatedAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AccountMutation) AddField(name string, value ent.Value) error { + switch name { + case account.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case account.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + case account.FieldLastBattleTime: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddLastBattleTime(v) + return nil + case account.FieldAccountCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddAccountCreatedAt(v) + return nil + } + return fmt.Errorf("unknown Account numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *AccountMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(account.FieldClanID) { + fields = append(fields, account.FieldClanID) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *AccountMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *AccountMutation) ClearField(name string) error { + switch name { + case account.FieldClanID: + m.ClearClanID() + return nil + } + return fmt.Errorf("unknown Account nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *AccountMutation) ResetField(name string) error { + switch name { + case account.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case account.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case account.FieldLastBattleTime: + m.ResetLastBattleTime() + return nil + case account.FieldAccountCreatedAt: + m.ResetAccountCreatedAt() + return nil + case account.FieldRealm: + m.ResetRealm() + return nil + case account.FieldNickname: + m.ResetNickname() + return nil + case account.FieldPrivate: + m.ResetPrivate() + return nil + case account.FieldClanID: + m.ResetClanID() + return nil + } + return fmt.Errorf("unknown Account field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *AccountMutation) AddedEdges() []string { + edges := make([]string, 0, 4) + if m.clan != nil { + edges = append(edges, account.EdgeClan) + } + if m.snapshots != nil { + edges = append(edges, account.EdgeSnapshots) + } + if m.vehicle_snapshots != nil { + edges = append(edges, account.EdgeVehicleSnapshots) + } + if m.achievement_snapshots != nil { + edges = append(edges, account.EdgeAchievementSnapshots) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *AccountMutation) AddedIDs(name string) []ent.Value { + switch name { + case account.EdgeClan: + if id := m.clan; id != nil { + return []ent.Value{*id} + } + case account.EdgeSnapshots: + ids := make([]ent.Value, 0, len(m.snapshots)) + for id := range m.snapshots { + ids = append(ids, id) + } + return ids + case account.EdgeVehicleSnapshots: + ids := make([]ent.Value, 0, len(m.vehicle_snapshots)) + for id := range m.vehicle_snapshots { + ids = append(ids, id) + } + return ids + case account.EdgeAchievementSnapshots: + ids := make([]ent.Value, 0, len(m.achievement_snapshots)) + for id := range m.achievement_snapshots { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *AccountMutation) RemovedEdges() []string { + edges := make([]string, 0, 4) + if m.removedsnapshots != nil { + edges = append(edges, account.EdgeSnapshots) + } + if m.removedvehicle_snapshots != nil { + edges = append(edges, account.EdgeVehicleSnapshots) + } + if m.removedachievement_snapshots != nil { + edges = append(edges, account.EdgeAchievementSnapshots) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *AccountMutation) RemovedIDs(name string) []ent.Value { + switch name { + case account.EdgeSnapshots: + ids := make([]ent.Value, 0, len(m.removedsnapshots)) + for id := range m.removedsnapshots { + ids = append(ids, id) + } + return ids + case account.EdgeVehicleSnapshots: + ids := make([]ent.Value, 0, len(m.removedvehicle_snapshots)) + for id := range m.removedvehicle_snapshots { + ids = append(ids, id) + } + return ids + case account.EdgeAchievementSnapshots: + ids := make([]ent.Value, 0, len(m.removedachievement_snapshots)) + for id := range m.removedachievement_snapshots { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *AccountMutation) ClearedEdges() []string { + edges := make([]string, 0, 4) + if m.clearedclan { + edges = append(edges, account.EdgeClan) + } + if m.clearedsnapshots { + edges = append(edges, account.EdgeSnapshots) + } + if m.clearedvehicle_snapshots { + edges = append(edges, account.EdgeVehicleSnapshots) + } + if m.clearedachievement_snapshots { + edges = append(edges, account.EdgeAchievementSnapshots) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *AccountMutation) EdgeCleared(name string) bool { + switch name { + case account.EdgeClan: + return m.clearedclan + case account.EdgeSnapshots: + return m.clearedsnapshots + case account.EdgeVehicleSnapshots: + return m.clearedvehicle_snapshots + case account.EdgeAchievementSnapshots: + return m.clearedachievement_snapshots + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *AccountMutation) ClearEdge(name string) error { + switch name { + case account.EdgeClan: + m.ClearClan() + return nil + } + return fmt.Errorf("unknown Account unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *AccountMutation) ResetEdge(name string) error { + switch name { + case account.EdgeClan: + m.ResetClan() + return nil + case account.EdgeSnapshots: + m.ResetSnapshots() + return nil + case account.EdgeVehicleSnapshots: + m.ResetVehicleSnapshots() + return nil + case account.EdgeAchievementSnapshots: + m.ResetAchievementSnapshots() + return nil + } + return fmt.Errorf("unknown Account edge %s", name) +} + +// AccountSnapshotMutation represents an operation that mutates the AccountSnapshot nodes in the graph. +type AccountSnapshotMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + _type *models.SnapshotType + last_battle_time *int + addlast_battle_time *int + reference_id *string + rating_battles *int + addrating_battles *int + rating_frame *frame.StatsFrame + regular_battles *int + addregular_battles *int + regular_frame *frame.StatsFrame + clearedFields map[string]struct{} + account *string + clearedaccount bool + done bool + oldValue func(context.Context) (*AccountSnapshot, error) + predicates []predicate.AccountSnapshot +} + +var _ ent.Mutation = (*AccountSnapshotMutation)(nil) + +// accountsnapshotOption allows management of the mutation configuration using functional options. +type accountsnapshotOption func(*AccountSnapshotMutation) + +// newAccountSnapshotMutation creates new mutation for the AccountSnapshot entity. +func newAccountSnapshotMutation(c config, op Op, opts ...accountsnapshotOption) *AccountSnapshotMutation { + m := &AccountSnapshotMutation{ + config: c, + op: op, + typ: TypeAccountSnapshot, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withAccountSnapshotID sets the ID field of the mutation. +func withAccountSnapshotID(id string) accountsnapshotOption { + return func(m *AccountSnapshotMutation) { + var ( + err error + once sync.Once + value *AccountSnapshot + ) + m.oldValue = func(ctx context.Context) (*AccountSnapshot, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().AccountSnapshot.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withAccountSnapshot sets the old AccountSnapshot of the mutation. +func withAccountSnapshot(node *AccountSnapshot) accountsnapshotOption { + return func(m *AccountSnapshotMutation) { + m.oldValue = func(context.Context) (*AccountSnapshot, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m AccountSnapshotMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m AccountSnapshotMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of AccountSnapshot entities. +func (m *AccountSnapshotMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *AccountSnapshotMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *AccountSnapshotMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().AccountSnapshot.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *AccountSnapshotMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *AccountSnapshotMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *AccountSnapshotMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *AccountSnapshotMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *AccountSnapshotMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *AccountSnapshotMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *AccountSnapshotMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *AccountSnapshotMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *AccountSnapshotMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *AccountSnapshotMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetType sets the "type" field. +func (m *AccountSnapshotMutation) SetType(mt models.SnapshotType) { + m._type = &mt +} + +// GetType returns the value of the "type" field in the mutation. +func (m *AccountSnapshotMutation) GetType() (r models.SnapshotType, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldType(ctx context.Context) (v models.SnapshotType, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ResetType resets all changes to the "type" field. +func (m *AccountSnapshotMutation) ResetType() { + m._type = nil +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (m *AccountSnapshotMutation) SetLastBattleTime(i int) { + m.last_battle_time = &i + m.addlast_battle_time = nil +} + +// LastBattleTime returns the value of the "last_battle_time" field in the mutation. +func (m *AccountSnapshotMutation) LastBattleTime() (r int, exists bool) { + v := m.last_battle_time + if v == nil { + return + } + return *v, true +} + +// OldLastBattleTime returns the old "last_battle_time" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLastBattleTime requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLastBattleTime: %w", err) + } + return oldValue.LastBattleTime, nil +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (m *AccountSnapshotMutation) AddLastBattleTime(i int) { + if m.addlast_battle_time != nil { + *m.addlast_battle_time += i + } else { + m.addlast_battle_time = &i + } +} + +// AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. +func (m *AccountSnapshotMutation) AddedLastBattleTime() (r int, exists bool) { + v := m.addlast_battle_time + if v == nil { + return + } + return *v, true +} + +// ResetLastBattleTime resets all changes to the "last_battle_time" field. +func (m *AccountSnapshotMutation) ResetLastBattleTime() { + m.last_battle_time = nil + m.addlast_battle_time = nil +} + +// SetAccountID sets the "account_id" field. +func (m *AccountSnapshotMutation) SetAccountID(s string) { + m.account = &s +} + +// AccountID returns the value of the "account_id" field in the mutation. +func (m *AccountSnapshotMutation) AccountID() (r string, exists bool) { + v := m.account + if v == nil { + return + } + return *v, true +} + +// OldAccountID returns the old "account_id" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldAccountID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAccountID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAccountID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAccountID: %w", err) + } + return oldValue.AccountID, nil +} + +// ResetAccountID resets all changes to the "account_id" field. +func (m *AccountSnapshotMutation) ResetAccountID() { + m.account = nil +} + +// SetReferenceID sets the "reference_id" field. +func (m *AccountSnapshotMutation) SetReferenceID(s string) { + m.reference_id = &s +} + +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *AccountSnapshotMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id + if v == nil { + return + } + return *v, true +} + +// OldReferenceID returns the old "reference_id" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldReferenceID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferenceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + } + return oldValue.ReferenceID, nil +} + +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *AccountSnapshotMutation) ResetReferenceID() { + m.reference_id = nil +} + +// SetRatingBattles sets the "rating_battles" field. +func (m *AccountSnapshotMutation) SetRatingBattles(i int) { + m.rating_battles = &i + m.addrating_battles = nil +} + +// RatingBattles returns the value of the "rating_battles" field in the mutation. +func (m *AccountSnapshotMutation) RatingBattles() (r int, exists bool) { + v := m.rating_battles + if v == nil { + return + } + return *v, true +} + +// OldRatingBattles returns the old "rating_battles" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldRatingBattles(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRatingBattles is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRatingBattles requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRatingBattles: %w", err) + } + return oldValue.RatingBattles, nil +} + +// AddRatingBattles adds i to the "rating_battles" field. +func (m *AccountSnapshotMutation) AddRatingBattles(i int) { + if m.addrating_battles != nil { + *m.addrating_battles += i + } else { + m.addrating_battles = &i + } +} + +// AddedRatingBattles returns the value that was added to the "rating_battles" field in this mutation. +func (m *AccountSnapshotMutation) AddedRatingBattles() (r int, exists bool) { + v := m.addrating_battles + if v == nil { + return + } + return *v, true +} + +// ResetRatingBattles resets all changes to the "rating_battles" field. +func (m *AccountSnapshotMutation) ResetRatingBattles() { + m.rating_battles = nil + m.addrating_battles = nil +} + +// SetRatingFrame sets the "rating_frame" field. +func (m *AccountSnapshotMutation) SetRatingFrame(ff frame.StatsFrame) { + m.rating_frame = &ff +} + +// RatingFrame returns the value of the "rating_frame" field in the mutation. +func (m *AccountSnapshotMutation) RatingFrame() (r frame.StatsFrame, exists bool) { + v := m.rating_frame + if v == nil { + return + } + return *v, true +} + +// OldRatingFrame returns the old "rating_frame" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldRatingFrame(ctx context.Context) (v frame.StatsFrame, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRatingFrame is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRatingFrame requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRatingFrame: %w", err) + } + return oldValue.RatingFrame, nil +} + +// ResetRatingFrame resets all changes to the "rating_frame" field. +func (m *AccountSnapshotMutation) ResetRatingFrame() { + m.rating_frame = nil +} + +// SetRegularBattles sets the "regular_battles" field. +func (m *AccountSnapshotMutation) SetRegularBattles(i int) { + m.regular_battles = &i + m.addregular_battles = nil +} + +// RegularBattles returns the value of the "regular_battles" field in the mutation. +func (m *AccountSnapshotMutation) RegularBattles() (r int, exists bool) { + v := m.regular_battles + if v == nil { + return + } + return *v, true +} + +// OldRegularBattles returns the old "regular_battles" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldRegularBattles(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRegularBattles is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRegularBattles requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRegularBattles: %w", err) + } + return oldValue.RegularBattles, nil +} + +// AddRegularBattles adds i to the "regular_battles" field. +func (m *AccountSnapshotMutation) AddRegularBattles(i int) { + if m.addregular_battles != nil { + *m.addregular_battles += i + } else { + m.addregular_battles = &i + } +} + +// AddedRegularBattles returns the value that was added to the "regular_battles" field in this mutation. +func (m *AccountSnapshotMutation) AddedRegularBattles() (r int, exists bool) { + v := m.addregular_battles + if v == nil { + return + } + return *v, true +} + +// ResetRegularBattles resets all changes to the "regular_battles" field. +func (m *AccountSnapshotMutation) ResetRegularBattles() { + m.regular_battles = nil + m.addregular_battles = nil +} + +// SetRegularFrame sets the "regular_frame" field. +func (m *AccountSnapshotMutation) SetRegularFrame(ff frame.StatsFrame) { + m.regular_frame = &ff +} + +// RegularFrame returns the value of the "regular_frame" field in the mutation. +func (m *AccountSnapshotMutation) RegularFrame() (r frame.StatsFrame, exists bool) { + v := m.regular_frame + if v == nil { + return + } + return *v, true +} + +// OldRegularFrame returns the old "regular_frame" field's value of the AccountSnapshot entity. +// If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AccountSnapshotMutation) OldRegularFrame(ctx context.Context) (v frame.StatsFrame, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRegularFrame is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRegularFrame requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRegularFrame: %w", err) + } + return oldValue.RegularFrame, nil +} + +// ResetRegularFrame resets all changes to the "regular_frame" field. +func (m *AccountSnapshotMutation) ResetRegularFrame() { + m.regular_frame = nil +} + +// ClearAccount clears the "account" edge to the Account entity. +func (m *AccountSnapshotMutation) ClearAccount() { + m.clearedaccount = true + m.clearedFields[accountsnapshot.FieldAccountID] = struct{}{} +} + +// AccountCleared reports if the "account" edge to the Account entity was cleared. +func (m *AccountSnapshotMutation) AccountCleared() bool { + return m.clearedaccount +} + +// AccountIDs returns the "account" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// AccountID instead. It exists only for internal usage by the builders. +func (m *AccountSnapshotMutation) AccountIDs() (ids []string) { + if id := m.account; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetAccount resets all changes to the "account" edge. +func (m *AccountSnapshotMutation) ResetAccount() { + m.account = nil + m.clearedaccount = false +} + +// Where appends a list predicates to the AccountSnapshotMutation builder. +func (m *AccountSnapshotMutation) Where(ps ...predicate.AccountSnapshot) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the AccountSnapshotMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *AccountSnapshotMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.AccountSnapshot, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *AccountSnapshotMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *AccountSnapshotMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (AccountSnapshot). +func (m *AccountSnapshotMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *AccountSnapshotMutation) Fields() []string { + fields := make([]string, 0, 10) + if m.created_at != nil { + fields = append(fields, accountsnapshot.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, accountsnapshot.FieldUpdatedAt) + } + if m._type != nil { + fields = append(fields, accountsnapshot.FieldType) + } + if m.last_battle_time != nil { + fields = append(fields, accountsnapshot.FieldLastBattleTime) + } + if m.account != nil { + fields = append(fields, accountsnapshot.FieldAccountID) + } + if m.reference_id != nil { + fields = append(fields, accountsnapshot.FieldReferenceID) + } + if m.rating_battles != nil { + fields = append(fields, accountsnapshot.FieldRatingBattles) + } + if m.rating_frame != nil { + fields = append(fields, accountsnapshot.FieldRatingFrame) + } + if m.regular_battles != nil { + fields = append(fields, accountsnapshot.FieldRegularBattles) + } + if m.regular_frame != nil { + fields = append(fields, accountsnapshot.FieldRegularFrame) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *AccountSnapshotMutation) Field(name string) (ent.Value, bool) { + switch name { + case accountsnapshot.FieldCreatedAt: + return m.CreatedAt() + case accountsnapshot.FieldUpdatedAt: + return m.UpdatedAt() + case accountsnapshot.FieldType: + return m.GetType() + case accountsnapshot.FieldLastBattleTime: + return m.LastBattleTime() + case accountsnapshot.FieldAccountID: + return m.AccountID() + case accountsnapshot.FieldReferenceID: + return m.ReferenceID() + case accountsnapshot.FieldRatingBattles: + return m.RatingBattles() + case accountsnapshot.FieldRatingFrame: + return m.RatingFrame() + case accountsnapshot.FieldRegularBattles: + return m.RegularBattles() + case accountsnapshot.FieldRegularFrame: + return m.RegularFrame() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *AccountSnapshotMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case accountsnapshot.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case accountsnapshot.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case accountsnapshot.FieldType: + return m.OldType(ctx) + case accountsnapshot.FieldLastBattleTime: + return m.OldLastBattleTime(ctx) + case accountsnapshot.FieldAccountID: + return m.OldAccountID(ctx) + case accountsnapshot.FieldReferenceID: + return m.OldReferenceID(ctx) + case accountsnapshot.FieldRatingBattles: + return m.OldRatingBattles(ctx) + case accountsnapshot.FieldRatingFrame: + return m.OldRatingFrame(ctx) + case accountsnapshot.FieldRegularBattles: + return m.OldRegularBattles(ctx) + case accountsnapshot.FieldRegularFrame: + return m.OldRegularFrame(ctx) + } + return nil, fmt.Errorf("unknown AccountSnapshot field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AccountSnapshotMutation) SetField(name string, value ent.Value) error { + switch name { + case accountsnapshot.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case accountsnapshot.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case accountsnapshot.FieldType: + v, ok := value.(models.SnapshotType) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case accountsnapshot.FieldLastBattleTime: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLastBattleTime(v) + return nil + case accountsnapshot.FieldAccountID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAccountID(v) + return nil + case accountsnapshot.FieldReferenceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferenceID(v) + return nil + case accountsnapshot.FieldRatingBattles: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRatingBattles(v) + return nil + case accountsnapshot.FieldRatingFrame: + v, ok := value.(frame.StatsFrame) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRatingFrame(v) + return nil + case accountsnapshot.FieldRegularBattles: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRegularBattles(v) + return nil + case accountsnapshot.FieldRegularFrame: + v, ok := value.(frame.StatsFrame) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRegularFrame(v) + return nil + } + return fmt.Errorf("unknown AccountSnapshot field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *AccountSnapshotMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, accountsnapshot.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, accountsnapshot.FieldUpdatedAt) + } + if m.addlast_battle_time != nil { + fields = append(fields, accountsnapshot.FieldLastBattleTime) + } + if m.addrating_battles != nil { + fields = append(fields, accountsnapshot.FieldRatingBattles) + } + if m.addregular_battles != nil { + fields = append(fields, accountsnapshot.FieldRegularBattles) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *AccountSnapshotMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case accountsnapshot.FieldCreatedAt: + return m.AddedCreatedAt() + case accountsnapshot.FieldUpdatedAt: + return m.AddedUpdatedAt() + case accountsnapshot.FieldLastBattleTime: + return m.AddedLastBattleTime() + case accountsnapshot.FieldRatingBattles: + return m.AddedRatingBattles() + case accountsnapshot.FieldRegularBattles: + return m.AddedRegularBattles() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AccountSnapshotMutation) AddField(name string, value ent.Value) error { + switch name { + case accountsnapshot.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case accountsnapshot.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + case accountsnapshot.FieldLastBattleTime: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddLastBattleTime(v) + return nil + case accountsnapshot.FieldRatingBattles: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddRatingBattles(v) + return nil + case accountsnapshot.FieldRegularBattles: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddRegularBattles(v) + return nil + } + return fmt.Errorf("unknown AccountSnapshot numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *AccountSnapshotMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *AccountSnapshotMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *AccountSnapshotMutation) ClearField(name string) error { + return fmt.Errorf("unknown AccountSnapshot nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *AccountSnapshotMutation) ResetField(name string) error { + switch name { + case accountsnapshot.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case accountsnapshot.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case accountsnapshot.FieldType: + m.ResetType() + return nil + case accountsnapshot.FieldLastBattleTime: + m.ResetLastBattleTime() + return nil + case accountsnapshot.FieldAccountID: + m.ResetAccountID() + return nil + case accountsnapshot.FieldReferenceID: + m.ResetReferenceID() + return nil + case accountsnapshot.FieldRatingBattles: + m.ResetRatingBattles() + return nil + case accountsnapshot.FieldRatingFrame: + m.ResetRatingFrame() + return nil + case accountsnapshot.FieldRegularBattles: + m.ResetRegularBattles() + return nil + case accountsnapshot.FieldRegularFrame: + m.ResetRegularFrame() + return nil + } + return fmt.Errorf("unknown AccountSnapshot field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *AccountSnapshotMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.account != nil { + edges = append(edges, accountsnapshot.EdgeAccount) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *AccountSnapshotMutation) AddedIDs(name string) []ent.Value { + switch name { + case accountsnapshot.EdgeAccount: + if id := m.account; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *AccountSnapshotMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *AccountSnapshotMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *AccountSnapshotMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedaccount { + edges = append(edges, accountsnapshot.EdgeAccount) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *AccountSnapshotMutation) EdgeCleared(name string) bool { + switch name { + case accountsnapshot.EdgeAccount: + return m.clearedaccount + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *AccountSnapshotMutation) ClearEdge(name string) error { + switch name { + case accountsnapshot.EdgeAccount: + m.ClearAccount() + return nil + } + return fmt.Errorf("unknown AccountSnapshot unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *AccountSnapshotMutation) ResetEdge(name string) error { + switch name { + case accountsnapshot.EdgeAccount: + m.ResetAccount() + return nil + } + return fmt.Errorf("unknown AccountSnapshot edge %s", name) +} + +// AchievementsSnapshotMutation represents an operation that mutates the AchievementsSnapshot nodes in the graph. +type AchievementsSnapshotMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + _type *models.SnapshotType + reference_id *string + battles *int + addbattles *int + last_battle_time *int + addlast_battle_time *int + data *types.AchievementsFrame + clearedFields map[string]struct{} + account *string + clearedaccount bool + done bool + oldValue func(context.Context) (*AchievementsSnapshot, error) + predicates []predicate.AchievementsSnapshot +} + +var _ ent.Mutation = (*AchievementsSnapshotMutation)(nil) + +// achievementssnapshotOption allows management of the mutation configuration using functional options. +type achievementssnapshotOption func(*AchievementsSnapshotMutation) + +// newAchievementsSnapshotMutation creates new mutation for the AchievementsSnapshot entity. +func newAchievementsSnapshotMutation(c config, op Op, opts ...achievementssnapshotOption) *AchievementsSnapshotMutation { + m := &AchievementsSnapshotMutation{ + config: c, + op: op, + typ: TypeAchievementsSnapshot, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withAchievementsSnapshotID sets the ID field of the mutation. +func withAchievementsSnapshotID(id string) achievementssnapshotOption { + return func(m *AchievementsSnapshotMutation) { + var ( + err error + once sync.Once + value *AchievementsSnapshot + ) + m.oldValue = func(ctx context.Context) (*AchievementsSnapshot, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().AchievementsSnapshot.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withAchievementsSnapshot sets the old AchievementsSnapshot of the mutation. +func withAchievementsSnapshot(node *AchievementsSnapshot) achievementssnapshotOption { + return func(m *AchievementsSnapshotMutation) { + m.oldValue = func(context.Context) (*AchievementsSnapshot, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m AchievementsSnapshotMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m AchievementsSnapshotMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of AchievementsSnapshot entities. +func (m *AchievementsSnapshotMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *AchievementsSnapshotMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *AchievementsSnapshotMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().AchievementsSnapshot.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *AchievementsSnapshotMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *AchievementsSnapshotMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the AchievementsSnapshot entity. +// If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AchievementsSnapshotMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *AchievementsSnapshotMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *AchievementsSnapshotMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *AchievementsSnapshotMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *AchievementsSnapshotMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *AchievementsSnapshotMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the AchievementsSnapshot entity. +// If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AchievementsSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *AchievementsSnapshotMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *AchievementsSnapshotMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *AchievementsSnapshotMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetType sets the "type" field. +func (m *AchievementsSnapshotMutation) SetType(mt models.SnapshotType) { + m._type = &mt +} + +// GetType returns the value of the "type" field in the mutation. +func (m *AchievementsSnapshotMutation) GetType() (r models.SnapshotType, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the AchievementsSnapshot entity. +// If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AchievementsSnapshotMutation) OldType(ctx context.Context) (v models.SnapshotType, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ResetType resets all changes to the "type" field. +func (m *AchievementsSnapshotMutation) ResetType() { + m._type = nil +} + +// SetAccountID sets the "account_id" field. +func (m *AchievementsSnapshotMutation) SetAccountID(s string) { + m.account = &s +} + +// AccountID returns the value of the "account_id" field in the mutation. +func (m *AchievementsSnapshotMutation) AccountID() (r string, exists bool) { + v := m.account + if v == nil { + return + } + return *v, true +} + +// OldAccountID returns the old "account_id" field's value of the AchievementsSnapshot entity. +// If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AchievementsSnapshotMutation) OldAccountID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAccountID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAccountID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAccountID: %w", err) + } + return oldValue.AccountID, nil +} + +// ResetAccountID resets all changes to the "account_id" field. +func (m *AchievementsSnapshotMutation) ResetAccountID() { + m.account = nil +} + +// SetReferenceID sets the "reference_id" field. +func (m *AchievementsSnapshotMutation) SetReferenceID(s string) { + m.reference_id = &s +} + +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *AchievementsSnapshotMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id + if v == nil { + return + } + return *v, true +} + +// OldReferenceID returns the old "reference_id" field's value of the AchievementsSnapshot entity. +// If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AchievementsSnapshotMutation) OldReferenceID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferenceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + } + return oldValue.ReferenceID, nil +} + +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *AchievementsSnapshotMutation) ResetReferenceID() { + m.reference_id = nil +} + +// SetBattles sets the "battles" field. +func (m *AchievementsSnapshotMutation) SetBattles(i int) { + m.battles = &i + m.addbattles = nil +} + +// Battles returns the value of the "battles" field in the mutation. +func (m *AchievementsSnapshotMutation) Battles() (r int, exists bool) { + v := m.battles + if v == nil { + return + } + return *v, true +} + +// OldBattles returns the old "battles" field's value of the AchievementsSnapshot entity. +// If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AchievementsSnapshotMutation) OldBattles(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldBattles is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldBattles requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldBattles: %w", err) + } + return oldValue.Battles, nil +} + +// AddBattles adds i to the "battles" field. +func (m *AchievementsSnapshotMutation) AddBattles(i int) { + if m.addbattles != nil { + *m.addbattles += i + } else { + m.addbattles = &i + } +} + +// AddedBattles returns the value that was added to the "battles" field in this mutation. +func (m *AchievementsSnapshotMutation) AddedBattles() (r int, exists bool) { + v := m.addbattles + if v == nil { + return + } + return *v, true +} + +// ResetBattles resets all changes to the "battles" field. +func (m *AchievementsSnapshotMutation) ResetBattles() { + m.battles = nil + m.addbattles = nil +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (m *AchievementsSnapshotMutation) SetLastBattleTime(i int) { + m.last_battle_time = &i + m.addlast_battle_time = nil +} + +// LastBattleTime returns the value of the "last_battle_time" field in the mutation. +func (m *AchievementsSnapshotMutation) LastBattleTime() (r int, exists bool) { + v := m.last_battle_time + if v == nil { + return + } + return *v, true +} + +// OldLastBattleTime returns the old "last_battle_time" field's value of the AchievementsSnapshot entity. +// If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AchievementsSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLastBattleTime requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLastBattleTime: %w", err) + } + return oldValue.LastBattleTime, nil +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (m *AchievementsSnapshotMutation) AddLastBattleTime(i int) { + if m.addlast_battle_time != nil { + *m.addlast_battle_time += i + } else { + m.addlast_battle_time = &i + } +} + +// AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. +func (m *AchievementsSnapshotMutation) AddedLastBattleTime() (r int, exists bool) { + v := m.addlast_battle_time + if v == nil { + return + } + return *v, true +} + +// ResetLastBattleTime resets all changes to the "last_battle_time" field. +func (m *AchievementsSnapshotMutation) ResetLastBattleTime() { + m.last_battle_time = nil + m.addlast_battle_time = nil +} + +// SetData sets the "data" field. +func (m *AchievementsSnapshotMutation) SetData(tf types.AchievementsFrame) { + m.data = &tf +} + +// Data returns the value of the "data" field in the mutation. +func (m *AchievementsSnapshotMutation) Data() (r types.AchievementsFrame, exists bool) { + v := m.data + if v == nil { + return + } + return *v, true +} + +// OldData returns the old "data" field's value of the AchievementsSnapshot entity. +// If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AchievementsSnapshotMutation) OldData(ctx context.Context) (v types.AchievementsFrame, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldData is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldData requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldData: %w", err) + } + return oldValue.Data, nil +} + +// ResetData resets all changes to the "data" field. +func (m *AchievementsSnapshotMutation) ResetData() { + m.data = nil +} + +// ClearAccount clears the "account" edge to the Account entity. +func (m *AchievementsSnapshotMutation) ClearAccount() { + m.clearedaccount = true + m.clearedFields[achievementssnapshot.FieldAccountID] = struct{}{} +} + +// AccountCleared reports if the "account" edge to the Account entity was cleared. +func (m *AchievementsSnapshotMutation) AccountCleared() bool { + return m.clearedaccount +} + +// AccountIDs returns the "account" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// AccountID instead. It exists only for internal usage by the builders. +func (m *AchievementsSnapshotMutation) AccountIDs() (ids []string) { + if id := m.account; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetAccount resets all changes to the "account" edge. +func (m *AchievementsSnapshotMutation) ResetAccount() { + m.account = nil + m.clearedaccount = false +} + +// Where appends a list predicates to the AchievementsSnapshotMutation builder. +func (m *AchievementsSnapshotMutation) Where(ps ...predicate.AchievementsSnapshot) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the AchievementsSnapshotMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *AchievementsSnapshotMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.AchievementsSnapshot, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *AchievementsSnapshotMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *AchievementsSnapshotMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (AchievementsSnapshot). +func (m *AchievementsSnapshotMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *AchievementsSnapshotMutation) Fields() []string { + fields := make([]string, 0, 8) + if m.created_at != nil { + fields = append(fields, achievementssnapshot.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, achievementssnapshot.FieldUpdatedAt) + } + if m._type != nil { + fields = append(fields, achievementssnapshot.FieldType) + } + if m.account != nil { + fields = append(fields, achievementssnapshot.FieldAccountID) + } + if m.reference_id != nil { + fields = append(fields, achievementssnapshot.FieldReferenceID) + } + if m.battles != nil { + fields = append(fields, achievementssnapshot.FieldBattles) + } + if m.last_battle_time != nil { + fields = append(fields, achievementssnapshot.FieldLastBattleTime) + } + if m.data != nil { + fields = append(fields, achievementssnapshot.FieldData) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *AchievementsSnapshotMutation) Field(name string) (ent.Value, bool) { + switch name { + case achievementssnapshot.FieldCreatedAt: + return m.CreatedAt() + case achievementssnapshot.FieldUpdatedAt: + return m.UpdatedAt() + case achievementssnapshot.FieldType: + return m.GetType() + case achievementssnapshot.FieldAccountID: + return m.AccountID() + case achievementssnapshot.FieldReferenceID: + return m.ReferenceID() + case achievementssnapshot.FieldBattles: + return m.Battles() + case achievementssnapshot.FieldLastBattleTime: + return m.LastBattleTime() + case achievementssnapshot.FieldData: + return m.Data() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *AchievementsSnapshotMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case achievementssnapshot.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case achievementssnapshot.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case achievementssnapshot.FieldType: + return m.OldType(ctx) + case achievementssnapshot.FieldAccountID: + return m.OldAccountID(ctx) + case achievementssnapshot.FieldReferenceID: + return m.OldReferenceID(ctx) + case achievementssnapshot.FieldBattles: + return m.OldBattles(ctx) + case achievementssnapshot.FieldLastBattleTime: + return m.OldLastBattleTime(ctx) + case achievementssnapshot.FieldData: + return m.OldData(ctx) + } + return nil, fmt.Errorf("unknown AchievementsSnapshot field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AchievementsSnapshotMutation) SetField(name string, value ent.Value) error { + switch name { + case achievementssnapshot.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case achievementssnapshot.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case achievementssnapshot.FieldType: + v, ok := value.(models.SnapshotType) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case achievementssnapshot.FieldAccountID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAccountID(v) + return nil + case achievementssnapshot.FieldReferenceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferenceID(v) + return nil + case achievementssnapshot.FieldBattles: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetBattles(v) + return nil + case achievementssnapshot.FieldLastBattleTime: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLastBattleTime(v) + return nil + case achievementssnapshot.FieldData: + v, ok := value.(types.AchievementsFrame) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetData(v) + return nil + } + return fmt.Errorf("unknown AchievementsSnapshot field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *AchievementsSnapshotMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, achievementssnapshot.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, achievementssnapshot.FieldUpdatedAt) + } + if m.addbattles != nil { + fields = append(fields, achievementssnapshot.FieldBattles) + } + if m.addlast_battle_time != nil { + fields = append(fields, achievementssnapshot.FieldLastBattleTime) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *AchievementsSnapshotMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case achievementssnapshot.FieldCreatedAt: + return m.AddedCreatedAt() + case achievementssnapshot.FieldUpdatedAt: + return m.AddedUpdatedAt() + case achievementssnapshot.FieldBattles: + return m.AddedBattles() + case achievementssnapshot.FieldLastBattleTime: + return m.AddedLastBattleTime() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AchievementsSnapshotMutation) AddField(name string, value ent.Value) error { + switch name { + case achievementssnapshot.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case achievementssnapshot.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + case achievementssnapshot.FieldBattles: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddBattles(v) + return nil + case achievementssnapshot.FieldLastBattleTime: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddLastBattleTime(v) + return nil + } + return fmt.Errorf("unknown AchievementsSnapshot numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *AchievementsSnapshotMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *AchievementsSnapshotMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *AchievementsSnapshotMutation) ClearField(name string) error { + return fmt.Errorf("unknown AchievementsSnapshot nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *AchievementsSnapshotMutation) ResetField(name string) error { + switch name { + case achievementssnapshot.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case achievementssnapshot.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case achievementssnapshot.FieldType: + m.ResetType() + return nil + case achievementssnapshot.FieldAccountID: + m.ResetAccountID() + return nil + case achievementssnapshot.FieldReferenceID: + m.ResetReferenceID() + return nil + case achievementssnapshot.FieldBattles: + m.ResetBattles() + return nil + case achievementssnapshot.FieldLastBattleTime: + m.ResetLastBattleTime() + return nil + case achievementssnapshot.FieldData: + m.ResetData() + return nil + } + return fmt.Errorf("unknown AchievementsSnapshot field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *AchievementsSnapshotMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.account != nil { + edges = append(edges, achievementssnapshot.EdgeAccount) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *AchievementsSnapshotMutation) AddedIDs(name string) []ent.Value { + switch name { + case achievementssnapshot.EdgeAccount: + if id := m.account; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *AchievementsSnapshotMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *AchievementsSnapshotMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *AchievementsSnapshotMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedaccount { + edges = append(edges, achievementssnapshot.EdgeAccount) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *AchievementsSnapshotMutation) EdgeCleared(name string) bool { + switch name { + case achievementssnapshot.EdgeAccount: + return m.clearedaccount + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *AchievementsSnapshotMutation) ClearEdge(name string) error { + switch name { + case achievementssnapshot.EdgeAccount: + m.ClearAccount() + return nil + } + return fmt.Errorf("unknown AchievementsSnapshot unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *AchievementsSnapshotMutation) ResetEdge(name string) error { + switch name { + case achievementssnapshot.EdgeAccount: + m.ResetAccount() + return nil + } + return fmt.Errorf("unknown AchievementsSnapshot edge %s", name) +} + +// AppConfigurationMutation represents an operation that mutates the AppConfiguration nodes in the graph. +type AppConfigurationMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + key *string + value *any + metadata *map[string]interface{} + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*AppConfiguration, error) + predicates []predicate.AppConfiguration +} + +var _ ent.Mutation = (*AppConfigurationMutation)(nil) + +// appconfigurationOption allows management of the mutation configuration using functional options. +type appconfigurationOption func(*AppConfigurationMutation) + +// newAppConfigurationMutation creates new mutation for the AppConfiguration entity. +func newAppConfigurationMutation(c config, op Op, opts ...appconfigurationOption) *AppConfigurationMutation { + m := &AppConfigurationMutation{ + config: c, + op: op, + typ: TypeAppConfiguration, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withAppConfigurationID sets the ID field of the mutation. +func withAppConfigurationID(id string) appconfigurationOption { + return func(m *AppConfigurationMutation) { + var ( + err error + once sync.Once + value *AppConfiguration + ) + m.oldValue = func(ctx context.Context) (*AppConfiguration, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().AppConfiguration.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withAppConfiguration sets the old AppConfiguration of the mutation. +func withAppConfiguration(node *AppConfiguration) appconfigurationOption { + return func(m *AppConfigurationMutation) { + m.oldValue = func(context.Context) (*AppConfiguration, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m AppConfigurationMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m AppConfigurationMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of AppConfiguration entities. +func (m *AppConfigurationMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *AppConfigurationMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *AppConfigurationMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().AppConfiguration.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *AppConfigurationMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *AppConfigurationMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the AppConfiguration entity. +// If the AppConfiguration object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AppConfigurationMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *AppConfigurationMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *AppConfigurationMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *AppConfigurationMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *AppConfigurationMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *AppConfigurationMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the AppConfiguration entity. +// If the AppConfiguration object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AppConfigurationMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *AppConfigurationMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *AppConfigurationMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *AppConfigurationMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetKey sets the "key" field. +func (m *AppConfigurationMutation) SetKey(s string) { + m.key = &s +} + +// Key returns the value of the "key" field in the mutation. +func (m *AppConfigurationMutation) Key() (r string, exists bool) { + v := m.key + if v == nil { + return + } + return *v, true +} + +// OldKey returns the old "key" field's value of the AppConfiguration entity. +// If the AppConfiguration object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AppConfigurationMutation) OldKey(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldKey is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldKey requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldKey: %w", err) + } + return oldValue.Key, nil +} + +// ResetKey resets all changes to the "key" field. +func (m *AppConfigurationMutation) ResetKey() { + m.key = nil +} + +// SetValue sets the "value" field. +func (m *AppConfigurationMutation) SetValue(a any) { + m.value = &a +} + +// Value returns the value of the "value" field in the mutation. +func (m *AppConfigurationMutation) Value() (r any, exists bool) { + v := m.value + if v == nil { + return + } + return *v, true +} + +// OldValue returns the old "value" field's value of the AppConfiguration entity. +// If the AppConfiguration object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AppConfigurationMutation) OldValue(ctx context.Context) (v any, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldValue is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldValue requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldValue: %w", err) + } + return oldValue.Value, nil +} + +// ResetValue resets all changes to the "value" field. +func (m *AppConfigurationMutation) ResetValue() { + m.value = nil +} + +// SetMetadata sets the "metadata" field. +func (m *AppConfigurationMutation) SetMetadata(value map[string]interface{}) { + m.metadata = &value +} + +// Metadata returns the value of the "metadata" field in the mutation. +func (m *AppConfigurationMutation) Metadata() (r map[string]interface{}, exists bool) { + v := m.metadata + if v == nil { + return + } + return *v, true +} + +// OldMetadata returns the old "metadata" field's value of the AppConfiguration entity. +// If the AppConfiguration object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AppConfigurationMutation) OldMetadata(ctx context.Context) (v map[string]interface{}, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMetadata is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMetadata requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMetadata: %w", err) + } + return oldValue.Metadata, nil +} + +// ClearMetadata clears the value of the "metadata" field. +func (m *AppConfigurationMutation) ClearMetadata() { + m.metadata = nil + m.clearedFields[appconfiguration.FieldMetadata] = struct{}{} +} + +// MetadataCleared returns if the "metadata" field was cleared in this mutation. +func (m *AppConfigurationMutation) MetadataCleared() bool { + _, ok := m.clearedFields[appconfiguration.FieldMetadata] + return ok +} + +// ResetMetadata resets all changes to the "metadata" field. +func (m *AppConfigurationMutation) ResetMetadata() { + m.metadata = nil + delete(m.clearedFields, appconfiguration.FieldMetadata) +} + +// Where appends a list predicates to the AppConfigurationMutation builder. +func (m *AppConfigurationMutation) Where(ps ...predicate.AppConfiguration) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the AppConfigurationMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *AppConfigurationMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.AppConfiguration, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *AppConfigurationMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *AppConfigurationMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (AppConfiguration). +func (m *AppConfigurationMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *AppConfigurationMutation) Fields() []string { + fields := make([]string, 0, 5) + if m.created_at != nil { + fields = append(fields, appconfiguration.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, appconfiguration.FieldUpdatedAt) + } + if m.key != nil { + fields = append(fields, appconfiguration.FieldKey) + } + if m.value != nil { + fields = append(fields, appconfiguration.FieldValue) + } + if m.metadata != nil { + fields = append(fields, appconfiguration.FieldMetadata) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *AppConfigurationMutation) Field(name string) (ent.Value, bool) { + switch name { + case appconfiguration.FieldCreatedAt: + return m.CreatedAt() + case appconfiguration.FieldUpdatedAt: + return m.UpdatedAt() + case appconfiguration.FieldKey: + return m.Key() + case appconfiguration.FieldValue: + return m.Value() + case appconfiguration.FieldMetadata: + return m.Metadata() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *AppConfigurationMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case appconfiguration.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case appconfiguration.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case appconfiguration.FieldKey: + return m.OldKey(ctx) + case appconfiguration.FieldValue: + return m.OldValue(ctx) + case appconfiguration.FieldMetadata: + return m.OldMetadata(ctx) + } + return nil, fmt.Errorf("unknown AppConfiguration field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AppConfigurationMutation) SetField(name string, value ent.Value) error { + switch name { + case appconfiguration.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case appconfiguration.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case appconfiguration.FieldKey: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetKey(v) + return nil + case appconfiguration.FieldValue: + v, ok := value.(any) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetValue(v) + return nil + case appconfiguration.FieldMetadata: + v, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMetadata(v) + return nil + } + return fmt.Errorf("unknown AppConfiguration field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *AppConfigurationMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, appconfiguration.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, appconfiguration.FieldUpdatedAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *AppConfigurationMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case appconfiguration.FieldCreatedAt: + return m.AddedCreatedAt() + case appconfiguration.FieldUpdatedAt: + return m.AddedUpdatedAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AppConfigurationMutation) AddField(name string, value ent.Value) error { + switch name { + case appconfiguration.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case appconfiguration.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown AppConfiguration numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *AppConfigurationMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(appconfiguration.FieldMetadata) { + fields = append(fields, appconfiguration.FieldMetadata) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *AppConfigurationMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *AppConfigurationMutation) ClearField(name string) error { + switch name { + case appconfiguration.FieldMetadata: + m.ClearMetadata() + return nil + } + return fmt.Errorf("unknown AppConfiguration nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *AppConfigurationMutation) ResetField(name string) error { + switch name { + case appconfiguration.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case appconfiguration.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case appconfiguration.FieldKey: + m.ResetKey() + return nil + case appconfiguration.FieldValue: + m.ResetValue() + return nil + case appconfiguration.FieldMetadata: + m.ResetMetadata() + return nil + } + return fmt.Errorf("unknown AppConfiguration field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *AppConfigurationMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *AppConfigurationMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *AppConfigurationMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *AppConfigurationMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *AppConfigurationMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *AppConfigurationMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *AppConfigurationMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown AppConfiguration unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *AppConfigurationMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown AppConfiguration edge %s", name) +} + +// ApplicationCommandMutation represents an operation that mutates the ApplicationCommand nodes in the graph. +type ApplicationCommandMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + name *string + version *string + options_hash *string + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*ApplicationCommand, error) + predicates []predicate.ApplicationCommand +} + +var _ ent.Mutation = (*ApplicationCommandMutation)(nil) + +// applicationcommandOption allows management of the mutation configuration using functional options. +type applicationcommandOption func(*ApplicationCommandMutation) + +// newApplicationCommandMutation creates new mutation for the ApplicationCommand entity. +func newApplicationCommandMutation(c config, op Op, opts ...applicationcommandOption) *ApplicationCommandMutation { + m := &ApplicationCommandMutation{ + config: c, + op: op, + typ: TypeApplicationCommand, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withApplicationCommandID sets the ID field of the mutation. +func withApplicationCommandID(id string) applicationcommandOption { + return func(m *ApplicationCommandMutation) { + var ( + err error + once sync.Once + value *ApplicationCommand + ) + m.oldValue = func(ctx context.Context) (*ApplicationCommand, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().ApplicationCommand.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withApplicationCommand sets the old ApplicationCommand of the mutation. +func withApplicationCommand(node *ApplicationCommand) applicationcommandOption { + return func(m *ApplicationCommandMutation) { + m.oldValue = func(context.Context) (*ApplicationCommand, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m ApplicationCommandMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m ApplicationCommandMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of ApplicationCommand entities. +func (m *ApplicationCommandMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *ApplicationCommandMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *ApplicationCommandMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().ApplicationCommand.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *ApplicationCommandMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *ApplicationCommandMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the ApplicationCommand entity. +// If the ApplicationCommand object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ApplicationCommandMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *ApplicationCommandMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *ApplicationCommandMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *ApplicationCommandMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *ApplicationCommandMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *ApplicationCommandMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the ApplicationCommand entity. +// If the ApplicationCommand object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ApplicationCommandMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *ApplicationCommandMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *ApplicationCommandMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *ApplicationCommandMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetName sets the "name" field. +func (m *ApplicationCommandMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *ApplicationCommandMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "name" field's value of the ApplicationCommand entity. +// If the ApplicationCommand object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ApplicationCommandMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "name" field. +func (m *ApplicationCommandMutation) ResetName() { + m.name = nil +} + +// SetVersion sets the "version" field. +func (m *ApplicationCommandMutation) SetVersion(s string) { + m.version = &s +} + +// Version returns the value of the "version" field in the mutation. +func (m *ApplicationCommandMutation) Version() (r string, exists bool) { + v := m.version + if v == nil { + return + } + return *v, true +} + +// OldVersion returns the old "version" field's value of the ApplicationCommand entity. +// If the ApplicationCommand object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ApplicationCommandMutation) OldVersion(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldVersion is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldVersion requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldVersion: %w", err) + } + return oldValue.Version, nil +} + +// ResetVersion resets all changes to the "version" field. +func (m *ApplicationCommandMutation) ResetVersion() { + m.version = nil +} + +// SetOptionsHash sets the "options_hash" field. +func (m *ApplicationCommandMutation) SetOptionsHash(s string) { + m.options_hash = &s +} + +// OptionsHash returns the value of the "options_hash" field in the mutation. +func (m *ApplicationCommandMutation) OptionsHash() (r string, exists bool) { + v := m.options_hash + if v == nil { + return + } + return *v, true +} + +// OldOptionsHash returns the old "options_hash" field's value of the ApplicationCommand entity. +// If the ApplicationCommand object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ApplicationCommandMutation) OldOptionsHash(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldOptionsHash is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldOptionsHash requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldOptionsHash: %w", err) + } + return oldValue.OptionsHash, nil +} + +// ResetOptionsHash resets all changes to the "options_hash" field. +func (m *ApplicationCommandMutation) ResetOptionsHash() { + m.options_hash = nil +} + +// Where appends a list predicates to the ApplicationCommandMutation builder. +func (m *ApplicationCommandMutation) Where(ps ...predicate.ApplicationCommand) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ApplicationCommandMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ApplicationCommandMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.ApplicationCommand, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ApplicationCommandMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ApplicationCommandMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (ApplicationCommand). +func (m *ApplicationCommandMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *ApplicationCommandMutation) Fields() []string { + fields := make([]string, 0, 5) + if m.created_at != nil { + fields = append(fields, applicationcommand.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, applicationcommand.FieldUpdatedAt) + } + if m.name != nil { + fields = append(fields, applicationcommand.FieldName) + } + if m.version != nil { + fields = append(fields, applicationcommand.FieldVersion) + } + if m.options_hash != nil { + fields = append(fields, applicationcommand.FieldOptionsHash) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *ApplicationCommandMutation) Field(name string) (ent.Value, bool) { + switch name { + case applicationcommand.FieldCreatedAt: + return m.CreatedAt() + case applicationcommand.FieldUpdatedAt: + return m.UpdatedAt() + case applicationcommand.FieldName: + return m.Name() + case applicationcommand.FieldVersion: + return m.Version() + case applicationcommand.FieldOptionsHash: + return m.OptionsHash() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *ApplicationCommandMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case applicationcommand.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case applicationcommand.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case applicationcommand.FieldName: + return m.OldName(ctx) + case applicationcommand.FieldVersion: + return m.OldVersion(ctx) + case applicationcommand.FieldOptionsHash: + return m.OldOptionsHash(ctx) + } + return nil, fmt.Errorf("unknown ApplicationCommand field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ApplicationCommandMutation) SetField(name string, value ent.Value) error { + switch name { + case applicationcommand.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case applicationcommand.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case applicationcommand.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case applicationcommand.FieldVersion: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetVersion(v) + return nil + case applicationcommand.FieldOptionsHash: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOptionsHash(v) + return nil + } + return fmt.Errorf("unknown ApplicationCommand field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ApplicationCommandMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, applicationcommand.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, applicationcommand.FieldUpdatedAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *ApplicationCommandMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case applicationcommand.FieldCreatedAt: + return m.AddedCreatedAt() + case applicationcommand.FieldUpdatedAt: + return m.AddedUpdatedAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ApplicationCommandMutation) AddField(name string, value ent.Value) error { + switch name { + case applicationcommand.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case applicationcommand.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown ApplicationCommand numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ApplicationCommandMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ApplicationCommandMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *ApplicationCommandMutation) ClearField(name string) error { + return fmt.Errorf("unknown ApplicationCommand nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *ApplicationCommandMutation) ResetField(name string) error { + switch name { + case applicationcommand.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case applicationcommand.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case applicationcommand.FieldName: + m.ResetName() + return nil + case applicationcommand.FieldVersion: + m.ResetVersion() + return nil + case applicationcommand.FieldOptionsHash: + m.ResetOptionsHash() + return nil + } + return fmt.Errorf("unknown ApplicationCommand field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ApplicationCommandMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ApplicationCommandMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ApplicationCommandMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *ApplicationCommandMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ApplicationCommandMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ApplicationCommandMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *ApplicationCommandMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown ApplicationCommand unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *ApplicationCommandMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown ApplicationCommand edge %s", name) +} + +// ClanMutation represents an operation that mutates the Clan nodes in the graph. +type ClanMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + tag *string + name *string + emblem_id *string + members *[]string + appendmembers []string + clearedFields map[string]struct{} + accounts map[string]struct{} + removedaccounts map[string]struct{} + clearedaccounts bool + done bool + oldValue func(context.Context) (*Clan, error) + predicates []predicate.Clan +} + +var _ ent.Mutation = (*ClanMutation)(nil) + +// clanOption allows management of the mutation configuration using functional options. +type clanOption func(*ClanMutation) + +// newClanMutation creates new mutation for the Clan entity. +func newClanMutation(c config, op Op, opts ...clanOption) *ClanMutation { + m := &ClanMutation{ + config: c, + op: op, + typ: TypeClan, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withClanID sets the ID field of the mutation. +func withClanID(id string) clanOption { + return func(m *ClanMutation) { + var ( + err error + once sync.Once + value *Clan + ) + m.oldValue = func(ctx context.Context) (*Clan, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Clan.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withClan sets the old Clan of the mutation. +func withClan(node *Clan) clanOption { + return func(m *ClanMutation) { + m.oldValue = func(context.Context) (*Clan, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m ClanMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m ClanMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of Clan entities. +func (m *ClanMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *ClanMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *ClanMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().Clan.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *ClanMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *ClanMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *ClanMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *ClanMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *ClanMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *ClanMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *ClanMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *ClanMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *ClanMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *ClanMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetTag sets the "tag" field. +func (m *ClanMutation) SetTag(s string) { + m.tag = &s +} + +// Tag returns the value of the "tag" field in the mutation. +func (m *ClanMutation) Tag() (r string, exists bool) { + v := m.tag + if v == nil { + return + } + return *v, true +} + +// OldTag returns the old "tag" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ClanMutation) OldTag(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTag is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTag requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTag: %w", err) + } + return oldValue.Tag, nil +} + +// ResetTag resets all changes to the "tag" field. +func (m *ClanMutation) ResetTag() { + m.tag = nil +} + +// SetName sets the "name" field. +func (m *ClanMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *ClanMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "name" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ClanMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "name" field. +func (m *ClanMutation) ResetName() { + m.name = nil +} + +// SetEmblemID sets the "emblem_id" field. +func (m *ClanMutation) SetEmblemID(s string) { + m.emblem_id = &s +} + +// EmblemID returns the value of the "emblem_id" field in the mutation. +func (m *ClanMutation) EmblemID() (r string, exists bool) { + v := m.emblem_id + if v == nil { + return + } + return *v, true +} + +// OldEmblemID returns the old "emblem_id" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ClanMutation) OldEmblemID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldEmblemID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldEmblemID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldEmblemID: %w", err) + } + return oldValue.EmblemID, nil +} + +// ClearEmblemID clears the value of the "emblem_id" field. +func (m *ClanMutation) ClearEmblemID() { + m.emblem_id = nil + m.clearedFields[clan.FieldEmblemID] = struct{}{} +} + +// EmblemIDCleared returns if the "emblem_id" field was cleared in this mutation. +func (m *ClanMutation) EmblemIDCleared() bool { + _, ok := m.clearedFields[clan.FieldEmblemID] + return ok +} + +// ResetEmblemID resets all changes to the "emblem_id" field. +func (m *ClanMutation) ResetEmblemID() { + m.emblem_id = nil + delete(m.clearedFields, clan.FieldEmblemID) +} + +// SetMembers sets the "members" field. +func (m *ClanMutation) SetMembers(s []string) { + m.members = &s + m.appendmembers = nil +} + +// Members returns the value of the "members" field in the mutation. +func (m *ClanMutation) Members() (r []string, exists bool) { + v := m.members + if v == nil { + return + } + return *v, true +} + +// OldMembers returns the old "members" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ClanMutation) OldMembers(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMembers is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMembers requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMembers: %w", err) + } + return oldValue.Members, nil +} + +// AppendMembers adds s to the "members" field. +func (m *ClanMutation) AppendMembers(s []string) { + m.appendmembers = append(m.appendmembers, s...) +} + +// AppendedMembers returns the list of values that were appended to the "members" field in this mutation. +func (m *ClanMutation) AppendedMembers() ([]string, bool) { + if len(m.appendmembers) == 0 { + return nil, false + } + return m.appendmembers, true +} + +// ResetMembers resets all changes to the "members" field. +func (m *ClanMutation) ResetMembers() { + m.members = nil + m.appendmembers = nil +} + +// AddAccountIDs adds the "accounts" edge to the Account entity by ids. +func (m *ClanMutation) AddAccountIDs(ids ...string) { + if m.accounts == nil { + m.accounts = make(map[string]struct{}) + } + for i := range ids { + m.accounts[ids[i]] = struct{}{} + } +} + +// ClearAccounts clears the "accounts" edge to the Account entity. +func (m *ClanMutation) ClearAccounts() { + m.clearedaccounts = true +} + +// AccountsCleared reports if the "accounts" edge to the Account entity was cleared. +func (m *ClanMutation) AccountsCleared() bool { + return m.clearedaccounts +} + +// RemoveAccountIDs removes the "accounts" edge to the Account entity by IDs. +func (m *ClanMutation) RemoveAccountIDs(ids ...string) { + if m.removedaccounts == nil { + m.removedaccounts = make(map[string]struct{}) + } + for i := range ids { + delete(m.accounts, ids[i]) + m.removedaccounts[ids[i]] = struct{}{} + } +} + +// RemovedAccounts returns the removed IDs of the "accounts" edge to the Account entity. +func (m *ClanMutation) RemovedAccountsIDs() (ids []string) { + for id := range m.removedaccounts { + ids = append(ids, id) + } + return +} + +// AccountsIDs returns the "accounts" edge IDs in the mutation. +func (m *ClanMutation) AccountsIDs() (ids []string) { + for id := range m.accounts { + ids = append(ids, id) + } + return +} + +// ResetAccounts resets all changes to the "accounts" edge. +func (m *ClanMutation) ResetAccounts() { + m.accounts = nil + m.clearedaccounts = false + m.removedaccounts = nil +} + +// Where appends a list predicates to the ClanMutation builder. +func (m *ClanMutation) Where(ps ...predicate.Clan) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ClanMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ClanMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Clan, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ClanMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ClanMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Clan). +func (m *ClanMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *ClanMutation) Fields() []string { + fields := make([]string, 0, 6) + if m.created_at != nil { + fields = append(fields, clan.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, clan.FieldUpdatedAt) + } + if m.tag != nil { + fields = append(fields, clan.FieldTag) + } + if m.name != nil { + fields = append(fields, clan.FieldName) + } + if m.emblem_id != nil { + fields = append(fields, clan.FieldEmblemID) + } + if m.members != nil { + fields = append(fields, clan.FieldMembers) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *ClanMutation) Field(name string) (ent.Value, bool) { + switch name { + case clan.FieldCreatedAt: + return m.CreatedAt() + case clan.FieldUpdatedAt: + return m.UpdatedAt() + case clan.FieldTag: + return m.Tag() + case clan.FieldName: + return m.Name() + case clan.FieldEmblemID: + return m.EmblemID() + case clan.FieldMembers: + return m.Members() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *ClanMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case clan.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case clan.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case clan.FieldTag: + return m.OldTag(ctx) + case clan.FieldName: + return m.OldName(ctx) + case clan.FieldEmblemID: + return m.OldEmblemID(ctx) + case clan.FieldMembers: + return m.OldMembers(ctx) + } + return nil, fmt.Errorf("unknown Clan field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ClanMutation) SetField(name string, value ent.Value) error { + switch name { + case clan.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case clan.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case clan.FieldTag: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTag(v) + return nil + case clan.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case clan.FieldEmblemID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetEmblemID(v) + return nil + case clan.FieldMembers: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMembers(v) + return nil + } + return fmt.Errorf("unknown Clan field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ClanMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, clan.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, clan.FieldUpdatedAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *ClanMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case clan.FieldCreatedAt: + return m.AddedCreatedAt() + case clan.FieldUpdatedAt: + return m.AddedUpdatedAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ClanMutation) AddField(name string, value ent.Value) error { + switch name { + case clan.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case clan.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown Clan numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ClanMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(clan.FieldEmblemID) { + fields = append(fields, clan.FieldEmblemID) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ClanMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *ClanMutation) ClearField(name string) error { + switch name { + case clan.FieldEmblemID: + m.ClearEmblemID() + return nil + } + return fmt.Errorf("unknown Clan nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *ClanMutation) ResetField(name string) error { + switch name { + case clan.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case clan.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case clan.FieldTag: + m.ResetTag() + return nil + case clan.FieldName: + m.ResetName() + return nil + case clan.FieldEmblemID: + m.ResetEmblemID() + return nil + case clan.FieldMembers: + m.ResetMembers() + return nil + } + return fmt.Errorf("unknown Clan field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ClanMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.accounts != nil { + edges = append(edges, clan.EdgeAccounts) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ClanMutation) AddedIDs(name string) []ent.Value { + switch name { + case clan.EdgeAccounts: + ids := make([]ent.Value, 0, len(m.accounts)) + for id := range m.accounts { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ClanMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + if m.removedaccounts != nil { + edges = append(edges, clan.EdgeAccounts) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *ClanMutation) RemovedIDs(name string) []ent.Value { + switch name { + case clan.EdgeAccounts: + ids := make([]ent.Value, 0, len(m.removedaccounts)) + for id := range m.removedaccounts { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ClanMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedaccounts { + edges = append(edges, clan.EdgeAccounts) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ClanMutation) EdgeCleared(name string) bool { + switch name { + case clan.EdgeAccounts: + return m.clearedaccounts + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *ClanMutation) ClearEdge(name string) error { + switch name { + } + return fmt.Errorf("unknown Clan unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *ClanMutation) ResetEdge(name string) error { + switch name { + case clan.EdgeAccounts: + m.ResetAccounts() + return nil + } + return fmt.Errorf("unknown Clan edge %s", name) +} + +// CronTaskMutation represents an operation that mutates the CronTask nodes in the graph. +type CronTaskMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + _type *string + reference_id *string + targets *[]string + appendtargets []string + status *string + scheduled_after *int + addscheduled_after *int + last_run *int + addlast_run *int + logs *[]models.TaskLog + appendlogs []models.TaskLog + data *map[string]interface{} + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*CronTask, error) + predicates []predicate.CronTask +} + +var _ ent.Mutation = (*CronTaskMutation)(nil) + +// crontaskOption allows management of the mutation configuration using functional options. +type crontaskOption func(*CronTaskMutation) + +// newCronTaskMutation creates new mutation for the CronTask entity. +func newCronTaskMutation(c config, op Op, opts ...crontaskOption) *CronTaskMutation { + m := &CronTaskMutation{ + config: c, + op: op, + typ: TypeCronTask, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withCronTaskID sets the ID field of the mutation. +func withCronTaskID(id string) crontaskOption { + return func(m *CronTaskMutation) { + var ( + err error + once sync.Once + value *CronTask + ) + m.oldValue = func(ctx context.Context) (*CronTask, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().CronTask.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withCronTask sets the old CronTask of the mutation. +func withCronTask(node *CronTask) crontaskOption { + return func(m *CronTaskMutation) { + m.oldValue = func(context.Context) (*CronTask, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m CronTaskMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m CronTaskMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of CronTask entities. +func (m *CronTaskMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *CronTaskMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *CronTaskMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().CronTask.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *CronTaskMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *CronTaskMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *CronTaskMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *CronTaskMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *CronTaskMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *CronTaskMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *CronTaskMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *CronTaskMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *CronTaskMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *CronTaskMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetType sets the "type" field. +func (m *CronTaskMutation) SetType(s string) { + m._type = &s +} + +// GetType returns the value of the "type" field in the mutation. +func (m *CronTaskMutation) GetType() (r string, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldType(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ResetType resets all changes to the "type" field. +func (m *CronTaskMutation) ResetType() { + m._type = nil +} + +// SetReferenceID sets the "reference_id" field. +func (m *CronTaskMutation) SetReferenceID(s string) { + m.reference_id = &s +} + +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *CronTaskMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id + if v == nil { + return + } + return *v, true +} + +// OldReferenceID returns the old "reference_id" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldReferenceID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferenceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + } + return oldValue.ReferenceID, nil +} + +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *CronTaskMutation) ResetReferenceID() { + m.reference_id = nil +} + +// SetTargets sets the "targets" field. +func (m *CronTaskMutation) SetTargets(s []string) { + m.targets = &s + m.appendtargets = nil +} + +// Targets returns the value of the "targets" field in the mutation. +func (m *CronTaskMutation) Targets() (r []string, exists bool) { + v := m.targets + if v == nil { + return + } + return *v, true +} + +// OldTargets returns the old "targets" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldTargets(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTargets is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTargets requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTargets: %w", err) + } + return oldValue.Targets, nil +} + +// AppendTargets adds s to the "targets" field. +func (m *CronTaskMutation) AppendTargets(s []string) { + m.appendtargets = append(m.appendtargets, s...) +} + +// AppendedTargets returns the list of values that were appended to the "targets" field in this mutation. +func (m *CronTaskMutation) AppendedTargets() ([]string, bool) { + if len(m.appendtargets) == 0 { + return nil, false + } + return m.appendtargets, true +} + +// ResetTargets resets all changes to the "targets" field. +func (m *CronTaskMutation) ResetTargets() { + m.targets = nil + m.appendtargets = nil +} + +// SetStatus sets the "status" field. +func (m *CronTaskMutation) SetStatus(s string) { + m.status = &s +} + +// Status returns the value of the "status" field in the mutation. +func (m *CronTaskMutation) Status() (r string, exists bool) { + v := m.status + if v == nil { + return + } + return *v, true +} + +// OldStatus returns the old "status" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldStatus(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldStatus is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldStatus requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStatus: %w", err) + } + return oldValue.Status, nil +} + +// ResetStatus resets all changes to the "status" field. +func (m *CronTaskMutation) ResetStatus() { + m.status = nil +} + +// SetScheduledAfter sets the "scheduled_after" field. +func (m *CronTaskMutation) SetScheduledAfter(i int) { + m.scheduled_after = &i + m.addscheduled_after = nil +} + +// ScheduledAfter returns the value of the "scheduled_after" field in the mutation. +func (m *CronTaskMutation) ScheduledAfter() (r int, exists bool) { + v := m.scheduled_after + if v == nil { + return + } + return *v, true +} + +// OldScheduledAfter returns the old "scheduled_after" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldScheduledAfter(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldScheduledAfter is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldScheduledAfter requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldScheduledAfter: %w", err) + } + return oldValue.ScheduledAfter, nil +} + +// AddScheduledAfter adds i to the "scheduled_after" field. +func (m *CronTaskMutation) AddScheduledAfter(i int) { + if m.addscheduled_after != nil { + *m.addscheduled_after += i + } else { + m.addscheduled_after = &i + } +} + +// AddedScheduledAfter returns the value that was added to the "scheduled_after" field in this mutation. +func (m *CronTaskMutation) AddedScheduledAfter() (r int, exists bool) { + v := m.addscheduled_after + if v == nil { + return + } + return *v, true +} + +// ResetScheduledAfter resets all changes to the "scheduled_after" field. +func (m *CronTaskMutation) ResetScheduledAfter() { + m.scheduled_after = nil + m.addscheduled_after = nil +} + +// SetLastRun sets the "last_run" field. +func (m *CronTaskMutation) SetLastRun(i int) { + m.last_run = &i + m.addlast_run = nil +} + +// LastRun returns the value of the "last_run" field in the mutation. +func (m *CronTaskMutation) LastRun() (r int, exists bool) { + v := m.last_run + if v == nil { + return + } + return *v, true +} + +// OldLastRun returns the old "last_run" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldLastRun(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLastRun is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLastRun requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLastRun: %w", err) + } + return oldValue.LastRun, nil +} + +// AddLastRun adds i to the "last_run" field. +func (m *CronTaskMutation) AddLastRun(i int) { + if m.addlast_run != nil { + *m.addlast_run += i + } else { + m.addlast_run = &i + } +} + +// AddedLastRun returns the value that was added to the "last_run" field in this mutation. +func (m *CronTaskMutation) AddedLastRun() (r int, exists bool) { + v := m.addlast_run + if v == nil { + return + } + return *v, true +} + +// ResetLastRun resets all changes to the "last_run" field. +func (m *CronTaskMutation) ResetLastRun() { + m.last_run = nil + m.addlast_run = nil +} + +// SetLogs sets the "logs" field. +func (m *CronTaskMutation) SetLogs(ml []models.TaskLog) { + m.logs = &ml + m.appendlogs = nil +} + +// Logs returns the value of the "logs" field in the mutation. +func (m *CronTaskMutation) Logs() (r []models.TaskLog, exists bool) { + v := m.logs + if v == nil { + return + } + return *v, true +} + +// OldLogs returns the old "logs" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldLogs(ctx context.Context) (v []models.TaskLog, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLogs is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLogs requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLogs: %w", err) + } + return oldValue.Logs, nil +} + +// AppendLogs adds ml to the "logs" field. +func (m *CronTaskMutation) AppendLogs(ml []models.TaskLog) { + m.appendlogs = append(m.appendlogs, ml...) +} + +// AppendedLogs returns the list of values that were appended to the "logs" field in this mutation. +func (m *CronTaskMutation) AppendedLogs() ([]models.TaskLog, bool) { + if len(m.appendlogs) == 0 { + return nil, false + } + return m.appendlogs, true +} + +// ResetLogs resets all changes to the "logs" field. +func (m *CronTaskMutation) ResetLogs() { + m.logs = nil + m.appendlogs = nil +} + +// SetData sets the "data" field. +func (m *CronTaskMutation) SetData(value map[string]interface{}) { + m.data = &value +} + +// Data returns the value of the "data" field in the mutation. +func (m *CronTaskMutation) Data() (r map[string]interface{}, exists bool) { + v := m.data + if v == nil { + return + } + return *v, true +} + +// OldData returns the old "data" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldData(ctx context.Context) (v map[string]interface{}, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldData is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldData requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldData: %w", err) + } + return oldValue.Data, nil +} + +// ResetData resets all changes to the "data" field. +func (m *CronTaskMutation) ResetData() { + m.data = nil +} + +// Where appends a list predicates to the CronTaskMutation builder. +func (m *CronTaskMutation) Where(ps ...predicate.CronTask) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the CronTaskMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *CronTaskMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.CronTask, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *CronTaskMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *CronTaskMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (CronTask). +func (m *CronTaskMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *CronTaskMutation) Fields() []string { + fields := make([]string, 0, 10) + if m.created_at != nil { + fields = append(fields, crontask.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, crontask.FieldUpdatedAt) + } + if m._type != nil { + fields = append(fields, crontask.FieldType) + } + if m.reference_id != nil { + fields = append(fields, crontask.FieldReferenceID) + } + if m.targets != nil { + fields = append(fields, crontask.FieldTargets) + } + if m.status != nil { + fields = append(fields, crontask.FieldStatus) + } + if m.scheduled_after != nil { + fields = append(fields, crontask.FieldScheduledAfter) + } + if m.last_run != nil { + fields = append(fields, crontask.FieldLastRun) + } + if m.logs != nil { + fields = append(fields, crontask.FieldLogs) + } + if m.data != nil { + fields = append(fields, crontask.FieldData) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *CronTaskMutation) Field(name string) (ent.Value, bool) { + switch name { + case crontask.FieldCreatedAt: + return m.CreatedAt() + case crontask.FieldUpdatedAt: + return m.UpdatedAt() + case crontask.FieldType: + return m.GetType() + case crontask.FieldReferenceID: + return m.ReferenceID() + case crontask.FieldTargets: + return m.Targets() + case crontask.FieldStatus: + return m.Status() + case crontask.FieldScheduledAfter: + return m.ScheduledAfter() + case crontask.FieldLastRun: + return m.LastRun() + case crontask.FieldLogs: + return m.Logs() + case crontask.FieldData: + return m.Data() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *CronTaskMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case crontask.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case crontask.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case crontask.FieldType: + return m.OldType(ctx) + case crontask.FieldReferenceID: + return m.OldReferenceID(ctx) + case crontask.FieldTargets: + return m.OldTargets(ctx) + case crontask.FieldStatus: + return m.OldStatus(ctx) + case crontask.FieldScheduledAfter: + return m.OldScheduledAfter(ctx) + case crontask.FieldLastRun: + return m.OldLastRun(ctx) + case crontask.FieldLogs: + return m.OldLogs(ctx) + case crontask.FieldData: + return m.OldData(ctx) + } + return nil, fmt.Errorf("unknown CronTask field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *CronTaskMutation) SetField(name string, value ent.Value) error { + switch name { + case crontask.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case crontask.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case crontask.FieldType: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case crontask.FieldReferenceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferenceID(v) + return nil + case crontask.FieldTargets: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTargets(v) + return nil + case crontask.FieldStatus: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStatus(v) + return nil + case crontask.FieldScheduledAfter: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetScheduledAfter(v) + return nil + case crontask.FieldLastRun: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLastRun(v) + return nil + case crontask.FieldLogs: + v, ok := value.([]models.TaskLog) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLogs(v) + return nil + case crontask.FieldData: + v, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetData(v) + return nil + } + return fmt.Errorf("unknown CronTask field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *CronTaskMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, crontask.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, crontask.FieldUpdatedAt) + } + if m.addscheduled_after != nil { + fields = append(fields, crontask.FieldScheduledAfter) + } + if m.addlast_run != nil { + fields = append(fields, crontask.FieldLastRun) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *CronTaskMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case crontask.FieldCreatedAt: + return m.AddedCreatedAt() + case crontask.FieldUpdatedAt: + return m.AddedUpdatedAt() + case crontask.FieldScheduledAfter: + return m.AddedScheduledAfter() + case crontask.FieldLastRun: + return m.AddedLastRun() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *CronTaskMutation) AddField(name string, value ent.Value) error { + switch name { + case crontask.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case crontask.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + case crontask.FieldScheduledAfter: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddScheduledAfter(v) + return nil + case crontask.FieldLastRun: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddLastRun(v) + return nil + } + return fmt.Errorf("unknown CronTask numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *CronTaskMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *CronTaskMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *CronTaskMutation) ClearField(name string) error { + return fmt.Errorf("unknown CronTask nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *CronTaskMutation) ResetField(name string) error { + switch name { + case crontask.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case crontask.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case crontask.FieldType: + m.ResetType() + return nil + case crontask.FieldReferenceID: + m.ResetReferenceID() + return nil + case crontask.FieldTargets: + m.ResetTargets() + return nil + case crontask.FieldStatus: + m.ResetStatus() + return nil + case crontask.FieldScheduledAfter: + m.ResetScheduledAfter() + return nil + case crontask.FieldLastRun: + m.ResetLastRun() + return nil + case crontask.FieldLogs: + m.ResetLogs() + return nil + case crontask.FieldData: + m.ResetData() + return nil + } + return fmt.Errorf("unknown CronTask field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *CronTaskMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *CronTaskMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *CronTaskMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *CronTaskMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *CronTaskMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *CronTaskMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *CronTaskMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown CronTask unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *CronTaskMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown CronTask edge %s", name) +} + +// UserMutation represents an operation that mutates the User nodes in the graph. +type UserMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + permissions *string + feature_flags *[]string + appendfeature_flags []string + clearedFields map[string]struct{} + subscriptions map[string]struct{} + removedsubscriptions map[string]struct{} + clearedsubscriptions bool + connections map[string]struct{} + removedconnections map[string]struct{} + clearedconnections bool + content map[string]struct{} + removedcontent map[string]struct{} + clearedcontent bool + done bool + oldValue func(context.Context) (*User, error) + predicates []predicate.User +} + +var _ ent.Mutation = (*UserMutation)(nil) + +// userOption allows management of the mutation configuration using functional options. +type userOption func(*UserMutation) + +// newUserMutation creates new mutation for the User entity. +func newUserMutation(c config, op Op, opts ...userOption) *UserMutation { + m := &UserMutation{ + config: c, + op: op, + typ: TypeUser, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserID sets the ID field of the mutation. +func withUserID(id string) userOption { + return func(m *UserMutation) { + var ( + err error + once sync.Once + value *User + ) + m.oldValue = func(ctx context.Context) (*User, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().User.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUser sets the old User of the mutation. +func withUser(node *User) userOption { + return func(m *UserMutation) { + m.oldValue = func(context.Context) (*User, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of User entities. +func (m *UserMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().User.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *UserMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *UserMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *UserMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *UserMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *UserMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *UserMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *UserMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *UserMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *UserMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *UserMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetPermissions sets the "permissions" field. +func (m *UserMutation) SetPermissions(s string) { + m.permissions = &s +} + +// Permissions returns the value of the "permissions" field in the mutation. +func (m *UserMutation) Permissions() (r string, exists bool) { + v := m.permissions + if v == nil { + return + } + return *v, true +} + +// OldPermissions returns the old "permissions" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldPermissions(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPermissions is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPermissions requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPermissions: %w", err) + } + return oldValue.Permissions, nil +} + +// ResetPermissions resets all changes to the "permissions" field. +func (m *UserMutation) ResetPermissions() { + m.permissions = nil +} + +// SetFeatureFlags sets the "feature_flags" field. +func (m *UserMutation) SetFeatureFlags(s []string) { + m.feature_flags = &s + m.appendfeature_flags = nil +} + +// FeatureFlags returns the value of the "feature_flags" field in the mutation. +func (m *UserMutation) FeatureFlags() (r []string, exists bool) { + v := m.feature_flags + if v == nil { + return + } + return *v, true +} + +// OldFeatureFlags returns the old "feature_flags" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldFeatureFlags(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFeatureFlags is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFeatureFlags requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFeatureFlags: %w", err) + } + return oldValue.FeatureFlags, nil +} + +// AppendFeatureFlags adds s to the "feature_flags" field. +func (m *UserMutation) AppendFeatureFlags(s []string) { + m.appendfeature_flags = append(m.appendfeature_flags, s...) +} + +// AppendedFeatureFlags returns the list of values that were appended to the "feature_flags" field in this mutation. +func (m *UserMutation) AppendedFeatureFlags() ([]string, bool) { + if len(m.appendfeature_flags) == 0 { + return nil, false + } + return m.appendfeature_flags, true +} + +// ClearFeatureFlags clears the value of the "feature_flags" field. +func (m *UserMutation) ClearFeatureFlags() { + m.feature_flags = nil + m.appendfeature_flags = nil + m.clearedFields[user.FieldFeatureFlags] = struct{}{} +} + +// FeatureFlagsCleared returns if the "feature_flags" field was cleared in this mutation. +func (m *UserMutation) FeatureFlagsCleared() bool { + _, ok := m.clearedFields[user.FieldFeatureFlags] + return ok +} + +// ResetFeatureFlags resets all changes to the "feature_flags" field. +func (m *UserMutation) ResetFeatureFlags() { + m.feature_flags = nil + m.appendfeature_flags = nil + delete(m.clearedFields, user.FieldFeatureFlags) +} + +// AddSubscriptionIDs adds the "subscriptions" edge to the UserSubscription entity by ids. +func (m *UserMutation) AddSubscriptionIDs(ids ...string) { + if m.subscriptions == nil { + m.subscriptions = make(map[string]struct{}) + } + for i := range ids { + m.subscriptions[ids[i]] = struct{}{} + } +} + +// ClearSubscriptions clears the "subscriptions" edge to the UserSubscription entity. +func (m *UserMutation) ClearSubscriptions() { + m.clearedsubscriptions = true +} + +// SubscriptionsCleared reports if the "subscriptions" edge to the UserSubscription entity was cleared. +func (m *UserMutation) SubscriptionsCleared() bool { + return m.clearedsubscriptions +} + +// RemoveSubscriptionIDs removes the "subscriptions" edge to the UserSubscription entity by IDs. +func (m *UserMutation) RemoveSubscriptionIDs(ids ...string) { + if m.removedsubscriptions == nil { + m.removedsubscriptions = make(map[string]struct{}) + } + for i := range ids { + delete(m.subscriptions, ids[i]) + m.removedsubscriptions[ids[i]] = struct{}{} + } +} + +// RemovedSubscriptions returns the removed IDs of the "subscriptions" edge to the UserSubscription entity. +func (m *UserMutation) RemovedSubscriptionsIDs() (ids []string) { + for id := range m.removedsubscriptions { + ids = append(ids, id) + } + return +} + +// SubscriptionsIDs returns the "subscriptions" edge IDs in the mutation. +func (m *UserMutation) SubscriptionsIDs() (ids []string) { + for id := range m.subscriptions { + ids = append(ids, id) + } + return +} + +// ResetSubscriptions resets all changes to the "subscriptions" edge. +func (m *UserMutation) ResetSubscriptions() { + m.subscriptions = nil + m.clearedsubscriptions = false + m.removedsubscriptions = nil +} + +// AddConnectionIDs adds the "connections" edge to the UserConnection entity by ids. +func (m *UserMutation) AddConnectionIDs(ids ...string) { + if m.connections == nil { + m.connections = make(map[string]struct{}) + } + for i := range ids { + m.connections[ids[i]] = struct{}{} + } +} + +// ClearConnections clears the "connections" edge to the UserConnection entity. +func (m *UserMutation) ClearConnections() { + m.clearedconnections = true +} + +// ConnectionsCleared reports if the "connections" edge to the UserConnection entity was cleared. +func (m *UserMutation) ConnectionsCleared() bool { + return m.clearedconnections +} + +// RemoveConnectionIDs removes the "connections" edge to the UserConnection entity by IDs. +func (m *UserMutation) RemoveConnectionIDs(ids ...string) { + if m.removedconnections == nil { + m.removedconnections = make(map[string]struct{}) + } + for i := range ids { + delete(m.connections, ids[i]) + m.removedconnections[ids[i]] = struct{}{} + } +} + +// RemovedConnections returns the removed IDs of the "connections" edge to the UserConnection entity. +func (m *UserMutation) RemovedConnectionsIDs() (ids []string) { + for id := range m.removedconnections { + ids = append(ids, id) + } + return +} + +// ConnectionsIDs returns the "connections" edge IDs in the mutation. +func (m *UserMutation) ConnectionsIDs() (ids []string) { + for id := range m.connections { + ids = append(ids, id) + } + return +} + +// ResetConnections resets all changes to the "connections" edge. +func (m *UserMutation) ResetConnections() { + m.connections = nil + m.clearedconnections = false + m.removedconnections = nil +} + +// AddContentIDs adds the "content" edge to the UserContent entity by ids. +func (m *UserMutation) AddContentIDs(ids ...string) { + if m.content == nil { + m.content = make(map[string]struct{}) + } + for i := range ids { + m.content[ids[i]] = struct{}{} + } +} + +// ClearContent clears the "content" edge to the UserContent entity. +func (m *UserMutation) ClearContent() { + m.clearedcontent = true +} + +// ContentCleared reports if the "content" edge to the UserContent entity was cleared. +func (m *UserMutation) ContentCleared() bool { + return m.clearedcontent +} + +// RemoveContentIDs removes the "content" edge to the UserContent entity by IDs. +func (m *UserMutation) RemoveContentIDs(ids ...string) { + if m.removedcontent == nil { + m.removedcontent = make(map[string]struct{}) + } + for i := range ids { + delete(m.content, ids[i]) + m.removedcontent[ids[i]] = struct{}{} + } +} + +// RemovedContent returns the removed IDs of the "content" edge to the UserContent entity. +func (m *UserMutation) RemovedContentIDs() (ids []string) { + for id := range m.removedcontent { + ids = append(ids, id) + } + return +} + +// ContentIDs returns the "content" edge IDs in the mutation. +func (m *UserMutation) ContentIDs() (ids []string) { + for id := range m.content { + ids = append(ids, id) + } + return +} + +// ResetContent resets all changes to the "content" edge. +func (m *UserMutation) ResetContent() { + m.content = nil + m.clearedcontent = false + m.removedcontent = nil +} + +// Where appends a list predicates to the UserMutation builder. +func (m *UserMutation) Where(ps ...predicate.User) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.User, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (User). +func (m *UserMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserMutation) Fields() []string { + fields := make([]string, 0, 4) + if m.created_at != nil { + fields = append(fields, user.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, user.FieldUpdatedAt) + } + if m.permissions != nil { + fields = append(fields, user.FieldPermissions) + } + if m.feature_flags != nil { + fields = append(fields, user.FieldFeatureFlags) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserMutation) Field(name string) (ent.Value, bool) { + switch name { + case user.FieldCreatedAt: + return m.CreatedAt() + case user.FieldUpdatedAt: + return m.UpdatedAt() + case user.FieldPermissions: + return m.Permissions() + case user.FieldFeatureFlags: + return m.FeatureFlags() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case user.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case user.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case user.FieldPermissions: + return m.OldPermissions(ctx) + case user.FieldFeatureFlags: + return m.OldFeatureFlags(ctx) + } + return nil, fmt.Errorf("unknown User field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserMutation) SetField(name string, value ent.Value) error { + switch name { + case user.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case user.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case user.FieldPermissions: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPermissions(v) + return nil + case user.FieldFeatureFlags: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFeatureFlags(v) + return nil + } + return fmt.Errorf("unknown User field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, user.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, user.FieldUpdatedAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case user.FieldCreatedAt: + return m.AddedCreatedAt() + case user.FieldUpdatedAt: + return m.AddedUpdatedAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserMutation) AddField(name string, value ent.Value) error { + switch name { + case user.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case user.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown User numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(user.FieldFeatureFlags) { + fields = append(fields, user.FieldFeatureFlags) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserMutation) ClearField(name string) error { + switch name { + case user.FieldFeatureFlags: + m.ClearFeatureFlags() + return nil + } + return fmt.Errorf("unknown User nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserMutation) ResetField(name string) error { + switch name { + case user.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case user.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case user.FieldPermissions: + m.ResetPermissions() + return nil + case user.FieldFeatureFlags: + m.ResetFeatureFlags() + return nil + } + return fmt.Errorf("unknown User field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserMutation) AddedEdges() []string { + edges := make([]string, 0, 3) + if m.subscriptions != nil { + edges = append(edges, user.EdgeSubscriptions) + } + if m.connections != nil { + edges = append(edges, user.EdgeConnections) + } + if m.content != nil { + edges = append(edges, user.EdgeContent) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserMutation) AddedIDs(name string) []ent.Value { + switch name { + case user.EdgeSubscriptions: + ids := make([]ent.Value, 0, len(m.subscriptions)) + for id := range m.subscriptions { + ids = append(ids, id) + } + return ids + case user.EdgeConnections: + ids := make([]ent.Value, 0, len(m.connections)) + for id := range m.connections { + ids = append(ids, id) + } + return ids + case user.EdgeContent: + ids := make([]ent.Value, 0, len(m.content)) + for id := range m.content { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserMutation) RemovedEdges() []string { + edges := make([]string, 0, 3) + if m.removedsubscriptions != nil { + edges = append(edges, user.EdgeSubscriptions) + } + if m.removedconnections != nil { + edges = append(edges, user.EdgeConnections) + } + if m.removedcontent != nil { + edges = append(edges, user.EdgeContent) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserMutation) RemovedIDs(name string) []ent.Value { + switch name { + case user.EdgeSubscriptions: + ids := make([]ent.Value, 0, len(m.removedsubscriptions)) + for id := range m.removedsubscriptions { + ids = append(ids, id) + } + return ids + case user.EdgeConnections: + ids := make([]ent.Value, 0, len(m.removedconnections)) + for id := range m.removedconnections { + ids = append(ids, id) + } + return ids + case user.EdgeContent: + ids := make([]ent.Value, 0, len(m.removedcontent)) + for id := range m.removedcontent { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserMutation) ClearedEdges() []string { + edges := make([]string, 0, 3) + if m.clearedsubscriptions { + edges = append(edges, user.EdgeSubscriptions) + } + if m.clearedconnections { + edges = append(edges, user.EdgeConnections) + } + if m.clearedcontent { + edges = append(edges, user.EdgeContent) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserMutation) EdgeCleared(name string) bool { + switch name { + case user.EdgeSubscriptions: + return m.clearedsubscriptions + case user.EdgeConnections: + return m.clearedconnections + case user.EdgeContent: + return m.clearedcontent + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserMutation) ClearEdge(name string) error { + switch name { + } + return fmt.Errorf("unknown User unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserMutation) ResetEdge(name string) error { + switch name { + case user.EdgeSubscriptions: + m.ResetSubscriptions() + return nil + case user.EdgeConnections: + m.ResetConnections() + return nil + case user.EdgeContent: + m.ResetContent() + return nil + } + return fmt.Errorf("unknown User edge %s", name) +} + +// UserConnectionMutation represents an operation that mutates the UserConnection nodes in the graph. +type UserConnectionMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + _type *models.ConnectionType + reference_id *string + permissions *string + metadata *map[string]interface{} + clearedFields map[string]struct{} + user *string + cleareduser bool + done bool + oldValue func(context.Context) (*UserConnection, error) + predicates []predicate.UserConnection +} + +var _ ent.Mutation = (*UserConnectionMutation)(nil) + +// userconnectionOption allows management of the mutation configuration using functional options. +type userconnectionOption func(*UserConnectionMutation) + +// newUserConnectionMutation creates new mutation for the UserConnection entity. +func newUserConnectionMutation(c config, op Op, opts ...userconnectionOption) *UserConnectionMutation { + m := &UserConnectionMutation{ + config: c, + op: op, + typ: TypeUserConnection, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserConnectionID sets the ID field of the mutation. +func withUserConnectionID(id string) userconnectionOption { + return func(m *UserConnectionMutation) { + var ( + err error + once sync.Once + value *UserConnection + ) + m.oldValue = func(ctx context.Context) (*UserConnection, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().UserConnection.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUserConnection sets the old UserConnection of the mutation. +func withUserConnection(node *UserConnection) userconnectionOption { + return func(m *UserConnectionMutation) { + m.oldValue = func(context.Context) (*UserConnection, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserConnectionMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserConnectionMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of UserConnection entities. +func (m *UserConnectionMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserConnectionMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserConnectionMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().UserConnection.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *UserConnectionMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *UserConnectionMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the UserConnection entity. +// If the UserConnection object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserConnectionMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *UserConnectionMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *UserConnectionMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *UserConnectionMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *UserConnectionMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *UserConnectionMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the UserConnection entity. +// If the UserConnection object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserConnectionMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *UserConnectionMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *UserConnectionMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *UserConnectionMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetType sets the "type" field. +func (m *UserConnectionMutation) SetType(mt models.ConnectionType) { + m._type = &mt +} + +// GetType returns the value of the "type" field in the mutation. +func (m *UserConnectionMutation) GetType() (r models.ConnectionType, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the UserConnection entity. +// If the UserConnection object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserConnectionMutation) OldType(ctx context.Context) (v models.ConnectionType, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ResetType resets all changes to the "type" field. +func (m *UserConnectionMutation) ResetType() { + m._type = nil +} + +// SetUserID sets the "user_id" field. +func (m *UserConnectionMutation) SetUserID(s string) { + m.user = &s +} + +// UserID returns the value of the "user_id" field in the mutation. +func (m *UserConnectionMutation) UserID() (r string, exists bool) { + v := m.user + if v == nil { + return + } + return *v, true +} + +// OldUserID returns the old "user_id" field's value of the UserConnection entity. +// If the UserConnection object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserConnectionMutation) OldUserID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserID: %w", err) + } + return oldValue.UserID, nil +} + +// ResetUserID resets all changes to the "user_id" field. +func (m *UserConnectionMutation) ResetUserID() { + m.user = nil +} + +// SetReferenceID sets the "reference_id" field. +func (m *UserConnectionMutation) SetReferenceID(s string) { + m.reference_id = &s +} + +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *UserConnectionMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id + if v == nil { + return + } + return *v, true +} + +// OldReferenceID returns the old "reference_id" field's value of the UserConnection entity. +// If the UserConnection object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserConnectionMutation) OldReferenceID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferenceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + } + return oldValue.ReferenceID, nil +} + +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *UserConnectionMutation) ResetReferenceID() { + m.reference_id = nil +} + +// SetPermissions sets the "permissions" field. +func (m *UserConnectionMutation) SetPermissions(s string) { + m.permissions = &s +} + +// Permissions returns the value of the "permissions" field in the mutation. +func (m *UserConnectionMutation) Permissions() (r string, exists bool) { + v := m.permissions + if v == nil { + return + } + return *v, true +} + +// OldPermissions returns the old "permissions" field's value of the UserConnection entity. +// If the UserConnection object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserConnectionMutation) OldPermissions(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPermissions is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPermissions requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPermissions: %w", err) + } + return oldValue.Permissions, nil +} + +// ClearPermissions clears the value of the "permissions" field. +func (m *UserConnectionMutation) ClearPermissions() { + m.permissions = nil + m.clearedFields[userconnection.FieldPermissions] = struct{}{} +} + +// PermissionsCleared returns if the "permissions" field was cleared in this mutation. +func (m *UserConnectionMutation) PermissionsCleared() bool { + _, ok := m.clearedFields[userconnection.FieldPermissions] + return ok +} + +// ResetPermissions resets all changes to the "permissions" field. +func (m *UserConnectionMutation) ResetPermissions() { + m.permissions = nil + delete(m.clearedFields, userconnection.FieldPermissions) +} + +// SetMetadata sets the "metadata" field. +func (m *UserConnectionMutation) SetMetadata(value map[string]interface{}) { + m.metadata = &value +} + +// Metadata returns the value of the "metadata" field in the mutation. +func (m *UserConnectionMutation) Metadata() (r map[string]interface{}, exists bool) { + v := m.metadata + if v == nil { + return + } + return *v, true +} + +// OldMetadata returns the old "metadata" field's value of the UserConnection entity. +// If the UserConnection object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserConnectionMutation) OldMetadata(ctx context.Context) (v map[string]interface{}, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMetadata is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMetadata requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMetadata: %w", err) + } + return oldValue.Metadata, nil +} + +// ClearMetadata clears the value of the "metadata" field. +func (m *UserConnectionMutation) ClearMetadata() { + m.metadata = nil + m.clearedFields[userconnection.FieldMetadata] = struct{}{} +} + +// MetadataCleared returns if the "metadata" field was cleared in this mutation. +func (m *UserConnectionMutation) MetadataCleared() bool { + _, ok := m.clearedFields[userconnection.FieldMetadata] + return ok +} + +// ResetMetadata resets all changes to the "metadata" field. +func (m *UserConnectionMutation) ResetMetadata() { + m.metadata = nil + delete(m.clearedFields, userconnection.FieldMetadata) +} + +// ClearUser clears the "user" edge to the User entity. +func (m *UserConnectionMutation) ClearUser() { + m.cleareduser = true + m.clearedFields[userconnection.FieldUserID] = struct{}{} +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *UserConnectionMutation) UserCleared() bool { + return m.cleareduser +} + +// UserIDs returns the "user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserID instead. It exists only for internal usage by the builders. +func (m *UserConnectionMutation) UserIDs() (ids []string) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *UserConnectionMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// Where appends a list predicates to the UserConnectionMutation builder. +func (m *UserConnectionMutation) Where(ps ...predicate.UserConnection) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserConnectionMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserConnectionMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.UserConnection, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserConnectionMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserConnectionMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (UserConnection). +func (m *UserConnectionMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserConnectionMutation) Fields() []string { + fields := make([]string, 0, 7) + if m.created_at != nil { + fields = append(fields, userconnection.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, userconnection.FieldUpdatedAt) + } + if m._type != nil { + fields = append(fields, userconnection.FieldType) + } + if m.user != nil { + fields = append(fields, userconnection.FieldUserID) + } + if m.reference_id != nil { + fields = append(fields, userconnection.FieldReferenceID) + } + if m.permissions != nil { + fields = append(fields, userconnection.FieldPermissions) + } + if m.metadata != nil { + fields = append(fields, userconnection.FieldMetadata) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserConnectionMutation) Field(name string) (ent.Value, bool) { + switch name { + case userconnection.FieldCreatedAt: + return m.CreatedAt() + case userconnection.FieldUpdatedAt: + return m.UpdatedAt() + case userconnection.FieldType: + return m.GetType() + case userconnection.FieldUserID: + return m.UserID() + case userconnection.FieldReferenceID: + return m.ReferenceID() + case userconnection.FieldPermissions: + return m.Permissions() + case userconnection.FieldMetadata: + return m.Metadata() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserConnectionMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case userconnection.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case userconnection.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case userconnection.FieldType: + return m.OldType(ctx) + case userconnection.FieldUserID: + return m.OldUserID(ctx) + case userconnection.FieldReferenceID: + return m.OldReferenceID(ctx) + case userconnection.FieldPermissions: + return m.OldPermissions(ctx) + case userconnection.FieldMetadata: + return m.OldMetadata(ctx) + } + return nil, fmt.Errorf("unknown UserConnection field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserConnectionMutation) SetField(name string, value ent.Value) error { + switch name { + case userconnection.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case userconnection.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case userconnection.FieldType: + v, ok := value.(models.ConnectionType) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case userconnection.FieldUserID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserID(v) + return nil + case userconnection.FieldReferenceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferenceID(v) + return nil + case userconnection.FieldPermissions: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPermissions(v) + return nil + case userconnection.FieldMetadata: + v, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMetadata(v) + return nil + } + return fmt.Errorf("unknown UserConnection field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserConnectionMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, userconnection.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, userconnection.FieldUpdatedAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserConnectionMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case userconnection.FieldCreatedAt: + return m.AddedCreatedAt() + case userconnection.FieldUpdatedAt: + return m.AddedUpdatedAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserConnectionMutation) AddField(name string, value ent.Value) error { + switch name { + case userconnection.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case userconnection.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown UserConnection numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserConnectionMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(userconnection.FieldPermissions) { + fields = append(fields, userconnection.FieldPermissions) + } + if m.FieldCleared(userconnection.FieldMetadata) { + fields = append(fields, userconnection.FieldMetadata) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserConnectionMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserConnectionMutation) ClearField(name string) error { + switch name { + case userconnection.FieldPermissions: + m.ClearPermissions() + return nil + case userconnection.FieldMetadata: + m.ClearMetadata() + return nil + } + return fmt.Errorf("unknown UserConnection nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserConnectionMutation) ResetField(name string) error { + switch name { + case userconnection.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case userconnection.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case userconnection.FieldType: + m.ResetType() + return nil + case userconnection.FieldUserID: + m.ResetUserID() + return nil + case userconnection.FieldReferenceID: + m.ResetReferenceID() + return nil + case userconnection.FieldPermissions: + m.ResetPermissions() + return nil + case userconnection.FieldMetadata: + m.ResetMetadata() + return nil + } + return fmt.Errorf("unknown UserConnection field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserConnectionMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.user != nil { + edges = append(edges, userconnection.EdgeUser) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserConnectionMutation) AddedIDs(name string) []ent.Value { + switch name { + case userconnection.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserConnectionMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserConnectionMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserConnectionMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.cleareduser { + edges = append(edges, userconnection.EdgeUser) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserConnectionMutation) EdgeCleared(name string) bool { + switch name { + case userconnection.EdgeUser: + return m.cleareduser + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserConnectionMutation) ClearEdge(name string) error { + switch name { + case userconnection.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown UserConnection unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserConnectionMutation) ResetEdge(name string) error { + switch name { + case userconnection.EdgeUser: + m.ResetUser() + return nil + } + return fmt.Errorf("unknown UserConnection edge %s", name) +} + +// UserContentMutation represents an operation that mutates the UserContent nodes in the graph. +type UserContentMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + _type *models.UserContentType + reference_id *string + value *any + metadata *map[string]interface{} + clearedFields map[string]struct{} + user *string + cleareduser bool + done bool + oldValue func(context.Context) (*UserContent, error) + predicates []predicate.UserContent +} + +var _ ent.Mutation = (*UserContentMutation)(nil) + +// usercontentOption allows management of the mutation configuration using functional options. +type usercontentOption func(*UserContentMutation) + +// newUserContentMutation creates new mutation for the UserContent entity. +func newUserContentMutation(c config, op Op, opts ...usercontentOption) *UserContentMutation { + m := &UserContentMutation{ + config: c, + op: op, + typ: TypeUserContent, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserContentID sets the ID field of the mutation. +func withUserContentID(id string) usercontentOption { + return func(m *UserContentMutation) { + var ( + err error + once sync.Once + value *UserContent + ) + m.oldValue = func(ctx context.Context) (*UserContent, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().UserContent.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUserContent sets the old UserContent of the mutation. +func withUserContent(node *UserContent) usercontentOption { + return func(m *UserContentMutation) { + m.oldValue = func(context.Context) (*UserContent, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserContentMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserContentMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of UserContent entities. +func (m *UserContentMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserContentMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserContentMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().UserContent.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *UserContentMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *UserContentMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the UserContent entity. +// If the UserContent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserContentMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *UserContentMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *UserContentMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *UserContentMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *UserContentMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *UserContentMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the UserContent entity. +// If the UserContent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserContentMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *UserContentMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *UserContentMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *UserContentMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetType sets the "type" field. +func (m *UserContentMutation) SetType(mct models.UserContentType) { + m._type = &mct +} + +// GetType returns the value of the "type" field in the mutation. +func (m *UserContentMutation) GetType() (r models.UserContentType, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the UserContent entity. +// If the UserContent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserContentMutation) OldType(ctx context.Context) (v models.UserContentType, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ResetType resets all changes to the "type" field. +func (m *UserContentMutation) ResetType() { + m._type = nil +} + +// SetUserID sets the "user_id" field. +func (m *UserContentMutation) SetUserID(s string) { + m.user = &s +} + +// UserID returns the value of the "user_id" field in the mutation. +func (m *UserContentMutation) UserID() (r string, exists bool) { + v := m.user + if v == nil { + return + } + return *v, true +} + +// OldUserID returns the old "user_id" field's value of the UserContent entity. +// If the UserContent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserContentMutation) OldUserID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserID: %w", err) + } + return oldValue.UserID, nil +} + +// ResetUserID resets all changes to the "user_id" field. +func (m *UserContentMutation) ResetUserID() { + m.user = nil +} + +// SetReferenceID sets the "reference_id" field. +func (m *UserContentMutation) SetReferenceID(s string) { + m.reference_id = &s +} + +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *UserContentMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id + if v == nil { + return + } + return *v, true +} + +// OldReferenceID returns the old "reference_id" field's value of the UserContent entity. +// If the UserContent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserContentMutation) OldReferenceID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferenceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + } + return oldValue.ReferenceID, nil +} + +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *UserContentMutation) ResetReferenceID() { + m.reference_id = nil +} + +// SetValue sets the "value" field. +func (m *UserContentMutation) SetValue(a any) { + m.value = &a +} + +// Value returns the value of the "value" field in the mutation. +func (m *UserContentMutation) Value() (r any, exists bool) { + v := m.value + if v == nil { + return + } + return *v, true +} + +// OldValue returns the old "value" field's value of the UserContent entity. +// If the UserContent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserContentMutation) OldValue(ctx context.Context) (v any, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldValue is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldValue requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldValue: %w", err) + } + return oldValue.Value, nil +} + +// ResetValue resets all changes to the "value" field. +func (m *UserContentMutation) ResetValue() { + m.value = nil +} + +// SetMetadata sets the "metadata" field. +func (m *UserContentMutation) SetMetadata(value map[string]interface{}) { + m.metadata = &value +} + +// Metadata returns the value of the "metadata" field in the mutation. +func (m *UserContentMutation) Metadata() (r map[string]interface{}, exists bool) { + v := m.metadata + if v == nil { + return + } + return *v, true +} + +// OldMetadata returns the old "metadata" field's value of the UserContent entity. +// If the UserContent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserContentMutation) OldMetadata(ctx context.Context) (v map[string]interface{}, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMetadata is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMetadata requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMetadata: %w", err) + } + return oldValue.Metadata, nil +} + +// ResetMetadata resets all changes to the "metadata" field. +func (m *UserContentMutation) ResetMetadata() { + m.metadata = nil +} + +// ClearUser clears the "user" edge to the User entity. +func (m *UserContentMutation) ClearUser() { + m.cleareduser = true + m.clearedFields[usercontent.FieldUserID] = struct{}{} +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *UserContentMutation) UserCleared() bool { + return m.cleareduser +} + +// UserIDs returns the "user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserID instead. It exists only for internal usage by the builders. +func (m *UserContentMutation) UserIDs() (ids []string) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *UserContentMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// Where appends a list predicates to the UserContentMutation builder. +func (m *UserContentMutation) Where(ps ...predicate.UserContent) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserContentMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserContentMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.UserContent, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserContentMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserContentMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (UserContent). +func (m *UserContentMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserContentMutation) Fields() []string { + fields := make([]string, 0, 7) + if m.created_at != nil { + fields = append(fields, usercontent.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, usercontent.FieldUpdatedAt) + } + if m._type != nil { + fields = append(fields, usercontent.FieldType) + } + if m.user != nil { + fields = append(fields, usercontent.FieldUserID) + } + if m.reference_id != nil { + fields = append(fields, usercontent.FieldReferenceID) + } + if m.value != nil { + fields = append(fields, usercontent.FieldValue) + } + if m.metadata != nil { + fields = append(fields, usercontent.FieldMetadata) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserContentMutation) Field(name string) (ent.Value, bool) { + switch name { + case usercontent.FieldCreatedAt: + return m.CreatedAt() + case usercontent.FieldUpdatedAt: + return m.UpdatedAt() + case usercontent.FieldType: + return m.GetType() + case usercontent.FieldUserID: + return m.UserID() + case usercontent.FieldReferenceID: + return m.ReferenceID() + case usercontent.FieldValue: + return m.Value() + case usercontent.FieldMetadata: + return m.Metadata() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserContentMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case usercontent.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case usercontent.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case usercontent.FieldType: + return m.OldType(ctx) + case usercontent.FieldUserID: + return m.OldUserID(ctx) + case usercontent.FieldReferenceID: + return m.OldReferenceID(ctx) + case usercontent.FieldValue: + return m.OldValue(ctx) + case usercontent.FieldMetadata: + return m.OldMetadata(ctx) + } + return nil, fmt.Errorf("unknown UserContent field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserContentMutation) SetField(name string, value ent.Value) error { + switch name { + case usercontent.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case usercontent.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case usercontent.FieldType: + v, ok := value.(models.UserContentType) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case usercontent.FieldUserID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserID(v) + return nil + case usercontent.FieldReferenceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferenceID(v) + return nil + case usercontent.FieldValue: + v, ok := value.(any) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetValue(v) + return nil + case usercontent.FieldMetadata: + v, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMetadata(v) + return nil + } + return fmt.Errorf("unknown UserContent field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserContentMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, usercontent.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, usercontent.FieldUpdatedAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserContentMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case usercontent.FieldCreatedAt: + return m.AddedCreatedAt() + case usercontent.FieldUpdatedAt: + return m.AddedUpdatedAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserContentMutation) AddField(name string, value ent.Value) error { + switch name { + case usercontent.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case usercontent.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown UserContent numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserContentMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserContentMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserContentMutation) ClearField(name string) error { + return fmt.Errorf("unknown UserContent nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserContentMutation) ResetField(name string) error { + switch name { + case usercontent.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case usercontent.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case usercontent.FieldType: + m.ResetType() + return nil + case usercontent.FieldUserID: + m.ResetUserID() + return nil + case usercontent.FieldReferenceID: + m.ResetReferenceID() + return nil + case usercontent.FieldValue: + m.ResetValue() + return nil + case usercontent.FieldMetadata: + m.ResetMetadata() + return nil + } + return fmt.Errorf("unknown UserContent field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserContentMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.user != nil { + edges = append(edges, usercontent.EdgeUser) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserContentMutation) AddedIDs(name string) []ent.Value { + switch name { + case usercontent.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserContentMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserContentMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserContentMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.cleareduser { + edges = append(edges, usercontent.EdgeUser) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserContentMutation) EdgeCleared(name string) bool { + switch name { + case usercontent.EdgeUser: + return m.cleareduser + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserContentMutation) ClearEdge(name string) error { + switch name { + case usercontent.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown UserContent unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserContentMutation) ResetEdge(name string) error { + switch name { + case usercontent.EdgeUser: + m.ResetUser() + return nil + } + return fmt.Errorf("unknown UserContent edge %s", name) +} + +// UserSubscriptionMutation represents an operation that mutates the UserSubscription nodes in the graph. +type UserSubscriptionMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + _type *models.SubscriptionType + expires_at *int + addexpires_at *int + permissions *string + reference_id *string + clearedFields map[string]struct{} + user *string + cleareduser bool + done bool + oldValue func(context.Context) (*UserSubscription, error) + predicates []predicate.UserSubscription +} + +var _ ent.Mutation = (*UserSubscriptionMutation)(nil) + +// usersubscriptionOption allows management of the mutation configuration using functional options. +type usersubscriptionOption func(*UserSubscriptionMutation) + +// newUserSubscriptionMutation creates new mutation for the UserSubscription entity. +func newUserSubscriptionMutation(c config, op Op, opts ...usersubscriptionOption) *UserSubscriptionMutation { + m := &UserSubscriptionMutation{ + config: c, + op: op, + typ: TypeUserSubscription, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserSubscriptionID sets the ID field of the mutation. +func withUserSubscriptionID(id string) usersubscriptionOption { + return func(m *UserSubscriptionMutation) { + var ( + err error + once sync.Once + value *UserSubscription + ) + m.oldValue = func(ctx context.Context) (*UserSubscription, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().UserSubscription.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUserSubscription sets the old UserSubscription of the mutation. +func withUserSubscription(node *UserSubscription) usersubscriptionOption { + return func(m *UserSubscriptionMutation) { + m.oldValue = func(context.Context) (*UserSubscription, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserSubscriptionMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserSubscriptionMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of UserSubscription entities. +func (m *UserSubscriptionMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserSubscriptionMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserSubscriptionMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().UserSubscription.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *UserSubscriptionMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *UserSubscriptionMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the UserSubscription entity. +// If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *UserSubscriptionMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *UserSubscriptionMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *UserSubscriptionMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *UserSubscriptionMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *UserSubscriptionMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the UserSubscription entity. +// If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *UserSubscriptionMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *UserSubscriptionMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *UserSubscriptionMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetType sets the "type" field. +func (m *UserSubscriptionMutation) SetType(mt models.SubscriptionType) { + m._type = &mt +} + +// GetType returns the value of the "type" field in the mutation. +func (m *UserSubscriptionMutation) GetType() (r models.SubscriptionType, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the UserSubscription entity. +// If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionMutation) OldType(ctx context.Context) (v models.SubscriptionType, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ResetType resets all changes to the "type" field. +func (m *UserSubscriptionMutation) ResetType() { + m._type = nil +} + +// SetExpiresAt sets the "expires_at" field. +func (m *UserSubscriptionMutation) SetExpiresAt(i int) { + m.expires_at = &i + m.addexpires_at = nil +} + +// ExpiresAt returns the value of the "expires_at" field in the mutation. +func (m *UserSubscriptionMutation) ExpiresAt() (r int, exists bool) { + v := m.expires_at + if v == nil { + return + } + return *v, true +} + +// OldExpiresAt returns the old "expires_at" field's value of the UserSubscription entity. +// If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionMutation) OldExpiresAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldExpiresAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldExpiresAt: %w", err) + } + return oldValue.ExpiresAt, nil +} + +// AddExpiresAt adds i to the "expires_at" field. +func (m *UserSubscriptionMutation) AddExpiresAt(i int) { + if m.addexpires_at != nil { + *m.addexpires_at += i + } else { + m.addexpires_at = &i + } +} + +// AddedExpiresAt returns the value that was added to the "expires_at" field in this mutation. +func (m *UserSubscriptionMutation) AddedExpiresAt() (r int, exists bool) { + v := m.addexpires_at + if v == nil { + return + } + return *v, true +} + +// ResetExpiresAt resets all changes to the "expires_at" field. +func (m *UserSubscriptionMutation) ResetExpiresAt() { + m.expires_at = nil + m.addexpires_at = nil +} + +// SetUserID sets the "user_id" field. +func (m *UserSubscriptionMutation) SetUserID(s string) { + m.user = &s +} + +// UserID returns the value of the "user_id" field in the mutation. +func (m *UserSubscriptionMutation) UserID() (r string, exists bool) { + v := m.user + if v == nil { + return + } + return *v, true +} + +// OldUserID returns the old "user_id" field's value of the UserSubscription entity. +// If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionMutation) OldUserID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserID: %w", err) + } + return oldValue.UserID, nil +} + +// ResetUserID resets all changes to the "user_id" field. +func (m *UserSubscriptionMutation) ResetUserID() { + m.user = nil +} + +// SetPermissions sets the "permissions" field. +func (m *UserSubscriptionMutation) SetPermissions(s string) { + m.permissions = &s +} + +// Permissions returns the value of the "permissions" field in the mutation. +func (m *UserSubscriptionMutation) Permissions() (r string, exists bool) { + v := m.permissions + if v == nil { + return + } + return *v, true +} + +// OldPermissions returns the old "permissions" field's value of the UserSubscription entity. +// If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionMutation) OldPermissions(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPermissions is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPermissions requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPermissions: %w", err) + } + return oldValue.Permissions, nil +} + +// ResetPermissions resets all changes to the "permissions" field. +func (m *UserSubscriptionMutation) ResetPermissions() { + m.permissions = nil +} + +// SetReferenceID sets the "reference_id" field. +func (m *UserSubscriptionMutation) SetReferenceID(s string) { + m.reference_id = &s +} + +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *UserSubscriptionMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id + if v == nil { + return + } + return *v, true +} + +// OldReferenceID returns the old "reference_id" field's value of the UserSubscription entity. +// If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionMutation) OldReferenceID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferenceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + } + return oldValue.ReferenceID, nil +} + +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *UserSubscriptionMutation) ResetReferenceID() { + m.reference_id = nil +} + +// ClearUser clears the "user" edge to the User entity. +func (m *UserSubscriptionMutation) ClearUser() { + m.cleareduser = true + m.clearedFields[usersubscription.FieldUserID] = struct{}{} +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *UserSubscriptionMutation) UserCleared() bool { + return m.cleareduser +} + +// UserIDs returns the "user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserID instead. It exists only for internal usage by the builders. +func (m *UserSubscriptionMutation) UserIDs() (ids []string) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *UserSubscriptionMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// Where appends a list predicates to the UserSubscriptionMutation builder. +func (m *UserSubscriptionMutation) Where(ps ...predicate.UserSubscription) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserSubscriptionMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserSubscriptionMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.UserSubscription, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserSubscriptionMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserSubscriptionMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (UserSubscription). +func (m *UserSubscriptionMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserSubscriptionMutation) Fields() []string { + fields := make([]string, 0, 7) + if m.created_at != nil { + fields = append(fields, usersubscription.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, usersubscription.FieldUpdatedAt) + } + if m._type != nil { + fields = append(fields, usersubscription.FieldType) + } + if m.expires_at != nil { + fields = append(fields, usersubscription.FieldExpiresAt) + } + if m.user != nil { + fields = append(fields, usersubscription.FieldUserID) + } + if m.permissions != nil { + fields = append(fields, usersubscription.FieldPermissions) + } + if m.reference_id != nil { + fields = append(fields, usersubscription.FieldReferenceID) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserSubscriptionMutation) Field(name string) (ent.Value, bool) { + switch name { + case usersubscription.FieldCreatedAt: + return m.CreatedAt() + case usersubscription.FieldUpdatedAt: + return m.UpdatedAt() + case usersubscription.FieldType: + return m.GetType() + case usersubscription.FieldExpiresAt: + return m.ExpiresAt() + case usersubscription.FieldUserID: + return m.UserID() + case usersubscription.FieldPermissions: + return m.Permissions() + case usersubscription.FieldReferenceID: + return m.ReferenceID() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserSubscriptionMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case usersubscription.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case usersubscription.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case usersubscription.FieldType: + return m.OldType(ctx) + case usersubscription.FieldExpiresAt: + return m.OldExpiresAt(ctx) + case usersubscription.FieldUserID: + return m.OldUserID(ctx) + case usersubscription.FieldPermissions: + return m.OldPermissions(ctx) + case usersubscription.FieldReferenceID: + return m.OldReferenceID(ctx) + } + return nil, fmt.Errorf("unknown UserSubscription field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserSubscriptionMutation) SetField(name string, value ent.Value) error { + switch name { + case usersubscription.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case usersubscription.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case usersubscription.FieldType: + v, ok := value.(models.SubscriptionType) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case usersubscription.FieldExpiresAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetExpiresAt(v) + return nil + case usersubscription.FieldUserID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserID(v) + return nil + case usersubscription.FieldPermissions: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPermissions(v) + return nil + case usersubscription.FieldReferenceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferenceID(v) + return nil + } + return fmt.Errorf("unknown UserSubscription field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserSubscriptionMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, usersubscription.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, usersubscription.FieldUpdatedAt) + } + if m.addexpires_at != nil { + fields = append(fields, usersubscription.FieldExpiresAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserSubscriptionMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case usersubscription.FieldCreatedAt: + return m.AddedCreatedAt() + case usersubscription.FieldUpdatedAt: + return m.AddedUpdatedAt() + case usersubscription.FieldExpiresAt: + return m.AddedExpiresAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserSubscriptionMutation) AddField(name string, value ent.Value) error { + switch name { + case usersubscription.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case usersubscription.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + case usersubscription.FieldExpiresAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddExpiresAt(v) + return nil + } + return fmt.Errorf("unknown UserSubscription numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserSubscriptionMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserSubscriptionMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserSubscriptionMutation) ClearField(name string) error { + return fmt.Errorf("unknown UserSubscription nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserSubscriptionMutation) ResetField(name string) error { + switch name { + case usersubscription.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case usersubscription.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case usersubscription.FieldType: + m.ResetType() + return nil + case usersubscription.FieldExpiresAt: + m.ResetExpiresAt() + return nil + case usersubscription.FieldUserID: + m.ResetUserID() + return nil + case usersubscription.FieldPermissions: + m.ResetPermissions() + return nil + case usersubscription.FieldReferenceID: + m.ResetReferenceID() + return nil + } + return fmt.Errorf("unknown UserSubscription field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserSubscriptionMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.user != nil { + edges = append(edges, usersubscription.EdgeUser) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserSubscriptionMutation) AddedIDs(name string) []ent.Value { + switch name { + case usersubscription.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserSubscriptionMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserSubscriptionMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserSubscriptionMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.cleareduser { + edges = append(edges, usersubscription.EdgeUser) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserSubscriptionMutation) EdgeCleared(name string) bool { + switch name { + case usersubscription.EdgeUser: + return m.cleareduser + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserSubscriptionMutation) ClearEdge(name string) error { + switch name { + case usersubscription.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown UserSubscription unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserSubscriptionMutation) ResetEdge(name string) error { + switch name { + case usersubscription.EdgeUser: + m.ResetUser() + return nil + } + return fmt.Errorf("unknown UserSubscription edge %s", name) +} + +// VehicleMutation represents an operation that mutates the Vehicle nodes in the graph. +type VehicleMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + tier *int + addtier *int + localized_names *map[string]string + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*Vehicle, error) + predicates []predicate.Vehicle +} + +var _ ent.Mutation = (*VehicleMutation)(nil) + +// vehicleOption allows management of the mutation configuration using functional options. +type vehicleOption func(*VehicleMutation) + +// newVehicleMutation creates new mutation for the Vehicle entity. +func newVehicleMutation(c config, op Op, opts ...vehicleOption) *VehicleMutation { + m := &VehicleMutation{ + config: c, + op: op, + typ: TypeVehicle, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withVehicleID sets the ID field of the mutation. +func withVehicleID(id string) vehicleOption { + return func(m *VehicleMutation) { + var ( + err error + once sync.Once + value *Vehicle + ) + m.oldValue = func(ctx context.Context) (*Vehicle, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Vehicle.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withVehicle sets the old Vehicle of the mutation. +func withVehicle(node *Vehicle) vehicleOption { + return func(m *VehicleMutation) { + m.oldValue = func(context.Context) (*Vehicle, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m VehicleMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m VehicleMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of Vehicle entities. +func (m *VehicleMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *VehicleMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *VehicleMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().Vehicle.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *VehicleMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *VehicleMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the Vehicle entity. +// If the Vehicle object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *VehicleMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *VehicleMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *VehicleMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *VehicleMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *VehicleMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the Vehicle entity. +// If the Vehicle object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *VehicleMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *VehicleMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *VehicleMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetTier sets the "tier" field. +func (m *VehicleMutation) SetTier(i int) { + m.tier = &i + m.addtier = nil +} + +// Tier returns the value of the "tier" field in the mutation. +func (m *VehicleMutation) Tier() (r int, exists bool) { + v := m.tier + if v == nil { + return + } + return *v, true +} + +// OldTier returns the old "tier" field's value of the Vehicle entity. +// If the Vehicle object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleMutation) OldTier(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTier is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTier requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTier: %w", err) + } + return oldValue.Tier, nil +} + +// AddTier adds i to the "tier" field. +func (m *VehicleMutation) AddTier(i int) { + if m.addtier != nil { + *m.addtier += i + } else { + m.addtier = &i + } +} + +// AddedTier returns the value that was added to the "tier" field in this mutation. +func (m *VehicleMutation) AddedTier() (r int, exists bool) { + v := m.addtier + if v == nil { + return + } + return *v, true +} + +// ResetTier resets all changes to the "tier" field. +func (m *VehicleMutation) ResetTier() { + m.tier = nil + m.addtier = nil +} + +// SetLocalizedNames sets the "localized_names" field. +func (m *VehicleMutation) SetLocalizedNames(value map[string]string) { + m.localized_names = &value +} + +// LocalizedNames returns the value of the "localized_names" field in the mutation. +func (m *VehicleMutation) LocalizedNames() (r map[string]string, exists bool) { + v := m.localized_names + if v == nil { + return + } + return *v, true +} + +// OldLocalizedNames returns the old "localized_names" field's value of the Vehicle entity. +// If the Vehicle object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleMutation) OldLocalizedNames(ctx context.Context) (v map[string]string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLocalizedNames is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLocalizedNames requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLocalizedNames: %w", err) + } + return oldValue.LocalizedNames, nil +} + +// ResetLocalizedNames resets all changes to the "localized_names" field. +func (m *VehicleMutation) ResetLocalizedNames() { + m.localized_names = nil +} + +// Where appends a list predicates to the VehicleMutation builder. +func (m *VehicleMutation) Where(ps ...predicate.Vehicle) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the VehicleMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *VehicleMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Vehicle, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *VehicleMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *VehicleMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Vehicle). +func (m *VehicleMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *VehicleMutation) Fields() []string { + fields := make([]string, 0, 4) + if m.created_at != nil { + fields = append(fields, vehicle.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, vehicle.FieldUpdatedAt) + } + if m.tier != nil { + fields = append(fields, vehicle.FieldTier) + } + if m.localized_names != nil { + fields = append(fields, vehicle.FieldLocalizedNames) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *VehicleMutation) Field(name string) (ent.Value, bool) { + switch name { + case vehicle.FieldCreatedAt: + return m.CreatedAt() + case vehicle.FieldUpdatedAt: + return m.UpdatedAt() + case vehicle.FieldTier: + return m.Tier() + case vehicle.FieldLocalizedNames: + return m.LocalizedNames() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *VehicleMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case vehicle.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case vehicle.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case vehicle.FieldTier: + return m.OldTier(ctx) + case vehicle.FieldLocalizedNames: + return m.OldLocalizedNames(ctx) + } + return nil, fmt.Errorf("unknown Vehicle field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *VehicleMutation) SetField(name string, value ent.Value) error { + switch name { + case vehicle.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case vehicle.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case vehicle.FieldTier: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTier(v) + return nil + case vehicle.FieldLocalizedNames: + v, ok := value.(map[string]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLocalizedNames(v) + return nil + } + return fmt.Errorf("unknown Vehicle field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *VehicleMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, vehicle.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, vehicle.FieldUpdatedAt) + } + if m.addtier != nil { + fields = append(fields, vehicle.FieldTier) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *VehicleMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case vehicle.FieldCreatedAt: + return m.AddedCreatedAt() + case vehicle.FieldUpdatedAt: + return m.AddedUpdatedAt() + case vehicle.FieldTier: + return m.AddedTier() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *VehicleMutation) AddField(name string, value ent.Value) error { + switch name { + case vehicle.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case vehicle.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + case vehicle.FieldTier: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddTier(v) + return nil + } + return fmt.Errorf("unknown Vehicle numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *VehicleMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *VehicleMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *VehicleMutation) ClearField(name string) error { + return fmt.Errorf("unknown Vehicle nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *VehicleMutation) ResetField(name string) error { + switch name { + case vehicle.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case vehicle.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case vehicle.FieldTier: + m.ResetTier() + return nil + case vehicle.FieldLocalizedNames: + m.ResetLocalizedNames() + return nil + } + return fmt.Errorf("unknown Vehicle field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *VehicleMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *VehicleMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *VehicleMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *VehicleMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *VehicleMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *VehicleMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *VehicleMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown Vehicle unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *VehicleMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown Vehicle edge %s", name) +} + +// VehicleAverageMutation represents an operation that mutates the VehicleAverage nodes in the graph. +type VehicleAverageMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + data *map[string]frame.StatsFrame + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*VehicleAverage, error) + predicates []predicate.VehicleAverage +} + +var _ ent.Mutation = (*VehicleAverageMutation)(nil) + +// vehicleaverageOption allows management of the mutation configuration using functional options. +type vehicleaverageOption func(*VehicleAverageMutation) + +// newVehicleAverageMutation creates new mutation for the VehicleAverage entity. +func newVehicleAverageMutation(c config, op Op, opts ...vehicleaverageOption) *VehicleAverageMutation { + m := &VehicleAverageMutation{ + config: c, + op: op, + typ: TypeVehicleAverage, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withVehicleAverageID sets the ID field of the mutation. +func withVehicleAverageID(id string) vehicleaverageOption { + return func(m *VehicleAverageMutation) { + var ( + err error + once sync.Once + value *VehicleAverage + ) + m.oldValue = func(ctx context.Context) (*VehicleAverage, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().VehicleAverage.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withVehicleAverage sets the old VehicleAverage of the mutation. +func withVehicleAverage(node *VehicleAverage) vehicleaverageOption { + return func(m *VehicleAverageMutation) { + m.oldValue = func(context.Context) (*VehicleAverage, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m VehicleAverageMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m VehicleAverageMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of VehicleAverage entities. +func (m *VehicleAverageMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *VehicleAverageMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *VehicleAverageMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().VehicleAverage.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *VehicleAverageMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *VehicleAverageMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the VehicleAverage entity. +// If the VehicleAverage object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleAverageMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *VehicleAverageMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *VehicleAverageMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *VehicleAverageMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *VehicleAverageMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *VehicleAverageMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the VehicleAverage entity. +// If the VehicleAverage object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleAverageMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *VehicleAverageMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *VehicleAverageMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *VehicleAverageMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetData sets the "data" field. +func (m *VehicleAverageMutation) SetData(mf map[string]frame.StatsFrame) { + m.data = &mf +} + +// Data returns the value of the "data" field in the mutation. +func (m *VehicleAverageMutation) Data() (r map[string]frame.StatsFrame, exists bool) { + v := m.data + if v == nil { + return + } + return *v, true +} + +// OldData returns the old "data" field's value of the VehicleAverage entity. +// If the VehicleAverage object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleAverageMutation) OldData(ctx context.Context) (v map[string]frame.StatsFrame, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldData is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldData requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldData: %w", err) + } + return oldValue.Data, nil +} + +// ResetData resets all changes to the "data" field. +func (m *VehicleAverageMutation) ResetData() { + m.data = nil +} + +// Where appends a list predicates to the VehicleAverageMutation builder. +func (m *VehicleAverageMutation) Where(ps ...predicate.VehicleAverage) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the VehicleAverageMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *VehicleAverageMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.VehicleAverage, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *VehicleAverageMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *VehicleAverageMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (VehicleAverage). +func (m *VehicleAverageMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *VehicleAverageMutation) Fields() []string { + fields := make([]string, 0, 3) + if m.created_at != nil { + fields = append(fields, vehicleaverage.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, vehicleaverage.FieldUpdatedAt) + } + if m.data != nil { + fields = append(fields, vehicleaverage.FieldData) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *VehicleAverageMutation) Field(name string) (ent.Value, bool) { + switch name { + case vehicleaverage.FieldCreatedAt: + return m.CreatedAt() + case vehicleaverage.FieldUpdatedAt: + return m.UpdatedAt() + case vehicleaverage.FieldData: + return m.Data() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *VehicleAverageMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case vehicleaverage.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case vehicleaverage.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case vehicleaverage.FieldData: + return m.OldData(ctx) + } + return nil, fmt.Errorf("unknown VehicleAverage field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *VehicleAverageMutation) SetField(name string, value ent.Value) error { + switch name { + case vehicleaverage.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case vehicleaverage.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case vehicleaverage.FieldData: + v, ok := value.(map[string]frame.StatsFrame) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetData(v) + return nil + } + return fmt.Errorf("unknown VehicleAverage field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *VehicleAverageMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, vehicleaverage.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, vehicleaverage.FieldUpdatedAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *VehicleAverageMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case vehicleaverage.FieldCreatedAt: + return m.AddedCreatedAt() + case vehicleaverage.FieldUpdatedAt: + return m.AddedUpdatedAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *VehicleAverageMutation) AddField(name string, value ent.Value) error { + switch name { + case vehicleaverage.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case vehicleaverage.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown VehicleAverage numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *VehicleAverageMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *VehicleAverageMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *VehicleAverageMutation) ClearField(name string) error { + return fmt.Errorf("unknown VehicleAverage nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *VehicleAverageMutation) ResetField(name string) error { + switch name { + case vehicleaverage.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case vehicleaverage.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case vehicleaverage.FieldData: + m.ResetData() + return nil + } + return fmt.Errorf("unknown VehicleAverage field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *VehicleAverageMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *VehicleAverageMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *VehicleAverageMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *VehicleAverageMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *VehicleAverageMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *VehicleAverageMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *VehicleAverageMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown VehicleAverage unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *VehicleAverageMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown VehicleAverage edge %s", name) +} + +// VehicleSnapshotMutation represents an operation that mutates the VehicleSnapshot nodes in the graph. +type VehicleSnapshotMutation struct { + config + op Op + typ string + id *string + created_at *int + addcreated_at *int + updated_at *int + addupdated_at *int + _type *models.SnapshotType + vehicle_id *string + reference_id *string + battles *int + addbattles *int + last_battle_time *int + addlast_battle_time *int + frame *frame.StatsFrame + clearedFields map[string]struct{} + account *string + clearedaccount bool + done bool + oldValue func(context.Context) (*VehicleSnapshot, error) + predicates []predicate.VehicleSnapshot +} + +var _ ent.Mutation = (*VehicleSnapshotMutation)(nil) + +// vehiclesnapshotOption allows management of the mutation configuration using functional options. +type vehiclesnapshotOption func(*VehicleSnapshotMutation) + +// newVehicleSnapshotMutation creates new mutation for the VehicleSnapshot entity. +func newVehicleSnapshotMutation(c config, op Op, opts ...vehiclesnapshotOption) *VehicleSnapshotMutation { + m := &VehicleSnapshotMutation{ + config: c, + op: op, + typ: TypeVehicleSnapshot, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withVehicleSnapshotID sets the ID field of the mutation. +func withVehicleSnapshotID(id string) vehiclesnapshotOption { + return func(m *VehicleSnapshotMutation) { + var ( + err error + once sync.Once + value *VehicleSnapshot + ) + m.oldValue = func(ctx context.Context) (*VehicleSnapshot, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().VehicleSnapshot.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withVehicleSnapshot sets the old VehicleSnapshot of the mutation. +func withVehicleSnapshot(node *VehicleSnapshot) vehiclesnapshotOption { + return func(m *VehicleSnapshotMutation) { + m.oldValue = func(context.Context) (*VehicleSnapshot, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m VehicleSnapshotMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m VehicleSnapshotMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of VehicleSnapshot entities. +func (m *VehicleSnapshotMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *VehicleSnapshotMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *VehicleSnapshotMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().VehicleSnapshot.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *VehicleSnapshotMutation) SetCreatedAt(i int) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *VehicleSnapshotMutation) CreatedAt() (r int, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the VehicleSnapshot entity. +// If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleSnapshotMutation) OldCreatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *VehicleSnapshotMutation) AddCreatedAt(i int) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *VehicleSnapshotMutation) AddedCreatedAt() (r int, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *VehicleSnapshotMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *VehicleSnapshotMutation) SetUpdatedAt(i int) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *VehicleSnapshotMutation) UpdatedAt() (r int, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the VehicleSnapshot entity. +// If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *VehicleSnapshotMutation) AddUpdatedAt(i int) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *VehicleSnapshotMutation) AddedUpdatedAt() (r int, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *VehicleSnapshotMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetType sets the "type" field. +func (m *VehicleSnapshotMutation) SetType(mt models.SnapshotType) { + m._type = &mt +} + +// GetType returns the value of the "type" field in the mutation. +func (m *VehicleSnapshotMutation) GetType() (r models.SnapshotType, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the VehicleSnapshot entity. +// If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleSnapshotMutation) OldType(ctx context.Context) (v models.SnapshotType, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ResetType resets all changes to the "type" field. +func (m *VehicleSnapshotMutation) ResetType() { + m._type = nil +} + +// SetAccountID sets the "account_id" field. +func (m *VehicleSnapshotMutation) SetAccountID(s string) { + m.account = &s +} + +// AccountID returns the value of the "account_id" field in the mutation. +func (m *VehicleSnapshotMutation) AccountID() (r string, exists bool) { + v := m.account + if v == nil { + return + } + return *v, true +} + +// OldAccountID returns the old "account_id" field's value of the VehicleSnapshot entity. +// If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleSnapshotMutation) OldAccountID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAccountID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAccountID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAccountID: %w", err) + } + return oldValue.AccountID, nil +} + +// ResetAccountID resets all changes to the "account_id" field. +func (m *VehicleSnapshotMutation) ResetAccountID() { + m.account = nil +} + +// SetVehicleID sets the "vehicle_id" field. +func (m *VehicleSnapshotMutation) SetVehicleID(s string) { + m.vehicle_id = &s +} + +// VehicleID returns the value of the "vehicle_id" field in the mutation. +func (m *VehicleSnapshotMutation) VehicleID() (r string, exists bool) { + v := m.vehicle_id + if v == nil { + return + } + return *v, true +} + +// OldVehicleID returns the old "vehicle_id" field's value of the VehicleSnapshot entity. +// If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleSnapshotMutation) OldVehicleID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldVehicleID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldVehicleID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldVehicleID: %w", err) + } + return oldValue.VehicleID, nil +} + +// ResetVehicleID resets all changes to the "vehicle_id" field. +func (m *VehicleSnapshotMutation) ResetVehicleID() { + m.vehicle_id = nil +} + +// SetReferenceID sets the "reference_id" field. +func (m *VehicleSnapshotMutation) SetReferenceID(s string) { + m.reference_id = &s +} + +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *VehicleSnapshotMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id + if v == nil { + return + } + return *v, true +} + +// OldReferenceID returns the old "reference_id" field's value of the VehicleSnapshot entity. +// If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleSnapshotMutation) OldReferenceID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferenceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + } + return oldValue.ReferenceID, nil +} + +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *VehicleSnapshotMutation) ResetReferenceID() { + m.reference_id = nil +} + +// SetBattles sets the "battles" field. +func (m *VehicleSnapshotMutation) SetBattles(i int) { + m.battles = &i + m.addbattles = nil +} + +// Battles returns the value of the "battles" field in the mutation. +func (m *VehicleSnapshotMutation) Battles() (r int, exists bool) { + v := m.battles + if v == nil { + return + } + return *v, true +} + +// OldBattles returns the old "battles" field's value of the VehicleSnapshot entity. +// If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleSnapshotMutation) OldBattles(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldBattles is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldBattles requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldBattles: %w", err) + } + return oldValue.Battles, nil +} + +// AddBattles adds i to the "battles" field. +func (m *VehicleSnapshotMutation) AddBattles(i int) { + if m.addbattles != nil { + *m.addbattles += i + } else { + m.addbattles = &i + } +} + +// AddedBattles returns the value that was added to the "battles" field in this mutation. +func (m *VehicleSnapshotMutation) AddedBattles() (r int, exists bool) { + v := m.addbattles + if v == nil { + return + } + return *v, true +} + +// ResetBattles resets all changes to the "battles" field. +func (m *VehicleSnapshotMutation) ResetBattles() { + m.battles = nil + m.addbattles = nil +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (m *VehicleSnapshotMutation) SetLastBattleTime(i int) { + m.last_battle_time = &i + m.addlast_battle_time = nil +} + +// LastBattleTime returns the value of the "last_battle_time" field in the mutation. +func (m *VehicleSnapshotMutation) LastBattleTime() (r int, exists bool) { + v := m.last_battle_time + if v == nil { + return + } + return *v, true +} + +// OldLastBattleTime returns the old "last_battle_time" field's value of the VehicleSnapshot entity. +// If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLastBattleTime requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLastBattleTime: %w", err) + } + return oldValue.LastBattleTime, nil +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (m *VehicleSnapshotMutation) AddLastBattleTime(i int) { + if m.addlast_battle_time != nil { + *m.addlast_battle_time += i + } else { + m.addlast_battle_time = &i + } +} + +// AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. +func (m *VehicleSnapshotMutation) AddedLastBattleTime() (r int, exists bool) { + v := m.addlast_battle_time + if v == nil { + return + } + return *v, true +} + +// ResetLastBattleTime resets all changes to the "last_battle_time" field. +func (m *VehicleSnapshotMutation) ResetLastBattleTime() { + m.last_battle_time = nil + m.addlast_battle_time = nil +} + +// SetFrame sets the "frame" field. +func (m *VehicleSnapshotMutation) SetFrame(ff frame.StatsFrame) { + m.frame = &ff +} + +// Frame returns the value of the "frame" field in the mutation. +func (m *VehicleSnapshotMutation) Frame() (r frame.StatsFrame, exists bool) { + v := m.frame + if v == nil { + return + } + return *v, true +} + +// OldFrame returns the old "frame" field's value of the VehicleSnapshot entity. +// If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VehicleSnapshotMutation) OldFrame(ctx context.Context) (v frame.StatsFrame, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFrame is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFrame requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFrame: %w", err) + } + return oldValue.Frame, nil +} + +// ResetFrame resets all changes to the "frame" field. +func (m *VehicleSnapshotMutation) ResetFrame() { + m.frame = nil +} + +// ClearAccount clears the "account" edge to the Account entity. +func (m *VehicleSnapshotMutation) ClearAccount() { + m.clearedaccount = true + m.clearedFields[vehiclesnapshot.FieldAccountID] = struct{}{} +} + +// AccountCleared reports if the "account" edge to the Account entity was cleared. +func (m *VehicleSnapshotMutation) AccountCleared() bool { + return m.clearedaccount +} + +// AccountIDs returns the "account" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// AccountID instead. It exists only for internal usage by the builders. +func (m *VehicleSnapshotMutation) AccountIDs() (ids []string) { + if id := m.account; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetAccount resets all changes to the "account" edge. +func (m *VehicleSnapshotMutation) ResetAccount() { + m.account = nil + m.clearedaccount = false +} + +// Where appends a list predicates to the VehicleSnapshotMutation builder. +func (m *VehicleSnapshotMutation) Where(ps ...predicate.VehicleSnapshot) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the VehicleSnapshotMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *VehicleSnapshotMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.VehicleSnapshot, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *VehicleSnapshotMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *VehicleSnapshotMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (VehicleSnapshot). +func (m *VehicleSnapshotMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *VehicleSnapshotMutation) Fields() []string { + fields := make([]string, 0, 9) + if m.created_at != nil { + fields = append(fields, vehiclesnapshot.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, vehiclesnapshot.FieldUpdatedAt) + } + if m._type != nil { + fields = append(fields, vehiclesnapshot.FieldType) + } + if m.account != nil { + fields = append(fields, vehiclesnapshot.FieldAccountID) + } + if m.vehicle_id != nil { + fields = append(fields, vehiclesnapshot.FieldVehicleID) + } + if m.reference_id != nil { + fields = append(fields, vehiclesnapshot.FieldReferenceID) + } + if m.battles != nil { + fields = append(fields, vehiclesnapshot.FieldBattles) + } + if m.last_battle_time != nil { + fields = append(fields, vehiclesnapshot.FieldLastBattleTime) + } + if m.frame != nil { + fields = append(fields, vehiclesnapshot.FieldFrame) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *VehicleSnapshotMutation) Field(name string) (ent.Value, bool) { + switch name { + case vehiclesnapshot.FieldCreatedAt: + return m.CreatedAt() + case vehiclesnapshot.FieldUpdatedAt: + return m.UpdatedAt() + case vehiclesnapshot.FieldType: + return m.GetType() + case vehiclesnapshot.FieldAccountID: + return m.AccountID() + case vehiclesnapshot.FieldVehicleID: + return m.VehicleID() + case vehiclesnapshot.FieldReferenceID: + return m.ReferenceID() + case vehiclesnapshot.FieldBattles: + return m.Battles() + case vehiclesnapshot.FieldLastBattleTime: + return m.LastBattleTime() + case vehiclesnapshot.FieldFrame: + return m.Frame() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *VehicleSnapshotMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case vehiclesnapshot.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case vehiclesnapshot.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case vehiclesnapshot.FieldType: + return m.OldType(ctx) + case vehiclesnapshot.FieldAccountID: + return m.OldAccountID(ctx) + case vehiclesnapshot.FieldVehicleID: + return m.OldVehicleID(ctx) + case vehiclesnapshot.FieldReferenceID: + return m.OldReferenceID(ctx) + case vehiclesnapshot.FieldBattles: + return m.OldBattles(ctx) + case vehiclesnapshot.FieldLastBattleTime: + return m.OldLastBattleTime(ctx) + case vehiclesnapshot.FieldFrame: + return m.OldFrame(ctx) + } + return nil, fmt.Errorf("unknown VehicleSnapshot field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *VehicleSnapshotMutation) SetField(name string, value ent.Value) error { + switch name { + case vehiclesnapshot.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case vehiclesnapshot.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case vehiclesnapshot.FieldType: + v, ok := value.(models.SnapshotType) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case vehiclesnapshot.FieldAccountID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAccountID(v) + return nil + case vehiclesnapshot.FieldVehicleID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetVehicleID(v) + return nil + case vehiclesnapshot.FieldReferenceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferenceID(v) + return nil + case vehiclesnapshot.FieldBattles: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetBattles(v) + return nil + case vehiclesnapshot.FieldLastBattleTime: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLastBattleTime(v) + return nil + case vehiclesnapshot.FieldFrame: + v, ok := value.(frame.StatsFrame) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFrame(v) + return nil + } + return fmt.Errorf("unknown VehicleSnapshot field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *VehicleSnapshotMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, vehiclesnapshot.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, vehiclesnapshot.FieldUpdatedAt) + } + if m.addbattles != nil { + fields = append(fields, vehiclesnapshot.FieldBattles) + } + if m.addlast_battle_time != nil { + fields = append(fields, vehiclesnapshot.FieldLastBattleTime) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *VehicleSnapshotMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case vehiclesnapshot.FieldCreatedAt: + return m.AddedCreatedAt() + case vehiclesnapshot.FieldUpdatedAt: + return m.AddedUpdatedAt() + case vehiclesnapshot.FieldBattles: + return m.AddedBattles() + case vehiclesnapshot.FieldLastBattleTime: + return m.AddedLastBattleTime() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *VehicleSnapshotMutation) AddField(name string, value ent.Value) error { + switch name { + case vehiclesnapshot.FieldCreatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case vehiclesnapshot.FieldUpdatedAt: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + case vehiclesnapshot.FieldBattles: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddBattles(v) + return nil + case vehiclesnapshot.FieldLastBattleTime: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddLastBattleTime(v) + return nil + } + return fmt.Errorf("unknown VehicleSnapshot numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *VehicleSnapshotMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *VehicleSnapshotMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *VehicleSnapshotMutation) ClearField(name string) error { + return fmt.Errorf("unknown VehicleSnapshot nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *VehicleSnapshotMutation) ResetField(name string) error { + switch name { + case vehiclesnapshot.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case vehiclesnapshot.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case vehiclesnapshot.FieldType: + m.ResetType() + return nil + case vehiclesnapshot.FieldAccountID: + m.ResetAccountID() + return nil + case vehiclesnapshot.FieldVehicleID: + m.ResetVehicleID() + return nil + case vehiclesnapshot.FieldReferenceID: + m.ResetReferenceID() + return nil + case vehiclesnapshot.FieldBattles: + m.ResetBattles() + return nil + case vehiclesnapshot.FieldLastBattleTime: + m.ResetLastBattleTime() + return nil + case vehiclesnapshot.FieldFrame: + m.ResetFrame() + return nil + } + return fmt.Errorf("unknown VehicleSnapshot field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *VehicleSnapshotMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.account != nil { + edges = append(edges, vehiclesnapshot.EdgeAccount) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *VehicleSnapshotMutation) AddedIDs(name string) []ent.Value { + switch name { + case vehiclesnapshot.EdgeAccount: + if id := m.account; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *VehicleSnapshotMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *VehicleSnapshotMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *VehicleSnapshotMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedaccount { + edges = append(edges, vehiclesnapshot.EdgeAccount) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *VehicleSnapshotMutation) EdgeCleared(name string) bool { + switch name { + case vehiclesnapshot.EdgeAccount: + return m.clearedaccount + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *VehicleSnapshotMutation) ClearEdge(name string) error { + switch name { + case vehiclesnapshot.EdgeAccount: + m.ClearAccount() + return nil + } + return fmt.Errorf("unknown VehicleSnapshot unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *VehicleSnapshotMutation) ResetEdge(name string) error { + switch name { + case vehiclesnapshot.EdgeAccount: + m.ResetAccount() + return nil + } + return fmt.Errorf("unknown VehicleSnapshot edge %s", name) +} diff --git a/internal/database/ent/db/predicate/predicate.go b/internal/database/ent/db/predicate/predicate.go new file mode 100644 index 00000000..899084f2 --- /dev/null +++ b/internal/database/ent/db/predicate/predicate.go @@ -0,0 +1,49 @@ +// Code generated by ent, DO NOT EDIT. + +package predicate + +import ( + "entgo.io/ent/dialect/sql" +) + +// Account is the predicate function for account builders. +type Account func(*sql.Selector) + +// AccountSnapshot is the predicate function for accountsnapshot builders. +type AccountSnapshot func(*sql.Selector) + +// AchievementsSnapshot is the predicate function for achievementssnapshot builders. +type AchievementsSnapshot func(*sql.Selector) + +// AppConfiguration is the predicate function for appconfiguration builders. +type AppConfiguration func(*sql.Selector) + +// ApplicationCommand is the predicate function for applicationcommand builders. +type ApplicationCommand func(*sql.Selector) + +// Clan is the predicate function for clan builders. +type Clan func(*sql.Selector) + +// CronTask is the predicate function for crontask builders. +type CronTask func(*sql.Selector) + +// User is the predicate function for user builders. +type User func(*sql.Selector) + +// UserConnection is the predicate function for userconnection builders. +type UserConnection func(*sql.Selector) + +// UserContent is the predicate function for usercontent builders. +type UserContent func(*sql.Selector) + +// UserSubscription is the predicate function for usersubscription builders. +type UserSubscription func(*sql.Selector) + +// Vehicle is the predicate function for vehicle builders. +type Vehicle func(*sql.Selector) + +// VehicleAverage is the predicate function for vehicleaverage builders. +type VehicleAverage func(*sql.Selector) + +// VehicleSnapshot is the predicate function for vehiclesnapshot builders. +type VehicleSnapshot func(*sql.Selector) diff --git a/internal/database/ent/db/runtime.go b/internal/database/ent/db/runtime.go new file mode 100644 index 00000000..15bfe7e8 --- /dev/null +++ b/internal/database/ent/db/runtime.go @@ -0,0 +1,363 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/clan" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" + "github.com/cufee/aftermath/internal/database/ent/schema" +) + +// The init function reads all schema descriptors with runtime code +// (default values, validators, hooks and policies) and stitches it +// to their package variables. +func init() { + accountFields := schema.Account{}.Fields() + _ = accountFields + // accountDescCreatedAt is the schema descriptor for created_at field. + accountDescCreatedAt := accountFields[1].Descriptor() + // account.DefaultCreatedAt holds the default value on creation for the created_at field. + account.DefaultCreatedAt = accountDescCreatedAt.Default.(func() int) + // accountDescUpdatedAt is the schema descriptor for updated_at field. + accountDescUpdatedAt := accountFields[2].Descriptor() + // account.DefaultUpdatedAt holds the default value on creation for the updated_at field. + account.DefaultUpdatedAt = accountDescUpdatedAt.Default.(func() int) + // account.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + account.UpdateDefaultUpdatedAt = accountDescUpdatedAt.UpdateDefault.(func() int) + // accountDescRealm is the schema descriptor for realm field. + accountDescRealm := accountFields[5].Descriptor() + // account.RealmValidator is a validator for the "realm" field. It is called by the builders before save. + account.RealmValidator = func() func(string) error { + validators := accountDescRealm.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(realm string) error { + for _, fn := range fns { + if err := fn(realm); err != nil { + return err + } + } + return nil + } + }() + // accountDescNickname is the schema descriptor for nickname field. + accountDescNickname := accountFields[6].Descriptor() + // account.NicknameValidator is a validator for the "nickname" field. It is called by the builders before save. + account.NicknameValidator = accountDescNickname.Validators[0].(func(string) error) + // accountDescPrivate is the schema descriptor for private field. + accountDescPrivate := accountFields[7].Descriptor() + // account.DefaultPrivate holds the default value on creation for the private field. + account.DefaultPrivate = accountDescPrivate.Default.(bool) + accountsnapshotFields := schema.AccountSnapshot{}.Fields() + _ = accountsnapshotFields + // accountsnapshotDescCreatedAt is the schema descriptor for created_at field. + accountsnapshotDescCreatedAt := accountsnapshotFields[1].Descriptor() + // accountsnapshot.DefaultCreatedAt holds the default value on creation for the created_at field. + accountsnapshot.DefaultCreatedAt = accountsnapshotDescCreatedAt.Default.(func() int) + // accountsnapshotDescUpdatedAt is the schema descriptor for updated_at field. + accountsnapshotDescUpdatedAt := accountsnapshotFields[2].Descriptor() + // accountsnapshot.DefaultUpdatedAt holds the default value on creation for the updated_at field. + accountsnapshot.DefaultUpdatedAt = accountsnapshotDescUpdatedAt.Default.(func() int) + // accountsnapshot.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + accountsnapshot.UpdateDefaultUpdatedAt = accountsnapshotDescUpdatedAt.UpdateDefault.(func() int) + // accountsnapshotDescAccountID is the schema descriptor for account_id field. + accountsnapshotDescAccountID := accountsnapshotFields[5].Descriptor() + // accountsnapshot.AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. + accountsnapshot.AccountIDValidator = accountsnapshotDescAccountID.Validators[0].(func(string) error) + // accountsnapshotDescReferenceID is the schema descriptor for reference_id field. + accountsnapshotDescReferenceID := accountsnapshotFields[6].Descriptor() + // accountsnapshot.ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + accountsnapshot.ReferenceIDValidator = accountsnapshotDescReferenceID.Validators[0].(func(string) error) + // accountsnapshotDescID is the schema descriptor for id field. + accountsnapshotDescID := accountsnapshotFields[0].Descriptor() + // accountsnapshot.DefaultID holds the default value on creation for the id field. + accountsnapshot.DefaultID = accountsnapshotDescID.Default.(func() string) + achievementssnapshotFields := schema.AchievementsSnapshot{}.Fields() + _ = achievementssnapshotFields + // achievementssnapshotDescCreatedAt is the schema descriptor for created_at field. + achievementssnapshotDescCreatedAt := achievementssnapshotFields[1].Descriptor() + // achievementssnapshot.DefaultCreatedAt holds the default value on creation for the created_at field. + achievementssnapshot.DefaultCreatedAt = achievementssnapshotDescCreatedAt.Default.(func() int) + // achievementssnapshotDescUpdatedAt is the schema descriptor for updated_at field. + achievementssnapshotDescUpdatedAt := achievementssnapshotFields[2].Descriptor() + // achievementssnapshot.DefaultUpdatedAt holds the default value on creation for the updated_at field. + achievementssnapshot.DefaultUpdatedAt = achievementssnapshotDescUpdatedAt.Default.(func() int) + // achievementssnapshot.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + achievementssnapshot.UpdateDefaultUpdatedAt = achievementssnapshotDescUpdatedAt.UpdateDefault.(func() int) + // achievementssnapshotDescAccountID is the schema descriptor for account_id field. + achievementssnapshotDescAccountID := achievementssnapshotFields[4].Descriptor() + // achievementssnapshot.AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. + achievementssnapshot.AccountIDValidator = achievementssnapshotDescAccountID.Validators[0].(func(string) error) + // achievementssnapshotDescReferenceID is the schema descriptor for reference_id field. + achievementssnapshotDescReferenceID := achievementssnapshotFields[5].Descriptor() + // achievementssnapshot.ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + achievementssnapshot.ReferenceIDValidator = achievementssnapshotDescReferenceID.Validators[0].(func(string) error) + // achievementssnapshotDescID is the schema descriptor for id field. + achievementssnapshotDescID := achievementssnapshotFields[0].Descriptor() + // achievementssnapshot.DefaultID holds the default value on creation for the id field. + achievementssnapshot.DefaultID = achievementssnapshotDescID.Default.(func() string) + appconfigurationFields := schema.AppConfiguration{}.Fields() + _ = appconfigurationFields + // appconfigurationDescCreatedAt is the schema descriptor for created_at field. + appconfigurationDescCreatedAt := appconfigurationFields[1].Descriptor() + // appconfiguration.DefaultCreatedAt holds the default value on creation for the created_at field. + appconfiguration.DefaultCreatedAt = appconfigurationDescCreatedAt.Default.(func() int) + // appconfigurationDescUpdatedAt is the schema descriptor for updated_at field. + appconfigurationDescUpdatedAt := appconfigurationFields[2].Descriptor() + // appconfiguration.DefaultUpdatedAt holds the default value on creation for the updated_at field. + appconfiguration.DefaultUpdatedAt = appconfigurationDescUpdatedAt.Default.(func() int) + // appconfiguration.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + appconfiguration.UpdateDefaultUpdatedAt = appconfigurationDescUpdatedAt.UpdateDefault.(func() int) + // appconfigurationDescKey is the schema descriptor for key field. + appconfigurationDescKey := appconfigurationFields[3].Descriptor() + // appconfiguration.KeyValidator is a validator for the "key" field. It is called by the builders before save. + appconfiguration.KeyValidator = appconfigurationDescKey.Validators[0].(func(string) error) + // appconfigurationDescID is the schema descriptor for id field. + appconfigurationDescID := appconfigurationFields[0].Descriptor() + // appconfiguration.DefaultID holds the default value on creation for the id field. + appconfiguration.DefaultID = appconfigurationDescID.Default.(func() string) + applicationcommandFields := schema.ApplicationCommand{}.Fields() + _ = applicationcommandFields + // applicationcommandDescCreatedAt is the schema descriptor for created_at field. + applicationcommandDescCreatedAt := applicationcommandFields[1].Descriptor() + // applicationcommand.DefaultCreatedAt holds the default value on creation for the created_at field. + applicationcommand.DefaultCreatedAt = applicationcommandDescCreatedAt.Default.(func() int) + // applicationcommandDescUpdatedAt is the schema descriptor for updated_at field. + applicationcommandDescUpdatedAt := applicationcommandFields[2].Descriptor() + // applicationcommand.DefaultUpdatedAt holds the default value on creation for the updated_at field. + applicationcommand.DefaultUpdatedAt = applicationcommandDescUpdatedAt.Default.(func() int) + // applicationcommand.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + applicationcommand.UpdateDefaultUpdatedAt = applicationcommandDescUpdatedAt.UpdateDefault.(func() int) + // applicationcommandDescName is the schema descriptor for name field. + applicationcommandDescName := applicationcommandFields[3].Descriptor() + // applicationcommand.NameValidator is a validator for the "name" field. It is called by the builders before save. + applicationcommand.NameValidator = applicationcommandDescName.Validators[0].(func(string) error) + // applicationcommandDescVersion is the schema descriptor for version field. + applicationcommandDescVersion := applicationcommandFields[4].Descriptor() + // applicationcommand.VersionValidator is a validator for the "version" field. It is called by the builders before save. + applicationcommand.VersionValidator = applicationcommandDescVersion.Validators[0].(func(string) error) + // applicationcommandDescOptionsHash is the schema descriptor for options_hash field. + applicationcommandDescOptionsHash := applicationcommandFields[5].Descriptor() + // applicationcommand.OptionsHashValidator is a validator for the "options_hash" field. It is called by the builders before save. + applicationcommand.OptionsHashValidator = applicationcommandDescOptionsHash.Validators[0].(func(string) error) + // applicationcommandDescID is the schema descriptor for id field. + applicationcommandDescID := applicationcommandFields[0].Descriptor() + // applicationcommand.DefaultID holds the default value on creation for the id field. + applicationcommand.DefaultID = applicationcommandDescID.Default.(func() string) + clanFields := schema.Clan{}.Fields() + _ = clanFields + // clanDescCreatedAt is the schema descriptor for created_at field. + clanDescCreatedAt := clanFields[1].Descriptor() + // clan.DefaultCreatedAt holds the default value on creation for the created_at field. + clan.DefaultCreatedAt = clanDescCreatedAt.Default.(func() int) + // clanDescUpdatedAt is the schema descriptor for updated_at field. + clanDescUpdatedAt := clanFields[2].Descriptor() + // clan.DefaultUpdatedAt holds the default value on creation for the updated_at field. + clan.DefaultUpdatedAt = clanDescUpdatedAt.Default.(func() int) + // clan.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + clan.UpdateDefaultUpdatedAt = clanDescUpdatedAt.UpdateDefault.(func() int) + // clanDescTag is the schema descriptor for tag field. + clanDescTag := clanFields[3].Descriptor() + // clan.TagValidator is a validator for the "tag" field. It is called by the builders before save. + clan.TagValidator = clanDescTag.Validators[0].(func(string) error) + // clanDescName is the schema descriptor for name field. + clanDescName := clanFields[4].Descriptor() + // clan.NameValidator is a validator for the "name" field. It is called by the builders before save. + clan.NameValidator = clanDescName.Validators[0].(func(string) error) + // clanDescEmblemID is the schema descriptor for emblem_id field. + clanDescEmblemID := clanFields[5].Descriptor() + // clan.DefaultEmblemID holds the default value on creation for the emblem_id field. + clan.DefaultEmblemID = clanDescEmblemID.Default.(string) + crontaskFields := schema.CronTask{}.Fields() + _ = crontaskFields + // crontaskDescCreatedAt is the schema descriptor for created_at field. + crontaskDescCreatedAt := crontaskFields[1].Descriptor() + // crontask.DefaultCreatedAt holds the default value on creation for the created_at field. + crontask.DefaultCreatedAt = crontaskDescCreatedAt.Default.(func() int) + // crontaskDescUpdatedAt is the schema descriptor for updated_at field. + crontaskDescUpdatedAt := crontaskFields[2].Descriptor() + // crontask.DefaultUpdatedAt holds the default value on creation for the updated_at field. + crontask.DefaultUpdatedAt = crontaskDescUpdatedAt.Default.(func() int) + // crontask.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + crontask.UpdateDefaultUpdatedAt = crontaskDescUpdatedAt.UpdateDefault.(func() int) + // crontaskDescType is the schema descriptor for type field. + crontaskDescType := crontaskFields[3].Descriptor() + // crontask.TypeValidator is a validator for the "type" field. It is called by the builders before save. + crontask.TypeValidator = crontaskDescType.Validators[0].(func(string) error) + // crontaskDescReferenceID is the schema descriptor for reference_id field. + crontaskDescReferenceID := crontaskFields[4].Descriptor() + // crontask.ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + crontask.ReferenceIDValidator = crontaskDescReferenceID.Validators[0].(func(string) error) + // crontaskDescStatus is the schema descriptor for status field. + crontaskDescStatus := crontaskFields[6].Descriptor() + // crontask.StatusValidator is a validator for the "status" field. It is called by the builders before save. + crontask.StatusValidator = crontaskDescStatus.Validators[0].(func(string) error) + // crontaskDescID is the schema descriptor for id field. + crontaskDescID := crontaskFields[0].Descriptor() + // crontask.DefaultID holds the default value on creation for the id field. + crontask.DefaultID = crontaskDescID.Default.(func() string) + userFields := schema.User{}.Fields() + _ = userFields + // userDescCreatedAt is the schema descriptor for created_at field. + userDescCreatedAt := userFields[1].Descriptor() + // user.DefaultCreatedAt holds the default value on creation for the created_at field. + user.DefaultCreatedAt = userDescCreatedAt.Default.(func() int) + // userDescUpdatedAt is the schema descriptor for updated_at field. + userDescUpdatedAt := userFields[2].Descriptor() + // user.DefaultUpdatedAt holds the default value on creation for the updated_at field. + user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() int) + // user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() int) + // userDescPermissions is the schema descriptor for permissions field. + userDescPermissions := userFields[3].Descriptor() + // user.DefaultPermissions holds the default value on creation for the permissions field. + user.DefaultPermissions = userDescPermissions.Default.(string) + userconnectionFields := schema.UserConnection{}.Fields() + _ = userconnectionFields + // userconnectionDescCreatedAt is the schema descriptor for created_at field. + userconnectionDescCreatedAt := userconnectionFields[1].Descriptor() + // userconnection.DefaultCreatedAt holds the default value on creation for the created_at field. + userconnection.DefaultCreatedAt = userconnectionDescCreatedAt.Default.(func() int) + // userconnectionDescUpdatedAt is the schema descriptor for updated_at field. + userconnectionDescUpdatedAt := userconnectionFields[2].Descriptor() + // userconnection.DefaultUpdatedAt holds the default value on creation for the updated_at field. + userconnection.DefaultUpdatedAt = userconnectionDescUpdatedAt.Default.(func() int) + // userconnection.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + userconnection.UpdateDefaultUpdatedAt = userconnectionDescUpdatedAt.UpdateDefault.(func() int) + // userconnectionDescPermissions is the schema descriptor for permissions field. + userconnectionDescPermissions := userconnectionFields[6].Descriptor() + // userconnection.DefaultPermissions holds the default value on creation for the permissions field. + userconnection.DefaultPermissions = userconnectionDescPermissions.Default.(string) + // userconnectionDescID is the schema descriptor for id field. + userconnectionDescID := userconnectionFields[0].Descriptor() + // userconnection.DefaultID holds the default value on creation for the id field. + userconnection.DefaultID = userconnectionDescID.Default.(func() string) + usercontentFields := schema.UserContent{}.Fields() + _ = usercontentFields + // usercontentDescCreatedAt is the schema descriptor for created_at field. + usercontentDescCreatedAt := usercontentFields[1].Descriptor() + // usercontent.DefaultCreatedAt holds the default value on creation for the created_at field. + usercontent.DefaultCreatedAt = usercontentDescCreatedAt.Default.(func() int) + // usercontentDescUpdatedAt is the schema descriptor for updated_at field. + usercontentDescUpdatedAt := usercontentFields[2].Descriptor() + // usercontent.DefaultUpdatedAt holds the default value on creation for the updated_at field. + usercontent.DefaultUpdatedAt = usercontentDescUpdatedAt.Default.(func() int) + // usercontent.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + usercontent.UpdateDefaultUpdatedAt = usercontentDescUpdatedAt.UpdateDefault.(func() int) + // usercontentDescID is the schema descriptor for id field. + usercontentDescID := usercontentFields[0].Descriptor() + // usercontent.DefaultID holds the default value on creation for the id field. + usercontent.DefaultID = usercontentDescID.Default.(func() string) + usersubscriptionFields := schema.UserSubscription{}.Fields() + _ = usersubscriptionFields + // usersubscriptionDescCreatedAt is the schema descriptor for created_at field. + usersubscriptionDescCreatedAt := usersubscriptionFields[1].Descriptor() + // usersubscription.DefaultCreatedAt holds the default value on creation for the created_at field. + usersubscription.DefaultCreatedAt = usersubscriptionDescCreatedAt.Default.(func() int) + // usersubscriptionDescUpdatedAt is the schema descriptor for updated_at field. + usersubscriptionDescUpdatedAt := usersubscriptionFields[2].Descriptor() + // usersubscription.DefaultUpdatedAt holds the default value on creation for the updated_at field. + usersubscription.DefaultUpdatedAt = usersubscriptionDescUpdatedAt.Default.(func() int) + // usersubscription.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + usersubscription.UpdateDefaultUpdatedAt = usersubscriptionDescUpdatedAt.UpdateDefault.(func() int) + // usersubscriptionDescUserID is the schema descriptor for user_id field. + usersubscriptionDescUserID := usersubscriptionFields[5].Descriptor() + // usersubscription.UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. + usersubscription.UserIDValidator = usersubscriptionDescUserID.Validators[0].(func(string) error) + // usersubscriptionDescPermissions is the schema descriptor for permissions field. + usersubscriptionDescPermissions := usersubscriptionFields[6].Descriptor() + // usersubscription.PermissionsValidator is a validator for the "permissions" field. It is called by the builders before save. + usersubscription.PermissionsValidator = usersubscriptionDescPermissions.Validators[0].(func(string) error) + // usersubscriptionDescReferenceID is the schema descriptor for reference_id field. + usersubscriptionDescReferenceID := usersubscriptionFields[7].Descriptor() + // usersubscription.ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + usersubscription.ReferenceIDValidator = usersubscriptionDescReferenceID.Validators[0].(func(string) error) + // usersubscriptionDescID is the schema descriptor for id field. + usersubscriptionDescID := usersubscriptionFields[0].Descriptor() + // usersubscription.DefaultID holds the default value on creation for the id field. + usersubscription.DefaultID = usersubscriptionDescID.Default.(func() string) + vehicleFields := schema.Vehicle{}.Fields() + _ = vehicleFields + // vehicleDescCreatedAt is the schema descriptor for created_at field. + vehicleDescCreatedAt := vehicleFields[1].Descriptor() + // vehicle.DefaultCreatedAt holds the default value on creation for the created_at field. + vehicle.DefaultCreatedAt = vehicleDescCreatedAt.Default.(func() int) + // vehicleDescUpdatedAt is the schema descriptor for updated_at field. + vehicleDescUpdatedAt := vehicleFields[2].Descriptor() + // vehicle.DefaultUpdatedAt holds the default value on creation for the updated_at field. + vehicle.DefaultUpdatedAt = vehicleDescUpdatedAt.Default.(func() int) + // vehicle.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + vehicle.UpdateDefaultUpdatedAt = vehicleDescUpdatedAt.UpdateDefault.(func() int) + // vehicleDescTier is the schema descriptor for tier field. + vehicleDescTier := vehicleFields[3].Descriptor() + // vehicle.TierValidator is a validator for the "tier" field. It is called by the builders before save. + vehicle.TierValidator = func() func(int) error { + validators := vehicleDescTier.Validators + fns := [...]func(int) error{ + validators[0].(func(int) error), + validators[1].(func(int) error), + } + return func(tier int) error { + for _, fn := range fns { + if err := fn(tier); err != nil { + return err + } + } + return nil + } + }() + vehicleaverageFields := schema.VehicleAverage{}.Fields() + _ = vehicleaverageFields + // vehicleaverageDescCreatedAt is the schema descriptor for created_at field. + vehicleaverageDescCreatedAt := vehicleaverageFields[1].Descriptor() + // vehicleaverage.DefaultCreatedAt holds the default value on creation for the created_at field. + vehicleaverage.DefaultCreatedAt = vehicleaverageDescCreatedAt.Default.(func() int) + // vehicleaverageDescUpdatedAt is the schema descriptor for updated_at field. + vehicleaverageDescUpdatedAt := vehicleaverageFields[2].Descriptor() + // vehicleaverage.DefaultUpdatedAt holds the default value on creation for the updated_at field. + vehicleaverage.DefaultUpdatedAt = vehicleaverageDescUpdatedAt.Default.(func() int) + // vehicleaverage.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + vehicleaverage.UpdateDefaultUpdatedAt = vehicleaverageDescUpdatedAt.UpdateDefault.(func() int) + vehiclesnapshotFields := schema.VehicleSnapshot{}.Fields() + _ = vehiclesnapshotFields + // vehiclesnapshotDescCreatedAt is the schema descriptor for created_at field. + vehiclesnapshotDescCreatedAt := vehiclesnapshotFields[1].Descriptor() + // vehiclesnapshot.DefaultCreatedAt holds the default value on creation for the created_at field. + vehiclesnapshot.DefaultCreatedAt = vehiclesnapshotDescCreatedAt.Default.(func() int) + // vehiclesnapshotDescUpdatedAt is the schema descriptor for updated_at field. + vehiclesnapshotDescUpdatedAt := vehiclesnapshotFields[2].Descriptor() + // vehiclesnapshot.DefaultUpdatedAt holds the default value on creation for the updated_at field. + vehiclesnapshot.DefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.Default.(func() int) + // vehiclesnapshot.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + vehiclesnapshot.UpdateDefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.UpdateDefault.(func() int) + // vehiclesnapshotDescAccountID is the schema descriptor for account_id field. + vehiclesnapshotDescAccountID := vehiclesnapshotFields[4].Descriptor() + // vehiclesnapshot.AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. + vehiclesnapshot.AccountIDValidator = vehiclesnapshotDescAccountID.Validators[0].(func(string) error) + // vehiclesnapshotDescVehicleID is the schema descriptor for vehicle_id field. + vehiclesnapshotDescVehicleID := vehiclesnapshotFields[5].Descriptor() + // vehiclesnapshot.VehicleIDValidator is a validator for the "vehicle_id" field. It is called by the builders before save. + vehiclesnapshot.VehicleIDValidator = vehiclesnapshotDescVehicleID.Validators[0].(func(string) error) + // vehiclesnapshotDescReferenceID is the schema descriptor for reference_id field. + vehiclesnapshotDescReferenceID := vehiclesnapshotFields[6].Descriptor() + // vehiclesnapshot.ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + vehiclesnapshot.ReferenceIDValidator = vehiclesnapshotDescReferenceID.Validators[0].(func(string) error) + // vehiclesnapshotDescID is the schema descriptor for id field. + vehiclesnapshotDescID := vehiclesnapshotFields[0].Descriptor() + // vehiclesnapshot.DefaultID holds the default value on creation for the id field. + vehiclesnapshot.DefaultID = vehiclesnapshotDescID.Default.(func() string) +} diff --git a/internal/database/ent/db/runtime/runtime.go b/internal/database/ent/db/runtime/runtime.go new file mode 100644 index 00000000..167b2ce9 --- /dev/null +++ b/internal/database/ent/db/runtime/runtime.go @@ -0,0 +1,10 @@ +// Code generated by ent, DO NOT EDIT. + +package runtime + +// The schema-stitching logic is generated in github.com/cufee/aftermath/internal/database/ent/db/runtime.go + +const ( + Version = "v0.13.1" // Version of ent codegen. + Sum = "h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=" // Sum of ent codegen. +) diff --git a/internal/database/ent/db/tx.go b/internal/database/ent/db/tx.go new file mode 100644 index 00000000..a9856134 --- /dev/null +++ b/internal/database/ent/db/tx.go @@ -0,0 +1,249 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "sync" + + "entgo.io/ent/dialect" +) + +// Tx is a transactional client that is created by calling Client.Tx(). +type Tx struct { + config + // Account is the client for interacting with the Account builders. + Account *AccountClient + // AccountSnapshot is the client for interacting with the AccountSnapshot builders. + AccountSnapshot *AccountSnapshotClient + // AchievementsSnapshot is the client for interacting with the AchievementsSnapshot builders. + AchievementsSnapshot *AchievementsSnapshotClient + // AppConfiguration is the client for interacting with the AppConfiguration builders. + AppConfiguration *AppConfigurationClient + // ApplicationCommand is the client for interacting with the ApplicationCommand builders. + ApplicationCommand *ApplicationCommandClient + // Clan is the client for interacting with the Clan builders. + Clan *ClanClient + // CronTask is the client for interacting with the CronTask builders. + CronTask *CronTaskClient + // User is the client for interacting with the User builders. + User *UserClient + // UserConnection is the client for interacting with the UserConnection builders. + UserConnection *UserConnectionClient + // UserContent is the client for interacting with the UserContent builders. + UserContent *UserContentClient + // UserSubscription is the client for interacting with the UserSubscription builders. + UserSubscription *UserSubscriptionClient + // Vehicle is the client for interacting with the Vehicle builders. + Vehicle *VehicleClient + // VehicleAverage is the client for interacting with the VehicleAverage builders. + VehicleAverage *VehicleAverageClient + // VehicleSnapshot is the client for interacting with the VehicleSnapshot builders. + VehicleSnapshot *VehicleSnapshotClient + + // lazily loaded. + client *Client + clientOnce sync.Once + // ctx lives for the life of the transaction. It is + // the same context used by the underlying connection. + ctx context.Context +} + +type ( + // Committer is the interface that wraps the Commit method. + Committer interface { + Commit(context.Context, *Tx) error + } + + // The CommitFunc type is an adapter to allow the use of ordinary + // function as a Committer. If f is a function with the appropriate + // signature, CommitFunc(f) is a Committer that calls f. + CommitFunc func(context.Context, *Tx) error + + // CommitHook defines the "commit middleware". A function that gets a Committer + // and returns a Committer. For example: + // + // hook := func(next ent.Committer) ent.Committer { + // return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Commit(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + CommitHook func(Committer) Committer +) + +// Commit calls f(ctx, m). +func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Commit commits the transaction. +func (tx *Tx) Commit() error { + txDriver := tx.config.driver.(*txDriver) + var fn Committer = CommitFunc(func(context.Context, *Tx) error { + return txDriver.tx.Commit() + }) + txDriver.mu.Lock() + hooks := append([]CommitHook(nil), txDriver.onCommit...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Commit(tx.ctx, tx) +} + +// OnCommit adds a hook to call on commit. +func (tx *Tx) OnCommit(f CommitHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onCommit = append(txDriver.onCommit, f) + txDriver.mu.Unlock() +} + +type ( + // Rollbacker is the interface that wraps the Rollback method. + Rollbacker interface { + Rollback(context.Context, *Tx) error + } + + // The RollbackFunc type is an adapter to allow the use of ordinary + // function as a Rollbacker. If f is a function with the appropriate + // signature, RollbackFunc(f) is a Rollbacker that calls f. + RollbackFunc func(context.Context, *Tx) error + + // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker + // and returns a Rollbacker. For example: + // + // hook := func(next ent.Rollbacker) ent.Rollbacker { + // return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Rollback(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + RollbackHook func(Rollbacker) Rollbacker +) + +// Rollback calls f(ctx, m). +func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Rollback rollbacks the transaction. +func (tx *Tx) Rollback() error { + txDriver := tx.config.driver.(*txDriver) + var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { + return txDriver.tx.Rollback() + }) + txDriver.mu.Lock() + hooks := append([]RollbackHook(nil), txDriver.onRollback...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Rollback(tx.ctx, tx) +} + +// OnRollback adds a hook to call on rollback. +func (tx *Tx) OnRollback(f RollbackHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onRollback = append(txDriver.onRollback, f) + txDriver.mu.Unlock() +} + +// Client returns a Client that binds to current transaction. +func (tx *Tx) Client() *Client { + tx.clientOnce.Do(func() { + tx.client = &Client{config: tx.config} + tx.client.init() + }) + return tx.client +} + +func (tx *Tx) init() { + tx.Account = NewAccountClient(tx.config) + tx.AccountSnapshot = NewAccountSnapshotClient(tx.config) + tx.AchievementsSnapshot = NewAchievementsSnapshotClient(tx.config) + tx.AppConfiguration = NewAppConfigurationClient(tx.config) + tx.ApplicationCommand = NewApplicationCommandClient(tx.config) + tx.Clan = NewClanClient(tx.config) + tx.CronTask = NewCronTaskClient(tx.config) + tx.User = NewUserClient(tx.config) + tx.UserConnection = NewUserConnectionClient(tx.config) + tx.UserContent = NewUserContentClient(tx.config) + tx.UserSubscription = NewUserSubscriptionClient(tx.config) + tx.Vehicle = NewVehicleClient(tx.config) + tx.VehicleAverage = NewVehicleAverageClient(tx.config) + tx.VehicleSnapshot = NewVehicleSnapshotClient(tx.config) +} + +// txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. +// The idea is to support transactions without adding any extra code to the builders. +// When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. +// Commit and Rollback are nop for the internal builders and the user must call one +// of them in order to commit or rollback the transaction. +// +// If a closed transaction is embedded in one of the generated entities, and the entity +// applies a query, for example: Account.QueryXXX(), the query will be executed +// through the driver which created this transaction. +// +// Note that txDriver is not goroutine safe. +type txDriver struct { + // the driver we started the transaction from. + drv dialect.Driver + // tx is the underlying transaction. + tx dialect.Tx + // completion hooks. + mu sync.Mutex + onCommit []CommitHook + onRollback []RollbackHook +} + +// newTx creates a new transactional driver. +func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { + tx, err := drv.Tx(ctx) + if err != nil { + return nil, err + } + return &txDriver{tx: tx, drv: drv}, nil +} + +// Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls +// from the internal builders. Should be called only by the internal builders. +func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } + +// Dialect returns the dialect of the driver we started the transaction from. +func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } + +// Close is a nop close. +func (*txDriver) Close() error { return nil } + +// Commit is a nop commit for the internal builders. +// User must call `Tx.Commit` in order to commit the transaction. +func (*txDriver) Commit() error { return nil } + +// Rollback is a nop rollback for the internal builders. +// User must call `Tx.Rollback` in order to rollback the transaction. +func (*txDriver) Rollback() error { return nil } + +// Exec calls tx.Exec. +func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error { + return tx.tx.Exec(ctx, query, args, v) +} + +// Query calls tx.Query. +func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error { + return tx.tx.Query(ctx, query, args, v) +} + +var _ dialect.Driver = (*txDriver)(nil) diff --git a/internal/database/ent/db/user.go b/internal/database/ent/db/user.go new file mode 100644 index 00000000..f62dd2bb --- /dev/null +++ b/internal/database/ent/db/user.go @@ -0,0 +1,199 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/user" +) + +// User is the model entity for the User schema. +type User struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Permissions holds the value of the "permissions" field. + Permissions string `json:"permissions,omitempty"` + // FeatureFlags holds the value of the "feature_flags" field. + FeatureFlags []string `json:"feature_flags,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserQuery when eager-loading is set. + Edges UserEdges `json:"edges"` + selectValues sql.SelectValues +} + +// UserEdges holds the relations/edges for other nodes in the graph. +type UserEdges struct { + // Subscriptions holds the value of the subscriptions edge. + Subscriptions []*UserSubscription `json:"subscriptions,omitempty"` + // Connections holds the value of the connections edge. + Connections []*UserConnection `json:"connections,omitempty"` + // Content holds the value of the content edge. + Content []*UserContent `json:"content,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [3]bool +} + +// SubscriptionsOrErr returns the Subscriptions value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) SubscriptionsOrErr() ([]*UserSubscription, error) { + if e.loadedTypes[0] { + return e.Subscriptions, nil + } + return nil, &NotLoadedError{edge: "subscriptions"} +} + +// ConnectionsOrErr returns the Connections value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) ConnectionsOrErr() ([]*UserConnection, error) { + if e.loadedTypes[1] { + return e.Connections, nil + } + return nil, &NotLoadedError{edge: "connections"} +} + +// ContentOrErr returns the Content value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) ContentOrErr() ([]*UserContent, error) { + if e.loadedTypes[2] { + return e.Content, nil + } + return nil, &NotLoadedError{edge: "content"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*User) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case user.FieldFeatureFlags: + values[i] = new([]byte) + case user.FieldCreatedAt, user.FieldUpdatedAt: + values[i] = new(sql.NullInt64) + case user.FieldID, user.FieldPermissions: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the User fields. +func (u *User) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case user.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + u.ID = value.String + } + case user.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + u.CreatedAt = int(value.Int64) + } + case user.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + u.UpdatedAt = int(value.Int64) + } + case user.FieldPermissions: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field permissions", values[i]) + } else if value.Valid { + u.Permissions = value.String + } + case user.FieldFeatureFlags: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field feature_flags", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &u.FeatureFlags); err != nil { + return fmt.Errorf("unmarshal field feature_flags: %w", err) + } + } + default: + u.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the User. +// This includes values selected through modifiers, order, etc. +func (u *User) Value(name string) (ent.Value, error) { + return u.selectValues.Get(name) +} + +// QuerySubscriptions queries the "subscriptions" edge of the User entity. +func (u *User) QuerySubscriptions() *UserSubscriptionQuery { + return NewUserClient(u.config).QuerySubscriptions(u) +} + +// QueryConnections queries the "connections" edge of the User entity. +func (u *User) QueryConnections() *UserConnectionQuery { + return NewUserClient(u.config).QueryConnections(u) +} + +// QueryContent queries the "content" edge of the User entity. +func (u *User) QueryContent() *UserContentQuery { + return NewUserClient(u.config).QueryContent(u) +} + +// Update returns a builder for updating this User. +// Note that you need to call User.Unwrap() before calling this method if this User +// was returned from a transaction, and the transaction was committed or rolled back. +func (u *User) Update() *UserUpdateOne { + return NewUserClient(u.config).UpdateOne(u) +} + +// Unwrap unwraps the User entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (u *User) Unwrap() *User { + _tx, ok := u.config.driver.(*txDriver) + if !ok { + panic("db: User is not a transactional entity") + } + u.config.driver = _tx.drv + return u +} + +// String implements the fmt.Stringer. +func (u *User) String() string { + var builder strings.Builder + builder.WriteString("User(") + builder.WriteString(fmt.Sprintf("id=%v, ", u.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", u.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", u.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("permissions=") + builder.WriteString(u.Permissions) + builder.WriteString(", ") + builder.WriteString("feature_flags=") + builder.WriteString(fmt.Sprintf("%v", u.FeatureFlags)) + builder.WriteByte(')') + return builder.String() +} + +// Users is a parsable slice of User. +type Users []*User diff --git a/internal/database/ent/db/user/user.go b/internal/database/ent/db/user/user.go new file mode 100644 index 00000000..9ee9bd12 --- /dev/null +++ b/internal/database/ent/db/user/user.go @@ -0,0 +1,168 @@ +// Code generated by ent, DO NOT EDIT. + +package user + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the user type in the database. + Label = "user" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldPermissions holds the string denoting the permissions field in the database. + FieldPermissions = "permissions" + // FieldFeatureFlags holds the string denoting the feature_flags field in the database. + FieldFeatureFlags = "feature_flags" + // EdgeSubscriptions holds the string denoting the subscriptions edge name in mutations. + EdgeSubscriptions = "subscriptions" + // EdgeConnections holds the string denoting the connections edge name in mutations. + EdgeConnections = "connections" + // EdgeContent holds the string denoting the content edge name in mutations. + EdgeContent = "content" + // Table holds the table name of the user in the database. + Table = "users" + // SubscriptionsTable is the table that holds the subscriptions relation/edge. + SubscriptionsTable = "user_subscriptions" + // SubscriptionsInverseTable is the table name for the UserSubscription entity. + // It exists in this package in order to avoid circular dependency with the "usersubscription" package. + SubscriptionsInverseTable = "user_subscriptions" + // SubscriptionsColumn is the table column denoting the subscriptions relation/edge. + SubscriptionsColumn = "user_id" + // ConnectionsTable is the table that holds the connections relation/edge. + ConnectionsTable = "user_connections" + // ConnectionsInverseTable is the table name for the UserConnection entity. + // It exists in this package in order to avoid circular dependency with the "userconnection" package. + ConnectionsInverseTable = "user_connections" + // ConnectionsColumn is the table column denoting the connections relation/edge. + ConnectionsColumn = "user_id" + // ContentTable is the table that holds the content relation/edge. + ContentTable = "user_contents" + // ContentInverseTable is the table name for the UserContent entity. + // It exists in this package in order to avoid circular dependency with the "usercontent" package. + ContentInverseTable = "user_contents" + // ContentColumn is the table column denoting the content relation/edge. + ContentColumn = "user_id" +) + +// Columns holds all SQL columns for user fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldPermissions, + FieldFeatureFlags, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // DefaultPermissions holds the default value on creation for the "permissions" field. + DefaultPermissions string +) + +// OrderOption defines the ordering options for the User queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByPermissions orders the results by the permissions field. +func ByPermissions(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPermissions, opts...).ToFunc() +} + +// BySubscriptionsCount orders the results by subscriptions count. +func BySubscriptionsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newSubscriptionsStep(), opts...) + } +} + +// BySubscriptions orders the results by subscriptions terms. +func BySubscriptions(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newSubscriptionsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByConnectionsCount orders the results by connections count. +func ByConnectionsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newConnectionsStep(), opts...) + } +} + +// ByConnections orders the results by connections terms. +func ByConnections(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newConnectionsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByContentCount orders the results by content count. +func ByContentCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newContentStep(), opts...) + } +} + +// ByContent orders the results by content terms. +func ByContent(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newContentStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newSubscriptionsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(SubscriptionsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SubscriptionsTable, SubscriptionsColumn), + ) +} +func newConnectionsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ConnectionsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ConnectionsTable, ConnectionsColumn), + ) +} +func newContentStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ContentInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ContentTable, ContentColumn), + ) +} diff --git a/internal/database/ent/db/user/where.go b/internal/database/ent/db/user/where.go new file mode 100644 index 00000000..a0abc0ee --- /dev/null +++ b/internal/database/ent/db/user/where.go @@ -0,0 +1,318 @@ +// Code generated by ent, DO NOT EDIT. + +package user + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.User { + return predicate.User(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.User { + return predicate.User(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.User { + return predicate.User(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.User { + return predicate.User(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.User { + return predicate.User(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.User { + return predicate.User(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.User { + return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.User { + return predicate.User(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Permissions applies equality check predicate on the "permissions" field. It's identical to PermissionsEQ. +func Permissions(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldPermissions, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.User { + return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.User { + return predicate.User(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.User { + return predicate.User(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.User { + return predicate.User(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.User { + return predicate.User(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.User { + return predicate.User(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.User { + return predicate.User(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.User { + return predicate.User(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.User { + return predicate.User(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.User { + return predicate.User(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.User { + return predicate.User(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.User { + return predicate.User(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.User { + return predicate.User(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.User { + return predicate.User(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.User { + return predicate.User(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.User { + return predicate.User(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// PermissionsEQ applies the EQ predicate on the "permissions" field. +func PermissionsEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldPermissions, v)) +} + +// PermissionsNEQ applies the NEQ predicate on the "permissions" field. +func PermissionsNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldPermissions, v)) +} + +// PermissionsIn applies the In predicate on the "permissions" field. +func PermissionsIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldPermissions, vs...)) +} + +// PermissionsNotIn applies the NotIn predicate on the "permissions" field. +func PermissionsNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldPermissions, vs...)) +} + +// PermissionsGT applies the GT predicate on the "permissions" field. +func PermissionsGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldPermissions, v)) +} + +// PermissionsGTE applies the GTE predicate on the "permissions" field. +func PermissionsGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldPermissions, v)) +} + +// PermissionsLT applies the LT predicate on the "permissions" field. +func PermissionsLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldPermissions, v)) +} + +// PermissionsLTE applies the LTE predicate on the "permissions" field. +func PermissionsLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldPermissions, v)) +} + +// PermissionsContains applies the Contains predicate on the "permissions" field. +func PermissionsContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldPermissions, v)) +} + +// PermissionsHasPrefix applies the HasPrefix predicate on the "permissions" field. +func PermissionsHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldPermissions, v)) +} + +// PermissionsHasSuffix applies the HasSuffix predicate on the "permissions" field. +func PermissionsHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldPermissions, v)) +} + +// PermissionsEqualFold applies the EqualFold predicate on the "permissions" field. +func PermissionsEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldPermissions, v)) +} + +// PermissionsContainsFold applies the ContainsFold predicate on the "permissions" field. +func PermissionsContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldPermissions, v)) +} + +// FeatureFlagsIsNil applies the IsNil predicate on the "feature_flags" field. +func FeatureFlagsIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldFeatureFlags)) +} + +// FeatureFlagsNotNil applies the NotNil predicate on the "feature_flags" field. +func FeatureFlagsNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldFeatureFlags)) +} + +// HasSubscriptions applies the HasEdge predicate on the "subscriptions" edge. +func HasSubscriptions() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SubscriptionsTable, SubscriptionsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasSubscriptionsWith applies the HasEdge predicate on the "subscriptions" edge with a given conditions (other predicates). +func HasSubscriptionsWith(preds ...predicate.UserSubscription) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newSubscriptionsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasConnections applies the HasEdge predicate on the "connections" edge. +func HasConnections() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ConnectionsTable, ConnectionsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasConnectionsWith applies the HasEdge predicate on the "connections" edge with a given conditions (other predicates). +func HasConnectionsWith(preds ...predicate.UserConnection) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newConnectionsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasContent applies the HasEdge predicate on the "content" edge. +func HasContent() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ContentTable, ContentColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasContentWith applies the HasEdge predicate on the "content" edge with a given conditions (other predicates). +func HasContentWith(preds ...predicate.UserContent) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newContentStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.User) predicate.User { + return predicate.User(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.User) predicate.User { + return predicate.User(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.User) predicate.User { + return predicate.User(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/user_create.go b/internal/database/ent/db/user_create.go new file mode 100644 index 00000000..a5916908 --- /dev/null +++ b/internal/database/ent/db/user_create.go @@ -0,0 +1,368 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" +) + +// UserCreate is the builder for creating a User entity. +type UserCreate struct { + config + mutation *UserMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (uc *UserCreate) SetCreatedAt(i int) *UserCreate { + uc.mutation.SetCreatedAt(i) + return uc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (uc *UserCreate) SetNillableCreatedAt(i *int) *UserCreate { + if i != nil { + uc.SetCreatedAt(*i) + } + return uc +} + +// SetUpdatedAt sets the "updated_at" field. +func (uc *UserCreate) SetUpdatedAt(i int) *UserCreate { + uc.mutation.SetUpdatedAt(i) + return uc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (uc *UserCreate) SetNillableUpdatedAt(i *int) *UserCreate { + if i != nil { + uc.SetUpdatedAt(*i) + } + return uc +} + +// SetPermissions sets the "permissions" field. +func (uc *UserCreate) SetPermissions(s string) *UserCreate { + uc.mutation.SetPermissions(s) + return uc +} + +// SetNillablePermissions sets the "permissions" field if the given value is not nil. +func (uc *UserCreate) SetNillablePermissions(s *string) *UserCreate { + if s != nil { + uc.SetPermissions(*s) + } + return uc +} + +// SetFeatureFlags sets the "feature_flags" field. +func (uc *UserCreate) SetFeatureFlags(s []string) *UserCreate { + uc.mutation.SetFeatureFlags(s) + return uc +} + +// SetID sets the "id" field. +func (uc *UserCreate) SetID(s string) *UserCreate { + uc.mutation.SetID(s) + return uc +} + +// AddSubscriptionIDs adds the "subscriptions" edge to the UserSubscription entity by IDs. +func (uc *UserCreate) AddSubscriptionIDs(ids ...string) *UserCreate { + uc.mutation.AddSubscriptionIDs(ids...) + return uc +} + +// AddSubscriptions adds the "subscriptions" edges to the UserSubscription entity. +func (uc *UserCreate) AddSubscriptions(u ...*UserSubscription) *UserCreate { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uc.AddSubscriptionIDs(ids...) +} + +// AddConnectionIDs adds the "connections" edge to the UserConnection entity by IDs. +func (uc *UserCreate) AddConnectionIDs(ids ...string) *UserCreate { + uc.mutation.AddConnectionIDs(ids...) + return uc +} + +// AddConnections adds the "connections" edges to the UserConnection entity. +func (uc *UserCreate) AddConnections(u ...*UserConnection) *UserCreate { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uc.AddConnectionIDs(ids...) +} + +// AddContentIDs adds the "content" edge to the UserContent entity by IDs. +func (uc *UserCreate) AddContentIDs(ids ...string) *UserCreate { + uc.mutation.AddContentIDs(ids...) + return uc +} + +// AddContent adds the "content" edges to the UserContent entity. +func (uc *UserCreate) AddContent(u ...*UserContent) *UserCreate { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uc.AddContentIDs(ids...) +} + +// Mutation returns the UserMutation object of the builder. +func (uc *UserCreate) Mutation() *UserMutation { + return uc.mutation +} + +// Save creates the User in the database. +func (uc *UserCreate) Save(ctx context.Context) (*User, error) { + uc.defaults() + return withHooks(ctx, uc.sqlSave, uc.mutation, uc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (uc *UserCreate) SaveX(ctx context.Context) *User { + v, err := uc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (uc *UserCreate) Exec(ctx context.Context) error { + _, err := uc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (uc *UserCreate) ExecX(ctx context.Context) { + if err := uc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (uc *UserCreate) defaults() { + if _, ok := uc.mutation.CreatedAt(); !ok { + v := user.DefaultCreatedAt() + uc.mutation.SetCreatedAt(v) + } + if _, ok := uc.mutation.UpdatedAt(); !ok { + v := user.DefaultUpdatedAt() + uc.mutation.SetUpdatedAt(v) + } + if _, ok := uc.mutation.Permissions(); !ok { + v := user.DefaultPermissions + uc.mutation.SetPermissions(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (uc *UserCreate) check() error { + if _, ok := uc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "User.created_at"`)} + } + if _, ok := uc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "User.updated_at"`)} + } + if _, ok := uc.mutation.Permissions(); !ok { + return &ValidationError{Name: "permissions", err: errors.New(`db: missing required field "User.permissions"`)} + } + return nil +} + +func (uc *UserCreate) sqlSave(ctx context.Context) (*User, error) { + if err := uc.check(); err != nil { + return nil, err + } + _node, _spec := uc.createSpec() + if err := sqlgraph.CreateNode(ctx, uc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected User.ID type: %T", _spec.ID.Value) + } + } + uc.mutation.id = &_node.ID + uc.mutation.done = true + return _node, nil +} + +func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { + var ( + _node = &User{config: uc.config} + _spec = sqlgraph.NewCreateSpec(user.Table, sqlgraph.NewFieldSpec(user.FieldID, field.TypeString)) + ) + if id, ok := uc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := uc.mutation.CreatedAt(); ok { + _spec.SetField(user.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := uc.mutation.UpdatedAt(); ok { + _spec.SetField(user.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := uc.mutation.Permissions(); ok { + _spec.SetField(user.FieldPermissions, field.TypeString, value) + _node.Permissions = value + } + if value, ok := uc.mutation.FeatureFlags(); ok { + _spec.SetField(user.FieldFeatureFlags, field.TypeJSON, value) + _node.FeatureFlags = value + } + if nodes := uc.mutation.SubscriptionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SubscriptionsTable, + Columns: []string{user.SubscriptionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := uc.mutation.ConnectionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ConnectionsTable, + Columns: []string{user.ConnectionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := uc.mutation.ContentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ContentTable, + Columns: []string{user.ContentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// UserCreateBulk is the builder for creating many User entities in bulk. +type UserCreateBulk struct { + config + err error + builders []*UserCreate +} + +// Save creates the User entities in the database. +func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) { + if ucb.err != nil { + return nil, ucb.err + } + specs := make([]*sqlgraph.CreateSpec, len(ucb.builders)) + nodes := make([]*User, len(ucb.builders)) + mutators := make([]Mutator, len(ucb.builders)) + for i := range ucb.builders { + func(i int, root context.Context) { + builder := ucb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, ucb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, ucb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, ucb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (ucb *UserCreateBulk) SaveX(ctx context.Context) []*User { + v, err := ucb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ucb *UserCreateBulk) Exec(ctx context.Context) error { + _, err := ucb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucb *UserCreateBulk) ExecX(ctx context.Context) { + if err := ucb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/user_delete.go b/internal/database/ent/db/user_delete.go new file mode 100644 index 00000000..4b01013f --- /dev/null +++ b/internal/database/ent/db/user_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/user" +) + +// UserDelete is the builder for deleting a User entity. +type UserDelete struct { + config + hooks []Hook + mutation *UserMutation +} + +// Where appends a list predicates to the UserDelete builder. +func (ud *UserDelete) Where(ps ...predicate.User) *UserDelete { + ud.mutation.Where(ps...) + return ud +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (ud *UserDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, ud.sqlExec, ud.mutation, ud.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (ud *UserDelete) ExecX(ctx context.Context) int { + n, err := ud.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (ud *UserDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(user.Table, sqlgraph.NewFieldSpec(user.FieldID, field.TypeString)) + if ps := ud.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, ud.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + ud.mutation.done = true + return affected, err +} + +// UserDeleteOne is the builder for deleting a single User entity. +type UserDeleteOne struct { + ud *UserDelete +} + +// Where appends a list predicates to the UserDelete builder. +func (udo *UserDeleteOne) Where(ps ...predicate.User) *UserDeleteOne { + udo.ud.mutation.Where(ps...) + return udo +} + +// Exec executes the deletion query. +func (udo *UserDeleteOne) Exec(ctx context.Context) error { + n, err := udo.ud.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{user.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (udo *UserDeleteOne) ExecX(ctx context.Context) { + if err := udo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/user_query.go b/internal/database/ent/db/user_query.go new file mode 100644 index 00000000..5fef02bf --- /dev/null +++ b/internal/database/ent/db/user_query.go @@ -0,0 +1,753 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" +) + +// UserQuery is the builder for querying User entities. +type UserQuery struct { + config + ctx *QueryContext + order []user.OrderOption + inters []Interceptor + predicates []predicate.User + withSubscriptions *UserSubscriptionQuery + withConnections *UserConnectionQuery + withContent *UserContentQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserQuery builder. +func (uq *UserQuery) Where(ps ...predicate.User) *UserQuery { + uq.predicates = append(uq.predicates, ps...) + return uq +} + +// Limit the number of records to be returned by this query. +func (uq *UserQuery) Limit(limit int) *UserQuery { + uq.ctx.Limit = &limit + return uq +} + +// Offset to start from. +func (uq *UserQuery) Offset(offset int) *UserQuery { + uq.ctx.Offset = &offset + return uq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (uq *UserQuery) Unique(unique bool) *UserQuery { + uq.ctx.Unique = &unique + return uq +} + +// Order specifies how the records should be ordered. +func (uq *UserQuery) Order(o ...user.OrderOption) *UserQuery { + uq.order = append(uq.order, o...) + return uq +} + +// QuerySubscriptions chains the current query on the "subscriptions" edge. +func (uq *UserQuery) QuerySubscriptions() *UserSubscriptionQuery { + query := (&UserSubscriptionClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(usersubscription.Table, usersubscription.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.SubscriptionsTable, user.SubscriptionsColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryConnections chains the current query on the "connections" edge. +func (uq *UserQuery) QueryConnections() *UserConnectionQuery { + query := (&UserConnectionClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(userconnection.Table, userconnection.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.ConnectionsTable, user.ConnectionsColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryContent chains the current query on the "content" edge. +func (uq *UserQuery) QueryContent() *UserContentQuery { + query := (&UserContentClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(usercontent.Table, usercontent.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.ContentTable, user.ContentColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first User entity from the query. +// Returns a *NotFoundError when no User was found. +func (uq *UserQuery) First(ctx context.Context) (*User, error) { + nodes, err := uq.Limit(1).All(setContextOp(ctx, uq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{user.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (uq *UserQuery) FirstX(ctx context.Context) *User { + node, err := uq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first User ID from the query. +// Returns a *NotFoundError when no User ID was found. +func (uq *UserQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = uq.Limit(1).IDs(setContextOp(ctx, uq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{user.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (uq *UserQuery) FirstIDX(ctx context.Context) string { + id, err := uq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single User entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one User entity is found. +// Returns a *NotFoundError when no User entities are found. +func (uq *UserQuery) Only(ctx context.Context) (*User, error) { + nodes, err := uq.Limit(2).All(setContextOp(ctx, uq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{user.Label} + default: + return nil, &NotSingularError{user.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (uq *UserQuery) OnlyX(ctx context.Context) *User { + node, err := uq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only User ID in the query. +// Returns a *NotSingularError when more than one User ID is found. +// Returns a *NotFoundError when no entities are found. +func (uq *UserQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = uq.Limit(2).IDs(setContextOp(ctx, uq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{user.Label} + default: + err = &NotSingularError{user.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (uq *UserQuery) OnlyIDX(ctx context.Context) string { + id, err := uq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Users. +func (uq *UserQuery) All(ctx context.Context) ([]*User, error) { + ctx = setContextOp(ctx, uq.ctx, "All") + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*User, *UserQuery]() + return withInterceptors[[]*User](ctx, uq, qr, uq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (uq *UserQuery) AllX(ctx context.Context) []*User { + nodes, err := uq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of User IDs. +func (uq *UserQuery) IDs(ctx context.Context) (ids []string, err error) { + if uq.ctx.Unique == nil && uq.path != nil { + uq.Unique(true) + } + ctx = setContextOp(ctx, uq.ctx, "IDs") + if err = uq.Select(user.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (uq *UserQuery) IDsX(ctx context.Context) []string { + ids, err := uq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (uq *UserQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, uq.ctx, "Count") + if err := uq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, uq, querierCount[*UserQuery](), uq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (uq *UserQuery) CountX(ctx context.Context) int { + count, err := uq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (uq *UserQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, uq.ctx, "Exist") + switch _, err := uq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (uq *UserQuery) ExistX(ctx context.Context) bool { + exist, err := uq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (uq *UserQuery) Clone() *UserQuery { + if uq == nil { + return nil + } + return &UserQuery{ + config: uq.config, + ctx: uq.ctx.Clone(), + order: append([]user.OrderOption{}, uq.order...), + inters: append([]Interceptor{}, uq.inters...), + predicates: append([]predicate.User{}, uq.predicates...), + withSubscriptions: uq.withSubscriptions.Clone(), + withConnections: uq.withConnections.Clone(), + withContent: uq.withContent.Clone(), + // clone intermediate query. + sql: uq.sql.Clone(), + path: uq.path, + } +} + +// WithSubscriptions tells the query-builder to eager-load the nodes that are connected to +// the "subscriptions" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithSubscriptions(opts ...func(*UserSubscriptionQuery)) *UserQuery { + query := (&UserSubscriptionClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withSubscriptions = query + return uq +} + +// WithConnections tells the query-builder to eager-load the nodes that are connected to +// the "connections" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithConnections(opts ...func(*UserConnectionQuery)) *UserQuery { + query := (&UserConnectionClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withConnections = query + return uq +} + +// WithContent tells the query-builder to eager-load the nodes that are connected to +// the "content" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithContent(opts ...func(*UserContentQuery)) *UserQuery { + query := (&UserContentClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withContent = query + return uq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.User.Query(). +// GroupBy(user.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (uq *UserQuery) GroupBy(field string, fields ...string) *UserGroupBy { + uq.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserGroupBy{build: uq} + grbuild.flds = &uq.ctx.Fields + grbuild.label = user.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.User.Query(). +// Select(user.FieldCreatedAt). +// Scan(ctx, &v) +func (uq *UserQuery) Select(fields ...string) *UserSelect { + uq.ctx.Fields = append(uq.ctx.Fields, fields...) + sbuild := &UserSelect{UserQuery: uq} + sbuild.label = user.Label + sbuild.flds, sbuild.scan = &uq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserSelect configured with the given aggregations. +func (uq *UserQuery) Aggregate(fns ...AggregateFunc) *UserSelect { + return uq.Select().Aggregate(fns...) +} + +func (uq *UserQuery) prepareQuery(ctx context.Context) error { + for _, inter := range uq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, uq); err != nil { + return err + } + } + } + for _, f := range uq.ctx.Fields { + if !user.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if uq.path != nil { + prev, err := uq.path(ctx) + if err != nil { + return err + } + uq.sql = prev + } + return nil +} + +func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, error) { + var ( + nodes = []*User{} + _spec = uq.querySpec() + loadedTypes = [3]bool{ + uq.withSubscriptions != nil, + uq.withConnections != nil, + uq.withContent != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*User).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &User{config: uq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, uq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := uq.withSubscriptions; query != nil { + if err := uq.loadSubscriptions(ctx, query, nodes, + func(n *User) { n.Edges.Subscriptions = []*UserSubscription{} }, + func(n *User, e *UserSubscription) { n.Edges.Subscriptions = append(n.Edges.Subscriptions, e) }); err != nil { + return nil, err + } + } + if query := uq.withConnections; query != nil { + if err := uq.loadConnections(ctx, query, nodes, + func(n *User) { n.Edges.Connections = []*UserConnection{} }, + func(n *User, e *UserConnection) { n.Edges.Connections = append(n.Edges.Connections, e) }); err != nil { + return nil, err + } + } + if query := uq.withContent; query != nil { + if err := uq.loadContent(ctx, query, nodes, + func(n *User) { n.Edges.Content = []*UserContent{} }, + func(n *User, e *UserContent) { n.Edges.Content = append(n.Edges.Content, e) }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (uq *UserQuery) loadSubscriptions(ctx context.Context, query *UserSubscriptionQuery, nodes []*User, init func(*User), assign func(*User, *UserSubscription)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(usersubscription.FieldUserID) + } + query.Where(predicate.UserSubscription(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.SubscriptionsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.UserID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} +func (uq *UserQuery) loadConnections(ctx context.Context, query *UserConnectionQuery, nodes []*User, init func(*User), assign func(*User, *UserConnection)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(userconnection.FieldUserID) + } + query.Where(predicate.UserConnection(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.ConnectionsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.UserID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} +func (uq *UserQuery) loadContent(ctx context.Context, query *UserContentQuery, nodes []*User, init func(*User), assign func(*User, *UserContent)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(usercontent.FieldUserID) + } + query.Where(predicate.UserContent(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.ContentColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.UserID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} + +func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) { + _spec := uq.querySpec() + _spec.Node.Columns = uq.ctx.Fields + if len(uq.ctx.Fields) > 0 { + _spec.Unique = uq.ctx.Unique != nil && *uq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, uq.driver, _spec) +} + +func (uq *UserQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeString)) + _spec.From = uq.sql + if unique := uq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if uq.path != nil { + _spec.Unique = true + } + if fields := uq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, user.FieldID) + for i := range fields { + if fields[i] != user.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := uq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := uq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := uq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := uq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (uq *UserQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(uq.driver.Dialect()) + t1 := builder.Table(user.Table) + columns := uq.ctx.Fields + if len(columns) == 0 { + columns = user.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if uq.sql != nil { + selector = uq.sql + selector.Select(selector.Columns(columns...)...) + } + if uq.ctx.Unique != nil && *uq.ctx.Unique { + selector.Distinct() + } + for _, p := range uq.predicates { + p(selector) + } + for _, p := range uq.order { + p(selector) + } + if offset := uq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := uq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// UserGroupBy is the group-by builder for User entities. +type UserGroupBy struct { + selector + build *UserQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (ugb *UserGroupBy) Aggregate(fns ...AggregateFunc) *UserGroupBy { + ugb.fns = append(ugb.fns, fns...) + return ugb +} + +// Scan applies the selector query and scans the result into the given value. +func (ugb *UserGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ugb.build.ctx, "GroupBy") + if err := ugb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserQuery, *UserGroupBy](ctx, ugb.build, ugb, ugb.build.inters, v) +} + +func (ugb *UserGroupBy) sqlScan(ctx context.Context, root *UserQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(ugb.fns)) + for _, fn := range ugb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*ugb.flds)+len(ugb.fns)) + for _, f := range *ugb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*ugb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ugb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserSelect is the builder for selecting fields of User entities. +type UserSelect struct { + *UserQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (us *UserSelect) Aggregate(fns ...AggregateFunc) *UserSelect { + us.fns = append(us.fns, fns...) + return us +} + +// Scan applies the selector query and scans the result into the given value. +func (us *UserSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, us.ctx, "Select") + if err := us.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserQuery, *UserSelect](ctx, us.UserQuery, us, us.inters, v) +} + +func (us *UserSelect) sqlScan(ctx context.Context, root *UserQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(us.fns)) + for _, fn := range us.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*us.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := us.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/user_update.go b/internal/database/ent/db/user_update.go new file mode 100644 index 00000000..4d7932c2 --- /dev/null +++ b/internal/database/ent/db/user_update.go @@ -0,0 +1,813 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/dialect/sql/sqljson" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" +) + +// UserUpdate is the builder for updating User entities. +type UserUpdate struct { + config + hooks []Hook + mutation *UserMutation +} + +// Where appends a list predicates to the UserUpdate builder. +func (uu *UserUpdate) Where(ps ...predicate.User) *UserUpdate { + uu.mutation.Where(ps...) + return uu +} + +// SetUpdatedAt sets the "updated_at" field. +func (uu *UserUpdate) SetUpdatedAt(i int) *UserUpdate { + uu.mutation.ResetUpdatedAt() + uu.mutation.SetUpdatedAt(i) + return uu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (uu *UserUpdate) AddUpdatedAt(i int) *UserUpdate { + uu.mutation.AddUpdatedAt(i) + return uu +} + +// SetPermissions sets the "permissions" field. +func (uu *UserUpdate) SetPermissions(s string) *UserUpdate { + uu.mutation.SetPermissions(s) + return uu +} + +// SetNillablePermissions sets the "permissions" field if the given value is not nil. +func (uu *UserUpdate) SetNillablePermissions(s *string) *UserUpdate { + if s != nil { + uu.SetPermissions(*s) + } + return uu +} + +// SetFeatureFlags sets the "feature_flags" field. +func (uu *UserUpdate) SetFeatureFlags(s []string) *UserUpdate { + uu.mutation.SetFeatureFlags(s) + return uu +} + +// AppendFeatureFlags appends s to the "feature_flags" field. +func (uu *UserUpdate) AppendFeatureFlags(s []string) *UserUpdate { + uu.mutation.AppendFeatureFlags(s) + return uu +} + +// ClearFeatureFlags clears the value of the "feature_flags" field. +func (uu *UserUpdate) ClearFeatureFlags() *UserUpdate { + uu.mutation.ClearFeatureFlags() + return uu +} + +// AddSubscriptionIDs adds the "subscriptions" edge to the UserSubscription entity by IDs. +func (uu *UserUpdate) AddSubscriptionIDs(ids ...string) *UserUpdate { + uu.mutation.AddSubscriptionIDs(ids...) + return uu +} + +// AddSubscriptions adds the "subscriptions" edges to the UserSubscription entity. +func (uu *UserUpdate) AddSubscriptions(u ...*UserSubscription) *UserUpdate { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uu.AddSubscriptionIDs(ids...) +} + +// AddConnectionIDs adds the "connections" edge to the UserConnection entity by IDs. +func (uu *UserUpdate) AddConnectionIDs(ids ...string) *UserUpdate { + uu.mutation.AddConnectionIDs(ids...) + return uu +} + +// AddConnections adds the "connections" edges to the UserConnection entity. +func (uu *UserUpdate) AddConnections(u ...*UserConnection) *UserUpdate { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uu.AddConnectionIDs(ids...) +} + +// AddContentIDs adds the "content" edge to the UserContent entity by IDs. +func (uu *UserUpdate) AddContentIDs(ids ...string) *UserUpdate { + uu.mutation.AddContentIDs(ids...) + return uu +} + +// AddContent adds the "content" edges to the UserContent entity. +func (uu *UserUpdate) AddContent(u ...*UserContent) *UserUpdate { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uu.AddContentIDs(ids...) +} + +// Mutation returns the UserMutation object of the builder. +func (uu *UserUpdate) Mutation() *UserMutation { + return uu.mutation +} + +// ClearSubscriptions clears all "subscriptions" edges to the UserSubscription entity. +func (uu *UserUpdate) ClearSubscriptions() *UserUpdate { + uu.mutation.ClearSubscriptions() + return uu +} + +// RemoveSubscriptionIDs removes the "subscriptions" edge to UserSubscription entities by IDs. +func (uu *UserUpdate) RemoveSubscriptionIDs(ids ...string) *UserUpdate { + uu.mutation.RemoveSubscriptionIDs(ids...) + return uu +} + +// RemoveSubscriptions removes "subscriptions" edges to UserSubscription entities. +func (uu *UserUpdate) RemoveSubscriptions(u ...*UserSubscription) *UserUpdate { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uu.RemoveSubscriptionIDs(ids...) +} + +// ClearConnections clears all "connections" edges to the UserConnection entity. +func (uu *UserUpdate) ClearConnections() *UserUpdate { + uu.mutation.ClearConnections() + return uu +} + +// RemoveConnectionIDs removes the "connections" edge to UserConnection entities by IDs. +func (uu *UserUpdate) RemoveConnectionIDs(ids ...string) *UserUpdate { + uu.mutation.RemoveConnectionIDs(ids...) + return uu +} + +// RemoveConnections removes "connections" edges to UserConnection entities. +func (uu *UserUpdate) RemoveConnections(u ...*UserConnection) *UserUpdate { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uu.RemoveConnectionIDs(ids...) +} + +// ClearContent clears all "content" edges to the UserContent entity. +func (uu *UserUpdate) ClearContent() *UserUpdate { + uu.mutation.ClearContent() + return uu +} + +// RemoveContentIDs removes the "content" edge to UserContent entities by IDs. +func (uu *UserUpdate) RemoveContentIDs(ids ...string) *UserUpdate { + uu.mutation.RemoveContentIDs(ids...) + return uu +} + +// RemoveContent removes "content" edges to UserContent entities. +func (uu *UserUpdate) RemoveContent(u ...*UserContent) *UserUpdate { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uu.RemoveContentIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (uu *UserUpdate) Save(ctx context.Context) (int, error) { + uu.defaults() + return withHooks(ctx, uu.sqlSave, uu.mutation, uu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (uu *UserUpdate) SaveX(ctx context.Context) int { + affected, err := uu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (uu *UserUpdate) Exec(ctx context.Context) error { + _, err := uu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (uu *UserUpdate) ExecX(ctx context.Context) { + if err := uu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (uu *UserUpdate) defaults() { + if _, ok := uu.mutation.UpdatedAt(); !ok { + v := user.UpdateDefaultUpdatedAt() + uu.mutation.SetUpdatedAt(v) + } +} + +func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { + _spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeString)) + if ps := uu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := uu.mutation.UpdatedAt(); ok { + _spec.SetField(user.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := uu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(user.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := uu.mutation.Permissions(); ok { + _spec.SetField(user.FieldPermissions, field.TypeString, value) + } + if value, ok := uu.mutation.FeatureFlags(); ok { + _spec.SetField(user.FieldFeatureFlags, field.TypeJSON, value) + } + if value, ok := uu.mutation.AppendedFeatureFlags(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, user.FieldFeatureFlags, value) + }) + } + if uu.mutation.FeatureFlagsCleared() { + _spec.ClearField(user.FieldFeatureFlags, field.TypeJSON) + } + if uu.mutation.SubscriptionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SubscriptionsTable, + Columns: []string{user.SubscriptionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.RemovedSubscriptionsIDs(); len(nodes) > 0 && !uu.mutation.SubscriptionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SubscriptionsTable, + Columns: []string{user.SubscriptionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.SubscriptionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SubscriptionsTable, + Columns: []string{user.SubscriptionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if uu.mutation.ConnectionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ConnectionsTable, + Columns: []string{user.ConnectionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.RemovedConnectionsIDs(); len(nodes) > 0 && !uu.mutation.ConnectionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ConnectionsTable, + Columns: []string{user.ConnectionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.ConnectionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ConnectionsTable, + Columns: []string{user.ConnectionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if uu.mutation.ContentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ContentTable, + Columns: []string{user.ContentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.RemovedContentIDs(); len(nodes) > 0 && !uu.mutation.ContentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ContentTable, + Columns: []string{user.ContentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.ContentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ContentTable, + Columns: []string{user.ContentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{user.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + uu.mutation.done = true + return n, nil +} + +// UserUpdateOne is the builder for updating a single User entity. +type UserUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (uuo *UserUpdateOne) SetUpdatedAt(i int) *UserUpdateOne { + uuo.mutation.ResetUpdatedAt() + uuo.mutation.SetUpdatedAt(i) + return uuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (uuo *UserUpdateOne) AddUpdatedAt(i int) *UserUpdateOne { + uuo.mutation.AddUpdatedAt(i) + return uuo +} + +// SetPermissions sets the "permissions" field. +func (uuo *UserUpdateOne) SetPermissions(s string) *UserUpdateOne { + uuo.mutation.SetPermissions(s) + return uuo +} + +// SetNillablePermissions sets the "permissions" field if the given value is not nil. +func (uuo *UserUpdateOne) SetNillablePermissions(s *string) *UserUpdateOne { + if s != nil { + uuo.SetPermissions(*s) + } + return uuo +} + +// SetFeatureFlags sets the "feature_flags" field. +func (uuo *UserUpdateOne) SetFeatureFlags(s []string) *UserUpdateOne { + uuo.mutation.SetFeatureFlags(s) + return uuo +} + +// AppendFeatureFlags appends s to the "feature_flags" field. +func (uuo *UserUpdateOne) AppendFeatureFlags(s []string) *UserUpdateOne { + uuo.mutation.AppendFeatureFlags(s) + return uuo +} + +// ClearFeatureFlags clears the value of the "feature_flags" field. +func (uuo *UserUpdateOne) ClearFeatureFlags() *UserUpdateOne { + uuo.mutation.ClearFeatureFlags() + return uuo +} + +// AddSubscriptionIDs adds the "subscriptions" edge to the UserSubscription entity by IDs. +func (uuo *UserUpdateOne) AddSubscriptionIDs(ids ...string) *UserUpdateOne { + uuo.mutation.AddSubscriptionIDs(ids...) + return uuo +} + +// AddSubscriptions adds the "subscriptions" edges to the UserSubscription entity. +func (uuo *UserUpdateOne) AddSubscriptions(u ...*UserSubscription) *UserUpdateOne { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uuo.AddSubscriptionIDs(ids...) +} + +// AddConnectionIDs adds the "connections" edge to the UserConnection entity by IDs. +func (uuo *UserUpdateOne) AddConnectionIDs(ids ...string) *UserUpdateOne { + uuo.mutation.AddConnectionIDs(ids...) + return uuo +} + +// AddConnections adds the "connections" edges to the UserConnection entity. +func (uuo *UserUpdateOne) AddConnections(u ...*UserConnection) *UserUpdateOne { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uuo.AddConnectionIDs(ids...) +} + +// AddContentIDs adds the "content" edge to the UserContent entity by IDs. +func (uuo *UserUpdateOne) AddContentIDs(ids ...string) *UserUpdateOne { + uuo.mutation.AddContentIDs(ids...) + return uuo +} + +// AddContent adds the "content" edges to the UserContent entity. +func (uuo *UserUpdateOne) AddContent(u ...*UserContent) *UserUpdateOne { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uuo.AddContentIDs(ids...) +} + +// Mutation returns the UserMutation object of the builder. +func (uuo *UserUpdateOne) Mutation() *UserMutation { + return uuo.mutation +} + +// ClearSubscriptions clears all "subscriptions" edges to the UserSubscription entity. +func (uuo *UserUpdateOne) ClearSubscriptions() *UserUpdateOne { + uuo.mutation.ClearSubscriptions() + return uuo +} + +// RemoveSubscriptionIDs removes the "subscriptions" edge to UserSubscription entities by IDs. +func (uuo *UserUpdateOne) RemoveSubscriptionIDs(ids ...string) *UserUpdateOne { + uuo.mutation.RemoveSubscriptionIDs(ids...) + return uuo +} + +// RemoveSubscriptions removes "subscriptions" edges to UserSubscription entities. +func (uuo *UserUpdateOne) RemoveSubscriptions(u ...*UserSubscription) *UserUpdateOne { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uuo.RemoveSubscriptionIDs(ids...) +} + +// ClearConnections clears all "connections" edges to the UserConnection entity. +func (uuo *UserUpdateOne) ClearConnections() *UserUpdateOne { + uuo.mutation.ClearConnections() + return uuo +} + +// RemoveConnectionIDs removes the "connections" edge to UserConnection entities by IDs. +func (uuo *UserUpdateOne) RemoveConnectionIDs(ids ...string) *UserUpdateOne { + uuo.mutation.RemoveConnectionIDs(ids...) + return uuo +} + +// RemoveConnections removes "connections" edges to UserConnection entities. +func (uuo *UserUpdateOne) RemoveConnections(u ...*UserConnection) *UserUpdateOne { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uuo.RemoveConnectionIDs(ids...) +} + +// ClearContent clears all "content" edges to the UserContent entity. +func (uuo *UserUpdateOne) ClearContent() *UserUpdateOne { + uuo.mutation.ClearContent() + return uuo +} + +// RemoveContentIDs removes the "content" edge to UserContent entities by IDs. +func (uuo *UserUpdateOne) RemoveContentIDs(ids ...string) *UserUpdateOne { + uuo.mutation.RemoveContentIDs(ids...) + return uuo +} + +// RemoveContent removes "content" edges to UserContent entities. +func (uuo *UserUpdateOne) RemoveContent(u ...*UserContent) *UserUpdateOne { + ids := make([]string, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return uuo.RemoveContentIDs(ids...) +} + +// Where appends a list predicates to the UserUpdate builder. +func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne { + uuo.mutation.Where(ps...) + return uuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (uuo *UserUpdateOne) Select(field string, fields ...string) *UserUpdateOne { + uuo.fields = append([]string{field}, fields...) + return uuo +} + +// Save executes the query and returns the updated User entity. +func (uuo *UserUpdateOne) Save(ctx context.Context) (*User, error) { + uuo.defaults() + return withHooks(ctx, uuo.sqlSave, uuo.mutation, uuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (uuo *UserUpdateOne) SaveX(ctx context.Context) *User { + node, err := uuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (uuo *UserUpdateOne) Exec(ctx context.Context) error { + _, err := uuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (uuo *UserUpdateOne) ExecX(ctx context.Context) { + if err := uuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (uuo *UserUpdateOne) defaults() { + if _, ok := uuo.mutation.UpdatedAt(); !ok { + v := user.UpdateDefaultUpdatedAt() + uuo.mutation.SetUpdatedAt(v) + } +} + +func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { + _spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeString)) + id, ok := uuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "User.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := uuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, user.FieldID) + for _, f := range fields { + if !user.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != user.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := uuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := uuo.mutation.UpdatedAt(); ok { + _spec.SetField(user.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := uuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(user.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := uuo.mutation.Permissions(); ok { + _spec.SetField(user.FieldPermissions, field.TypeString, value) + } + if value, ok := uuo.mutation.FeatureFlags(); ok { + _spec.SetField(user.FieldFeatureFlags, field.TypeJSON, value) + } + if value, ok := uuo.mutation.AppendedFeatureFlags(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, user.FieldFeatureFlags, value) + }) + } + if uuo.mutation.FeatureFlagsCleared() { + _spec.ClearField(user.FieldFeatureFlags, field.TypeJSON) + } + if uuo.mutation.SubscriptionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SubscriptionsTable, + Columns: []string{user.SubscriptionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.RemovedSubscriptionsIDs(); len(nodes) > 0 && !uuo.mutation.SubscriptionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SubscriptionsTable, + Columns: []string{user.SubscriptionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.SubscriptionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SubscriptionsTable, + Columns: []string{user.SubscriptionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if uuo.mutation.ConnectionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ConnectionsTable, + Columns: []string{user.ConnectionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.RemovedConnectionsIDs(); len(nodes) > 0 && !uuo.mutation.ConnectionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ConnectionsTable, + Columns: []string{user.ConnectionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.ConnectionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ConnectionsTable, + Columns: []string{user.ConnectionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if uuo.mutation.ContentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ContentTable, + Columns: []string{user.ContentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.RemovedContentIDs(); len(nodes) > 0 && !uuo.mutation.ContentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ContentTable, + Columns: []string{user.ContentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.ContentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ContentTable, + Columns: []string{user.ContentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &User{config: uuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, uuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{user.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + uuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/userconnection.go b/internal/database/ent/db/userconnection.go new file mode 100644 index 00000000..c5b03752 --- /dev/null +++ b/internal/database/ent/db/userconnection.go @@ -0,0 +1,204 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserConnection is the model entity for the UserConnection schema. +type UserConnection struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Type holds the value of the "type" field. + Type models.ConnectionType `json:"type,omitempty"` + // UserID holds the value of the "user_id" field. + UserID string `json:"user_id,omitempty"` + // ReferenceID holds the value of the "reference_id" field. + ReferenceID string `json:"reference_id,omitempty"` + // Permissions holds the value of the "permissions" field. + Permissions string `json:"permissions,omitempty"` + // Metadata holds the value of the "metadata" field. + Metadata map[string]interface{} `json:"metadata,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserConnectionQuery when eager-loading is set. + Edges UserConnectionEdges `json:"edges"` + selectValues sql.SelectValues +} + +// UserConnectionEdges holds the relations/edges for other nodes in the graph. +type UserConnectionEdges struct { + // User holds the value of the user edge. + User *User `json:"user,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserConnectionEdges) UserOrErr() (*User, error) { + if e.User != nil { + return e.User, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: user.Label} + } + return nil, &NotLoadedError{edge: "user"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*UserConnection) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case userconnection.FieldMetadata: + values[i] = new([]byte) + case userconnection.FieldCreatedAt, userconnection.FieldUpdatedAt: + values[i] = new(sql.NullInt64) + case userconnection.FieldID, userconnection.FieldType, userconnection.FieldUserID, userconnection.FieldReferenceID, userconnection.FieldPermissions: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the UserConnection fields. +func (uc *UserConnection) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case userconnection.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + uc.ID = value.String + } + case userconnection.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + uc.CreatedAt = int(value.Int64) + } + case userconnection.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + uc.UpdatedAt = int(value.Int64) + } + case userconnection.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + uc.Type = models.ConnectionType(value.String) + } + case userconnection.FieldUserID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field user_id", values[i]) + } else if value.Valid { + uc.UserID = value.String + } + case userconnection.FieldReferenceID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reference_id", values[i]) + } else if value.Valid { + uc.ReferenceID = value.String + } + case userconnection.FieldPermissions: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field permissions", values[i]) + } else if value.Valid { + uc.Permissions = value.String + } + case userconnection.FieldMetadata: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field metadata", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &uc.Metadata); err != nil { + return fmt.Errorf("unmarshal field metadata: %w", err) + } + } + default: + uc.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the UserConnection. +// This includes values selected through modifiers, order, etc. +func (uc *UserConnection) Value(name string) (ent.Value, error) { + return uc.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the UserConnection entity. +func (uc *UserConnection) QueryUser() *UserQuery { + return NewUserConnectionClient(uc.config).QueryUser(uc) +} + +// Update returns a builder for updating this UserConnection. +// Note that you need to call UserConnection.Unwrap() before calling this method if this UserConnection +// was returned from a transaction, and the transaction was committed or rolled back. +func (uc *UserConnection) Update() *UserConnectionUpdateOne { + return NewUserConnectionClient(uc.config).UpdateOne(uc) +} + +// Unwrap unwraps the UserConnection entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (uc *UserConnection) Unwrap() *UserConnection { + _tx, ok := uc.config.driver.(*txDriver) + if !ok { + panic("db: UserConnection is not a transactional entity") + } + uc.config.driver = _tx.drv + return uc +} + +// String implements the fmt.Stringer. +func (uc *UserConnection) String() string { + var builder strings.Builder + builder.WriteString("UserConnection(") + builder.WriteString(fmt.Sprintf("id=%v, ", uc.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", uc.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", uc.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("type=") + builder.WriteString(fmt.Sprintf("%v", uc.Type)) + builder.WriteString(", ") + builder.WriteString("user_id=") + builder.WriteString(uc.UserID) + builder.WriteString(", ") + builder.WriteString("reference_id=") + builder.WriteString(uc.ReferenceID) + builder.WriteString(", ") + builder.WriteString("permissions=") + builder.WriteString(uc.Permissions) + builder.WriteString(", ") + builder.WriteString("metadata=") + builder.WriteString(fmt.Sprintf("%v", uc.Metadata)) + builder.WriteByte(')') + return builder.String() +} + +// UserConnections is a parsable slice of UserConnection. +type UserConnections []*UserConnection diff --git a/internal/database/ent/db/userconnection/userconnection.go b/internal/database/ent/db/userconnection/userconnection.go new file mode 100644 index 00000000..1cb8085b --- /dev/null +++ b/internal/database/ent/db/userconnection/userconnection.go @@ -0,0 +1,140 @@ +// Code generated by ent, DO NOT EDIT. + +package userconnection + +import ( + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/models" +) + +const ( + // Label holds the string label denoting the userconnection type in the database. + Label = "user_connection" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldUserID holds the string denoting the user_id field in the database. + FieldUserID = "user_id" + // FieldReferenceID holds the string denoting the reference_id field in the database. + FieldReferenceID = "reference_id" + // FieldPermissions holds the string denoting the permissions field in the database. + FieldPermissions = "permissions" + // FieldMetadata holds the string denoting the metadata field in the database. + FieldMetadata = "metadata" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // Table holds the table name of the userconnection in the database. + Table = "user_connections" + // UserTable is the table that holds the user relation/edge. + UserTable = "user_connections" + // UserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + UserInverseTable = "users" + // UserColumn is the table column denoting the user relation/edge. + UserColumn = "user_id" +) + +// Columns holds all SQL columns for userconnection fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldType, + FieldUserID, + FieldReferenceID, + FieldPermissions, + FieldMetadata, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // DefaultPermissions holds the default value on creation for the "permissions" field. + DefaultPermissions string + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save. +func TypeValidator(_type models.ConnectionType) error { + switch _type { + case "wargaming": + return nil + default: + return fmt.Errorf("userconnection: invalid enum value for type field: %q", _type) + } +} + +// OrderOption defines the ordering options for the UserConnection queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByUserID orders the results by the user_id field. +func ByUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserID, opts...).ToFunc() +} + +// ByReferenceID orders the results by the reference_id field. +func ByReferenceID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReferenceID, opts...).ToFunc() +} + +// ByPermissions orders the results by the permissions field. +func ByPermissions(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPermissions, opts...).ToFunc() +} + +// ByUserField orders the results by user field. +func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...)) + } +} +func newUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) +} diff --git a/internal/database/ent/db/userconnection/where.go b/internal/database/ent/db/userconnection/where.go new file mode 100644 index 00000000..6d1f373a --- /dev/null +++ b/internal/database/ent/db/userconnection/where.go @@ -0,0 +1,453 @@ +// Code generated by ent, DO NOT EDIT. + +package userconnection + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. +func UserID(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldUserID, v)) +} + +// ReferenceID applies equality check predicate on the "reference_id" field. It's identical to ReferenceIDEQ. +func ReferenceID(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldReferenceID, v)) +} + +// Permissions applies equality check predicate on the "permissions" field. It's identical to PermissionsEQ. +func Permissions(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldPermissions, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v models.ConnectionType) predicate.UserConnection { + vc := v + return predicate.UserConnection(sql.FieldEQ(FieldType, vc)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v models.ConnectionType) predicate.UserConnection { + vc := v + return predicate.UserConnection(sql.FieldNEQ(FieldType, vc)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...models.ConnectionType) predicate.UserConnection { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.UserConnection(sql.FieldIn(FieldType, v...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...models.ConnectionType) predicate.UserConnection { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.UserConnection(sql.FieldNotIn(FieldType, v...)) +} + +// UserIDEQ applies the EQ predicate on the "user_id" field. +func UserIDEQ(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldUserID, v)) +} + +// UserIDNEQ applies the NEQ predicate on the "user_id" field. +func UserIDNEQ(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNEQ(FieldUserID, v)) +} + +// UserIDIn applies the In predicate on the "user_id" field. +func UserIDIn(vs ...string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldIn(FieldUserID, vs...)) +} + +// UserIDNotIn applies the NotIn predicate on the "user_id" field. +func UserIDNotIn(vs ...string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNotIn(FieldUserID, vs...)) +} + +// UserIDGT applies the GT predicate on the "user_id" field. +func UserIDGT(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGT(FieldUserID, v)) +} + +// UserIDGTE applies the GTE predicate on the "user_id" field. +func UserIDGTE(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGTE(FieldUserID, v)) +} + +// UserIDLT applies the LT predicate on the "user_id" field. +func UserIDLT(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLT(FieldUserID, v)) +} + +// UserIDLTE applies the LTE predicate on the "user_id" field. +func UserIDLTE(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLTE(FieldUserID, v)) +} + +// UserIDContains applies the Contains predicate on the "user_id" field. +func UserIDContains(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldContains(FieldUserID, v)) +} + +// UserIDHasPrefix applies the HasPrefix predicate on the "user_id" field. +func UserIDHasPrefix(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldHasPrefix(FieldUserID, v)) +} + +// UserIDHasSuffix applies the HasSuffix predicate on the "user_id" field. +func UserIDHasSuffix(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldHasSuffix(FieldUserID, v)) +} + +// UserIDEqualFold applies the EqualFold predicate on the "user_id" field. +func UserIDEqualFold(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEqualFold(FieldUserID, v)) +} + +// UserIDContainsFold applies the ContainsFold predicate on the "user_id" field. +func UserIDContainsFold(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldContainsFold(FieldUserID, v)) +} + +// ReferenceIDEQ applies the EQ predicate on the "reference_id" field. +func ReferenceIDEQ(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldReferenceID, v)) +} + +// ReferenceIDNEQ applies the NEQ predicate on the "reference_id" field. +func ReferenceIDNEQ(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNEQ(FieldReferenceID, v)) +} + +// ReferenceIDIn applies the In predicate on the "reference_id" field. +func ReferenceIDIn(vs ...string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldIn(FieldReferenceID, vs...)) +} + +// ReferenceIDNotIn applies the NotIn predicate on the "reference_id" field. +func ReferenceIDNotIn(vs ...string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNotIn(FieldReferenceID, vs...)) +} + +// ReferenceIDGT applies the GT predicate on the "reference_id" field. +func ReferenceIDGT(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGT(FieldReferenceID, v)) +} + +// ReferenceIDGTE applies the GTE predicate on the "reference_id" field. +func ReferenceIDGTE(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGTE(FieldReferenceID, v)) +} + +// ReferenceIDLT applies the LT predicate on the "reference_id" field. +func ReferenceIDLT(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLT(FieldReferenceID, v)) +} + +// ReferenceIDLTE applies the LTE predicate on the "reference_id" field. +func ReferenceIDLTE(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLTE(FieldReferenceID, v)) +} + +// ReferenceIDContains applies the Contains predicate on the "reference_id" field. +func ReferenceIDContains(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldContains(FieldReferenceID, v)) +} + +// ReferenceIDHasPrefix applies the HasPrefix predicate on the "reference_id" field. +func ReferenceIDHasPrefix(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldHasPrefix(FieldReferenceID, v)) +} + +// ReferenceIDHasSuffix applies the HasSuffix predicate on the "reference_id" field. +func ReferenceIDHasSuffix(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldHasSuffix(FieldReferenceID, v)) +} + +// ReferenceIDEqualFold applies the EqualFold predicate on the "reference_id" field. +func ReferenceIDEqualFold(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEqualFold(FieldReferenceID, v)) +} + +// ReferenceIDContainsFold applies the ContainsFold predicate on the "reference_id" field. +func ReferenceIDContainsFold(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldContainsFold(FieldReferenceID, v)) +} + +// PermissionsEQ applies the EQ predicate on the "permissions" field. +func PermissionsEQ(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEQ(FieldPermissions, v)) +} + +// PermissionsNEQ applies the NEQ predicate on the "permissions" field. +func PermissionsNEQ(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNEQ(FieldPermissions, v)) +} + +// PermissionsIn applies the In predicate on the "permissions" field. +func PermissionsIn(vs ...string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldIn(FieldPermissions, vs...)) +} + +// PermissionsNotIn applies the NotIn predicate on the "permissions" field. +func PermissionsNotIn(vs ...string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldNotIn(FieldPermissions, vs...)) +} + +// PermissionsGT applies the GT predicate on the "permissions" field. +func PermissionsGT(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGT(FieldPermissions, v)) +} + +// PermissionsGTE applies the GTE predicate on the "permissions" field. +func PermissionsGTE(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldGTE(FieldPermissions, v)) +} + +// PermissionsLT applies the LT predicate on the "permissions" field. +func PermissionsLT(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLT(FieldPermissions, v)) +} + +// PermissionsLTE applies the LTE predicate on the "permissions" field. +func PermissionsLTE(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldLTE(FieldPermissions, v)) +} + +// PermissionsContains applies the Contains predicate on the "permissions" field. +func PermissionsContains(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldContains(FieldPermissions, v)) +} + +// PermissionsHasPrefix applies the HasPrefix predicate on the "permissions" field. +func PermissionsHasPrefix(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldHasPrefix(FieldPermissions, v)) +} + +// PermissionsHasSuffix applies the HasSuffix predicate on the "permissions" field. +func PermissionsHasSuffix(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldHasSuffix(FieldPermissions, v)) +} + +// PermissionsIsNil applies the IsNil predicate on the "permissions" field. +func PermissionsIsNil() predicate.UserConnection { + return predicate.UserConnection(sql.FieldIsNull(FieldPermissions)) +} + +// PermissionsNotNil applies the NotNil predicate on the "permissions" field. +func PermissionsNotNil() predicate.UserConnection { + return predicate.UserConnection(sql.FieldNotNull(FieldPermissions)) +} + +// PermissionsEqualFold applies the EqualFold predicate on the "permissions" field. +func PermissionsEqualFold(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldEqualFold(FieldPermissions, v)) +} + +// PermissionsContainsFold applies the ContainsFold predicate on the "permissions" field. +func PermissionsContainsFold(v string) predicate.UserConnection { + return predicate.UserConnection(sql.FieldContainsFold(FieldPermissions, v)) +} + +// MetadataIsNil applies the IsNil predicate on the "metadata" field. +func MetadataIsNil() predicate.UserConnection { + return predicate.UserConnection(sql.FieldIsNull(FieldMetadata)) +} + +// MetadataNotNil applies the NotNil predicate on the "metadata" field. +func MetadataNotNil() predicate.UserConnection { + return predicate.UserConnection(sql.FieldNotNull(FieldMetadata)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.UserConnection { + return predicate.UserConnection(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). +func HasUserWith(preds ...predicate.User) predicate.UserConnection { + return predicate.UserConnection(func(s *sql.Selector) { + step := newUserStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.UserConnection) predicate.UserConnection { + return predicate.UserConnection(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.UserConnection) predicate.UserConnection { + return predicate.UserConnection(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.UserConnection) predicate.UserConnection { + return predicate.UserConnection(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/userconnection_create.go b/internal/database/ent/db/userconnection_create.go new file mode 100644 index 00000000..ee25aacf --- /dev/null +++ b/internal/database/ent/db/userconnection_create.go @@ -0,0 +1,348 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserConnectionCreate is the builder for creating a UserConnection entity. +type UserConnectionCreate struct { + config + mutation *UserConnectionMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (ucc *UserConnectionCreate) SetCreatedAt(i int) *UserConnectionCreate { + ucc.mutation.SetCreatedAt(i) + return ucc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (ucc *UserConnectionCreate) SetNillableCreatedAt(i *int) *UserConnectionCreate { + if i != nil { + ucc.SetCreatedAt(*i) + } + return ucc +} + +// SetUpdatedAt sets the "updated_at" field. +func (ucc *UserConnectionCreate) SetUpdatedAt(i int) *UserConnectionCreate { + ucc.mutation.SetUpdatedAt(i) + return ucc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (ucc *UserConnectionCreate) SetNillableUpdatedAt(i *int) *UserConnectionCreate { + if i != nil { + ucc.SetUpdatedAt(*i) + } + return ucc +} + +// SetType sets the "type" field. +func (ucc *UserConnectionCreate) SetType(mt models.ConnectionType) *UserConnectionCreate { + ucc.mutation.SetType(mt) + return ucc +} + +// SetUserID sets the "user_id" field. +func (ucc *UserConnectionCreate) SetUserID(s string) *UserConnectionCreate { + ucc.mutation.SetUserID(s) + return ucc +} + +// SetReferenceID sets the "reference_id" field. +func (ucc *UserConnectionCreate) SetReferenceID(s string) *UserConnectionCreate { + ucc.mutation.SetReferenceID(s) + return ucc +} + +// SetPermissions sets the "permissions" field. +func (ucc *UserConnectionCreate) SetPermissions(s string) *UserConnectionCreate { + ucc.mutation.SetPermissions(s) + return ucc +} + +// SetNillablePermissions sets the "permissions" field if the given value is not nil. +func (ucc *UserConnectionCreate) SetNillablePermissions(s *string) *UserConnectionCreate { + if s != nil { + ucc.SetPermissions(*s) + } + return ucc +} + +// SetMetadata sets the "metadata" field. +func (ucc *UserConnectionCreate) SetMetadata(m map[string]interface{}) *UserConnectionCreate { + ucc.mutation.SetMetadata(m) + return ucc +} + +// SetID sets the "id" field. +func (ucc *UserConnectionCreate) SetID(s string) *UserConnectionCreate { + ucc.mutation.SetID(s) + return ucc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (ucc *UserConnectionCreate) SetNillableID(s *string) *UserConnectionCreate { + if s != nil { + ucc.SetID(*s) + } + return ucc +} + +// SetUser sets the "user" edge to the User entity. +func (ucc *UserConnectionCreate) SetUser(u *User) *UserConnectionCreate { + return ucc.SetUserID(u.ID) +} + +// Mutation returns the UserConnectionMutation object of the builder. +func (ucc *UserConnectionCreate) Mutation() *UserConnectionMutation { + return ucc.mutation +} + +// Save creates the UserConnection in the database. +func (ucc *UserConnectionCreate) Save(ctx context.Context) (*UserConnection, error) { + ucc.defaults() + return withHooks(ctx, ucc.sqlSave, ucc.mutation, ucc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (ucc *UserConnectionCreate) SaveX(ctx context.Context) *UserConnection { + v, err := ucc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ucc *UserConnectionCreate) Exec(ctx context.Context) error { + _, err := ucc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucc *UserConnectionCreate) ExecX(ctx context.Context) { + if err := ucc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ucc *UserConnectionCreate) defaults() { + if _, ok := ucc.mutation.CreatedAt(); !ok { + v := userconnection.DefaultCreatedAt() + ucc.mutation.SetCreatedAt(v) + } + if _, ok := ucc.mutation.UpdatedAt(); !ok { + v := userconnection.DefaultUpdatedAt() + ucc.mutation.SetUpdatedAt(v) + } + if _, ok := ucc.mutation.Permissions(); !ok { + v := userconnection.DefaultPermissions + ucc.mutation.SetPermissions(v) + } + if _, ok := ucc.mutation.ID(); !ok { + v := userconnection.DefaultID() + ucc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ucc *UserConnectionCreate) check() error { + if _, ok := ucc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "UserConnection.created_at"`)} + } + if _, ok := ucc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "UserConnection.updated_at"`)} + } + if _, ok := ucc.mutation.GetType(); !ok { + return &ValidationError{Name: "type", err: errors.New(`db: missing required field "UserConnection.type"`)} + } + if v, ok := ucc.mutation.GetType(); ok { + if err := userconnection.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "UserConnection.type": %w`, err)} + } + } + if _, ok := ucc.mutation.UserID(); !ok { + return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "UserConnection.user_id"`)} + } + if _, ok := ucc.mutation.ReferenceID(); !ok { + return &ValidationError{Name: "reference_id", err: errors.New(`db: missing required field "UserConnection.reference_id"`)} + } + if _, ok := ucc.mutation.UserID(); !ok { + return &ValidationError{Name: "user", err: errors.New(`db: missing required edge "UserConnection.user"`)} + } + return nil +} + +func (ucc *UserConnectionCreate) sqlSave(ctx context.Context) (*UserConnection, error) { + if err := ucc.check(); err != nil { + return nil, err + } + _node, _spec := ucc.createSpec() + if err := sqlgraph.CreateNode(ctx, ucc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected UserConnection.ID type: %T", _spec.ID.Value) + } + } + ucc.mutation.id = &_node.ID + ucc.mutation.done = true + return _node, nil +} + +func (ucc *UserConnectionCreate) createSpec() (*UserConnection, *sqlgraph.CreateSpec) { + var ( + _node = &UserConnection{config: ucc.config} + _spec = sqlgraph.NewCreateSpec(userconnection.Table, sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString)) + ) + if id, ok := ucc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := ucc.mutation.CreatedAt(); ok { + _spec.SetField(userconnection.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := ucc.mutation.UpdatedAt(); ok { + _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := ucc.mutation.GetType(); ok { + _spec.SetField(userconnection.FieldType, field.TypeEnum, value) + _node.Type = value + } + if value, ok := ucc.mutation.ReferenceID(); ok { + _spec.SetField(userconnection.FieldReferenceID, field.TypeString, value) + _node.ReferenceID = value + } + if value, ok := ucc.mutation.Permissions(); ok { + _spec.SetField(userconnection.FieldPermissions, field.TypeString, value) + _node.Permissions = value + } + if value, ok := ucc.mutation.Metadata(); ok { + _spec.SetField(userconnection.FieldMetadata, field.TypeJSON, value) + _node.Metadata = value + } + if nodes := ucc.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: userconnection.UserTable, + Columns: []string{userconnection.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.UserID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// UserConnectionCreateBulk is the builder for creating many UserConnection entities in bulk. +type UserConnectionCreateBulk struct { + config + err error + builders []*UserConnectionCreate +} + +// Save creates the UserConnection entities in the database. +func (uccb *UserConnectionCreateBulk) Save(ctx context.Context) ([]*UserConnection, error) { + if uccb.err != nil { + return nil, uccb.err + } + specs := make([]*sqlgraph.CreateSpec, len(uccb.builders)) + nodes := make([]*UserConnection, len(uccb.builders)) + mutators := make([]Mutator, len(uccb.builders)) + for i := range uccb.builders { + func(i int, root context.Context) { + builder := uccb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserConnectionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, uccb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, uccb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, uccb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (uccb *UserConnectionCreateBulk) SaveX(ctx context.Context) []*UserConnection { + v, err := uccb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (uccb *UserConnectionCreateBulk) Exec(ctx context.Context) error { + _, err := uccb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (uccb *UserConnectionCreateBulk) ExecX(ctx context.Context) { + if err := uccb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/userconnection_delete.go b/internal/database/ent/db/userconnection_delete.go new file mode 100644 index 00000000..48b985f9 --- /dev/null +++ b/internal/database/ent/db/userconnection_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" +) + +// UserConnectionDelete is the builder for deleting a UserConnection entity. +type UserConnectionDelete struct { + config + hooks []Hook + mutation *UserConnectionMutation +} + +// Where appends a list predicates to the UserConnectionDelete builder. +func (ucd *UserConnectionDelete) Where(ps ...predicate.UserConnection) *UserConnectionDelete { + ucd.mutation.Where(ps...) + return ucd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (ucd *UserConnectionDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, ucd.sqlExec, ucd.mutation, ucd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucd *UserConnectionDelete) ExecX(ctx context.Context) int { + n, err := ucd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (ucd *UserConnectionDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(userconnection.Table, sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString)) + if ps := ucd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, ucd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + ucd.mutation.done = true + return affected, err +} + +// UserConnectionDeleteOne is the builder for deleting a single UserConnection entity. +type UserConnectionDeleteOne struct { + ucd *UserConnectionDelete +} + +// Where appends a list predicates to the UserConnectionDelete builder. +func (ucdo *UserConnectionDeleteOne) Where(ps ...predicate.UserConnection) *UserConnectionDeleteOne { + ucdo.ucd.mutation.Where(ps...) + return ucdo +} + +// Exec executes the deletion query. +func (ucdo *UserConnectionDeleteOne) Exec(ctx context.Context) error { + n, err := ucdo.ucd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{userconnection.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucdo *UserConnectionDeleteOne) ExecX(ctx context.Context) { + if err := ucdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/userconnection_query.go b/internal/database/ent/db/userconnection_query.go new file mode 100644 index 00000000..020530b6 --- /dev/null +++ b/internal/database/ent/db/userconnection_query.go @@ -0,0 +1,605 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" +) + +// UserConnectionQuery is the builder for querying UserConnection entities. +type UserConnectionQuery struct { + config + ctx *QueryContext + order []userconnection.OrderOption + inters []Interceptor + predicates []predicate.UserConnection + withUser *UserQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserConnectionQuery builder. +func (ucq *UserConnectionQuery) Where(ps ...predicate.UserConnection) *UserConnectionQuery { + ucq.predicates = append(ucq.predicates, ps...) + return ucq +} + +// Limit the number of records to be returned by this query. +func (ucq *UserConnectionQuery) Limit(limit int) *UserConnectionQuery { + ucq.ctx.Limit = &limit + return ucq +} + +// Offset to start from. +func (ucq *UserConnectionQuery) Offset(offset int) *UserConnectionQuery { + ucq.ctx.Offset = &offset + return ucq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (ucq *UserConnectionQuery) Unique(unique bool) *UserConnectionQuery { + ucq.ctx.Unique = &unique + return ucq +} + +// Order specifies how the records should be ordered. +func (ucq *UserConnectionQuery) Order(o ...userconnection.OrderOption) *UserConnectionQuery { + ucq.order = append(ucq.order, o...) + return ucq +} + +// QueryUser chains the current query on the "user" edge. +func (ucq *UserConnectionQuery) QueryUser() *UserQuery { + query := (&UserClient{config: ucq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := ucq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := ucq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(userconnection.Table, userconnection.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, userconnection.UserTable, userconnection.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(ucq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first UserConnection entity from the query. +// Returns a *NotFoundError when no UserConnection was found. +func (ucq *UserConnectionQuery) First(ctx context.Context) (*UserConnection, error) { + nodes, err := ucq.Limit(1).All(setContextOp(ctx, ucq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{userconnection.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (ucq *UserConnectionQuery) FirstX(ctx context.Context) *UserConnection { + node, err := ucq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first UserConnection ID from the query. +// Returns a *NotFoundError when no UserConnection ID was found. +func (ucq *UserConnectionQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = ucq.Limit(1).IDs(setContextOp(ctx, ucq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{userconnection.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (ucq *UserConnectionQuery) FirstIDX(ctx context.Context) string { + id, err := ucq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single UserConnection entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one UserConnection entity is found. +// Returns a *NotFoundError when no UserConnection entities are found. +func (ucq *UserConnectionQuery) Only(ctx context.Context) (*UserConnection, error) { + nodes, err := ucq.Limit(2).All(setContextOp(ctx, ucq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{userconnection.Label} + default: + return nil, &NotSingularError{userconnection.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (ucq *UserConnectionQuery) OnlyX(ctx context.Context) *UserConnection { + node, err := ucq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only UserConnection ID in the query. +// Returns a *NotSingularError when more than one UserConnection ID is found. +// Returns a *NotFoundError when no entities are found. +func (ucq *UserConnectionQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = ucq.Limit(2).IDs(setContextOp(ctx, ucq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{userconnection.Label} + default: + err = &NotSingularError{userconnection.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (ucq *UserConnectionQuery) OnlyIDX(ctx context.Context) string { + id, err := ucq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of UserConnections. +func (ucq *UserConnectionQuery) All(ctx context.Context) ([]*UserConnection, error) { + ctx = setContextOp(ctx, ucq.ctx, "All") + if err := ucq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*UserConnection, *UserConnectionQuery]() + return withInterceptors[[]*UserConnection](ctx, ucq, qr, ucq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (ucq *UserConnectionQuery) AllX(ctx context.Context) []*UserConnection { + nodes, err := ucq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of UserConnection IDs. +func (ucq *UserConnectionQuery) IDs(ctx context.Context) (ids []string, err error) { + if ucq.ctx.Unique == nil && ucq.path != nil { + ucq.Unique(true) + } + ctx = setContextOp(ctx, ucq.ctx, "IDs") + if err = ucq.Select(userconnection.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (ucq *UserConnectionQuery) IDsX(ctx context.Context) []string { + ids, err := ucq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (ucq *UserConnectionQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, ucq.ctx, "Count") + if err := ucq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, ucq, querierCount[*UserConnectionQuery](), ucq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (ucq *UserConnectionQuery) CountX(ctx context.Context) int { + count, err := ucq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (ucq *UserConnectionQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, ucq.ctx, "Exist") + switch _, err := ucq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (ucq *UserConnectionQuery) ExistX(ctx context.Context) bool { + exist, err := ucq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserConnectionQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (ucq *UserConnectionQuery) Clone() *UserConnectionQuery { + if ucq == nil { + return nil + } + return &UserConnectionQuery{ + config: ucq.config, + ctx: ucq.ctx.Clone(), + order: append([]userconnection.OrderOption{}, ucq.order...), + inters: append([]Interceptor{}, ucq.inters...), + predicates: append([]predicate.UserConnection{}, ucq.predicates...), + withUser: ucq.withUser.Clone(), + // clone intermediate query. + sql: ucq.sql.Clone(), + path: ucq.path, + } +} + +// WithUser tells the query-builder to eager-load the nodes that are connected to +// the "user" edge. The optional arguments are used to configure the query builder of the edge. +func (ucq *UserConnectionQuery) WithUser(opts ...func(*UserQuery)) *UserConnectionQuery { + query := (&UserClient{config: ucq.config}).Query() + for _, opt := range opts { + opt(query) + } + ucq.withUser = query + return ucq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.UserConnection.Query(). +// GroupBy(userconnection.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (ucq *UserConnectionQuery) GroupBy(field string, fields ...string) *UserConnectionGroupBy { + ucq.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserConnectionGroupBy{build: ucq} + grbuild.flds = &ucq.ctx.Fields + grbuild.label = userconnection.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.UserConnection.Query(). +// Select(userconnection.FieldCreatedAt). +// Scan(ctx, &v) +func (ucq *UserConnectionQuery) Select(fields ...string) *UserConnectionSelect { + ucq.ctx.Fields = append(ucq.ctx.Fields, fields...) + sbuild := &UserConnectionSelect{UserConnectionQuery: ucq} + sbuild.label = userconnection.Label + sbuild.flds, sbuild.scan = &ucq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserConnectionSelect configured with the given aggregations. +func (ucq *UserConnectionQuery) Aggregate(fns ...AggregateFunc) *UserConnectionSelect { + return ucq.Select().Aggregate(fns...) +} + +func (ucq *UserConnectionQuery) prepareQuery(ctx context.Context) error { + for _, inter := range ucq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, ucq); err != nil { + return err + } + } + } + for _, f := range ucq.ctx.Fields { + if !userconnection.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if ucq.path != nil { + prev, err := ucq.path(ctx) + if err != nil { + return err + } + ucq.sql = prev + } + return nil +} + +func (ucq *UserConnectionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserConnection, error) { + var ( + nodes = []*UserConnection{} + _spec = ucq.querySpec() + loadedTypes = [1]bool{ + ucq.withUser != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*UserConnection).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &UserConnection{config: ucq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, ucq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := ucq.withUser; query != nil { + if err := ucq.loadUser(ctx, query, nodes, nil, + func(n *UserConnection, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (ucq *UserConnectionQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*UserConnection, init func(*UserConnection), assign func(*UserConnection, *User)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*UserConnection) + for i := range nodes { + fk := nodes[i].UserID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (ucq *UserConnectionQuery) sqlCount(ctx context.Context) (int, error) { + _spec := ucq.querySpec() + _spec.Node.Columns = ucq.ctx.Fields + if len(ucq.ctx.Fields) > 0 { + _spec.Unique = ucq.ctx.Unique != nil && *ucq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, ucq.driver, _spec) +} + +func (ucq *UserConnectionQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(userconnection.Table, userconnection.Columns, sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString)) + _spec.From = ucq.sql + if unique := ucq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if ucq.path != nil { + _spec.Unique = true + } + if fields := ucq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, userconnection.FieldID) + for i := range fields { + if fields[i] != userconnection.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if ucq.withUser != nil { + _spec.Node.AddColumnOnce(userconnection.FieldUserID) + } + } + if ps := ucq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := ucq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := ucq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := ucq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (ucq *UserConnectionQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(ucq.driver.Dialect()) + t1 := builder.Table(userconnection.Table) + columns := ucq.ctx.Fields + if len(columns) == 0 { + columns = userconnection.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if ucq.sql != nil { + selector = ucq.sql + selector.Select(selector.Columns(columns...)...) + } + if ucq.ctx.Unique != nil && *ucq.ctx.Unique { + selector.Distinct() + } + for _, p := range ucq.predicates { + p(selector) + } + for _, p := range ucq.order { + p(selector) + } + if offset := ucq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := ucq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// UserConnectionGroupBy is the group-by builder for UserConnection entities. +type UserConnectionGroupBy struct { + selector + build *UserConnectionQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (ucgb *UserConnectionGroupBy) Aggregate(fns ...AggregateFunc) *UserConnectionGroupBy { + ucgb.fns = append(ucgb.fns, fns...) + return ucgb +} + +// Scan applies the selector query and scans the result into the given value. +func (ucgb *UserConnectionGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ucgb.build.ctx, "GroupBy") + if err := ucgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserConnectionQuery, *UserConnectionGroupBy](ctx, ucgb.build, ucgb, ucgb.build.inters, v) +} + +func (ucgb *UserConnectionGroupBy) sqlScan(ctx context.Context, root *UserConnectionQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(ucgb.fns)) + for _, fn := range ucgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*ucgb.flds)+len(ucgb.fns)) + for _, f := range *ucgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*ucgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ucgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserConnectionSelect is the builder for selecting fields of UserConnection entities. +type UserConnectionSelect struct { + *UserConnectionQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ucs *UserConnectionSelect) Aggregate(fns ...AggregateFunc) *UserConnectionSelect { + ucs.fns = append(ucs.fns, fns...) + return ucs +} + +// Scan applies the selector query and scans the result into the given value. +func (ucs *UserConnectionSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ucs.ctx, "Select") + if err := ucs.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserConnectionQuery, *UserConnectionSelect](ctx, ucs.UserConnectionQuery, ucs, ucs.inters, v) +} + +func (ucs *UserConnectionSelect) sqlScan(ctx context.Context, root *UserConnectionQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(ucs.fns)) + for _, fn := range ucs.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*ucs.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ucs.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/userconnection_update.go b/internal/database/ent/db/userconnection_update.go new file mode 100644 index 00000000..d7985deb --- /dev/null +++ b/internal/database/ent/db/userconnection_update.go @@ -0,0 +1,420 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/userconnection" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserConnectionUpdate is the builder for updating UserConnection entities. +type UserConnectionUpdate struct { + config + hooks []Hook + mutation *UserConnectionMutation +} + +// Where appends a list predicates to the UserConnectionUpdate builder. +func (ucu *UserConnectionUpdate) Where(ps ...predicate.UserConnection) *UserConnectionUpdate { + ucu.mutation.Where(ps...) + return ucu +} + +// SetUpdatedAt sets the "updated_at" field. +func (ucu *UserConnectionUpdate) SetUpdatedAt(i int) *UserConnectionUpdate { + ucu.mutation.ResetUpdatedAt() + ucu.mutation.SetUpdatedAt(i) + return ucu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (ucu *UserConnectionUpdate) AddUpdatedAt(i int) *UserConnectionUpdate { + ucu.mutation.AddUpdatedAt(i) + return ucu +} + +// SetType sets the "type" field. +func (ucu *UserConnectionUpdate) SetType(mt models.ConnectionType) *UserConnectionUpdate { + ucu.mutation.SetType(mt) + return ucu +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (ucu *UserConnectionUpdate) SetNillableType(mt *models.ConnectionType) *UserConnectionUpdate { + if mt != nil { + ucu.SetType(*mt) + } + return ucu +} + +// SetReferenceID sets the "reference_id" field. +func (ucu *UserConnectionUpdate) SetReferenceID(s string) *UserConnectionUpdate { + ucu.mutation.SetReferenceID(s) + return ucu +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (ucu *UserConnectionUpdate) SetNillableReferenceID(s *string) *UserConnectionUpdate { + if s != nil { + ucu.SetReferenceID(*s) + } + return ucu +} + +// SetPermissions sets the "permissions" field. +func (ucu *UserConnectionUpdate) SetPermissions(s string) *UserConnectionUpdate { + ucu.mutation.SetPermissions(s) + return ucu +} + +// SetNillablePermissions sets the "permissions" field if the given value is not nil. +func (ucu *UserConnectionUpdate) SetNillablePermissions(s *string) *UserConnectionUpdate { + if s != nil { + ucu.SetPermissions(*s) + } + return ucu +} + +// ClearPermissions clears the value of the "permissions" field. +func (ucu *UserConnectionUpdate) ClearPermissions() *UserConnectionUpdate { + ucu.mutation.ClearPermissions() + return ucu +} + +// SetMetadata sets the "metadata" field. +func (ucu *UserConnectionUpdate) SetMetadata(m map[string]interface{}) *UserConnectionUpdate { + ucu.mutation.SetMetadata(m) + return ucu +} + +// ClearMetadata clears the value of the "metadata" field. +func (ucu *UserConnectionUpdate) ClearMetadata() *UserConnectionUpdate { + ucu.mutation.ClearMetadata() + return ucu +} + +// Mutation returns the UserConnectionMutation object of the builder. +func (ucu *UserConnectionUpdate) Mutation() *UserConnectionMutation { + return ucu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (ucu *UserConnectionUpdate) Save(ctx context.Context) (int, error) { + ucu.defaults() + return withHooks(ctx, ucu.sqlSave, ucu.mutation, ucu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (ucu *UserConnectionUpdate) SaveX(ctx context.Context) int { + affected, err := ucu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (ucu *UserConnectionUpdate) Exec(ctx context.Context) error { + _, err := ucu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucu *UserConnectionUpdate) ExecX(ctx context.Context) { + if err := ucu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ucu *UserConnectionUpdate) defaults() { + if _, ok := ucu.mutation.UpdatedAt(); !ok { + v := userconnection.UpdateDefaultUpdatedAt() + ucu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ucu *UserConnectionUpdate) check() error { + if v, ok := ucu.mutation.GetType(); ok { + if err := userconnection.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "UserConnection.type": %w`, err)} + } + } + if _, ok := ucu.mutation.UserID(); ucu.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "UserConnection.user"`) + } + return nil +} + +func (ucu *UserConnectionUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := ucu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(userconnection.Table, userconnection.Columns, sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString)) + if ps := ucu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := ucu.mutation.UpdatedAt(); ok { + _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ucu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(userconnection.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ucu.mutation.GetType(); ok { + _spec.SetField(userconnection.FieldType, field.TypeEnum, value) + } + if value, ok := ucu.mutation.ReferenceID(); ok { + _spec.SetField(userconnection.FieldReferenceID, field.TypeString, value) + } + if value, ok := ucu.mutation.Permissions(); ok { + _spec.SetField(userconnection.FieldPermissions, field.TypeString, value) + } + if ucu.mutation.PermissionsCleared() { + _spec.ClearField(userconnection.FieldPermissions, field.TypeString) + } + if value, ok := ucu.mutation.Metadata(); ok { + _spec.SetField(userconnection.FieldMetadata, field.TypeJSON, value) + } + if ucu.mutation.MetadataCleared() { + _spec.ClearField(userconnection.FieldMetadata, field.TypeJSON) + } + if n, err = sqlgraph.UpdateNodes(ctx, ucu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{userconnection.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + ucu.mutation.done = true + return n, nil +} + +// UserConnectionUpdateOne is the builder for updating a single UserConnection entity. +type UserConnectionUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserConnectionMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (ucuo *UserConnectionUpdateOne) SetUpdatedAt(i int) *UserConnectionUpdateOne { + ucuo.mutation.ResetUpdatedAt() + ucuo.mutation.SetUpdatedAt(i) + return ucuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (ucuo *UserConnectionUpdateOne) AddUpdatedAt(i int) *UserConnectionUpdateOne { + ucuo.mutation.AddUpdatedAt(i) + return ucuo +} + +// SetType sets the "type" field. +func (ucuo *UserConnectionUpdateOne) SetType(mt models.ConnectionType) *UserConnectionUpdateOne { + ucuo.mutation.SetType(mt) + return ucuo +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (ucuo *UserConnectionUpdateOne) SetNillableType(mt *models.ConnectionType) *UserConnectionUpdateOne { + if mt != nil { + ucuo.SetType(*mt) + } + return ucuo +} + +// SetReferenceID sets the "reference_id" field. +func (ucuo *UserConnectionUpdateOne) SetReferenceID(s string) *UserConnectionUpdateOne { + ucuo.mutation.SetReferenceID(s) + return ucuo +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (ucuo *UserConnectionUpdateOne) SetNillableReferenceID(s *string) *UserConnectionUpdateOne { + if s != nil { + ucuo.SetReferenceID(*s) + } + return ucuo +} + +// SetPermissions sets the "permissions" field. +func (ucuo *UserConnectionUpdateOne) SetPermissions(s string) *UserConnectionUpdateOne { + ucuo.mutation.SetPermissions(s) + return ucuo +} + +// SetNillablePermissions sets the "permissions" field if the given value is not nil. +func (ucuo *UserConnectionUpdateOne) SetNillablePermissions(s *string) *UserConnectionUpdateOne { + if s != nil { + ucuo.SetPermissions(*s) + } + return ucuo +} + +// ClearPermissions clears the value of the "permissions" field. +func (ucuo *UserConnectionUpdateOne) ClearPermissions() *UserConnectionUpdateOne { + ucuo.mutation.ClearPermissions() + return ucuo +} + +// SetMetadata sets the "metadata" field. +func (ucuo *UserConnectionUpdateOne) SetMetadata(m map[string]interface{}) *UserConnectionUpdateOne { + ucuo.mutation.SetMetadata(m) + return ucuo +} + +// ClearMetadata clears the value of the "metadata" field. +func (ucuo *UserConnectionUpdateOne) ClearMetadata() *UserConnectionUpdateOne { + ucuo.mutation.ClearMetadata() + return ucuo +} + +// Mutation returns the UserConnectionMutation object of the builder. +func (ucuo *UserConnectionUpdateOne) Mutation() *UserConnectionMutation { + return ucuo.mutation +} + +// Where appends a list predicates to the UserConnectionUpdate builder. +func (ucuo *UserConnectionUpdateOne) Where(ps ...predicate.UserConnection) *UserConnectionUpdateOne { + ucuo.mutation.Where(ps...) + return ucuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (ucuo *UserConnectionUpdateOne) Select(field string, fields ...string) *UserConnectionUpdateOne { + ucuo.fields = append([]string{field}, fields...) + return ucuo +} + +// Save executes the query and returns the updated UserConnection entity. +func (ucuo *UserConnectionUpdateOne) Save(ctx context.Context) (*UserConnection, error) { + ucuo.defaults() + return withHooks(ctx, ucuo.sqlSave, ucuo.mutation, ucuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (ucuo *UserConnectionUpdateOne) SaveX(ctx context.Context) *UserConnection { + node, err := ucuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (ucuo *UserConnectionUpdateOne) Exec(ctx context.Context) error { + _, err := ucuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucuo *UserConnectionUpdateOne) ExecX(ctx context.Context) { + if err := ucuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ucuo *UserConnectionUpdateOne) defaults() { + if _, ok := ucuo.mutation.UpdatedAt(); !ok { + v := userconnection.UpdateDefaultUpdatedAt() + ucuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ucuo *UserConnectionUpdateOne) check() error { + if v, ok := ucuo.mutation.GetType(); ok { + if err := userconnection.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "UserConnection.type": %w`, err)} + } + } + if _, ok := ucuo.mutation.UserID(); ucuo.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "UserConnection.user"`) + } + return nil +} + +func (ucuo *UserConnectionUpdateOne) sqlSave(ctx context.Context) (_node *UserConnection, err error) { + if err := ucuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(userconnection.Table, userconnection.Columns, sqlgraph.NewFieldSpec(userconnection.FieldID, field.TypeString)) + id, ok := ucuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "UserConnection.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := ucuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, userconnection.FieldID) + for _, f := range fields { + if !userconnection.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != userconnection.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := ucuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := ucuo.mutation.UpdatedAt(); ok { + _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ucuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(userconnection.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ucuo.mutation.GetType(); ok { + _spec.SetField(userconnection.FieldType, field.TypeEnum, value) + } + if value, ok := ucuo.mutation.ReferenceID(); ok { + _spec.SetField(userconnection.FieldReferenceID, field.TypeString, value) + } + if value, ok := ucuo.mutation.Permissions(); ok { + _spec.SetField(userconnection.FieldPermissions, field.TypeString, value) + } + if ucuo.mutation.PermissionsCleared() { + _spec.ClearField(userconnection.FieldPermissions, field.TypeString) + } + if value, ok := ucuo.mutation.Metadata(); ok { + _spec.SetField(userconnection.FieldMetadata, field.TypeJSON, value) + } + if ucuo.mutation.MetadataCleared() { + _spec.ClearField(userconnection.FieldMetadata, field.TypeJSON) + } + _node = &UserConnection{config: ucuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, ucuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{userconnection.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + ucuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/usercontent.go b/internal/database/ent/db/usercontent.go new file mode 100644 index 00000000..f9c1b079 --- /dev/null +++ b/internal/database/ent/db/usercontent.go @@ -0,0 +1,206 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserContent is the model entity for the UserContent schema. +type UserContent struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Type holds the value of the "type" field. + Type models.UserContentType `json:"type,omitempty"` + // UserID holds the value of the "user_id" field. + UserID string `json:"user_id,omitempty"` + // ReferenceID holds the value of the "reference_id" field. + ReferenceID string `json:"reference_id,omitempty"` + // Value holds the value of the "value" field. + Value any `json:"value,omitempty"` + // Metadata holds the value of the "metadata" field. + Metadata map[string]interface{} `json:"metadata,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserContentQuery when eager-loading is set. + Edges UserContentEdges `json:"edges"` + selectValues sql.SelectValues +} + +// UserContentEdges holds the relations/edges for other nodes in the graph. +type UserContentEdges struct { + // User holds the value of the user edge. + User *User `json:"user,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserContentEdges) UserOrErr() (*User, error) { + if e.User != nil { + return e.User, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: user.Label} + } + return nil, &NotLoadedError{edge: "user"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*UserContent) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case usercontent.FieldValue, usercontent.FieldMetadata: + values[i] = new([]byte) + case usercontent.FieldCreatedAt, usercontent.FieldUpdatedAt: + values[i] = new(sql.NullInt64) + case usercontent.FieldID, usercontent.FieldType, usercontent.FieldUserID, usercontent.FieldReferenceID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the UserContent fields. +func (uc *UserContent) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case usercontent.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + uc.ID = value.String + } + case usercontent.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + uc.CreatedAt = int(value.Int64) + } + case usercontent.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + uc.UpdatedAt = int(value.Int64) + } + case usercontent.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + uc.Type = models.UserContentType(value.String) + } + case usercontent.FieldUserID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field user_id", values[i]) + } else if value.Valid { + uc.UserID = value.String + } + case usercontent.FieldReferenceID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reference_id", values[i]) + } else if value.Valid { + uc.ReferenceID = value.String + } + case usercontent.FieldValue: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field value", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &uc.Value); err != nil { + return fmt.Errorf("unmarshal field value: %w", err) + } + } + case usercontent.FieldMetadata: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field metadata", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &uc.Metadata); err != nil { + return fmt.Errorf("unmarshal field metadata: %w", err) + } + } + default: + uc.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// GetValue returns the ent.Value that was dynamically selected and assigned to the UserContent. +// This includes values selected through modifiers, order, etc. +func (uc *UserContent) GetValue(name string) (ent.Value, error) { + return uc.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the UserContent entity. +func (uc *UserContent) QueryUser() *UserQuery { + return NewUserContentClient(uc.config).QueryUser(uc) +} + +// Update returns a builder for updating this UserContent. +// Note that you need to call UserContent.Unwrap() before calling this method if this UserContent +// was returned from a transaction, and the transaction was committed or rolled back. +func (uc *UserContent) Update() *UserContentUpdateOne { + return NewUserContentClient(uc.config).UpdateOne(uc) +} + +// Unwrap unwraps the UserContent entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (uc *UserContent) Unwrap() *UserContent { + _tx, ok := uc.config.driver.(*txDriver) + if !ok { + panic("db: UserContent is not a transactional entity") + } + uc.config.driver = _tx.drv + return uc +} + +// String implements the fmt.Stringer. +func (uc *UserContent) String() string { + var builder strings.Builder + builder.WriteString("UserContent(") + builder.WriteString(fmt.Sprintf("id=%v, ", uc.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", uc.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", uc.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("type=") + builder.WriteString(fmt.Sprintf("%v", uc.Type)) + builder.WriteString(", ") + builder.WriteString("user_id=") + builder.WriteString(uc.UserID) + builder.WriteString(", ") + builder.WriteString("reference_id=") + builder.WriteString(uc.ReferenceID) + builder.WriteString(", ") + builder.WriteString("value=") + builder.WriteString(fmt.Sprintf("%v", uc.Value)) + builder.WriteString(", ") + builder.WriteString("metadata=") + builder.WriteString(fmt.Sprintf("%v", uc.Metadata)) + builder.WriteByte(')') + return builder.String() +} + +// UserContents is a parsable slice of UserContent. +type UserContents []*UserContent diff --git a/internal/database/ent/db/usercontent/usercontent.go b/internal/database/ent/db/usercontent/usercontent.go new file mode 100644 index 00000000..c3ea1314 --- /dev/null +++ b/internal/database/ent/db/usercontent/usercontent.go @@ -0,0 +1,133 @@ +// Code generated by ent, DO NOT EDIT. + +package usercontent + +import ( + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/models" +) + +const ( + // Label holds the string label denoting the usercontent type in the database. + Label = "user_content" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldUserID holds the string denoting the user_id field in the database. + FieldUserID = "user_id" + // FieldReferenceID holds the string denoting the reference_id field in the database. + FieldReferenceID = "reference_id" + // FieldValue holds the string denoting the value field in the database. + FieldValue = "value" + // FieldMetadata holds the string denoting the metadata field in the database. + FieldMetadata = "metadata" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // Table holds the table name of the usercontent in the database. + Table = "user_contents" + // UserTable is the table that holds the user relation/edge. + UserTable = "user_contents" + // UserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + UserInverseTable = "users" + // UserColumn is the table column denoting the user relation/edge. + UserColumn = "user_id" +) + +// Columns holds all SQL columns for usercontent fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldType, + FieldUserID, + FieldReferenceID, + FieldValue, + FieldMetadata, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save. +func TypeValidator(_type models.UserContentType) error { + switch _type { + case "clan-background-image", "personal-background-image": + return nil + default: + return fmt.Errorf("usercontent: invalid enum value for type field: %q", _type) + } +} + +// OrderOption defines the ordering options for the UserContent queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByUserID orders the results by the user_id field. +func ByUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserID, opts...).ToFunc() +} + +// ByReferenceID orders the results by the reference_id field. +func ByReferenceID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReferenceID, opts...).ToFunc() +} + +// ByUserField orders the results by user field. +func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...)) + } +} +func newUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) +} diff --git a/internal/database/ent/db/usercontent/where.go b/internal/database/ent/db/usercontent/where.go new file mode 100644 index 00000000..41358635 --- /dev/null +++ b/internal/database/ent/db/usercontent/where.go @@ -0,0 +1,363 @@ +// Code generated by ent, DO NOT EDIT. + +package usercontent + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.UserContent { + return predicate.UserContent(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.UserContent { + return predicate.UserContent(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.UserContent { + return predicate.UserContent(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.UserContent { + return predicate.UserContent(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.UserContent { + return predicate.UserContent(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.UserContent { + return predicate.UserContent(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.UserContent { + return predicate.UserContent(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.UserContent { + return predicate.UserContent(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.UserContent { + return predicate.UserContent(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. +func UserID(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldUserID, v)) +} + +// ReferenceID applies equality check predicate on the "reference_id" field. It's identical to ReferenceIDEQ. +func ReferenceID(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldReferenceID, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.UserContent { + return predicate.UserContent(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.UserContent { + return predicate.UserContent(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.UserContent { + return predicate.UserContent(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.UserContent { + return predicate.UserContent(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.UserContent { + return predicate.UserContent(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v models.UserContentType) predicate.UserContent { + vc := v + return predicate.UserContent(sql.FieldEQ(FieldType, vc)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v models.UserContentType) predicate.UserContent { + vc := v + return predicate.UserContent(sql.FieldNEQ(FieldType, vc)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...models.UserContentType) predicate.UserContent { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.UserContent(sql.FieldIn(FieldType, v...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...models.UserContentType) predicate.UserContent { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.UserContent(sql.FieldNotIn(FieldType, v...)) +} + +// UserIDEQ applies the EQ predicate on the "user_id" field. +func UserIDEQ(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldUserID, v)) +} + +// UserIDNEQ applies the NEQ predicate on the "user_id" field. +func UserIDNEQ(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldNEQ(FieldUserID, v)) +} + +// UserIDIn applies the In predicate on the "user_id" field. +func UserIDIn(vs ...string) predicate.UserContent { + return predicate.UserContent(sql.FieldIn(FieldUserID, vs...)) +} + +// UserIDNotIn applies the NotIn predicate on the "user_id" field. +func UserIDNotIn(vs ...string) predicate.UserContent { + return predicate.UserContent(sql.FieldNotIn(FieldUserID, vs...)) +} + +// UserIDGT applies the GT predicate on the "user_id" field. +func UserIDGT(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldGT(FieldUserID, v)) +} + +// UserIDGTE applies the GTE predicate on the "user_id" field. +func UserIDGTE(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldGTE(FieldUserID, v)) +} + +// UserIDLT applies the LT predicate on the "user_id" field. +func UserIDLT(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldLT(FieldUserID, v)) +} + +// UserIDLTE applies the LTE predicate on the "user_id" field. +func UserIDLTE(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldLTE(FieldUserID, v)) +} + +// UserIDContains applies the Contains predicate on the "user_id" field. +func UserIDContains(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldContains(FieldUserID, v)) +} + +// UserIDHasPrefix applies the HasPrefix predicate on the "user_id" field. +func UserIDHasPrefix(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldHasPrefix(FieldUserID, v)) +} + +// UserIDHasSuffix applies the HasSuffix predicate on the "user_id" field. +func UserIDHasSuffix(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldHasSuffix(FieldUserID, v)) +} + +// UserIDEqualFold applies the EqualFold predicate on the "user_id" field. +func UserIDEqualFold(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldEqualFold(FieldUserID, v)) +} + +// UserIDContainsFold applies the ContainsFold predicate on the "user_id" field. +func UserIDContainsFold(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldContainsFold(FieldUserID, v)) +} + +// ReferenceIDEQ applies the EQ predicate on the "reference_id" field. +func ReferenceIDEQ(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldReferenceID, v)) +} + +// ReferenceIDNEQ applies the NEQ predicate on the "reference_id" field. +func ReferenceIDNEQ(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldNEQ(FieldReferenceID, v)) +} + +// ReferenceIDIn applies the In predicate on the "reference_id" field. +func ReferenceIDIn(vs ...string) predicate.UserContent { + return predicate.UserContent(sql.FieldIn(FieldReferenceID, vs...)) +} + +// ReferenceIDNotIn applies the NotIn predicate on the "reference_id" field. +func ReferenceIDNotIn(vs ...string) predicate.UserContent { + return predicate.UserContent(sql.FieldNotIn(FieldReferenceID, vs...)) +} + +// ReferenceIDGT applies the GT predicate on the "reference_id" field. +func ReferenceIDGT(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldGT(FieldReferenceID, v)) +} + +// ReferenceIDGTE applies the GTE predicate on the "reference_id" field. +func ReferenceIDGTE(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldGTE(FieldReferenceID, v)) +} + +// ReferenceIDLT applies the LT predicate on the "reference_id" field. +func ReferenceIDLT(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldLT(FieldReferenceID, v)) +} + +// ReferenceIDLTE applies the LTE predicate on the "reference_id" field. +func ReferenceIDLTE(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldLTE(FieldReferenceID, v)) +} + +// ReferenceIDContains applies the Contains predicate on the "reference_id" field. +func ReferenceIDContains(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldContains(FieldReferenceID, v)) +} + +// ReferenceIDHasPrefix applies the HasPrefix predicate on the "reference_id" field. +func ReferenceIDHasPrefix(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldHasPrefix(FieldReferenceID, v)) +} + +// ReferenceIDHasSuffix applies the HasSuffix predicate on the "reference_id" field. +func ReferenceIDHasSuffix(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldHasSuffix(FieldReferenceID, v)) +} + +// ReferenceIDEqualFold applies the EqualFold predicate on the "reference_id" field. +func ReferenceIDEqualFold(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldEqualFold(FieldReferenceID, v)) +} + +// ReferenceIDContainsFold applies the ContainsFold predicate on the "reference_id" field. +func ReferenceIDContainsFold(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldContainsFold(FieldReferenceID, v)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.UserContent { + return predicate.UserContent(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). +func HasUserWith(preds ...predicate.User) predicate.UserContent { + return predicate.UserContent(func(s *sql.Selector) { + step := newUserStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.UserContent) predicate.UserContent { + return predicate.UserContent(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.UserContent) predicate.UserContent { + return predicate.UserContent(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.UserContent) predicate.UserContent { + return predicate.UserContent(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/usercontent_create.go b/internal/database/ent/db/usercontent_create.go new file mode 100644 index 00000000..50abe9e4 --- /dev/null +++ b/internal/database/ent/db/usercontent_create.go @@ -0,0 +1,342 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserContentCreate is the builder for creating a UserContent entity. +type UserContentCreate struct { + config + mutation *UserContentMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (ucc *UserContentCreate) SetCreatedAt(i int) *UserContentCreate { + ucc.mutation.SetCreatedAt(i) + return ucc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (ucc *UserContentCreate) SetNillableCreatedAt(i *int) *UserContentCreate { + if i != nil { + ucc.SetCreatedAt(*i) + } + return ucc +} + +// SetUpdatedAt sets the "updated_at" field. +func (ucc *UserContentCreate) SetUpdatedAt(i int) *UserContentCreate { + ucc.mutation.SetUpdatedAt(i) + return ucc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (ucc *UserContentCreate) SetNillableUpdatedAt(i *int) *UserContentCreate { + if i != nil { + ucc.SetUpdatedAt(*i) + } + return ucc +} + +// SetType sets the "type" field. +func (ucc *UserContentCreate) SetType(mct models.UserContentType) *UserContentCreate { + ucc.mutation.SetType(mct) + return ucc +} + +// SetUserID sets the "user_id" field. +func (ucc *UserContentCreate) SetUserID(s string) *UserContentCreate { + ucc.mutation.SetUserID(s) + return ucc +} + +// SetReferenceID sets the "reference_id" field. +func (ucc *UserContentCreate) SetReferenceID(s string) *UserContentCreate { + ucc.mutation.SetReferenceID(s) + return ucc +} + +// SetValue sets the "value" field. +func (ucc *UserContentCreate) SetValue(a any) *UserContentCreate { + ucc.mutation.SetValue(a) + return ucc +} + +// SetMetadata sets the "metadata" field. +func (ucc *UserContentCreate) SetMetadata(m map[string]interface{}) *UserContentCreate { + ucc.mutation.SetMetadata(m) + return ucc +} + +// SetID sets the "id" field. +func (ucc *UserContentCreate) SetID(s string) *UserContentCreate { + ucc.mutation.SetID(s) + return ucc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (ucc *UserContentCreate) SetNillableID(s *string) *UserContentCreate { + if s != nil { + ucc.SetID(*s) + } + return ucc +} + +// SetUser sets the "user" edge to the User entity. +func (ucc *UserContentCreate) SetUser(u *User) *UserContentCreate { + return ucc.SetUserID(u.ID) +} + +// Mutation returns the UserContentMutation object of the builder. +func (ucc *UserContentCreate) Mutation() *UserContentMutation { + return ucc.mutation +} + +// Save creates the UserContent in the database. +func (ucc *UserContentCreate) Save(ctx context.Context) (*UserContent, error) { + ucc.defaults() + return withHooks(ctx, ucc.sqlSave, ucc.mutation, ucc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (ucc *UserContentCreate) SaveX(ctx context.Context) *UserContent { + v, err := ucc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ucc *UserContentCreate) Exec(ctx context.Context) error { + _, err := ucc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucc *UserContentCreate) ExecX(ctx context.Context) { + if err := ucc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ucc *UserContentCreate) defaults() { + if _, ok := ucc.mutation.CreatedAt(); !ok { + v := usercontent.DefaultCreatedAt() + ucc.mutation.SetCreatedAt(v) + } + if _, ok := ucc.mutation.UpdatedAt(); !ok { + v := usercontent.DefaultUpdatedAt() + ucc.mutation.SetUpdatedAt(v) + } + if _, ok := ucc.mutation.ID(); !ok { + v := usercontent.DefaultID() + ucc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ucc *UserContentCreate) check() error { + if _, ok := ucc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "UserContent.created_at"`)} + } + if _, ok := ucc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "UserContent.updated_at"`)} + } + if _, ok := ucc.mutation.GetType(); !ok { + return &ValidationError{Name: "type", err: errors.New(`db: missing required field "UserContent.type"`)} + } + if v, ok := ucc.mutation.GetType(); ok { + if err := usercontent.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "UserContent.type": %w`, err)} + } + } + if _, ok := ucc.mutation.UserID(); !ok { + return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "UserContent.user_id"`)} + } + if _, ok := ucc.mutation.ReferenceID(); !ok { + return &ValidationError{Name: "reference_id", err: errors.New(`db: missing required field "UserContent.reference_id"`)} + } + if _, ok := ucc.mutation.Value(); !ok { + return &ValidationError{Name: "value", err: errors.New(`db: missing required field "UserContent.value"`)} + } + if _, ok := ucc.mutation.Metadata(); !ok { + return &ValidationError{Name: "metadata", err: errors.New(`db: missing required field "UserContent.metadata"`)} + } + if _, ok := ucc.mutation.UserID(); !ok { + return &ValidationError{Name: "user", err: errors.New(`db: missing required edge "UserContent.user"`)} + } + return nil +} + +func (ucc *UserContentCreate) sqlSave(ctx context.Context) (*UserContent, error) { + if err := ucc.check(); err != nil { + return nil, err + } + _node, _spec := ucc.createSpec() + if err := sqlgraph.CreateNode(ctx, ucc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected UserContent.ID type: %T", _spec.ID.Value) + } + } + ucc.mutation.id = &_node.ID + ucc.mutation.done = true + return _node, nil +} + +func (ucc *UserContentCreate) createSpec() (*UserContent, *sqlgraph.CreateSpec) { + var ( + _node = &UserContent{config: ucc.config} + _spec = sqlgraph.NewCreateSpec(usercontent.Table, sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString)) + ) + if id, ok := ucc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := ucc.mutation.CreatedAt(); ok { + _spec.SetField(usercontent.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := ucc.mutation.UpdatedAt(); ok { + _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := ucc.mutation.GetType(); ok { + _spec.SetField(usercontent.FieldType, field.TypeEnum, value) + _node.Type = value + } + if value, ok := ucc.mutation.ReferenceID(); ok { + _spec.SetField(usercontent.FieldReferenceID, field.TypeString, value) + _node.ReferenceID = value + } + if value, ok := ucc.mutation.Value(); ok { + _spec.SetField(usercontent.FieldValue, field.TypeJSON, value) + _node.Value = value + } + if value, ok := ucc.mutation.Metadata(); ok { + _spec.SetField(usercontent.FieldMetadata, field.TypeJSON, value) + _node.Metadata = value + } + if nodes := ucc.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: usercontent.UserTable, + Columns: []string{usercontent.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.UserID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// UserContentCreateBulk is the builder for creating many UserContent entities in bulk. +type UserContentCreateBulk struct { + config + err error + builders []*UserContentCreate +} + +// Save creates the UserContent entities in the database. +func (uccb *UserContentCreateBulk) Save(ctx context.Context) ([]*UserContent, error) { + if uccb.err != nil { + return nil, uccb.err + } + specs := make([]*sqlgraph.CreateSpec, len(uccb.builders)) + nodes := make([]*UserContent, len(uccb.builders)) + mutators := make([]Mutator, len(uccb.builders)) + for i := range uccb.builders { + func(i int, root context.Context) { + builder := uccb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserContentMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, uccb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, uccb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, uccb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (uccb *UserContentCreateBulk) SaveX(ctx context.Context) []*UserContent { + v, err := uccb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (uccb *UserContentCreateBulk) Exec(ctx context.Context) error { + _, err := uccb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (uccb *UserContentCreateBulk) ExecX(ctx context.Context) { + if err := uccb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/usercontent_delete.go b/internal/database/ent/db/usercontent_delete.go new file mode 100644 index 00000000..a2674f8a --- /dev/null +++ b/internal/database/ent/db/usercontent_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" +) + +// UserContentDelete is the builder for deleting a UserContent entity. +type UserContentDelete struct { + config + hooks []Hook + mutation *UserContentMutation +} + +// Where appends a list predicates to the UserContentDelete builder. +func (ucd *UserContentDelete) Where(ps ...predicate.UserContent) *UserContentDelete { + ucd.mutation.Where(ps...) + return ucd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (ucd *UserContentDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, ucd.sqlExec, ucd.mutation, ucd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucd *UserContentDelete) ExecX(ctx context.Context) int { + n, err := ucd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (ucd *UserContentDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(usercontent.Table, sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString)) + if ps := ucd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, ucd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + ucd.mutation.done = true + return affected, err +} + +// UserContentDeleteOne is the builder for deleting a single UserContent entity. +type UserContentDeleteOne struct { + ucd *UserContentDelete +} + +// Where appends a list predicates to the UserContentDelete builder. +func (ucdo *UserContentDeleteOne) Where(ps ...predicate.UserContent) *UserContentDeleteOne { + ucdo.ucd.mutation.Where(ps...) + return ucdo +} + +// Exec executes the deletion query. +func (ucdo *UserContentDeleteOne) Exec(ctx context.Context) error { + n, err := ucdo.ucd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{usercontent.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucdo *UserContentDeleteOne) ExecX(ctx context.Context) { + if err := ucdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/usercontent_query.go b/internal/database/ent/db/usercontent_query.go new file mode 100644 index 00000000..21683730 --- /dev/null +++ b/internal/database/ent/db/usercontent_query.go @@ -0,0 +1,605 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" +) + +// UserContentQuery is the builder for querying UserContent entities. +type UserContentQuery struct { + config + ctx *QueryContext + order []usercontent.OrderOption + inters []Interceptor + predicates []predicate.UserContent + withUser *UserQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserContentQuery builder. +func (ucq *UserContentQuery) Where(ps ...predicate.UserContent) *UserContentQuery { + ucq.predicates = append(ucq.predicates, ps...) + return ucq +} + +// Limit the number of records to be returned by this query. +func (ucq *UserContentQuery) Limit(limit int) *UserContentQuery { + ucq.ctx.Limit = &limit + return ucq +} + +// Offset to start from. +func (ucq *UserContentQuery) Offset(offset int) *UserContentQuery { + ucq.ctx.Offset = &offset + return ucq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (ucq *UserContentQuery) Unique(unique bool) *UserContentQuery { + ucq.ctx.Unique = &unique + return ucq +} + +// Order specifies how the records should be ordered. +func (ucq *UserContentQuery) Order(o ...usercontent.OrderOption) *UserContentQuery { + ucq.order = append(ucq.order, o...) + return ucq +} + +// QueryUser chains the current query on the "user" edge. +func (ucq *UserContentQuery) QueryUser() *UserQuery { + query := (&UserClient{config: ucq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := ucq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := ucq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(usercontent.Table, usercontent.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, usercontent.UserTable, usercontent.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(ucq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first UserContent entity from the query. +// Returns a *NotFoundError when no UserContent was found. +func (ucq *UserContentQuery) First(ctx context.Context) (*UserContent, error) { + nodes, err := ucq.Limit(1).All(setContextOp(ctx, ucq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{usercontent.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (ucq *UserContentQuery) FirstX(ctx context.Context) *UserContent { + node, err := ucq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first UserContent ID from the query. +// Returns a *NotFoundError when no UserContent ID was found. +func (ucq *UserContentQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = ucq.Limit(1).IDs(setContextOp(ctx, ucq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{usercontent.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (ucq *UserContentQuery) FirstIDX(ctx context.Context) string { + id, err := ucq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single UserContent entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one UserContent entity is found. +// Returns a *NotFoundError when no UserContent entities are found. +func (ucq *UserContentQuery) Only(ctx context.Context) (*UserContent, error) { + nodes, err := ucq.Limit(2).All(setContextOp(ctx, ucq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{usercontent.Label} + default: + return nil, &NotSingularError{usercontent.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (ucq *UserContentQuery) OnlyX(ctx context.Context) *UserContent { + node, err := ucq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only UserContent ID in the query. +// Returns a *NotSingularError when more than one UserContent ID is found. +// Returns a *NotFoundError when no entities are found. +func (ucq *UserContentQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = ucq.Limit(2).IDs(setContextOp(ctx, ucq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{usercontent.Label} + default: + err = &NotSingularError{usercontent.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (ucq *UserContentQuery) OnlyIDX(ctx context.Context) string { + id, err := ucq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of UserContents. +func (ucq *UserContentQuery) All(ctx context.Context) ([]*UserContent, error) { + ctx = setContextOp(ctx, ucq.ctx, "All") + if err := ucq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*UserContent, *UserContentQuery]() + return withInterceptors[[]*UserContent](ctx, ucq, qr, ucq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (ucq *UserContentQuery) AllX(ctx context.Context) []*UserContent { + nodes, err := ucq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of UserContent IDs. +func (ucq *UserContentQuery) IDs(ctx context.Context) (ids []string, err error) { + if ucq.ctx.Unique == nil && ucq.path != nil { + ucq.Unique(true) + } + ctx = setContextOp(ctx, ucq.ctx, "IDs") + if err = ucq.Select(usercontent.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (ucq *UserContentQuery) IDsX(ctx context.Context) []string { + ids, err := ucq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (ucq *UserContentQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, ucq.ctx, "Count") + if err := ucq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, ucq, querierCount[*UserContentQuery](), ucq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (ucq *UserContentQuery) CountX(ctx context.Context) int { + count, err := ucq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (ucq *UserContentQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, ucq.ctx, "Exist") + switch _, err := ucq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (ucq *UserContentQuery) ExistX(ctx context.Context) bool { + exist, err := ucq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserContentQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (ucq *UserContentQuery) Clone() *UserContentQuery { + if ucq == nil { + return nil + } + return &UserContentQuery{ + config: ucq.config, + ctx: ucq.ctx.Clone(), + order: append([]usercontent.OrderOption{}, ucq.order...), + inters: append([]Interceptor{}, ucq.inters...), + predicates: append([]predicate.UserContent{}, ucq.predicates...), + withUser: ucq.withUser.Clone(), + // clone intermediate query. + sql: ucq.sql.Clone(), + path: ucq.path, + } +} + +// WithUser tells the query-builder to eager-load the nodes that are connected to +// the "user" edge. The optional arguments are used to configure the query builder of the edge. +func (ucq *UserContentQuery) WithUser(opts ...func(*UserQuery)) *UserContentQuery { + query := (&UserClient{config: ucq.config}).Query() + for _, opt := range opts { + opt(query) + } + ucq.withUser = query + return ucq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.UserContent.Query(). +// GroupBy(usercontent.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (ucq *UserContentQuery) GroupBy(field string, fields ...string) *UserContentGroupBy { + ucq.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserContentGroupBy{build: ucq} + grbuild.flds = &ucq.ctx.Fields + grbuild.label = usercontent.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.UserContent.Query(). +// Select(usercontent.FieldCreatedAt). +// Scan(ctx, &v) +func (ucq *UserContentQuery) Select(fields ...string) *UserContentSelect { + ucq.ctx.Fields = append(ucq.ctx.Fields, fields...) + sbuild := &UserContentSelect{UserContentQuery: ucq} + sbuild.label = usercontent.Label + sbuild.flds, sbuild.scan = &ucq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserContentSelect configured with the given aggregations. +func (ucq *UserContentQuery) Aggregate(fns ...AggregateFunc) *UserContentSelect { + return ucq.Select().Aggregate(fns...) +} + +func (ucq *UserContentQuery) prepareQuery(ctx context.Context) error { + for _, inter := range ucq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, ucq); err != nil { + return err + } + } + } + for _, f := range ucq.ctx.Fields { + if !usercontent.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if ucq.path != nil { + prev, err := ucq.path(ctx) + if err != nil { + return err + } + ucq.sql = prev + } + return nil +} + +func (ucq *UserContentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserContent, error) { + var ( + nodes = []*UserContent{} + _spec = ucq.querySpec() + loadedTypes = [1]bool{ + ucq.withUser != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*UserContent).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &UserContent{config: ucq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, ucq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := ucq.withUser; query != nil { + if err := ucq.loadUser(ctx, query, nodes, nil, + func(n *UserContent, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (ucq *UserContentQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*UserContent, init func(*UserContent), assign func(*UserContent, *User)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*UserContent) + for i := range nodes { + fk := nodes[i].UserID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (ucq *UserContentQuery) sqlCount(ctx context.Context) (int, error) { + _spec := ucq.querySpec() + _spec.Node.Columns = ucq.ctx.Fields + if len(ucq.ctx.Fields) > 0 { + _spec.Unique = ucq.ctx.Unique != nil && *ucq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, ucq.driver, _spec) +} + +func (ucq *UserContentQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(usercontent.Table, usercontent.Columns, sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString)) + _spec.From = ucq.sql + if unique := ucq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if ucq.path != nil { + _spec.Unique = true + } + if fields := ucq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usercontent.FieldID) + for i := range fields { + if fields[i] != usercontent.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if ucq.withUser != nil { + _spec.Node.AddColumnOnce(usercontent.FieldUserID) + } + } + if ps := ucq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := ucq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := ucq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := ucq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (ucq *UserContentQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(ucq.driver.Dialect()) + t1 := builder.Table(usercontent.Table) + columns := ucq.ctx.Fields + if len(columns) == 0 { + columns = usercontent.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if ucq.sql != nil { + selector = ucq.sql + selector.Select(selector.Columns(columns...)...) + } + if ucq.ctx.Unique != nil && *ucq.ctx.Unique { + selector.Distinct() + } + for _, p := range ucq.predicates { + p(selector) + } + for _, p := range ucq.order { + p(selector) + } + if offset := ucq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := ucq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// UserContentGroupBy is the group-by builder for UserContent entities. +type UserContentGroupBy struct { + selector + build *UserContentQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (ucgb *UserContentGroupBy) Aggregate(fns ...AggregateFunc) *UserContentGroupBy { + ucgb.fns = append(ucgb.fns, fns...) + return ucgb +} + +// Scan applies the selector query and scans the result into the given value. +func (ucgb *UserContentGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ucgb.build.ctx, "GroupBy") + if err := ucgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserContentQuery, *UserContentGroupBy](ctx, ucgb.build, ucgb, ucgb.build.inters, v) +} + +func (ucgb *UserContentGroupBy) sqlScan(ctx context.Context, root *UserContentQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(ucgb.fns)) + for _, fn := range ucgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*ucgb.flds)+len(ucgb.fns)) + for _, f := range *ucgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*ucgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ucgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserContentSelect is the builder for selecting fields of UserContent entities. +type UserContentSelect struct { + *UserContentQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ucs *UserContentSelect) Aggregate(fns ...AggregateFunc) *UserContentSelect { + ucs.fns = append(ucs.fns, fns...) + return ucs +} + +// Scan applies the selector query and scans the result into the given value. +func (ucs *UserContentSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ucs.ctx, "Select") + if err := ucs.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserContentQuery, *UserContentSelect](ctx, ucs.UserContentQuery, ucs, ucs.inters, v) +} + +func (ucs *UserContentSelect) sqlScan(ctx context.Context, root *UserContentQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(ucs.fns)) + for _, fn := range ucs.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*ucs.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ucs.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/usercontent_update.go b/internal/database/ent/db/usercontent_update.go new file mode 100644 index 00000000..a6d031e0 --- /dev/null +++ b/internal/database/ent/db/usercontent_update.go @@ -0,0 +1,368 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/usercontent" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserContentUpdate is the builder for updating UserContent entities. +type UserContentUpdate struct { + config + hooks []Hook + mutation *UserContentMutation +} + +// Where appends a list predicates to the UserContentUpdate builder. +func (ucu *UserContentUpdate) Where(ps ...predicate.UserContent) *UserContentUpdate { + ucu.mutation.Where(ps...) + return ucu +} + +// SetUpdatedAt sets the "updated_at" field. +func (ucu *UserContentUpdate) SetUpdatedAt(i int) *UserContentUpdate { + ucu.mutation.ResetUpdatedAt() + ucu.mutation.SetUpdatedAt(i) + return ucu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (ucu *UserContentUpdate) AddUpdatedAt(i int) *UserContentUpdate { + ucu.mutation.AddUpdatedAt(i) + return ucu +} + +// SetType sets the "type" field. +func (ucu *UserContentUpdate) SetType(mct models.UserContentType) *UserContentUpdate { + ucu.mutation.SetType(mct) + return ucu +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (ucu *UserContentUpdate) SetNillableType(mct *models.UserContentType) *UserContentUpdate { + if mct != nil { + ucu.SetType(*mct) + } + return ucu +} + +// SetReferenceID sets the "reference_id" field. +func (ucu *UserContentUpdate) SetReferenceID(s string) *UserContentUpdate { + ucu.mutation.SetReferenceID(s) + return ucu +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (ucu *UserContentUpdate) SetNillableReferenceID(s *string) *UserContentUpdate { + if s != nil { + ucu.SetReferenceID(*s) + } + return ucu +} + +// SetValue sets the "value" field. +func (ucu *UserContentUpdate) SetValue(a any) *UserContentUpdate { + ucu.mutation.SetValue(a) + return ucu +} + +// SetMetadata sets the "metadata" field. +func (ucu *UserContentUpdate) SetMetadata(m map[string]interface{}) *UserContentUpdate { + ucu.mutation.SetMetadata(m) + return ucu +} + +// Mutation returns the UserContentMutation object of the builder. +func (ucu *UserContentUpdate) Mutation() *UserContentMutation { + return ucu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (ucu *UserContentUpdate) Save(ctx context.Context) (int, error) { + ucu.defaults() + return withHooks(ctx, ucu.sqlSave, ucu.mutation, ucu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (ucu *UserContentUpdate) SaveX(ctx context.Context) int { + affected, err := ucu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (ucu *UserContentUpdate) Exec(ctx context.Context) error { + _, err := ucu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucu *UserContentUpdate) ExecX(ctx context.Context) { + if err := ucu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ucu *UserContentUpdate) defaults() { + if _, ok := ucu.mutation.UpdatedAt(); !ok { + v := usercontent.UpdateDefaultUpdatedAt() + ucu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ucu *UserContentUpdate) check() error { + if v, ok := ucu.mutation.GetType(); ok { + if err := usercontent.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "UserContent.type": %w`, err)} + } + } + if _, ok := ucu.mutation.UserID(); ucu.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "UserContent.user"`) + } + return nil +} + +func (ucu *UserContentUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := ucu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(usercontent.Table, usercontent.Columns, sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString)) + if ps := ucu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := ucu.mutation.UpdatedAt(); ok { + _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ucu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(usercontent.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ucu.mutation.GetType(); ok { + _spec.SetField(usercontent.FieldType, field.TypeEnum, value) + } + if value, ok := ucu.mutation.ReferenceID(); ok { + _spec.SetField(usercontent.FieldReferenceID, field.TypeString, value) + } + if value, ok := ucu.mutation.Value(); ok { + _spec.SetField(usercontent.FieldValue, field.TypeJSON, value) + } + if value, ok := ucu.mutation.Metadata(); ok { + _spec.SetField(usercontent.FieldMetadata, field.TypeJSON, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, ucu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usercontent.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + ucu.mutation.done = true + return n, nil +} + +// UserContentUpdateOne is the builder for updating a single UserContent entity. +type UserContentUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserContentMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (ucuo *UserContentUpdateOne) SetUpdatedAt(i int) *UserContentUpdateOne { + ucuo.mutation.ResetUpdatedAt() + ucuo.mutation.SetUpdatedAt(i) + return ucuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (ucuo *UserContentUpdateOne) AddUpdatedAt(i int) *UserContentUpdateOne { + ucuo.mutation.AddUpdatedAt(i) + return ucuo +} + +// SetType sets the "type" field. +func (ucuo *UserContentUpdateOne) SetType(mct models.UserContentType) *UserContentUpdateOne { + ucuo.mutation.SetType(mct) + return ucuo +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (ucuo *UserContentUpdateOne) SetNillableType(mct *models.UserContentType) *UserContentUpdateOne { + if mct != nil { + ucuo.SetType(*mct) + } + return ucuo +} + +// SetReferenceID sets the "reference_id" field. +func (ucuo *UserContentUpdateOne) SetReferenceID(s string) *UserContentUpdateOne { + ucuo.mutation.SetReferenceID(s) + return ucuo +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (ucuo *UserContentUpdateOne) SetNillableReferenceID(s *string) *UserContentUpdateOne { + if s != nil { + ucuo.SetReferenceID(*s) + } + return ucuo +} + +// SetValue sets the "value" field. +func (ucuo *UserContentUpdateOne) SetValue(a any) *UserContentUpdateOne { + ucuo.mutation.SetValue(a) + return ucuo +} + +// SetMetadata sets the "metadata" field. +func (ucuo *UserContentUpdateOne) SetMetadata(m map[string]interface{}) *UserContentUpdateOne { + ucuo.mutation.SetMetadata(m) + return ucuo +} + +// Mutation returns the UserContentMutation object of the builder. +func (ucuo *UserContentUpdateOne) Mutation() *UserContentMutation { + return ucuo.mutation +} + +// Where appends a list predicates to the UserContentUpdate builder. +func (ucuo *UserContentUpdateOne) Where(ps ...predicate.UserContent) *UserContentUpdateOne { + ucuo.mutation.Where(ps...) + return ucuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (ucuo *UserContentUpdateOne) Select(field string, fields ...string) *UserContentUpdateOne { + ucuo.fields = append([]string{field}, fields...) + return ucuo +} + +// Save executes the query and returns the updated UserContent entity. +func (ucuo *UserContentUpdateOne) Save(ctx context.Context) (*UserContent, error) { + ucuo.defaults() + return withHooks(ctx, ucuo.sqlSave, ucuo.mutation, ucuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (ucuo *UserContentUpdateOne) SaveX(ctx context.Context) *UserContent { + node, err := ucuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (ucuo *UserContentUpdateOne) Exec(ctx context.Context) error { + _, err := ucuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucuo *UserContentUpdateOne) ExecX(ctx context.Context) { + if err := ucuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ucuo *UserContentUpdateOne) defaults() { + if _, ok := ucuo.mutation.UpdatedAt(); !ok { + v := usercontent.UpdateDefaultUpdatedAt() + ucuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ucuo *UserContentUpdateOne) check() error { + if v, ok := ucuo.mutation.GetType(); ok { + if err := usercontent.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "UserContent.type": %w`, err)} + } + } + if _, ok := ucuo.mutation.UserID(); ucuo.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "UserContent.user"`) + } + return nil +} + +func (ucuo *UserContentUpdateOne) sqlSave(ctx context.Context) (_node *UserContent, err error) { + if err := ucuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(usercontent.Table, usercontent.Columns, sqlgraph.NewFieldSpec(usercontent.FieldID, field.TypeString)) + id, ok := ucuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "UserContent.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := ucuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usercontent.FieldID) + for _, f := range fields { + if !usercontent.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != usercontent.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := ucuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := ucuo.mutation.UpdatedAt(); ok { + _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ucuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(usercontent.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := ucuo.mutation.GetType(); ok { + _spec.SetField(usercontent.FieldType, field.TypeEnum, value) + } + if value, ok := ucuo.mutation.ReferenceID(); ok { + _spec.SetField(usercontent.FieldReferenceID, field.TypeString, value) + } + if value, ok := ucuo.mutation.Value(); ok { + _spec.SetField(usercontent.FieldValue, field.TypeJSON, value) + } + if value, ok := ucuo.mutation.Metadata(); ok { + _spec.SetField(usercontent.FieldMetadata, field.TypeJSON, value) + } + _node = &UserContent{config: ucuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, ucuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usercontent.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + ucuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/usersubscription.go b/internal/database/ent/db/usersubscription.go new file mode 100644 index 00000000..573a557e --- /dev/null +++ b/internal/database/ent/db/usersubscription.go @@ -0,0 +1,199 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserSubscription is the model entity for the UserSubscription schema. +type UserSubscription struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Type holds the value of the "type" field. + Type models.SubscriptionType `json:"type,omitempty"` + // ExpiresAt holds the value of the "expires_at" field. + ExpiresAt int `json:"expires_at,omitempty"` + // UserID holds the value of the "user_id" field. + UserID string `json:"user_id,omitempty"` + // Permissions holds the value of the "permissions" field. + Permissions string `json:"permissions,omitempty"` + // ReferenceID holds the value of the "reference_id" field. + ReferenceID string `json:"reference_id,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserSubscriptionQuery when eager-loading is set. + Edges UserSubscriptionEdges `json:"edges"` + selectValues sql.SelectValues +} + +// UserSubscriptionEdges holds the relations/edges for other nodes in the graph. +type UserSubscriptionEdges struct { + // User holds the value of the user edge. + User *User `json:"user,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserSubscriptionEdges) UserOrErr() (*User, error) { + if e.User != nil { + return e.User, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: user.Label} + } + return nil, &NotLoadedError{edge: "user"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*UserSubscription) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case usersubscription.FieldCreatedAt, usersubscription.FieldUpdatedAt, usersubscription.FieldExpiresAt: + values[i] = new(sql.NullInt64) + case usersubscription.FieldID, usersubscription.FieldType, usersubscription.FieldUserID, usersubscription.FieldPermissions, usersubscription.FieldReferenceID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the UserSubscription fields. +func (us *UserSubscription) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case usersubscription.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + us.ID = value.String + } + case usersubscription.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + us.CreatedAt = int(value.Int64) + } + case usersubscription.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + us.UpdatedAt = int(value.Int64) + } + case usersubscription.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + us.Type = models.SubscriptionType(value.String) + } + case usersubscription.FieldExpiresAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field expires_at", values[i]) + } else if value.Valid { + us.ExpiresAt = int(value.Int64) + } + case usersubscription.FieldUserID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field user_id", values[i]) + } else if value.Valid { + us.UserID = value.String + } + case usersubscription.FieldPermissions: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field permissions", values[i]) + } else if value.Valid { + us.Permissions = value.String + } + case usersubscription.FieldReferenceID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reference_id", values[i]) + } else if value.Valid { + us.ReferenceID = value.String + } + default: + us.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the UserSubscription. +// This includes values selected through modifiers, order, etc. +func (us *UserSubscription) Value(name string) (ent.Value, error) { + return us.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the UserSubscription entity. +func (us *UserSubscription) QueryUser() *UserQuery { + return NewUserSubscriptionClient(us.config).QueryUser(us) +} + +// Update returns a builder for updating this UserSubscription. +// Note that you need to call UserSubscription.Unwrap() before calling this method if this UserSubscription +// was returned from a transaction, and the transaction was committed or rolled back. +func (us *UserSubscription) Update() *UserSubscriptionUpdateOne { + return NewUserSubscriptionClient(us.config).UpdateOne(us) +} + +// Unwrap unwraps the UserSubscription entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (us *UserSubscription) Unwrap() *UserSubscription { + _tx, ok := us.config.driver.(*txDriver) + if !ok { + panic("db: UserSubscription is not a transactional entity") + } + us.config.driver = _tx.drv + return us +} + +// String implements the fmt.Stringer. +func (us *UserSubscription) String() string { + var builder strings.Builder + builder.WriteString("UserSubscription(") + builder.WriteString(fmt.Sprintf("id=%v, ", us.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", us.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", us.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("type=") + builder.WriteString(fmt.Sprintf("%v", us.Type)) + builder.WriteString(", ") + builder.WriteString("expires_at=") + builder.WriteString(fmt.Sprintf("%v", us.ExpiresAt)) + builder.WriteString(", ") + builder.WriteString("user_id=") + builder.WriteString(us.UserID) + builder.WriteString(", ") + builder.WriteString("permissions=") + builder.WriteString(us.Permissions) + builder.WriteString(", ") + builder.WriteString("reference_id=") + builder.WriteString(us.ReferenceID) + builder.WriteByte(')') + return builder.String() +} + +// UserSubscriptions is a parsable slice of UserSubscription. +type UserSubscriptions []*UserSubscription diff --git a/internal/database/ent/db/usersubscription/usersubscription.go b/internal/database/ent/db/usersubscription/usersubscription.go new file mode 100644 index 00000000..e8a6c86f --- /dev/null +++ b/internal/database/ent/db/usersubscription/usersubscription.go @@ -0,0 +1,149 @@ +// Code generated by ent, DO NOT EDIT. + +package usersubscription + +import ( + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/models" +) + +const ( + // Label holds the string label denoting the usersubscription type in the database. + Label = "user_subscription" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldExpiresAt holds the string denoting the expires_at field in the database. + FieldExpiresAt = "expires_at" + // FieldUserID holds the string denoting the user_id field in the database. + FieldUserID = "user_id" + // FieldPermissions holds the string denoting the permissions field in the database. + FieldPermissions = "permissions" + // FieldReferenceID holds the string denoting the reference_id field in the database. + FieldReferenceID = "reference_id" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // Table holds the table name of the usersubscription in the database. + Table = "user_subscriptions" + // UserTable is the table that holds the user relation/edge. + UserTable = "user_subscriptions" + // UserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + UserInverseTable = "users" + // UserColumn is the table column denoting the user relation/edge. + UserColumn = "user_id" +) + +// Columns holds all SQL columns for usersubscription fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldType, + FieldExpiresAt, + FieldUserID, + FieldPermissions, + FieldReferenceID, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. + UserIDValidator func(string) error + // PermissionsValidator is a validator for the "permissions" field. It is called by the builders before save. + PermissionsValidator func(string) error + // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + ReferenceIDValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save. +func TypeValidator(_type models.SubscriptionType) error { + switch _type { + case "aftermath-pro", "aftermath-pro-clan", "aftermath-plus", "supporter", "verified-clan", "server-moderator", "content-moderator", "developer", "server-booster", "content-translator": + return nil + default: + return fmt.Errorf("usersubscription: invalid enum value for type field: %q", _type) + } +} + +// OrderOption defines the ordering options for the UserSubscription queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByExpiresAt orders the results by the expires_at field. +func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldExpiresAt, opts...).ToFunc() +} + +// ByUserID orders the results by the user_id field. +func ByUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserID, opts...).ToFunc() +} + +// ByPermissions orders the results by the permissions field. +func ByPermissions(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPermissions, opts...).ToFunc() +} + +// ByReferenceID orders the results by the reference_id field. +func ByReferenceID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReferenceID, opts...).ToFunc() +} + +// ByUserField orders the results by user field. +func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...)) + } +} +func newUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) +} diff --git a/internal/database/ent/db/usersubscription/where.go b/internal/database/ent/db/usersubscription/where.go new file mode 100644 index 00000000..71f3025c --- /dev/null +++ b/internal/database/ent/db/usersubscription/where.go @@ -0,0 +1,478 @@ +// Code generated by ent, DO NOT EDIT. + +package usersubscription + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ. +func ExpiresAt(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldExpiresAt, v)) +} + +// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. +func UserID(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldUserID, v)) +} + +// Permissions applies equality check predicate on the "permissions" field. It's identical to PermissionsEQ. +func Permissions(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldPermissions, v)) +} + +// ReferenceID applies equality check predicate on the "reference_id" field. It's identical to ReferenceIDEQ. +func ReferenceID(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldReferenceID, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v models.SubscriptionType) predicate.UserSubscription { + vc := v + return predicate.UserSubscription(sql.FieldEQ(FieldType, vc)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v models.SubscriptionType) predicate.UserSubscription { + vc := v + return predicate.UserSubscription(sql.FieldNEQ(FieldType, vc)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...models.SubscriptionType) predicate.UserSubscription { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.UserSubscription(sql.FieldIn(FieldType, v...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...models.SubscriptionType) predicate.UserSubscription { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.UserSubscription(sql.FieldNotIn(FieldType, v...)) +} + +// ExpiresAtEQ applies the EQ predicate on the "expires_at" field. +func ExpiresAtEQ(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldExpiresAt, v)) +} + +// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field. +func ExpiresAtNEQ(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNEQ(FieldExpiresAt, v)) +} + +// ExpiresAtIn applies the In predicate on the "expires_at" field. +func ExpiresAtIn(vs ...int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field. +func ExpiresAtNotIn(vs ...int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNotIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtGT applies the GT predicate on the "expires_at" field. +func ExpiresAtGT(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGT(FieldExpiresAt, v)) +} + +// ExpiresAtGTE applies the GTE predicate on the "expires_at" field. +func ExpiresAtGTE(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGTE(FieldExpiresAt, v)) +} + +// ExpiresAtLT applies the LT predicate on the "expires_at" field. +func ExpiresAtLT(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLT(FieldExpiresAt, v)) +} + +// ExpiresAtLTE applies the LTE predicate on the "expires_at" field. +func ExpiresAtLTE(v int) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLTE(FieldExpiresAt, v)) +} + +// UserIDEQ applies the EQ predicate on the "user_id" field. +func UserIDEQ(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldUserID, v)) +} + +// UserIDNEQ applies the NEQ predicate on the "user_id" field. +func UserIDNEQ(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNEQ(FieldUserID, v)) +} + +// UserIDIn applies the In predicate on the "user_id" field. +func UserIDIn(vs ...string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldIn(FieldUserID, vs...)) +} + +// UserIDNotIn applies the NotIn predicate on the "user_id" field. +func UserIDNotIn(vs ...string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNotIn(FieldUserID, vs...)) +} + +// UserIDGT applies the GT predicate on the "user_id" field. +func UserIDGT(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGT(FieldUserID, v)) +} + +// UserIDGTE applies the GTE predicate on the "user_id" field. +func UserIDGTE(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGTE(FieldUserID, v)) +} + +// UserIDLT applies the LT predicate on the "user_id" field. +func UserIDLT(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLT(FieldUserID, v)) +} + +// UserIDLTE applies the LTE predicate on the "user_id" field. +func UserIDLTE(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLTE(FieldUserID, v)) +} + +// UserIDContains applies the Contains predicate on the "user_id" field. +func UserIDContains(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldContains(FieldUserID, v)) +} + +// UserIDHasPrefix applies the HasPrefix predicate on the "user_id" field. +func UserIDHasPrefix(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldHasPrefix(FieldUserID, v)) +} + +// UserIDHasSuffix applies the HasSuffix predicate on the "user_id" field. +func UserIDHasSuffix(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldHasSuffix(FieldUserID, v)) +} + +// UserIDEqualFold applies the EqualFold predicate on the "user_id" field. +func UserIDEqualFold(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEqualFold(FieldUserID, v)) +} + +// UserIDContainsFold applies the ContainsFold predicate on the "user_id" field. +func UserIDContainsFold(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldContainsFold(FieldUserID, v)) +} + +// PermissionsEQ applies the EQ predicate on the "permissions" field. +func PermissionsEQ(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldPermissions, v)) +} + +// PermissionsNEQ applies the NEQ predicate on the "permissions" field. +func PermissionsNEQ(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNEQ(FieldPermissions, v)) +} + +// PermissionsIn applies the In predicate on the "permissions" field. +func PermissionsIn(vs ...string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldIn(FieldPermissions, vs...)) +} + +// PermissionsNotIn applies the NotIn predicate on the "permissions" field. +func PermissionsNotIn(vs ...string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNotIn(FieldPermissions, vs...)) +} + +// PermissionsGT applies the GT predicate on the "permissions" field. +func PermissionsGT(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGT(FieldPermissions, v)) +} + +// PermissionsGTE applies the GTE predicate on the "permissions" field. +func PermissionsGTE(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGTE(FieldPermissions, v)) +} + +// PermissionsLT applies the LT predicate on the "permissions" field. +func PermissionsLT(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLT(FieldPermissions, v)) +} + +// PermissionsLTE applies the LTE predicate on the "permissions" field. +func PermissionsLTE(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLTE(FieldPermissions, v)) +} + +// PermissionsContains applies the Contains predicate on the "permissions" field. +func PermissionsContains(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldContains(FieldPermissions, v)) +} + +// PermissionsHasPrefix applies the HasPrefix predicate on the "permissions" field. +func PermissionsHasPrefix(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldHasPrefix(FieldPermissions, v)) +} + +// PermissionsHasSuffix applies the HasSuffix predicate on the "permissions" field. +func PermissionsHasSuffix(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldHasSuffix(FieldPermissions, v)) +} + +// PermissionsEqualFold applies the EqualFold predicate on the "permissions" field. +func PermissionsEqualFold(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEqualFold(FieldPermissions, v)) +} + +// PermissionsContainsFold applies the ContainsFold predicate on the "permissions" field. +func PermissionsContainsFold(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldContainsFold(FieldPermissions, v)) +} + +// ReferenceIDEQ applies the EQ predicate on the "reference_id" field. +func ReferenceIDEQ(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEQ(FieldReferenceID, v)) +} + +// ReferenceIDNEQ applies the NEQ predicate on the "reference_id" field. +func ReferenceIDNEQ(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNEQ(FieldReferenceID, v)) +} + +// ReferenceIDIn applies the In predicate on the "reference_id" field. +func ReferenceIDIn(vs ...string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldIn(FieldReferenceID, vs...)) +} + +// ReferenceIDNotIn applies the NotIn predicate on the "reference_id" field. +func ReferenceIDNotIn(vs ...string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldNotIn(FieldReferenceID, vs...)) +} + +// ReferenceIDGT applies the GT predicate on the "reference_id" field. +func ReferenceIDGT(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGT(FieldReferenceID, v)) +} + +// ReferenceIDGTE applies the GTE predicate on the "reference_id" field. +func ReferenceIDGTE(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldGTE(FieldReferenceID, v)) +} + +// ReferenceIDLT applies the LT predicate on the "reference_id" field. +func ReferenceIDLT(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLT(FieldReferenceID, v)) +} + +// ReferenceIDLTE applies the LTE predicate on the "reference_id" field. +func ReferenceIDLTE(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldLTE(FieldReferenceID, v)) +} + +// ReferenceIDContains applies the Contains predicate on the "reference_id" field. +func ReferenceIDContains(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldContains(FieldReferenceID, v)) +} + +// ReferenceIDHasPrefix applies the HasPrefix predicate on the "reference_id" field. +func ReferenceIDHasPrefix(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldHasPrefix(FieldReferenceID, v)) +} + +// ReferenceIDHasSuffix applies the HasSuffix predicate on the "reference_id" field. +func ReferenceIDHasSuffix(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldHasSuffix(FieldReferenceID, v)) +} + +// ReferenceIDEqualFold applies the EqualFold predicate on the "reference_id" field. +func ReferenceIDEqualFold(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldEqualFold(FieldReferenceID, v)) +} + +// ReferenceIDContainsFold applies the ContainsFold predicate on the "reference_id" field. +func ReferenceIDContainsFold(v string) predicate.UserSubscription { + return predicate.UserSubscription(sql.FieldContainsFold(FieldReferenceID, v)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.UserSubscription { + return predicate.UserSubscription(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). +func HasUserWith(preds ...predicate.User) predicate.UserSubscription { + return predicate.UserSubscription(func(s *sql.Selector) { + step := newUserStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.UserSubscription) predicate.UserSubscription { + return predicate.UserSubscription(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.UserSubscription) predicate.UserSubscription { + return predicate.UserSubscription(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.UserSubscription) predicate.UserSubscription { + return predicate.UserSubscription(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/usersubscription_create.go b/internal/database/ent/db/usersubscription_create.go new file mode 100644 index 00000000..fe9f5eee --- /dev/null +++ b/internal/database/ent/db/usersubscription_create.go @@ -0,0 +1,357 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserSubscriptionCreate is the builder for creating a UserSubscription entity. +type UserSubscriptionCreate struct { + config + mutation *UserSubscriptionMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (usc *UserSubscriptionCreate) SetCreatedAt(i int) *UserSubscriptionCreate { + usc.mutation.SetCreatedAt(i) + return usc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (usc *UserSubscriptionCreate) SetNillableCreatedAt(i *int) *UserSubscriptionCreate { + if i != nil { + usc.SetCreatedAt(*i) + } + return usc +} + +// SetUpdatedAt sets the "updated_at" field. +func (usc *UserSubscriptionCreate) SetUpdatedAt(i int) *UserSubscriptionCreate { + usc.mutation.SetUpdatedAt(i) + return usc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (usc *UserSubscriptionCreate) SetNillableUpdatedAt(i *int) *UserSubscriptionCreate { + if i != nil { + usc.SetUpdatedAt(*i) + } + return usc +} + +// SetType sets the "type" field. +func (usc *UserSubscriptionCreate) SetType(mt models.SubscriptionType) *UserSubscriptionCreate { + usc.mutation.SetType(mt) + return usc +} + +// SetExpiresAt sets the "expires_at" field. +func (usc *UserSubscriptionCreate) SetExpiresAt(i int) *UserSubscriptionCreate { + usc.mutation.SetExpiresAt(i) + return usc +} + +// SetUserID sets the "user_id" field. +func (usc *UserSubscriptionCreate) SetUserID(s string) *UserSubscriptionCreate { + usc.mutation.SetUserID(s) + return usc +} + +// SetPermissions sets the "permissions" field. +func (usc *UserSubscriptionCreate) SetPermissions(s string) *UserSubscriptionCreate { + usc.mutation.SetPermissions(s) + return usc +} + +// SetReferenceID sets the "reference_id" field. +func (usc *UserSubscriptionCreate) SetReferenceID(s string) *UserSubscriptionCreate { + usc.mutation.SetReferenceID(s) + return usc +} + +// SetID sets the "id" field. +func (usc *UserSubscriptionCreate) SetID(s string) *UserSubscriptionCreate { + usc.mutation.SetID(s) + return usc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (usc *UserSubscriptionCreate) SetNillableID(s *string) *UserSubscriptionCreate { + if s != nil { + usc.SetID(*s) + } + return usc +} + +// SetUser sets the "user" edge to the User entity. +func (usc *UserSubscriptionCreate) SetUser(u *User) *UserSubscriptionCreate { + return usc.SetUserID(u.ID) +} + +// Mutation returns the UserSubscriptionMutation object of the builder. +func (usc *UserSubscriptionCreate) Mutation() *UserSubscriptionMutation { + return usc.mutation +} + +// Save creates the UserSubscription in the database. +func (usc *UserSubscriptionCreate) Save(ctx context.Context) (*UserSubscription, error) { + usc.defaults() + return withHooks(ctx, usc.sqlSave, usc.mutation, usc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (usc *UserSubscriptionCreate) SaveX(ctx context.Context) *UserSubscription { + v, err := usc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (usc *UserSubscriptionCreate) Exec(ctx context.Context) error { + _, err := usc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (usc *UserSubscriptionCreate) ExecX(ctx context.Context) { + if err := usc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (usc *UserSubscriptionCreate) defaults() { + if _, ok := usc.mutation.CreatedAt(); !ok { + v := usersubscription.DefaultCreatedAt() + usc.mutation.SetCreatedAt(v) + } + if _, ok := usc.mutation.UpdatedAt(); !ok { + v := usersubscription.DefaultUpdatedAt() + usc.mutation.SetUpdatedAt(v) + } + if _, ok := usc.mutation.ID(); !ok { + v := usersubscription.DefaultID() + usc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (usc *UserSubscriptionCreate) check() error { + if _, ok := usc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "UserSubscription.created_at"`)} + } + if _, ok := usc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "UserSubscription.updated_at"`)} + } + if _, ok := usc.mutation.GetType(); !ok { + return &ValidationError{Name: "type", err: errors.New(`db: missing required field "UserSubscription.type"`)} + } + if v, ok := usc.mutation.GetType(); ok { + if err := usersubscription.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "UserSubscription.type": %w`, err)} + } + } + if _, ok := usc.mutation.ExpiresAt(); !ok { + return &ValidationError{Name: "expires_at", err: errors.New(`db: missing required field "UserSubscription.expires_at"`)} + } + if _, ok := usc.mutation.UserID(); !ok { + return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "UserSubscription.user_id"`)} + } + if v, ok := usc.mutation.UserID(); ok { + if err := usersubscription.UserIDValidator(v); err != nil { + return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "UserSubscription.user_id": %w`, err)} + } + } + if _, ok := usc.mutation.Permissions(); !ok { + return &ValidationError{Name: "permissions", err: errors.New(`db: missing required field "UserSubscription.permissions"`)} + } + if v, ok := usc.mutation.Permissions(); ok { + if err := usersubscription.PermissionsValidator(v); err != nil { + return &ValidationError{Name: "permissions", err: fmt.Errorf(`db: validator failed for field "UserSubscription.permissions": %w`, err)} + } + } + if _, ok := usc.mutation.ReferenceID(); !ok { + return &ValidationError{Name: "reference_id", err: errors.New(`db: missing required field "UserSubscription.reference_id"`)} + } + if v, ok := usc.mutation.ReferenceID(); ok { + if err := usersubscription.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "UserSubscription.reference_id": %w`, err)} + } + } + if _, ok := usc.mutation.UserID(); !ok { + return &ValidationError{Name: "user", err: errors.New(`db: missing required edge "UserSubscription.user"`)} + } + return nil +} + +func (usc *UserSubscriptionCreate) sqlSave(ctx context.Context) (*UserSubscription, error) { + if err := usc.check(); err != nil { + return nil, err + } + _node, _spec := usc.createSpec() + if err := sqlgraph.CreateNode(ctx, usc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected UserSubscription.ID type: %T", _spec.ID.Value) + } + } + usc.mutation.id = &_node.ID + usc.mutation.done = true + return _node, nil +} + +func (usc *UserSubscriptionCreate) createSpec() (*UserSubscription, *sqlgraph.CreateSpec) { + var ( + _node = &UserSubscription{config: usc.config} + _spec = sqlgraph.NewCreateSpec(usersubscription.Table, sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString)) + ) + if id, ok := usc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := usc.mutation.CreatedAt(); ok { + _spec.SetField(usersubscription.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := usc.mutation.UpdatedAt(); ok { + _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := usc.mutation.GetType(); ok { + _spec.SetField(usersubscription.FieldType, field.TypeEnum, value) + _node.Type = value + } + if value, ok := usc.mutation.ExpiresAt(); ok { + _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt, value) + _node.ExpiresAt = value + } + if value, ok := usc.mutation.Permissions(); ok { + _spec.SetField(usersubscription.FieldPermissions, field.TypeString, value) + _node.Permissions = value + } + if value, ok := usc.mutation.ReferenceID(); ok { + _spec.SetField(usersubscription.FieldReferenceID, field.TypeString, value) + _node.ReferenceID = value + } + if nodes := usc.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: usersubscription.UserTable, + Columns: []string{usersubscription.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.UserID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// UserSubscriptionCreateBulk is the builder for creating many UserSubscription entities in bulk. +type UserSubscriptionCreateBulk struct { + config + err error + builders []*UserSubscriptionCreate +} + +// Save creates the UserSubscription entities in the database. +func (uscb *UserSubscriptionCreateBulk) Save(ctx context.Context) ([]*UserSubscription, error) { + if uscb.err != nil { + return nil, uscb.err + } + specs := make([]*sqlgraph.CreateSpec, len(uscb.builders)) + nodes := make([]*UserSubscription, len(uscb.builders)) + mutators := make([]Mutator, len(uscb.builders)) + for i := range uscb.builders { + func(i int, root context.Context) { + builder := uscb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserSubscriptionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, uscb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, uscb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, uscb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (uscb *UserSubscriptionCreateBulk) SaveX(ctx context.Context) []*UserSubscription { + v, err := uscb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (uscb *UserSubscriptionCreateBulk) Exec(ctx context.Context) error { + _, err := uscb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (uscb *UserSubscriptionCreateBulk) ExecX(ctx context.Context) { + if err := uscb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/usersubscription_delete.go b/internal/database/ent/db/usersubscription_delete.go new file mode 100644 index 00000000..48e06d20 --- /dev/null +++ b/internal/database/ent/db/usersubscription_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" +) + +// UserSubscriptionDelete is the builder for deleting a UserSubscription entity. +type UserSubscriptionDelete struct { + config + hooks []Hook + mutation *UserSubscriptionMutation +} + +// Where appends a list predicates to the UserSubscriptionDelete builder. +func (usd *UserSubscriptionDelete) Where(ps ...predicate.UserSubscription) *UserSubscriptionDelete { + usd.mutation.Where(ps...) + return usd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (usd *UserSubscriptionDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, usd.sqlExec, usd.mutation, usd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (usd *UserSubscriptionDelete) ExecX(ctx context.Context) int { + n, err := usd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (usd *UserSubscriptionDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(usersubscription.Table, sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString)) + if ps := usd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, usd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + usd.mutation.done = true + return affected, err +} + +// UserSubscriptionDeleteOne is the builder for deleting a single UserSubscription entity. +type UserSubscriptionDeleteOne struct { + usd *UserSubscriptionDelete +} + +// Where appends a list predicates to the UserSubscriptionDelete builder. +func (usdo *UserSubscriptionDeleteOne) Where(ps ...predicate.UserSubscription) *UserSubscriptionDeleteOne { + usdo.usd.mutation.Where(ps...) + return usdo +} + +// Exec executes the deletion query. +func (usdo *UserSubscriptionDeleteOne) Exec(ctx context.Context) error { + n, err := usdo.usd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{usersubscription.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (usdo *UserSubscriptionDeleteOne) ExecX(ctx context.Context) { + if err := usdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/usersubscription_query.go b/internal/database/ent/db/usersubscription_query.go new file mode 100644 index 00000000..dc392666 --- /dev/null +++ b/internal/database/ent/db/usersubscription_query.go @@ -0,0 +1,605 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" +) + +// UserSubscriptionQuery is the builder for querying UserSubscription entities. +type UserSubscriptionQuery struct { + config + ctx *QueryContext + order []usersubscription.OrderOption + inters []Interceptor + predicates []predicate.UserSubscription + withUser *UserQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserSubscriptionQuery builder. +func (usq *UserSubscriptionQuery) Where(ps ...predicate.UserSubscription) *UserSubscriptionQuery { + usq.predicates = append(usq.predicates, ps...) + return usq +} + +// Limit the number of records to be returned by this query. +func (usq *UserSubscriptionQuery) Limit(limit int) *UserSubscriptionQuery { + usq.ctx.Limit = &limit + return usq +} + +// Offset to start from. +func (usq *UserSubscriptionQuery) Offset(offset int) *UserSubscriptionQuery { + usq.ctx.Offset = &offset + return usq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (usq *UserSubscriptionQuery) Unique(unique bool) *UserSubscriptionQuery { + usq.ctx.Unique = &unique + return usq +} + +// Order specifies how the records should be ordered. +func (usq *UserSubscriptionQuery) Order(o ...usersubscription.OrderOption) *UserSubscriptionQuery { + usq.order = append(usq.order, o...) + return usq +} + +// QueryUser chains the current query on the "user" edge. +func (usq *UserSubscriptionQuery) QueryUser() *UserQuery { + query := (&UserClient{config: usq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := usq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := usq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(usersubscription.Table, usersubscription.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, usersubscription.UserTable, usersubscription.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(usq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first UserSubscription entity from the query. +// Returns a *NotFoundError when no UserSubscription was found. +func (usq *UserSubscriptionQuery) First(ctx context.Context) (*UserSubscription, error) { + nodes, err := usq.Limit(1).All(setContextOp(ctx, usq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{usersubscription.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (usq *UserSubscriptionQuery) FirstX(ctx context.Context) *UserSubscription { + node, err := usq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first UserSubscription ID from the query. +// Returns a *NotFoundError when no UserSubscription ID was found. +func (usq *UserSubscriptionQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = usq.Limit(1).IDs(setContextOp(ctx, usq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{usersubscription.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (usq *UserSubscriptionQuery) FirstIDX(ctx context.Context) string { + id, err := usq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single UserSubscription entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one UserSubscription entity is found. +// Returns a *NotFoundError when no UserSubscription entities are found. +func (usq *UserSubscriptionQuery) Only(ctx context.Context) (*UserSubscription, error) { + nodes, err := usq.Limit(2).All(setContextOp(ctx, usq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{usersubscription.Label} + default: + return nil, &NotSingularError{usersubscription.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (usq *UserSubscriptionQuery) OnlyX(ctx context.Context) *UserSubscription { + node, err := usq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only UserSubscription ID in the query. +// Returns a *NotSingularError when more than one UserSubscription ID is found. +// Returns a *NotFoundError when no entities are found. +func (usq *UserSubscriptionQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = usq.Limit(2).IDs(setContextOp(ctx, usq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{usersubscription.Label} + default: + err = &NotSingularError{usersubscription.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (usq *UserSubscriptionQuery) OnlyIDX(ctx context.Context) string { + id, err := usq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of UserSubscriptions. +func (usq *UserSubscriptionQuery) All(ctx context.Context) ([]*UserSubscription, error) { + ctx = setContextOp(ctx, usq.ctx, "All") + if err := usq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*UserSubscription, *UserSubscriptionQuery]() + return withInterceptors[[]*UserSubscription](ctx, usq, qr, usq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (usq *UserSubscriptionQuery) AllX(ctx context.Context) []*UserSubscription { + nodes, err := usq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of UserSubscription IDs. +func (usq *UserSubscriptionQuery) IDs(ctx context.Context) (ids []string, err error) { + if usq.ctx.Unique == nil && usq.path != nil { + usq.Unique(true) + } + ctx = setContextOp(ctx, usq.ctx, "IDs") + if err = usq.Select(usersubscription.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (usq *UserSubscriptionQuery) IDsX(ctx context.Context) []string { + ids, err := usq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (usq *UserSubscriptionQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, usq.ctx, "Count") + if err := usq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, usq, querierCount[*UserSubscriptionQuery](), usq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (usq *UserSubscriptionQuery) CountX(ctx context.Context) int { + count, err := usq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (usq *UserSubscriptionQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, usq.ctx, "Exist") + switch _, err := usq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (usq *UserSubscriptionQuery) ExistX(ctx context.Context) bool { + exist, err := usq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserSubscriptionQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (usq *UserSubscriptionQuery) Clone() *UserSubscriptionQuery { + if usq == nil { + return nil + } + return &UserSubscriptionQuery{ + config: usq.config, + ctx: usq.ctx.Clone(), + order: append([]usersubscription.OrderOption{}, usq.order...), + inters: append([]Interceptor{}, usq.inters...), + predicates: append([]predicate.UserSubscription{}, usq.predicates...), + withUser: usq.withUser.Clone(), + // clone intermediate query. + sql: usq.sql.Clone(), + path: usq.path, + } +} + +// WithUser tells the query-builder to eager-load the nodes that are connected to +// the "user" edge. The optional arguments are used to configure the query builder of the edge. +func (usq *UserSubscriptionQuery) WithUser(opts ...func(*UserQuery)) *UserSubscriptionQuery { + query := (&UserClient{config: usq.config}).Query() + for _, opt := range opts { + opt(query) + } + usq.withUser = query + return usq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.UserSubscription.Query(). +// GroupBy(usersubscription.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (usq *UserSubscriptionQuery) GroupBy(field string, fields ...string) *UserSubscriptionGroupBy { + usq.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserSubscriptionGroupBy{build: usq} + grbuild.flds = &usq.ctx.Fields + grbuild.label = usersubscription.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.UserSubscription.Query(). +// Select(usersubscription.FieldCreatedAt). +// Scan(ctx, &v) +func (usq *UserSubscriptionQuery) Select(fields ...string) *UserSubscriptionSelect { + usq.ctx.Fields = append(usq.ctx.Fields, fields...) + sbuild := &UserSubscriptionSelect{UserSubscriptionQuery: usq} + sbuild.label = usersubscription.Label + sbuild.flds, sbuild.scan = &usq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserSubscriptionSelect configured with the given aggregations. +func (usq *UserSubscriptionQuery) Aggregate(fns ...AggregateFunc) *UserSubscriptionSelect { + return usq.Select().Aggregate(fns...) +} + +func (usq *UserSubscriptionQuery) prepareQuery(ctx context.Context) error { + for _, inter := range usq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, usq); err != nil { + return err + } + } + } + for _, f := range usq.ctx.Fields { + if !usersubscription.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if usq.path != nil { + prev, err := usq.path(ctx) + if err != nil { + return err + } + usq.sql = prev + } + return nil +} + +func (usq *UserSubscriptionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserSubscription, error) { + var ( + nodes = []*UserSubscription{} + _spec = usq.querySpec() + loadedTypes = [1]bool{ + usq.withUser != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*UserSubscription).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &UserSubscription{config: usq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, usq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := usq.withUser; query != nil { + if err := usq.loadUser(ctx, query, nodes, nil, + func(n *UserSubscription, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (usq *UserSubscriptionQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*UserSubscription, init func(*UserSubscription), assign func(*UserSubscription, *User)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*UserSubscription) + for i := range nodes { + fk := nodes[i].UserID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (usq *UserSubscriptionQuery) sqlCount(ctx context.Context) (int, error) { + _spec := usq.querySpec() + _spec.Node.Columns = usq.ctx.Fields + if len(usq.ctx.Fields) > 0 { + _spec.Unique = usq.ctx.Unique != nil && *usq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, usq.driver, _spec) +} + +func (usq *UserSubscriptionQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(usersubscription.Table, usersubscription.Columns, sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString)) + _spec.From = usq.sql + if unique := usq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if usq.path != nil { + _spec.Unique = true + } + if fields := usq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usersubscription.FieldID) + for i := range fields { + if fields[i] != usersubscription.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if usq.withUser != nil { + _spec.Node.AddColumnOnce(usersubscription.FieldUserID) + } + } + if ps := usq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := usq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := usq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := usq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (usq *UserSubscriptionQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(usq.driver.Dialect()) + t1 := builder.Table(usersubscription.Table) + columns := usq.ctx.Fields + if len(columns) == 0 { + columns = usersubscription.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if usq.sql != nil { + selector = usq.sql + selector.Select(selector.Columns(columns...)...) + } + if usq.ctx.Unique != nil && *usq.ctx.Unique { + selector.Distinct() + } + for _, p := range usq.predicates { + p(selector) + } + for _, p := range usq.order { + p(selector) + } + if offset := usq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := usq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// UserSubscriptionGroupBy is the group-by builder for UserSubscription entities. +type UserSubscriptionGroupBy struct { + selector + build *UserSubscriptionQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (usgb *UserSubscriptionGroupBy) Aggregate(fns ...AggregateFunc) *UserSubscriptionGroupBy { + usgb.fns = append(usgb.fns, fns...) + return usgb +} + +// Scan applies the selector query and scans the result into the given value. +func (usgb *UserSubscriptionGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, usgb.build.ctx, "GroupBy") + if err := usgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserSubscriptionQuery, *UserSubscriptionGroupBy](ctx, usgb.build, usgb, usgb.build.inters, v) +} + +func (usgb *UserSubscriptionGroupBy) sqlScan(ctx context.Context, root *UserSubscriptionQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(usgb.fns)) + for _, fn := range usgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*usgb.flds)+len(usgb.fns)) + for _, f := range *usgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*usgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := usgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserSubscriptionSelect is the builder for selecting fields of UserSubscription entities. +type UserSubscriptionSelect struct { + *UserSubscriptionQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (uss *UserSubscriptionSelect) Aggregate(fns ...AggregateFunc) *UserSubscriptionSelect { + uss.fns = append(uss.fns, fns...) + return uss +} + +// Scan applies the selector query and scans the result into the given value. +func (uss *UserSubscriptionSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, uss.ctx, "Select") + if err := uss.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserSubscriptionQuery, *UserSubscriptionSelect](ctx, uss.UserSubscriptionQuery, uss, uss.inters, v) +} + +func (uss *UserSubscriptionSelect) sqlScan(ctx context.Context, root *UserSubscriptionQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(uss.fns)) + for _, fn := range uss.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*uss.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := uss.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/usersubscription_update.go b/internal/database/ent/db/usersubscription_update.go new file mode 100644 index 00000000..ccb7e131 --- /dev/null +++ b/internal/database/ent/db/usersubscription_update.go @@ -0,0 +1,440 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/usersubscription" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserSubscriptionUpdate is the builder for updating UserSubscription entities. +type UserSubscriptionUpdate struct { + config + hooks []Hook + mutation *UserSubscriptionMutation +} + +// Where appends a list predicates to the UserSubscriptionUpdate builder. +func (usu *UserSubscriptionUpdate) Where(ps ...predicate.UserSubscription) *UserSubscriptionUpdate { + usu.mutation.Where(ps...) + return usu +} + +// SetUpdatedAt sets the "updated_at" field. +func (usu *UserSubscriptionUpdate) SetUpdatedAt(i int) *UserSubscriptionUpdate { + usu.mutation.ResetUpdatedAt() + usu.mutation.SetUpdatedAt(i) + return usu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (usu *UserSubscriptionUpdate) AddUpdatedAt(i int) *UserSubscriptionUpdate { + usu.mutation.AddUpdatedAt(i) + return usu +} + +// SetType sets the "type" field. +func (usu *UserSubscriptionUpdate) SetType(mt models.SubscriptionType) *UserSubscriptionUpdate { + usu.mutation.SetType(mt) + return usu +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (usu *UserSubscriptionUpdate) SetNillableType(mt *models.SubscriptionType) *UserSubscriptionUpdate { + if mt != nil { + usu.SetType(*mt) + } + return usu +} + +// SetExpiresAt sets the "expires_at" field. +func (usu *UserSubscriptionUpdate) SetExpiresAt(i int) *UserSubscriptionUpdate { + usu.mutation.ResetExpiresAt() + usu.mutation.SetExpiresAt(i) + return usu +} + +// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. +func (usu *UserSubscriptionUpdate) SetNillableExpiresAt(i *int) *UserSubscriptionUpdate { + if i != nil { + usu.SetExpiresAt(*i) + } + return usu +} + +// AddExpiresAt adds i to the "expires_at" field. +func (usu *UserSubscriptionUpdate) AddExpiresAt(i int) *UserSubscriptionUpdate { + usu.mutation.AddExpiresAt(i) + return usu +} + +// SetPermissions sets the "permissions" field. +func (usu *UserSubscriptionUpdate) SetPermissions(s string) *UserSubscriptionUpdate { + usu.mutation.SetPermissions(s) + return usu +} + +// SetNillablePermissions sets the "permissions" field if the given value is not nil. +func (usu *UserSubscriptionUpdate) SetNillablePermissions(s *string) *UserSubscriptionUpdate { + if s != nil { + usu.SetPermissions(*s) + } + return usu +} + +// SetReferenceID sets the "reference_id" field. +func (usu *UserSubscriptionUpdate) SetReferenceID(s string) *UserSubscriptionUpdate { + usu.mutation.SetReferenceID(s) + return usu +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (usu *UserSubscriptionUpdate) SetNillableReferenceID(s *string) *UserSubscriptionUpdate { + if s != nil { + usu.SetReferenceID(*s) + } + return usu +} + +// Mutation returns the UserSubscriptionMutation object of the builder. +func (usu *UserSubscriptionUpdate) Mutation() *UserSubscriptionMutation { + return usu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (usu *UserSubscriptionUpdate) Save(ctx context.Context) (int, error) { + usu.defaults() + return withHooks(ctx, usu.sqlSave, usu.mutation, usu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (usu *UserSubscriptionUpdate) SaveX(ctx context.Context) int { + affected, err := usu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (usu *UserSubscriptionUpdate) Exec(ctx context.Context) error { + _, err := usu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (usu *UserSubscriptionUpdate) ExecX(ctx context.Context) { + if err := usu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (usu *UserSubscriptionUpdate) defaults() { + if _, ok := usu.mutation.UpdatedAt(); !ok { + v := usersubscription.UpdateDefaultUpdatedAt() + usu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (usu *UserSubscriptionUpdate) check() error { + if v, ok := usu.mutation.GetType(); ok { + if err := usersubscription.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "UserSubscription.type": %w`, err)} + } + } + if v, ok := usu.mutation.Permissions(); ok { + if err := usersubscription.PermissionsValidator(v); err != nil { + return &ValidationError{Name: "permissions", err: fmt.Errorf(`db: validator failed for field "UserSubscription.permissions": %w`, err)} + } + } + if v, ok := usu.mutation.ReferenceID(); ok { + if err := usersubscription.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "UserSubscription.reference_id": %w`, err)} + } + } + if _, ok := usu.mutation.UserID(); usu.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "UserSubscription.user"`) + } + return nil +} + +func (usu *UserSubscriptionUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := usu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(usersubscription.Table, usersubscription.Columns, sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString)) + if ps := usu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := usu.mutation.UpdatedAt(); ok { + _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := usu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := usu.mutation.GetType(); ok { + _spec.SetField(usersubscription.FieldType, field.TypeEnum, value) + } + if value, ok := usu.mutation.ExpiresAt(); ok { + _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt, value) + } + if value, ok := usu.mutation.AddedExpiresAt(); ok { + _spec.AddField(usersubscription.FieldExpiresAt, field.TypeInt, value) + } + if value, ok := usu.mutation.Permissions(); ok { + _spec.SetField(usersubscription.FieldPermissions, field.TypeString, value) + } + if value, ok := usu.mutation.ReferenceID(); ok { + _spec.SetField(usersubscription.FieldReferenceID, field.TypeString, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, usu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usersubscription.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + usu.mutation.done = true + return n, nil +} + +// UserSubscriptionUpdateOne is the builder for updating a single UserSubscription entity. +type UserSubscriptionUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserSubscriptionMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (usuo *UserSubscriptionUpdateOne) SetUpdatedAt(i int) *UserSubscriptionUpdateOne { + usuo.mutation.ResetUpdatedAt() + usuo.mutation.SetUpdatedAt(i) + return usuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (usuo *UserSubscriptionUpdateOne) AddUpdatedAt(i int) *UserSubscriptionUpdateOne { + usuo.mutation.AddUpdatedAt(i) + return usuo +} + +// SetType sets the "type" field. +func (usuo *UserSubscriptionUpdateOne) SetType(mt models.SubscriptionType) *UserSubscriptionUpdateOne { + usuo.mutation.SetType(mt) + return usuo +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (usuo *UserSubscriptionUpdateOne) SetNillableType(mt *models.SubscriptionType) *UserSubscriptionUpdateOne { + if mt != nil { + usuo.SetType(*mt) + } + return usuo +} + +// SetExpiresAt sets the "expires_at" field. +func (usuo *UserSubscriptionUpdateOne) SetExpiresAt(i int) *UserSubscriptionUpdateOne { + usuo.mutation.ResetExpiresAt() + usuo.mutation.SetExpiresAt(i) + return usuo +} + +// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. +func (usuo *UserSubscriptionUpdateOne) SetNillableExpiresAt(i *int) *UserSubscriptionUpdateOne { + if i != nil { + usuo.SetExpiresAt(*i) + } + return usuo +} + +// AddExpiresAt adds i to the "expires_at" field. +func (usuo *UserSubscriptionUpdateOne) AddExpiresAt(i int) *UserSubscriptionUpdateOne { + usuo.mutation.AddExpiresAt(i) + return usuo +} + +// SetPermissions sets the "permissions" field. +func (usuo *UserSubscriptionUpdateOne) SetPermissions(s string) *UserSubscriptionUpdateOne { + usuo.mutation.SetPermissions(s) + return usuo +} + +// SetNillablePermissions sets the "permissions" field if the given value is not nil. +func (usuo *UserSubscriptionUpdateOne) SetNillablePermissions(s *string) *UserSubscriptionUpdateOne { + if s != nil { + usuo.SetPermissions(*s) + } + return usuo +} + +// SetReferenceID sets the "reference_id" field. +func (usuo *UserSubscriptionUpdateOne) SetReferenceID(s string) *UserSubscriptionUpdateOne { + usuo.mutation.SetReferenceID(s) + return usuo +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (usuo *UserSubscriptionUpdateOne) SetNillableReferenceID(s *string) *UserSubscriptionUpdateOne { + if s != nil { + usuo.SetReferenceID(*s) + } + return usuo +} + +// Mutation returns the UserSubscriptionMutation object of the builder. +func (usuo *UserSubscriptionUpdateOne) Mutation() *UserSubscriptionMutation { + return usuo.mutation +} + +// Where appends a list predicates to the UserSubscriptionUpdate builder. +func (usuo *UserSubscriptionUpdateOne) Where(ps ...predicate.UserSubscription) *UserSubscriptionUpdateOne { + usuo.mutation.Where(ps...) + return usuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (usuo *UserSubscriptionUpdateOne) Select(field string, fields ...string) *UserSubscriptionUpdateOne { + usuo.fields = append([]string{field}, fields...) + return usuo +} + +// Save executes the query and returns the updated UserSubscription entity. +func (usuo *UserSubscriptionUpdateOne) Save(ctx context.Context) (*UserSubscription, error) { + usuo.defaults() + return withHooks(ctx, usuo.sqlSave, usuo.mutation, usuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (usuo *UserSubscriptionUpdateOne) SaveX(ctx context.Context) *UserSubscription { + node, err := usuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (usuo *UserSubscriptionUpdateOne) Exec(ctx context.Context) error { + _, err := usuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (usuo *UserSubscriptionUpdateOne) ExecX(ctx context.Context) { + if err := usuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (usuo *UserSubscriptionUpdateOne) defaults() { + if _, ok := usuo.mutation.UpdatedAt(); !ok { + v := usersubscription.UpdateDefaultUpdatedAt() + usuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (usuo *UserSubscriptionUpdateOne) check() error { + if v, ok := usuo.mutation.GetType(); ok { + if err := usersubscription.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "UserSubscription.type": %w`, err)} + } + } + if v, ok := usuo.mutation.Permissions(); ok { + if err := usersubscription.PermissionsValidator(v); err != nil { + return &ValidationError{Name: "permissions", err: fmt.Errorf(`db: validator failed for field "UserSubscription.permissions": %w`, err)} + } + } + if v, ok := usuo.mutation.ReferenceID(); ok { + if err := usersubscription.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "UserSubscription.reference_id": %w`, err)} + } + } + if _, ok := usuo.mutation.UserID(); usuo.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "UserSubscription.user"`) + } + return nil +} + +func (usuo *UserSubscriptionUpdateOne) sqlSave(ctx context.Context) (_node *UserSubscription, err error) { + if err := usuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(usersubscription.Table, usersubscription.Columns, sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeString)) + id, ok := usuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "UserSubscription.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := usuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usersubscription.FieldID) + for _, f := range fields { + if !usersubscription.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != usersubscription.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := usuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := usuo.mutation.UpdatedAt(); ok { + _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := usuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := usuo.mutation.GetType(); ok { + _spec.SetField(usersubscription.FieldType, field.TypeEnum, value) + } + if value, ok := usuo.mutation.ExpiresAt(); ok { + _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt, value) + } + if value, ok := usuo.mutation.AddedExpiresAt(); ok { + _spec.AddField(usersubscription.FieldExpiresAt, field.TypeInt, value) + } + if value, ok := usuo.mutation.Permissions(); ok { + _spec.SetField(usersubscription.FieldPermissions, field.TypeString, value) + } + if value, ok := usuo.mutation.ReferenceID(); ok { + _spec.SetField(usersubscription.FieldReferenceID, field.TypeString, value) + } + _node = &UserSubscription{config: usuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, usuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usersubscription.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + usuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/vehicle.go b/internal/database/ent/db/vehicle.go new file mode 100644 index 00000000..14f5ecf1 --- /dev/null +++ b/internal/database/ent/db/vehicle.go @@ -0,0 +1,141 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" +) + +// Vehicle is the model entity for the Vehicle schema. +type Vehicle struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Tier holds the value of the "tier" field. + Tier int `json:"tier,omitempty"` + // LocalizedNames holds the value of the "localized_names" field. + LocalizedNames map[string]string `json:"localized_names,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Vehicle) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case vehicle.FieldLocalizedNames: + values[i] = new([]byte) + case vehicle.FieldCreatedAt, vehicle.FieldUpdatedAt, vehicle.FieldTier: + values[i] = new(sql.NullInt64) + case vehicle.FieldID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Vehicle fields. +func (v *Vehicle) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case vehicle.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + v.ID = value.String + } + case vehicle.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + v.CreatedAt = int(value.Int64) + } + case vehicle.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + v.UpdatedAt = int(value.Int64) + } + case vehicle.FieldTier: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field tier", values[i]) + } else if value.Valid { + v.Tier = int(value.Int64) + } + case vehicle.FieldLocalizedNames: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field localized_names", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &v.LocalizedNames); err != nil { + return fmt.Errorf("unmarshal field localized_names: %w", err) + } + } + default: + v.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the Vehicle. +// This includes values selected through modifiers, order, etc. +func (v *Vehicle) Value(name string) (ent.Value, error) { + return v.selectValues.Get(name) +} + +// Update returns a builder for updating this Vehicle. +// Note that you need to call Vehicle.Unwrap() before calling this method if this Vehicle +// was returned from a transaction, and the transaction was committed or rolled back. +func (v *Vehicle) Update() *VehicleUpdateOne { + return NewVehicleClient(v.config).UpdateOne(v) +} + +// Unwrap unwraps the Vehicle entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (v *Vehicle) Unwrap() *Vehicle { + _tx, ok := v.config.driver.(*txDriver) + if !ok { + panic("db: Vehicle is not a transactional entity") + } + v.config.driver = _tx.drv + return v +} + +// String implements the fmt.Stringer. +func (v *Vehicle) String() string { + var builder strings.Builder + builder.WriteString("Vehicle(") + builder.WriteString(fmt.Sprintf("id=%v, ", v.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", v.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", v.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("tier=") + builder.WriteString(fmt.Sprintf("%v", v.Tier)) + builder.WriteString(", ") + builder.WriteString("localized_names=") + builder.WriteString(fmt.Sprintf("%v", v.LocalizedNames)) + builder.WriteByte(')') + return builder.String() +} + +// Vehicles is a parsable slice of Vehicle. +type Vehicles []*Vehicle diff --git a/internal/database/ent/db/vehicle/vehicle.go b/internal/database/ent/db/vehicle/vehicle.go new file mode 100644 index 00000000..236f55e0 --- /dev/null +++ b/internal/database/ent/db/vehicle/vehicle.go @@ -0,0 +1,77 @@ +// Code generated by ent, DO NOT EDIT. + +package vehicle + +import ( + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the vehicle type in the database. + Label = "vehicle" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldTier holds the string denoting the tier field in the database. + FieldTier = "tier" + // FieldLocalizedNames holds the string denoting the localized_names field in the database. + FieldLocalizedNames = "localized_names" + // Table holds the table name of the vehicle in the database. + Table = "vehicles" +) + +// Columns holds all SQL columns for vehicle fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldTier, + FieldLocalizedNames, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // TierValidator is a validator for the "tier" field. It is called by the builders before save. + TierValidator func(int) error +) + +// OrderOption defines the ordering options for the Vehicle queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByTier orders the results by the tier field. +func ByTier(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTier, opts...).ToFunc() +} diff --git a/internal/database/ent/db/vehicle/where.go b/internal/database/ent/db/vehicle/where.go new file mode 100644 index 00000000..c09e0846 --- /dev/null +++ b/internal/database/ent/db/vehicle/where.go @@ -0,0 +1,213 @@ +// Code generated by ent, DO NOT EDIT. + +package vehicle + +import ( + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.Vehicle { + return predicate.Vehicle(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Tier applies equality check predicate on the "tier" field. It's identical to TierEQ. +func Tier(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldEQ(FieldTier, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// TierEQ applies the EQ predicate on the "tier" field. +func TierEQ(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldEQ(FieldTier, v)) +} + +// TierNEQ applies the NEQ predicate on the "tier" field. +func TierNEQ(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldNEQ(FieldTier, v)) +} + +// TierIn applies the In predicate on the "tier" field. +func TierIn(vs ...int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldIn(FieldTier, vs...)) +} + +// TierNotIn applies the NotIn predicate on the "tier" field. +func TierNotIn(vs ...int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldNotIn(FieldTier, vs...)) +} + +// TierGT applies the GT predicate on the "tier" field. +func TierGT(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldGT(FieldTier, v)) +} + +// TierGTE applies the GTE predicate on the "tier" field. +func TierGTE(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldGTE(FieldTier, v)) +} + +// TierLT applies the LT predicate on the "tier" field. +func TierLT(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldLT(FieldTier, v)) +} + +// TierLTE applies the LTE predicate on the "tier" field. +func TierLTE(v int) predicate.Vehicle { + return predicate.Vehicle(sql.FieldLTE(FieldTier, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Vehicle) predicate.Vehicle { + return predicate.Vehicle(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Vehicle) predicate.Vehicle { + return predicate.Vehicle(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Vehicle) predicate.Vehicle { + return predicate.Vehicle(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/vehicle_create.go b/internal/database/ent/db/vehicle_create.go new file mode 100644 index 00000000..5487bf7b --- /dev/null +++ b/internal/database/ent/db/vehicle_create.go @@ -0,0 +1,268 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" +) + +// VehicleCreate is the builder for creating a Vehicle entity. +type VehicleCreate struct { + config + mutation *VehicleMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (vc *VehicleCreate) SetCreatedAt(i int) *VehicleCreate { + vc.mutation.SetCreatedAt(i) + return vc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (vc *VehicleCreate) SetNillableCreatedAt(i *int) *VehicleCreate { + if i != nil { + vc.SetCreatedAt(*i) + } + return vc +} + +// SetUpdatedAt sets the "updated_at" field. +func (vc *VehicleCreate) SetUpdatedAt(i int) *VehicleCreate { + vc.mutation.SetUpdatedAt(i) + return vc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (vc *VehicleCreate) SetNillableUpdatedAt(i *int) *VehicleCreate { + if i != nil { + vc.SetUpdatedAt(*i) + } + return vc +} + +// SetTier sets the "tier" field. +func (vc *VehicleCreate) SetTier(i int) *VehicleCreate { + vc.mutation.SetTier(i) + return vc +} + +// SetLocalizedNames sets the "localized_names" field. +func (vc *VehicleCreate) SetLocalizedNames(m map[string]string) *VehicleCreate { + vc.mutation.SetLocalizedNames(m) + return vc +} + +// SetID sets the "id" field. +func (vc *VehicleCreate) SetID(s string) *VehicleCreate { + vc.mutation.SetID(s) + return vc +} + +// Mutation returns the VehicleMutation object of the builder. +func (vc *VehicleCreate) Mutation() *VehicleMutation { + return vc.mutation +} + +// Save creates the Vehicle in the database. +func (vc *VehicleCreate) Save(ctx context.Context) (*Vehicle, error) { + vc.defaults() + return withHooks(ctx, vc.sqlSave, vc.mutation, vc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (vc *VehicleCreate) SaveX(ctx context.Context) *Vehicle { + v, err := vc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (vc *VehicleCreate) Exec(ctx context.Context) error { + _, err := vc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vc *VehicleCreate) ExecX(ctx context.Context) { + if err := vc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vc *VehicleCreate) defaults() { + if _, ok := vc.mutation.CreatedAt(); !ok { + v := vehicle.DefaultCreatedAt() + vc.mutation.SetCreatedAt(v) + } + if _, ok := vc.mutation.UpdatedAt(); !ok { + v := vehicle.DefaultUpdatedAt() + vc.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (vc *VehicleCreate) check() error { + if _, ok := vc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "Vehicle.created_at"`)} + } + if _, ok := vc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "Vehicle.updated_at"`)} + } + if _, ok := vc.mutation.Tier(); !ok { + return &ValidationError{Name: "tier", err: errors.New(`db: missing required field "Vehicle.tier"`)} + } + if v, ok := vc.mutation.Tier(); ok { + if err := vehicle.TierValidator(v); err != nil { + return &ValidationError{Name: "tier", err: fmt.Errorf(`db: validator failed for field "Vehicle.tier": %w`, err)} + } + } + if _, ok := vc.mutation.LocalizedNames(); !ok { + return &ValidationError{Name: "localized_names", err: errors.New(`db: missing required field "Vehicle.localized_names"`)} + } + return nil +} + +func (vc *VehicleCreate) sqlSave(ctx context.Context) (*Vehicle, error) { + if err := vc.check(); err != nil { + return nil, err + } + _node, _spec := vc.createSpec() + if err := sqlgraph.CreateNode(ctx, vc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected Vehicle.ID type: %T", _spec.ID.Value) + } + } + vc.mutation.id = &_node.ID + vc.mutation.done = true + return _node, nil +} + +func (vc *VehicleCreate) createSpec() (*Vehicle, *sqlgraph.CreateSpec) { + var ( + _node = &Vehicle{config: vc.config} + _spec = sqlgraph.NewCreateSpec(vehicle.Table, sqlgraph.NewFieldSpec(vehicle.FieldID, field.TypeString)) + ) + if id, ok := vc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := vc.mutation.CreatedAt(); ok { + _spec.SetField(vehicle.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := vc.mutation.UpdatedAt(); ok { + _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := vc.mutation.Tier(); ok { + _spec.SetField(vehicle.FieldTier, field.TypeInt, value) + _node.Tier = value + } + if value, ok := vc.mutation.LocalizedNames(); ok { + _spec.SetField(vehicle.FieldLocalizedNames, field.TypeJSON, value) + _node.LocalizedNames = value + } + return _node, _spec +} + +// VehicleCreateBulk is the builder for creating many Vehicle entities in bulk. +type VehicleCreateBulk struct { + config + err error + builders []*VehicleCreate +} + +// Save creates the Vehicle entities in the database. +func (vcb *VehicleCreateBulk) Save(ctx context.Context) ([]*Vehicle, error) { + if vcb.err != nil { + return nil, vcb.err + } + specs := make([]*sqlgraph.CreateSpec, len(vcb.builders)) + nodes := make([]*Vehicle, len(vcb.builders)) + mutators := make([]Mutator, len(vcb.builders)) + for i := range vcb.builders { + func(i int, root context.Context) { + builder := vcb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*VehicleMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, vcb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, vcb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, vcb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (vcb *VehicleCreateBulk) SaveX(ctx context.Context) []*Vehicle { + v, err := vcb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (vcb *VehicleCreateBulk) Exec(ctx context.Context) error { + _, err := vcb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vcb *VehicleCreateBulk) ExecX(ctx context.Context) { + if err := vcb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/vehicle_delete.go b/internal/database/ent/db/vehicle_delete.go new file mode 100644 index 00000000..bb495b37 --- /dev/null +++ b/internal/database/ent/db/vehicle_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" +) + +// VehicleDelete is the builder for deleting a Vehicle entity. +type VehicleDelete struct { + config + hooks []Hook + mutation *VehicleMutation +} + +// Where appends a list predicates to the VehicleDelete builder. +func (vd *VehicleDelete) Where(ps ...predicate.Vehicle) *VehicleDelete { + vd.mutation.Where(ps...) + return vd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (vd *VehicleDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, vd.sqlExec, vd.mutation, vd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (vd *VehicleDelete) ExecX(ctx context.Context) int { + n, err := vd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (vd *VehicleDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(vehicle.Table, sqlgraph.NewFieldSpec(vehicle.FieldID, field.TypeString)) + if ps := vd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, vd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + vd.mutation.done = true + return affected, err +} + +// VehicleDeleteOne is the builder for deleting a single Vehicle entity. +type VehicleDeleteOne struct { + vd *VehicleDelete +} + +// Where appends a list predicates to the VehicleDelete builder. +func (vdo *VehicleDeleteOne) Where(ps ...predicate.Vehicle) *VehicleDeleteOne { + vdo.vd.mutation.Where(ps...) + return vdo +} + +// Exec executes the deletion query. +func (vdo *VehicleDeleteOne) Exec(ctx context.Context) error { + n, err := vdo.vd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{vehicle.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (vdo *VehicleDeleteOne) ExecX(ctx context.Context) { + if err := vdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/vehicle_query.go b/internal/database/ent/db/vehicle_query.go new file mode 100644 index 00000000..2eef76b0 --- /dev/null +++ b/internal/database/ent/db/vehicle_query.go @@ -0,0 +1,526 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" +) + +// VehicleQuery is the builder for querying Vehicle entities. +type VehicleQuery struct { + config + ctx *QueryContext + order []vehicle.OrderOption + inters []Interceptor + predicates []predicate.Vehicle + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the VehicleQuery builder. +func (vq *VehicleQuery) Where(ps ...predicate.Vehicle) *VehicleQuery { + vq.predicates = append(vq.predicates, ps...) + return vq +} + +// Limit the number of records to be returned by this query. +func (vq *VehicleQuery) Limit(limit int) *VehicleQuery { + vq.ctx.Limit = &limit + return vq +} + +// Offset to start from. +func (vq *VehicleQuery) Offset(offset int) *VehicleQuery { + vq.ctx.Offset = &offset + return vq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (vq *VehicleQuery) Unique(unique bool) *VehicleQuery { + vq.ctx.Unique = &unique + return vq +} + +// Order specifies how the records should be ordered. +func (vq *VehicleQuery) Order(o ...vehicle.OrderOption) *VehicleQuery { + vq.order = append(vq.order, o...) + return vq +} + +// First returns the first Vehicle entity from the query. +// Returns a *NotFoundError when no Vehicle was found. +func (vq *VehicleQuery) First(ctx context.Context) (*Vehicle, error) { + nodes, err := vq.Limit(1).All(setContextOp(ctx, vq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{vehicle.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (vq *VehicleQuery) FirstX(ctx context.Context) *Vehicle { + node, err := vq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Vehicle ID from the query. +// Returns a *NotFoundError when no Vehicle ID was found. +func (vq *VehicleQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = vq.Limit(1).IDs(setContextOp(ctx, vq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{vehicle.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (vq *VehicleQuery) FirstIDX(ctx context.Context) string { + id, err := vq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Vehicle entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Vehicle entity is found. +// Returns a *NotFoundError when no Vehicle entities are found. +func (vq *VehicleQuery) Only(ctx context.Context) (*Vehicle, error) { + nodes, err := vq.Limit(2).All(setContextOp(ctx, vq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{vehicle.Label} + default: + return nil, &NotSingularError{vehicle.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (vq *VehicleQuery) OnlyX(ctx context.Context) *Vehicle { + node, err := vq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Vehicle ID in the query. +// Returns a *NotSingularError when more than one Vehicle ID is found. +// Returns a *NotFoundError when no entities are found. +func (vq *VehicleQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = vq.Limit(2).IDs(setContextOp(ctx, vq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{vehicle.Label} + default: + err = &NotSingularError{vehicle.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (vq *VehicleQuery) OnlyIDX(ctx context.Context) string { + id, err := vq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Vehicles. +func (vq *VehicleQuery) All(ctx context.Context) ([]*Vehicle, error) { + ctx = setContextOp(ctx, vq.ctx, "All") + if err := vq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Vehicle, *VehicleQuery]() + return withInterceptors[[]*Vehicle](ctx, vq, qr, vq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (vq *VehicleQuery) AllX(ctx context.Context) []*Vehicle { + nodes, err := vq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Vehicle IDs. +func (vq *VehicleQuery) IDs(ctx context.Context) (ids []string, err error) { + if vq.ctx.Unique == nil && vq.path != nil { + vq.Unique(true) + } + ctx = setContextOp(ctx, vq.ctx, "IDs") + if err = vq.Select(vehicle.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (vq *VehicleQuery) IDsX(ctx context.Context) []string { + ids, err := vq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (vq *VehicleQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, vq.ctx, "Count") + if err := vq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, vq, querierCount[*VehicleQuery](), vq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (vq *VehicleQuery) CountX(ctx context.Context) int { + count, err := vq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (vq *VehicleQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, vq.ctx, "Exist") + switch _, err := vq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (vq *VehicleQuery) ExistX(ctx context.Context) bool { + exist, err := vq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the VehicleQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (vq *VehicleQuery) Clone() *VehicleQuery { + if vq == nil { + return nil + } + return &VehicleQuery{ + config: vq.config, + ctx: vq.ctx.Clone(), + order: append([]vehicle.OrderOption{}, vq.order...), + inters: append([]Interceptor{}, vq.inters...), + predicates: append([]predicate.Vehicle{}, vq.predicates...), + // clone intermediate query. + sql: vq.sql.Clone(), + path: vq.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Vehicle.Query(). +// GroupBy(vehicle.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (vq *VehicleQuery) GroupBy(field string, fields ...string) *VehicleGroupBy { + vq.ctx.Fields = append([]string{field}, fields...) + grbuild := &VehicleGroupBy{build: vq} + grbuild.flds = &vq.ctx.Fields + grbuild.label = vehicle.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.Vehicle.Query(). +// Select(vehicle.FieldCreatedAt). +// Scan(ctx, &v) +func (vq *VehicleQuery) Select(fields ...string) *VehicleSelect { + vq.ctx.Fields = append(vq.ctx.Fields, fields...) + sbuild := &VehicleSelect{VehicleQuery: vq} + sbuild.label = vehicle.Label + sbuild.flds, sbuild.scan = &vq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a VehicleSelect configured with the given aggregations. +func (vq *VehicleQuery) Aggregate(fns ...AggregateFunc) *VehicleSelect { + return vq.Select().Aggregate(fns...) +} + +func (vq *VehicleQuery) prepareQuery(ctx context.Context) error { + for _, inter := range vq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, vq); err != nil { + return err + } + } + } + for _, f := range vq.ctx.Fields { + if !vehicle.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if vq.path != nil { + prev, err := vq.path(ctx) + if err != nil { + return err + } + vq.sql = prev + } + return nil +} + +func (vq *VehicleQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Vehicle, error) { + var ( + nodes = []*Vehicle{} + _spec = vq.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*Vehicle).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &Vehicle{config: vq.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, vq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (vq *VehicleQuery) sqlCount(ctx context.Context) (int, error) { + _spec := vq.querySpec() + _spec.Node.Columns = vq.ctx.Fields + if len(vq.ctx.Fields) > 0 { + _spec.Unique = vq.ctx.Unique != nil && *vq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, vq.driver, _spec) +} + +func (vq *VehicleQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(vehicle.Table, vehicle.Columns, sqlgraph.NewFieldSpec(vehicle.FieldID, field.TypeString)) + _spec.From = vq.sql + if unique := vq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if vq.path != nil { + _spec.Unique = true + } + if fields := vq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, vehicle.FieldID) + for i := range fields { + if fields[i] != vehicle.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := vq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := vq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := vq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := vq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (vq *VehicleQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(vq.driver.Dialect()) + t1 := builder.Table(vehicle.Table) + columns := vq.ctx.Fields + if len(columns) == 0 { + columns = vehicle.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if vq.sql != nil { + selector = vq.sql + selector.Select(selector.Columns(columns...)...) + } + if vq.ctx.Unique != nil && *vq.ctx.Unique { + selector.Distinct() + } + for _, p := range vq.predicates { + p(selector) + } + for _, p := range vq.order { + p(selector) + } + if offset := vq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := vq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// VehicleGroupBy is the group-by builder for Vehicle entities. +type VehicleGroupBy struct { + selector + build *VehicleQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (vgb *VehicleGroupBy) Aggregate(fns ...AggregateFunc) *VehicleGroupBy { + vgb.fns = append(vgb.fns, fns...) + return vgb +} + +// Scan applies the selector query and scans the result into the given value. +func (vgb *VehicleGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, vgb.build.ctx, "GroupBy") + if err := vgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*VehicleQuery, *VehicleGroupBy](ctx, vgb.build, vgb, vgb.build.inters, v) +} + +func (vgb *VehicleGroupBy) sqlScan(ctx context.Context, root *VehicleQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(vgb.fns)) + for _, fn := range vgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*vgb.flds)+len(vgb.fns)) + for _, f := range *vgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*vgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := vgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// VehicleSelect is the builder for selecting fields of Vehicle entities. +type VehicleSelect struct { + *VehicleQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (vs *VehicleSelect) Aggregate(fns ...AggregateFunc) *VehicleSelect { + vs.fns = append(vs.fns, fns...) + return vs +} + +// Scan applies the selector query and scans the result into the given value. +func (vs *VehicleSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, vs.ctx, "Select") + if err := vs.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*VehicleQuery, *VehicleSelect](ctx, vs.VehicleQuery, vs, vs.inters, v) +} + +func (vs *VehicleSelect) sqlScan(ctx context.Context, root *VehicleQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(vs.fns)) + for _, fn := range vs.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*vs.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := vs.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/vehicle_update.go b/internal/database/ent/db/vehicle_update.go new file mode 100644 index 00000000..0459f716 --- /dev/null +++ b/internal/database/ent/db/vehicle_update.go @@ -0,0 +1,329 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" +) + +// VehicleUpdate is the builder for updating Vehicle entities. +type VehicleUpdate struct { + config + hooks []Hook + mutation *VehicleMutation +} + +// Where appends a list predicates to the VehicleUpdate builder. +func (vu *VehicleUpdate) Where(ps ...predicate.Vehicle) *VehicleUpdate { + vu.mutation.Where(ps...) + return vu +} + +// SetUpdatedAt sets the "updated_at" field. +func (vu *VehicleUpdate) SetUpdatedAt(i int) *VehicleUpdate { + vu.mutation.ResetUpdatedAt() + vu.mutation.SetUpdatedAt(i) + return vu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (vu *VehicleUpdate) AddUpdatedAt(i int) *VehicleUpdate { + vu.mutation.AddUpdatedAt(i) + return vu +} + +// SetTier sets the "tier" field. +func (vu *VehicleUpdate) SetTier(i int) *VehicleUpdate { + vu.mutation.ResetTier() + vu.mutation.SetTier(i) + return vu +} + +// SetNillableTier sets the "tier" field if the given value is not nil. +func (vu *VehicleUpdate) SetNillableTier(i *int) *VehicleUpdate { + if i != nil { + vu.SetTier(*i) + } + return vu +} + +// AddTier adds i to the "tier" field. +func (vu *VehicleUpdate) AddTier(i int) *VehicleUpdate { + vu.mutation.AddTier(i) + return vu +} + +// SetLocalizedNames sets the "localized_names" field. +func (vu *VehicleUpdate) SetLocalizedNames(m map[string]string) *VehicleUpdate { + vu.mutation.SetLocalizedNames(m) + return vu +} + +// Mutation returns the VehicleMutation object of the builder. +func (vu *VehicleUpdate) Mutation() *VehicleMutation { + return vu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (vu *VehicleUpdate) Save(ctx context.Context) (int, error) { + vu.defaults() + return withHooks(ctx, vu.sqlSave, vu.mutation, vu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (vu *VehicleUpdate) SaveX(ctx context.Context) int { + affected, err := vu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (vu *VehicleUpdate) Exec(ctx context.Context) error { + _, err := vu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vu *VehicleUpdate) ExecX(ctx context.Context) { + if err := vu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vu *VehicleUpdate) defaults() { + if _, ok := vu.mutation.UpdatedAt(); !ok { + v := vehicle.UpdateDefaultUpdatedAt() + vu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (vu *VehicleUpdate) check() error { + if v, ok := vu.mutation.Tier(); ok { + if err := vehicle.TierValidator(v); err != nil { + return &ValidationError{Name: "tier", err: fmt.Errorf(`db: validator failed for field "Vehicle.tier": %w`, err)} + } + } + return nil +} + +func (vu *VehicleUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := vu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(vehicle.Table, vehicle.Columns, sqlgraph.NewFieldSpec(vehicle.FieldID, field.TypeString)) + if ps := vu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := vu.mutation.UpdatedAt(); ok { + _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(vehicle.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vu.mutation.Tier(); ok { + _spec.SetField(vehicle.FieldTier, field.TypeInt, value) + } + if value, ok := vu.mutation.AddedTier(); ok { + _spec.AddField(vehicle.FieldTier, field.TypeInt, value) + } + if value, ok := vu.mutation.LocalizedNames(); ok { + _spec.SetField(vehicle.FieldLocalizedNames, field.TypeJSON, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, vu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{vehicle.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + vu.mutation.done = true + return n, nil +} + +// VehicleUpdateOne is the builder for updating a single Vehicle entity. +type VehicleUpdateOne struct { + config + fields []string + hooks []Hook + mutation *VehicleMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (vuo *VehicleUpdateOne) SetUpdatedAt(i int) *VehicleUpdateOne { + vuo.mutation.ResetUpdatedAt() + vuo.mutation.SetUpdatedAt(i) + return vuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (vuo *VehicleUpdateOne) AddUpdatedAt(i int) *VehicleUpdateOne { + vuo.mutation.AddUpdatedAt(i) + return vuo +} + +// SetTier sets the "tier" field. +func (vuo *VehicleUpdateOne) SetTier(i int) *VehicleUpdateOne { + vuo.mutation.ResetTier() + vuo.mutation.SetTier(i) + return vuo +} + +// SetNillableTier sets the "tier" field if the given value is not nil. +func (vuo *VehicleUpdateOne) SetNillableTier(i *int) *VehicleUpdateOne { + if i != nil { + vuo.SetTier(*i) + } + return vuo +} + +// AddTier adds i to the "tier" field. +func (vuo *VehicleUpdateOne) AddTier(i int) *VehicleUpdateOne { + vuo.mutation.AddTier(i) + return vuo +} + +// SetLocalizedNames sets the "localized_names" field. +func (vuo *VehicleUpdateOne) SetLocalizedNames(m map[string]string) *VehicleUpdateOne { + vuo.mutation.SetLocalizedNames(m) + return vuo +} + +// Mutation returns the VehicleMutation object of the builder. +func (vuo *VehicleUpdateOne) Mutation() *VehicleMutation { + return vuo.mutation +} + +// Where appends a list predicates to the VehicleUpdate builder. +func (vuo *VehicleUpdateOne) Where(ps ...predicate.Vehicle) *VehicleUpdateOne { + vuo.mutation.Where(ps...) + return vuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (vuo *VehicleUpdateOne) Select(field string, fields ...string) *VehicleUpdateOne { + vuo.fields = append([]string{field}, fields...) + return vuo +} + +// Save executes the query and returns the updated Vehicle entity. +func (vuo *VehicleUpdateOne) Save(ctx context.Context) (*Vehicle, error) { + vuo.defaults() + return withHooks(ctx, vuo.sqlSave, vuo.mutation, vuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (vuo *VehicleUpdateOne) SaveX(ctx context.Context) *Vehicle { + node, err := vuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (vuo *VehicleUpdateOne) Exec(ctx context.Context) error { + _, err := vuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vuo *VehicleUpdateOne) ExecX(ctx context.Context) { + if err := vuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vuo *VehicleUpdateOne) defaults() { + if _, ok := vuo.mutation.UpdatedAt(); !ok { + v := vehicle.UpdateDefaultUpdatedAt() + vuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (vuo *VehicleUpdateOne) check() error { + if v, ok := vuo.mutation.Tier(); ok { + if err := vehicle.TierValidator(v); err != nil { + return &ValidationError{Name: "tier", err: fmt.Errorf(`db: validator failed for field "Vehicle.tier": %w`, err)} + } + } + return nil +} + +func (vuo *VehicleUpdateOne) sqlSave(ctx context.Context) (_node *Vehicle, err error) { + if err := vuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(vehicle.Table, vehicle.Columns, sqlgraph.NewFieldSpec(vehicle.FieldID, field.TypeString)) + id, ok := vuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "Vehicle.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := vuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, vehicle.FieldID) + for _, f := range fields { + if !vehicle.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != vehicle.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := vuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := vuo.mutation.UpdatedAt(); ok { + _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(vehicle.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vuo.mutation.Tier(); ok { + _spec.SetField(vehicle.FieldTier, field.TypeInt, value) + } + if value, ok := vuo.mutation.AddedTier(); ok { + _spec.AddField(vehicle.FieldTier, field.TypeInt, value) + } + if value, ok := vuo.mutation.LocalizedNames(); ok { + _spec.SetField(vehicle.FieldLocalizedNames, field.TypeJSON, value) + } + _node = &Vehicle{config: vuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, vuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{vehicle.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + vuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/vehicleaverage.go b/internal/database/ent/db/vehicleaverage.go new file mode 100644 index 00000000..8b300a7e --- /dev/null +++ b/internal/database/ent/db/vehicleaverage.go @@ -0,0 +1,131 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// VehicleAverage is the model entity for the VehicleAverage schema. +type VehicleAverage struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Data holds the value of the "data" field. + Data map[string]frame.StatsFrame `json:"data,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*VehicleAverage) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case vehicleaverage.FieldData: + values[i] = new([]byte) + case vehicleaverage.FieldCreatedAt, vehicleaverage.FieldUpdatedAt: + values[i] = new(sql.NullInt64) + case vehicleaverage.FieldID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the VehicleAverage fields. +func (va *VehicleAverage) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case vehicleaverage.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + va.ID = value.String + } + case vehicleaverage.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + va.CreatedAt = int(value.Int64) + } + case vehicleaverage.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + va.UpdatedAt = int(value.Int64) + } + case vehicleaverage.FieldData: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field data", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &va.Data); err != nil { + return fmt.Errorf("unmarshal field data: %w", err) + } + } + default: + va.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the VehicleAverage. +// This includes values selected through modifiers, order, etc. +func (va *VehicleAverage) Value(name string) (ent.Value, error) { + return va.selectValues.Get(name) +} + +// Update returns a builder for updating this VehicleAverage. +// Note that you need to call VehicleAverage.Unwrap() before calling this method if this VehicleAverage +// was returned from a transaction, and the transaction was committed or rolled back. +func (va *VehicleAverage) Update() *VehicleAverageUpdateOne { + return NewVehicleAverageClient(va.config).UpdateOne(va) +} + +// Unwrap unwraps the VehicleAverage entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (va *VehicleAverage) Unwrap() *VehicleAverage { + _tx, ok := va.config.driver.(*txDriver) + if !ok { + panic("db: VehicleAverage is not a transactional entity") + } + va.config.driver = _tx.drv + return va +} + +// String implements the fmt.Stringer. +func (va *VehicleAverage) String() string { + var builder strings.Builder + builder.WriteString("VehicleAverage(") + builder.WriteString(fmt.Sprintf("id=%v, ", va.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", va.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", va.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("data=") + builder.WriteString(fmt.Sprintf("%v", va.Data)) + builder.WriteByte(')') + return builder.String() +} + +// VehicleAverages is a parsable slice of VehicleAverage. +type VehicleAverages []*VehicleAverage diff --git a/internal/database/ent/db/vehicleaverage/vehicleaverage.go b/internal/database/ent/db/vehicleaverage/vehicleaverage.go new file mode 100644 index 00000000..4b90c1bc --- /dev/null +++ b/internal/database/ent/db/vehicleaverage/vehicleaverage.go @@ -0,0 +1,67 @@ +// Code generated by ent, DO NOT EDIT. + +package vehicleaverage + +import ( + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the vehicleaverage type in the database. + Label = "vehicle_average" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldData holds the string denoting the data field in the database. + FieldData = "data" + // Table holds the table name of the vehicleaverage in the database. + Table = "vehicle_averages" +) + +// Columns holds all SQL columns for vehicleaverage fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldData, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int +) + +// OrderOption defines the ordering options for the VehicleAverage queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} diff --git a/internal/database/ent/db/vehicleaverage/where.go b/internal/database/ent/db/vehicleaverage/where.go new file mode 100644 index 00000000..08934c78 --- /dev/null +++ b/internal/database/ent/db/vehicleaverage/where.go @@ -0,0 +1,168 @@ +// Code generated by ent, DO NOT EDIT. + +package vehicleaverage + +import ( + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.VehicleAverage) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.VehicleAverage) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.VehicleAverage) predicate.VehicleAverage { + return predicate.VehicleAverage(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/vehicleaverage_create.go b/internal/database/ent/db/vehicleaverage_create.go new file mode 100644 index 00000000..01704d15 --- /dev/null +++ b/internal/database/ent/db/vehicleaverage_create.go @@ -0,0 +1,251 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// VehicleAverageCreate is the builder for creating a VehicleAverage entity. +type VehicleAverageCreate struct { + config + mutation *VehicleAverageMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (vac *VehicleAverageCreate) SetCreatedAt(i int) *VehicleAverageCreate { + vac.mutation.SetCreatedAt(i) + return vac +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (vac *VehicleAverageCreate) SetNillableCreatedAt(i *int) *VehicleAverageCreate { + if i != nil { + vac.SetCreatedAt(*i) + } + return vac +} + +// SetUpdatedAt sets the "updated_at" field. +func (vac *VehicleAverageCreate) SetUpdatedAt(i int) *VehicleAverageCreate { + vac.mutation.SetUpdatedAt(i) + return vac +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (vac *VehicleAverageCreate) SetNillableUpdatedAt(i *int) *VehicleAverageCreate { + if i != nil { + vac.SetUpdatedAt(*i) + } + return vac +} + +// SetData sets the "data" field. +func (vac *VehicleAverageCreate) SetData(mf map[string]frame.StatsFrame) *VehicleAverageCreate { + vac.mutation.SetData(mf) + return vac +} + +// SetID sets the "id" field. +func (vac *VehicleAverageCreate) SetID(s string) *VehicleAverageCreate { + vac.mutation.SetID(s) + return vac +} + +// Mutation returns the VehicleAverageMutation object of the builder. +func (vac *VehicleAverageCreate) Mutation() *VehicleAverageMutation { + return vac.mutation +} + +// Save creates the VehicleAverage in the database. +func (vac *VehicleAverageCreate) Save(ctx context.Context) (*VehicleAverage, error) { + vac.defaults() + return withHooks(ctx, vac.sqlSave, vac.mutation, vac.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (vac *VehicleAverageCreate) SaveX(ctx context.Context) *VehicleAverage { + v, err := vac.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (vac *VehicleAverageCreate) Exec(ctx context.Context) error { + _, err := vac.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vac *VehicleAverageCreate) ExecX(ctx context.Context) { + if err := vac.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vac *VehicleAverageCreate) defaults() { + if _, ok := vac.mutation.CreatedAt(); !ok { + v := vehicleaverage.DefaultCreatedAt() + vac.mutation.SetCreatedAt(v) + } + if _, ok := vac.mutation.UpdatedAt(); !ok { + v := vehicleaverage.DefaultUpdatedAt() + vac.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (vac *VehicleAverageCreate) check() error { + if _, ok := vac.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "VehicleAverage.created_at"`)} + } + if _, ok := vac.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "VehicleAverage.updated_at"`)} + } + if _, ok := vac.mutation.Data(); !ok { + return &ValidationError{Name: "data", err: errors.New(`db: missing required field "VehicleAverage.data"`)} + } + return nil +} + +func (vac *VehicleAverageCreate) sqlSave(ctx context.Context) (*VehicleAverage, error) { + if err := vac.check(); err != nil { + return nil, err + } + _node, _spec := vac.createSpec() + if err := sqlgraph.CreateNode(ctx, vac.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected VehicleAverage.ID type: %T", _spec.ID.Value) + } + } + vac.mutation.id = &_node.ID + vac.mutation.done = true + return _node, nil +} + +func (vac *VehicleAverageCreate) createSpec() (*VehicleAverage, *sqlgraph.CreateSpec) { + var ( + _node = &VehicleAverage{config: vac.config} + _spec = sqlgraph.NewCreateSpec(vehicleaverage.Table, sqlgraph.NewFieldSpec(vehicleaverage.FieldID, field.TypeString)) + ) + if id, ok := vac.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := vac.mutation.CreatedAt(); ok { + _spec.SetField(vehicleaverage.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := vac.mutation.UpdatedAt(); ok { + _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := vac.mutation.Data(); ok { + _spec.SetField(vehicleaverage.FieldData, field.TypeJSON, value) + _node.Data = value + } + return _node, _spec +} + +// VehicleAverageCreateBulk is the builder for creating many VehicleAverage entities in bulk. +type VehicleAverageCreateBulk struct { + config + err error + builders []*VehicleAverageCreate +} + +// Save creates the VehicleAverage entities in the database. +func (vacb *VehicleAverageCreateBulk) Save(ctx context.Context) ([]*VehicleAverage, error) { + if vacb.err != nil { + return nil, vacb.err + } + specs := make([]*sqlgraph.CreateSpec, len(vacb.builders)) + nodes := make([]*VehicleAverage, len(vacb.builders)) + mutators := make([]Mutator, len(vacb.builders)) + for i := range vacb.builders { + func(i int, root context.Context) { + builder := vacb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*VehicleAverageMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, vacb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, vacb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, vacb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (vacb *VehicleAverageCreateBulk) SaveX(ctx context.Context) []*VehicleAverage { + v, err := vacb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (vacb *VehicleAverageCreateBulk) Exec(ctx context.Context) error { + _, err := vacb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vacb *VehicleAverageCreateBulk) ExecX(ctx context.Context) { + if err := vacb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/vehicleaverage_delete.go b/internal/database/ent/db/vehicleaverage_delete.go new file mode 100644 index 00000000..59f4953c --- /dev/null +++ b/internal/database/ent/db/vehicleaverage_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" +) + +// VehicleAverageDelete is the builder for deleting a VehicleAverage entity. +type VehicleAverageDelete struct { + config + hooks []Hook + mutation *VehicleAverageMutation +} + +// Where appends a list predicates to the VehicleAverageDelete builder. +func (vad *VehicleAverageDelete) Where(ps ...predicate.VehicleAverage) *VehicleAverageDelete { + vad.mutation.Where(ps...) + return vad +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (vad *VehicleAverageDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, vad.sqlExec, vad.mutation, vad.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (vad *VehicleAverageDelete) ExecX(ctx context.Context) int { + n, err := vad.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (vad *VehicleAverageDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(vehicleaverage.Table, sqlgraph.NewFieldSpec(vehicleaverage.FieldID, field.TypeString)) + if ps := vad.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, vad.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + vad.mutation.done = true + return affected, err +} + +// VehicleAverageDeleteOne is the builder for deleting a single VehicleAverage entity. +type VehicleAverageDeleteOne struct { + vad *VehicleAverageDelete +} + +// Where appends a list predicates to the VehicleAverageDelete builder. +func (vado *VehicleAverageDeleteOne) Where(ps ...predicate.VehicleAverage) *VehicleAverageDeleteOne { + vado.vad.mutation.Where(ps...) + return vado +} + +// Exec executes the deletion query. +func (vado *VehicleAverageDeleteOne) Exec(ctx context.Context) error { + n, err := vado.vad.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{vehicleaverage.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (vado *VehicleAverageDeleteOne) ExecX(ctx context.Context) { + if err := vado.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/vehicleaverage_query.go b/internal/database/ent/db/vehicleaverage_query.go new file mode 100644 index 00000000..67b533b4 --- /dev/null +++ b/internal/database/ent/db/vehicleaverage_query.go @@ -0,0 +1,526 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" +) + +// VehicleAverageQuery is the builder for querying VehicleAverage entities. +type VehicleAverageQuery struct { + config + ctx *QueryContext + order []vehicleaverage.OrderOption + inters []Interceptor + predicates []predicate.VehicleAverage + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the VehicleAverageQuery builder. +func (vaq *VehicleAverageQuery) Where(ps ...predicate.VehicleAverage) *VehicleAverageQuery { + vaq.predicates = append(vaq.predicates, ps...) + return vaq +} + +// Limit the number of records to be returned by this query. +func (vaq *VehicleAverageQuery) Limit(limit int) *VehicleAverageQuery { + vaq.ctx.Limit = &limit + return vaq +} + +// Offset to start from. +func (vaq *VehicleAverageQuery) Offset(offset int) *VehicleAverageQuery { + vaq.ctx.Offset = &offset + return vaq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (vaq *VehicleAverageQuery) Unique(unique bool) *VehicleAverageQuery { + vaq.ctx.Unique = &unique + return vaq +} + +// Order specifies how the records should be ordered. +func (vaq *VehicleAverageQuery) Order(o ...vehicleaverage.OrderOption) *VehicleAverageQuery { + vaq.order = append(vaq.order, o...) + return vaq +} + +// First returns the first VehicleAverage entity from the query. +// Returns a *NotFoundError when no VehicleAverage was found. +func (vaq *VehicleAverageQuery) First(ctx context.Context) (*VehicleAverage, error) { + nodes, err := vaq.Limit(1).All(setContextOp(ctx, vaq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{vehicleaverage.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (vaq *VehicleAverageQuery) FirstX(ctx context.Context) *VehicleAverage { + node, err := vaq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first VehicleAverage ID from the query. +// Returns a *NotFoundError when no VehicleAverage ID was found. +func (vaq *VehicleAverageQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = vaq.Limit(1).IDs(setContextOp(ctx, vaq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{vehicleaverage.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (vaq *VehicleAverageQuery) FirstIDX(ctx context.Context) string { + id, err := vaq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single VehicleAverage entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one VehicleAverage entity is found. +// Returns a *NotFoundError when no VehicleAverage entities are found. +func (vaq *VehicleAverageQuery) Only(ctx context.Context) (*VehicleAverage, error) { + nodes, err := vaq.Limit(2).All(setContextOp(ctx, vaq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{vehicleaverage.Label} + default: + return nil, &NotSingularError{vehicleaverage.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (vaq *VehicleAverageQuery) OnlyX(ctx context.Context) *VehicleAverage { + node, err := vaq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only VehicleAverage ID in the query. +// Returns a *NotSingularError when more than one VehicleAverage ID is found. +// Returns a *NotFoundError when no entities are found. +func (vaq *VehicleAverageQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = vaq.Limit(2).IDs(setContextOp(ctx, vaq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{vehicleaverage.Label} + default: + err = &NotSingularError{vehicleaverage.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (vaq *VehicleAverageQuery) OnlyIDX(ctx context.Context) string { + id, err := vaq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of VehicleAverages. +func (vaq *VehicleAverageQuery) All(ctx context.Context) ([]*VehicleAverage, error) { + ctx = setContextOp(ctx, vaq.ctx, "All") + if err := vaq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*VehicleAverage, *VehicleAverageQuery]() + return withInterceptors[[]*VehicleAverage](ctx, vaq, qr, vaq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (vaq *VehicleAverageQuery) AllX(ctx context.Context) []*VehicleAverage { + nodes, err := vaq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of VehicleAverage IDs. +func (vaq *VehicleAverageQuery) IDs(ctx context.Context) (ids []string, err error) { + if vaq.ctx.Unique == nil && vaq.path != nil { + vaq.Unique(true) + } + ctx = setContextOp(ctx, vaq.ctx, "IDs") + if err = vaq.Select(vehicleaverage.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (vaq *VehicleAverageQuery) IDsX(ctx context.Context) []string { + ids, err := vaq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (vaq *VehicleAverageQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, vaq.ctx, "Count") + if err := vaq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, vaq, querierCount[*VehicleAverageQuery](), vaq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (vaq *VehicleAverageQuery) CountX(ctx context.Context) int { + count, err := vaq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (vaq *VehicleAverageQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, vaq.ctx, "Exist") + switch _, err := vaq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (vaq *VehicleAverageQuery) ExistX(ctx context.Context) bool { + exist, err := vaq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the VehicleAverageQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (vaq *VehicleAverageQuery) Clone() *VehicleAverageQuery { + if vaq == nil { + return nil + } + return &VehicleAverageQuery{ + config: vaq.config, + ctx: vaq.ctx.Clone(), + order: append([]vehicleaverage.OrderOption{}, vaq.order...), + inters: append([]Interceptor{}, vaq.inters...), + predicates: append([]predicate.VehicleAverage{}, vaq.predicates...), + // clone intermediate query. + sql: vaq.sql.Clone(), + path: vaq.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.VehicleAverage.Query(). +// GroupBy(vehicleaverage.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (vaq *VehicleAverageQuery) GroupBy(field string, fields ...string) *VehicleAverageGroupBy { + vaq.ctx.Fields = append([]string{field}, fields...) + grbuild := &VehicleAverageGroupBy{build: vaq} + grbuild.flds = &vaq.ctx.Fields + grbuild.label = vehicleaverage.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.VehicleAverage.Query(). +// Select(vehicleaverage.FieldCreatedAt). +// Scan(ctx, &v) +func (vaq *VehicleAverageQuery) Select(fields ...string) *VehicleAverageSelect { + vaq.ctx.Fields = append(vaq.ctx.Fields, fields...) + sbuild := &VehicleAverageSelect{VehicleAverageQuery: vaq} + sbuild.label = vehicleaverage.Label + sbuild.flds, sbuild.scan = &vaq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a VehicleAverageSelect configured with the given aggregations. +func (vaq *VehicleAverageQuery) Aggregate(fns ...AggregateFunc) *VehicleAverageSelect { + return vaq.Select().Aggregate(fns...) +} + +func (vaq *VehicleAverageQuery) prepareQuery(ctx context.Context) error { + for _, inter := range vaq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, vaq); err != nil { + return err + } + } + } + for _, f := range vaq.ctx.Fields { + if !vehicleaverage.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if vaq.path != nil { + prev, err := vaq.path(ctx) + if err != nil { + return err + } + vaq.sql = prev + } + return nil +} + +func (vaq *VehicleAverageQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*VehicleAverage, error) { + var ( + nodes = []*VehicleAverage{} + _spec = vaq.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*VehicleAverage).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &VehicleAverage{config: vaq.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, vaq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (vaq *VehicleAverageQuery) sqlCount(ctx context.Context) (int, error) { + _spec := vaq.querySpec() + _spec.Node.Columns = vaq.ctx.Fields + if len(vaq.ctx.Fields) > 0 { + _spec.Unique = vaq.ctx.Unique != nil && *vaq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, vaq.driver, _spec) +} + +func (vaq *VehicleAverageQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(vehicleaverage.Table, vehicleaverage.Columns, sqlgraph.NewFieldSpec(vehicleaverage.FieldID, field.TypeString)) + _spec.From = vaq.sql + if unique := vaq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if vaq.path != nil { + _spec.Unique = true + } + if fields := vaq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, vehicleaverage.FieldID) + for i := range fields { + if fields[i] != vehicleaverage.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := vaq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := vaq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := vaq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := vaq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (vaq *VehicleAverageQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(vaq.driver.Dialect()) + t1 := builder.Table(vehicleaverage.Table) + columns := vaq.ctx.Fields + if len(columns) == 0 { + columns = vehicleaverage.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if vaq.sql != nil { + selector = vaq.sql + selector.Select(selector.Columns(columns...)...) + } + if vaq.ctx.Unique != nil && *vaq.ctx.Unique { + selector.Distinct() + } + for _, p := range vaq.predicates { + p(selector) + } + for _, p := range vaq.order { + p(selector) + } + if offset := vaq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := vaq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// VehicleAverageGroupBy is the group-by builder for VehicleAverage entities. +type VehicleAverageGroupBy struct { + selector + build *VehicleAverageQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (vagb *VehicleAverageGroupBy) Aggregate(fns ...AggregateFunc) *VehicleAverageGroupBy { + vagb.fns = append(vagb.fns, fns...) + return vagb +} + +// Scan applies the selector query and scans the result into the given value. +func (vagb *VehicleAverageGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, vagb.build.ctx, "GroupBy") + if err := vagb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*VehicleAverageQuery, *VehicleAverageGroupBy](ctx, vagb.build, vagb, vagb.build.inters, v) +} + +func (vagb *VehicleAverageGroupBy) sqlScan(ctx context.Context, root *VehicleAverageQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(vagb.fns)) + for _, fn := range vagb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*vagb.flds)+len(vagb.fns)) + for _, f := range *vagb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*vagb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := vagb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// VehicleAverageSelect is the builder for selecting fields of VehicleAverage entities. +type VehicleAverageSelect struct { + *VehicleAverageQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (vas *VehicleAverageSelect) Aggregate(fns ...AggregateFunc) *VehicleAverageSelect { + vas.fns = append(vas.fns, fns...) + return vas +} + +// Scan applies the selector query and scans the result into the given value. +func (vas *VehicleAverageSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, vas.ctx, "Select") + if err := vas.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*VehicleAverageQuery, *VehicleAverageSelect](ctx, vas.VehicleAverageQuery, vas, vas.inters, v) +} + +func (vas *VehicleAverageSelect) sqlScan(ctx context.Context, root *VehicleAverageQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(vas.fns)) + for _, fn := range vas.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*vas.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := vas.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/vehicleaverage_update.go b/internal/database/ent/db/vehicleaverage_update.go new file mode 100644 index 00000000..787df205 --- /dev/null +++ b/internal/database/ent/db/vehicleaverage_update.go @@ -0,0 +1,250 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// VehicleAverageUpdate is the builder for updating VehicleAverage entities. +type VehicleAverageUpdate struct { + config + hooks []Hook + mutation *VehicleAverageMutation +} + +// Where appends a list predicates to the VehicleAverageUpdate builder. +func (vau *VehicleAverageUpdate) Where(ps ...predicate.VehicleAverage) *VehicleAverageUpdate { + vau.mutation.Where(ps...) + return vau +} + +// SetUpdatedAt sets the "updated_at" field. +func (vau *VehicleAverageUpdate) SetUpdatedAt(i int) *VehicleAverageUpdate { + vau.mutation.ResetUpdatedAt() + vau.mutation.SetUpdatedAt(i) + return vau +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (vau *VehicleAverageUpdate) AddUpdatedAt(i int) *VehicleAverageUpdate { + vau.mutation.AddUpdatedAt(i) + return vau +} + +// SetData sets the "data" field. +func (vau *VehicleAverageUpdate) SetData(mf map[string]frame.StatsFrame) *VehicleAverageUpdate { + vau.mutation.SetData(mf) + return vau +} + +// Mutation returns the VehicleAverageMutation object of the builder. +func (vau *VehicleAverageUpdate) Mutation() *VehicleAverageMutation { + return vau.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (vau *VehicleAverageUpdate) Save(ctx context.Context) (int, error) { + vau.defaults() + return withHooks(ctx, vau.sqlSave, vau.mutation, vau.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (vau *VehicleAverageUpdate) SaveX(ctx context.Context) int { + affected, err := vau.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (vau *VehicleAverageUpdate) Exec(ctx context.Context) error { + _, err := vau.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vau *VehicleAverageUpdate) ExecX(ctx context.Context) { + if err := vau.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vau *VehicleAverageUpdate) defaults() { + if _, ok := vau.mutation.UpdatedAt(); !ok { + v := vehicleaverage.UpdateDefaultUpdatedAt() + vau.mutation.SetUpdatedAt(v) + } +} + +func (vau *VehicleAverageUpdate) sqlSave(ctx context.Context) (n int, err error) { + _spec := sqlgraph.NewUpdateSpec(vehicleaverage.Table, vehicleaverage.Columns, sqlgraph.NewFieldSpec(vehicleaverage.FieldID, field.TypeString)) + if ps := vau.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := vau.mutation.UpdatedAt(); ok { + _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vau.mutation.AddedUpdatedAt(); ok { + _spec.AddField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vau.mutation.Data(); ok { + _spec.SetField(vehicleaverage.FieldData, field.TypeJSON, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, vau.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{vehicleaverage.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + vau.mutation.done = true + return n, nil +} + +// VehicleAverageUpdateOne is the builder for updating a single VehicleAverage entity. +type VehicleAverageUpdateOne struct { + config + fields []string + hooks []Hook + mutation *VehicleAverageMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (vauo *VehicleAverageUpdateOne) SetUpdatedAt(i int) *VehicleAverageUpdateOne { + vauo.mutation.ResetUpdatedAt() + vauo.mutation.SetUpdatedAt(i) + return vauo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (vauo *VehicleAverageUpdateOne) AddUpdatedAt(i int) *VehicleAverageUpdateOne { + vauo.mutation.AddUpdatedAt(i) + return vauo +} + +// SetData sets the "data" field. +func (vauo *VehicleAverageUpdateOne) SetData(mf map[string]frame.StatsFrame) *VehicleAverageUpdateOne { + vauo.mutation.SetData(mf) + return vauo +} + +// Mutation returns the VehicleAverageMutation object of the builder. +func (vauo *VehicleAverageUpdateOne) Mutation() *VehicleAverageMutation { + return vauo.mutation +} + +// Where appends a list predicates to the VehicleAverageUpdate builder. +func (vauo *VehicleAverageUpdateOne) Where(ps ...predicate.VehicleAverage) *VehicleAverageUpdateOne { + vauo.mutation.Where(ps...) + return vauo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (vauo *VehicleAverageUpdateOne) Select(field string, fields ...string) *VehicleAverageUpdateOne { + vauo.fields = append([]string{field}, fields...) + return vauo +} + +// Save executes the query and returns the updated VehicleAverage entity. +func (vauo *VehicleAverageUpdateOne) Save(ctx context.Context) (*VehicleAverage, error) { + vauo.defaults() + return withHooks(ctx, vauo.sqlSave, vauo.mutation, vauo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (vauo *VehicleAverageUpdateOne) SaveX(ctx context.Context) *VehicleAverage { + node, err := vauo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (vauo *VehicleAverageUpdateOne) Exec(ctx context.Context) error { + _, err := vauo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vauo *VehicleAverageUpdateOne) ExecX(ctx context.Context) { + if err := vauo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vauo *VehicleAverageUpdateOne) defaults() { + if _, ok := vauo.mutation.UpdatedAt(); !ok { + v := vehicleaverage.UpdateDefaultUpdatedAt() + vauo.mutation.SetUpdatedAt(v) + } +} + +func (vauo *VehicleAverageUpdateOne) sqlSave(ctx context.Context) (_node *VehicleAverage, err error) { + _spec := sqlgraph.NewUpdateSpec(vehicleaverage.Table, vehicleaverage.Columns, sqlgraph.NewFieldSpec(vehicleaverage.FieldID, field.TypeString)) + id, ok := vauo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "VehicleAverage.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := vauo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, vehicleaverage.FieldID) + for _, f := range fields { + if !vehicleaverage.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != vehicleaverage.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := vauo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := vauo.mutation.UpdatedAt(); ok { + _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vauo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vauo.mutation.Data(); ok { + _spec.SetField(vehicleaverage.FieldData, field.TypeJSON, value) + } + _node = &VehicleAverage{config: vauo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, vauo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{vehicleaverage.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + vauo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/vehiclesnapshot.go b/internal/database/ent/db/vehiclesnapshot.go new file mode 100644 index 00000000..ae0d2d10 --- /dev/null +++ b/internal/database/ent/db/vehiclesnapshot.go @@ -0,0 +1,227 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// VehicleSnapshot is the model entity for the VehicleSnapshot schema. +type VehicleSnapshot struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int `json:"updated_at,omitempty"` + // Type holds the value of the "type" field. + Type models.SnapshotType `json:"type,omitempty"` + // AccountID holds the value of the "account_id" field. + AccountID string `json:"account_id,omitempty"` + // VehicleID holds the value of the "vehicle_id" field. + VehicleID string `json:"vehicle_id,omitempty"` + // ReferenceID holds the value of the "reference_id" field. + ReferenceID string `json:"reference_id,omitempty"` + // Battles holds the value of the "battles" field. + Battles int `json:"battles,omitempty"` + // LastBattleTime holds the value of the "last_battle_time" field. + LastBattleTime int `json:"last_battle_time,omitempty"` + // Frame holds the value of the "frame" field. + Frame frame.StatsFrame `json:"frame,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the VehicleSnapshotQuery when eager-loading is set. + Edges VehicleSnapshotEdges `json:"edges"` + selectValues sql.SelectValues +} + +// VehicleSnapshotEdges holds the relations/edges for other nodes in the graph. +type VehicleSnapshotEdges struct { + // Account holds the value of the account edge. + Account *Account `json:"account,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// AccountOrErr returns the Account value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e VehicleSnapshotEdges) AccountOrErr() (*Account, error) { + if e.Account != nil { + return e.Account, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: account.Label} + } + return nil, &NotLoadedError{edge: "account"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*VehicleSnapshot) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case vehiclesnapshot.FieldFrame: + values[i] = new([]byte) + case vehiclesnapshot.FieldCreatedAt, vehiclesnapshot.FieldUpdatedAt, vehiclesnapshot.FieldBattles, vehiclesnapshot.FieldLastBattleTime: + values[i] = new(sql.NullInt64) + case vehiclesnapshot.FieldID, vehiclesnapshot.FieldType, vehiclesnapshot.FieldAccountID, vehiclesnapshot.FieldVehicleID, vehiclesnapshot.FieldReferenceID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the VehicleSnapshot fields. +func (vs *VehicleSnapshot) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case vehiclesnapshot.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + vs.ID = value.String + } + case vehiclesnapshot.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + vs.CreatedAt = int(value.Int64) + } + case vehiclesnapshot.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + vs.UpdatedAt = int(value.Int64) + } + case vehiclesnapshot.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + vs.Type = models.SnapshotType(value.String) + } + case vehiclesnapshot.FieldAccountID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field account_id", values[i]) + } else if value.Valid { + vs.AccountID = value.String + } + case vehiclesnapshot.FieldVehicleID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field vehicle_id", values[i]) + } else if value.Valid { + vs.VehicleID = value.String + } + case vehiclesnapshot.FieldReferenceID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reference_id", values[i]) + } else if value.Valid { + vs.ReferenceID = value.String + } + case vehiclesnapshot.FieldBattles: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field battles", values[i]) + } else if value.Valid { + vs.Battles = int(value.Int64) + } + case vehiclesnapshot.FieldLastBattleTime: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) + } else if value.Valid { + vs.LastBattleTime = int(value.Int64) + } + case vehiclesnapshot.FieldFrame: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field frame", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &vs.Frame); err != nil { + return fmt.Errorf("unmarshal field frame: %w", err) + } + } + default: + vs.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the VehicleSnapshot. +// This includes values selected through modifiers, order, etc. +func (vs *VehicleSnapshot) Value(name string) (ent.Value, error) { + return vs.selectValues.Get(name) +} + +// QueryAccount queries the "account" edge of the VehicleSnapshot entity. +func (vs *VehicleSnapshot) QueryAccount() *AccountQuery { + return NewVehicleSnapshotClient(vs.config).QueryAccount(vs) +} + +// Update returns a builder for updating this VehicleSnapshot. +// Note that you need to call VehicleSnapshot.Unwrap() before calling this method if this VehicleSnapshot +// was returned from a transaction, and the transaction was committed or rolled back. +func (vs *VehicleSnapshot) Update() *VehicleSnapshotUpdateOne { + return NewVehicleSnapshotClient(vs.config).UpdateOne(vs) +} + +// Unwrap unwraps the VehicleSnapshot entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (vs *VehicleSnapshot) Unwrap() *VehicleSnapshot { + _tx, ok := vs.config.driver.(*txDriver) + if !ok { + panic("db: VehicleSnapshot is not a transactional entity") + } + vs.config.driver = _tx.drv + return vs +} + +// String implements the fmt.Stringer. +func (vs *VehicleSnapshot) String() string { + var builder strings.Builder + builder.WriteString("VehicleSnapshot(") + builder.WriteString(fmt.Sprintf("id=%v, ", vs.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", vs.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", vs.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("type=") + builder.WriteString(fmt.Sprintf("%v", vs.Type)) + builder.WriteString(", ") + builder.WriteString("account_id=") + builder.WriteString(vs.AccountID) + builder.WriteString(", ") + builder.WriteString("vehicle_id=") + builder.WriteString(vs.VehicleID) + builder.WriteString(", ") + builder.WriteString("reference_id=") + builder.WriteString(vs.ReferenceID) + builder.WriteString(", ") + builder.WriteString("battles=") + builder.WriteString(fmt.Sprintf("%v", vs.Battles)) + builder.WriteString(", ") + builder.WriteString("last_battle_time=") + builder.WriteString(fmt.Sprintf("%v", vs.LastBattleTime)) + builder.WriteString(", ") + builder.WriteString("frame=") + builder.WriteString(fmt.Sprintf("%v", vs.Frame)) + builder.WriteByte(')') + return builder.String() +} + +// VehicleSnapshots is a parsable slice of VehicleSnapshot. +type VehicleSnapshots []*VehicleSnapshot diff --git a/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go b/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go new file mode 100644 index 00000000..aa8d81ed --- /dev/null +++ b/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go @@ -0,0 +1,160 @@ +// Code generated by ent, DO NOT EDIT. + +package vehiclesnapshot + +import ( + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/models" +) + +const ( + // Label holds the string label denoting the vehiclesnapshot type in the database. + Label = "vehicle_snapshot" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldAccountID holds the string denoting the account_id field in the database. + FieldAccountID = "account_id" + // FieldVehicleID holds the string denoting the vehicle_id field in the database. + FieldVehicleID = "vehicle_id" + // FieldReferenceID holds the string denoting the reference_id field in the database. + FieldReferenceID = "reference_id" + // FieldBattles holds the string denoting the battles field in the database. + FieldBattles = "battles" + // FieldLastBattleTime holds the string denoting the last_battle_time field in the database. + FieldLastBattleTime = "last_battle_time" + // FieldFrame holds the string denoting the frame field in the database. + FieldFrame = "frame" + // EdgeAccount holds the string denoting the account edge name in mutations. + EdgeAccount = "account" + // Table holds the table name of the vehiclesnapshot in the database. + Table = "vehicle_snapshots" + // AccountTable is the table that holds the account relation/edge. + AccountTable = "vehicle_snapshots" + // AccountInverseTable is the table name for the Account entity. + // It exists in this package in order to avoid circular dependency with the "account" package. + AccountInverseTable = "accounts" + // AccountColumn is the table column denoting the account relation/edge. + AccountColumn = "account_id" +) + +// Columns holds all SQL columns for vehiclesnapshot fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldType, + FieldAccountID, + FieldVehicleID, + FieldReferenceID, + FieldBattles, + FieldLastBattleTime, + FieldFrame, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int + // AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. + AccountIDValidator func(string) error + // VehicleIDValidator is a validator for the "vehicle_id" field. It is called by the builders before save. + VehicleIDValidator func(string) error + // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + ReferenceIDValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save. +func TypeValidator(_type models.SnapshotType) error { + switch _type { + case "live", "daily": + return nil + default: + return fmt.Errorf("vehiclesnapshot: invalid enum value for type field: %q", _type) + } +} + +// OrderOption defines the ordering options for the VehicleSnapshot queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByAccountID orders the results by the account_id field. +func ByAccountID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAccountID, opts...).ToFunc() +} + +// ByVehicleID orders the results by the vehicle_id field. +func ByVehicleID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldVehicleID, opts...).ToFunc() +} + +// ByReferenceID orders the results by the reference_id field. +func ByReferenceID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReferenceID, opts...).ToFunc() +} + +// ByBattles orders the results by the battles field. +func ByBattles(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldBattles, opts...).ToFunc() +} + +// ByLastBattleTime orders the results by the last_battle_time field. +func ByLastBattleTime(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLastBattleTime, opts...).ToFunc() +} + +// ByAccountField orders the results by account field. +func ByAccountField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newAccountStep(), sql.OrderByField(field, opts...)) + } +} +func newAccountStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(AccountInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, AccountTable, AccountColumn), + ) +} diff --git a/internal/database/ent/db/vehiclesnapshot/where.go b/internal/database/ent/db/vehiclesnapshot/where.go new file mode 100644 index 00000000..758156fe --- /dev/null +++ b/internal/database/ent/db/vehiclesnapshot/where.go @@ -0,0 +1,523 @@ +// Code generated by ent, DO NOT EDIT. + +package vehiclesnapshot + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// AccountID applies equality check predicate on the "account_id" field. It's identical to AccountIDEQ. +func AccountID(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldAccountID, v)) +} + +// VehicleID applies equality check predicate on the "vehicle_id" field. It's identical to VehicleIDEQ. +func VehicleID(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldVehicleID, v)) +} + +// ReferenceID applies equality check predicate on the "reference_id" field. It's identical to ReferenceIDEQ. +func ReferenceID(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldReferenceID, v)) +} + +// Battles applies equality check predicate on the "battles" field. It's identical to BattlesEQ. +func Battles(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldBattles, v)) +} + +// LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. +func LastBattleTime(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v models.SnapshotType) predicate.VehicleSnapshot { + vc := v + return predicate.VehicleSnapshot(sql.FieldEQ(FieldType, vc)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v models.SnapshotType) predicate.VehicleSnapshot { + vc := v + return predicate.VehicleSnapshot(sql.FieldNEQ(FieldType, vc)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...models.SnapshotType) predicate.VehicleSnapshot { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.VehicleSnapshot(sql.FieldIn(FieldType, v...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...models.SnapshotType) predicate.VehicleSnapshot { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.VehicleSnapshot(sql.FieldNotIn(FieldType, v...)) +} + +// AccountIDEQ applies the EQ predicate on the "account_id" field. +func AccountIDEQ(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldAccountID, v)) +} + +// AccountIDNEQ applies the NEQ predicate on the "account_id" field. +func AccountIDNEQ(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNEQ(FieldAccountID, v)) +} + +// AccountIDIn applies the In predicate on the "account_id" field. +func AccountIDIn(vs ...string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldIn(FieldAccountID, vs...)) +} + +// AccountIDNotIn applies the NotIn predicate on the "account_id" field. +func AccountIDNotIn(vs ...string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNotIn(FieldAccountID, vs...)) +} + +// AccountIDGT applies the GT predicate on the "account_id" field. +func AccountIDGT(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGT(FieldAccountID, v)) +} + +// AccountIDGTE applies the GTE predicate on the "account_id" field. +func AccountIDGTE(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGTE(FieldAccountID, v)) +} + +// AccountIDLT applies the LT predicate on the "account_id" field. +func AccountIDLT(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLT(FieldAccountID, v)) +} + +// AccountIDLTE applies the LTE predicate on the "account_id" field. +func AccountIDLTE(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLTE(FieldAccountID, v)) +} + +// AccountIDContains applies the Contains predicate on the "account_id" field. +func AccountIDContains(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldContains(FieldAccountID, v)) +} + +// AccountIDHasPrefix applies the HasPrefix predicate on the "account_id" field. +func AccountIDHasPrefix(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldHasPrefix(FieldAccountID, v)) +} + +// AccountIDHasSuffix applies the HasSuffix predicate on the "account_id" field. +func AccountIDHasSuffix(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldHasSuffix(FieldAccountID, v)) +} + +// AccountIDEqualFold applies the EqualFold predicate on the "account_id" field. +func AccountIDEqualFold(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEqualFold(FieldAccountID, v)) +} + +// AccountIDContainsFold applies the ContainsFold predicate on the "account_id" field. +func AccountIDContainsFold(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldContainsFold(FieldAccountID, v)) +} + +// VehicleIDEQ applies the EQ predicate on the "vehicle_id" field. +func VehicleIDEQ(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldVehicleID, v)) +} + +// VehicleIDNEQ applies the NEQ predicate on the "vehicle_id" field. +func VehicleIDNEQ(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNEQ(FieldVehicleID, v)) +} + +// VehicleIDIn applies the In predicate on the "vehicle_id" field. +func VehicleIDIn(vs ...string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldIn(FieldVehicleID, vs...)) +} + +// VehicleIDNotIn applies the NotIn predicate on the "vehicle_id" field. +func VehicleIDNotIn(vs ...string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNotIn(FieldVehicleID, vs...)) +} + +// VehicleIDGT applies the GT predicate on the "vehicle_id" field. +func VehicleIDGT(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGT(FieldVehicleID, v)) +} + +// VehicleIDGTE applies the GTE predicate on the "vehicle_id" field. +func VehicleIDGTE(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGTE(FieldVehicleID, v)) +} + +// VehicleIDLT applies the LT predicate on the "vehicle_id" field. +func VehicleIDLT(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLT(FieldVehicleID, v)) +} + +// VehicleIDLTE applies the LTE predicate on the "vehicle_id" field. +func VehicleIDLTE(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLTE(FieldVehicleID, v)) +} + +// VehicleIDContains applies the Contains predicate on the "vehicle_id" field. +func VehicleIDContains(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldContains(FieldVehicleID, v)) +} + +// VehicleIDHasPrefix applies the HasPrefix predicate on the "vehicle_id" field. +func VehicleIDHasPrefix(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldHasPrefix(FieldVehicleID, v)) +} + +// VehicleIDHasSuffix applies the HasSuffix predicate on the "vehicle_id" field. +func VehicleIDHasSuffix(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldHasSuffix(FieldVehicleID, v)) +} + +// VehicleIDEqualFold applies the EqualFold predicate on the "vehicle_id" field. +func VehicleIDEqualFold(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEqualFold(FieldVehicleID, v)) +} + +// VehicleIDContainsFold applies the ContainsFold predicate on the "vehicle_id" field. +func VehicleIDContainsFold(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldContainsFold(FieldVehicleID, v)) +} + +// ReferenceIDEQ applies the EQ predicate on the "reference_id" field. +func ReferenceIDEQ(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldReferenceID, v)) +} + +// ReferenceIDNEQ applies the NEQ predicate on the "reference_id" field. +func ReferenceIDNEQ(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNEQ(FieldReferenceID, v)) +} + +// ReferenceIDIn applies the In predicate on the "reference_id" field. +func ReferenceIDIn(vs ...string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldIn(FieldReferenceID, vs...)) +} + +// ReferenceIDNotIn applies the NotIn predicate on the "reference_id" field. +func ReferenceIDNotIn(vs ...string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNotIn(FieldReferenceID, vs...)) +} + +// ReferenceIDGT applies the GT predicate on the "reference_id" field. +func ReferenceIDGT(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGT(FieldReferenceID, v)) +} + +// ReferenceIDGTE applies the GTE predicate on the "reference_id" field. +func ReferenceIDGTE(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGTE(FieldReferenceID, v)) +} + +// ReferenceIDLT applies the LT predicate on the "reference_id" field. +func ReferenceIDLT(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLT(FieldReferenceID, v)) +} + +// ReferenceIDLTE applies the LTE predicate on the "reference_id" field. +func ReferenceIDLTE(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLTE(FieldReferenceID, v)) +} + +// ReferenceIDContains applies the Contains predicate on the "reference_id" field. +func ReferenceIDContains(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldContains(FieldReferenceID, v)) +} + +// ReferenceIDHasPrefix applies the HasPrefix predicate on the "reference_id" field. +func ReferenceIDHasPrefix(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldHasPrefix(FieldReferenceID, v)) +} + +// ReferenceIDHasSuffix applies the HasSuffix predicate on the "reference_id" field. +func ReferenceIDHasSuffix(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldHasSuffix(FieldReferenceID, v)) +} + +// ReferenceIDEqualFold applies the EqualFold predicate on the "reference_id" field. +func ReferenceIDEqualFold(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEqualFold(FieldReferenceID, v)) +} + +// ReferenceIDContainsFold applies the ContainsFold predicate on the "reference_id" field. +func ReferenceIDContainsFold(v string) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldContainsFold(FieldReferenceID, v)) +} + +// BattlesEQ applies the EQ predicate on the "battles" field. +func BattlesEQ(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldBattles, v)) +} + +// BattlesNEQ applies the NEQ predicate on the "battles" field. +func BattlesNEQ(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNEQ(FieldBattles, v)) +} + +// BattlesIn applies the In predicate on the "battles" field. +func BattlesIn(vs ...int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldIn(FieldBattles, vs...)) +} + +// BattlesNotIn applies the NotIn predicate on the "battles" field. +func BattlesNotIn(vs ...int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNotIn(FieldBattles, vs...)) +} + +// BattlesGT applies the GT predicate on the "battles" field. +func BattlesGT(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGT(FieldBattles, v)) +} + +// BattlesGTE applies the GTE predicate on the "battles" field. +func BattlesGTE(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGTE(FieldBattles, v)) +} + +// BattlesLT applies the LT predicate on the "battles" field. +func BattlesLT(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLT(FieldBattles, v)) +} + +// BattlesLTE applies the LTE predicate on the "battles" field. +func BattlesLTE(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLTE(FieldBattles, v)) +} + +// LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. +func LastBattleTimeEQ(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) +} + +// LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. +func LastBattleTimeNEQ(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNEQ(FieldLastBattleTime, v)) +} + +// LastBattleTimeIn applies the In predicate on the "last_battle_time" field. +func LastBattleTimeIn(vs ...int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldIn(FieldLastBattleTime, vs...)) +} + +// LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. +func LastBattleTimeNotIn(vs ...int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldNotIn(FieldLastBattleTime, vs...)) +} + +// LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. +func LastBattleTimeGT(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGT(FieldLastBattleTime, v)) +} + +// LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. +func LastBattleTimeGTE(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldGTE(FieldLastBattleTime, v)) +} + +// LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. +func LastBattleTimeLT(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLT(FieldLastBattleTime, v)) +} + +// LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. +func LastBattleTimeLTE(v int) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.FieldLTE(FieldLastBattleTime, v)) +} + +// HasAccount applies the HasEdge predicate on the "account" edge. +func HasAccount() predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, AccountTable, AccountColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasAccountWith applies the HasEdge predicate on the "account" edge with a given conditions (other predicates). +func HasAccountWith(preds ...predicate.Account) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(func(s *sql.Selector) { + step := newAccountStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.VehicleSnapshot) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.VehicleSnapshot) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.VehicleSnapshot) predicate.VehicleSnapshot { + return predicate.VehicleSnapshot(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/vehiclesnapshot_create.go b/internal/database/ent/db/vehiclesnapshot_create.go new file mode 100644 index 00000000..6120d27b --- /dev/null +++ b/internal/database/ent/db/vehiclesnapshot_create.go @@ -0,0 +1,384 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// VehicleSnapshotCreate is the builder for creating a VehicleSnapshot entity. +type VehicleSnapshotCreate struct { + config + mutation *VehicleSnapshotMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (vsc *VehicleSnapshotCreate) SetCreatedAt(i int) *VehicleSnapshotCreate { + vsc.mutation.SetCreatedAt(i) + return vsc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (vsc *VehicleSnapshotCreate) SetNillableCreatedAt(i *int) *VehicleSnapshotCreate { + if i != nil { + vsc.SetCreatedAt(*i) + } + return vsc +} + +// SetUpdatedAt sets the "updated_at" field. +func (vsc *VehicleSnapshotCreate) SetUpdatedAt(i int) *VehicleSnapshotCreate { + vsc.mutation.SetUpdatedAt(i) + return vsc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (vsc *VehicleSnapshotCreate) SetNillableUpdatedAt(i *int) *VehicleSnapshotCreate { + if i != nil { + vsc.SetUpdatedAt(*i) + } + return vsc +} + +// SetType sets the "type" field. +func (vsc *VehicleSnapshotCreate) SetType(mt models.SnapshotType) *VehicleSnapshotCreate { + vsc.mutation.SetType(mt) + return vsc +} + +// SetAccountID sets the "account_id" field. +func (vsc *VehicleSnapshotCreate) SetAccountID(s string) *VehicleSnapshotCreate { + vsc.mutation.SetAccountID(s) + return vsc +} + +// SetVehicleID sets the "vehicle_id" field. +func (vsc *VehicleSnapshotCreate) SetVehicleID(s string) *VehicleSnapshotCreate { + vsc.mutation.SetVehicleID(s) + return vsc +} + +// SetReferenceID sets the "reference_id" field. +func (vsc *VehicleSnapshotCreate) SetReferenceID(s string) *VehicleSnapshotCreate { + vsc.mutation.SetReferenceID(s) + return vsc +} + +// SetBattles sets the "battles" field. +func (vsc *VehicleSnapshotCreate) SetBattles(i int) *VehicleSnapshotCreate { + vsc.mutation.SetBattles(i) + return vsc +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (vsc *VehicleSnapshotCreate) SetLastBattleTime(i int) *VehicleSnapshotCreate { + vsc.mutation.SetLastBattleTime(i) + return vsc +} + +// SetFrame sets the "frame" field. +func (vsc *VehicleSnapshotCreate) SetFrame(ff frame.StatsFrame) *VehicleSnapshotCreate { + vsc.mutation.SetFrame(ff) + return vsc +} + +// SetID sets the "id" field. +func (vsc *VehicleSnapshotCreate) SetID(s string) *VehicleSnapshotCreate { + vsc.mutation.SetID(s) + return vsc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (vsc *VehicleSnapshotCreate) SetNillableID(s *string) *VehicleSnapshotCreate { + if s != nil { + vsc.SetID(*s) + } + return vsc +} + +// SetAccount sets the "account" edge to the Account entity. +func (vsc *VehicleSnapshotCreate) SetAccount(a *Account) *VehicleSnapshotCreate { + return vsc.SetAccountID(a.ID) +} + +// Mutation returns the VehicleSnapshotMutation object of the builder. +func (vsc *VehicleSnapshotCreate) Mutation() *VehicleSnapshotMutation { + return vsc.mutation +} + +// Save creates the VehicleSnapshot in the database. +func (vsc *VehicleSnapshotCreate) Save(ctx context.Context) (*VehicleSnapshot, error) { + vsc.defaults() + return withHooks(ctx, vsc.sqlSave, vsc.mutation, vsc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (vsc *VehicleSnapshotCreate) SaveX(ctx context.Context) *VehicleSnapshot { + v, err := vsc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (vsc *VehicleSnapshotCreate) Exec(ctx context.Context) error { + _, err := vsc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vsc *VehicleSnapshotCreate) ExecX(ctx context.Context) { + if err := vsc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vsc *VehicleSnapshotCreate) defaults() { + if _, ok := vsc.mutation.CreatedAt(); !ok { + v := vehiclesnapshot.DefaultCreatedAt() + vsc.mutation.SetCreatedAt(v) + } + if _, ok := vsc.mutation.UpdatedAt(); !ok { + v := vehiclesnapshot.DefaultUpdatedAt() + vsc.mutation.SetUpdatedAt(v) + } + if _, ok := vsc.mutation.ID(); !ok { + v := vehiclesnapshot.DefaultID() + vsc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (vsc *VehicleSnapshotCreate) check() error { + if _, ok := vsc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "VehicleSnapshot.created_at"`)} + } + if _, ok := vsc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "VehicleSnapshot.updated_at"`)} + } + if _, ok := vsc.mutation.GetType(); !ok { + return &ValidationError{Name: "type", err: errors.New(`db: missing required field "VehicleSnapshot.type"`)} + } + if v, ok := vsc.mutation.GetType(); ok { + if err := vehiclesnapshot.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "VehicleSnapshot.type": %w`, err)} + } + } + if _, ok := vsc.mutation.AccountID(); !ok { + return &ValidationError{Name: "account_id", err: errors.New(`db: missing required field "VehicleSnapshot.account_id"`)} + } + if v, ok := vsc.mutation.AccountID(); ok { + if err := vehiclesnapshot.AccountIDValidator(v); err != nil { + return &ValidationError{Name: "account_id", err: fmt.Errorf(`db: validator failed for field "VehicleSnapshot.account_id": %w`, err)} + } + } + if _, ok := vsc.mutation.VehicleID(); !ok { + return &ValidationError{Name: "vehicle_id", err: errors.New(`db: missing required field "VehicleSnapshot.vehicle_id"`)} + } + if v, ok := vsc.mutation.VehicleID(); ok { + if err := vehiclesnapshot.VehicleIDValidator(v); err != nil { + return &ValidationError{Name: "vehicle_id", err: fmt.Errorf(`db: validator failed for field "VehicleSnapshot.vehicle_id": %w`, err)} + } + } + if _, ok := vsc.mutation.ReferenceID(); !ok { + return &ValidationError{Name: "reference_id", err: errors.New(`db: missing required field "VehicleSnapshot.reference_id"`)} + } + if v, ok := vsc.mutation.ReferenceID(); ok { + if err := vehiclesnapshot.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "VehicleSnapshot.reference_id": %w`, err)} + } + } + if _, ok := vsc.mutation.Battles(); !ok { + return &ValidationError{Name: "battles", err: errors.New(`db: missing required field "VehicleSnapshot.battles"`)} + } + if _, ok := vsc.mutation.LastBattleTime(); !ok { + return &ValidationError{Name: "last_battle_time", err: errors.New(`db: missing required field "VehicleSnapshot.last_battle_time"`)} + } + if _, ok := vsc.mutation.Frame(); !ok { + return &ValidationError{Name: "frame", err: errors.New(`db: missing required field "VehicleSnapshot.frame"`)} + } + if _, ok := vsc.mutation.AccountID(); !ok { + return &ValidationError{Name: "account", err: errors.New(`db: missing required edge "VehicleSnapshot.account"`)} + } + return nil +} + +func (vsc *VehicleSnapshotCreate) sqlSave(ctx context.Context) (*VehicleSnapshot, error) { + if err := vsc.check(); err != nil { + return nil, err + } + _node, _spec := vsc.createSpec() + if err := sqlgraph.CreateNode(ctx, vsc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected VehicleSnapshot.ID type: %T", _spec.ID.Value) + } + } + vsc.mutation.id = &_node.ID + vsc.mutation.done = true + return _node, nil +} + +func (vsc *VehicleSnapshotCreate) createSpec() (*VehicleSnapshot, *sqlgraph.CreateSpec) { + var ( + _node = &VehicleSnapshot{config: vsc.config} + _spec = sqlgraph.NewCreateSpec(vehiclesnapshot.Table, sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString)) + ) + if id, ok := vsc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := vsc.mutation.CreatedAt(); ok { + _spec.SetField(vehiclesnapshot.FieldCreatedAt, field.TypeInt, value) + _node.CreatedAt = value + } + if value, ok := vsc.mutation.UpdatedAt(); ok { + _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + _node.UpdatedAt = value + } + if value, ok := vsc.mutation.GetType(); ok { + _spec.SetField(vehiclesnapshot.FieldType, field.TypeEnum, value) + _node.Type = value + } + if value, ok := vsc.mutation.VehicleID(); ok { + _spec.SetField(vehiclesnapshot.FieldVehicleID, field.TypeString, value) + _node.VehicleID = value + } + if value, ok := vsc.mutation.ReferenceID(); ok { + _spec.SetField(vehiclesnapshot.FieldReferenceID, field.TypeString, value) + _node.ReferenceID = value + } + if value, ok := vsc.mutation.Battles(); ok { + _spec.SetField(vehiclesnapshot.FieldBattles, field.TypeInt, value) + _node.Battles = value + } + if value, ok := vsc.mutation.LastBattleTime(); ok { + _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + _node.LastBattleTime = value + } + if value, ok := vsc.mutation.Frame(); ok { + _spec.SetField(vehiclesnapshot.FieldFrame, field.TypeJSON, value) + _node.Frame = value + } + if nodes := vsc.mutation.AccountIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: vehiclesnapshot.AccountTable, + Columns: []string{vehiclesnapshot.AccountColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.AccountID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// VehicleSnapshotCreateBulk is the builder for creating many VehicleSnapshot entities in bulk. +type VehicleSnapshotCreateBulk struct { + config + err error + builders []*VehicleSnapshotCreate +} + +// Save creates the VehicleSnapshot entities in the database. +func (vscb *VehicleSnapshotCreateBulk) Save(ctx context.Context) ([]*VehicleSnapshot, error) { + if vscb.err != nil { + return nil, vscb.err + } + specs := make([]*sqlgraph.CreateSpec, len(vscb.builders)) + nodes := make([]*VehicleSnapshot, len(vscb.builders)) + mutators := make([]Mutator, len(vscb.builders)) + for i := range vscb.builders { + func(i int, root context.Context) { + builder := vscb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*VehicleSnapshotMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, vscb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, vscb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, vscb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (vscb *VehicleSnapshotCreateBulk) SaveX(ctx context.Context) []*VehicleSnapshot { + v, err := vscb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (vscb *VehicleSnapshotCreateBulk) Exec(ctx context.Context) error { + _, err := vscb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vscb *VehicleSnapshotCreateBulk) ExecX(ctx context.Context) { + if err := vscb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/vehiclesnapshot_delete.go b/internal/database/ent/db/vehiclesnapshot_delete.go new file mode 100644 index 00000000..2fc70b41 --- /dev/null +++ b/internal/database/ent/db/vehiclesnapshot_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" +) + +// VehicleSnapshotDelete is the builder for deleting a VehicleSnapshot entity. +type VehicleSnapshotDelete struct { + config + hooks []Hook + mutation *VehicleSnapshotMutation +} + +// Where appends a list predicates to the VehicleSnapshotDelete builder. +func (vsd *VehicleSnapshotDelete) Where(ps ...predicate.VehicleSnapshot) *VehicleSnapshotDelete { + vsd.mutation.Where(ps...) + return vsd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (vsd *VehicleSnapshotDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, vsd.sqlExec, vsd.mutation, vsd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (vsd *VehicleSnapshotDelete) ExecX(ctx context.Context) int { + n, err := vsd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (vsd *VehicleSnapshotDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(vehiclesnapshot.Table, sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString)) + if ps := vsd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, vsd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + vsd.mutation.done = true + return affected, err +} + +// VehicleSnapshotDeleteOne is the builder for deleting a single VehicleSnapshot entity. +type VehicleSnapshotDeleteOne struct { + vsd *VehicleSnapshotDelete +} + +// Where appends a list predicates to the VehicleSnapshotDelete builder. +func (vsdo *VehicleSnapshotDeleteOne) Where(ps ...predicate.VehicleSnapshot) *VehicleSnapshotDeleteOne { + vsdo.vsd.mutation.Where(ps...) + return vsdo +} + +// Exec executes the deletion query. +func (vsdo *VehicleSnapshotDeleteOne) Exec(ctx context.Context) error { + n, err := vsdo.vsd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{vehiclesnapshot.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (vsdo *VehicleSnapshotDeleteOne) ExecX(ctx context.Context) { + if err := vsdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/vehiclesnapshot_query.go b/internal/database/ent/db/vehiclesnapshot_query.go new file mode 100644 index 00000000..89f20225 --- /dev/null +++ b/internal/database/ent/db/vehiclesnapshot_query.go @@ -0,0 +1,605 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/account" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" +) + +// VehicleSnapshotQuery is the builder for querying VehicleSnapshot entities. +type VehicleSnapshotQuery struct { + config + ctx *QueryContext + order []vehiclesnapshot.OrderOption + inters []Interceptor + predicates []predicate.VehicleSnapshot + withAccount *AccountQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the VehicleSnapshotQuery builder. +func (vsq *VehicleSnapshotQuery) Where(ps ...predicate.VehicleSnapshot) *VehicleSnapshotQuery { + vsq.predicates = append(vsq.predicates, ps...) + return vsq +} + +// Limit the number of records to be returned by this query. +func (vsq *VehicleSnapshotQuery) Limit(limit int) *VehicleSnapshotQuery { + vsq.ctx.Limit = &limit + return vsq +} + +// Offset to start from. +func (vsq *VehicleSnapshotQuery) Offset(offset int) *VehicleSnapshotQuery { + vsq.ctx.Offset = &offset + return vsq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (vsq *VehicleSnapshotQuery) Unique(unique bool) *VehicleSnapshotQuery { + vsq.ctx.Unique = &unique + return vsq +} + +// Order specifies how the records should be ordered. +func (vsq *VehicleSnapshotQuery) Order(o ...vehiclesnapshot.OrderOption) *VehicleSnapshotQuery { + vsq.order = append(vsq.order, o...) + return vsq +} + +// QueryAccount chains the current query on the "account" edge. +func (vsq *VehicleSnapshotQuery) QueryAccount() *AccountQuery { + query := (&AccountClient{config: vsq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := vsq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := vsq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(vehiclesnapshot.Table, vehiclesnapshot.FieldID, selector), + sqlgraph.To(account.Table, account.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, vehiclesnapshot.AccountTable, vehiclesnapshot.AccountColumn), + ) + fromU = sqlgraph.SetNeighbors(vsq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first VehicleSnapshot entity from the query. +// Returns a *NotFoundError when no VehicleSnapshot was found. +func (vsq *VehicleSnapshotQuery) First(ctx context.Context) (*VehicleSnapshot, error) { + nodes, err := vsq.Limit(1).All(setContextOp(ctx, vsq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{vehiclesnapshot.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (vsq *VehicleSnapshotQuery) FirstX(ctx context.Context) *VehicleSnapshot { + node, err := vsq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first VehicleSnapshot ID from the query. +// Returns a *NotFoundError when no VehicleSnapshot ID was found. +func (vsq *VehicleSnapshotQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = vsq.Limit(1).IDs(setContextOp(ctx, vsq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{vehiclesnapshot.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (vsq *VehicleSnapshotQuery) FirstIDX(ctx context.Context) string { + id, err := vsq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single VehicleSnapshot entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one VehicleSnapshot entity is found. +// Returns a *NotFoundError when no VehicleSnapshot entities are found. +func (vsq *VehicleSnapshotQuery) Only(ctx context.Context) (*VehicleSnapshot, error) { + nodes, err := vsq.Limit(2).All(setContextOp(ctx, vsq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{vehiclesnapshot.Label} + default: + return nil, &NotSingularError{vehiclesnapshot.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (vsq *VehicleSnapshotQuery) OnlyX(ctx context.Context) *VehicleSnapshot { + node, err := vsq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only VehicleSnapshot ID in the query. +// Returns a *NotSingularError when more than one VehicleSnapshot ID is found. +// Returns a *NotFoundError when no entities are found. +func (vsq *VehicleSnapshotQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = vsq.Limit(2).IDs(setContextOp(ctx, vsq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{vehiclesnapshot.Label} + default: + err = &NotSingularError{vehiclesnapshot.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (vsq *VehicleSnapshotQuery) OnlyIDX(ctx context.Context) string { + id, err := vsq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of VehicleSnapshots. +func (vsq *VehicleSnapshotQuery) All(ctx context.Context) ([]*VehicleSnapshot, error) { + ctx = setContextOp(ctx, vsq.ctx, "All") + if err := vsq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*VehicleSnapshot, *VehicleSnapshotQuery]() + return withInterceptors[[]*VehicleSnapshot](ctx, vsq, qr, vsq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (vsq *VehicleSnapshotQuery) AllX(ctx context.Context) []*VehicleSnapshot { + nodes, err := vsq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of VehicleSnapshot IDs. +func (vsq *VehicleSnapshotQuery) IDs(ctx context.Context) (ids []string, err error) { + if vsq.ctx.Unique == nil && vsq.path != nil { + vsq.Unique(true) + } + ctx = setContextOp(ctx, vsq.ctx, "IDs") + if err = vsq.Select(vehiclesnapshot.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (vsq *VehicleSnapshotQuery) IDsX(ctx context.Context) []string { + ids, err := vsq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (vsq *VehicleSnapshotQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, vsq.ctx, "Count") + if err := vsq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, vsq, querierCount[*VehicleSnapshotQuery](), vsq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (vsq *VehicleSnapshotQuery) CountX(ctx context.Context) int { + count, err := vsq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (vsq *VehicleSnapshotQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, vsq.ctx, "Exist") + switch _, err := vsq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (vsq *VehicleSnapshotQuery) ExistX(ctx context.Context) bool { + exist, err := vsq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the VehicleSnapshotQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (vsq *VehicleSnapshotQuery) Clone() *VehicleSnapshotQuery { + if vsq == nil { + return nil + } + return &VehicleSnapshotQuery{ + config: vsq.config, + ctx: vsq.ctx.Clone(), + order: append([]vehiclesnapshot.OrderOption{}, vsq.order...), + inters: append([]Interceptor{}, vsq.inters...), + predicates: append([]predicate.VehicleSnapshot{}, vsq.predicates...), + withAccount: vsq.withAccount.Clone(), + // clone intermediate query. + sql: vsq.sql.Clone(), + path: vsq.path, + } +} + +// WithAccount tells the query-builder to eager-load the nodes that are connected to +// the "account" edge. The optional arguments are used to configure the query builder of the edge. +func (vsq *VehicleSnapshotQuery) WithAccount(opts ...func(*AccountQuery)) *VehicleSnapshotQuery { + query := (&AccountClient{config: vsq.config}).Query() + for _, opt := range opts { + opt(query) + } + vsq.withAccount = query + return vsq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.VehicleSnapshot.Query(). +// GroupBy(vehiclesnapshot.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (vsq *VehicleSnapshotQuery) GroupBy(field string, fields ...string) *VehicleSnapshotGroupBy { + vsq.ctx.Fields = append([]string{field}, fields...) + grbuild := &VehicleSnapshotGroupBy{build: vsq} + grbuild.flds = &vsq.ctx.Fields + grbuild.label = vehiclesnapshot.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int `json:"created_at,omitempty"` +// } +// +// client.VehicleSnapshot.Query(). +// Select(vehiclesnapshot.FieldCreatedAt). +// Scan(ctx, &v) +func (vsq *VehicleSnapshotQuery) Select(fields ...string) *VehicleSnapshotSelect { + vsq.ctx.Fields = append(vsq.ctx.Fields, fields...) + sbuild := &VehicleSnapshotSelect{VehicleSnapshotQuery: vsq} + sbuild.label = vehiclesnapshot.Label + sbuild.flds, sbuild.scan = &vsq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a VehicleSnapshotSelect configured with the given aggregations. +func (vsq *VehicleSnapshotQuery) Aggregate(fns ...AggregateFunc) *VehicleSnapshotSelect { + return vsq.Select().Aggregate(fns...) +} + +func (vsq *VehicleSnapshotQuery) prepareQuery(ctx context.Context) error { + for _, inter := range vsq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, vsq); err != nil { + return err + } + } + } + for _, f := range vsq.ctx.Fields { + if !vehiclesnapshot.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if vsq.path != nil { + prev, err := vsq.path(ctx) + if err != nil { + return err + } + vsq.sql = prev + } + return nil +} + +func (vsq *VehicleSnapshotQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*VehicleSnapshot, error) { + var ( + nodes = []*VehicleSnapshot{} + _spec = vsq.querySpec() + loadedTypes = [1]bool{ + vsq.withAccount != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*VehicleSnapshot).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &VehicleSnapshot{config: vsq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, vsq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := vsq.withAccount; query != nil { + if err := vsq.loadAccount(ctx, query, nodes, nil, + func(n *VehicleSnapshot, e *Account) { n.Edges.Account = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (vsq *VehicleSnapshotQuery) loadAccount(ctx context.Context, query *AccountQuery, nodes []*VehicleSnapshot, init func(*VehicleSnapshot), assign func(*VehicleSnapshot, *Account)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*VehicleSnapshot) + for i := range nodes { + fk := nodes[i].AccountID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(account.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "account_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (vsq *VehicleSnapshotQuery) sqlCount(ctx context.Context) (int, error) { + _spec := vsq.querySpec() + _spec.Node.Columns = vsq.ctx.Fields + if len(vsq.ctx.Fields) > 0 { + _spec.Unique = vsq.ctx.Unique != nil && *vsq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, vsq.driver, _spec) +} + +func (vsq *VehicleSnapshotQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(vehiclesnapshot.Table, vehiclesnapshot.Columns, sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString)) + _spec.From = vsq.sql + if unique := vsq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if vsq.path != nil { + _spec.Unique = true + } + if fields := vsq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, vehiclesnapshot.FieldID) + for i := range fields { + if fields[i] != vehiclesnapshot.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if vsq.withAccount != nil { + _spec.Node.AddColumnOnce(vehiclesnapshot.FieldAccountID) + } + } + if ps := vsq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := vsq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := vsq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := vsq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (vsq *VehicleSnapshotQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(vsq.driver.Dialect()) + t1 := builder.Table(vehiclesnapshot.Table) + columns := vsq.ctx.Fields + if len(columns) == 0 { + columns = vehiclesnapshot.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if vsq.sql != nil { + selector = vsq.sql + selector.Select(selector.Columns(columns...)...) + } + if vsq.ctx.Unique != nil && *vsq.ctx.Unique { + selector.Distinct() + } + for _, p := range vsq.predicates { + p(selector) + } + for _, p := range vsq.order { + p(selector) + } + if offset := vsq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := vsq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// VehicleSnapshotGroupBy is the group-by builder for VehicleSnapshot entities. +type VehicleSnapshotGroupBy struct { + selector + build *VehicleSnapshotQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (vsgb *VehicleSnapshotGroupBy) Aggregate(fns ...AggregateFunc) *VehicleSnapshotGroupBy { + vsgb.fns = append(vsgb.fns, fns...) + return vsgb +} + +// Scan applies the selector query and scans the result into the given value. +func (vsgb *VehicleSnapshotGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, vsgb.build.ctx, "GroupBy") + if err := vsgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*VehicleSnapshotQuery, *VehicleSnapshotGroupBy](ctx, vsgb.build, vsgb, vsgb.build.inters, v) +} + +func (vsgb *VehicleSnapshotGroupBy) sqlScan(ctx context.Context, root *VehicleSnapshotQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(vsgb.fns)) + for _, fn := range vsgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*vsgb.flds)+len(vsgb.fns)) + for _, f := range *vsgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*vsgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := vsgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// VehicleSnapshotSelect is the builder for selecting fields of VehicleSnapshot entities. +type VehicleSnapshotSelect struct { + *VehicleSnapshotQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (vss *VehicleSnapshotSelect) Aggregate(fns ...AggregateFunc) *VehicleSnapshotSelect { + vss.fns = append(vss.fns, fns...) + return vss +} + +// Scan applies the selector query and scans the result into the given value. +func (vss *VehicleSnapshotSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, vss.ctx, "Select") + if err := vss.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*VehicleSnapshotQuery, *VehicleSnapshotSelect](ctx, vss.VehicleSnapshotQuery, vss, vss.inters, v) +} + +func (vss *VehicleSnapshotSelect) sqlScan(ctx context.Context, root *VehicleSnapshotQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(vss.fns)) + for _, fn := range vss.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*vss.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := vss.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/vehiclesnapshot_update.go b/internal/database/ent/db/vehiclesnapshot_update.go new file mode 100644 index 00000000..a80ac271 --- /dev/null +++ b/internal/database/ent/db/vehiclesnapshot_update.go @@ -0,0 +1,485 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// VehicleSnapshotUpdate is the builder for updating VehicleSnapshot entities. +type VehicleSnapshotUpdate struct { + config + hooks []Hook + mutation *VehicleSnapshotMutation +} + +// Where appends a list predicates to the VehicleSnapshotUpdate builder. +func (vsu *VehicleSnapshotUpdate) Where(ps ...predicate.VehicleSnapshot) *VehicleSnapshotUpdate { + vsu.mutation.Where(ps...) + return vsu +} + +// SetUpdatedAt sets the "updated_at" field. +func (vsu *VehicleSnapshotUpdate) SetUpdatedAt(i int) *VehicleSnapshotUpdate { + vsu.mutation.ResetUpdatedAt() + vsu.mutation.SetUpdatedAt(i) + return vsu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (vsu *VehicleSnapshotUpdate) AddUpdatedAt(i int) *VehicleSnapshotUpdate { + vsu.mutation.AddUpdatedAt(i) + return vsu +} + +// SetType sets the "type" field. +func (vsu *VehicleSnapshotUpdate) SetType(mt models.SnapshotType) *VehicleSnapshotUpdate { + vsu.mutation.SetType(mt) + return vsu +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (vsu *VehicleSnapshotUpdate) SetNillableType(mt *models.SnapshotType) *VehicleSnapshotUpdate { + if mt != nil { + vsu.SetType(*mt) + } + return vsu +} + +// SetReferenceID sets the "reference_id" field. +func (vsu *VehicleSnapshotUpdate) SetReferenceID(s string) *VehicleSnapshotUpdate { + vsu.mutation.SetReferenceID(s) + return vsu +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (vsu *VehicleSnapshotUpdate) SetNillableReferenceID(s *string) *VehicleSnapshotUpdate { + if s != nil { + vsu.SetReferenceID(*s) + } + return vsu +} + +// SetBattles sets the "battles" field. +func (vsu *VehicleSnapshotUpdate) SetBattles(i int) *VehicleSnapshotUpdate { + vsu.mutation.ResetBattles() + vsu.mutation.SetBattles(i) + return vsu +} + +// SetNillableBattles sets the "battles" field if the given value is not nil. +func (vsu *VehicleSnapshotUpdate) SetNillableBattles(i *int) *VehicleSnapshotUpdate { + if i != nil { + vsu.SetBattles(*i) + } + return vsu +} + +// AddBattles adds i to the "battles" field. +func (vsu *VehicleSnapshotUpdate) AddBattles(i int) *VehicleSnapshotUpdate { + vsu.mutation.AddBattles(i) + return vsu +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (vsu *VehicleSnapshotUpdate) SetLastBattleTime(i int) *VehicleSnapshotUpdate { + vsu.mutation.ResetLastBattleTime() + vsu.mutation.SetLastBattleTime(i) + return vsu +} + +// SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. +func (vsu *VehicleSnapshotUpdate) SetNillableLastBattleTime(i *int) *VehicleSnapshotUpdate { + if i != nil { + vsu.SetLastBattleTime(*i) + } + return vsu +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (vsu *VehicleSnapshotUpdate) AddLastBattleTime(i int) *VehicleSnapshotUpdate { + vsu.mutation.AddLastBattleTime(i) + return vsu +} + +// SetFrame sets the "frame" field. +func (vsu *VehicleSnapshotUpdate) SetFrame(ff frame.StatsFrame) *VehicleSnapshotUpdate { + vsu.mutation.SetFrame(ff) + return vsu +} + +// SetNillableFrame sets the "frame" field if the given value is not nil. +func (vsu *VehicleSnapshotUpdate) SetNillableFrame(ff *frame.StatsFrame) *VehicleSnapshotUpdate { + if ff != nil { + vsu.SetFrame(*ff) + } + return vsu +} + +// Mutation returns the VehicleSnapshotMutation object of the builder. +func (vsu *VehicleSnapshotUpdate) Mutation() *VehicleSnapshotMutation { + return vsu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (vsu *VehicleSnapshotUpdate) Save(ctx context.Context) (int, error) { + vsu.defaults() + return withHooks(ctx, vsu.sqlSave, vsu.mutation, vsu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (vsu *VehicleSnapshotUpdate) SaveX(ctx context.Context) int { + affected, err := vsu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (vsu *VehicleSnapshotUpdate) Exec(ctx context.Context) error { + _, err := vsu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vsu *VehicleSnapshotUpdate) ExecX(ctx context.Context) { + if err := vsu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vsu *VehicleSnapshotUpdate) defaults() { + if _, ok := vsu.mutation.UpdatedAt(); !ok { + v := vehiclesnapshot.UpdateDefaultUpdatedAt() + vsu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (vsu *VehicleSnapshotUpdate) check() error { + if v, ok := vsu.mutation.GetType(); ok { + if err := vehiclesnapshot.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "VehicleSnapshot.type": %w`, err)} + } + } + if v, ok := vsu.mutation.ReferenceID(); ok { + if err := vehiclesnapshot.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "VehicleSnapshot.reference_id": %w`, err)} + } + } + if _, ok := vsu.mutation.AccountID(); vsu.mutation.AccountCleared() && !ok { + return errors.New(`db: clearing a required unique edge "VehicleSnapshot.account"`) + } + return nil +} + +func (vsu *VehicleSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := vsu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(vehiclesnapshot.Table, vehiclesnapshot.Columns, sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString)) + if ps := vsu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := vsu.mutation.UpdatedAt(); ok { + _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vsu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vsu.mutation.GetType(); ok { + _spec.SetField(vehiclesnapshot.FieldType, field.TypeEnum, value) + } + if value, ok := vsu.mutation.ReferenceID(); ok { + _spec.SetField(vehiclesnapshot.FieldReferenceID, field.TypeString, value) + } + if value, ok := vsu.mutation.Battles(); ok { + _spec.SetField(vehiclesnapshot.FieldBattles, field.TypeInt, value) + } + if value, ok := vsu.mutation.AddedBattles(); ok { + _spec.AddField(vehiclesnapshot.FieldBattles, field.TypeInt, value) + } + if value, ok := vsu.mutation.LastBattleTime(); ok { + _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := vsu.mutation.AddedLastBattleTime(); ok { + _spec.AddField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := vsu.mutation.Frame(); ok { + _spec.SetField(vehiclesnapshot.FieldFrame, field.TypeJSON, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, vsu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{vehiclesnapshot.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + vsu.mutation.done = true + return n, nil +} + +// VehicleSnapshotUpdateOne is the builder for updating a single VehicleSnapshot entity. +type VehicleSnapshotUpdateOne struct { + config + fields []string + hooks []Hook + mutation *VehicleSnapshotMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (vsuo *VehicleSnapshotUpdateOne) SetUpdatedAt(i int) *VehicleSnapshotUpdateOne { + vsuo.mutation.ResetUpdatedAt() + vsuo.mutation.SetUpdatedAt(i) + return vsuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (vsuo *VehicleSnapshotUpdateOne) AddUpdatedAt(i int) *VehicleSnapshotUpdateOne { + vsuo.mutation.AddUpdatedAt(i) + return vsuo +} + +// SetType sets the "type" field. +func (vsuo *VehicleSnapshotUpdateOne) SetType(mt models.SnapshotType) *VehicleSnapshotUpdateOne { + vsuo.mutation.SetType(mt) + return vsuo +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (vsuo *VehicleSnapshotUpdateOne) SetNillableType(mt *models.SnapshotType) *VehicleSnapshotUpdateOne { + if mt != nil { + vsuo.SetType(*mt) + } + return vsuo +} + +// SetReferenceID sets the "reference_id" field. +func (vsuo *VehicleSnapshotUpdateOne) SetReferenceID(s string) *VehicleSnapshotUpdateOne { + vsuo.mutation.SetReferenceID(s) + return vsuo +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (vsuo *VehicleSnapshotUpdateOne) SetNillableReferenceID(s *string) *VehicleSnapshotUpdateOne { + if s != nil { + vsuo.SetReferenceID(*s) + } + return vsuo +} + +// SetBattles sets the "battles" field. +func (vsuo *VehicleSnapshotUpdateOne) SetBattles(i int) *VehicleSnapshotUpdateOne { + vsuo.mutation.ResetBattles() + vsuo.mutation.SetBattles(i) + return vsuo +} + +// SetNillableBattles sets the "battles" field if the given value is not nil. +func (vsuo *VehicleSnapshotUpdateOne) SetNillableBattles(i *int) *VehicleSnapshotUpdateOne { + if i != nil { + vsuo.SetBattles(*i) + } + return vsuo +} + +// AddBattles adds i to the "battles" field. +func (vsuo *VehicleSnapshotUpdateOne) AddBattles(i int) *VehicleSnapshotUpdateOne { + vsuo.mutation.AddBattles(i) + return vsuo +} + +// SetLastBattleTime sets the "last_battle_time" field. +func (vsuo *VehicleSnapshotUpdateOne) SetLastBattleTime(i int) *VehicleSnapshotUpdateOne { + vsuo.mutation.ResetLastBattleTime() + vsuo.mutation.SetLastBattleTime(i) + return vsuo +} + +// SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. +func (vsuo *VehicleSnapshotUpdateOne) SetNillableLastBattleTime(i *int) *VehicleSnapshotUpdateOne { + if i != nil { + vsuo.SetLastBattleTime(*i) + } + return vsuo +} + +// AddLastBattleTime adds i to the "last_battle_time" field. +func (vsuo *VehicleSnapshotUpdateOne) AddLastBattleTime(i int) *VehicleSnapshotUpdateOne { + vsuo.mutation.AddLastBattleTime(i) + return vsuo +} + +// SetFrame sets the "frame" field. +func (vsuo *VehicleSnapshotUpdateOne) SetFrame(ff frame.StatsFrame) *VehicleSnapshotUpdateOne { + vsuo.mutation.SetFrame(ff) + return vsuo +} + +// SetNillableFrame sets the "frame" field if the given value is not nil. +func (vsuo *VehicleSnapshotUpdateOne) SetNillableFrame(ff *frame.StatsFrame) *VehicleSnapshotUpdateOne { + if ff != nil { + vsuo.SetFrame(*ff) + } + return vsuo +} + +// Mutation returns the VehicleSnapshotMutation object of the builder. +func (vsuo *VehicleSnapshotUpdateOne) Mutation() *VehicleSnapshotMutation { + return vsuo.mutation +} + +// Where appends a list predicates to the VehicleSnapshotUpdate builder. +func (vsuo *VehicleSnapshotUpdateOne) Where(ps ...predicate.VehicleSnapshot) *VehicleSnapshotUpdateOne { + vsuo.mutation.Where(ps...) + return vsuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (vsuo *VehicleSnapshotUpdateOne) Select(field string, fields ...string) *VehicleSnapshotUpdateOne { + vsuo.fields = append([]string{field}, fields...) + return vsuo +} + +// Save executes the query and returns the updated VehicleSnapshot entity. +func (vsuo *VehicleSnapshotUpdateOne) Save(ctx context.Context) (*VehicleSnapshot, error) { + vsuo.defaults() + return withHooks(ctx, vsuo.sqlSave, vsuo.mutation, vsuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (vsuo *VehicleSnapshotUpdateOne) SaveX(ctx context.Context) *VehicleSnapshot { + node, err := vsuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (vsuo *VehicleSnapshotUpdateOne) Exec(ctx context.Context) error { + _, err := vsuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vsuo *VehicleSnapshotUpdateOne) ExecX(ctx context.Context) { + if err := vsuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vsuo *VehicleSnapshotUpdateOne) defaults() { + if _, ok := vsuo.mutation.UpdatedAt(); !ok { + v := vehiclesnapshot.UpdateDefaultUpdatedAt() + vsuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (vsuo *VehicleSnapshotUpdateOne) check() error { + if v, ok := vsuo.mutation.GetType(); ok { + if err := vehiclesnapshot.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "VehicleSnapshot.type": %w`, err)} + } + } + if v, ok := vsuo.mutation.ReferenceID(); ok { + if err := vehiclesnapshot.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "VehicleSnapshot.reference_id": %w`, err)} + } + } + if _, ok := vsuo.mutation.AccountID(); vsuo.mutation.AccountCleared() && !ok { + return errors.New(`db: clearing a required unique edge "VehicleSnapshot.account"`) + } + return nil +} + +func (vsuo *VehicleSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *VehicleSnapshot, err error) { + if err := vsuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(vehiclesnapshot.Table, vehiclesnapshot.Columns, sqlgraph.NewFieldSpec(vehiclesnapshot.FieldID, field.TypeString)) + id, ok := vsuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "VehicleSnapshot.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := vsuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, vehiclesnapshot.FieldID) + for _, f := range fields { + if !vehiclesnapshot.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != vehiclesnapshot.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := vsuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := vsuo.mutation.UpdatedAt(); ok { + _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vsuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + } + if value, ok := vsuo.mutation.GetType(); ok { + _spec.SetField(vehiclesnapshot.FieldType, field.TypeEnum, value) + } + if value, ok := vsuo.mutation.ReferenceID(); ok { + _spec.SetField(vehiclesnapshot.FieldReferenceID, field.TypeString, value) + } + if value, ok := vsuo.mutation.Battles(); ok { + _spec.SetField(vehiclesnapshot.FieldBattles, field.TypeInt, value) + } + if value, ok := vsuo.mutation.AddedBattles(); ok { + _spec.AddField(vehiclesnapshot.FieldBattles, field.TypeInt, value) + } + if value, ok := vsuo.mutation.LastBattleTime(); ok { + _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := vsuo.mutation.AddedLastBattleTime(); ok { + _spec.AddField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + } + if value, ok := vsuo.mutation.Frame(); ok { + _spec.SetField(vehiclesnapshot.FieldFrame, field.TypeJSON, value) + } + _node = &VehicleSnapshot{config: vsuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, vsuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{vehiclesnapshot.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + vsuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/generate.go b/internal/database/ent/generate.go new file mode 100644 index 00000000..ce73767e --- /dev/null +++ b/internal/database/ent/generate.go @@ -0,0 +1,3 @@ +package ent + +//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration --target ./db ./schema diff --git a/internal/database/ent/migrate.go b/internal/database/ent/migrate.go new file mode 100644 index 00000000..39d7f1aa --- /dev/null +++ b/internal/database/ent/migrate.go @@ -0,0 +1,63 @@ +package ent + +import ( + "database/sql" +) + +const migration = ` +CREATE TABLE 'accounts' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'last_battle_time' integer NOT NULL, 'account_created_at' integer NOT NULL, 'realm' text NOT NULL, 'nickname' text NOT NULL, 'private' bool NOT NULL DEFAULT (false), 'clan_id' text NULL, PRIMARY KEY ('id'), CONSTRAINT 'accounts_clans_accounts' FOREIGN KEY ('clan_id') REFERENCES 'clans' ('id') ON DELETE SET NULL); +CREATE INDEX 'account_id_last_battle_time' ON 'accounts' ('id', 'last_battle_time'); +CREATE INDEX 'account_realm' ON 'accounts' ('realm'); +CREATE INDEX 'account_realm_last_battle_time' ON 'accounts' ('realm', 'last_battle_time'); +CREATE INDEX 'account_clan_id' ON 'accounts' ('clan_id'); +CREATE TABLE 'account_snapshots' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'last_battle_time' integer NOT NULL, 'reference_id' text NOT NULL, 'rating_battles' integer NOT NULL, 'rating_frame' json NOT NULL, 'regular_battles' integer NOT NULL, 'regular_frame' json NOT NULL, 'account_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'account_snapshots_accounts_snapshots' FOREIGN KEY ('account_id') REFERENCES 'accounts' ('id') ON DELETE NO ACTION); +CREATE INDEX 'accountsnapshot_created_at' ON 'account_snapshots' ('created_at'); +CREATE INDEX 'accountsnapshot_type_account_id_created_at' ON 'account_snapshots' ('type', 'account_id', 'created_at'); +CREATE INDEX 'accountsnapshot_type_account_id_reference_id' ON 'account_snapshots' ('type', 'account_id', 'reference_id'); +CREATE INDEX 'accountsnapshot_type_account_id_reference_id_created_at' ON 'account_snapshots' ('type', 'account_id', 'reference_id', 'created_at'); +CREATE TABLE 'achievements_snapshots' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'reference_id' text NOT NULL, 'battles' integer NOT NULL, 'last_battle_time' integer NOT NULL, 'data' json NOT NULL, 'account_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'achievements_snapshots_accounts_achievement_snapshots' FOREIGN KEY ('account_id') REFERENCES 'accounts' ('id') ON DELETE NO ACTION); +CREATE INDEX 'achievementssnapshot_created_at' ON 'achievements_snapshots' ('created_at'); +CREATE INDEX 'achievementssnapshot_account_id_reference_id' ON 'achievements_snapshots' ('account_id', 'reference_id'); +CREATE INDEX 'achievementssnapshot_account_id_reference_id_created_at' ON 'achievements_snapshots' ('account_id', 'reference_id', 'created_at'); +CREATE TABLE 'app_configurations' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'key' text NOT NULL, 'value' json NOT NULL, 'metadata' json NULL, PRIMARY KEY ('id')); +CREATE UNIQUE INDEX 'app_configurations_key_key' ON 'app_configurations' ('key'); +CREATE INDEX 'appconfiguration_key' ON 'app_configurations' ('key'); +CREATE TABLE 'application_commands' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'name' text NOT NULL, 'version' text NOT NULL, 'options_hash' text NOT NULL, PRIMARY KEY ('id')); +CREATE UNIQUE INDEX 'application_commands_name_key' ON 'application_commands' ('name'); +CREATE INDEX 'applicationcommand_options_hash' ON 'application_commands' ('options_hash'); +CREATE TABLE 'clans' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'tag' text NOT NULL, 'name' text NOT NULL, 'emblem_id' text NULL DEFAULT (''), 'members' json NOT NULL, PRIMARY KEY ('id')); +CREATE INDEX 'clan_tag' ON 'clans' ('tag'); +CREATE INDEX 'clan_name' ON 'clans' ('name'); +CREATE TABLE 'cron_tasks' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'reference_id' text NOT NULL, 'targets' json NOT NULL, 'status' text NOT NULL, 'scheduled_after' integer NOT NULL, 'last_run' integer NOT NULL, 'logs' json NOT NULL, 'data' json NOT NULL, PRIMARY KEY ('id')); +CREATE INDEX 'crontask_reference_id' ON 'cron_tasks' ('reference_id'); +CREATE INDEX 'crontask_status_last_run' ON 'cron_tasks' ('status', 'last_run'); +CREATE INDEX 'crontask_status_created_at' ON 'cron_tasks' ('status', 'created_at'); +CREATE INDEX 'crontask_status_scheduled_after' ON 'cron_tasks' ('status', 'scheduled_after'); +CREATE TABLE 'users' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'permissions' text NOT NULL DEFAULT (''), 'feature_flags' json NULL, PRIMARY KEY ('id')); +CREATE TABLE 'user_connections' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'reference_id' text NOT NULL, 'permissions' text NULL DEFAULT (''), 'metadata' json NULL, 'user_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'user_connections_users_connections' FOREIGN KEY ('user_id') REFERENCES 'users' ('id') ON DELETE NO ACTION); +CREATE INDEX 'userconnection_user_id' ON 'user_connections' ('user_id'); +CREATE INDEX 'userconnection_type_user_id' ON 'user_connections' ('type', 'user_id'); +CREATE INDEX 'userconnection_reference_id' ON 'user_connections' ('reference_id'); +CREATE INDEX 'userconnection_type_reference_id' ON 'user_connections' ('type', 'reference_id'); +CREATE TABLE 'user_contents' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'reference_id' text NOT NULL, 'value' json NOT NULL, 'metadata' json NOT NULL, 'user_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'user_contents_users_content' FOREIGN KEY ('user_id') REFERENCES 'users' ('id') ON DELETE NO ACTION); +CREATE INDEX 'usercontent_user_id' ON 'user_contents' ('user_id'); +CREATE INDEX 'usercontent_type_user_id' ON 'user_contents' ('type', 'user_id'); +CREATE INDEX 'usercontent_reference_id' ON 'user_contents' ('reference_id'); +CREATE INDEX 'usercontent_type_reference_id' ON 'user_contents' ('type', 'reference_id'); +CREATE TABLE 'user_subscriptions' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'expires_at' integer NOT NULL, 'permissions' text NOT NULL, 'reference_id' text NOT NULL, 'user_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'user_subscriptions_users_subscriptions' FOREIGN KEY ('user_id') REFERENCES 'users' ('id') ON DELETE NO ACTION); +CREATE INDEX 'usersubscription_user_id' ON 'user_subscriptions' ('user_id'); +CREATE INDEX 'usersubscription_type_user_id' ON 'user_subscriptions' ('type', 'user_id'); +CREATE INDEX 'usersubscription_expires_at' ON 'user_subscriptions' ('expires_at'); +CREATE INDEX 'usersubscription_expires_at_user_id' ON 'user_subscriptions' ('expires_at', 'user_id'); +CREATE TABLE 'vehicles' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'tier' integer NOT NULL, 'localized_names' json NOT NULL, PRIMARY KEY ('id')); +CREATE TABLE 'vehicle_averages' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'data' json NOT NULL, PRIMARY KEY ('id')); +CREATE TABLE 'vehicle_snapshots' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'vehicle_id' text NOT NULL, 'reference_id' text NOT NULL, 'battles' integer NOT NULL, 'last_battle_time' integer NOT NULL, 'frame' json NOT NULL, 'account_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'vehicle_snapshots_accounts_vehicle_snapshots' FOREIGN KEY ('account_id') REFERENCES 'accounts' ('id') ON DELETE NO ACTION); +CREATE INDEX 'vehiclesnapshot_created_at' ON 'vehicle_snapshots' ('created_at'); +CREATE INDEX 'vehiclesnapshot_vehicle_id_created_at' ON 'vehicle_snapshots' ('vehicle_id', 'created_at'); +CREATE INDEX 'vehiclesnapshot_account_id_created_at' ON 'vehicle_snapshots' ('account_id', 'created_at'); +CREATE INDEX 'vehiclesnapshot_account_id_type_created_at' ON 'vehicle_snapshots' ('account_id', 'type', 'created_at');` + +func RunMigrations(client *sql.DB) error { + _, err := client.Exec(migration) + return err +} diff --git a/internal/database/ent/migrate/main.go b/internal/database/ent/migrate/main.go new file mode 100644 index 00000000..d0ab8587 --- /dev/null +++ b/internal/database/ent/migrate/main.go @@ -0,0 +1,40 @@ +// go:build ignore + +package main + +import ( + "context" + "log" + "os" + + atlas "ariga.io/atlas/sql/migrate" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql/schema" + "github.com/cufee/aftermath/internal/database/ent/db/migrate" + _ "github.com/tursodatabase/libsql-client-go/libsql" +) + +func main() { + ctx := context.Background() + // Create a local migration directory able to understand Atlas migration file format for replay. + dir, err := atlas.NewLocalDir("internal/database/ent/migrate/migrations") + if err != nil { + log.Fatalf("failed creating atlas migration directory: %v", err) + } + // Migrate diff options. + opts := []schema.MigrateOption{ + schema.WithForeignKeys(false), + schema.WithDir(dir), // provide migration directory + schema.WithMigrationMode(schema.ModeReplay), // provide migration mode + schema.WithDialect(dialect.SQLite), // Ent dialect to use + schema.WithFormatter(atlas.DefaultFormatter), + } + if len(os.Args) != 2 { + log.Fatalln("migration name is required. Use: 'go run -mod=mod ent/migrate/main.go '") + } + // Generate migrations using Atlas support for MySQL (note the Ent dialect option passed above). + err = migrate.NamedDiff(ctx, "libsql+ws://0.0.0.0:8080", os.Args[1], opts...) + if err != nil { + log.Fatalf("failed generating migration file: %v", err) + } +} diff --git a/internal/database/ent/migrate/migrations/20240622203812_init.sql b/internal/database/ent/migrate/migrations/20240622203812_init.sql new file mode 100644 index 00000000..55d46318 --- /dev/null +++ b/internal/database/ent/migrate/migrations/20240622203812_init.sql @@ -0,0 +1,102 @@ +-- Create "accounts" table +CREATE TABLE `accounts` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `last_battle_time` integer NOT NULL, `account_created_at` integer NOT NULL, `realm` text NOT NULL, `nickname` text NOT NULL, `private` bool NOT NULL DEFAULT (false), `clan_id` text NULL, PRIMARY KEY (`id`), CONSTRAINT `accounts_clans_accounts` FOREIGN KEY (`clan_id`) REFERENCES `clans` (`id`) ON DELETE SET NULL); +-- Create index "account_id_last_battle_time" to table: "accounts" +CREATE INDEX `account_id_last_battle_time` ON `accounts` (`id`, `last_battle_time`); +-- Create index "account_realm" to table: "accounts" +CREATE INDEX `account_realm` ON `accounts` (`realm`); +-- Create index "account_realm_last_battle_time" to table: "accounts" +CREATE INDEX `account_realm_last_battle_time` ON `accounts` (`realm`, `last_battle_time`); +-- Create index "account_clan_id" to table: "accounts" +CREATE INDEX `account_clan_id` ON `accounts` (`clan_id`); +-- Create "account_snapshots" table +CREATE TABLE `account_snapshots` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `last_battle_time` integer NOT NULL, `reference_id` text NOT NULL, `rating_battles` integer NOT NULL, `rating_frame` json NOT NULL, `regular_battles` integer NOT NULL, `regular_frame` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `account_snapshots_accounts_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION); +-- Create index "accountsnapshot_created_at" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_created_at` ON `account_snapshots` (`created_at`); +-- Create index "accountsnapshot_type_account_id_created_at" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_type_account_id_created_at` ON `account_snapshots` (`type`, `account_id`, `created_at`); +-- Create index "accountsnapshot_type_account_id_reference_id" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_type_account_id_reference_id` ON `account_snapshots` (`type`, `account_id`, `reference_id`); +-- Create index "accountsnapshot_type_account_id_reference_id_created_at" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_type_account_id_reference_id_created_at` ON `account_snapshots` (`type`, `account_id`, `reference_id`, `created_at`); +-- Create "achievements_snapshots" table +CREATE TABLE `achievements_snapshots` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `battles` integer NOT NULL, `last_battle_time` integer NOT NULL, `data` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `achievements_snapshots_accounts_achievement_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION); +-- Create index "achievementssnapshot_created_at" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_created_at` ON `achievements_snapshots` (`created_at`); +-- Create index "achievementssnapshot_account_id_reference_id" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_account_id_reference_id` ON `achievements_snapshots` (`account_id`, `reference_id`); +-- Create index "achievementssnapshot_account_id_reference_id_created_at" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_account_id_reference_id_created_at` ON `achievements_snapshots` (`account_id`, `reference_id`, `created_at`); +-- Create "app_configurations" table +CREATE TABLE `app_configurations` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `key` text NOT NULL, `value` json NOT NULL, `metadata` json NULL, PRIMARY KEY (`id`)); +-- Create index "app_configurations_key_key" to table: "app_configurations" +CREATE UNIQUE INDEX `app_configurations_key_key` ON `app_configurations` (`key`); +-- Create index "appconfiguration_key" to table: "app_configurations" +CREATE INDEX `appconfiguration_key` ON `app_configurations` (`key`); +-- Create "application_commands" table +CREATE TABLE `application_commands` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `name` text NOT NULL, `version` text NOT NULL, `options_hash` text NOT NULL, PRIMARY KEY (`id`)); +-- Create index "application_commands_name_key" to table: "application_commands" +CREATE UNIQUE INDEX `application_commands_name_key` ON `application_commands` (`name`); +-- Create index "applicationcommand_options_hash" to table: "application_commands" +CREATE INDEX `applicationcommand_options_hash` ON `application_commands` (`options_hash`); +-- Create "clans" table +CREATE TABLE `clans` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `tag` text NOT NULL, `name` text NOT NULL, `emblem_id` text NULL DEFAULT (''), `members` json NOT NULL, PRIMARY KEY (`id`)); +-- Create index "clan_tag" to table: "clans" +CREATE INDEX `clan_tag` ON `clans` (`tag`); +-- Create index "clan_name" to table: "clans" +CREATE INDEX `clan_name` ON `clans` (`name`); +-- Create "cron_tasks" table +CREATE TABLE `cron_tasks` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `targets` json NOT NULL, `status` text NOT NULL, `scheduled_after` integer NOT NULL, `last_run` integer NOT NULL, `logs` json NOT NULL, `data` json NOT NULL, PRIMARY KEY (`id`)); +-- Create index "crontask_reference_id" to table: "cron_tasks" +CREATE INDEX `crontask_reference_id` ON `cron_tasks` (`reference_id`); +-- Create index "crontask_status_last_run" to table: "cron_tasks" +CREATE INDEX `crontask_status_last_run` ON `cron_tasks` (`status`, `last_run`); +-- Create index "crontask_status_created_at" to table: "cron_tasks" +CREATE INDEX `crontask_status_created_at` ON `cron_tasks` (`status`, `created_at`); +-- Create index "crontask_status_scheduled_after" to table: "cron_tasks" +CREATE INDEX `crontask_status_scheduled_after` ON `cron_tasks` (`status`, `scheduled_after`); +-- Create "users" table +CREATE TABLE `users` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `permissions` text NOT NULL DEFAULT (''), `feature_flags` json NULL, PRIMARY KEY (`id`)); +-- Create "user_connections" table +CREATE TABLE `user_connections` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `permissions` text NULL DEFAULT (''), `metadata` json NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_connections_users_connections` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Create index "userconnection_user_id" to table: "user_connections" +CREATE INDEX `userconnection_user_id` ON `user_connections` (`user_id`); +-- Create index "userconnection_type_user_id" to table: "user_connections" +CREATE INDEX `userconnection_type_user_id` ON `user_connections` (`type`, `user_id`); +-- Create index "userconnection_reference_id" to table: "user_connections" +CREATE INDEX `userconnection_reference_id` ON `user_connections` (`reference_id`); +-- Create index "userconnection_type_reference_id" to table: "user_connections" +CREATE INDEX `userconnection_type_reference_id` ON `user_connections` (`type`, `reference_id`); +-- Create "user_contents" table +CREATE TABLE `user_contents` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `value` json NOT NULL, `metadata` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_contents_users_content` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Create index "usercontent_user_id" to table: "user_contents" +CREATE INDEX `usercontent_user_id` ON `user_contents` (`user_id`); +-- Create index "usercontent_type_user_id" to table: "user_contents" +CREATE INDEX `usercontent_type_user_id` ON `user_contents` (`type`, `user_id`); +-- Create index "usercontent_reference_id" to table: "user_contents" +CREATE INDEX `usercontent_reference_id` ON `user_contents` (`reference_id`); +-- Create index "usercontent_type_reference_id" to table: "user_contents" +CREATE INDEX `usercontent_type_reference_id` ON `user_contents` (`type`, `reference_id`); +-- Create "user_subscriptions" table +CREATE TABLE `user_subscriptions` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `expires_at` integer NOT NULL, `permissions` text NOT NULL, `reference_id` text NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_subscriptions_users_subscriptions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Create index "usersubscription_user_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_user_id` ON `user_subscriptions` (`user_id`); +-- Create index "usersubscription_type_user_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_type_user_id` ON `user_subscriptions` (`type`, `user_id`); +-- Create index "usersubscription_expires_at" to table: "user_subscriptions" +CREATE INDEX `usersubscription_expires_at` ON `user_subscriptions` (`expires_at`); +-- Create index "usersubscription_expires_at_user_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_expires_at_user_id` ON `user_subscriptions` (`expires_at`, `user_id`); +-- Create "vehicles" table +CREATE TABLE `vehicles` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `tier` integer NOT NULL, `localized_names` json NOT NULL, PRIMARY KEY (`id`)); +-- Create "vehicle_averages" table +CREATE TABLE `vehicle_averages` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `data` json NOT NULL, PRIMARY KEY (`id`)); +-- Create "vehicle_snapshots" table +CREATE TABLE `vehicle_snapshots` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `vehicle_id` text NOT NULL, `reference_id` text NOT NULL, `battles` integer NOT NULL, `last_battle_time` integer NOT NULL, `frame` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `vehicle_snapshots_accounts_vehicle_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION); +-- Create index "vehiclesnapshot_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_created_at` ON `vehicle_snapshots` (`created_at`); +-- Create index "vehiclesnapshot_vehicle_id_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_vehicle_id_created_at` ON `vehicle_snapshots` (`vehicle_id`, `created_at`); +-- Create index "vehiclesnapshot_account_id_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_account_id_created_at` ON `vehicle_snapshots` (`account_id`, `created_at`); +-- Create index "vehiclesnapshot_account_id_type_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_account_id_type_created_at` ON `vehicle_snapshots` (`account_id`, `type`, `created_at`); diff --git a/internal/database/ent/migrate/migrations/atlas.sum b/internal/database/ent/migrate/migrations/atlas.sum new file mode 100644 index 00000000..4c057533 --- /dev/null +++ b/internal/database/ent/migrate/migrations/atlas.sum @@ -0,0 +1,2 @@ +h1:lIjXgF0PT71x+0IXOVk9uiIK/23v6AlTegTXIaTyyZI= +20240622203812_init.sql h1:Rt6NGvXbPBEpjg1pUFqn97FV8X3X2d1x7J2zOAAxolc= diff --git a/internal/database/ent/schema/account.go b/internal/database/ent/schema/account.go new file mode 100644 index 00000000..4df6d398 --- /dev/null +++ b/internal/database/ent/schema/account.go @@ -0,0 +1,57 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// Account holds the schema definition for the Account entity. +type Account struct { + ent.Schema +} + +// Fields of the Account. +func (Account) Fields() []ent.Field { + return []ent.Field{field.String("id"). + Unique(). + Immutable(), + field.Int("created_at"). + Immutable(). + DefaultFunc(timeNow), + field.Int("updated_at"). + DefaultFunc(timeNow). + UpdateDefault(timeNow), + field.Int("last_battle_time"), + field.Int("account_created_at"), + // + field.String("realm"). + MinLen(2). + MaxLen(5), + field.String("nickname").NotEmpty(), + field.Bool("private"). + Default(false), + // + field.String("clan_id").Optional(), + } +} + +// Edges of the Account. +func (Account) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("clan", Clan.Type).Ref("accounts").Field("clan_id").Unique(), + edge.To("snapshots", AccountSnapshot.Type), + edge.To("vehicle_snapshots", VehicleSnapshot.Type), + edge.To("achievement_snapshots", AchievementsSnapshot.Type), + } +} + +func (Account) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("id", "last_battle_time"), + index.Fields("realm"), + index.Fields("realm", "last_battle_time"), + index.Fields("clan_id"), + } +} diff --git a/internal/database/ent/schema/accountsnapshot.go b/internal/database/ent/schema/accountsnapshot.go new file mode 100644 index 00000000..6a33c4c4 --- /dev/null +++ b/internal/database/ent/schema/accountsnapshot.go @@ -0,0 +1,49 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// AccountSnapshot holds the schema definition for the AccountSnapshot entity. +type AccountSnapshot struct { + ent.Schema +} + +// Fields of the AccountSnapshot. +func (AccountSnapshot) Fields() []ent.Field { + return append(defaultFields, + field.Enum("type"). + GoType(models.SnapshotType("")), + field.Int("last_battle_time"), + // + field.String("account_id").NotEmpty().Immutable(), + field.String("reference_id").NotEmpty(), + // + field.Int("rating_battles"), + field.JSON("rating_frame", frame.StatsFrame{}), + // + field.Int("regular_battles"), + field.JSON("regular_frame", frame.StatsFrame{}), + ) +} + +// Edges of the AccountSnapshot. +func (AccountSnapshot) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("account", Account.Type).Ref("snapshots").Field("account_id").Required().Immutable().Unique(), + } +} + +func (AccountSnapshot) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("created_at"), + index.Fields("type", "account_id", "created_at"), + index.Fields("type", "account_id", "reference_id"), + index.Fields("type", "account_id", "reference_id", "created_at"), + } +} diff --git a/internal/database/ent/schema/achievementssnapshot.go b/internal/database/ent/schema/achievementssnapshot.go new file mode 100644 index 00000000..bfd87368 --- /dev/null +++ b/internal/database/ent/schema/achievementssnapshot.go @@ -0,0 +1,44 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/am-wg-proxy-next/v2/types" +) + +// AchievementsSnapshot holds the schema definition for the AchievementsSnapshot entity. +type AchievementsSnapshot struct { + ent.Schema +} + +// Fields of the AchievementsSnapshot. +func (AchievementsSnapshot) Fields() []ent.Field { + return append(defaultFields, + field.Enum("type"). + GoType(models.SnapshotType("")), + field.String("account_id").NotEmpty().Immutable(), + field.String("reference_id").NotEmpty(), + // + field.Int("battles"), + field.Int("last_battle_time"), + field.JSON("data", types.AchievementsFrame{}), + ) +} + +// Edges of the AchievementsSnapshot. +func (AchievementsSnapshot) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("account", Account.Type).Ref("achievement_snapshots").Field("account_id").Required().Immutable().Unique(), + } +} + +func (AchievementsSnapshot) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("created_at"), + index.Fields("account_id", "reference_id"), + index.Fields("account_id", "reference_id", "created_at"), + } +} diff --git a/internal/database/ent/schema/appconfiguration.go b/internal/database/ent/schema/appconfiguration.go new file mode 100644 index 00000000..96ce0b6e --- /dev/null +++ b/internal/database/ent/schema/appconfiguration.go @@ -0,0 +1,33 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// AppConfiguration holds the schema definition for the AppConfiguration entity. +type AppConfiguration struct { + ent.Schema +} + +// Fields of the AppConfiguration. +func (AppConfiguration) Fields() []ent.Field { + return append(defaultFields, + field.String("key").Unique().NotEmpty(), + // + field.Any("value"), + field.JSON("metadata", map[string]any{}).Optional(), + ) +} + +// Edges of the AppConfiguration. +func (AppConfiguration) Edges() []ent.Edge { + return nil +} + +func (AppConfiguration) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("key"), + } +} diff --git a/internal/database/ent/schema/applicationcommand.go b/internal/database/ent/schema/applicationcommand.go new file mode 100644 index 00000000..71f68cf3 --- /dev/null +++ b/internal/database/ent/schema/applicationcommand.go @@ -0,0 +1,45 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// model ApplicationCommand { +// id String @id +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt + +// name String +// version String +// optionsHash String + +// @@index([optionsHash]) +// @@map("application_commands") +// } + +// ApplicationCommand holds the schema definition for the ApplicationCommand entity. +type ApplicationCommand struct { + ent.Schema +} + +// Fields of the ApplicationCommand. +func (ApplicationCommand) Fields() []ent.Field { + return append(defaultFields, + field.String("name").Unique().NotEmpty(), + field.String("version").NotEmpty(), + field.String("options_hash").NotEmpty(), + ) +} + +// Edges of the ApplicationCommand. +func (ApplicationCommand) Edges() []ent.Edge { + return nil +} + +func (ApplicationCommand) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("options_hash"), + } +} diff --git a/internal/database/ent/schema/clan.go b/internal/database/ent/schema/clan.go new file mode 100644 index 00000000..9b953820 --- /dev/null +++ b/internal/database/ent/schema/clan.go @@ -0,0 +1,65 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// model Clan { +// id String @id +// createdAt DateTime +// updatedAt DateTime @updatedAt + +// tag String +// name String +// emblemId String @default("") + +// accounts Account[] +// membersString String + +// recordUpdatedAt DateTime @updatedAt + +// @@index([tag]) +// @@map("account_clans") +// } + +// Clan holds the schema definition for the Clan entity. +type Clan struct { + ent.Schema +} + +// Fields of the Clan. +func (Clan) Fields() []ent.Field { + return []ent.Field{ + field.String("id"). + Unique(). + Immutable(), + field.Int("created_at"). + Immutable(). + DefaultFunc(timeNow), + field.Int("updated_at"). + DefaultFunc(timeNow). + UpdateDefault(timeNow), + // + field.String("tag").NotEmpty(), + field.String("name").NotEmpty(), + field.String("emblem_id").Default("").Optional(), + field.Strings("members"), + } +} + +// Edges of the Clan. +func (Clan) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("accounts", Account.Type), + } +} + +func (Clan) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("tag"), + index.Fields("name"), + } +} diff --git a/internal/database/ent/schema/crontask.go b/internal/database/ent/schema/crontask.go new file mode 100644 index 00000000..f214f55d --- /dev/null +++ b/internal/database/ent/schema/crontask.go @@ -0,0 +1,43 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/cufee/aftermath/internal/database/models" +) + +// CronTask holds the schema definition for the CronTask entity. +type CronTask struct { + ent.Schema +} + +// Fields of the CronTask. +func (CronTask) Fields() []ent.Field { + return append(defaultFields, + field.String("type").NotEmpty(), + field.String("reference_id").NotEmpty(), + field.Strings("targets"), + // + field.String("status").NotEmpty(), + field.Int("scheduled_after"), + field.Int("last_run"), + // + field.JSON("logs", []models.TaskLog{}), + field.JSON("data", map[string]any{}), + ) +} + +// Edges of the CronTask. +func (CronTask) Edges() []ent.Edge { + return nil +} + +func (CronTask) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("reference_id"), + index.Fields("status", "last_run"), + index.Fields("status", "created_at"), + index.Fields("status", "scheduled_after"), + } +} diff --git a/internal/database/ent/schema/defaults.go b/internal/database/ent/schema/defaults.go new file mode 100644 index 00000000..ddf92c1c --- /dev/null +++ b/internal/database/ent/schema/defaults.go @@ -0,0 +1,24 @@ +package schema + +import ( + "time" + + "entgo.io/ent" + "entgo.io/ent/schema/field" + "github.com/lucsky/cuid" +) + +var defaultFields = []ent.Field{ + field.String("id"). + Unique(). + Immutable(). + DefaultFunc(cuid.New), + field.Int("created_at"). + Immutable(). + DefaultFunc(timeNow), + field.Int("updated_at"). + DefaultFunc(timeNow). + UpdateDefault(timeNow), +} + +func timeNow() int { return int(time.Now().Unix()) } diff --git a/internal/database/ent/schema/user.go b/internal/database/ent/schema/user.go new file mode 100644 index 00000000..188f638f --- /dev/null +++ b/internal/database/ent/schema/user.go @@ -0,0 +1,43 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" +) + +// User holds the schema definition for the User entity. +type User struct { + ent.Schema +} + +// Fields of the User. +func (User) Fields() []ent.Field { + return []ent.Field{ + field.String("id"). + Unique(). + Immutable(), + field.Int("created_at"). + Immutable(). + DefaultFunc(timeNow), + field.Int("updated_at"). + DefaultFunc(timeNow). + UpdateDefault(timeNow), + // + field.String("permissions").Default(""), + field.Strings("feature_flags").Optional(), + } +} + +// Edges of the User. +func (User) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("subscriptions", UserSubscription.Type), + edge.To("connections", UserConnection.Type), + edge.To("content", UserContent.Type), + } +} + +func (User) Indexes() []ent.Index { + return []ent.Index{} +} diff --git a/internal/database/ent/schema/userconnection.go b/internal/database/ent/schema/userconnection.go new file mode 100644 index 00000000..bc04fe6b --- /dev/null +++ b/internal/database/ent/schema/userconnection.go @@ -0,0 +1,64 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/cufee/aftermath/internal/database/models" +) + +// model UserConnection { +// id String @id @default(cuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt + +// user User @relation(fields: [userId], references: [id]) +// userId String + +// type String +// permissions String +// referenceId String + +// metadataEncoded Bytes + +// @@index([userId]) +// @@index([type, userId]) +// @@index([referenceId]) +// @@index([type, referenceId]) +// @@map("user_connections") +// } + +// UserConnection holds the schema definition for the UserConnection entity. +type UserConnection struct { + ent.Schema +} + +// Fields of the UserConnection. +func (UserConnection) Fields() []ent.Field { + return append(defaultFields, + field.Enum("type"). + GoType(models.ConnectionType("")), + field.String("user_id").Immutable(), + field.String("reference_id"), + field.String("permissions").Default("").Optional(), + field.JSON("metadata", map[string]any{}).Optional(), + ) +} + +// Edges of the UserConnection. +func (UserConnection) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("user", User.Type).Ref("connections").Field("user_id").Required().Immutable().Unique(), + } +} + +func (UserConnection) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("user_id"), + index.Fields("type", "user_id"), + index.Fields("reference_id"), + index.Fields("type", "reference_id"), + // index.Fields("reference_id").Edges("user").Unique(), + } +} diff --git a/internal/database/ent/schema/usercontent.go b/internal/database/ent/schema/usercontent.go new file mode 100644 index 00000000..9895f2b6 --- /dev/null +++ b/internal/database/ent/schema/usercontent.go @@ -0,0 +1,43 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserContent holds the schema definition for the UserContent entity. +type UserContent struct { + ent.Schema +} + +// Fields of the UserContent. +func (UserContent) Fields() []ent.Field { + return append(defaultFields, + field.Enum("type"). + GoType(models.UserContentType("")), + field.String("user_id").Immutable(), + field.String("reference_id"), + // + field.Any("value"), + field.JSON("metadata", map[string]any{}), + ) +} + +// Edges of the UserContent. +func (UserContent) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("user", User.Type).Ref("content").Field("user_id").Required().Immutable().Unique(), + } +} + +func (UserContent) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("user_id"), + index.Fields("type", "user_id"), + index.Fields("reference_id"), + index.Fields("type", "reference_id"), + } +} diff --git a/internal/database/ent/schema/usersubscription.go b/internal/database/ent/schema/usersubscription.go new file mode 100644 index 00000000..2394efdb --- /dev/null +++ b/internal/database/ent/schema/usersubscription.go @@ -0,0 +1,43 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/cufee/aftermath/internal/database/models" +) + +// UserSubscription holds the schema definition for the UserSubscription entity. +type UserSubscription struct { + ent.Schema +} + +// Fields of the UserSubscription. +func (UserSubscription) Fields() []ent.Field { + return append(defaultFields, + field.Enum("type"). + GoType(models.SubscriptionType("")), + field.Int("expires_at"), + // + field.String("user_id").NotEmpty().Immutable(), + field.String("permissions").NotEmpty(), + field.String("reference_id").NotEmpty(), + ) +} + +// Edges of the UserSubscription. +func (UserSubscription) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("user", User.Type).Ref("subscriptions").Field("user_id").Required().Immutable().Unique(), + } +} + +func (UserSubscription) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("user_id"), + index.Fields("type", "user_id"), + index.Fields("expires_at"), + index.Fields("expires_at", "user_id"), + } +} diff --git a/internal/database/ent/schema/vehicle.go b/internal/database/ent/schema/vehicle.go new file mode 100644 index 00000000..525287a0 --- /dev/null +++ b/internal/database/ent/schema/vehicle.go @@ -0,0 +1,41 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" +) + +// Vehicle holds the schema definition for the Vehicle entity. +type Vehicle struct { + ent.Schema +} + +// Fields of the Vehicle. +func (Vehicle) Fields() []ent.Field { + return []ent.Field{ + field.String("id"). + Unique(). + Immutable(), + field.Int("created_at"). + Immutable(). + DefaultFunc(timeNow), + field.Int("updated_at"). + DefaultFunc(timeNow). + UpdateDefault(timeNow), + // + field.Int("tier"). + Min(0). // vehicle that does not exist in official glossary has tier set to 0 + Max(10), + field.JSON("localized_names", map[string]string{}), + } + +} + +// Edges of the Vehicle. +func (Vehicle) Edges() []ent.Edge { + return nil +} + +func (Vehicle) Indexes() []ent.Index { + return nil +} diff --git a/internal/database/ent/schema/vehicleaverage.go b/internal/database/ent/schema/vehicleaverage.go new file mode 100644 index 00000000..edb1798a --- /dev/null +++ b/internal/database/ent/schema/vehicleaverage.go @@ -0,0 +1,38 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// VehicleAverage holds the schema definition for the VehicleAverage entity. +type VehicleAverage struct { + ent.Schema +} + +// Fields of the VehicleAverage. +func (VehicleAverage) Fields() []ent.Field { + return []ent.Field{ + field.String("id"). + Unique(). + Immutable(), + field.Int("created_at"). + Immutable(). + DefaultFunc(timeNow), + field.Int("updated_at"). + DefaultFunc(timeNow). + UpdateDefault(timeNow), + // + field.JSON("data", map[string]frame.StatsFrame{}), + } +} + +// Edges of the VehicleAverage. +func (VehicleAverage) Edges() []ent.Edge { + return nil +} + +func (VehicleAverage) Indexes() []ent.Index { + return nil +} diff --git a/internal/database/ent/schema/vehiclesnapshot.go b/internal/database/ent/schema/vehiclesnapshot.go new file mode 100644 index 00000000..88dc1fa1 --- /dev/null +++ b/internal/database/ent/schema/vehiclesnapshot.go @@ -0,0 +1,47 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" +) + +// VehicleSnapshot holds the schema definition for the VehicleSnapshot entity. +type VehicleSnapshot struct { + ent.Schema +} + +// Fields of the VehicleSnapshot. +func (VehicleSnapshot) Fields() []ent.Field { + return append(defaultFields, + field.Enum("type"). + GoType(models.SnapshotType("")), + // + field.String("account_id").NotEmpty().Immutable(), + field.String("vehicle_id").NotEmpty().Immutable(), + field.String("reference_id").NotEmpty(), + // + field.Int("battles"), + field.Int("last_battle_time"), + field.JSON("frame", frame.StatsFrame{}), + ) +} + +// Edges of the VehicleSnapshot. +func (VehicleSnapshot) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("account", Account.Type).Ref("vehicle_snapshots").Field("account_id").Required().Immutable().Unique(), + } +} + +func (VehicleSnapshot) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("created_at"), + index.Fields("vehicle_id", "created_at"), + index.Fields("account_id", "created_at"), + index.Fields("account_id", "type", "created_at"), + } +} diff --git a/internal/database/errors.go b/internal/database/errors.go new file mode 100644 index 00000000..3bf7ea53 --- /dev/null +++ b/internal/database/errors.go @@ -0,0 +1,7 @@ +package database + +import "github.com/cufee/aftermath/internal/database/ent/db" + +func IsNotFound(err error) bool { + return db.IsNotFound(err) +} diff --git a/internal/database/models/account.go b/internal/database/models/account.go new file mode 100644 index 00000000..773bfdca --- /dev/null +++ b/internal/database/models/account.go @@ -0,0 +1,16 @@ +package models + +import "time" + +type Account struct { + ID string + Realm string + Nickname string + + Private bool + CreatedAt time.Time + LastBattleTime time.Time + + ClanID string + ClanTag string +} diff --git a/internal/database/models/account_snapshot.go b/internal/database/models/account_snapshot.go new file mode 100644 index 00000000..21912618 --- /dev/null +++ b/internal/database/models/account_snapshot.go @@ -0,0 +1,18 @@ +package models + +import ( + "time" + + "github.com/cufee/aftermath/internal/stats/frame" +) + +type AccountSnapshot struct { + ID string + Type SnapshotType + CreatedAt time.Time + AccountID string + ReferenceID string + LastBattleTime time.Time + RatingBattles frame.StatsFrame + RegularBattles frame.StatsFrame +} diff --git a/internal/database/models/application_command.go b/internal/database/models/application_command.go new file mode 100644 index 00000000..b3df3693 --- /dev/null +++ b/internal/database/models/application_command.go @@ -0,0 +1,8 @@ +package models + +type ApplicationCommand struct { + ID string + Hash string + Name string + Version string +} diff --git a/internal/database/models/task.go b/internal/database/models/task.go new file mode 100644 index 00000000..bb1ad319 --- /dev/null +++ b/internal/database/models/task.go @@ -0,0 +1,139 @@ +package models + +import ( + "encoding/json" + "strings" + "time" + + "github.com/cufee/aftermath/internal/encoding" +) + +type TaskType string + +const ( + TaskTypeUpdateClans TaskType = "UPDATE_CLANS" + TaskTypeRecordSessions TaskType = "RECORD_ACCOUNT_SESSIONS" + TaskTypeUpdateAccountWN8 TaskType = "UPDATE_ACCOUNT_WN8" + TaskTypeRecordPlayerAchievements TaskType = "UPDATE_ACCOUNT_ACHIEVEMENTS" + + TaskTypeDatabaseCleanup TaskType = "CLEANUP_DATABASE" +) + +// Values provides list valid values for Enum. +func (TaskType) Values() []string { + var kinds []string + for _, s := range []TaskType{ + TaskTypeUpdateClans, + TaskTypeRecordSessions, + TaskTypeUpdateAccountWN8, + TaskTypeRecordPlayerAchievements, + // + TaskTypeDatabaseCleanup, + } { + kinds = append(kinds, string(s)) + } + return kinds +} + +// Task statuses +type TaskStatus string + +const ( + TaskStatusScheduled TaskStatus = "TASK_SCHEDULED" + TaskStatusInProgress TaskStatus = "TASK_IN_PROGRESS" + TaskStatusComplete TaskStatus = "TASK_COMPLETE" + TaskStatusFailed TaskStatus = "TASK_FAILED" +) + +// Values provides list valid values for Enum. +func (TaskStatus) Values() []string { + var kinds []string + for _, s := range []TaskStatus{ + TaskStatusScheduled, + TaskStatusInProgress, + TaskStatusComplete, + TaskStatusFailed, + } { + kinds = append(kinds, string(s)) + } + return kinds +} + +type Task struct { + ID string `json:"id"` + Type TaskType `json:"kind"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + ReferenceID string `json:"referenceId"` + Targets []string `json:"targets"` + + Logs []TaskLog `json:"logs"` + + Status TaskStatus `json:"status"` + ScheduledAfter time.Time `json:"scheduledAfter"` + LastRun time.Time `json:"lastRun"` + + Data map[string]any `json:"data"` +} + +func (t *Task) LogAttempt(log TaskLog) { + t.Logs = append(t.Logs, log) +} + +func (t *Task) OnCreated() { + t.LastRun = time.Now() + t.CreatedAt = time.Now() + t.UpdatedAt = time.Now() +} +func (t *Task) OnUpdated() { + t.UpdatedAt = time.Now() +} + +func (t *Task) encodeTargets() []byte { + return []byte(strings.Join(t.Targets, ";")) +} +func (t *Task) decodeTargets(targets []byte) { + if string(targets) != "" { + t.Targets = strings.Split(string(targets), ";") + } +} + +func (t *Task) encodeLogs() []byte { + if t.Logs == nil { + return []byte{} + } + data, _ := json.Marshal(t.Logs) + return data +} +func (t *Task) decodeLogs(logs []byte) { + _ = json.Unmarshal(logs, &t.Logs) +} + +func (t *Task) encodeData() []byte { + if t.Data == nil { + return []byte{} + } + data, _ := encoding.EncodeGob(t.Data) + return data +} +func (t *Task) decodeData(data []byte) { + t.Data = make(map[string]any) + _ = encoding.DecodeGob(data, &t.Data) +} + +type TaskLog struct { + Targets []string `json:"targets" bson:"targets"` + Timestamp time.Time `json:"timestamp" bson:"timestamp"` + Comment string `json:"result" bson:"result"` + Error string `json:"error" bson:"error"` +} + +func NewAttemptLog(task Task, comment, err string) TaskLog { + return TaskLog{ + Targets: task.Targets, + Timestamp: time.Now(), + Comment: comment, + Error: err, + } +} diff --git a/internal/database/models/user.go b/internal/database/models/user.go new file mode 100644 index 00000000..ae6172c4 --- /dev/null +++ b/internal/database/models/user.go @@ -0,0 +1,30 @@ +package models + +import "github.com/cufee/aftermath/internal/permissions" + +type User struct { + ID string + + Permissions permissions.Permissions + + Connections []UserConnection + Subscriptions []UserSubscription +} + +func (u User) Connection(kind ConnectionType) (UserConnection, bool) { + for _, connection := range u.Connections { + if connection.Type == kind { + return connection, true + } + } + return UserConnection{}, false +} + +func (u User) Subscription(kind SubscriptionType) (UserSubscription, bool) { + for _, subscription := range u.Subscriptions { + if subscription.Type == kind { + return subscription, true + } + } + return UserSubscription{}, false +} diff --git a/internal/database/models/user_connection.go b/internal/database/models/user_connection.go new file mode 100644 index 00000000..6dbb2590 --- /dev/null +++ b/internal/database/models/user_connection.go @@ -0,0 +1,32 @@ +package models + +import "github.com/cufee/aftermath/internal/permissions" + +type ConnectionType string + +const ( + ConnectionTypeWargaming = ConnectionType("wargaming") +) + +// Values provides list valid values for Enum. +func (ConnectionType) Values() []string { + var kinds []string + for _, s := range []ConnectionType{ + ConnectionTypeWargaming, + } { + kinds = append(kinds, string(s)) + } + return kinds +} + +type UserConnection struct { + ID string `json:"id"` + + Type ConnectionType `json:"type"` + + UserID string `json:"userId"` + ReferenceID string `json:"referenceId"` + Permissions permissions.Permissions `json:"permissions"` + + Metadata map[string]any `json:"metadata"` +} diff --git a/internal/database/models/user_subscription.go b/internal/database/models/user_subscription.go new file mode 100644 index 00000000..07926bf3 --- /dev/null +++ b/internal/database/models/user_subscription.go @@ -0,0 +1,106 @@ +package models + +import ( + "slices" + "time" + + "github.com/cufee/aftermath/internal/permissions" +) + +type SubscriptionType string + +func (s SubscriptionType) GetPermissions() permissions.Permissions { + switch s { + case SubscriptionTypePlus: + return permissions.SubscriptionAftermathPlus + case SubscriptionTypePro: + return permissions.SubscriptionAftermathPro + case SubscriptionTypeProClan: + return permissions.SubscriptionAftermathPro + default: + return permissions.User + } +} + +// Paid +const SubscriptionTypePro = SubscriptionType("aftermath-pro") +const SubscriptionTypeProClan = SubscriptionType("aftermath-pro-clan") +const SubscriptionTypePlus = SubscriptionType("aftermath-plus") + +// Misc +const SubscriptionTypeSupporter = SubscriptionType("supporter") +const SubscriptionTypeVerifiedClan = SubscriptionType("verified-clan") + +// Moderators +const SubscriptionTypeServerModerator = SubscriptionType("server-moderator") +const SubscriptionTypeContentModerator = SubscriptionType("content-moderator") + +// Special +const SubscriptionTypeDeveloper = SubscriptionType("developer") +const SubscriptionTypeServerBooster = SubscriptionType("server-booster") +const SubscriptionTypeContentTranslator = SubscriptionType("content-translator") + +var AllSubscriptionTypes = []SubscriptionType{ + SubscriptionTypePro, + SubscriptionTypeProClan, + SubscriptionTypePlus, + SubscriptionTypeSupporter, + SubscriptionTypeVerifiedClan, + SubscriptionTypeServerModerator, + SubscriptionTypeContentModerator, + SubscriptionTypeDeveloper, + SubscriptionTypeServerBooster, + SubscriptionTypeContentTranslator, +} + +// Values provides list valid values for Enum. +func (SubscriptionType) Values() []string { + var kinds []string + for _, s := range AllSubscriptionTypes { + kinds = append(kinds, string(s)) + } + return kinds +} + +func (s SubscriptionType) Valid() bool { + return slices.Contains(AllSubscriptionTypes, s) +} + +type UserContentType string + +const ( + UserContentTypeClanBackground = UserContentType("clan-background-image") + UserContentTypePersonalBackground = UserContentType("personal-background-image") +) + +func (t UserContentType) Valid() bool { + switch t { + case UserContentTypeClanBackground: + return true + case UserContentTypePersonalBackground: + return true + default: + return false + } +} + +// Values provides list valid values for Enum. +func (UserContentType) Values() []string { + var kinds []string + for _, s := range []UserContentType{ + UserContentTypeClanBackground, + UserContentTypePersonalBackground, + } { + kinds = append(kinds, string(s)) + } + return kinds +} + +type UserSubscription struct { + ID string + Type SubscriptionType + UserID string + ExpiresAt time.Time + ReferenceID string + Permissions permissions.Permissions +} diff --git a/internal/database/models/vehicle.go b/internal/database/models/vehicle.go new file mode 100644 index 00000000..b9f04a4e --- /dev/null +++ b/internal/database/models/vehicle.go @@ -0,0 +1,23 @@ +package models + +import "golang.org/x/text/language" + +type Vehicle struct { + ID string + Tier int + Type string + Class string + Nation string + + LocalizedNames map[string]string +} + +func (v Vehicle) Name(locale language.Tag) string { + if n := v.LocalizedNames[locale.String()]; n != "" { + return n + } + if n := v.LocalizedNames[language.English.String()]; n != "" { + return n + } + return "Secret Tank" +} diff --git a/internal/database/models/vehicle_snapshot.go b/internal/database/models/vehicle_snapshot.go new file mode 100644 index 00000000..c3152ce5 --- /dev/null +++ b/internal/database/models/vehicle_snapshot.go @@ -0,0 +1,40 @@ +package models + +import ( + "time" + + "github.com/cufee/aftermath/internal/stats/frame" +) + +type SnapshotType string + +const ( + SnapshotTypeLive SnapshotType = "live" + SnapshotTypeDaily SnapshotType = "daily" +) + +// Values provides list valid values for Enum. +func (SnapshotType) Values() []string { + var kinds []string + for _, s := range []SnapshotType{ + SnapshotTypeLive, + SnapshotTypeDaily, + } { + kinds = append(kinds, string(s)) + } + return kinds +} + +type VehicleSnapshot struct { + ID string + CreatedAt time.Time + + Type SnapshotType + LastBattleTime time.Time + + AccountID string + VehicleID string + ReferenceID string + + Stats frame.StatsFrame +} diff --git a/internal/database/prisma/db/.gitignore b/internal/database/prisma/db/.gitignore deleted file mode 100644 index a0c7514a..00000000 --- a/internal/database/prisma/db/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# gitignore generated by Prisma Client Go. DO NOT EDIT. -*_gen.go diff --git a/internal/database/prisma/migrations/20240608224040_init/migration.sql b/internal/database/prisma/migrations/20240608224040_init/migration.sql deleted file mode 100644 index 9b2a667d..00000000 --- a/internal/database/prisma/migrations/20240608224040_init/migration.sql +++ /dev/null @@ -1,210 +0,0 @@ --- CreateTable -CREATE TABLE "app_configurations" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "valueEncoded" BLOB NOT NULL, - "metadataEncoded" BLOB NOT NULL -); - --- CreateTable -CREATE TABLE "cron_tasks" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "type" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "targetsEncoded" BLOB NOT NULL, - "status" TEXT NOT NULL, - "lastRun" DATETIME NOT NULL, - "scheduledAfter" DATETIME NOT NULL, - "logsEncoded" BLOB NOT NULL, - "dataEncoded" BLOB NOT NULL -); - --- CreateTable -CREATE TABLE "users" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "permissions" TEXT NOT NULL, - "featureFlags" INTEGER NOT NULL DEFAULT 0 -); - --- CreateTable -CREATE TABLE "user_subscriptions" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "userId" TEXT NOT NULL, - "type" TEXT NOT NULL, - "expiresAt" DATETIME NOT NULL, - "referenceId" TEXT NOT NULL, - "permissions" TEXT NOT NULL, - CONSTRAINT "user_subscriptions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "user_connections" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "userId" TEXT NOT NULL, - "type" TEXT NOT NULL, - "permissions" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "metadataEncoded" BLOB NOT NULL, - CONSTRAINT "user_connections_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "user_content" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "userId" TEXT NOT NULL, - "type" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "valueEncoded" BLOB NOT NULL, - "metadataEncoded" BLOB NOT NULL, - CONSTRAINT "user_content_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "accounts" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "lastBattleTime" DATETIME NOT NULL, - "accountCreatedAt" DATETIME NOT NULL, - "realm" TEXT NOT NULL, - "nickname" TEXT NOT NULL, - "private" BOOLEAN NOT NULL DEFAULT false, - "clanId" TEXT, - CONSTRAINT "accounts_clanId_fkey" FOREIGN KEY ("clanId") REFERENCES "account_clans" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "account_clans" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL, - "updatedAt" DATETIME NOT NULL, - "tag" TEXT NOT NULL, - "name" TEXT NOT NULL, - "emblemId" TEXT NOT NULL DEFAULT '', - "membersString" TEXT NOT NULL, - "recordUpdatedAt" DATETIME NOT NULL -); - --- CreateTable -CREATE TABLE "account_snapshots" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL, - "type" TEXT NOT NULL, - "lastBattleTime" DATETIME NOT NULL, - "accountId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "frameEncoded" BLOB NOT NULL -); - --- CreateTable -CREATE TABLE "vehicle_snapshots" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL, - "type" TEXT NOT NULL, - "lastBattleTime" DATETIME NOT NULL, - "accountId" TEXT NOT NULL, - "vehicleId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "frameEncoded" BLOB NOT NULL -); - --- CreateTable -CREATE TABLE "achievements_snapshots" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "accountId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "dataEncoded" BLOB NOT NULL -); - --- CreateTable -CREATE TABLE "glossary_averages" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "dataEncoded" BLOB NOT NULL -); - --- CreateTable -CREATE TABLE "glossary_vehicles" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "tier" INTEGER NOT NULL, - "type" TEXT NOT NULL, - "class" TEXT NOT NULL, - "nation" TEXT NOT NULL, - "localizedNamesEncoded" BLOB NOT NULL -); - --- CreateIndex -CREATE INDEX "cron_tasks_referenceId_idx" ON "cron_tasks"("referenceId"); - --- CreateIndex -CREATE INDEX "cron_tasks_status_referenceId_scheduledAfter_idx" ON "cron_tasks"("status", "referenceId", "scheduledAfter"); - --- CreateIndex -CREATE INDEX "cron_tasks_lastRun_status_idx" ON "cron_tasks"("lastRun", "status"); - --- CreateIndex -CREATE INDEX "user_connections_userId_idx" ON "user_connections"("userId"); - --- CreateIndex -CREATE INDEX "user_connections_type_userId_idx" ON "user_connections"("type", "userId"); - --- CreateIndex -CREATE INDEX "user_connections_referenceId_idx" ON "user_connections"("referenceId"); - --- CreateIndex -CREATE INDEX "user_connections_type_referenceId_idx" ON "user_connections"("type", "referenceId"); - --- CreateIndex -CREATE INDEX "user_content_userId_idx" ON "user_content"("userId"); - --- CreateIndex -CREATE INDEX "user_content_type_userId_idx" ON "user_content"("type", "userId"); - --- CreateIndex -CREATE INDEX "user_content_referenceId_idx" ON "user_content"("referenceId"); - --- CreateIndex -CREATE INDEX "user_content_type_referenceId_idx" ON "user_content"("type", "referenceId"); - --- CreateIndex -CREATE INDEX "accounts_realm_idx" ON "accounts"("realm"); - --- CreateIndex -CREATE INDEX "accounts_realm_lastBattleTime_idx" ON "accounts"("realm", "lastBattleTime"); - --- CreateIndex -CREATE INDEX "accounts_id_lastBattleTime_idx" ON "accounts"("id", "lastBattleTime"); - --- CreateIndex -CREATE INDEX "accounts_clanId_idx" ON "accounts"("clanId"); - --- CreateIndex -CREATE INDEX "account_clans_tag_idx" ON "account_clans"("tag"); - --- CreateIndex -CREATE INDEX "account_snapshots_createdAt_idx" ON "account_snapshots"("createdAt"); - --- CreateIndex -CREATE INDEX "account_snapshots_accountId_lastBattleTime_idx" ON "account_snapshots"("accountId", "lastBattleTime"); - --- CreateIndex -CREATE INDEX "vehicle_snapshots_createdAt_idx" ON "vehicle_snapshots"("createdAt"); - --- CreateIndex -CREATE INDEX "vehicle_snapshots_accountId_vehicleId_lastBattleTime_idx" ON "vehicle_snapshots"("accountId", "vehicleId", "lastBattleTime"); diff --git a/internal/database/prisma/migrations/20240611180703_added_snapshots/migration.sql b/internal/database/prisma/migrations/20240611180703_added_snapshots/migration.sql deleted file mode 100644 index 6a503643..00000000 --- a/internal/database/prisma/migrations/20240611180703_added_snapshots/migration.sql +++ /dev/null @@ -1,48 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `frameEncoded` on the `account_snapshots` table. All the data in the column will be lost. - - Added the required column `battles` to the `vehicle_snapshots` table without a default value. This is not possible if the table is not empty. - - Added the required column `ratingBattles` to the `account_snapshots` table without a default value. This is not possible if the table is not empty. - - Added the required column `ratingFrameEncoded` to the `account_snapshots` table without a default value. This is not possible if the table is not empty. - - Added the required column `regularBattles` to the `account_snapshots` table without a default value. This is not possible if the table is not empty. - - Added the required column `regularFrameEncoded` to the `account_snapshots` table without a default value. This is not possible if the table is not empty. - -*/ --- RedefineTables -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_vehicle_snapshots" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL, - "type" TEXT NOT NULL, - "lastBattleTime" DATETIME NOT NULL, - "accountId" TEXT NOT NULL, - "vehicleId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "battles" INTEGER NOT NULL, - "frameEncoded" BLOB NOT NULL -); -INSERT INTO "new_vehicle_snapshots" ("accountId", "createdAt", "frameEncoded", "id", "lastBattleTime", "referenceId", "type", "vehicleId") SELECT "accountId", "createdAt", "frameEncoded", "id", "lastBattleTime", "referenceId", "type", "vehicleId" FROM "vehicle_snapshots"; -DROP TABLE "vehicle_snapshots"; -ALTER TABLE "new_vehicle_snapshots" RENAME TO "vehicle_snapshots"; -CREATE INDEX "vehicle_snapshots_createdAt_idx" ON "vehicle_snapshots"("createdAt"); -CREATE INDEX "vehicle_snapshots_accountId_vehicleId_lastBattleTime_idx" ON "vehicle_snapshots"("accountId", "vehicleId", "lastBattleTime"); -CREATE TABLE "new_account_snapshots" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL, - "type" TEXT NOT NULL, - "lastBattleTime" DATETIME NOT NULL, - "accountId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "ratingBattles" INTEGER NOT NULL, - "ratingFrameEncoded" BLOB NOT NULL, - "regularBattles" INTEGER NOT NULL, - "regularFrameEncoded" BLOB NOT NULL -); -INSERT INTO "new_account_snapshots" ("accountId", "createdAt", "id", "lastBattleTime", "referenceId", "type") SELECT "accountId", "createdAt", "id", "lastBattleTime", "referenceId", "type" FROM "account_snapshots"; -DROP TABLE "account_snapshots"; -ALTER TABLE "new_account_snapshots" RENAME TO "account_snapshots"; -CREATE INDEX "account_snapshots_createdAt_idx" ON "account_snapshots"("createdAt"); -CREATE INDEX "account_snapshots_accountId_lastBattleTime_idx" ON "account_snapshots"("accountId", "lastBattleTime"); -PRAGMA foreign_key_check; -PRAGMA foreign_keys=ON; diff --git a/internal/database/prisma/migrations/20240612202803_sessions/migration.sql b/internal/database/prisma/migrations/20240612202803_sessions/migration.sql deleted file mode 100644 index 0d46d0ba..00000000 --- a/internal/database/prisma/migrations/20240612202803_sessions/migration.sql +++ /dev/null @@ -1,50 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `updatedAt` on the `achievements_snapshots` table. All the data in the column will be lost. - -*/ --- DropIndex -DROP INDEX "account_snapshots_accountId_lastBattleTime_idx"; - --- DropIndex -DROP INDEX "vehicle_snapshots_accountId_vehicleId_lastBattleTime_idx"; - --- RedefineTables -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_achievements_snapshots" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL, - "accountId" TEXT NOT NULL, - "referenceId" TEXT NOT NULL, - "dataEncoded" BLOB NOT NULL -); -INSERT INTO "new_achievements_snapshots" ("accountId", "createdAt", "dataEncoded", "id", "referenceId") SELECT "accountId", "createdAt", "dataEncoded", "id", "referenceId" FROM "achievements_snapshots"; -DROP TABLE "achievements_snapshots"; -ALTER TABLE "new_achievements_snapshots" RENAME TO "achievements_snapshots"; -CREATE INDEX "achievements_snapshots_createdAt_idx" ON "achievements_snapshots"("createdAt"); -CREATE INDEX "achievements_snapshots_accountId_referenceId_idx" ON "achievements_snapshots"("accountId", "referenceId"); -CREATE INDEX "achievements_snapshots_accountId_referenceId_createdAt_idx" ON "achievements_snapshots"("accountId", "referenceId", "createdAt"); -PRAGMA foreign_key_check; -PRAGMA foreign_keys=ON; - --- CreateIndex -CREATE INDEX "account_snapshots_type_accountId_referenceId_idx" ON "account_snapshots"("type", "accountId", "referenceId"); - --- CreateIndex -CREATE INDEX "account_snapshots_type_accountId_referenceId_createdAt_idx" ON "account_snapshots"("type", "accountId", "referenceId", "createdAt"); - --- CreateIndex -CREATE INDEX "vehicle_snapshots_vehicleId_createdAt_idx" ON "vehicle_snapshots"("vehicleId", "createdAt"); - --- CreateIndex -CREATE INDEX "vehicle_snapshots_accountId_referenceId_idx" ON "vehicle_snapshots"("accountId", "referenceId"); - --- CreateIndex -CREATE INDEX "vehicle_snapshots_accountId_referenceId_vehicleId_idx" ON "vehicle_snapshots"("accountId", "referenceId", "vehicleId"); - --- CreateIndex -CREATE INDEX "vehicle_snapshots_accountId_referenceId_type_idx" ON "vehicle_snapshots"("accountId", "referenceId", "type"); - --- CreateIndex -CREATE INDEX "vehicle_snapshots_accountId_referenceId_createdAt_idx" ON "vehicle_snapshots"("accountId", "referenceId", "createdAt"); diff --git a/internal/database/prisma/migrations/20240614000132_added_app_commands/migration.sql b/internal/database/prisma/migrations/20240614000132_added_app_commands/migration.sql deleted file mode 100644 index b8a4abf1..00000000 --- a/internal/database/prisma/migrations/20240614000132_added_app_commands/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ --- CreateTable -CREATE TABLE "application_commands" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "version" TEXT NOT NULL, - "optionsHash" TEXT NOT NULL -); - --- CreateIndex -CREATE INDEX "account_snapshots_type_accountId_createdAt_idx" ON "account_snapshots"("type", "accountId", "createdAt"); diff --git a/internal/database/prisma/migrations/20240614000247_added_index/migration.sql b/internal/database/prisma/migrations/20240614000247_added_index/migration.sql deleted file mode 100644 index 428604b0..00000000 --- a/internal/database/prisma/migrations/20240614000247_added_index/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- CreateIndex -CREATE INDEX "application_commands_optionsHash_idx" ON "application_commands"("optionsHash"); diff --git a/internal/database/prisma/migrations/20240614002652_added_name/migration.sql b/internal/database/prisma/migrations/20240614002652_added_name/migration.sql deleted file mode 100644 index c7c9687b..00000000 --- a/internal/database/prisma/migrations/20240614002652_added_name/migration.sql +++ /dev/null @@ -1,22 +0,0 @@ -/* - Warnings: - - - Added the required column `name` to the `application_commands` table without a default value. This is not possible if the table is not empty. - -*/ --- RedefineTables -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_application_commands" ( - "id" TEXT NOT NULL PRIMARY KEY, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "name" TEXT NOT NULL, - "version" TEXT NOT NULL, - "optionsHash" TEXT NOT NULL -); -INSERT INTO "new_application_commands" ("createdAt", "id", "optionsHash", "updatedAt", "version") SELECT "createdAt", "id", "optionsHash", "updatedAt", "version" FROM "application_commands"; -DROP TABLE "application_commands"; -ALTER TABLE "new_application_commands" RENAME TO "application_commands"; -CREATE INDEX "application_commands_optionsHash_idx" ON "application_commands"("optionsHash"); -PRAGMA foreign_key_check; -PRAGMA foreign_keys=ON; diff --git a/internal/database/prisma/migrations/migration_lock.toml b/internal/database/prisma/migrations/migration_lock.toml deleted file mode 100644 index e5e5c470..00000000 --- a/internal/database/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "sqlite" \ No newline at end of file diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma deleted file mode 100644 index 04274134..00000000 --- a/internal/database/prisma/schema.prisma +++ /dev/null @@ -1,398 +0,0 @@ -datasource db { - provider = "sqlite" - url = env("DATABASE_URL") -} - -generator db { - provider = "go run github.com/steebchen/prisma-client-go" -} - -// -// === App Data === -// - -// Stores some long-lived app condiguration values -// the values here would typically be set programmatically -model AppConfiguration { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - valueEncoded Bytes - metadataEncoded Bytes - - @@map("app_configurations") -} - -// A record of a cron task -model CronTask { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - type String - referenceId String - targetsEncoded Bytes - - status String - lastRun DateTime - scheduledAfter DateTime - - logsEncoded Bytes - dataEncoded Bytes - - @@index([referenceId]) - @@index([status, referenceId, scheduledAfter]) - @@index([lastRun, status]) - @@map("cron_tasks") -} - -// -// === Authentification === -// - -// // A single-use nonce value generated during the auth flow -// model AuthNonce { -// id String @id @default(cuid()) -// createdAt DateTime @default(now()) -// updatedAt DateTime @updatedAt - -// expiresAt DateTime - -// referenceId String -// metadataEncoded Bytes - -// @@index([createdAt]) -// @@index([referenceId]) -// @@index([referenceId, expiresAt]) -// @@map("auth_nonces") -// @@ignore -// } - -// -// === Users === -// - -// User record withing the app, primary id is a Discord user ID -model User { - id String @id - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - permissions String - featureFlags Int @default(0) - - subscriptions UserSubscription[] - connections UserConnection[] - content UserContent[] - - @@map("users") -} - -// A subscription record for a user -model UserSubscription { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - user User @relation(fields: [userId], references: [id]) - userId String - - type String - expiresAt DateTime - referenceId String - permissions String - - @@map("user_subscriptions") -} - -// A connection to some external service for a user, like Google or Wargaming -// - ReferenceID here would be a user id received from the external service -model UserConnection { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - user User @relation(fields: [userId], references: [id]) - userId String - - type String - permissions String - referenceId String - - metadataEncoded Bytes - - @@index([userId]) - @@index([type, userId]) - @@index([referenceId]) - @@index([type, referenceId]) - @@map("user_connections") -} - -// Content uploaded by a user, such as an image -model UserContent { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - user User @relation(fields: [userId], references: [id]) - userId String - - type String - referenceId String - - valueEncoded Bytes - metadataEncoded Bytes - - @@index([userId]) - @@index([type, userId]) - @@index([referenceId]) - @@index([type, referenceId]) - @@map("user_content") -} - -// -// === Wargaming Data === -// - -// Wargaming Account record -model Account { - id String @id - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - lastBattleTime DateTime - accountCreatedAt DateTime - - realm String - nickname String - private Boolean @default(false) - - clan Clan? @relation(fields: [clanId], references: [id]) - clanId String? - - @@index([realm]) - @@index([realm, lastBattleTime]) - @@index([id, lastBattleTime]) - @@index([clanId]) - @@map("accounts") -} - -// Wargaming Clan record -model Clan { - id String @id - createdAt DateTime - updatedAt DateTime @updatedAt - - tag String - name String - emblemId String @default("") - - accounts Account[] - membersString String - - recordUpdatedAt DateTime @updatedAt - - @@index([tag]) - @@map("account_clans") -} - -// A snapshot of account statistics -model AccountSnapshot { - id String @id @default(cuid()) - createdAt DateTime - - type String - lastBattleTime DateTime - - accountId String - referenceId String - - ratingBattles Int - ratingFrameEncoded Bytes - - regularBattles Int - regularFrameEncoded Bytes - - @@index([createdAt]) - @@index([type, accountId, createdAt]) - @@index([type, accountId, referenceId]) - @@index([type, accountId, referenceId, createdAt]) - @@map("account_snapshots") -} - -// A snapshot of vehicle statistics on a specific account -model VehicleSnapshot { - id String @id @default(cuid()) - createdAt DateTime - - type String - lastBattleTime DateTime - - accountId String - vehicleId String - referenceId String - - battles Int - frameEncoded Bytes - - @@index([createdAt]) - @@index([vehicleId, createdAt]) - @@index([accountId, referenceId]) - @@index([accountId, referenceId, vehicleId]) - @@index([accountId, referenceId, type]) - @@index([accountId, referenceId, createdAt]) - @@map("vehicle_snapshots") -} - -// A snapshot of all account achievements -model AchievementsSnapshot { - id String @id @default(uuid()) - createdAt DateTime - - accountId String - referenceId String - - dataEncoded Bytes - - @@index([createdAt]) - @@index([accountId, referenceId]) - @@index([accountId, referenceId, createdAt]) - @@map("achievements_snapshots") -} - -// // A snapshot of account rating season stats -// model AccountRatingSeasonSnapshot { -// id String @id @default(cuid()) -// createdAt DateTime - -// seasonId String -// accountId String -// referenceId String -// lastBattleTime DateTime - -// dataEncoded Bytes - -// @@index([createdAt]) -// @@index([seasonId, accountId]) -// @@index([seasonId, referenceId]) -// @@map("account_rating_season_snapshots") -// @@ignore // unused atm and may require significant changes -// } - -// A stats frame representing an average player performance on a vehicle -model VehicleAverage { - id String @id - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - dataEncoded Bytes - - @@map("glossary_averages") -} - -// Wargaming Vehicle glossary information -model Vehicle { - id String @id - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - tier Int - type String - class String - nation String - - localizedNamesEncoded Bytes - - @@map("glossary_vehicles") -} - -// // Wargaming Achievement glossary information -// model Achievement { -// id String @id @default(uuid()) -// createdAt DateTime @default(now()) -// updatedAt DateTime @updatedAt - -// section String - -// dataEncoded Bytes - -// @@map("glossary_achievements") -// @@ignore -// } - -// -// === Discord Data === -// - -model ApplicationCommand { - id String @id - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - name String - version String - optionsHash String - - @@index([optionsHash]) - @@map("application_commands") -} - -// // A log of a user interaction with the bot -// model DiscordInteraction { -// id String @id @default(cuid()) -// createdAt DateTime @default(now()) -// updatedAt DateTime @updatedAt - -// type String -// userId String -// referenceId String - -// dataEncoded Bytes -// metadataEncoded Bytes - -// @@map("user_interactions") -// @@ignore -// } - -// // An active live session record of a user, requested through the bot -// model LiveSession { -// id String @id @default(cuid()) -// createdAt DateTime @default(now()) -// updatedAt DateTime @updatedAt - -// locale String - -// userId String -// referenceId String - -// lastUpdate DateTime @default(now()) -// lastBattleTime DateTime @default(now()) - -// options StatsRequestsOptions @relation(fields: [optionsId], references: [id]) -// optionsId String - -// metadataEncoded Bytes - -// @@index([referenceId]) -// @@map("live_sessions") -// @@ignore -// } - -// model StatsRequestsOptions { -// id String @id @default(cuid()) -// createdAt DateTime @default(now()) -// updatedAt DateTime @updatedAt - -// type String -// userId String -// accountId String -// referenceId String - -// dataEncoded Bytes -// liveSessions LiveSession[] @ignore - -// @@index([referenceId]) -// @@map("stats_request_options") -// @@ignore -// } diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 671e3ba6..b6f390e4 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -2,16 +2,23 @@ package database import ( "context" - "fmt" - "strings" "time" - "github.com/cufee/aftermath/internal/database/prisma/db" - "github.com/cufee/aftermath/internal/stats/frame" - "github.com/rs/zerolog/log" - "github.com/steebchen/prisma-client-go/runtime/transaction" + "github.com/cufee/aftermath/internal/database/models" ) +// import ( +// "context" +// "fmt" +// "strings" +// "time" + +// "github.com/cufee/aftermath/internal/database/prisma/db" +// "github.com/cufee/aftermath/internal/stats/frame" +// "github.com/rs/zerolog/log" +// "github.com/steebchen/prisma-client-go/runtime/transaction" +// ) + type getSnapshotQuery struct { vehicleIDs []string @@ -19,157 +26,157 @@ type getSnapshotQuery struct { createdBefore *time.Time } -func (s getSnapshotQuery) accountParams(accountID, referenceID string, kind snapshotType) []db.AccountSnapshotWhereParam { - var params []db.AccountSnapshotWhereParam - params = append(params, db.AccountSnapshot.Type.Equals(string(kind))) - params = append(params, db.AccountSnapshot.AccountID.Equals(accountID)) - params = append(params, db.AccountSnapshot.ReferenceID.Equals(referenceID)) - - if s.createdAfter != nil { - params = append(params, db.AccountSnapshot.CreatedAt.After(*s.createdAfter)) - } - if s.createdBefore != nil { - params = append(params, db.AccountSnapshot.CreatedAt.Before(*s.createdBefore)) - } - - return params -} - -func (s getSnapshotQuery) vehiclesQuery(accountID, referenceID string, kind snapshotType) (query string, params []interface{}) { - var conditions []string - var args []interface{} - - // Mandatory conditions - conditions = append(conditions, "type = ?") - args = append(args, kind) - - conditions = append(conditions, "accountId = ?") - args = append(args, accountID) - - conditions = append(conditions, "referenceId = ?") - args = append(args, referenceID) - - // Optional conditions - if s.createdAfter != nil { - conditions = append(conditions, "createdAt > ?") - args = append(args, *s.createdAfter) - } - if s.createdBefore != nil { - conditions = append(conditions, "createdAt < ?") - args = append(args, *s.createdBefore) - } - - // Filter by vehicle IDs if provided - if len(s.vehicleIDs) > 0 { - placeholders := make([]string, len(s.vehicleIDs)) - for i, id := range s.vehicleIDs { - placeholders[i] = "?" - args = append(args, id) - } - conditions = append(conditions, fmt.Sprintf("vehicleId IN (%s)", strings.Join(placeholders, ","))) - } - - // Determine the order by clause - var orderBy string = "createdAt DESC" - if s.createdAfter != nil { - orderBy = "createdAt ASC" - } - - // Base query - query = ` - SELECT - id, createdAt, type, lastBattleTime, accountId, vehicleId, referenceId, battles, frameEncoded - FROM - vehicle_snapshots - WHERE - %s - ORDER BY - %s - ` - - // Combine conditions into a single string - conditionsStr := strings.Join(conditions, " AND ") - query = fmt.Sprintf(query, conditionsStr, orderBy) - - // Wrap the query to select the latest or earliest snapshot per vehicleId - wrappedQuery := ` - SELECT * FROM ( - %s - ) AS ordered_snapshots - GROUP BY vehicleId - ` - - query = fmt.Sprintf(wrappedQuery, query) - return query, args -} - -func (s getSnapshotQuery) manyAccountsQuery(accountIDs []string, kind snapshotType) (query string, params []interface{}) { - var conditions []string - var args []interface{} - - // Mandatory conditions - conditions = append(conditions, "type = ?") - args = append(args, kind) - - // Optional conditions - if s.createdAfter != nil { - conditions = append(conditions, "createdAt > ?") - args = append(args, *s.createdAfter) - } - if s.createdBefore != nil { - conditions = append(conditions, "createdAt < ?") - args = append(args, *s.createdBefore) - } - - // Filter by account IDs - placeholders := make([]string, len(accountIDs)) - for i := range accountIDs { - placeholders[i] = "?" - } - conditions = append(conditions, fmt.Sprintf("accountId IN (%s)", strings.Join(placeholders, ","))) - for _, id := range accountIDs { - args = append(args, id) - } - - // Determine the order by clause - var orderBy string = "createdAt DESC" - if s.createdAfter != nil { - orderBy = "createdAt ASC" - } - - // Combine conditions into a single string - conditionsStr := strings.Join(conditions, " AND ") - - // Base query - query = fmt.Sprintf(` - SELECT - id, createdAt, type, lastBattleTime, accountId, referenceId, ratingBattles, ratingFrameEncoded, regularBattles, regularFrameEncoded - FROM - account_snapshots - WHERE - %s - ORDER BY - %s - `, conditionsStr, orderBy) - - // Wrap the query to select the latest snapshot per accountId - wrappedQuery := fmt.Sprintf(` - SELECT * FROM ( - %s - ) AS ordered_snapshots - GROUP BY accountId - `, query) - - return wrappedQuery, args -} - -func (s getSnapshotQuery) accountOrder() []db.AccountSnapshotOrderByParam { - order := db.DESC - if s.createdAfter != nil { - order = db.ASC - } - return []db.AccountSnapshotOrderByParam{db.AccountSnapshot.CreatedAt.Order(order)} -} +// func (s getSnapshotQuery) accountParams(accountID, referenceID string, kind models.SnapshotType) []db.AccountSnapshotWhereParam { +// var params []db.AccountSnapshotWhereParam +// params = append(params, db.AccountSnapshot.Type.Equals(string(kind))) +// params = append(params, db.AccountSnapshot.AccountID.Equals(accountID)) +// params = append(params, db.AccountSnapshot.ReferenceID.Equals(referenceID)) + +// if s.createdAfter != nil { +// params = append(params, db.AccountSnapshot.CreatedAt.After(*s.createdAfter)) +// } +// if s.createdBefore != nil { +// params = append(params, db.AccountSnapshot.CreatedAt.Before(*s.createdBefore)) +// } + +// return params +// } + +// func (s getSnapshotQuery) vehiclesQuery(accountID, referenceID string, kind models.SnapshotType) (query string, params []interface{}) { +// var conditions []string +// var args []interface{} + +// // Mandatory conditions +// conditions = append(conditions, "type = ?") +// args = append(args, kind) + +// conditions = append(conditions, "accountId = ?") +// args = append(args, accountID) + +// conditions = append(conditions, "referenceId = ?") +// args = append(args, referenceID) + +// // Optional conditions +// if s.createdAfter != nil { +// conditions = append(conditions, "createdAt > ?") +// args = append(args, *s.createdAfter) +// } +// if s.createdBefore != nil { +// conditions = append(conditions, "createdAt < ?") +// args = append(args, *s.createdBefore) +// } + +// // Filter by vehicle IDs if provided +// if len(s.vehicleIDs) > 0 { +// placeholders := make([]string, len(s.vehicleIDs)) +// for i, id := range s.vehicleIDs { +// placeholders[i] = "?" +// args = append(args, id) +// } +// conditions = append(conditions, fmt.Sprintf("vehicleId IN (%s)", strings.Join(placeholders, ","))) +// } + +// // Determine the order by clause +// var orderBy string = "createdAt DESC" +// if s.createdAfter != nil { +// orderBy = "createdAt ASC" +// } + +// // Base query +// query = ` +// SELECT +// id, createdAt, type, lastBattleTime, accountId, vehicleId, referenceId, battles, frameEncoded +// FROM +// vehicle_snapshots +// WHERE +// %s +// ORDER BY +// %s +// ` + +// // Combine conditions into a single string +// conditionsStr := strings.Join(conditions, " AND ") +// query = fmt.Sprintf(query, conditionsStr, orderBy) + +// // Wrap the query to select the latest or earliest snapshot per vehicleId +// wrappedQuery := ` +// SELECT * FROM ( +// %s +// ) AS ordered_snapshots +// GROUP BY vehicleId +// ` + +// query = fmt.Sprintf(wrappedQuery, query) +// return query, args +// } + +// func (s getSnapshotQuery) manyAccountsQuery(accountIDs []string, kind models.SnapshotType) (query string, params []interface{}) { +// var conditions []string +// var args []interface{} + +// // Mandatory conditions +// conditions = append(conditions, "type = ?") +// args = append(args, kind) + +// // Optional conditions +// if s.createdAfter != nil { +// conditions = append(conditions, "createdAt > ?") +// args = append(args, *s.createdAfter) +// } +// if s.createdBefore != nil { +// conditions = append(conditions, "createdAt < ?") +// args = append(args, *s.createdBefore) +// } + +// // Filter by account IDs +// placeholders := make([]string, len(accountIDs)) +// for i := range accountIDs { +// placeholders[i] = "?" +// } +// conditions = append(conditions, fmt.Sprintf("accountId IN (%s)", strings.Join(placeholders, ","))) +// for _, id := range accountIDs { +// args = append(args, id) +// } + +// // Determine the order by clause +// var orderBy string = "createdAt DESC" +// if s.createdAfter != nil { +// orderBy = "createdAt ASC" +// } + +// // Combine conditions into a single string +// conditionsStr := strings.Join(conditions, " AND ") + +// // Base query +// query = fmt.Sprintf(` +// SELECT +// id, createdAt, type, lastBattleTime, accountId, referenceId, ratingBattles, ratingFrameEncoded, regularBattles, regularFrameEncoded +// FROM +// account_snapshots +// WHERE +// %s +// ORDER BY +// %s +// `, conditionsStr, orderBy) + +// // Wrap the query to select the latest snapshot per accountId +// wrappedQuery := fmt.Sprintf(` +// SELECT * FROM ( +// %s +// ) AS ordered_snapshots +// GROUP BY accountId +// `, query) + +// return wrappedQuery, args +// } + +// func (s getSnapshotQuery) accountOrder() []db.AccountSnapshotOrderByParam { +// order := db.DESC +// if s.createdAfter != nil { +// order = db.ASC +// } +// return []db.AccountSnapshotOrderByParam{db.AccountSnapshot.CreatedAt.Order(order)} +// } type SnapshotQuery func(*getSnapshotQuery) @@ -189,222 +196,192 @@ func WithCreatedBefore(before time.Time) SnapshotQuery { } } -type snapshotType string - -const ( - SnapshotTypeLive snapshotType = "live" - SnapshotTypeDaily snapshotType = "daily" -) - -type VehicleSnapshot struct { - ID string - CreatedAt time.Time - - Type snapshotType - LastBattleTime time.Time - - AccountID string - VehicleID string - ReferenceID string - - Stats frame.StatsFrame -} - -func (s VehicleSnapshot) FromModel(model db.VehicleSnapshotModel) (VehicleSnapshot, error) { - s.ID = model.ID - s.Type = snapshotType(model.Type) - s.CreatedAt = model.CreatedAt - s.LastBattleTime = model.LastBattleTime - - s.AccountID = model.AccountID - s.VehicleID = model.VehicleID - s.ReferenceID = model.ReferenceID - - stats, err := frame.DecodeStatsFrame(model.FrameEncoded) - if err != nil { - return VehicleSnapshot{}, err - } - s.Stats = stats - return s, nil -} - -func (c *client) CreateVehicleSnapshots(ctx context.Context, snapshots ...VehicleSnapshot) error { - if len(snapshots) < 1 { - return nil - } - - var transactions []transaction.Transaction - for _, data := range snapshots { - encoded, err := data.Stats.Encode() - if err != nil { - log.Err(err).Str("accountId", data.AccountID).Str("vehicleId", data.VehicleID).Msg("failed to encode a stats frame for vehicle snapthsot") - continue - } - - transactions = append(transactions, c.prisma.VehicleSnapshot. - CreateOne( - db.VehicleSnapshot.CreatedAt.Set(data.CreatedAt), - db.VehicleSnapshot.Type.Set(string(data.Type)), - db.VehicleSnapshot.LastBattleTime.Set(data.LastBattleTime), - db.VehicleSnapshot.AccountID.Set(data.AccountID), - db.VehicleSnapshot.VehicleID.Set(data.VehicleID), - db.VehicleSnapshot.ReferenceID.Set(data.ReferenceID), - db.VehicleSnapshot.Battles.Set(int(data.Stats.Battles)), - db.VehicleSnapshot.FrameEncoded.Set(encoded), - ).Tx(), - ) - } - - return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) +// func (s models.VehicleSnapshot) FromModel(model db.models.VehicleSnapshotModel) (models.VehicleSnapshot, error) { +// s.ID = model.ID +// s.Type = models.SnapshotType(model.Type) +// s.CreatedAt = model.CreatedAt +// s.LastBattleTime = model.LastBattleTime + +// s.AccountID = model.AccountID +// s.VehicleID = model.VehicleID +// s.ReferenceID = model.ReferenceID + +// stats, err := frame.DecodeStatsFrame(model.FrameEncoded) +// if err != nil { +// return models.VehicleSnapshot{}, err +// } +// s.Stats = stats +// return s, nil +// } + +func (c *libsqlClient) CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error { + // if len(snapshots) < 1 { + // return nil + // } + + // var transactions []transaction.Transaction + // for _, data := range snapshots { + // encoded, err := data.Stats.Encode() + // if err != nil { + // log.Err(err).Str("accountId", data.AccountID).Str("vehicleId", data.VehicleID).Msg("failed to encode a stats frame for vehicle snapthsot") + // continue + // } + + // transactions = append(transactions, c.prisma.models.VehicleSnapshot. + // CreateOne( + // db.models.VehicleSnapshot.CreatedAt.Set(data.CreatedAt), + // db.models.VehicleSnapshot.Type.Set(string(data.Type)), + // db.models.VehicleSnapshot.LastBattleTime.Set(data.LastBattleTime), + // db.models.VehicleSnapshot.AccountID.Set(data.AccountID), + // db.models.VehicleSnapshot.VehicleID.Set(data.VehicleID), + // db.models.VehicleSnapshot.ReferenceID.Set(data.ReferenceID), + // db.models.VehicleSnapshot.Battles.Set(int(data.Stats.Battles)), + // db.models.VehicleSnapshot.FrameEncoded.Set(encoded), + // ).Tx(), + // ) + // } + + // return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) + return nil } -func (c *client) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) ([]VehicleSnapshot, error) { - var query getSnapshotQuery - for _, apply := range options { - apply(&query) - } - - var models []db.VehicleSnapshotModel - raw, args := query.vehiclesQuery(accountID, referenceID, kind) - err := c.prisma.Prisma.Raw.QueryRaw(raw, args...).Exec(ctx, &models) - if err != nil { - return nil, err - } - - var snapshots []VehicleSnapshot - for _, model := range models { - vehicle, err := VehicleSnapshot{}.FromModel(model) - if err != nil { - return nil, err - } - snapshots = append(snapshots, vehicle) - } +func (c *libsqlClient) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) { + // var query getSnapshotQuery + // for _, apply := range options { + // apply(&query) + // } + + // var models []db.models.VehicleSnapshotModel + // raw, args := query.vehiclesQuery(accountID, referenceID, kind) + // err := c.prisma.Prisma.Raw.QueryRaw(raw, args...).Exec(ctx, &models) + // if err != nil { + // return nil, err + // } + + var snapshots []models.VehicleSnapshot + // for _, model := range models { + // vehicle, err := models.VehicleSnapshot{}.FromModel(model) + // if err != nil { + // return nil, err + // } + // snapshots = append(snapshots, vehicle) + // } return snapshots, nil } -type AccountSnapshot struct { - ID string - Type snapshotType - CreatedAt time.Time - AccountID string - ReferenceID string - LastBattleTime time.Time - RatingBattles frame.StatsFrame - RegularBattles frame.StatsFrame +// func (s AccountSnapshot) FromModel(model *db.AccountSnapshotModel) (AccountSnapshot, error) { +// s.ID = model.ID +// s.Type = models.SnapshotType(model.Type) +// s.CreatedAt = model.CreatedAt +// s.AccountID = model.AccountID +// s.ReferenceID = model.ReferenceID +// s.LastBattleTime = model.LastBattleTime + +// rating, err := frame.DecodeStatsFrame(model.RatingFrameEncoded) +// if err != nil { +// return AccountSnapshot{}, err +// } +// s.RatingBattles = rating + +// regular, err := frame.DecodeStatsFrame(model.RegularFrameEncoded) +// if err != nil { +// return AccountSnapshot{}, err +// } +// s.RegularBattles = regular + +// return s, nil +// } + +func (c *libsqlClient) CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) error { + // if len(snapshots) < 1 { + // return nil + // } + + // var transactions []transaction.Transaction + // for _, data := range snapshots { + // ratingEncoded, err := data.RatingBattles.Encode() + // if err != nil { + // log.Err(err).Str("accountId", data.AccountID).Msg("failed to encode rating stats frame for account snapthsot") + // continue + // } + // regularEncoded, err := data.RegularBattles.Encode() + // if err != nil { + // log.Err(err).Str("accountId", data.AccountID).Msg("failed to encode regular stats frame for account snapthsot") + // continue + // } + + // transactions = append(transactions, c.prisma.AccountSnapshot. + // CreateOne( + // db.AccountSnapshot.CreatedAt.Set(data.CreatedAt), + // db.AccountSnapshot.Type.Set(string(data.Type)), + // db.AccountSnapshot.LastBattleTime.Set(data.LastBattleTime), + // db.AccountSnapshot.AccountID.Set(data.AccountID), + // db.AccountSnapshot.ReferenceID.Set(data.ReferenceID), + // db.AccountSnapshot.RatingBattles.Set(int(data.RatingBattles.Battles)), + // db.AccountSnapshot.RatingFrameEncoded.Set(ratingEncoded), + // db.AccountSnapshot.RegularBattles.Set(int(data.RegularBattles.Battles)), + // db.AccountSnapshot.RegularFrameEncoded.Set(regularEncoded), + // ).Tx(), + // ) + // } + + // return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) + return nil } -func (s AccountSnapshot) FromModel(model *db.AccountSnapshotModel) (AccountSnapshot, error) { - s.ID = model.ID - s.Type = snapshotType(model.Type) - s.CreatedAt = model.CreatedAt - s.AccountID = model.AccountID - s.ReferenceID = model.ReferenceID - s.LastBattleTime = model.LastBattleTime - - rating, err := frame.DecodeStatsFrame(model.RatingFrameEncoded) - if err != nil { - return AccountSnapshot{}, err - } - s.RatingBattles = rating - - regular, err := frame.DecodeStatsFrame(model.RegularFrameEncoded) - if err != nil { - return AccountSnapshot{}, err - } - s.RegularBattles = regular - - return s, nil -} - -func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...AccountSnapshot) error { - if len(snapshots) < 1 { - return nil - } - - var transactions []transaction.Transaction - for _, data := range snapshots { - ratingEncoded, err := data.RatingBattles.Encode() - if err != nil { - log.Err(err).Str("accountId", data.AccountID).Msg("failed to encode rating stats frame for account snapthsot") - continue - } - regularEncoded, err := data.RegularBattles.Encode() - if err != nil { - log.Err(err).Str("accountId", data.AccountID).Msg("failed to encode regular stats frame for account snapthsot") - continue - } - - transactions = append(transactions, c.prisma.AccountSnapshot. - CreateOne( - db.AccountSnapshot.CreatedAt.Set(data.CreatedAt), - db.AccountSnapshot.Type.Set(string(data.Type)), - db.AccountSnapshot.LastBattleTime.Set(data.LastBattleTime), - db.AccountSnapshot.AccountID.Set(data.AccountID), - db.AccountSnapshot.ReferenceID.Set(data.ReferenceID), - db.AccountSnapshot.RatingBattles.Set(int(data.RatingBattles.Battles)), - db.AccountSnapshot.RatingFrameEncoded.Set(ratingEncoded), - db.AccountSnapshot.RegularBattles.Set(int(data.RegularBattles.Battles)), - db.AccountSnapshot.RegularFrameEncoded.Set(regularEncoded), - ).Tx(), - ) - } - - return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) -} - -func (c *client) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]AccountSnapshot, error) { - models, err := c.prisma.AccountSnapshot.FindMany(db.AccountSnapshot.AccountID.Equals(accountID)).OrderBy(db.AccountSnapshot.CreatedAt.Order(db.DESC)).Take(limit).Exec(ctx) - if err != nil { - return nil, err - } - - var snapshots []AccountSnapshot - for _, model := range models { - s, err := AccountSnapshot{}.FromModel(&model) - if err != nil { - return nil, err - } - snapshots = append(snapshots, s) - } +func (c *libsqlClient) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) { + // models, err := c.prisma.AccountSnapshot.FindMany(db.AccountSnapshot.AccountID.Equals(accountID)).OrderBy(db.AccountSnapshot.CreatedAt.Order(db.DESC)).Take(limit).Exec(ctx) + // if err != nil { + // return nil, err + // } + + var snapshots []models.AccountSnapshot + // for _, model := range models { + // s, err := AccountSnapshot{}.FromModel(&model) + // if err != nil { + // return nil, err + // } + // snapshots = append(snapshots, s) + // } return snapshots, nil } -func (c *client) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind snapshotType, options ...SnapshotQuery) (AccountSnapshot, error) { - var query getSnapshotQuery - for _, apply := range options { - apply(&query) - } +func (c *libsqlClient) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) (models.AccountSnapshot, error) { + // var query getSnapshotQuery + // for _, apply := range options { + // apply(&query) + // } - model, err := c.prisma.AccountSnapshot.FindFirst(query.accountParams(accountID, referenceID, kind)...).OrderBy(query.accountOrder()...).Exec(ctx) - if err != nil { - return AccountSnapshot{}, err - } + // model, err := c.prisma.AccountSnapshot.FindFirst(query.accountParams(accountID, referenceID, kind)...).OrderBy(query.accountOrder()...).Exec(ctx) + // if err != nil { + // return AccountSnapshot{}, err + // } - return AccountSnapshot{}.FromModel(model) + return models.AccountSnapshot{}, nil } -func (c *client) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind snapshotType, options ...SnapshotQuery) ([]AccountSnapshot, error) { - var query getSnapshotQuery - for _, apply := range options { - apply(&query) - } - - var models []db.AccountSnapshotModel - raw, args := query.manyAccountsQuery(accountIDs, kind) - err := c.prisma.Prisma.Raw.QueryRaw(raw, args...).Exec(ctx, &models) - if err != nil { - return nil, err - } - - var snapshots []AccountSnapshot - for _, model := range models { - snapshot, err := AccountSnapshot{}.FromModel(&model) - if err != nil { - return nil, err - } - snapshots = append(snapshots, snapshot) - } +func (c *libsqlClient) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.AccountSnapshot, error) { + // var query getSnapshotQuery + // for _, apply := range options { + // apply(&query) + // } + + // var models []db.AccountSnapshotModel + // raw, args := query.manyAccountsQuery(accountIDs, kind) + // err := c.prisma.Prisma.Raw.QueryRaw(raw, args...).Exec(ctx, &models) + // if err != nil { + // return nil, err + // } + + var snapshots []models.AccountSnapshot + // for _, model := range models { + // snapshot, err := AccountSnapshot{}.FromModel(&model) + // if err != nil { + // return nil, err + // } + // snapshots = append(snapshots, snapshot) + // } return snapshots, nil diff --git a/internal/database/snapshots_test.go b/internal/database/snapshots_test.go index c4f7553d..06653cf5 100644 --- a/internal/database/snapshots_test.go +++ b/internal/database/snapshots_test.go @@ -1,121 +1,113 @@ package database -import ( - "context" - "testing" - "time" +// /* +// DATABASE_URL needs to be set and migrations need to be applied +// */ +// func TestGetVehicleSnapshots(t *testing.T) { +// client, err := NewClient() +// assert.NoError(t, err, "new client should not error") +// defer client.prisma.Disconnect() - "github.com/stretchr/testify/assert" -) +// ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) +// defer cancel() -/* -DATABASE_URL needs to be set and migrations need to be applied -*/ -func TestGetVehicleSnapshots(t *testing.T) { - client, err := NewClient() - assert.NoError(t, err, "new client should not error") - defer client.prisma.Disconnect() +// client.prisma.VehicleSnapshot.FindMany().Delete().Exec(ctx) +// defer client.prisma.VehicleSnapshot.FindMany().Delete().Exec(ctx) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - defer cancel() +// createdAtVehicle1 := time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC) +// createdAtVehicle2 := time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC) +// createdAtVehicle3 := time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC) - client.prisma.VehicleSnapshot.FindMany().Delete().Exec(ctx) - defer client.prisma.VehicleSnapshot.FindMany().Delete().Exec(ctx) +// createdAtVehicle4 := time.Date(2023, 9, 1, 0, 0, 0, 0, time.UTC) +// createdAtVehicle5 := time.Date(2023, 9, 2, 0, 0, 0, 0, time.UTC) - createdAtVehicle1 := time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC) - createdAtVehicle2 := time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC) - createdAtVehicle3 := time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC) +// vehicle1 := VehicleSnapshot{ +// VehicleID: "v1", +// AccountID: "a1", +// ReferenceID: "r1", - createdAtVehicle4 := time.Date(2023, 9, 1, 0, 0, 0, 0, time.UTC) - createdAtVehicle5 := time.Date(2023, 9, 2, 0, 0, 0, 0, time.UTC) +// Type: SnapshotTypeDaily, +// CreatedAt: createdAtVehicle1, +// LastBattleTime: createdAtVehicle1, +// } +// vehicle2 := VehicleSnapshot{ +// VehicleID: "v1", +// AccountID: "a1", +// ReferenceID: "r1", - vehicle1 := VehicleSnapshot{ - VehicleID: "v1", - AccountID: "a1", - ReferenceID: "r1", +// Type: SnapshotTypeDaily, +// CreatedAt: createdAtVehicle2, +// LastBattleTime: createdAtVehicle2, +// } +// vehicle3 := VehicleSnapshot{ +// VehicleID: "v1", +// AccountID: "a1", +// ReferenceID: "r1", - Type: SnapshotTypeDaily, - CreatedAt: createdAtVehicle1, - LastBattleTime: createdAtVehicle1, - } - vehicle2 := VehicleSnapshot{ - VehicleID: "v1", - AccountID: "a1", - ReferenceID: "r1", +// Type: SnapshotTypeDaily, +// CreatedAt: createdAtVehicle3, +// LastBattleTime: createdAtVehicle3, +// } +// vehicle4 := VehicleSnapshot{ +// VehicleID: "v4", +// AccountID: "a1", +// ReferenceID: "r2", - Type: SnapshotTypeDaily, - CreatedAt: createdAtVehicle2, - LastBattleTime: createdAtVehicle2, - } - vehicle3 := VehicleSnapshot{ - VehicleID: "v1", - AccountID: "a1", - ReferenceID: "r1", +// Type: SnapshotTypeDaily, +// CreatedAt: createdAtVehicle4, +// LastBattleTime: createdAtVehicle4, +// } +// vehicle5 := VehicleSnapshot{ +// VehicleID: "v5", +// AccountID: "a1", +// ReferenceID: "r2", - Type: SnapshotTypeDaily, - CreatedAt: createdAtVehicle3, - LastBattleTime: createdAtVehicle3, - } - vehicle4 := VehicleSnapshot{ - VehicleID: "v4", - AccountID: "a1", - ReferenceID: "r2", +// Type: SnapshotTypeDaily, +// CreatedAt: createdAtVehicle5, +// LastBattleTime: createdAtVehicle5, +// } +// vehicle6 := VehicleSnapshot{ +// VehicleID: "v5", +// AccountID: "a1", +// ReferenceID: "r2", - Type: SnapshotTypeDaily, - CreatedAt: createdAtVehicle4, - LastBattleTime: createdAtVehicle4, - } - vehicle5 := VehicleSnapshot{ - VehicleID: "v5", - AccountID: "a1", - ReferenceID: "r2", +// Type: SnapshotTypeDaily, +// CreatedAt: createdAtVehicle5, +// LastBattleTime: createdAtVehicle5, +// } - Type: SnapshotTypeDaily, - CreatedAt: createdAtVehicle5, - LastBattleTime: createdAtVehicle5, - } - vehicle6 := VehicleSnapshot{ - VehicleID: "v5", - AccountID: "a1", - ReferenceID: "r2", - - Type: SnapshotTypeDaily, - CreatedAt: createdAtVehicle5, - LastBattleTime: createdAtVehicle5, - } - - { // create snapshots - snaphots := []VehicleSnapshot{vehicle1, vehicle2, vehicle3, vehicle4, vehicle5, vehicle6} - err = client.CreateVehicleSnapshots(ctx, snaphots...) - assert.NoError(t, err, "create vehicle snapshot should not error") - } - { // when we check created after, vehicles need to be ordered by createdAt ASC, so we expect to get vehicle2 back - vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedAfter(createdAtVehicle1)) - assert.NoError(t, err, "get vehicle snapshot error") - assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") - assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned", vehicles) - } - { // when we check created before, vehicles need to be ordered by createdAt DESC, so we expect to get vehicle2 back - vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle3)) - assert.NoError(t, err, "get vehicle snapshot error") - assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") - assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned", vehicles) - } - { // make sure only 1 vehicle is returned per ID - vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle5.Add(time.Hour))) - assert.NoError(t, err, "get vehicle snapshot error") - assert.Len(t, vehicles, 2, "should return exactly 2 snapshots") - assert.NotEqual(t, vehicles[0].ID, vehicles[1].ID, "each vehicle id should only be returned once", vehicles) - } - { // get a cehicle with a specific id - vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", SnapshotTypeDaily, WithVehicleIDs([]string{"v5"})) - assert.NoError(t, err, "get vehicle snapshot error") - assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") - assert.NotEqual(t, vehicles[0].ID, "v5", "incorrect vehicle returned", vehicles) - } - { // this should return no result - vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle1)) - assert.NoError(t, err, "no results from a raw query does not trigger an error") - assert.Len(t, vehicles, 0, "return should have no results", vehicles) - } -} +// { // create snapshots +// snaphots := []VehicleSnapshot{vehicle1, vehicle2, vehicle3, vehicle4, vehicle5, vehicle6} +// err = client.CreateVehicleSnapshots(ctx, snaphots...) +// assert.NoError(t, err, "create vehicle snapshot should not error") +// } +// { // when we check created after, vehicles need to be ordered by createdAt ASC, so we expect to get vehicle2 back +// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedAfter(createdAtVehicle1)) +// assert.NoError(t, err, "get vehicle snapshot error") +// assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") +// assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned", vehicles) +// } +// { // when we check created before, vehicles need to be ordered by createdAt DESC, so we expect to get vehicle2 back +// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle3)) +// assert.NoError(t, err, "get vehicle snapshot error") +// assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") +// assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned", vehicles) +// } +// { // make sure only 1 vehicle is returned per ID +// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle5.Add(time.Hour))) +// assert.NoError(t, err, "get vehicle snapshot error") +// assert.Len(t, vehicles, 2, "should return exactly 2 snapshots") +// assert.NotEqual(t, vehicles[0].ID, vehicles[1].ID, "each vehicle id should only be returned once", vehicles) +// } +// { // get a cehicle with a specific id +// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", SnapshotTypeDaily, WithVehicleIDs([]string{"v5"})) +// assert.NoError(t, err, "get vehicle snapshot error") +// assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") +// assert.NotEqual(t, vehicles[0].ID, "v5", "incorrect vehicle returned", vehicles) +// } +// { // this should return no result +// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle1)) +// assert.NoError(t, err, "no results from a raw query does not trigger an error") +// assert.Len(t, vehicles, 0, "return should have no results", vehicles) +// } +// } diff --git a/internal/database/tasks.go b/internal/database/tasks.go index a2d64be9..73f95eb3 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -2,148 +2,45 @@ package database import ( "context" - "encoding/json" - "strings" "time" - "github.com/cufee/aftermath/internal/database/prisma/db" - "github.com/cufee/aftermath/internal/encoding" + "github.com/cufee/aftermath/internal/database/models" ) -type TaskType string +// func (t Task) FromModel(model db.CronTaskModel) Task { +// t.ID = model.ID +// t.Type = TaskType(model.Type) -const ( - TaskTypeUpdateClans TaskType = "UPDATE_CLANS" - TaskTypeRecordSessions TaskType = "RECORD_ACCOUNT_SESSIONS" - TaskTypeUpdateAccountWN8 TaskType = "UPDATE_ACCOUNT_WN8" - TaskTypeRecordPlayerAchievements TaskType = "UPDATE_ACCOUNT_ACHIEVEMENTS" +// t.Status = TaskStatus(model.Status) +// t.ReferenceID = model.ReferenceID - TaskTypeDatabaseCleanup TaskType = "CLEANUP_DATABASE" -) - -// Task statuses -type TaskStatus string - -const ( - TaskStatusScheduled TaskStatus = "TASK_SCHEDULED" - TaskStatusInProgress TaskStatus = "TASK_IN_PROGRESS" - TaskStatusComplete TaskStatus = "TASK_COMPLETE" - TaskStatusFailed TaskStatus = "TASK_FAILED" -) - -type Task struct { - ID string `json:"id"` - Type TaskType `json:"kind"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - - ReferenceID string `json:"referenceId"` - Targets []string `json:"targets"` - - Logs []TaskLog `json:"logs"` - - Status TaskStatus `json:"status"` - ScheduledAfter time.Time `json:"scheduledAfter"` - LastRun time.Time `json:"lastRun"` - - Data map[string]any `json:"data"` -} - -func (t Task) FromModel(model db.CronTaskModel) Task { - t.ID = model.ID - t.Type = TaskType(model.Type) - - t.Status = TaskStatus(model.Status) - t.ReferenceID = model.ReferenceID - - t.LastRun = model.LastRun - t.CreatedAt = model.CreatedAt - t.UpdatedAt = model.UpdatedAt - t.ScheduledAfter = model.ScheduledAfter - - t.decodeData(model.DataEncoded) - t.decodeLogs(model.LogsEncoded) - t.decodeTargets(model.TargetsEncoded) - return t -} - -func (t *Task) LogAttempt(log TaskLog) { - t.Logs = append(t.Logs, log) -} - -func (t *Task) OnCreated() { - t.LastRun = time.Now() - t.CreatedAt = time.Now() - t.UpdatedAt = time.Now() -} -func (t *Task) OnUpdated() { - t.UpdatedAt = time.Now() -} - -func (t *Task) encodeTargets() []byte { - return []byte(strings.Join(t.Targets, ";")) -} -func (t *Task) decodeTargets(targets []byte) { - if string(targets) != "" { - t.Targets = strings.Split(string(targets), ";") - } -} +// t.LastRun = model.LastRun +// t.CreatedAt = model.CreatedAt +// t.UpdatedAt = model.UpdatedAt +// t.ScheduledAfter = model.ScheduledAfter -func (t *Task) encodeLogs() []byte { - if t.Logs == nil { - return []byte{} - } - data, _ := json.Marshal(t.Logs) - return data -} -func (t *Task) decodeLogs(logs []byte) { - _ = json.Unmarshal(logs, &t.Logs) -} - -func (t *Task) encodeData() []byte { - if t.Data == nil { - return []byte{} - } - data, _ := encoding.EncodeGob(t.Data) - return data -} -func (t *Task) decodeData(data []byte) { - t.Data = make(map[string]any) - _ = encoding.DecodeGob(data, &t.Data) -} - -type TaskLog struct { - Targets []string `json:"targets" bson:"targets"` - Timestamp time.Time `json:"timestamp" bson:"timestamp"` - Comment string `json:"result" bson:"result"` - Error string `json:"error" bson:"error"` -} - -func NewAttemptLog(task Task, comment, err string) TaskLog { - return TaskLog{ - Targets: task.Targets, - Timestamp: time.Now(), - Comment: comment, - Error: err, - } -} +// t.decodeData(model.DataEncoded) +// t.decodeLogs(model.LogsEncoded) +// t.decodeTargets(model.TargetsEncoded) +// return t +// } /* Returns up limit tasks that have TaskStatusInProgress and were last updates 1+ hours ago */ -func (c *client) GetStaleTasks(ctx context.Context, limit int) ([]Task, error) { - models, err := c.prisma.CronTask.FindMany( - db.CronTask.Status.Equals(string(TaskStatusInProgress)), - db.CronTask.LastRun.Before(time.Now().Add(time.Hour*-1)), - ).OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)).Take(limit).Exec(ctx) - if err != nil && !db.IsErrNotFound(err) { - return nil, err - } - - var tasks []Task - for _, model := range models { - tasks = append(tasks, Task{}.FromModel(model)) - } +func (c *libsqlClient) GetStaleTasks(ctx context.Context, limit int) ([]models.Task, error) { + // models, err := c.prisma.CronTask.FindMany( + // db.CronTask.Status.Equals(string(TaskStatusInProgress)), + // db.CronTask.LastRun.Before(time.Now().Add(time.Hour*-1)), + // ).OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)).Take(limit).Exec(ctx) + // if err != nil && !database.IsNotFound(err) { + // return nil, err + // } + + var tasks []models.Task + // for _, model := range models { + // tasks = append(tasks, Task{}.FromModel(model)) + // } return tasks, nil } @@ -151,26 +48,26 @@ func (c *client) GetStaleTasks(ctx context.Context, limit int) ([]Task, error) { /* Returns all tasks that were created after createdAfter, sorted by ScheduledAfter (DESC) */ -func (c *client) GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...TaskStatus) ([]Task, error) { - var statusStr []string - for _, s := range status { - statusStr = append(statusStr, string(s)) - } - - params := []db.CronTaskWhereParam{db.CronTask.CreatedAt.After(createdAfter)} - if len(statusStr) > 0 { - params = append(params, db.CronTask.Status.In(statusStr)) - } - - models, err := c.prisma.CronTask.FindMany(params...).OrderBy(db.CronTask.ScheduledAfter.Order(db.DESC)).Exec(ctx) - if err != nil && !db.IsErrNotFound(err) { - return nil, err - } - - var tasks []Task - for _, model := range models { - tasks = append(tasks, Task{}.FromModel(model)) - } +func (c *libsqlClient) GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) { + // var statusStr []string + // for _, s := range status { + // statusStr = append(statusStr, string(s)) + // } + + // params := []db.CronTaskWhereParam{db.CronTask.CreatedAt.After(createdAfter)} + // if len(statusStr) > 0 { + // params = append(params, db.CronTask.Status.In(statusStr)) + // } + + // models, err := c.prisma.CronTask.FindMany(params...).OrderBy(db.CronTask.ScheduledAfter.Order(db.DESC)).Exec(ctx) + // if err != nil && !database.IsNotFound(err) { + // return nil, err + // } + + var tasks []models.Task + // for _, model := range models { + // tasks = append(tasks, Task{}.FromModel(model)) + // } return tasks, nil } @@ -179,76 +76,76 @@ func (c *client) GetRecentTasks(ctx context.Context, createdAfter time.Time, sta GetAndStartTasks retrieves up to limit number of tasks matching the referenceId and updates their status to in progress - this func will block until all other calls to task update funcs are done */ -func (c *client) GetAndStartTasks(ctx context.Context, limit int) ([]Task, error) { - if limit < 1 { - return nil, nil - } - if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { - return nil, err - } - defer c.tasksUpdateSem.Release(1) - - models, err := c.prisma.CronTask. - FindMany( - db.CronTask.Status.Equals(string(TaskStatusScheduled)), - db.CronTask.ScheduledAfter.Lt(time.Now()), - ). - OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)). - Take(limit). - Exec(ctx) - if err != nil { - if db.IsErrNotFound(err) { - return nil, nil - } - return nil, err - } - if len(models) < 1 { - return nil, nil - } - - var ids []string - var tasks []Task - for _, model := range models { - tasks = append(tasks, Task{}.FromModel(model)) - ids = append(ids, model.ID) - } - - _, err = c.prisma.CronTask. - FindMany(db.CronTask.ID.In(ids)). - Update(db.CronTask.Status.Set(string(TaskStatusInProgress)), db.CronTask.LastRun.Set(time.Now())). - Exec(ctx) - if err != nil { - return nil, err - } +func (c *libsqlClient) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task, error) { + // if limit < 1 { + // return nil, nil + // } + // if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { + // return nil, err + // } + // defer c.tasksUpdateSem.Release(1) + + // models, err := c.prisma.CronTask. + // FindMany( + // db.CronTask.Status.Equals(string(TaskStatusScheduled)), + // db.CronTask.ScheduledAfter.Lt(time.Now()), + // ). + // OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)). + // Take(limit). + // Exec(ctx) + // if err != nil { + // if database.IsNotFound(err) { + // return nil, nil + // } + // return nil, err + // } + // if len(models) < 1 { + // return nil, nil + // } + + // var ids []string + var tasks []models.Task + // for _, model := range models { + // tasks = append(tasks, Task{}.FromModel(model)) + // ids = append(ids, model.ID) + // } + + // _, err = c.prisma.CronTask. + // FindMany(db.CronTask.ID.In(ids)). + // Update(db.CronTask.Status.Set(string(TaskStatusInProgress)), db.CronTask.LastRun.Set(time.Now())). + // Exec(ctx) + // if err != nil { + // return nil, err + // } return tasks, nil } -func (c *client) CreateTasks(ctx context.Context, tasks ...Task) error { - if len(tasks) < 1 { - return nil - } - // we do not block using c.tasksUpdateSem here because the order of ops in GetAndStartTasks makes this safe - - var txns []db.PrismaTransaction - for _, task := range tasks { - task.OnCreated() - txns = append(txns, c.prisma.CronTask.CreateOne( - db.CronTask.Type.Set(string(task.Type)), - db.CronTask.ReferenceID.Set(task.ReferenceID), - db.CronTask.TargetsEncoded.Set(task.encodeTargets()), - db.CronTask.Status.Set(string(TaskStatusScheduled)), - db.CronTask.LastRun.Set(time.Now()), - db.CronTask.ScheduledAfter.Set(task.ScheduledAfter), - db.CronTask.LogsEncoded.Set(task.encodeLogs()), - db.CronTask.DataEncoded.Set(task.encodeData()), - ).Tx()) - } - - err := c.prisma.Prisma.Transaction(txns...).Exec(ctx) - if err != nil { - return err - } +func (c *libsqlClient) CreateTasks(ctx context.Context, tasks ...models.Task) error { + // if len(tasks) < 1 { + // return nil + // } + // // we do not block using c.tasksUpdateSem here because the order of ops in GetAndStartTasks makes this safe + + // var txns []db.PrismaTransaction + // for _, task := range tasks { + // task.OnCreated() + // txns = append(txns, c.prisma.CronTask.CreateOne( + // db.CronTask.Type.Set(string(task.Type)), + // db.CronTask.ReferenceID.Set(task.ReferenceID), + // db.CronTask.TargetsEncoded.Set(task.encodeTargets()), + // db.CronTask.Status.Set(string(TaskStatusScheduled)), + // db.CronTask.LastRun.Set(time.Now()), + // db.CronTask.ScheduledAfter.Set(task.ScheduledAfter), + // db.CronTask.LogsEncoded.Set(task.encodeLogs()), + // db.CronTask.DataEncoded.Set(task.encodeData()), + // ).Tx()) + // } + + // err := c.prisma.Prisma.Transaction(txns...).Exec(ctx) + // if err != nil { + // return err + // } return nil } @@ -257,35 +154,35 @@ UpdateTasks will update all tasks passed in - the following fields will be replaced: targets, status, leastRun, scheduleAfterm logs, data - this func will block until all other calls to task update funcs are done */ -func (c *client) UpdateTasks(ctx context.Context, tasks ...Task) error { - if len(tasks) < 1 { - return nil - } - - if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { - return err - } - defer c.tasksUpdateSem.Release(1) - - var txns []db.PrismaTransaction - for _, task := range tasks { - task.OnUpdated() - txns = append(txns, c.prisma.CronTask. - FindUnique(db.CronTask.ID.Equals(task.ID)). - Update( - db.CronTask.TargetsEncoded.Set(task.encodeTargets()), - db.CronTask.Status.Set(string(task.Status)), - db.CronTask.LastRun.Set(task.LastRun), - db.CronTask.ScheduledAfter.Set(task.ScheduledAfter), - db.CronTask.LogsEncoded.Set(task.encodeLogs()), - db.CronTask.DataEncoded.Set(task.encodeData()), - ).Tx()) - } - - err := c.prisma.Prisma.Transaction(txns...).Exec(ctx) - if err != nil { - return err - } +func (c *libsqlClient) UpdateTasks(ctx context.Context, tasks ...models.Task) error { + // if len(tasks) < 1 { + // return nil + // } + + // if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { + // return err + // } + // defer c.tasksUpdateSem.Release(1) + + // var txns []db.PrismaTransaction + // for _, task := range tasks { + // task.OnUpdated() + // txns = append(txns, c.prisma.CronTask. + // FindUnique(db.CronTask.ID.Equals(task.ID)). + // Update( + // db.CronTask.TargetsEncoded.Set(task.encodeTargets()), + // db.CronTask.Status.Set(string(task.Status)), + // db.CronTask.LastRun.Set(task.LastRun), + // db.CronTask.ScheduledAfter.Set(task.ScheduledAfter), + // db.CronTask.LogsEncoded.Set(task.encodeLogs()), + // db.CronTask.DataEncoded.Set(task.encodeData()), + // ).Tx()) + // } + + // err := c.prisma.Prisma.Transaction(txns...).Exec(ctx) + // if err != nil { + // return err + // } return nil } @@ -293,19 +190,19 @@ func (c *client) UpdateTasks(ctx context.Context, tasks ...Task) error { DeleteTasks will delete all tasks matching by ids - this func will block until all other calls to task update funcs are done */ -func (c *client) DeleteTasks(ctx context.Context, ids ...string) error { - if len(ids) < 1 { - return nil - } - - if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { - return nil - } - defer c.tasksUpdateSem.Release(1) - - _, err := c.prisma.CronTask.FindMany(db.CronTask.ID.In(ids)).Delete().Exec(ctx) - if err != nil { - return err - } +func (c *libsqlClient) DeleteTasks(ctx context.Context, ids ...string) error { + // if len(ids) < 1 { + // return nil + // } + + // if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { + // return nil + // } + // defer c.tasksUpdateSem.Release(1) + + // _, err := c.prisma.CronTask.FindMany(db.CronTask.ID.In(ids)).Delete().Exec(ctx) + // if err != nil { + // return err + // } return nil } diff --git a/internal/database/users.go b/internal/database/users.go index ed953375..83772c6b 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -2,134 +2,24 @@ package database import ( "context" - "fmt" - "slices" - "time" - "github.com/pkg/errors" - - "github.com/cufee/aftermath/internal/database/prisma/db" - "github.com/cufee/aftermath/internal/encoding" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" ) -type User struct { - ID string - - Permissions permissions.Permissions - - Connections []UserConnection - Subscriptions []UserSubscription -} - -func (u User) Connection(kind ConnectionType) (UserConnection, bool) { - for _, connection := range u.Connections { - if connection.Type == kind { - return connection, true - } - } - return UserConnection{}, false -} - -func (u User) Subscription(kind SubscriptionType) (UserSubscription, bool) { - for _, subscription := range u.Subscriptions { - if subscription.Type == kind { - return subscription, true - } - } - return UserSubscription{}, false -} - -type ConnectionType string - -const ( - ConnectionTypeWargaming = ConnectionType("wargaming") -) - -type UserConnection struct { - ID string `json:"id"` - - Type ConnectionType `json:"type"` - - UserID string `json:"userId"` - ReferenceID string `json:"referenceId"` - Permissions permissions.Permissions `json:"permissions"` - - Metadata map[string]any `json:"metadata"` -} - -func (c UserConnection) FromModel(model *db.UserConnectionModel) UserConnection { - c.ID = model.ID - c.UserID = model.UserID - c.Metadata = make(map[string]any) - c.ReferenceID = model.ReferenceID - c.Permissions = permissions.Parse(model.Permissions, permissions.Blank) - - c.Type = ConnectionType(model.Type) - if model.MetadataEncoded != nil { - _ = encoding.DecodeGob(model.MetadataEncoded, &c.Metadata) - } - return c -} - -type SubscriptionType string - -func (s SubscriptionType) GetPermissions() permissions.Permissions { - switch s { - case SubscriptionTypePlus: - return permissions.SubscriptionAftermathPlus - case SubscriptionTypePro: - return permissions.SubscriptionAftermathPro - case SubscriptionTypeProClan: - return permissions.SubscriptionAftermathPro - default: - return permissions.User - } -} - -// Paid -const SubscriptionTypePro = SubscriptionType("aftermath-pro") -const SubscriptionTypeProClan = SubscriptionType("aftermath-pro-clan") -const SubscriptionTypePlus = SubscriptionType("aftermath-plus") - -// Misc -const SubscriptionTypeSupporter = SubscriptionType("supporter") -const SubscriptionTypeVerifiedClan = SubscriptionType("verified-clan") - -// Moderators -const SubscriptionTypeServerModerator = SubscriptionType("server-moderator") -const SubscriptionTypeContentModerator = SubscriptionType("content-moderator") - -// Special -const SubscriptionTypeDeveloper = SubscriptionType("developer") -const SubscriptionTypeServerBooster = SubscriptionType("server-booster") -const SubscriptionTypeContentTranslator = SubscriptionType("content-translator") - -var AllSubscriptionTypes = []SubscriptionType{ - SubscriptionTypePro, - SubscriptionTypeProClan, - SubscriptionTypePlus, - SubscriptionTypeSupporter, - SubscriptionTypeVerifiedClan, - SubscriptionTypeServerModerator, - SubscriptionTypeContentModerator, - SubscriptionTypeDeveloper, - SubscriptionTypeServerBooster, - SubscriptionTypeContentTranslator, -} - -func (s SubscriptionType) Valid() bool { - return slices.Contains(AllSubscriptionTypes, s) -} +// func (c UserConnection) FromModel(model *db.UserConnectionModel) UserConnection { +// c.ID = model.ID +// c.UserID = model.UserID +// c.Metadata = make(map[string]any) +// c.ReferenceID = model.ReferenceID +// c.Permissions = permissions.Parse(model.Permissions, permissions.Blank) -type UserSubscription struct { - ID string - Type SubscriptionType - UserID string - ExpiresAt time.Time - ReferenceID string - Permissions permissions.Permissions -} +// c.Type = ConnectionType(model.Type) +// if model.MetadataEncoded != nil { +// _ = encoding.DecodeGob(model.MetadataEncoded, &c.Metadata) +// } +// return c +// } type userGetOpts struct { content bool @@ -159,127 +49,129 @@ func WithContent() userGetOption { Gets or creates a user with specified ID - assumes the ID is valid */ -func (c *client) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) { - user, err := c.GetUserByID(ctx, id, opts...) - if err != nil { - if db.IsErrNotFound(err) { - model, err := c.prisma.User.CreateOne(db.User.ID.Set(id), db.User.Permissions.Set(permissions.User.Encode())).Exec(ctx) - if err != nil { - return User{}, err - } - user.ID = model.ID - user.Permissions = permissions.Parse(model.Permissions, permissions.User) - } - return user, nil - } - - return user, nil +func (c *libsqlClient) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { + // user, err := c.GetUserByID(ctx, id, opts...) + // if err != nil { + // if database.IsNotFound(err) { + // model, err := c.prisma.User.CreateOne(db.User.ID.Set(id), db.User.Permissions.Set(permissions.User.Encode())).Exec(ctx) + // if err != nil { + // return User{}, err + // } + // user.ID = model.ID + // user.Permissions = permissions.Parse(model.Permissions, permissions.User) + // } + // return user, nil + // } + + return models.User{}, nil } /* Gets a user with specified ID - assumes the ID is valid */ -func (c *client) GetUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) { - var options userGetOpts - for _, apply := range opts { - apply(&options) - } - - var fields []db.UserRelationWith - if options.subscriptions { - fields = append(fields, db.User.Subscriptions.Fetch()) - } - if options.connections { - fields = append(fields, db.User.Connections.Fetch()) - } - if options.content { - fields = append(fields, db.User.Content.Fetch()) - } - - model, err := c.prisma.User.FindUnique(db.User.ID.Equals(id)).With(fields...).Exec(ctx) - if err != nil { - return User{}, err - } - - var user User - user.ID = model.ID - user.Permissions = permissions.Parse(model.Permissions, permissions.User) - - if options.connections { - for _, cModel := range model.Connections() { - user.Connections = append(user.Connections, UserConnection{}.FromModel(&cModel)) - } - } +func (c *libsqlClient) GetUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { + // var options userGetOpts + // for _, apply := range opts { + // apply(&options) + // } + + // var fields []db.UserRelationWith + // if options.subscriptions { + // fields = append(fields, db.User.Subscriptions.Fetch()) + // } + // if options.connections { + // fields = append(fields, db.User.Connections.Fetch()) + // } + // if options.content { + // fields = append(fields, db.User.Content.Fetch()) + // } + + // model, err := c.prisma.User.FindUnique(db.User.ID.Equals(id)).With(fields...).Exec(ctx) + // if err != nil { + // return User{}, err + // } + + var user models.User + // user.ID = model.ID + // user.Permissions = permissions.Parse(model.Permissions, permissions.User) + + // if options.connections { + // for _, cModel := range model.Connections() { + // user.Connections = append(user.Connections, UserConnection{}.FromModel(&cModel)) + // } + // } return user, nil } -func (c *client) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (User, error) { - model, err := c.prisma.User.UpsertOne(db.User.ID.Equals(userID)). - Create(db.User.ID.Set(userID), db.User.Permissions.Set(perms.String())). - Update(db.User.Permissions.Set(perms.String())).Exec(ctx) - if err != nil { - return User{}, err - } +func (c *libsqlClient) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) { + // model, err := c.prisma.User.UpsertOne(db.User.ID.Equals(userID)). + // Create(db.User.ID.Set(userID), db.User.Permissions.Set(perms.String())). + // Update(db.User.Permissions.Set(perms.String())).Exec(ctx) + // if err != nil { + // return User{}, err + // } - var user User - user.ID = model.ID - user.Permissions = permissions.Parse(model.Permissions, permissions.User) + var user models.User + // user.ID = model.ID + // user.Permissions = permissions.Parse(model.Permissions, permissions.User) return user, nil } -func (c *client) UpdateConnection(ctx context.Context, connection UserConnection) (UserConnection, error) { - if connection.ReferenceID == "" { - return UserConnection{}, errors.New("connection referenceID cannot be left blank") - } - - encoded, err := encoding.EncodeGob(connection.Metadata) - if err != nil { - return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) - } - - model, err := c.prisma.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( - db.UserConnection.ReferenceID.Set(connection.ReferenceID), - db.UserConnection.Permissions.Set(connection.Permissions.Encode()), - db.UserConnection.MetadataEncoded.Set(encoded), - ).Exec(ctx) - if err != nil { - return UserConnection{}, err - } - - return connection.FromModel(model), nil -} - -func (c *client) UpsertConnection(ctx context.Context, connection UserConnection) (UserConnection, error) { - if connection.ID != "" { - return c.UpdateConnection(ctx, connection) - } - if connection.UserID == "" { - return UserConnection{}, errors.New("connection userID cannot be left blank") - } - if connection.ReferenceID == "" { - return UserConnection{}, errors.New("connection referenceID cannot be left blank") - } - if connection.Type == "" { - return UserConnection{}, errors.New("connection Type cannot be left blank") - } - - encoded, err := encoding.EncodeGob(connection.Metadata) - if err != nil { - return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) - } - - model, err := c.prisma.UserConnection.CreateOne( - db.UserConnection.User.Link(db.User.ID.Equals(connection.UserID)), - db.UserConnection.Type.Set(string(connection.Type)), - db.UserConnection.Permissions.Set(connection.Permissions.Encode()), - db.UserConnection.ReferenceID.Set(connection.ReferenceID), - db.UserConnection.MetadataEncoded.Set(encoded), - ).Exec(ctx) - if err != nil { - return UserConnection{}, err - } - - return connection.FromModel(model), nil +func (c *libsqlClient) UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { + // if connection.ReferenceID == "" { + // return UserConnection{}, errors.New("connection referenceID cannot be left blank") + // } + + // encoded, err := encoding.EncodeGob(connection.Metadata) + // if err != nil { + // return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) + // } + + // model, err := c.prisma.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( + // db.UserConnection.ReferenceID.Set(connection.ReferenceID), + // db.UserConnection.Permissions.Set(connection.Permissions.Encode()), + // db.UserConnection.MetadataEncoded.Set(encoded), + // ).Exec(ctx) + // if err != nil { + // return UserConnection{}, err + // } + + // return connection.FromModel(model), nil + return models.UserConnection{}, nil +} + +func (c *libsqlClient) UpsertConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { + // if connection.ID != "" { + // return c.UpdateConnection(ctx, connection) + // } + // if connection.UserID == "" { + // return UserConnection{}, errors.New("connection userID cannot be left blank") + // } + // if connection.ReferenceID == "" { + // return UserConnection{}, errors.New("connection referenceID cannot be left blank") + // } + // if connection.Type == "" { + // return UserConnection{}, errors.New("connection Type cannot be left blank") + // } + + // encoded, err := encoding.EncodeGob(connection.Metadata) + // if err != nil { + // return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) + // } + + // model, err := c.prisma.UserConnection.CreateOne( + // db.UserConnection.User.Link(db.User.ID.Equals(connection.UserID)), + // db.UserConnection.Type.Set(string(connection.Type)), + // db.UserConnection.Permissions.Set(connection.Permissions.Encode()), + // db.UserConnection.ReferenceID.Set(connection.ReferenceID), + // db.UserConnection.MetadataEncoded.Set(encoded), + // ).Exec(ctx) + // if err != nil { + // return UserConnection{}, err + // } + + // return connection.FromModel(model), nil + return models.UserConnection{}, nil } diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index 2e11eb6e..e5cc80df 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -3,97 +3,74 @@ package database import ( "context" - "github.com/cufee/aftermath/internal/database/prisma/db" - "github.com/cufee/aftermath/internal/encoding" - "github.com/rs/zerolog/log" - "github.com/steebchen/prisma-client-go/runtime/transaction" - "golang.org/x/text/language" + "github.com/cufee/aftermath/internal/database/models" ) -type Vehicle struct { - ID string - Tier int - Type string - Class string - Nation string +// func (v Vehicle) FromModel(model db.VehicleModel) Vehicle { +// v.ID = model.ID +// v.Tier = model.Tier +// v.Type = model.Type +// v.Nation = model.Nation +// v.LocalizedNames = make(map[string]string) +// if model.LocalizedNamesEncoded != nil { +// err := encoding.DecodeGob(model.LocalizedNamesEncoded, &v.LocalizedNames) +// if err != nil { +// log.Err(err).Str("id", v.ID).Msg("failed to decode vehicle localized names") +// } +// } +// return v +// } - LocalizedNames map[string]string -} - -func (v Vehicle) FromModel(model db.VehicleModel) Vehicle { - v.ID = model.ID - v.Tier = model.Tier - v.Type = model.Type - v.Nation = model.Nation - v.LocalizedNames = make(map[string]string) - if model.LocalizedNamesEncoded != nil { - err := encoding.DecodeGob(model.LocalizedNamesEncoded, &v.LocalizedNames) - if err != nil { - log.Err(err).Str("id", v.ID).Msg("failed to decode vehicle localized names") - } - } - return v -} - -func (v Vehicle) EncodeNames() []byte { - encoded, _ := encoding.EncodeGob(v.LocalizedNames) - return encoded -} - -func (v Vehicle) Name(locale language.Tag) string { - if n := v.LocalizedNames[locale.String()]; n != "" { - return n - } - if n := v.LocalizedNames[language.English.String()]; n != "" { - return n - } - return "Secret Tank" -} +// func (v Vehicle) EncodeNames() []byte { +// encoded, _ := encoding.EncodeGob(v.LocalizedNames) +// return encoded +// } -func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]Vehicle) error { - if len(vehicles) < 1 { - return nil - } +func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) error { + // if len(vehicles) < 1 { + // return nil + // } - var transactions []transaction.Transaction - for id, data := range vehicles { - transactions = append(transactions, c.prisma.Vehicle. - UpsertOne(db.Vehicle.ID.Equals(id)). - Create( - db.Vehicle.ID.Set(id), - db.Vehicle.Tier.Set(data.Tier), - db.Vehicle.Type.Set(data.Type), - db.Vehicle.Class.Set(data.Class), - db.Vehicle.Nation.Set(data.Nation), - db.Vehicle.LocalizedNamesEncoded.Set(data.EncodeNames()), - ). - Update( - db.Vehicle.Tier.Set(data.Tier), - db.Vehicle.Type.Set(data.Type), - db.Vehicle.Class.Set(data.Class), - db.Vehicle.Nation.Set(data.Nation), - db.Vehicle.LocalizedNamesEncoded.Set(data.EncodeNames()), - ).Tx(), - ) - } + // var transactions []transaction.Transaction + // for id, data := range vehicles { + // transactions = append(transactions, c.prisma.Vehicle. + // UpsertOne(db.Vehicle.ID.Equals(id)). + // Create( + // db.Vehicle.ID.Set(id), + // db.Vehicle.Tier.Set(data.Tier), + // db.Vehicle.Type.Set(data.Type), + // db.Vehicle.Class.Set(data.Class), + // db.Vehicle.Nation.Set(data.Nation), + // db.Vehicle.LocalizedNamesEncoded.Set(data.EncodeNames()), + // ). + // Update( + // db.Vehicle.Tier.Set(data.Tier), + // db.Vehicle.Type.Set(data.Type), + // db.Vehicle.Class.Set(data.Class), + // db.Vehicle.Nation.Set(data.Nation), + // db.Vehicle.LocalizedNamesEncoded.Set(data.EncodeNames()), + // ).Tx(), + // ) + // } - return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) + // return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) + return nil } -func (c *client) GetVehicles(ctx context.Context, ids []string) (map[string]Vehicle, error) { - if len(ids) < 1 { - return nil, nil - } +func (c *libsqlClient) GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) { + // if len(ids) < 1 { + // return nil, nil + // } - models, err := c.prisma.Vehicle.FindMany(db.Vehicle.ID.In(ids)).Exec(ctx) - if err != nil { - return nil, err - } + // models, err := c.prisma.Vehicle.FindMany(db.Vehicle.ID.In(ids)).Exec(ctx) + // if err != nil { + // return nil, err + // } - vehicles := make(map[string]Vehicle) - for _, model := range models { - vehicles[model.ID] = vehicles[model.ID].FromModel(model) - } + vehicles := make(map[string]models.Vehicle) + // for _, model := range models { + // vehicles[model.ID] = vehicles[model.ID].FromModel(model) + // } return vehicles, nil } diff --git a/internal/logic/sessions.go b/internal/logic/sessions.go index d5e75343..36f17b94 100644 --- a/internal/logic/sessions.go +++ b/internal/logic/sessions.go @@ -6,7 +6,8 @@ import ( "time" "github.com/cufee/aftermath/internal/database" - "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/retry" "github.com/cufee/aftermath/internal/stats/fetch" @@ -29,11 +30,11 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } // existing snaphsots for accounts - existingSnapshots, err := dbClient.GetManyAccountSnapshots(ctx, accountIDs, database.SnapshotTypeDaily) - if err != nil && !db.IsErrNotFound(err) { + existingSnapshots, err := dbClient.GetManyAccountSnapshots(ctx, accountIDs, models.SnapshotTypeDaily) + if err != nil && !database.IsNotFound(err) { return nil, errors.Wrap(err, "failed to get existing snapshots") } - existingSnapshotsMap := make(map[string]*database.AccountSnapshot) + existingSnapshotsMap := make(map[string]*models.AccountSnapshot) for _, snapshot := range existingSnapshots { existingSnapshotsMap[snapshot.AccountID] = &snapshot } @@ -90,9 +91,9 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl close(vehicleCh) var accountErrors = make(map[string]error) - var accountUpdates []database.Account - var snapshots []database.AccountSnapshot - var vehicleSnapshots []database.VehicleSnapshot + var accountUpdates []models.Account + var snapshots []models.AccountSnapshot + var vehicleSnapshots []models.VehicleSnapshot for result := range vehicleCh { // there is only 1 key in this map for id, vehicles := range result.Data { @@ -100,18 +101,18 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl accountErrors[id] = result.Err continue } - existingSnapshots, err := dbClient.GetVehicleSnapshots(ctx, id, id, database.SnapshotTypeDaily) - if err != nil && !db.IsErrNotFound(err) { + existingSnapshots, err := dbClient.GetVehicleSnapshots(ctx, id, id, models.SnapshotTypeDaily) + if err != nil && !database.IsNotFound(err) { accountErrors[id] = err continue } - existingSnapshotsMap := make(map[string]*database.VehicleSnapshot) + existingSnapshotsMap := make(map[string]*models.VehicleSnapshot) for _, snapshot := range existingSnapshots { existingSnapshotsMap[snapshot.VehicleID] = &snapshot } stats := fetch.WargamingToStats(realm, accounts[id], clans[id], vehicles) - accountUpdates = append(accountUpdates, database.Account{ + accountUpdates = append(accountUpdates, models.Account{ Realm: stats.Realm, ID: stats.Account.ID, Nickname: stats.Account.Nickname, @@ -123,8 +124,8 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl ClanID: stats.Account.ClanID, ClanTag: stats.Account.ClanTag, }) - snapshots = append(snapshots, database.AccountSnapshot{ - Type: database.SnapshotTypeDaily, + snapshots = append(snapshots, models.AccountSnapshot{ + Type: models.SnapshotTypeDaily, CreatedAt: createdAt, AccountID: stats.Account.ID, ReferenceID: stats.Account.ID, @@ -147,9 +148,9 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl continue } - vehicleSnapshots = append(vehicleSnapshots, database.VehicleSnapshot{ + vehicleSnapshots = append(vehicleSnapshots, models.VehicleSnapshot{ CreatedAt: createdAt, - Type: database.SnapshotTypeDaily, + Type: models.SnapshotTypeDaily, LastBattleTime: vehicle.LastBattleTime, AccountID: stats.Account.ID, VehicleID: vehicle.VehicleID, diff --git a/internal/stats/fetch/client.go b/internal/stats/fetch/client.go index 7844d93f..f15e157b 100644 --- a/internal/stats/fetch/client.go +++ b/internal/stats/fetch/client.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/am-wg-proxy-next/v2/types" ) @@ -12,7 +12,7 @@ import ( type AccountStatsOverPeriod struct { Realm string `json:"realm"` - Account database.Account `json:"account"` + Account models.Account `json:"account"` PeriodStart time.Time `json:"start"` PeriodEnd time.Time `json:"end"` @@ -47,7 +47,7 @@ type StatsWithVehicles struct { } type Client interface { - Account(ctx context.Context, id string) (database.Account, error) + Account(ctx context.Context, id string) (models.Account, error) Search(ctx context.Context, nickname, realm string) (types.Account, error) CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) diff --git a/internal/stats/fetch/convert.go b/internal/stats/fetch/convert.go index 39fa8c39..7a9b27fb 100644 --- a/internal/stats/fetch/convert.go +++ b/internal/stats/fetch/convert.go @@ -5,7 +5,7 @@ import ( "strconv" "time" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/am-wg-proxy-next/v2/types" @@ -15,8 +15,8 @@ func timestampToTime(timestamp int) time.Time { return time.Unix(int64(timestamp), 0) } -func WargamingToAccount(realm string, account types.ExtendedAccount, clan types.ClanMember, private bool) database.Account { - a := database.Account{ +func WargamingToAccount(realm string, account types.ExtendedAccount, clan types.ClanMember, private bool) models.Account { + a := models.Account{ ID: strconv.Itoa(account.ID), Realm: realm, Nickname: account.Nickname, diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index b893916f..752715e3 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -9,7 +9,8 @@ import ( "github.com/pkg/errors" "github.com/cufee/aftermath/internal/database" - "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/retry" @@ -55,7 +56,7 @@ func (c *multiSourceClient) Search(ctx context.Context, nickname, realm string) /* Gets account info from wg and updates cache */ -func (c *multiSourceClient) Account(ctx context.Context, id string) (database.Account, error) { +func (c *multiSourceClient) Account(ctx context.Context, id string) (models.Account, error) { realm := c.wargaming.RealmFromAccountID(id) var group sync.WaitGroup @@ -80,7 +81,7 @@ func (c *multiSourceClient) Account(ctx context.Context, id string) (database.Ac group.Wait() if wgAccount.Err != nil { - return database.Account{}, wgAccount.Err + return models.Account{}, wgAccount.Err } account := WargamingToAccount(realm, wgAccount.Data, clan, false) @@ -88,7 +89,7 @@ func (c *multiSourceClient) Account(ctx context.Context, id string) (database.Ac ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - errMap := c.database.UpsertAccounts(ctx, []database.Account{account}) + errMap := c.database.UpsertAccounts(ctx, []models.Account{account}) if err := errMap[id]; err != nil { log.Err(err).Msg("failed to update account cache") } @@ -171,7 +172,7 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - errMap := c.database.UpsertAccounts(ctx, []database.Account{stats.Account}) + errMap := c.database.UpsertAccounts(ctx, []models.Account{stats.Account}) if err := errMap[id]; err != nil { log.Err(err).Msg("failed to update account cache") } @@ -276,8 +277,8 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session sessionBefore := sessionStart - var accountSnapshot retry.DataWithErr[database.AccountSnapshot] - var vehiclesSnapshots retry.DataWithErr[[]database.VehicleSnapshot] + var accountSnapshot retry.DataWithErr[models.AccountSnapshot] + var vehiclesSnapshots retry.DataWithErr[[]models.VehicleSnapshot] var averages retry.DataWithErr[map[string]frame.StatsFrame] var current retry.DataWithErr[AccountStatsOverPeriod] @@ -304,10 +305,10 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session group.Add(1) go func() { defer group.Done() - s, err := c.database.GetAccountSnapshot(ctx, id, id, database.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) - accountSnapshot = retry.DataWithErr[database.AccountSnapshot]{Data: s, Err: err} - v, err := c.database.GetVehicleSnapshots(ctx, id, id, database.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) - vehiclesSnapshots = retry.DataWithErr[[]database.VehicleSnapshot]{Data: v, Err: err} + s, err := c.database.GetAccountSnapshot(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) + accountSnapshot = retry.DataWithErr[models.AccountSnapshot]{Data: s, Err: err} + v, err := c.database.GetVehicleSnapshots(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) + vehiclesSnapshots = retry.DataWithErr[[]models.VehicleSnapshot]{Data: v, Err: err} }() // wait for all requests to finish and check errors @@ -316,7 +317,7 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, current.Err } if accountSnapshot.Err != nil { - if db.IsErrNotFound(accountSnapshot.Err) { + if database.IsNotFound(accountSnapshot.Err) { return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, ErrSessionNotFound } return AccountStatsOverPeriod{}, AccountStatsOverPeriod{}, accountSnapshot.Err diff --git a/internal/stats/prepare/common/card.go b/internal/stats/prepare/common/card.go index 38eb4b89..fbbfe206 100644 --- a/internal/stats/prepare/common/card.go +++ b/internal/stats/prepare/common/card.go @@ -3,7 +3,7 @@ package common import ( "github.com/pkg/errors" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/frame" ) @@ -82,7 +82,7 @@ func PresetValue(preset Tag, stats frame.StatsFrame, args ...any) (frame.Value, if !ok { return frame.InvalidValue, errors.New("invalid args for avg_tier, first arg should be vehicles") } - glossary, ok := args[1].(map[string]database.Vehicle) + glossary, ok := args[1].(map[string]models.Vehicle) if !ok { return frame.InvalidValue, errors.New("invalid args for avg_tier, second arg should be glossary") } @@ -97,7 +97,7 @@ func PresetValue(preset Tag, stats frame.StatsFrame, args ...any) (frame.Value, } } -func avgTierValue(vehicles map[string]frame.VehicleStatsFrame, glossary map[string]database.Vehicle) frame.Value { +func avgTierValue(vehicles map[string]frame.VehicleStatsFrame, glossary map[string]models.Vehicle) frame.Value { var weightedTotal, battlesTotal float32 for _, vehicle := range vehicles { if data, ok := glossary[vehicle.VehicleID]; ok && data.Tier > 0 { diff --git a/internal/stats/prepare/period/card.go b/internal/stats/prepare/period/card.go index d8511cb1..7fcffe72 100644 --- a/internal/stats/prepare/period/card.go +++ b/internal/stats/prepare/period/card.go @@ -4,18 +4,18 @@ import ( "fmt" "math" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/prepare/common" ) -func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]database.Vehicle, opts ...common.Option) (Cards, error) { +func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]models.Vehicle, opts ...common.Option) (Cards, error) { options := common.DefaultOptions for _, apply := range opts { apply(&options) } if glossary == nil { - glossary = make(map[string]database.Vehicle) + glossary = make(map[string]models.Vehicle) } var cards Cards diff --git a/internal/stats/prepare/period/preset.go b/internal/stats/prepare/period/preset.go index 3a64bbc6..dfe9b336 100644 --- a/internal/stats/prepare/period/preset.go +++ b/internal/stats/prepare/period/preset.go @@ -1,12 +1,12 @@ package period import ( - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/aftermath/internal/stats/prepare/common" ) -func presetToBlock(preset common.Tag, stats frame.StatsFrame, vehicles map[string]frame.VehicleStatsFrame, glossary map[string]database.Vehicle) (common.StatsBlock[BlockData], error) { +func presetToBlock(preset common.Tag, stats frame.StatsFrame, vehicles map[string]frame.VehicleStatsFrame, glossary map[string]models.Vehicle) (common.StatsBlock[BlockData], error) { block := common.StatsBlock[BlockData](common.NewBlock(preset, BlockData{})) switch preset { diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/card.go index fc3f947d..ea7ede65 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/card.go @@ -4,20 +4,20 @@ import ( "math" "slices" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/aftermath/internal/stats/prepare/common" "golang.org/x/text/language" ) -func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string]database.Vehicle, opts ...common.Option) (Cards, error) { +func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string]models.Vehicle, opts ...common.Option) (Cards, error) { options := common.DefaultOptions for _, apply := range opts { apply(&options) } if glossary == nil { - glossary = make(map[string]database.Vehicle) + glossary = make(map[string]models.Vehicle) } var cards Cards @@ -167,7 +167,7 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] return cards, nil } -func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, career frame.VehicleStatsFrame, printer func(string) string, locale language.Tag, glossary database.Vehicle) (VehicleCard, error) { +func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, career frame.VehicleStatsFrame, printer func(string) string, locale language.Tag, glossary models.Vehicle) (VehicleCard, error) { var sFrame, cFrame frame.StatsFrame if session.StatsFrame != nil { sFrame = *session.StatsFrame @@ -194,7 +194,7 @@ func makeVehicleCard(presets []common.Tag, cardType common.CardType, session, ca }, nil } -func makeHighlightCard(highlight common.Highlight, session, career frame.VehicleStatsFrame, printer func(string) string, locale language.Tag, glossary database.Vehicle) (VehicleCard, error) { +func makeHighlightCard(highlight common.Highlight, session, career frame.VehicleStatsFrame, printer func(string) string, locale language.Tag, glossary models.Vehicle) (VehicleCard, error) { var sFrame, cFrame frame.StatsFrame if session.StatsFrame != nil { sFrame = *session.StatsFrame diff --git a/internal/stats/render/common/badges.go b/internal/stats/render/common/badges.go index 3d8e24c0..7b2fa492 100644 --- a/internal/stats/render/common/badges.go +++ b/internal/stats/render/common/badges.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/render/assets" ) @@ -34,20 +34,20 @@ func (sub subscriptionHeader) Block() (Block, error) { } var ( - subscriptionWeight = map[database.SubscriptionType]int{ - database.SubscriptionTypeDeveloper: 999, + subscriptionWeight = map[models.SubscriptionType]int{ + models.SubscriptionTypeDeveloper: 999, // Moderators - database.SubscriptionTypeServerModerator: 99, - database.SubscriptionTypeContentModerator: 98, + models.SubscriptionTypeServerModerator: 99, + models.SubscriptionTypeContentModerator: 98, // Paid - database.SubscriptionTypePro: 89, - database.SubscriptionTypeProClan: 88, - database.SubscriptionTypePlus: 79, + models.SubscriptionTypePro: 89, + models.SubscriptionTypeProClan: 88, + models.SubscriptionTypePlus: 79, // - database.SubscriptionTypeSupporter: 29, - database.SubscriptionTypeServerBooster: 28, + models.SubscriptionTypeSupporter: 29, + models.SubscriptionTypeServerBooster: 28, // - database.SubscriptionTypeVerifiedClan: 19, + models.SubscriptionTypeVerifiedClan: 19, } // Personal @@ -142,8 +142,8 @@ var ( } ) -func SubscriptionsBadges(subscriptions []database.UserSubscription) ([]Block, error) { - slices.SortFunc(subscriptions, func(i, j database.UserSubscription) int { +func SubscriptionsBadges(subscriptions []models.UserSubscription) ([]Block, error) { + slices.SortFunc(subscriptions, func(i, j models.UserSubscription) int { return subscriptionWeight[j.Type] - subscriptionWeight[i.Type] }) @@ -151,11 +151,11 @@ func SubscriptionsBadges(subscriptions []database.UserSubscription) ([]Block, er for _, subscription := range subscriptions { var header *subscriptionHeader switch subscription.Type { - case database.SubscriptionTypeDeveloper: + case models.SubscriptionTypeDeveloper: header = subscriptionDeveloper - case database.SubscriptionTypeServerModerator: + case models.SubscriptionTypeServerModerator: header = subscriptionServerModerator - case database.SubscriptionTypeContentModerator: + case models.SubscriptionTypeContentModerator: header = subscriptionContentModerator } @@ -171,7 +171,7 @@ func SubscriptionsBadges(subscriptions []database.UserSubscription) ([]Block, er for _, subscription := range subscriptions { var header *subscriptionHeader switch subscription.Type { - case database.SubscriptionTypeContentTranslator: + case models.SubscriptionTypeContentTranslator: header = subscriptionTranslator } @@ -187,13 +187,13 @@ func SubscriptionsBadges(subscriptions []database.UserSubscription) ([]Block, er for _, subscription := range subscriptions { var header *subscriptionHeader switch subscription.Type { - case database.SubscriptionTypePro: + case models.SubscriptionTypePro: header = userSubscriptionPro - case database.SubscriptionTypePlus: + case models.SubscriptionTypePlus: header = userSubscriptionPlus - case database.SubscriptionTypeServerBooster: + case models.SubscriptionTypeServerBooster: header = subscriptionServerBooster - case database.SubscriptionTypeSupporter: + case models.SubscriptionTypeSupporter: header = userSubscriptionSupporter } @@ -210,14 +210,14 @@ func SubscriptionsBadges(subscriptions []database.UserSubscription) ([]Block, er return badges, nil } -func ClanSubscriptionsBadges(subscriptions []database.UserSubscription) *subscriptionHeader { +func ClanSubscriptionsBadges(subscriptions []models.UserSubscription) *subscriptionHeader { var headers []*subscriptionHeader for _, subscription := range subscriptions { switch subscription.Type { - case database.SubscriptionTypeProClan: + case models.SubscriptionTypeProClan: headers = append(headers, clanSubscriptionPro) - case database.SubscriptionTypeVerifiedClan: + case models.SubscriptionTypeVerifiedClan: headers = append(headers, clanSubscriptionVerified) } } diff --git a/internal/stats/render/common/header.go b/internal/stats/render/common/header.go index e28f925b..24c8945c 100644 --- a/internal/stats/render/common/header.go +++ b/internal/stats/render/common/header.go @@ -1,16 +1,16 @@ package common import ( - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" ) -func NewHeaderCard(width float64, subscriptions []database.UserSubscription, promoText []string) (Block, bool) { +func NewHeaderCard(width float64, subscriptions []models.UserSubscription, promoText []string) (Block, bool) { var cards []Block var addPromoText = len(promoText) > 0 for _, sub := range subscriptions { switch sub.Type { - case database.SubscriptionTypePro, database.SubscriptionTypePlus, database.SubscriptionTypeDeveloper: + case models.SubscriptionTypePro, models.SubscriptionTypePlus, models.SubscriptionTypeDeveloper: addPromoText = false } if !addPromoText { diff --git a/internal/stats/render/common/player_title.go b/internal/stats/render/common/player_title.go index d77c6a07..7636f5a9 100644 --- a/internal/stats/render/common/player_title.go +++ b/internal/stats/render/common/player_title.go @@ -3,7 +3,7 @@ package common import ( "image/color" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/rs/zerolog/log" ) @@ -34,7 +34,7 @@ func DefaultPlayerTitleStyle(containerStyle Style) TitleCardStyle { } } -func NewPlayerTitleCard(style TitleCardStyle, nickname, clanTag string, subscriptions []database.UserSubscription) Block { +func NewPlayerTitleCard(style TitleCardStyle, nickname, clanTag string, subscriptions []models.UserSubscription) Block { clanTagBlock, hasClanTagBlock := newClanTagBlock(style.ClanTag, clanTag, subscriptions) if !hasClanTagBlock { return NewBlocksContent(style.Container, NewTextContent(style.Nickname, nickname)) @@ -65,7 +65,7 @@ func NewPlayerTitleCard(style TitleCardStyle, nickname, clanTag string, subscrip } -func newClanTagBlock(style Style, clanTag string, subs []database.UserSubscription) (Block, bool) { +func newClanTagBlock(style Style, clanTag string, subs []models.UserSubscription) (Block, bool) { if clanTag == "" { return Block{}, false } diff --git a/internal/stats/render/common/tier_percentage.go b/internal/stats/render/common/tier_percentage.go index dd96c2d4..d6238cc8 100644 --- a/internal/stats/render/common/tier_percentage.go +++ b/internal/stats/render/common/tier_percentage.go @@ -4,11 +4,11 @@ import ( "fmt" "image/color" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/frame" ) -func NewTierPercentageCard(style Style, vehicles map[string]frame.VehicleStatsFrame, glossary map[int]database.Vehicle) Block { +func NewTierPercentageCard(style Style, vehicles map[string]frame.VehicleStatsFrame, glossary map[int]models.Vehicle) Block { var blocks []Block var elements int = 10 diff --git a/internal/stats/render/period/cards.go b/internal/stats/render/period/cards.go index 57918453..0074d6fc 100644 --- a/internal/stats/render/period/cards.go +++ b/internal/stats/render/period/cards.go @@ -5,7 +5,7 @@ import ( "github.com/pkg/errors" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch" prepare "github.com/cufee/aftermath/internal/stats/prepare/common" "github.com/cufee/aftermath/internal/stats/prepare/period" @@ -15,7 +15,7 @@ import ( "github.com/rs/zerolog/log" ) -func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs []database.UserSubscription, opts render.Options) (render.Segments, error) { +func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs []models.UserSubscription, opts render.Options) (render.Segments, error) { if len(cards.Overview.Blocks) == 0 && len(cards.Highlights) == 0 { log.Error().Msg("player cards slice is 0 length, this should not happen") return render.Segments{}, errors.New("no cards provided") diff --git a/internal/stats/render/period/image.go b/internal/stats/render/period/image.go index 95cbfcb3..47f9f5aa 100644 --- a/internal/stats/render/period/image.go +++ b/internal/stats/render/period/image.go @@ -3,13 +3,13 @@ package period import ( "image" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/prepare/period" "github.com/cufee/aftermath/internal/stats/render" ) -func CardsToImage(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs []database.UserSubscription, opts ...render.Option) (image.Image, error) { +func CardsToImage(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs []models.UserSubscription, opts ...render.Option) (image.Image, error) { o := render.DefaultOptions() for _, apply := range opts { apply(&o) diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/cards.go index c09ec22e..28dd1dca 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/cards.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/frame" prepare "github.com/cufee/aftermath/internal/stats/prepare/common" @@ -14,7 +14,7 @@ import ( "github.com/cufee/aftermath/internal/stats/render" ) -func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []database.UserSubscription, opts render.Options) (render.Segments, error) { +func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []models.UserSubscription, opts render.Options) (render.Segments, error) { var ( // primary cards // when there are some unrated battles or no battles at all diff --git a/internal/stats/render/session/image.go b/internal/stats/render/session/image.go index dd19c6c7..65ea4196 100644 --- a/internal/stats/render/session/image.go +++ b/internal/stats/render/session/image.go @@ -3,13 +3,13 @@ package session import ( "image" - "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch" "github.com/cufee/aftermath/internal/stats/prepare/session" "github.com/cufee/aftermath/internal/stats/render" ) -func CardsToImage(session, career fetch.AccountStatsOverPeriod, cards session.Cards, subs []database.UserSubscription, opts ...render.Option) (image.Image, error) { +func CardsToImage(session, career fetch.AccountStatsOverPeriod, cards session.Cards, subs []models.UserSubscription, opts ...render.Option) (image.Image, error) { o := render.DefaultOptions() for _, apply := range opts { apply(&o) diff --git a/internal/stats/session.go b/internal/stats/session.go index aa079f46..22ac652a 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -5,7 +5,7 @@ import ( "slices" "time" - "github.com/cufee/aftermath/internal/database/prisma/db" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/logic" "github.com/cufee/aftermath/internal/stats/fetch" @@ -23,7 +23,7 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time _, err := r.database.GetAccountByID(ctx, accountId) stop() if err != nil { - if db.IsErrNotFound(err) { + if database.IsNotFound(err) { _, err := r.fetchClient.Account(ctx, accountId) // this will cache the account if err != nil { return nil, meta, err diff --git a/main.go b/main.go index cd67cab4..a83c4d65 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,7 @@ import ( _ "github.com/joho/godotenv/autoload" ) -//go:generate go run github.com/steebchen/prisma-client-go generate --schema ./internal/database/prisma/schema.prisma +//go:generate go generate ./internal/database/ent //go:embed static/* var static embed.FS @@ -42,7 +42,7 @@ func main() { loadStaticAssets(static) liveCoreClient, cacheCoreClient := coreClientsFromEnv() - startSchedulerFromEnvAsync(cacheCoreClient.Wargaming()) + startSchedulerFromEnvAsync(cacheCoreClient.Database(), cacheCoreClient.Wargaming()) // Load some init options to registered admin accounts and etc logic.ApplyInitOptions(liveCoreClient.Database()) @@ -71,15 +71,11 @@ func main() { servePublic() } -func startSchedulerFromEnvAsync(wgClient wargaming.Client) { +func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Client) { if os.Getenv("SCHEDULER_ENABLED") != "true" { return } - dbClient, err := database.NewClient() - if err != nil { - log.Fatal().Msgf("database#NewClient failed %s", err) - } bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) if err != nil { log.Fatal().Msgf("failed to init a blitzstars client %s", err) @@ -114,7 +110,7 @@ func startSchedulerFromEnvAsync(wgClient wargaming.Client) { func coreClientsFromEnv() (core.Client, core.Client) { // Dependencies - dbClient, err := database.NewClient() + dbClient, err := database.NewLibSQLClient(os.Getenv("DATABASE_URL")) if err != nil { log.Fatal().Msgf("database#NewClient failed %s", err) } From d905c24f01940f72f14551c29c276937a534a618 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 21:02:27 -0400 Subject: [PATCH 091/341] database client logic --- Taskfile.yaml | 2 +- cmds/core/scheduler/glossary.go | 7 +- cmds/core/server/handlers/private/accounts.go | 6 +- go.sum | 12 + internal/database/accounts.go | 167 +++-- internal/database/averages.go | 115 ++- internal/database/cleanup.go | 43 +- internal/database/client.go | 20 +- internal/database/discord.go | 131 ++-- internal/database/ent/db/account.go | 16 +- internal/database/ent/db/account/account.go | 6 +- internal/database/ent/db/account/where.go | 72 +- internal/database/ent/db/account_create.go | 20 +- internal/database/ent/db/account_query.go | 4 +- internal/database/ent/db/account_update.go | 56 +- internal/database/ent/db/accountsnapshot.go | 12 +- .../ent/db/accountsnapshot/accountsnapshot.go | 6 +- .../database/ent/db/accountsnapshot/where.go | 54 +- .../database/ent/db/accountsnapshot_create.go | 16 +- .../database/ent/db/accountsnapshot_query.go | 4 +- .../database/ent/db/accountsnapshot_update.go | 36 +- .../database/ent/db/achievementssnapshot.go | 12 +- .../achievementssnapshot.go | 6 +- .../ent/db/achievementssnapshot/where.go | 54 +- .../ent/db/achievementssnapshot_create.go | 16 +- .../ent/db/achievementssnapshot_query.go | 4 +- .../ent/db/achievementssnapshot_update.go | 36 +- internal/database/ent/db/appconfiguration.go | 8 +- .../db/appconfiguration/appconfiguration.go | 6 +- .../database/ent/db/appconfiguration/where.go | 36 +- .../ent/db/appconfiguration_create.go | 12 +- .../database/ent/db/appconfiguration_query.go | 4 +- .../ent/db/appconfiguration_update.go | 16 +- .../database/ent/db/applicationcommand.go | 8 +- .../applicationcommand/applicationcommand.go | 6 +- .../ent/db/applicationcommand/where.go | 36 +- .../ent/db/applicationcommand_create.go | 12 +- .../ent/db/applicationcommand_query.go | 4 +- .../ent/db/applicationcommand_update.go | 16 +- internal/database/ent/db/clan.go | 8 +- internal/database/ent/db/clan/clan.go | 6 +- internal/database/ent/db/clan/where.go | 36 +- internal/database/ent/db/clan_create.go | 12 +- internal/database/ent/db/clan_query.go | 4 +- internal/database/ent/db/clan_update.go | 16 +- internal/database/ent/db/crontask.go | 28 +- internal/database/ent/db/crontask/crontask.go | 33 +- internal/database/ent/db/crontask/where.go | 225 ++---- internal/database/ent/db/crontask_create.go | 32 +- internal/database/ent/db/crontask_query.go | 4 +- internal/database/ent/db/crontask_update.go | 104 +-- internal/database/ent/db/migrate/schema.go | 76 +- internal/database/ent/db/mutation.go | 684 +++++++++--------- internal/database/ent/db/runtime.go | 92 ++- internal/database/ent/db/user.go | 8 +- internal/database/ent/db/user/user.go | 6 +- internal/database/ent/db/user/where.go | 36 +- internal/database/ent/db/user_create.go | 12 +- internal/database/ent/db/user_query.go | 4 +- internal/database/ent/db/user_update.go | 16 +- internal/database/ent/db/userconnection.go | 8 +- .../ent/db/userconnection/userconnection.go | 6 +- .../database/ent/db/userconnection/where.go | 36 +- .../database/ent/db/userconnection_create.go | 12 +- .../database/ent/db/userconnection_query.go | 4 +- .../database/ent/db/userconnection_update.go | 16 +- internal/database/ent/db/usercontent.go | 8 +- .../ent/db/usercontent/usercontent.go | 6 +- internal/database/ent/db/usercontent/where.go | 36 +- .../database/ent/db/usercontent_create.go | 12 +- internal/database/ent/db/usercontent_query.go | 4 +- .../database/ent/db/usercontent_update.go | 16 +- internal/database/ent/db/usersubscription.go | 12 +- .../db/usersubscription/usersubscription.go | 6 +- .../database/ent/db/usersubscription/where.go | 54 +- .../ent/db/usersubscription_create.go | 16 +- .../database/ent/db/usersubscription_query.go | 4 +- .../ent/db/usersubscription_update.go | 36 +- internal/database/ent/db/vehicle.go | 8 +- internal/database/ent/db/vehicle/vehicle.go | 6 +- internal/database/ent/db/vehicle/where.go | 36 +- internal/database/ent/db/vehicle_create.go | 12 +- internal/database/ent/db/vehicle_query.go | 4 +- internal/database/ent/db/vehicle_update.go | 16 +- internal/database/ent/db/vehicleaverage.go | 10 +- .../ent/db/vehicleaverage/vehicleaverage.go | 6 +- .../database/ent/db/vehicleaverage/where.go | 36 +- .../database/ent/db/vehicleaverage_create.go | 16 +- .../database/ent/db/vehicleaverage_query.go | 4 +- .../database/ent/db/vehicleaverage_update.go | 40 +- internal/database/ent/db/vehiclesnapshot.go | 12 +- .../ent/db/vehiclesnapshot/vehiclesnapshot.go | 6 +- .../database/ent/db/vehiclesnapshot/where.go | 54 +- .../database/ent/db/vehiclesnapshot_create.go | 16 +- .../database/ent/db/vehiclesnapshot_query.go | 4 +- .../database/ent/db/vehiclesnapshot_update.go | 36 +- internal/database/ent/migrate.go | 63 -- internal/database/ent/migrate/main.go | 2 +- internal/database/ent/schema/account.go | 8 +- ...accountsnapshot.go => account_snapshot.go} | 2 +- ...tssnapshot.go => achievements_snapshot.go} | 2 +- ...pconfiguration.go => app_configuration.go} | 0 ...ationcommand.go => application_command.go} | 0 internal/database/ent/schema/clan.go | 4 +- .../ent/schema/{crontask.go => cron_task.go} | 10 +- internal/database/ent/schema/defaults.go | 6 +- internal/database/ent/schema/user.go | 4 +- .../{userconnection.go => user_connection.go} | 0 .../{usercontent.go => user_content.go} | 0 ...ersubscription.go => user_subscription.go} | 2 +- internal/database/ent/schema/vehicle.go | 4 +- .../{vehicleaverage.go => vehicle_average.go} | 6 +- ...vehiclesnapshot.go => vehicle_snapshot.go} | 2 +- internal/database/models/vehicle.go | 8 +- internal/database/snapshots.go | 461 ++++-------- internal/database/tasks.go | 294 ++++---- internal/database/users.go | 239 +++--- internal/database/vehicles.go | 128 ++-- internal/stats/fetch/multisource.go | 8 +- 119 files changed, 2106 insertions(+), 2300 deletions(-) delete mode 100644 internal/database/ent/migrate.go rename internal/database/ent/schema/{accountsnapshot.go => account_snapshot.go} (97%) rename internal/database/ent/schema/{achievementssnapshot.go => achievements_snapshot.go} (97%) rename internal/database/ent/schema/{appconfiguration.go => app_configuration.go} (100%) rename internal/database/ent/schema/{applicationcommand.go => application_command.go} (100%) rename internal/database/ent/schema/{crontask.go => cron_task.go} (82%) rename internal/database/ent/schema/{userconnection.go => user_connection.go} (100%) rename internal/database/ent/schema/{usercontent.go => user_content.go} (100%) rename internal/database/ent/schema/{usersubscription.go => user_subscription.go} (97%) rename internal/database/ent/schema/{vehicleaverage.go => vehicle_average.go} (85%) rename internal/database/ent/schema/{vehiclesnapshot.go => vehicle_snapshot.go} (97%) diff --git a/Taskfile.yaml b/Taskfile.yaml index 8cbe17a8..cd2e67c1 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -29,7 +29,7 @@ tasks: db-migrate: desc: generate migrations - cmd: atlas migrate diff init --dir "file://internal/database/ent/migrate/migrations" --to "ent://internal/database/ent/schema" --dev-url "sqlite://tmp/file?mode=memory&_fk=1" + cmd: atlas migrate diff {{.CLI_ARGS}} --dir "file://internal/database/ent/migrate/migrations" --to "ent://internal/database/ent/schema" --dev-url "sqlite://file?mode=memory&_fk=1" db-migrate-apply: desc: apply migrations using atlas cmd: ${ATLAS_TEMP_DIR} migrate apply --allow-dirty --dir "file://internal/database/ent/migrate/migrations" --tx-mode all --url "libsql+ws://0.0.0.0:8080" diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go index 89f5d23e..14327f15 100644 --- a/cmds/core/scheduler/glossary.go +++ b/cmds/core/scheduler/glossary.go @@ -51,11 +51,8 @@ func UpdateGlossaryWorker(client core.Client) func() { vehicles := make(map[string]models.Vehicle) for id, data := range glossary { vehicles[id] = models.Vehicle{ - ID: id, - Tier: data.Tier, - Type: data.Type, - // Class: , - Nation: data.Nation, + ID: id, + Tier: data.Tier, LocalizedNames: map[string]string{language.English.String(): data.Name}, } } diff --git a/cmds/core/server/handlers/private/accounts.go b/cmds/core/server/handlers/private/accounts.go index 2afb639b..e1128c0d 100644 --- a/cmds/core/server/handlers/private/accounts.go +++ b/cmds/core/server/handlers/private/accounts.go @@ -96,9 +96,9 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { inserts = append(inserts, fetch.WargamingToAccount(realm, account, types.ClanMember{}, false)) } - errors := client.Database().UpsertAccounts(ctx, inserts) - if len(errors) > 0 { - log.Error().Any("errors", errors).Msg("failed to upsert some accounts") + err = client.Database().UpsertAccounts(ctx, inserts) + if err != nil { + log.Err(err).Msg("failed to upsert accounts") return } diff --git a/go.sum b/go.sum index 24ee26f5..844c602f 100644 --- a/go.sum +++ b/go.sum @@ -67,17 +67,23 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -88,6 +94,10 @@ github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -148,6 +158,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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= diff --git a/internal/database/accounts.go b/internal/database/accounts.go index 79f5a55e..ef7be030 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -10,8 +10,8 @@ import ( "github.com/cufee/aftermath/internal/database/models" ) -func toAccount(model db.Account) models.Account { - return models.Account{ +func toAccount(model *db.Account) models.Account { + account := models.Account{ ID: model.ID, Realm: model.Realm, Nickname: model.Nickname, @@ -19,6 +19,11 @@ func toAccount(model db.Account) models.Account { CreatedAt: time.Unix(int64(model.AccountCreatedAt), 0), LastBattleTime: time.Unix(int64(model.LastBattleTime), 0), } + if model.Edges.Clan != nil { + account.ClanID = model.Edges.Clan.ID + account.ClanTag = model.Edges.Clan.Tag + } + return account } func (c *libsqlClient) GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) { @@ -36,96 +41,98 @@ func (c *libsqlClient) GetRealmAccountIDs(ctx context.Context, realm string) ([] } func (c *libsqlClient) GetAccountByID(ctx context.Context, id string) (models.Account, error) { - // model, err := c.prisma.Account.FindUnique(db.Account.ID.Equals(id)).With(db.Account.Clan.Fetch()).Exec(ctx) - // if err != nil { - // return Account{}, err - // } - - // account := Account{}.FromModel(*model) - // if clan, ok := model.Clan(); ok { - // account.AddClan(clan) - // } - - return models.Account{}, nil + result, err := c.db.Account.Query().Where(account.ID(id)).WithClan().Only(ctx) + if err != nil { + return models.Account{}, err + } + return toAccount(result), nil } func (c *libsqlClient) GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) { - // if len(ids) < 1 { - // return nil, nil - // } + if len(ids) < 1 { + return nil, nil + } - // models, err := c.prisma.Account.FindMany(db.Account.ID.In(ids)).With(db.Account.Clan.Fetch()).Exec(ctx) - // if err != nil { - // return nil, err - // } + result, err := c.db.Account.Query().Where(account.IDIn(ids...)).WithClan().All(ctx) + if err != nil { + return nil, err + } var accounts []models.Account - // for _, model := range models { - // account := Account{}.FromModel(model) - // if clan, ok := model.Clan(); ok { - // account.AddClan(clan) - // } - // accounts = append(accounts, account) - // } + for _, a := range result { + accounts = append(accounts, toAccount(a)) + } return accounts, nil } -func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Account) map[string]error { - // if len(accounts) < 1 { - // return nil - // } - - // var mx sync.Mutex - // var wg sync.WaitGroup - errors := make(map[string]error, len(accounts)) - - // // we don't really want to exit if one fails - // for _, account := range accounts { - // wg.Add(1) - // go func(account Account) { - // defer wg.Done() - // optional := []db.AccountSetParam{db.Account.Private.Set(account.Private)} - // if account.ClanID != "" { - // clan, err := c.prisma.Clan.FindUnique(db.Clan.ID.Equals(account.ClanID)).Exec(ctx) - // if err == nil { - // optional = append(optional, db.Account.Clan.Link(db.Clan.ID.Equals(clan.ID))) - // } - // } - - // _, err := c.prisma.Account. - // UpsertOne(db.Account.ID.Equals(account.ID)). - // Create(db.Account.ID.Set(account.ID), - // db.Account.LastBattleTime.Set(account.LastBattleTime), - // db.Account.AccountCreatedAt.Set(account.CreatedAt), - // db.Account.Realm.Set(account.Realm), - // db.Account.Nickname.Set(account.Nickname), - // optional..., - // ). - // Update( - // append(optional, - // db.Account.Nickname.Set(account.Nickname), - // db.Account.LastBattleTime.Set(account.LastBattleTime), - // )..., - // ). - // Exec(ctx) - // if err != nil { - // mx.Lock() - // errors[account.ID] = err - // mx.Unlock() - // return - // } - // }(account) - // } - // wg.Wait() - - return errors +func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Account) error { + if len(accounts) < 1 { + return nil + } + + var ids []string + accountsMap := make(map[string]*models.Account) + for _, a := range accounts { + ids = append(ids, a.ID) + accountsMap[a.ID] = &a + } + + tx, err := c.db.Tx(ctx) + if err != nil { + return err + } + + records, err := tx.Account.Query().Where(account.IDIn(ids...)).All(ctx) + if err != nil && !IsNotFound(err) { + return rollback(tx, err) + } + + for _, r := range records { + update, ok := accountsMap[r.ID] + if !ok { + continue // this should never happen tho + } + + err = tx.Account.UpdateOneID(r.ID). + SetRealm(update.Realm). + SetNickname(update.Nickname). + SetPrivate(update.Private). + SetLastBattleTime(update.LastBattleTime.Unix()). + SetClanID(update.ClanID). + Exec(ctx) + if err != nil { + return rollback(tx, err) + } + + delete(accountsMap, r.ID) + } + + var inserts []*db.AccountCreate + for _, a := range accountsMap { + inserts = append(inserts, + c.db.Account.Create(). + SetID(a.ID). + SetRealm(a.Realm). + SetNickname(a.Nickname). + SetPrivate(a.Private). + SetAccountCreatedAt(a.CreatedAt.Unix()). + SetLastBattleTime(a.LastBattleTime.Unix()). + SetClanID(a.ClanID), + ) + } + + err = c.db.Account.CreateBulk(inserts...).Exec(ctx) + if err != nil { + return rollback(tx, err) + } + return tx.Commit() } func (c *libsqlClient) AccountSetPrivate(ctx context.Context, id string, value bool) error { - // _, err := c.prisma.Account.FindUnique(db.Account.ID.Equals(id)).Update(db.Account.Private.Set(value)).Exec(ctx) - // if err != nil && !database.IsNotFound(err) { - // return err - // } + err := c.db.Account.UpdateOneID(id).SetPrivate(value).Exec(ctx) + if err != nil && !IsNotFound(err) { + return err + } return nil } diff --git a/internal/database/averages.go b/internal/database/averages.go index b798fe9b..881307b3 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -3,78 +3,75 @@ package database import ( "context" + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" "github.com/cufee/aftermath/internal/stats/frame" ) func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { - // if len(averages) < 1 { - // return nil - // } + if len(averages) < 1 { + return nil + } - // var transactions []transaction.Transaction - // for id, data := range averages { - // encoded, err := data.Encode() - // if err != nil { - // log.Err(err).Str("id", id).Msg("failed to encode a stats frame for vehicle averages") - // continue - // } + var ids []string + for id := range averages { + ids = append(ids, id) + } - // transactions = append(transactions, c.prisma.VehicleAverage. - // UpsertOne(db.VehicleAverage.ID.Equals(id)). - // Create( - // db.VehicleAverage.ID.Set(id), - // db.VehicleAverage.DataEncoded.Set(encoded), - // ). - // Update( - // db.VehicleAverage.DataEncoded.Set(encoded), - // ).Tx(), - // ) - // } + tx, err := c.db.Tx(ctx) + if err != nil { + return err + } - // return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) - return nil -} + existing, err := tx.VehicleAverage.Query().Where(vehicleaverage.IDIn(ids...)).All(ctx) + if err != nil { + return rollback(tx, err) + } -func (c *libsqlClient) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { - // if len(ids) < 1 { - // return nil, nil - // } + for _, r := range existing { + update, ok := averages[r.ID] + if !ok { + continue // should never happen tho + } - // qCtx, cancel := context.WithTimeout(ctx, time.Second) - // defer cancel() - // records, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(ids)).Exec(qCtx) - // if err != nil { - // return nil, err - // } + err := tx.VehicleAverage.UpdateOneID(r.ID).SetData(update).Exec(ctx) + if err != nil { + return rollback(tx, err) + } - averages := make(map[string]frame.StatsFrame) - // var badRecords []string - // var lastErr error + delete(averages, r.ID) + } + + var inserts []*db.VehicleAverageCreate + for id, frame := range averages { + inserts = append(inserts, + c.db.VehicleAverage.Create(). + SetID(id). + SetData(frame), + ) + } - // for _, record := range records { - // parsed, err := frame.DecodeStatsFrame(record.DataEncoded) - // lastErr = err - // if err != nil { - // badRecords = append(badRecords, record.ID) - // continue - // } - // averages[record.ID] = parsed - // } + err = tx.VehicleAverage.CreateBulk(inserts...).Exec(ctx) + if err != nil { + return rollback(tx, err) + } - // if len(badRecords) == len(ids) || len(badRecords) == 0 { - // return averages, lastErr - // } + return tx.Commit() +} + +func (c *libsqlClient) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { + if len(ids) < 1 { + return nil, nil + } - // go func() { - // // one bad record should not break the whole query since this data is optional - // // we can just delete the record and move on - // ctx, cancel := context.WithCancel(context.Background()) - // defer cancel() - // _, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(badRecords)).Delete().Exec(ctx) - // if err != nil { - // log.Err(err).Strs("ids", badRecords).Msg("failed to delete a bad vehicle average records") - // } - // }() + records, err := c.db.VehicleAverage.Query().Where(vehicleaverage.IDIn(ids...)).All(ctx) + if err != nil { + return nil, err + } + averages := make(map[string]frame.StatsFrame) + for _, a := range records { + averages[a.ID] = a.Data + } return averages, nil } diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go index 22eae805..89216aa7 100644 --- a/internal/database/cleanup.go +++ b/internal/database/cleanup.go @@ -3,24 +3,41 @@ package database import ( "context" "time" + + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" ) func (c *libsqlClient) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { - // _, err := c.prisma.CronTask.FindMany(db.CronTask.CreatedAt.Before(expiration)).Delete().Exec(ctx) - // if err != nil && !database.IsNotFound(err) { - // return err - // } - return nil + tx, err := c.db.Tx(ctx) + if err != nil { + return err + } + + _, err = tx.CronTask.Delete().Where(crontask.CreatedAtLT(expiration.Unix())).Exec(ctx) + if err != nil && !IsNotFound(err) { + return rollback(tx, err) + } + return tx.Commit() } func (c *libsqlClient) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { - // _, err := c.prisma.AccountSnapshot.FindMany(db.AccountSnapshot.CreatedAt.Before(expiration)).Delete().Exec(ctx) - // if err != nil && !database.IsNotFound(err) { - // return err - // } - // _, err = c.prisma.VehicleSnapshot.FindMany(db.VehicleSnapshot.CreatedAt.Before(expiration)).Delete().Exec(ctx) - // if err != nil && !database.IsNotFound(err) { - // return err - // } + _, err := c.db.AccountSnapshot.Delete().Where(accountsnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) + if err != nil && !IsNotFound(err) { + return err + } + + _, err = c.db.VehicleSnapshot.Delete().Where(vehiclesnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) + if err != nil && !IsNotFound(err) { + return err + } + + _, err = c.db.AchievementsSnapshot.Delete().Where(achievementssnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) + if err != nil && !IsNotFound(err) { + return err + } + return nil } diff --git a/internal/database/client.go b/internal/database/client.go index 3166f44b..e1b4024d 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -3,13 +3,13 @@ package database import ( "context" "database/sql" + "fmt" "time" "github.com/cufee/aftermath/internal/database/ent/db" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" "github.com/cufee/aftermath/internal/stats/frame" - "golang.org/x/sync/semaphore" "entgo.io/ent/dialect" entsql "entgo.io/ent/dialect/sql" @@ -22,7 +22,7 @@ type AccountsClient interface { GetAccountByID(ctx context.Context, id string) (models.Account, error) GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) - UpsertAccounts(ctx context.Context, accounts []models.Account) map[string]error + UpsertAccounts(ctx context.Context, accounts []models.Account) error AccountSetPrivate(ctx context.Context, id string, value bool) error } @@ -81,8 +81,7 @@ type Client interface { } type libsqlClient struct { - db *db.Client - tasksUpdateSem *semaphore.Weighted + db *db.Client } func (c *libsqlClient) Disconnect() error { @@ -96,9 +95,16 @@ func NewLibSQLClient(primaryUrl string) (*libsqlClient, error) { } dbClient := db.NewClient(db.Driver(entsql.OpenDB(dialect.SQLite, driver))) - return &libsqlClient{ - db: dbClient, - tasksUpdateSem: semaphore.NewWeighted(1), + db: dbClient, }, nil } + +// rollback calls to tx.Rollback and wraps the given error +// with the rollback error if occurred. +func rollback(tx *db.Tx, err error) error { + if rerr := tx.Rollback(); rerr != nil { + err = fmt.Errorf("%w: %v", err, rerr) + } + return err +} diff --git a/internal/database/discord.go b/internal/database/discord.go index c9d368ba..91f084b3 100644 --- a/internal/database/discord.go +++ b/internal/database/discord.go @@ -3,64 +3,109 @@ package database import ( "context" + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" "github.com/cufee/aftermath/internal/database/models" ) +func toApplicationCommand(record *db.ApplicationCommand) models.ApplicationCommand { + return models.ApplicationCommand{ + ID: record.ID, + Name: record.Name, + Hash: record.OptionsHash, + Version: record.Version, + } + +} + func (c *libsqlClient) GetCommandsByID(ctx context.Context, commandIDs ...string) ([]models.ApplicationCommand, error) { - // if len(commandIDs) < 1 { - // return nil, nil - // } + if len(commandIDs) < 1 { + return nil, nil + } - // models, err := c.prisma.models.ApplicationCommand.FindMany(db.models.ApplicationCommand.ID.In(commandIDs)).Exec(ctx) - // if err != nil { - // return nil, err - // } + records, err := c.db.ApplicationCommand.Query().Where(applicationcommand.IDIn(commandIDs...)).All(ctx) + if err != nil { + return nil, err + } var commands []models.ApplicationCommand - // for _, model := range models { - // commands = append(commands, models.ApplicationCommand{}.fromModel(model)) - // } + for _, c := range records { + commands = append(commands, toApplicationCommand(c)) + } return commands, nil } func (c *libsqlClient) GetCommandsByHash(ctx context.Context, commandHashes ...string) ([]models.ApplicationCommand, error) { - // if len(commandHashes) < 1 { - // return nil, nil - // } - - // models, err := c.prisma.models.ApplicationCommand.FindMany(db.models.ApplicationCommand.OptionsHash.In(commandHashes)).Exec(ctx) - // if err != nil { - // return nil, err - // } + if len(commandHashes) < 1 { + return nil, nil + } + records, err := c.db.ApplicationCommand.Query().Where(applicationcommand.OptionsHashIn(commandHashes...)).All(ctx) + if err != nil { + return nil, err + } var commands []models.ApplicationCommand - // for _, model := range models { - // commands = append(commands, models.ApplicationCommand{}.fromModel(model)) - // } + for _, c := range records { + commands = append(commands, toApplicationCommand(c)) + } return commands, nil } func (c *libsqlClient) UpsertCommands(ctx context.Context, commands ...models.ApplicationCommand) error { - // if len(commands) < 1 { - // return nil - // } - - // var tx []db.PrismaTransaction - // for _, cmd := range commands { - // tx = append(tx, c.prisma.models.ApplicationCommand.UpsertOne(db.models.ApplicationCommand.ID.Equals(cmd.ID)). - // Create( - // db.models.ApplicationCommand.ID.Set(cmd.ID), - // db.models.ApplicationCommand.Name.Set(cmd.Name), - // db.models.ApplicationCommand.Version.Set(cmd.Version), - // db.models.ApplicationCommand.OptionsHash.Set(cmd.Hash), - // ). - // Update( - // db.models.ApplicationCommand.Name.Set(cmd.Name), - // db.models.ApplicationCommand.Version.Set(cmd.Version), - // db.models.ApplicationCommand.OptionsHash.Set(cmd.Hash), - // ).Tx(), - // ) - // } - // return c.prisma.Prisma.Transaction(tx...).Exec(ctx) - return nil + if len(commands) < 1 { + return nil + } + + var ids []string + commandsMap := make(map[string]*models.ApplicationCommand) + for _, c := range commands { + ids = append(ids, c.ID) + commandsMap[c.ID] = &c + } + + tx, err := c.db.Tx(ctx) + if err != nil { + return err + } + + existing, err := tx.ApplicationCommand.Query().Where(applicationcommand.IDIn(ids...)).All(ctx) + if err != nil { + return rollback(tx, err) + } + + for _, c := range existing { + update, ok := commandsMap[c.ID] + if !ok { + continue + } + + err := tx.ApplicationCommand.UpdateOneID(c.ID). + SetName(update.Name). + SetVersion(update.Version). + SetOptionsHash(update.Hash). + Exec(ctx) + if err != nil { + return rollback(tx, err) + } + + delete(commandsMap, c.ID) + } + + var inserts []*db.ApplicationCommandCreate + for _, cmd := range commandsMap { + inserts = append(inserts, + c.db.ApplicationCommand.Create(). + SetID(cmd.ID). + SetName(cmd.Name). + SetVersion(cmd.Version). + SetOptionsHash(cmd.Hash), + ) + } + + err = tx.ApplicationCommand.CreateBulk(inserts...).Exec(ctx) + if err != nil { + return rollback(tx, err) + } + + return tx.Commit() } diff --git a/internal/database/ent/db/account.go b/internal/database/ent/db/account.go index 7cdf8b15..40cc0fa5 100644 --- a/internal/database/ent/db/account.go +++ b/internal/database/ent/db/account.go @@ -18,13 +18,13 @@ type Account struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // LastBattleTime holds the value of the "last_battle_time" field. - LastBattleTime int `json:"last_battle_time,omitempty"` + LastBattleTime int64 `json:"last_battle_time,omitempty"` // AccountCreatedAt holds the value of the "account_created_at" field. - AccountCreatedAt int `json:"account_created_at,omitempty"` + AccountCreatedAt int64 `json:"account_created_at,omitempty"` // Realm holds the value of the "realm" field. Realm string `json:"realm,omitempty"` // Nickname holds the value of the "nickname" field. @@ -128,25 +128,25 @@ func (a *Account) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - a.CreatedAt = int(value.Int64) + a.CreatedAt = value.Int64 } case account.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - a.UpdatedAt = int(value.Int64) + a.UpdatedAt = value.Int64 } case account.FieldLastBattleTime: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) } else if value.Valid { - a.LastBattleTime = int(value.Int64) + a.LastBattleTime = value.Int64 } case account.FieldAccountCreatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field account_created_at", values[i]) } else if value.Valid { - a.AccountCreatedAt = int(value.Int64) + a.AccountCreatedAt = value.Int64 } case account.FieldRealm: if value, ok := values[i].(*sql.NullString); !ok { diff --git a/internal/database/ent/db/account/account.go b/internal/database/ent/db/account/account.go index d2979c1d..3434e510 100644 --- a/internal/database/ent/db/account/account.go +++ b/internal/database/ent/db/account/account.go @@ -93,11 +93,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // RealmValidator is a validator for the "realm" field. It is called by the builders before save. RealmValidator func(string) error // NicknameValidator is a validator for the "nickname" field. It is called by the builders before save. diff --git a/internal/database/ent/db/account/where.go b/internal/database/ent/db/account/where.go index a6b0cd6d..8f872414 100644 --- a/internal/database/ent/db/account/where.go +++ b/internal/database/ent/db/account/where.go @@ -64,22 +64,22 @@ func IDContainsFold(id string) predicate.Account { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.Account { +func CreatedAt(v int64) predicate.Account { return predicate.Account(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.Account { +func UpdatedAt(v int64) predicate.Account { return predicate.Account(sql.FieldEQ(FieldUpdatedAt, v)) } // LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. -func LastBattleTime(v int) predicate.Account { +func LastBattleTime(v int64) predicate.Account { return predicate.Account(sql.FieldEQ(FieldLastBattleTime, v)) } // AccountCreatedAt applies equality check predicate on the "account_created_at" field. It's identical to AccountCreatedAtEQ. -func AccountCreatedAt(v int) predicate.Account { +func AccountCreatedAt(v int64) predicate.Account { return predicate.Account(sql.FieldEQ(FieldAccountCreatedAt, v)) } @@ -104,162 +104,162 @@ func ClanID(v string) predicate.Account { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.Account { +func CreatedAtEQ(v int64) predicate.Account { return predicate.Account(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.Account { +func CreatedAtNEQ(v int64) predicate.Account { return predicate.Account(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.Account { +func CreatedAtIn(vs ...int64) predicate.Account { return predicate.Account(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.Account { +func CreatedAtNotIn(vs ...int64) predicate.Account { return predicate.Account(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.Account { +func CreatedAtGT(v int64) predicate.Account { return predicate.Account(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.Account { +func CreatedAtGTE(v int64) predicate.Account { return predicate.Account(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.Account { +func CreatedAtLT(v int64) predicate.Account { return predicate.Account(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.Account { +func CreatedAtLTE(v int64) predicate.Account { return predicate.Account(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.Account { +func UpdatedAtEQ(v int64) predicate.Account { return predicate.Account(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.Account { +func UpdatedAtNEQ(v int64) predicate.Account { return predicate.Account(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.Account { +func UpdatedAtIn(vs ...int64) predicate.Account { return predicate.Account(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.Account { +func UpdatedAtNotIn(vs ...int64) predicate.Account { return predicate.Account(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.Account { +func UpdatedAtGT(v int64) predicate.Account { return predicate.Account(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.Account { +func UpdatedAtGTE(v int64) predicate.Account { return predicate.Account(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.Account { +func UpdatedAtLT(v int64) predicate.Account { return predicate.Account(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.Account { +func UpdatedAtLTE(v int64) predicate.Account { return predicate.Account(sql.FieldLTE(FieldUpdatedAt, v)) } // LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. -func LastBattleTimeEQ(v int) predicate.Account { +func LastBattleTimeEQ(v int64) predicate.Account { return predicate.Account(sql.FieldEQ(FieldLastBattleTime, v)) } // LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. -func LastBattleTimeNEQ(v int) predicate.Account { +func LastBattleTimeNEQ(v int64) predicate.Account { return predicate.Account(sql.FieldNEQ(FieldLastBattleTime, v)) } // LastBattleTimeIn applies the In predicate on the "last_battle_time" field. -func LastBattleTimeIn(vs ...int) predicate.Account { +func LastBattleTimeIn(vs ...int64) predicate.Account { return predicate.Account(sql.FieldIn(FieldLastBattleTime, vs...)) } // LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. -func LastBattleTimeNotIn(vs ...int) predicate.Account { +func LastBattleTimeNotIn(vs ...int64) predicate.Account { return predicate.Account(sql.FieldNotIn(FieldLastBattleTime, vs...)) } // LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. -func LastBattleTimeGT(v int) predicate.Account { +func LastBattleTimeGT(v int64) predicate.Account { return predicate.Account(sql.FieldGT(FieldLastBattleTime, v)) } // LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. -func LastBattleTimeGTE(v int) predicate.Account { +func LastBattleTimeGTE(v int64) predicate.Account { return predicate.Account(sql.FieldGTE(FieldLastBattleTime, v)) } // LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. -func LastBattleTimeLT(v int) predicate.Account { +func LastBattleTimeLT(v int64) predicate.Account { return predicate.Account(sql.FieldLT(FieldLastBattleTime, v)) } // LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. -func LastBattleTimeLTE(v int) predicate.Account { +func LastBattleTimeLTE(v int64) predicate.Account { return predicate.Account(sql.FieldLTE(FieldLastBattleTime, v)) } // AccountCreatedAtEQ applies the EQ predicate on the "account_created_at" field. -func AccountCreatedAtEQ(v int) predicate.Account { +func AccountCreatedAtEQ(v int64) predicate.Account { return predicate.Account(sql.FieldEQ(FieldAccountCreatedAt, v)) } // AccountCreatedAtNEQ applies the NEQ predicate on the "account_created_at" field. -func AccountCreatedAtNEQ(v int) predicate.Account { +func AccountCreatedAtNEQ(v int64) predicate.Account { return predicate.Account(sql.FieldNEQ(FieldAccountCreatedAt, v)) } // AccountCreatedAtIn applies the In predicate on the "account_created_at" field. -func AccountCreatedAtIn(vs ...int) predicate.Account { +func AccountCreatedAtIn(vs ...int64) predicate.Account { return predicate.Account(sql.FieldIn(FieldAccountCreatedAt, vs...)) } // AccountCreatedAtNotIn applies the NotIn predicate on the "account_created_at" field. -func AccountCreatedAtNotIn(vs ...int) predicate.Account { +func AccountCreatedAtNotIn(vs ...int64) predicate.Account { return predicate.Account(sql.FieldNotIn(FieldAccountCreatedAt, vs...)) } // AccountCreatedAtGT applies the GT predicate on the "account_created_at" field. -func AccountCreatedAtGT(v int) predicate.Account { +func AccountCreatedAtGT(v int64) predicate.Account { return predicate.Account(sql.FieldGT(FieldAccountCreatedAt, v)) } // AccountCreatedAtGTE applies the GTE predicate on the "account_created_at" field. -func AccountCreatedAtGTE(v int) predicate.Account { +func AccountCreatedAtGTE(v int64) predicate.Account { return predicate.Account(sql.FieldGTE(FieldAccountCreatedAt, v)) } // AccountCreatedAtLT applies the LT predicate on the "account_created_at" field. -func AccountCreatedAtLT(v int) predicate.Account { +func AccountCreatedAtLT(v int64) predicate.Account { return predicate.Account(sql.FieldLT(FieldAccountCreatedAt, v)) } // AccountCreatedAtLTE applies the LTE predicate on the "account_created_at" field. -func AccountCreatedAtLTE(v int) predicate.Account { +func AccountCreatedAtLTE(v int64) predicate.Account { return predicate.Account(sql.FieldLTE(FieldAccountCreatedAt, v)) } diff --git a/internal/database/ent/db/account_create.go b/internal/database/ent/db/account_create.go index 4afd1e09..7afba00e 100644 --- a/internal/database/ent/db/account_create.go +++ b/internal/database/ent/db/account_create.go @@ -24,13 +24,13 @@ type AccountCreate struct { } // SetCreatedAt sets the "created_at" field. -func (ac *AccountCreate) SetCreatedAt(i int) *AccountCreate { +func (ac *AccountCreate) SetCreatedAt(i int64) *AccountCreate { ac.mutation.SetCreatedAt(i) return ac } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (ac *AccountCreate) SetNillableCreatedAt(i *int) *AccountCreate { +func (ac *AccountCreate) SetNillableCreatedAt(i *int64) *AccountCreate { if i != nil { ac.SetCreatedAt(*i) } @@ -38,13 +38,13 @@ func (ac *AccountCreate) SetNillableCreatedAt(i *int) *AccountCreate { } // SetUpdatedAt sets the "updated_at" field. -func (ac *AccountCreate) SetUpdatedAt(i int) *AccountCreate { +func (ac *AccountCreate) SetUpdatedAt(i int64) *AccountCreate { ac.mutation.SetUpdatedAt(i) return ac } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (ac *AccountCreate) SetNillableUpdatedAt(i *int) *AccountCreate { +func (ac *AccountCreate) SetNillableUpdatedAt(i *int64) *AccountCreate { if i != nil { ac.SetUpdatedAt(*i) } @@ -52,13 +52,13 @@ func (ac *AccountCreate) SetNillableUpdatedAt(i *int) *AccountCreate { } // SetLastBattleTime sets the "last_battle_time" field. -func (ac *AccountCreate) SetLastBattleTime(i int) *AccountCreate { +func (ac *AccountCreate) SetLastBattleTime(i int64) *AccountCreate { ac.mutation.SetLastBattleTime(i) return ac } // SetAccountCreatedAt sets the "account_created_at" field. -func (ac *AccountCreate) SetAccountCreatedAt(i int) *AccountCreate { +func (ac *AccountCreate) SetAccountCreatedAt(i int64) *AccountCreate { ac.mutation.SetAccountCreatedAt(i) return ac } @@ -277,19 +277,19 @@ func (ac *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := ac.mutation.CreatedAt(); ok { - _spec.SetField(account.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(account.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := ac.mutation.UpdatedAt(); ok { - _spec.SetField(account.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(account.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := ac.mutation.LastBattleTime(); ok { - _spec.SetField(account.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(account.FieldLastBattleTime, field.TypeInt64, value) _node.LastBattleTime = value } if value, ok := ac.mutation.AccountCreatedAt(); ok { - _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt, value) + _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt64, value) _node.AccountCreatedAt = value } if value, ok := ac.mutation.Realm(); ok { diff --git a/internal/database/ent/db/account_query.go b/internal/database/ent/db/account_query.go index 9dce0052..bd81a273 100644 --- a/internal/database/ent/db/account_query.go +++ b/internal/database/ent/db/account_query.go @@ -406,7 +406,7 @@ func (aq *AccountQuery) WithAchievementSnapshots(opts ...func(*AchievementsSnaps // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -429,7 +429,7 @@ func (aq *AccountQuery) GroupBy(field string, fields ...string) *AccountGroupBy // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.Account.Query(). diff --git a/internal/database/ent/db/account_update.go b/internal/database/ent/db/account_update.go index c58f930b..7a80ee46 100644 --- a/internal/database/ent/db/account_update.go +++ b/internal/database/ent/db/account_update.go @@ -32,27 +32,27 @@ func (au *AccountUpdate) Where(ps ...predicate.Account) *AccountUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (au *AccountUpdate) SetUpdatedAt(i int) *AccountUpdate { +func (au *AccountUpdate) SetUpdatedAt(i int64) *AccountUpdate { au.mutation.ResetUpdatedAt() au.mutation.SetUpdatedAt(i) return au } // AddUpdatedAt adds i to the "updated_at" field. -func (au *AccountUpdate) AddUpdatedAt(i int) *AccountUpdate { +func (au *AccountUpdate) AddUpdatedAt(i int64) *AccountUpdate { au.mutation.AddUpdatedAt(i) return au } // SetLastBattleTime sets the "last_battle_time" field. -func (au *AccountUpdate) SetLastBattleTime(i int) *AccountUpdate { +func (au *AccountUpdate) SetLastBattleTime(i int64) *AccountUpdate { au.mutation.ResetLastBattleTime() au.mutation.SetLastBattleTime(i) return au } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (au *AccountUpdate) SetNillableLastBattleTime(i *int) *AccountUpdate { +func (au *AccountUpdate) SetNillableLastBattleTime(i *int64) *AccountUpdate { if i != nil { au.SetLastBattleTime(*i) } @@ -60,20 +60,20 @@ func (au *AccountUpdate) SetNillableLastBattleTime(i *int) *AccountUpdate { } // AddLastBattleTime adds i to the "last_battle_time" field. -func (au *AccountUpdate) AddLastBattleTime(i int) *AccountUpdate { +func (au *AccountUpdate) AddLastBattleTime(i int64) *AccountUpdate { au.mutation.AddLastBattleTime(i) return au } // SetAccountCreatedAt sets the "account_created_at" field. -func (au *AccountUpdate) SetAccountCreatedAt(i int) *AccountUpdate { +func (au *AccountUpdate) SetAccountCreatedAt(i int64) *AccountUpdate { au.mutation.ResetAccountCreatedAt() au.mutation.SetAccountCreatedAt(i) return au } // SetNillableAccountCreatedAt sets the "account_created_at" field if the given value is not nil. -func (au *AccountUpdate) SetNillableAccountCreatedAt(i *int) *AccountUpdate { +func (au *AccountUpdate) SetNillableAccountCreatedAt(i *int64) *AccountUpdate { if i != nil { au.SetAccountCreatedAt(*i) } @@ -81,7 +81,7 @@ func (au *AccountUpdate) SetNillableAccountCreatedAt(i *int) *AccountUpdate { } // AddAccountCreatedAt adds i to the "account_created_at" field. -func (au *AccountUpdate) AddAccountCreatedAt(i int) *AccountUpdate { +func (au *AccountUpdate) AddAccountCreatedAt(i int64) *AccountUpdate { au.mutation.AddAccountCreatedAt(i) return au } @@ -336,22 +336,22 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := au.mutation.UpdatedAt(); ok { - _spec.SetField(account.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(account.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := au.mutation.AddedUpdatedAt(); ok { - _spec.AddField(account.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(account.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := au.mutation.LastBattleTime(); ok { - _spec.SetField(account.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(account.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := au.mutation.AddedLastBattleTime(); ok { - _spec.AddField(account.FieldLastBattleTime, field.TypeInt, value) + _spec.AddField(account.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := au.mutation.AccountCreatedAt(); ok { - _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt, value) + _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt64, value) } if value, ok := au.mutation.AddedAccountCreatedAt(); ok { - _spec.AddField(account.FieldAccountCreatedAt, field.TypeInt, value) + _spec.AddField(account.FieldAccountCreatedAt, field.TypeInt64, value) } if value, ok := au.mutation.Realm(); ok { _spec.SetField(account.FieldRealm, field.TypeString, value) @@ -547,27 +547,27 @@ type AccountUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (auo *AccountUpdateOne) SetUpdatedAt(i int) *AccountUpdateOne { +func (auo *AccountUpdateOne) SetUpdatedAt(i int64) *AccountUpdateOne { auo.mutation.ResetUpdatedAt() auo.mutation.SetUpdatedAt(i) return auo } // AddUpdatedAt adds i to the "updated_at" field. -func (auo *AccountUpdateOne) AddUpdatedAt(i int) *AccountUpdateOne { +func (auo *AccountUpdateOne) AddUpdatedAt(i int64) *AccountUpdateOne { auo.mutation.AddUpdatedAt(i) return auo } // SetLastBattleTime sets the "last_battle_time" field. -func (auo *AccountUpdateOne) SetLastBattleTime(i int) *AccountUpdateOne { +func (auo *AccountUpdateOne) SetLastBattleTime(i int64) *AccountUpdateOne { auo.mutation.ResetLastBattleTime() auo.mutation.SetLastBattleTime(i) return auo } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (auo *AccountUpdateOne) SetNillableLastBattleTime(i *int) *AccountUpdateOne { +func (auo *AccountUpdateOne) SetNillableLastBattleTime(i *int64) *AccountUpdateOne { if i != nil { auo.SetLastBattleTime(*i) } @@ -575,20 +575,20 @@ func (auo *AccountUpdateOne) SetNillableLastBattleTime(i *int) *AccountUpdateOne } // AddLastBattleTime adds i to the "last_battle_time" field. -func (auo *AccountUpdateOne) AddLastBattleTime(i int) *AccountUpdateOne { +func (auo *AccountUpdateOne) AddLastBattleTime(i int64) *AccountUpdateOne { auo.mutation.AddLastBattleTime(i) return auo } // SetAccountCreatedAt sets the "account_created_at" field. -func (auo *AccountUpdateOne) SetAccountCreatedAt(i int) *AccountUpdateOne { +func (auo *AccountUpdateOne) SetAccountCreatedAt(i int64) *AccountUpdateOne { auo.mutation.ResetAccountCreatedAt() auo.mutation.SetAccountCreatedAt(i) return auo } // SetNillableAccountCreatedAt sets the "account_created_at" field if the given value is not nil. -func (auo *AccountUpdateOne) SetNillableAccountCreatedAt(i *int) *AccountUpdateOne { +func (auo *AccountUpdateOne) SetNillableAccountCreatedAt(i *int64) *AccountUpdateOne { if i != nil { auo.SetAccountCreatedAt(*i) } @@ -596,7 +596,7 @@ func (auo *AccountUpdateOne) SetNillableAccountCreatedAt(i *int) *AccountUpdateO } // AddAccountCreatedAt adds i to the "account_created_at" field. -func (auo *AccountUpdateOne) AddAccountCreatedAt(i int) *AccountUpdateOne { +func (auo *AccountUpdateOne) AddAccountCreatedAt(i int64) *AccountUpdateOne { auo.mutation.AddAccountCreatedAt(i) return auo } @@ -881,22 +881,22 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } } if value, ok := auo.mutation.UpdatedAt(); ok { - _spec.SetField(account.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(account.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := auo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(account.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(account.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := auo.mutation.LastBattleTime(); ok { - _spec.SetField(account.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(account.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := auo.mutation.AddedLastBattleTime(); ok { - _spec.AddField(account.FieldLastBattleTime, field.TypeInt, value) + _spec.AddField(account.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := auo.mutation.AccountCreatedAt(); ok { - _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt, value) + _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt64, value) } if value, ok := auo.mutation.AddedAccountCreatedAt(); ok { - _spec.AddField(account.FieldAccountCreatedAt, field.TypeInt, value) + _spec.AddField(account.FieldAccountCreatedAt, field.TypeInt64, value) } if value, ok := auo.mutation.Realm(); ok { _spec.SetField(account.FieldRealm, field.TypeString, value) diff --git a/internal/database/ent/db/accountsnapshot.go b/internal/database/ent/db/accountsnapshot.go index d0dd7952..18d8c5f7 100644 --- a/internal/database/ent/db/accountsnapshot.go +++ b/internal/database/ent/db/accountsnapshot.go @@ -21,13 +21,13 @@ type AccountSnapshot struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.SnapshotType `json:"type,omitempty"` // LastBattleTime holds the value of the "last_battle_time" field. - LastBattleTime int `json:"last_battle_time,omitempty"` + LastBattleTime int64 `json:"last_battle_time,omitempty"` // AccountID holds the value of the "account_id" field. AccountID string `json:"account_id,omitempty"` // ReferenceID holds the value of the "reference_id" field. @@ -102,13 +102,13 @@ func (as *AccountSnapshot) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - as.CreatedAt = int(value.Int64) + as.CreatedAt = value.Int64 } case accountsnapshot.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - as.UpdatedAt = int(value.Int64) + as.UpdatedAt = value.Int64 } case accountsnapshot.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -120,7 +120,7 @@ func (as *AccountSnapshot) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) } else if value.Valid { - as.LastBattleTime = int(value.Int64) + as.LastBattleTime = value.Int64 } case accountsnapshot.FieldAccountID: if value, ok := values[i].(*sql.NullString); !ok { diff --git a/internal/database/ent/db/accountsnapshot/accountsnapshot.go b/internal/database/ent/db/accountsnapshot/accountsnapshot.go index d99d867d..1a60c4b6 100644 --- a/internal/database/ent/db/accountsnapshot/accountsnapshot.go +++ b/internal/database/ent/db/accountsnapshot/accountsnapshot.go @@ -75,11 +75,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. AccountIDValidator func(string) error // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. diff --git a/internal/database/ent/db/accountsnapshot/where.go b/internal/database/ent/db/accountsnapshot/where.go index 16101ea9..6f49d0e8 100644 --- a/internal/database/ent/db/accountsnapshot/where.go +++ b/internal/database/ent/db/accountsnapshot/where.go @@ -65,17 +65,17 @@ func IDContainsFold(id string) predicate.AccountSnapshot { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.AccountSnapshot { +func CreatedAt(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.AccountSnapshot { +func UpdatedAt(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } // LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. -func LastBattleTime(v int) predicate.AccountSnapshot { +func LastBattleTime(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } @@ -100,82 +100,82 @@ func RegularBattles(v int) predicate.AccountSnapshot { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.AccountSnapshot { +func CreatedAtEQ(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.AccountSnapshot { +func CreatedAtNEQ(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.AccountSnapshot { +func CreatedAtIn(vs ...int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.AccountSnapshot { +func CreatedAtNotIn(vs ...int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.AccountSnapshot { +func CreatedAtGT(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.AccountSnapshot { +func CreatedAtGTE(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.AccountSnapshot { +func CreatedAtLT(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.AccountSnapshot { +func CreatedAtLTE(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.AccountSnapshot { +func UpdatedAtEQ(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.AccountSnapshot { +func UpdatedAtNEQ(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.AccountSnapshot { +func UpdatedAtIn(vs ...int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.AccountSnapshot { +func UpdatedAtNotIn(vs ...int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.AccountSnapshot { +func UpdatedAtGT(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.AccountSnapshot { +func UpdatedAtGTE(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.AccountSnapshot { +func UpdatedAtLT(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.AccountSnapshot { +func UpdatedAtLTE(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLTE(FieldUpdatedAt, v)) } @@ -210,42 +210,42 @@ func TypeNotIn(vs ...models.SnapshotType) predicate.AccountSnapshot { } // LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. -func LastBattleTimeEQ(v int) predicate.AccountSnapshot { +func LastBattleTimeEQ(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. -func LastBattleTimeNEQ(v int) predicate.AccountSnapshot { +func LastBattleTimeNEQ(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNEQ(FieldLastBattleTime, v)) } // LastBattleTimeIn applies the In predicate on the "last_battle_time" field. -func LastBattleTimeIn(vs ...int) predicate.AccountSnapshot { +func LastBattleTimeIn(vs ...int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldIn(FieldLastBattleTime, vs...)) } // LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. -func LastBattleTimeNotIn(vs ...int) predicate.AccountSnapshot { +func LastBattleTimeNotIn(vs ...int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNotIn(FieldLastBattleTime, vs...)) } // LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. -func LastBattleTimeGT(v int) predicate.AccountSnapshot { +func LastBattleTimeGT(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGT(FieldLastBattleTime, v)) } // LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. -func LastBattleTimeGTE(v int) predicate.AccountSnapshot { +func LastBattleTimeGTE(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGTE(FieldLastBattleTime, v)) } // LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. -func LastBattleTimeLT(v int) predicate.AccountSnapshot { +func LastBattleTimeLT(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLT(FieldLastBattleTime, v)) } // LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. -func LastBattleTimeLTE(v int) predicate.AccountSnapshot { +func LastBattleTimeLTE(v int64) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLTE(FieldLastBattleTime, v)) } diff --git a/internal/database/ent/db/accountsnapshot_create.go b/internal/database/ent/db/accountsnapshot_create.go index c4059901..9cc5742c 100644 --- a/internal/database/ent/db/accountsnapshot_create.go +++ b/internal/database/ent/db/accountsnapshot_create.go @@ -23,13 +23,13 @@ type AccountSnapshotCreate struct { } // SetCreatedAt sets the "created_at" field. -func (asc *AccountSnapshotCreate) SetCreatedAt(i int) *AccountSnapshotCreate { +func (asc *AccountSnapshotCreate) SetCreatedAt(i int64) *AccountSnapshotCreate { asc.mutation.SetCreatedAt(i) return asc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (asc *AccountSnapshotCreate) SetNillableCreatedAt(i *int) *AccountSnapshotCreate { +func (asc *AccountSnapshotCreate) SetNillableCreatedAt(i *int64) *AccountSnapshotCreate { if i != nil { asc.SetCreatedAt(*i) } @@ -37,13 +37,13 @@ func (asc *AccountSnapshotCreate) SetNillableCreatedAt(i *int) *AccountSnapshotC } // SetUpdatedAt sets the "updated_at" field. -func (asc *AccountSnapshotCreate) SetUpdatedAt(i int) *AccountSnapshotCreate { +func (asc *AccountSnapshotCreate) SetUpdatedAt(i int64) *AccountSnapshotCreate { asc.mutation.SetUpdatedAt(i) return asc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (asc *AccountSnapshotCreate) SetNillableUpdatedAt(i *int) *AccountSnapshotCreate { +func (asc *AccountSnapshotCreate) SetNillableUpdatedAt(i *int64) *AccountSnapshotCreate { if i != nil { asc.SetUpdatedAt(*i) } @@ -57,7 +57,7 @@ func (asc *AccountSnapshotCreate) SetType(mt models.SnapshotType) *AccountSnapsh } // SetLastBattleTime sets the "last_battle_time" field. -func (asc *AccountSnapshotCreate) SetLastBattleTime(i int) *AccountSnapshotCreate { +func (asc *AccountSnapshotCreate) SetLastBattleTime(i int64) *AccountSnapshotCreate { asc.mutation.SetLastBattleTime(i) return asc } @@ -252,11 +252,11 @@ func (asc *AccountSnapshotCreate) createSpec() (*AccountSnapshot, *sqlgraph.Crea _spec.ID.Value = id } if value, ok := asc.mutation.CreatedAt(); ok { - _spec.SetField(accountsnapshot.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(accountsnapshot.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := asc.mutation.UpdatedAt(); ok { - _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := asc.mutation.GetType(); ok { @@ -264,7 +264,7 @@ func (asc *AccountSnapshotCreate) createSpec() (*AccountSnapshot, *sqlgraph.Crea _node.Type = value } if value, ok := asc.mutation.LastBattleTime(); ok { - _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) _node.LastBattleTime = value } if value, ok := asc.mutation.ReferenceID(); ok { diff --git a/internal/database/ent/db/accountsnapshot_query.go b/internal/database/ent/db/accountsnapshot_query.go index af3bf5f9..33acaaf3 100644 --- a/internal/database/ent/db/accountsnapshot_query.go +++ b/internal/database/ent/db/accountsnapshot_query.go @@ -297,7 +297,7 @@ func (asq *AccountSnapshotQuery) WithAccount(opts ...func(*AccountQuery)) *Accou // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +320,7 @@ func (asq *AccountSnapshotQuery) GroupBy(field string, fields ...string) *Accoun // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.AccountSnapshot.Query(). diff --git a/internal/database/ent/db/accountsnapshot_update.go b/internal/database/ent/db/accountsnapshot_update.go index 0d1dcb15..407e16bf 100644 --- a/internal/database/ent/db/accountsnapshot_update.go +++ b/internal/database/ent/db/accountsnapshot_update.go @@ -30,14 +30,14 @@ func (asu *AccountSnapshotUpdate) Where(ps ...predicate.AccountSnapshot) *Accoun } // SetUpdatedAt sets the "updated_at" field. -func (asu *AccountSnapshotUpdate) SetUpdatedAt(i int) *AccountSnapshotUpdate { +func (asu *AccountSnapshotUpdate) SetUpdatedAt(i int64) *AccountSnapshotUpdate { asu.mutation.ResetUpdatedAt() asu.mutation.SetUpdatedAt(i) return asu } // AddUpdatedAt adds i to the "updated_at" field. -func (asu *AccountSnapshotUpdate) AddUpdatedAt(i int) *AccountSnapshotUpdate { +func (asu *AccountSnapshotUpdate) AddUpdatedAt(i int64) *AccountSnapshotUpdate { asu.mutation.AddUpdatedAt(i) return asu } @@ -57,14 +57,14 @@ func (asu *AccountSnapshotUpdate) SetNillableType(mt *models.SnapshotType) *Acco } // SetLastBattleTime sets the "last_battle_time" field. -func (asu *AccountSnapshotUpdate) SetLastBattleTime(i int) *AccountSnapshotUpdate { +func (asu *AccountSnapshotUpdate) SetLastBattleTime(i int64) *AccountSnapshotUpdate { asu.mutation.ResetLastBattleTime() asu.mutation.SetLastBattleTime(i) return asu } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (asu *AccountSnapshotUpdate) SetNillableLastBattleTime(i *int) *AccountSnapshotUpdate { +func (asu *AccountSnapshotUpdate) SetNillableLastBattleTime(i *int64) *AccountSnapshotUpdate { if i != nil { asu.SetLastBattleTime(*i) } @@ -72,7 +72,7 @@ func (asu *AccountSnapshotUpdate) SetNillableLastBattleTime(i *int) *AccountSnap } // AddLastBattleTime adds i to the "last_battle_time" field. -func (asu *AccountSnapshotUpdate) AddLastBattleTime(i int) *AccountSnapshotUpdate { +func (asu *AccountSnapshotUpdate) AddLastBattleTime(i int64) *AccountSnapshotUpdate { asu.mutation.AddLastBattleTime(i) return asu } @@ -233,19 +233,19 @@ func (asu *AccountSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error } } if value, ok := asu.mutation.UpdatedAt(); ok { - _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := asu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := asu.mutation.GetType(); ok { _spec.SetField(accountsnapshot.FieldType, field.TypeEnum, value) } if value, ok := asu.mutation.LastBattleTime(); ok { - _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := asu.mutation.AddedLastBattleTime(); ok { - _spec.AddField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.AddField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := asu.mutation.ReferenceID(); ok { _spec.SetField(accountsnapshot.FieldReferenceID, field.TypeString, value) @@ -289,14 +289,14 @@ type AccountSnapshotUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (asuo *AccountSnapshotUpdateOne) SetUpdatedAt(i int) *AccountSnapshotUpdateOne { +func (asuo *AccountSnapshotUpdateOne) SetUpdatedAt(i int64) *AccountSnapshotUpdateOne { asuo.mutation.ResetUpdatedAt() asuo.mutation.SetUpdatedAt(i) return asuo } // AddUpdatedAt adds i to the "updated_at" field. -func (asuo *AccountSnapshotUpdateOne) AddUpdatedAt(i int) *AccountSnapshotUpdateOne { +func (asuo *AccountSnapshotUpdateOne) AddUpdatedAt(i int64) *AccountSnapshotUpdateOne { asuo.mutation.AddUpdatedAt(i) return asuo } @@ -316,14 +316,14 @@ func (asuo *AccountSnapshotUpdateOne) SetNillableType(mt *models.SnapshotType) * } // SetLastBattleTime sets the "last_battle_time" field. -func (asuo *AccountSnapshotUpdateOne) SetLastBattleTime(i int) *AccountSnapshotUpdateOne { +func (asuo *AccountSnapshotUpdateOne) SetLastBattleTime(i int64) *AccountSnapshotUpdateOne { asuo.mutation.ResetLastBattleTime() asuo.mutation.SetLastBattleTime(i) return asuo } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (asuo *AccountSnapshotUpdateOne) SetNillableLastBattleTime(i *int) *AccountSnapshotUpdateOne { +func (asuo *AccountSnapshotUpdateOne) SetNillableLastBattleTime(i *int64) *AccountSnapshotUpdateOne { if i != nil { asuo.SetLastBattleTime(*i) } @@ -331,7 +331,7 @@ func (asuo *AccountSnapshotUpdateOne) SetNillableLastBattleTime(i *int) *Account } // AddLastBattleTime adds i to the "last_battle_time" field. -func (asuo *AccountSnapshotUpdateOne) AddLastBattleTime(i int) *AccountSnapshotUpdateOne { +func (asuo *AccountSnapshotUpdateOne) AddLastBattleTime(i int64) *AccountSnapshotUpdateOne { asuo.mutation.AddLastBattleTime(i) return asuo } @@ -522,19 +522,19 @@ func (asuo *AccountSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *Accou } } if value, ok := asuo.mutation.UpdatedAt(); ok { - _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := asuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(accountsnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := asuo.mutation.GetType(); ok { _spec.SetField(accountsnapshot.FieldType, field.TypeEnum, value) } if value, ok := asuo.mutation.LastBattleTime(); ok { - _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := asuo.mutation.AddedLastBattleTime(); ok { - _spec.AddField(accountsnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.AddField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := asuo.mutation.ReferenceID(); ok { _spec.SetField(accountsnapshot.FieldReferenceID, field.TypeString, value) diff --git a/internal/database/ent/db/achievementssnapshot.go b/internal/database/ent/db/achievementssnapshot.go index b26ece54..6a16a84d 100644 --- a/internal/database/ent/db/achievementssnapshot.go +++ b/internal/database/ent/db/achievementssnapshot.go @@ -21,9 +21,9 @@ type AchievementsSnapshot struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.SnapshotType `json:"type,omitempty"` // AccountID holds the value of the "account_id" field. @@ -33,7 +33,7 @@ type AchievementsSnapshot struct { // Battles holds the value of the "battles" field. Battles int `json:"battles,omitempty"` // LastBattleTime holds the value of the "last_battle_time" field. - LastBattleTime int `json:"last_battle_time,omitempty"` + LastBattleTime int64 `json:"last_battle_time,omitempty"` // Data holds the value of the "data" field. Data types.AchievementsFrame `json:"data,omitempty"` // Edges holds the relations/edges for other nodes in the graph. @@ -98,13 +98,13 @@ func (as *AchievementsSnapshot) assignValues(columns []string, values []any) err if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - as.CreatedAt = int(value.Int64) + as.CreatedAt = value.Int64 } case achievementssnapshot.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - as.UpdatedAt = int(value.Int64) + as.UpdatedAt = value.Int64 } case achievementssnapshot.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -134,7 +134,7 @@ func (as *AchievementsSnapshot) assignValues(columns []string, values []any) err if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) } else if value.Valid { - as.LastBattleTime = int(value.Int64) + as.LastBattleTime = value.Int64 } case achievementssnapshot.FieldData: if value, ok := values[i].(*[]byte); !ok { diff --git a/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go b/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go index e6c64d4f..959b9e38 100644 --- a/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go +++ b/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go @@ -69,11 +69,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. AccountIDValidator func(string) error // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. diff --git a/internal/database/ent/db/achievementssnapshot/where.go b/internal/database/ent/db/achievementssnapshot/where.go index d7b2035f..5d0bea81 100644 --- a/internal/database/ent/db/achievementssnapshot/where.go +++ b/internal/database/ent/db/achievementssnapshot/where.go @@ -65,12 +65,12 @@ func IDContainsFold(id string) predicate.AchievementsSnapshot { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.AchievementsSnapshot { +func CreatedAt(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.AchievementsSnapshot { +func UpdatedAt(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -90,87 +90,87 @@ func Battles(v int) predicate.AchievementsSnapshot { } // LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. -func LastBattleTime(v int) predicate.AchievementsSnapshot { +func LastBattleTime(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.AchievementsSnapshot { +func CreatedAtEQ(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.AchievementsSnapshot { +func CreatedAtNEQ(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.AchievementsSnapshot { +func CreatedAtIn(vs ...int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.AchievementsSnapshot { +func CreatedAtNotIn(vs ...int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.AchievementsSnapshot { +func CreatedAtGT(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.AchievementsSnapshot { +func CreatedAtGTE(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.AchievementsSnapshot { +func CreatedAtLT(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.AchievementsSnapshot { +func CreatedAtLTE(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.AchievementsSnapshot { +func UpdatedAtEQ(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.AchievementsSnapshot { +func UpdatedAtNEQ(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.AchievementsSnapshot { +func UpdatedAtIn(vs ...int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.AchievementsSnapshot { +func UpdatedAtNotIn(vs ...int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.AchievementsSnapshot { +func UpdatedAtGT(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.AchievementsSnapshot { +func UpdatedAtGTE(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.AchievementsSnapshot { +func UpdatedAtLT(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.AchievementsSnapshot { +func UpdatedAtLTE(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLTE(FieldUpdatedAt, v)) } @@ -375,42 +375,42 @@ func BattlesLTE(v int) predicate.AchievementsSnapshot { } // LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. -func LastBattleTimeEQ(v int) predicate.AchievementsSnapshot { +func LastBattleTimeEQ(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. -func LastBattleTimeNEQ(v int) predicate.AchievementsSnapshot { +func LastBattleTimeNEQ(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldLastBattleTime, v)) } // LastBattleTimeIn applies the In predicate on the "last_battle_time" field. -func LastBattleTimeIn(vs ...int) predicate.AchievementsSnapshot { +func LastBattleTimeIn(vs ...int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldIn(FieldLastBattleTime, vs...)) } // LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. -func LastBattleTimeNotIn(vs ...int) predicate.AchievementsSnapshot { +func LastBattleTimeNotIn(vs ...int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldLastBattleTime, vs...)) } // LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. -func LastBattleTimeGT(v int) predicate.AchievementsSnapshot { +func LastBattleTimeGT(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGT(FieldLastBattleTime, v)) } // LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. -func LastBattleTimeGTE(v int) predicate.AchievementsSnapshot { +func LastBattleTimeGTE(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGTE(FieldLastBattleTime, v)) } // LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. -func LastBattleTimeLT(v int) predicate.AchievementsSnapshot { +func LastBattleTimeLT(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLT(FieldLastBattleTime, v)) } // LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. -func LastBattleTimeLTE(v int) predicate.AchievementsSnapshot { +func LastBattleTimeLTE(v int64) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLTE(FieldLastBattleTime, v)) } diff --git a/internal/database/ent/db/achievementssnapshot_create.go b/internal/database/ent/db/achievementssnapshot_create.go index 26b95982..7e18e271 100644 --- a/internal/database/ent/db/achievementssnapshot_create.go +++ b/internal/database/ent/db/achievementssnapshot_create.go @@ -23,13 +23,13 @@ type AchievementsSnapshotCreate struct { } // SetCreatedAt sets the "created_at" field. -func (asc *AchievementsSnapshotCreate) SetCreatedAt(i int) *AchievementsSnapshotCreate { +func (asc *AchievementsSnapshotCreate) SetCreatedAt(i int64) *AchievementsSnapshotCreate { asc.mutation.SetCreatedAt(i) return asc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (asc *AchievementsSnapshotCreate) SetNillableCreatedAt(i *int) *AchievementsSnapshotCreate { +func (asc *AchievementsSnapshotCreate) SetNillableCreatedAt(i *int64) *AchievementsSnapshotCreate { if i != nil { asc.SetCreatedAt(*i) } @@ -37,13 +37,13 @@ func (asc *AchievementsSnapshotCreate) SetNillableCreatedAt(i *int) *Achievement } // SetUpdatedAt sets the "updated_at" field. -func (asc *AchievementsSnapshotCreate) SetUpdatedAt(i int) *AchievementsSnapshotCreate { +func (asc *AchievementsSnapshotCreate) SetUpdatedAt(i int64) *AchievementsSnapshotCreate { asc.mutation.SetUpdatedAt(i) return asc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (asc *AchievementsSnapshotCreate) SetNillableUpdatedAt(i *int) *AchievementsSnapshotCreate { +func (asc *AchievementsSnapshotCreate) SetNillableUpdatedAt(i *int64) *AchievementsSnapshotCreate { if i != nil { asc.SetUpdatedAt(*i) } @@ -75,7 +75,7 @@ func (asc *AchievementsSnapshotCreate) SetBattles(i int) *AchievementsSnapshotCr } // SetLastBattleTime sets the "last_battle_time" field. -func (asc *AchievementsSnapshotCreate) SetLastBattleTime(i int) *AchievementsSnapshotCreate { +func (asc *AchievementsSnapshotCreate) SetLastBattleTime(i int64) *AchievementsSnapshotCreate { asc.mutation.SetLastBattleTime(i) return asc } @@ -234,11 +234,11 @@ func (asc *AchievementsSnapshotCreate) createSpec() (*AchievementsSnapshot, *sql _spec.ID.Value = id } if value, ok := asc.mutation.CreatedAt(); ok { - _spec.SetField(achievementssnapshot.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(achievementssnapshot.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := asc.mutation.UpdatedAt(); ok { - _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := asc.mutation.GetType(); ok { @@ -254,7 +254,7 @@ func (asc *AchievementsSnapshotCreate) createSpec() (*AchievementsSnapshot, *sql _node.Battles = value } if value, ok := asc.mutation.LastBattleTime(); ok { - _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) _node.LastBattleTime = value } if value, ok := asc.mutation.Data(); ok { diff --git a/internal/database/ent/db/achievementssnapshot_query.go b/internal/database/ent/db/achievementssnapshot_query.go index fed3275c..fa60ab7f 100644 --- a/internal/database/ent/db/achievementssnapshot_query.go +++ b/internal/database/ent/db/achievementssnapshot_query.go @@ -297,7 +297,7 @@ func (asq *AchievementsSnapshotQuery) WithAccount(opts ...func(*AccountQuery)) * // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +320,7 @@ func (asq *AchievementsSnapshotQuery) GroupBy(field string, fields ...string) *A // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.AchievementsSnapshot.Query(). diff --git a/internal/database/ent/db/achievementssnapshot_update.go b/internal/database/ent/db/achievementssnapshot_update.go index 10f05608..834089c4 100644 --- a/internal/database/ent/db/achievementssnapshot_update.go +++ b/internal/database/ent/db/achievementssnapshot_update.go @@ -30,14 +30,14 @@ func (asu *AchievementsSnapshotUpdate) Where(ps ...predicate.AchievementsSnapsho } // SetUpdatedAt sets the "updated_at" field. -func (asu *AchievementsSnapshotUpdate) SetUpdatedAt(i int) *AchievementsSnapshotUpdate { +func (asu *AchievementsSnapshotUpdate) SetUpdatedAt(i int64) *AchievementsSnapshotUpdate { asu.mutation.ResetUpdatedAt() asu.mutation.SetUpdatedAt(i) return asu } // AddUpdatedAt adds i to the "updated_at" field. -func (asu *AchievementsSnapshotUpdate) AddUpdatedAt(i int) *AchievementsSnapshotUpdate { +func (asu *AchievementsSnapshotUpdate) AddUpdatedAt(i int64) *AchievementsSnapshotUpdate { asu.mutation.AddUpdatedAt(i) return asu } @@ -92,14 +92,14 @@ func (asu *AchievementsSnapshotUpdate) AddBattles(i int) *AchievementsSnapshotUp } // SetLastBattleTime sets the "last_battle_time" field. -func (asu *AchievementsSnapshotUpdate) SetLastBattleTime(i int) *AchievementsSnapshotUpdate { +func (asu *AchievementsSnapshotUpdate) SetLastBattleTime(i int64) *AchievementsSnapshotUpdate { asu.mutation.ResetLastBattleTime() asu.mutation.SetLastBattleTime(i) return asu } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (asu *AchievementsSnapshotUpdate) SetNillableLastBattleTime(i *int) *AchievementsSnapshotUpdate { +func (asu *AchievementsSnapshotUpdate) SetNillableLastBattleTime(i *int64) *AchievementsSnapshotUpdate { if i != nil { asu.SetLastBattleTime(*i) } @@ -107,7 +107,7 @@ func (asu *AchievementsSnapshotUpdate) SetNillableLastBattleTime(i *int) *Achiev } // AddLastBattleTime adds i to the "last_battle_time" field. -func (asu *AchievementsSnapshotUpdate) AddLastBattleTime(i int) *AchievementsSnapshotUpdate { +func (asu *AchievementsSnapshotUpdate) AddLastBattleTime(i int64) *AchievementsSnapshotUpdate { asu.mutation.AddLastBattleTime(i) return asu } @@ -198,10 +198,10 @@ func (asu *AchievementsSnapshotUpdate) sqlSave(ctx context.Context) (n int, err } } if value, ok := asu.mutation.UpdatedAt(); ok { - _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := asu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := asu.mutation.GetType(); ok { _spec.SetField(achievementssnapshot.FieldType, field.TypeEnum, value) @@ -216,10 +216,10 @@ func (asu *AchievementsSnapshotUpdate) sqlSave(ctx context.Context) (n int, err _spec.AddField(achievementssnapshot.FieldBattles, field.TypeInt, value) } if value, ok := asu.mutation.LastBattleTime(); ok { - _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := asu.mutation.AddedLastBattleTime(); ok { - _spec.AddField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.AddField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := asu.mutation.Data(); ok { _spec.SetField(achievementssnapshot.FieldData, field.TypeJSON, value) @@ -245,14 +245,14 @@ type AchievementsSnapshotUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (asuo *AchievementsSnapshotUpdateOne) SetUpdatedAt(i int) *AchievementsSnapshotUpdateOne { +func (asuo *AchievementsSnapshotUpdateOne) SetUpdatedAt(i int64) *AchievementsSnapshotUpdateOne { asuo.mutation.ResetUpdatedAt() asuo.mutation.SetUpdatedAt(i) return asuo } // AddUpdatedAt adds i to the "updated_at" field. -func (asuo *AchievementsSnapshotUpdateOne) AddUpdatedAt(i int) *AchievementsSnapshotUpdateOne { +func (asuo *AchievementsSnapshotUpdateOne) AddUpdatedAt(i int64) *AchievementsSnapshotUpdateOne { asuo.mutation.AddUpdatedAt(i) return asuo } @@ -307,14 +307,14 @@ func (asuo *AchievementsSnapshotUpdateOne) AddBattles(i int) *AchievementsSnapsh } // SetLastBattleTime sets the "last_battle_time" field. -func (asuo *AchievementsSnapshotUpdateOne) SetLastBattleTime(i int) *AchievementsSnapshotUpdateOne { +func (asuo *AchievementsSnapshotUpdateOne) SetLastBattleTime(i int64) *AchievementsSnapshotUpdateOne { asuo.mutation.ResetLastBattleTime() asuo.mutation.SetLastBattleTime(i) return asuo } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (asuo *AchievementsSnapshotUpdateOne) SetNillableLastBattleTime(i *int) *AchievementsSnapshotUpdateOne { +func (asuo *AchievementsSnapshotUpdateOne) SetNillableLastBattleTime(i *int64) *AchievementsSnapshotUpdateOne { if i != nil { asuo.SetLastBattleTime(*i) } @@ -322,7 +322,7 @@ func (asuo *AchievementsSnapshotUpdateOne) SetNillableLastBattleTime(i *int) *Ac } // AddLastBattleTime adds i to the "last_battle_time" field. -func (asuo *AchievementsSnapshotUpdateOne) AddLastBattleTime(i int) *AchievementsSnapshotUpdateOne { +func (asuo *AchievementsSnapshotUpdateOne) AddLastBattleTime(i int64) *AchievementsSnapshotUpdateOne { asuo.mutation.AddLastBattleTime(i) return asuo } @@ -443,10 +443,10 @@ func (asuo *AchievementsSnapshotUpdateOne) sqlSave(ctx context.Context) (_node * } } if value, ok := asuo.mutation.UpdatedAt(); ok { - _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := asuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(achievementssnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := asuo.mutation.GetType(); ok { _spec.SetField(achievementssnapshot.FieldType, field.TypeEnum, value) @@ -461,10 +461,10 @@ func (asuo *AchievementsSnapshotUpdateOne) sqlSave(ctx context.Context) (_node * _spec.AddField(achievementssnapshot.FieldBattles, field.TypeInt, value) } if value, ok := asuo.mutation.LastBattleTime(); ok { - _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := asuo.mutation.AddedLastBattleTime(); ok { - _spec.AddField(achievementssnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.AddField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := asuo.mutation.Data(); ok { _spec.SetField(achievementssnapshot.FieldData, field.TypeJSON, value) diff --git a/internal/database/ent/db/appconfiguration.go b/internal/database/ent/db/appconfiguration.go index 8dbdef16..5aaf0eb8 100644 --- a/internal/database/ent/db/appconfiguration.go +++ b/internal/database/ent/db/appconfiguration.go @@ -18,9 +18,9 @@ type AppConfiguration struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Key holds the value of the "key" field. Key string `json:"key,omitempty"` // Value holds the value of the "value" field. @@ -66,13 +66,13 @@ func (ac *AppConfiguration) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - ac.CreatedAt = int(value.Int64) + ac.CreatedAt = value.Int64 } case appconfiguration.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - ac.UpdatedAt = int(value.Int64) + ac.UpdatedAt = value.Int64 } case appconfiguration.FieldKey: if value, ok := values[i].(*sql.NullString); !ok { diff --git a/internal/database/ent/db/appconfiguration/appconfiguration.go b/internal/database/ent/db/appconfiguration/appconfiguration.go index 02f8ba48..facffdce 100644 --- a/internal/database/ent/db/appconfiguration/appconfiguration.go +++ b/internal/database/ent/db/appconfiguration/appconfiguration.go @@ -47,11 +47,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // KeyValidator is a validator for the "key" field. It is called by the builders before save. KeyValidator func(string) error // DefaultID holds the default value on creation for the "id" field. diff --git a/internal/database/ent/db/appconfiguration/where.go b/internal/database/ent/db/appconfiguration/where.go index ffb17f2d..6d63474c 100644 --- a/internal/database/ent/db/appconfiguration/where.go +++ b/internal/database/ent/db/appconfiguration/where.go @@ -63,12 +63,12 @@ func IDContainsFold(id string) predicate.AppConfiguration { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.AppConfiguration { +func CreatedAt(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.AppConfiguration { +func UpdatedAt(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -78,82 +78,82 @@ func Key(v string) predicate.AppConfiguration { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.AppConfiguration { +func CreatedAtEQ(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.AppConfiguration { +func CreatedAtNEQ(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.AppConfiguration { +func CreatedAtIn(vs ...int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.AppConfiguration { +func CreatedAtNotIn(vs ...int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.AppConfiguration { +func CreatedAtGT(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.AppConfiguration { +func CreatedAtGTE(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.AppConfiguration { +func CreatedAtLT(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.AppConfiguration { +func CreatedAtLTE(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.AppConfiguration { +func UpdatedAtEQ(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.AppConfiguration { +func UpdatedAtNEQ(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.AppConfiguration { +func UpdatedAtIn(vs ...int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.AppConfiguration { +func UpdatedAtNotIn(vs ...int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.AppConfiguration { +func UpdatedAtGT(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.AppConfiguration { +func UpdatedAtGTE(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.AppConfiguration { +func UpdatedAtLT(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.AppConfiguration { +func UpdatedAtLTE(v int64) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/appconfiguration_create.go b/internal/database/ent/db/appconfiguration_create.go index 2cf22a7b..c6a9c349 100644 --- a/internal/database/ent/db/appconfiguration_create.go +++ b/internal/database/ent/db/appconfiguration_create.go @@ -20,13 +20,13 @@ type AppConfigurationCreate struct { } // SetCreatedAt sets the "created_at" field. -func (acc *AppConfigurationCreate) SetCreatedAt(i int) *AppConfigurationCreate { +func (acc *AppConfigurationCreate) SetCreatedAt(i int64) *AppConfigurationCreate { acc.mutation.SetCreatedAt(i) return acc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (acc *AppConfigurationCreate) SetNillableCreatedAt(i *int) *AppConfigurationCreate { +func (acc *AppConfigurationCreate) SetNillableCreatedAt(i *int64) *AppConfigurationCreate { if i != nil { acc.SetCreatedAt(*i) } @@ -34,13 +34,13 @@ func (acc *AppConfigurationCreate) SetNillableCreatedAt(i *int) *AppConfiguratio } // SetUpdatedAt sets the "updated_at" field. -func (acc *AppConfigurationCreate) SetUpdatedAt(i int) *AppConfigurationCreate { +func (acc *AppConfigurationCreate) SetUpdatedAt(i int64) *AppConfigurationCreate { acc.mutation.SetUpdatedAt(i) return acc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (acc *AppConfigurationCreate) SetNillableUpdatedAt(i *int) *AppConfigurationCreate { +func (acc *AppConfigurationCreate) SetNillableUpdatedAt(i *int64) *AppConfigurationCreate { if i != nil { acc.SetUpdatedAt(*i) } @@ -183,11 +183,11 @@ func (acc *AppConfigurationCreate) createSpec() (*AppConfiguration, *sqlgraph.Cr _spec.ID.Value = id } if value, ok := acc.mutation.CreatedAt(); ok { - _spec.SetField(appconfiguration.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(appconfiguration.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := acc.mutation.UpdatedAt(); ok { - _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := acc.mutation.Key(); ok { diff --git a/internal/database/ent/db/appconfiguration_query.go b/internal/database/ent/db/appconfiguration_query.go index 549a21e4..759954bb 100644 --- a/internal/database/ent/db/appconfiguration_query.go +++ b/internal/database/ent/db/appconfiguration_query.go @@ -261,7 +261,7 @@ func (acq *AppConfigurationQuery) Clone() *AppConfigurationQuery { // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +284,7 @@ func (acq *AppConfigurationQuery) GroupBy(field string, fields ...string) *AppCo // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.AppConfiguration.Query(). diff --git a/internal/database/ent/db/appconfiguration_update.go b/internal/database/ent/db/appconfiguration_update.go index 168e52c4..12994e32 100644 --- a/internal/database/ent/db/appconfiguration_update.go +++ b/internal/database/ent/db/appconfiguration_update.go @@ -28,14 +28,14 @@ func (acu *AppConfigurationUpdate) Where(ps ...predicate.AppConfiguration) *AppC } // SetUpdatedAt sets the "updated_at" field. -func (acu *AppConfigurationUpdate) SetUpdatedAt(i int) *AppConfigurationUpdate { +func (acu *AppConfigurationUpdate) SetUpdatedAt(i int64) *AppConfigurationUpdate { acu.mutation.ResetUpdatedAt() acu.mutation.SetUpdatedAt(i) return acu } // AddUpdatedAt adds i to the "updated_at" field. -func (acu *AppConfigurationUpdate) AddUpdatedAt(i int) *AppConfigurationUpdate { +func (acu *AppConfigurationUpdate) AddUpdatedAt(i int64) *AppConfigurationUpdate { acu.mutation.AddUpdatedAt(i) return acu } @@ -136,10 +136,10 @@ func (acu *AppConfigurationUpdate) sqlSave(ctx context.Context) (n int, err erro } } if value, ok := acu.mutation.UpdatedAt(); ok { - _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := acu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := acu.mutation.Key(); ok { _spec.SetField(appconfiguration.FieldKey, field.TypeString, value) @@ -174,14 +174,14 @@ type AppConfigurationUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (acuo *AppConfigurationUpdateOne) SetUpdatedAt(i int) *AppConfigurationUpdateOne { +func (acuo *AppConfigurationUpdateOne) SetUpdatedAt(i int64) *AppConfigurationUpdateOne { acuo.mutation.ResetUpdatedAt() acuo.mutation.SetUpdatedAt(i) return acuo } // AddUpdatedAt adds i to the "updated_at" field. -func (acuo *AppConfigurationUpdateOne) AddUpdatedAt(i int) *AppConfigurationUpdateOne { +func (acuo *AppConfigurationUpdateOne) AddUpdatedAt(i int64) *AppConfigurationUpdateOne { acuo.mutation.AddUpdatedAt(i) return acuo } @@ -312,10 +312,10 @@ func (acuo *AppConfigurationUpdateOne) sqlSave(ctx context.Context) (_node *AppC } } if value, ok := acuo.mutation.UpdatedAt(); ok { - _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := acuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(appconfiguration.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := acuo.mutation.Key(); ok { _spec.SetField(appconfiguration.FieldKey, field.TypeString, value) diff --git a/internal/database/ent/db/applicationcommand.go b/internal/database/ent/db/applicationcommand.go index ea3626b9..0ba69869 100644 --- a/internal/database/ent/db/applicationcommand.go +++ b/internal/database/ent/db/applicationcommand.go @@ -17,9 +17,9 @@ type ApplicationCommand struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Name holds the value of the "name" field. Name string `json:"name,omitempty"` // Version holds the value of the "version" field. @@ -63,13 +63,13 @@ func (ac *ApplicationCommand) assignValues(columns []string, values []any) error if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - ac.CreatedAt = int(value.Int64) + ac.CreatedAt = value.Int64 } case applicationcommand.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - ac.UpdatedAt = int(value.Int64) + ac.UpdatedAt = value.Int64 } case applicationcommand.FieldName: if value, ok := values[i].(*sql.NullString); !ok { diff --git a/internal/database/ent/db/applicationcommand/applicationcommand.go b/internal/database/ent/db/applicationcommand/applicationcommand.go index 6860891e..4e67599d 100644 --- a/internal/database/ent/db/applicationcommand/applicationcommand.go +++ b/internal/database/ent/db/applicationcommand/applicationcommand.go @@ -47,11 +47,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // NameValidator is a validator for the "name" field. It is called by the builders before save. NameValidator func(string) error // VersionValidator is a validator for the "version" field. It is called by the builders before save. diff --git a/internal/database/ent/db/applicationcommand/where.go b/internal/database/ent/db/applicationcommand/where.go index d281b98f..81486987 100644 --- a/internal/database/ent/db/applicationcommand/where.go +++ b/internal/database/ent/db/applicationcommand/where.go @@ -63,12 +63,12 @@ func IDContainsFold(id string) predicate.ApplicationCommand { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.ApplicationCommand { +func CreatedAt(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.ApplicationCommand { +func UpdatedAt(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -88,82 +88,82 @@ func OptionsHash(v string) predicate.ApplicationCommand { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.ApplicationCommand { +func CreatedAtEQ(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.ApplicationCommand { +func CreatedAtNEQ(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.ApplicationCommand { +func CreatedAtIn(vs ...int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.ApplicationCommand { +func CreatedAtNotIn(vs ...int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.ApplicationCommand { +func CreatedAtGT(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.ApplicationCommand { +func CreatedAtGTE(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.ApplicationCommand { +func CreatedAtLT(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.ApplicationCommand { +func CreatedAtLTE(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.ApplicationCommand { +func UpdatedAtEQ(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.ApplicationCommand { +func UpdatedAtNEQ(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.ApplicationCommand { +func UpdatedAtIn(vs ...int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.ApplicationCommand { +func UpdatedAtNotIn(vs ...int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.ApplicationCommand { +func UpdatedAtGT(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.ApplicationCommand { +func UpdatedAtGTE(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.ApplicationCommand { +func UpdatedAtLT(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.ApplicationCommand { +func UpdatedAtLTE(v int64) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/applicationcommand_create.go b/internal/database/ent/db/applicationcommand_create.go index 14f3abb9..830a457d 100644 --- a/internal/database/ent/db/applicationcommand_create.go +++ b/internal/database/ent/db/applicationcommand_create.go @@ -20,13 +20,13 @@ type ApplicationCommandCreate struct { } // SetCreatedAt sets the "created_at" field. -func (acc *ApplicationCommandCreate) SetCreatedAt(i int) *ApplicationCommandCreate { +func (acc *ApplicationCommandCreate) SetCreatedAt(i int64) *ApplicationCommandCreate { acc.mutation.SetCreatedAt(i) return acc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (acc *ApplicationCommandCreate) SetNillableCreatedAt(i *int) *ApplicationCommandCreate { +func (acc *ApplicationCommandCreate) SetNillableCreatedAt(i *int64) *ApplicationCommandCreate { if i != nil { acc.SetCreatedAt(*i) } @@ -34,13 +34,13 @@ func (acc *ApplicationCommandCreate) SetNillableCreatedAt(i *int) *ApplicationCo } // SetUpdatedAt sets the "updated_at" field. -func (acc *ApplicationCommandCreate) SetUpdatedAt(i int) *ApplicationCommandCreate { +func (acc *ApplicationCommandCreate) SetUpdatedAt(i int64) *ApplicationCommandCreate { acc.mutation.SetUpdatedAt(i) return acc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (acc *ApplicationCommandCreate) SetNillableUpdatedAt(i *int) *ApplicationCommandCreate { +func (acc *ApplicationCommandCreate) SetNillableUpdatedAt(i *int64) *ApplicationCommandCreate { if i != nil { acc.SetUpdatedAt(*i) } @@ -196,11 +196,11 @@ func (acc *ApplicationCommandCreate) createSpec() (*ApplicationCommand, *sqlgrap _spec.ID.Value = id } if value, ok := acc.mutation.CreatedAt(); ok { - _spec.SetField(applicationcommand.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(applicationcommand.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := acc.mutation.UpdatedAt(); ok { - _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := acc.mutation.Name(); ok { diff --git a/internal/database/ent/db/applicationcommand_query.go b/internal/database/ent/db/applicationcommand_query.go index 5c45967e..5bd7757c 100644 --- a/internal/database/ent/db/applicationcommand_query.go +++ b/internal/database/ent/db/applicationcommand_query.go @@ -261,7 +261,7 @@ func (acq *ApplicationCommandQuery) Clone() *ApplicationCommandQuery { // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +284,7 @@ func (acq *ApplicationCommandQuery) GroupBy(field string, fields ...string) *App // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.ApplicationCommand.Query(). diff --git a/internal/database/ent/db/applicationcommand_update.go b/internal/database/ent/db/applicationcommand_update.go index ebdf0db2..f681b841 100644 --- a/internal/database/ent/db/applicationcommand_update.go +++ b/internal/database/ent/db/applicationcommand_update.go @@ -28,14 +28,14 @@ func (acu *ApplicationCommandUpdate) Where(ps ...predicate.ApplicationCommand) * } // SetUpdatedAt sets the "updated_at" field. -func (acu *ApplicationCommandUpdate) SetUpdatedAt(i int) *ApplicationCommandUpdate { +func (acu *ApplicationCommandUpdate) SetUpdatedAt(i int64) *ApplicationCommandUpdate { acu.mutation.ResetUpdatedAt() acu.mutation.SetUpdatedAt(i) return acu } // AddUpdatedAt adds i to the "updated_at" field. -func (acu *ApplicationCommandUpdate) AddUpdatedAt(i int) *ApplicationCommandUpdate { +func (acu *ApplicationCommandUpdate) AddUpdatedAt(i int64) *ApplicationCommandUpdate { acu.mutation.AddUpdatedAt(i) return acu } @@ -156,10 +156,10 @@ func (acu *ApplicationCommandUpdate) sqlSave(ctx context.Context) (n int, err er } } if value, ok := acu.mutation.UpdatedAt(); ok { - _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := acu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := acu.mutation.Name(); ok { _spec.SetField(applicationcommand.FieldName, field.TypeString, value) @@ -191,14 +191,14 @@ type ApplicationCommandUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (acuo *ApplicationCommandUpdateOne) SetUpdatedAt(i int) *ApplicationCommandUpdateOne { +func (acuo *ApplicationCommandUpdateOne) SetUpdatedAt(i int64) *ApplicationCommandUpdateOne { acuo.mutation.ResetUpdatedAt() acuo.mutation.SetUpdatedAt(i) return acuo } // AddUpdatedAt adds i to the "updated_at" field. -func (acuo *ApplicationCommandUpdateOne) AddUpdatedAt(i int) *ApplicationCommandUpdateOne { +func (acuo *ApplicationCommandUpdateOne) AddUpdatedAt(i int64) *ApplicationCommandUpdateOne { acuo.mutation.AddUpdatedAt(i) return acuo } @@ -349,10 +349,10 @@ func (acuo *ApplicationCommandUpdateOne) sqlSave(ctx context.Context) (_node *Ap } } if value, ok := acuo.mutation.UpdatedAt(); ok { - _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := acuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(applicationcommand.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := acuo.mutation.Name(); ok { _spec.SetField(applicationcommand.FieldName, field.TypeString, value) diff --git a/internal/database/ent/db/clan.go b/internal/database/ent/db/clan.go index 98645aef..1f883f27 100644 --- a/internal/database/ent/db/clan.go +++ b/internal/database/ent/db/clan.go @@ -18,9 +18,9 @@ type Clan struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Tag holds the value of the "tag" field. Tag string `json:"tag,omitempty"` // Name holds the value of the "name" field. @@ -89,13 +89,13 @@ func (c *Clan) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - c.CreatedAt = int(value.Int64) + c.CreatedAt = value.Int64 } case clan.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - c.UpdatedAt = int(value.Int64) + c.UpdatedAt = value.Int64 } case clan.FieldTag: if value, ok := values[i].(*sql.NullString); !ok { diff --git a/internal/database/ent/db/clan/clan.go b/internal/database/ent/db/clan/clan.go index df17d622..59a2f6e0 100644 --- a/internal/database/ent/db/clan/clan.go +++ b/internal/database/ent/db/clan/clan.go @@ -60,11 +60,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // TagValidator is a validator for the "tag" field. It is called by the builders before save. TagValidator func(string) error // NameValidator is a validator for the "name" field. It is called by the builders before save. diff --git a/internal/database/ent/db/clan/where.go b/internal/database/ent/db/clan/where.go index c860dc4d..6ab4d8fe 100644 --- a/internal/database/ent/db/clan/where.go +++ b/internal/database/ent/db/clan/where.go @@ -64,12 +64,12 @@ func IDContainsFold(id string) predicate.Clan { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.Clan { +func CreatedAt(v int64) predicate.Clan { return predicate.Clan(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.Clan { +func UpdatedAt(v int64) predicate.Clan { return predicate.Clan(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -89,82 +89,82 @@ func EmblemID(v string) predicate.Clan { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.Clan { +func CreatedAtEQ(v int64) predicate.Clan { return predicate.Clan(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.Clan { +func CreatedAtNEQ(v int64) predicate.Clan { return predicate.Clan(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.Clan { +func CreatedAtIn(vs ...int64) predicate.Clan { return predicate.Clan(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.Clan { +func CreatedAtNotIn(vs ...int64) predicate.Clan { return predicate.Clan(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.Clan { +func CreatedAtGT(v int64) predicate.Clan { return predicate.Clan(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.Clan { +func CreatedAtGTE(v int64) predicate.Clan { return predicate.Clan(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.Clan { +func CreatedAtLT(v int64) predicate.Clan { return predicate.Clan(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.Clan { +func CreatedAtLTE(v int64) predicate.Clan { return predicate.Clan(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.Clan { +func UpdatedAtEQ(v int64) predicate.Clan { return predicate.Clan(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.Clan { +func UpdatedAtNEQ(v int64) predicate.Clan { return predicate.Clan(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.Clan { +func UpdatedAtIn(vs ...int64) predicate.Clan { return predicate.Clan(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.Clan { +func UpdatedAtNotIn(vs ...int64) predicate.Clan { return predicate.Clan(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.Clan { +func UpdatedAtGT(v int64) predicate.Clan { return predicate.Clan(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.Clan { +func UpdatedAtGTE(v int64) predicate.Clan { return predicate.Clan(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.Clan { +func UpdatedAtLT(v int64) predicate.Clan { return predicate.Clan(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.Clan { +func UpdatedAtLTE(v int64) predicate.Clan { return predicate.Clan(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/clan_create.go b/internal/database/ent/db/clan_create.go index fb96c54d..438f1f4d 100644 --- a/internal/database/ent/db/clan_create.go +++ b/internal/database/ent/db/clan_create.go @@ -21,13 +21,13 @@ type ClanCreate struct { } // SetCreatedAt sets the "created_at" field. -func (cc *ClanCreate) SetCreatedAt(i int) *ClanCreate { +func (cc *ClanCreate) SetCreatedAt(i int64) *ClanCreate { cc.mutation.SetCreatedAt(i) return cc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (cc *ClanCreate) SetNillableCreatedAt(i *int) *ClanCreate { +func (cc *ClanCreate) SetNillableCreatedAt(i *int64) *ClanCreate { if i != nil { cc.SetCreatedAt(*i) } @@ -35,13 +35,13 @@ func (cc *ClanCreate) SetNillableCreatedAt(i *int) *ClanCreate { } // SetUpdatedAt sets the "updated_at" field. -func (cc *ClanCreate) SetUpdatedAt(i int) *ClanCreate { +func (cc *ClanCreate) SetUpdatedAt(i int64) *ClanCreate { cc.mutation.SetUpdatedAt(i) return cc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (cc *ClanCreate) SetNillableUpdatedAt(i *int) *ClanCreate { +func (cc *ClanCreate) SetNillableUpdatedAt(i *int64) *ClanCreate { if i != nil { cc.SetUpdatedAt(*i) } @@ -213,11 +213,11 @@ func (cc *ClanCreate) createSpec() (*Clan, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := cc.mutation.CreatedAt(); ok { - _spec.SetField(clan.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(clan.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := cc.mutation.UpdatedAt(); ok { - _spec.SetField(clan.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(clan.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := cc.mutation.Tag(); ok { diff --git a/internal/database/ent/db/clan_query.go b/internal/database/ent/db/clan_query.go index 274928c5..76649a32 100644 --- a/internal/database/ent/db/clan_query.go +++ b/internal/database/ent/db/clan_query.go @@ -298,7 +298,7 @@ func (cq *ClanQuery) WithAccounts(opts ...func(*AccountQuery)) *ClanQuery { // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -321,7 +321,7 @@ func (cq *ClanQuery) GroupBy(field string, fields ...string) *ClanGroupBy { // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.Clan.Query(). diff --git a/internal/database/ent/db/clan_update.go b/internal/database/ent/db/clan_update.go index 85b62c7a..8a5ae595 100644 --- a/internal/database/ent/db/clan_update.go +++ b/internal/database/ent/db/clan_update.go @@ -30,14 +30,14 @@ func (cu *ClanUpdate) Where(ps ...predicate.Clan) *ClanUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (cu *ClanUpdate) SetUpdatedAt(i int) *ClanUpdate { +func (cu *ClanUpdate) SetUpdatedAt(i int64) *ClanUpdate { cu.mutation.ResetUpdatedAt() cu.mutation.SetUpdatedAt(i) return cu } // AddUpdatedAt adds i to the "updated_at" field. -func (cu *ClanUpdate) AddUpdatedAt(i int) *ClanUpdate { +func (cu *ClanUpdate) AddUpdatedAt(i int64) *ClanUpdate { cu.mutation.AddUpdatedAt(i) return cu } @@ -207,10 +207,10 @@ func (cu *ClanUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := cu.mutation.UpdatedAt(); ok { - _spec.SetField(clan.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(clan.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := cu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(clan.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(clan.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := cu.mutation.Tag(); ok { _spec.SetField(clan.FieldTag, field.TypeString, value) @@ -298,14 +298,14 @@ type ClanUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (cuo *ClanUpdateOne) SetUpdatedAt(i int) *ClanUpdateOne { +func (cuo *ClanUpdateOne) SetUpdatedAt(i int64) *ClanUpdateOne { cuo.mutation.ResetUpdatedAt() cuo.mutation.SetUpdatedAt(i) return cuo } // AddUpdatedAt adds i to the "updated_at" field. -func (cuo *ClanUpdateOne) AddUpdatedAt(i int) *ClanUpdateOne { +func (cuo *ClanUpdateOne) AddUpdatedAt(i int64) *ClanUpdateOne { cuo.mutation.AddUpdatedAt(i) return cuo } @@ -505,10 +505,10 @@ func (cuo *ClanUpdateOne) sqlSave(ctx context.Context) (_node *Clan, err error) } } if value, ok := cuo.mutation.UpdatedAt(); ok { - _spec.SetField(clan.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(clan.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := cuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(clan.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(clan.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := cuo.mutation.Tag(); ok { _spec.SetField(clan.FieldTag, field.TypeString, value) diff --git a/internal/database/ent/db/crontask.go b/internal/database/ent/db/crontask.go index c7580c3c..c4ba99dd 100644 --- a/internal/database/ent/db/crontask.go +++ b/internal/database/ent/db/crontask.go @@ -19,21 +19,21 @@ type CronTask struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Type holds the value of the "type" field. - Type string `json:"type,omitempty"` + Type models.TaskType `json:"type,omitempty"` // ReferenceID holds the value of the "reference_id" field. ReferenceID string `json:"reference_id,omitempty"` // Targets holds the value of the "targets" field. Targets []string `json:"targets,omitempty"` // Status holds the value of the "status" field. - Status string `json:"status,omitempty"` + Status models.TaskStatus `json:"status,omitempty"` // ScheduledAfter holds the value of the "scheduled_after" field. - ScheduledAfter int `json:"scheduled_after,omitempty"` + ScheduledAfter int64 `json:"scheduled_after,omitempty"` // LastRun holds the value of the "last_run" field. - LastRun int `json:"last_run,omitempty"` + LastRun int64 `json:"last_run,omitempty"` // Logs holds the value of the "logs" field. Logs []models.TaskLog `json:"logs,omitempty"` // Data holds the value of the "data" field. @@ -77,19 +77,19 @@ func (ct *CronTask) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - ct.CreatedAt = int(value.Int64) + ct.CreatedAt = value.Int64 } case crontask.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - ct.UpdatedAt = int(value.Int64) + ct.UpdatedAt = value.Int64 } case crontask.FieldType: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field type", values[i]) } else if value.Valid { - ct.Type = value.String + ct.Type = models.TaskType(value.String) } case crontask.FieldReferenceID: if value, ok := values[i].(*sql.NullString); !ok { @@ -109,19 +109,19 @@ func (ct *CronTask) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field status", values[i]) } else if value.Valid { - ct.Status = value.String + ct.Status = models.TaskStatus(value.String) } case crontask.FieldScheduledAfter: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field scheduled_after", values[i]) } else if value.Valid { - ct.ScheduledAfter = int(value.Int64) + ct.ScheduledAfter = value.Int64 } case crontask.FieldLastRun: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field last_run", values[i]) } else if value.Valid { - ct.LastRun = int(value.Int64) + ct.LastRun = value.Int64 } case crontask.FieldLogs: if value, ok := values[i].(*[]byte); !ok { @@ -182,7 +182,7 @@ func (ct *CronTask) String() string { builder.WriteString(fmt.Sprintf("%v", ct.UpdatedAt)) builder.WriteString(", ") builder.WriteString("type=") - builder.WriteString(ct.Type) + builder.WriteString(fmt.Sprintf("%v", ct.Type)) builder.WriteString(", ") builder.WriteString("reference_id=") builder.WriteString(ct.ReferenceID) @@ -191,7 +191,7 @@ func (ct *CronTask) String() string { builder.WriteString(fmt.Sprintf("%v", ct.Targets)) builder.WriteString(", ") builder.WriteString("status=") - builder.WriteString(ct.Status) + builder.WriteString(fmt.Sprintf("%v", ct.Status)) builder.WriteString(", ") builder.WriteString("scheduled_after=") builder.WriteString(fmt.Sprintf("%v", ct.ScheduledAfter)) diff --git a/internal/database/ent/db/crontask/crontask.go b/internal/database/ent/db/crontask/crontask.go index eec007e1..f3ba2093 100644 --- a/internal/database/ent/db/crontask/crontask.go +++ b/internal/database/ent/db/crontask/crontask.go @@ -3,7 +3,10 @@ package crontask import ( + "fmt" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/models" ) const ( @@ -62,21 +65,37 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int - // TypeValidator is a validator for the "type" field. It is called by the builders before save. - TypeValidator func(string) error + UpdateDefaultUpdatedAt func() int64 // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. ReferenceIDValidator func(string) error - // StatusValidator is a validator for the "status" field. It is called by the builders before save. - StatusValidator func(string) error // DefaultID holds the default value on creation for the "id" field. DefaultID func() string ) +// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save. +func TypeValidator(_type models.TaskType) error { + switch _type { + case "UPDATE_CLANS", "RECORD_ACCOUNT_SESSIONS", "UPDATE_ACCOUNT_WN8", "UPDATE_ACCOUNT_ACHIEVEMENTS", "CLEANUP_DATABASE": + return nil + default: + return fmt.Errorf("crontask: invalid enum value for type field: %q", _type) + } +} + +// StatusValidator is a validator for the "status" field enum values. It is called by the builders before save. +func StatusValidator(s models.TaskStatus) error { + switch s { + case "TASK_SCHEDULED", "TASK_IN_PROGRESS", "TASK_COMPLETE", "TASK_FAILED": + return nil + default: + return fmt.Errorf("crontask: invalid enum value for status field: %q", s) + } +} + // OrderOption defines the ordering options for the CronTask queries. type OrderOption func(*sql.Selector) diff --git a/internal/database/ent/db/crontask/where.go b/internal/database/ent/db/crontask/where.go index a3da3987..3eb15146 100644 --- a/internal/database/ent/db/crontask/where.go +++ b/internal/database/ent/db/crontask/where.go @@ -5,6 +5,7 @@ package crontask import ( "entgo.io/ent/dialect/sql" "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" ) // ID filters vertices based on their ID field. @@ -63,183 +64,138 @@ func IDContainsFold(id string) predicate.CronTask { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.CronTask { +func CreatedAt(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.CronTask { +func UpdatedAt(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldUpdatedAt, v)) } -// Type applies equality check predicate on the "type" field. It's identical to TypeEQ. -func Type(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldEQ(FieldType, v)) -} - // ReferenceID applies equality check predicate on the "reference_id" field. It's identical to ReferenceIDEQ. func ReferenceID(v string) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldReferenceID, v)) } -// Status applies equality check predicate on the "status" field. It's identical to StatusEQ. -func Status(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldEQ(FieldStatus, v)) -} - // ScheduledAfter applies equality check predicate on the "scheduled_after" field. It's identical to ScheduledAfterEQ. -func ScheduledAfter(v int) predicate.CronTask { +func ScheduledAfter(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldScheduledAfter, v)) } // LastRun applies equality check predicate on the "last_run" field. It's identical to LastRunEQ. -func LastRun(v int) predicate.CronTask { +func LastRun(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldLastRun, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.CronTask { +func CreatedAtEQ(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.CronTask { +func CreatedAtNEQ(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.CronTask { +func CreatedAtIn(vs ...int64) predicate.CronTask { return predicate.CronTask(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.CronTask { +func CreatedAtNotIn(vs ...int64) predicate.CronTask { return predicate.CronTask(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.CronTask { +func CreatedAtGT(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.CronTask { +func CreatedAtGTE(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.CronTask { +func CreatedAtLT(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.CronTask { +func CreatedAtLTE(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.CronTask { +func UpdatedAtEQ(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.CronTask { +func UpdatedAtNEQ(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.CronTask { +func UpdatedAtIn(vs ...int64) predicate.CronTask { return predicate.CronTask(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.CronTask { +func UpdatedAtNotIn(vs ...int64) predicate.CronTask { return predicate.CronTask(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.CronTask { +func UpdatedAtGT(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.CronTask { +func UpdatedAtGTE(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.CronTask { +func UpdatedAtLT(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.CronTask { +func UpdatedAtLTE(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldLTE(FieldUpdatedAt, v)) } // TypeEQ applies the EQ predicate on the "type" field. -func TypeEQ(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldEQ(FieldType, v)) +func TypeEQ(v models.TaskType) predicate.CronTask { + vc := v + return predicate.CronTask(sql.FieldEQ(FieldType, vc)) } // TypeNEQ applies the NEQ predicate on the "type" field. -func TypeNEQ(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldNEQ(FieldType, v)) +func TypeNEQ(v models.TaskType) predicate.CronTask { + vc := v + return predicate.CronTask(sql.FieldNEQ(FieldType, vc)) } // TypeIn applies the In predicate on the "type" field. -func TypeIn(vs ...string) predicate.CronTask { - return predicate.CronTask(sql.FieldIn(FieldType, vs...)) +func TypeIn(vs ...models.TaskType) predicate.CronTask { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.CronTask(sql.FieldIn(FieldType, v...)) } // TypeNotIn applies the NotIn predicate on the "type" field. -func TypeNotIn(vs ...string) predicate.CronTask { - return predicate.CronTask(sql.FieldNotIn(FieldType, vs...)) -} - -// TypeGT applies the GT predicate on the "type" field. -func TypeGT(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldGT(FieldType, v)) -} - -// TypeGTE applies the GTE predicate on the "type" field. -func TypeGTE(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldGTE(FieldType, v)) -} - -// TypeLT applies the LT predicate on the "type" field. -func TypeLT(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldLT(FieldType, v)) -} - -// TypeLTE applies the LTE predicate on the "type" field. -func TypeLTE(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldLTE(FieldType, v)) -} - -// TypeContains applies the Contains predicate on the "type" field. -func TypeContains(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldContains(FieldType, v)) -} - -// TypeHasPrefix applies the HasPrefix predicate on the "type" field. -func TypeHasPrefix(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldHasPrefix(FieldType, v)) -} - -// TypeHasSuffix applies the HasSuffix predicate on the "type" field. -func TypeHasSuffix(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldHasSuffix(FieldType, v)) -} - -// TypeEqualFold applies the EqualFold predicate on the "type" field. -func TypeEqualFold(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldEqualFold(FieldType, v)) -} - -// TypeContainsFold applies the ContainsFold predicate on the "type" field. -func TypeContainsFold(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldContainsFold(FieldType, v)) +func TypeNotIn(vs ...models.TaskType) predicate.CronTask { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.CronTask(sql.FieldNotIn(FieldType, v...)) } // ReferenceIDEQ applies the EQ predicate on the "reference_id" field. @@ -308,147 +264,112 @@ func ReferenceIDContainsFold(v string) predicate.CronTask { } // StatusEQ applies the EQ predicate on the "status" field. -func StatusEQ(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldEQ(FieldStatus, v)) +func StatusEQ(v models.TaskStatus) predicate.CronTask { + vc := v + return predicate.CronTask(sql.FieldEQ(FieldStatus, vc)) } // StatusNEQ applies the NEQ predicate on the "status" field. -func StatusNEQ(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldNEQ(FieldStatus, v)) +func StatusNEQ(v models.TaskStatus) predicate.CronTask { + vc := v + return predicate.CronTask(sql.FieldNEQ(FieldStatus, vc)) } // StatusIn applies the In predicate on the "status" field. -func StatusIn(vs ...string) predicate.CronTask { - return predicate.CronTask(sql.FieldIn(FieldStatus, vs...)) +func StatusIn(vs ...models.TaskStatus) predicate.CronTask { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.CronTask(sql.FieldIn(FieldStatus, v...)) } // StatusNotIn applies the NotIn predicate on the "status" field. -func StatusNotIn(vs ...string) predicate.CronTask { - return predicate.CronTask(sql.FieldNotIn(FieldStatus, vs...)) -} - -// StatusGT applies the GT predicate on the "status" field. -func StatusGT(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldGT(FieldStatus, v)) -} - -// StatusGTE applies the GTE predicate on the "status" field. -func StatusGTE(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldGTE(FieldStatus, v)) -} - -// StatusLT applies the LT predicate on the "status" field. -func StatusLT(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldLT(FieldStatus, v)) -} - -// StatusLTE applies the LTE predicate on the "status" field. -func StatusLTE(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldLTE(FieldStatus, v)) -} - -// StatusContains applies the Contains predicate on the "status" field. -func StatusContains(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldContains(FieldStatus, v)) -} - -// StatusHasPrefix applies the HasPrefix predicate on the "status" field. -func StatusHasPrefix(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldHasPrefix(FieldStatus, v)) -} - -// StatusHasSuffix applies the HasSuffix predicate on the "status" field. -func StatusHasSuffix(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldHasSuffix(FieldStatus, v)) -} - -// StatusEqualFold applies the EqualFold predicate on the "status" field. -func StatusEqualFold(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldEqualFold(FieldStatus, v)) -} - -// StatusContainsFold applies the ContainsFold predicate on the "status" field. -func StatusContainsFold(v string) predicate.CronTask { - return predicate.CronTask(sql.FieldContainsFold(FieldStatus, v)) +func StatusNotIn(vs ...models.TaskStatus) predicate.CronTask { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.CronTask(sql.FieldNotIn(FieldStatus, v...)) } // ScheduledAfterEQ applies the EQ predicate on the "scheduled_after" field. -func ScheduledAfterEQ(v int) predicate.CronTask { +func ScheduledAfterEQ(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldScheduledAfter, v)) } // ScheduledAfterNEQ applies the NEQ predicate on the "scheduled_after" field. -func ScheduledAfterNEQ(v int) predicate.CronTask { +func ScheduledAfterNEQ(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldNEQ(FieldScheduledAfter, v)) } // ScheduledAfterIn applies the In predicate on the "scheduled_after" field. -func ScheduledAfterIn(vs ...int) predicate.CronTask { +func ScheduledAfterIn(vs ...int64) predicate.CronTask { return predicate.CronTask(sql.FieldIn(FieldScheduledAfter, vs...)) } // ScheduledAfterNotIn applies the NotIn predicate on the "scheduled_after" field. -func ScheduledAfterNotIn(vs ...int) predicate.CronTask { +func ScheduledAfterNotIn(vs ...int64) predicate.CronTask { return predicate.CronTask(sql.FieldNotIn(FieldScheduledAfter, vs...)) } // ScheduledAfterGT applies the GT predicate on the "scheduled_after" field. -func ScheduledAfterGT(v int) predicate.CronTask { +func ScheduledAfterGT(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldGT(FieldScheduledAfter, v)) } // ScheduledAfterGTE applies the GTE predicate on the "scheduled_after" field. -func ScheduledAfterGTE(v int) predicate.CronTask { +func ScheduledAfterGTE(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldGTE(FieldScheduledAfter, v)) } // ScheduledAfterLT applies the LT predicate on the "scheduled_after" field. -func ScheduledAfterLT(v int) predicate.CronTask { +func ScheduledAfterLT(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldLT(FieldScheduledAfter, v)) } // ScheduledAfterLTE applies the LTE predicate on the "scheduled_after" field. -func ScheduledAfterLTE(v int) predicate.CronTask { +func ScheduledAfterLTE(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldLTE(FieldScheduledAfter, v)) } // LastRunEQ applies the EQ predicate on the "last_run" field. -func LastRunEQ(v int) predicate.CronTask { +func LastRunEQ(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldLastRun, v)) } // LastRunNEQ applies the NEQ predicate on the "last_run" field. -func LastRunNEQ(v int) predicate.CronTask { +func LastRunNEQ(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldNEQ(FieldLastRun, v)) } // LastRunIn applies the In predicate on the "last_run" field. -func LastRunIn(vs ...int) predicate.CronTask { +func LastRunIn(vs ...int64) predicate.CronTask { return predicate.CronTask(sql.FieldIn(FieldLastRun, vs...)) } // LastRunNotIn applies the NotIn predicate on the "last_run" field. -func LastRunNotIn(vs ...int) predicate.CronTask { +func LastRunNotIn(vs ...int64) predicate.CronTask { return predicate.CronTask(sql.FieldNotIn(FieldLastRun, vs...)) } // LastRunGT applies the GT predicate on the "last_run" field. -func LastRunGT(v int) predicate.CronTask { +func LastRunGT(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldGT(FieldLastRun, v)) } // LastRunGTE applies the GTE predicate on the "last_run" field. -func LastRunGTE(v int) predicate.CronTask { +func LastRunGTE(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldGTE(FieldLastRun, v)) } // LastRunLT applies the LT predicate on the "last_run" field. -func LastRunLT(v int) predicate.CronTask { +func LastRunLT(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldLT(FieldLastRun, v)) } // LastRunLTE applies the LTE predicate on the "last_run" field. -func LastRunLTE(v int) predicate.CronTask { +func LastRunLTE(v int64) predicate.CronTask { return predicate.CronTask(sql.FieldLTE(FieldLastRun, v)) } diff --git a/internal/database/ent/db/crontask_create.go b/internal/database/ent/db/crontask_create.go index 40345b5d..78feb06e 100644 --- a/internal/database/ent/db/crontask_create.go +++ b/internal/database/ent/db/crontask_create.go @@ -21,13 +21,13 @@ type CronTaskCreate struct { } // SetCreatedAt sets the "created_at" field. -func (ctc *CronTaskCreate) SetCreatedAt(i int) *CronTaskCreate { +func (ctc *CronTaskCreate) SetCreatedAt(i int64) *CronTaskCreate { ctc.mutation.SetCreatedAt(i) return ctc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (ctc *CronTaskCreate) SetNillableCreatedAt(i *int) *CronTaskCreate { +func (ctc *CronTaskCreate) SetNillableCreatedAt(i *int64) *CronTaskCreate { if i != nil { ctc.SetCreatedAt(*i) } @@ -35,13 +35,13 @@ func (ctc *CronTaskCreate) SetNillableCreatedAt(i *int) *CronTaskCreate { } // SetUpdatedAt sets the "updated_at" field. -func (ctc *CronTaskCreate) SetUpdatedAt(i int) *CronTaskCreate { +func (ctc *CronTaskCreate) SetUpdatedAt(i int64) *CronTaskCreate { ctc.mutation.SetUpdatedAt(i) return ctc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (ctc *CronTaskCreate) SetNillableUpdatedAt(i *int) *CronTaskCreate { +func (ctc *CronTaskCreate) SetNillableUpdatedAt(i *int64) *CronTaskCreate { if i != nil { ctc.SetUpdatedAt(*i) } @@ -49,8 +49,8 @@ func (ctc *CronTaskCreate) SetNillableUpdatedAt(i *int) *CronTaskCreate { } // SetType sets the "type" field. -func (ctc *CronTaskCreate) SetType(s string) *CronTaskCreate { - ctc.mutation.SetType(s) +func (ctc *CronTaskCreate) SetType(mt models.TaskType) *CronTaskCreate { + ctc.mutation.SetType(mt) return ctc } @@ -67,19 +67,19 @@ func (ctc *CronTaskCreate) SetTargets(s []string) *CronTaskCreate { } // SetStatus sets the "status" field. -func (ctc *CronTaskCreate) SetStatus(s string) *CronTaskCreate { - ctc.mutation.SetStatus(s) +func (ctc *CronTaskCreate) SetStatus(ms models.TaskStatus) *CronTaskCreate { + ctc.mutation.SetStatus(ms) return ctc } // SetScheduledAfter sets the "scheduled_after" field. -func (ctc *CronTaskCreate) SetScheduledAfter(i int) *CronTaskCreate { +func (ctc *CronTaskCreate) SetScheduledAfter(i int64) *CronTaskCreate { ctc.mutation.SetScheduledAfter(i) return ctc } // SetLastRun sets the "last_run" field. -func (ctc *CronTaskCreate) SetLastRun(i int) *CronTaskCreate { +func (ctc *CronTaskCreate) SetLastRun(i int64) *CronTaskCreate { ctc.mutation.SetLastRun(i) return ctc } @@ -242,15 +242,15 @@ func (ctc *CronTaskCreate) createSpec() (*CronTask, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := ctc.mutation.CreatedAt(); ok { - _spec.SetField(crontask.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(crontask.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := ctc.mutation.UpdatedAt(); ok { - _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := ctc.mutation.GetType(); ok { - _spec.SetField(crontask.FieldType, field.TypeString, value) + _spec.SetField(crontask.FieldType, field.TypeEnum, value) _node.Type = value } if value, ok := ctc.mutation.ReferenceID(); ok { @@ -262,15 +262,15 @@ func (ctc *CronTaskCreate) createSpec() (*CronTask, *sqlgraph.CreateSpec) { _node.Targets = value } if value, ok := ctc.mutation.Status(); ok { - _spec.SetField(crontask.FieldStatus, field.TypeString, value) + _spec.SetField(crontask.FieldStatus, field.TypeEnum, value) _node.Status = value } if value, ok := ctc.mutation.ScheduledAfter(); ok { - _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt, value) + _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt64, value) _node.ScheduledAfter = value } if value, ok := ctc.mutation.LastRun(); ok { - _spec.SetField(crontask.FieldLastRun, field.TypeInt, value) + _spec.SetField(crontask.FieldLastRun, field.TypeInt64, value) _node.LastRun = value } if value, ok := ctc.mutation.Logs(); ok { diff --git a/internal/database/ent/db/crontask_query.go b/internal/database/ent/db/crontask_query.go index 2ff7de14..27b8ca39 100644 --- a/internal/database/ent/db/crontask_query.go +++ b/internal/database/ent/db/crontask_query.go @@ -261,7 +261,7 @@ func (ctq *CronTaskQuery) Clone() *CronTaskQuery { // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +284,7 @@ func (ctq *CronTaskQuery) GroupBy(field string, fields ...string) *CronTaskGroup // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.CronTask.Query(). diff --git a/internal/database/ent/db/crontask_update.go b/internal/database/ent/db/crontask_update.go index 1d3bd10f..ddbf9627 100644 --- a/internal/database/ent/db/crontask_update.go +++ b/internal/database/ent/db/crontask_update.go @@ -30,28 +30,28 @@ func (ctu *CronTaskUpdate) Where(ps ...predicate.CronTask) *CronTaskUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (ctu *CronTaskUpdate) SetUpdatedAt(i int) *CronTaskUpdate { +func (ctu *CronTaskUpdate) SetUpdatedAt(i int64) *CronTaskUpdate { ctu.mutation.ResetUpdatedAt() ctu.mutation.SetUpdatedAt(i) return ctu } // AddUpdatedAt adds i to the "updated_at" field. -func (ctu *CronTaskUpdate) AddUpdatedAt(i int) *CronTaskUpdate { +func (ctu *CronTaskUpdate) AddUpdatedAt(i int64) *CronTaskUpdate { ctu.mutation.AddUpdatedAt(i) return ctu } // SetType sets the "type" field. -func (ctu *CronTaskUpdate) SetType(s string) *CronTaskUpdate { - ctu.mutation.SetType(s) +func (ctu *CronTaskUpdate) SetType(mt models.TaskType) *CronTaskUpdate { + ctu.mutation.SetType(mt) return ctu } // SetNillableType sets the "type" field if the given value is not nil. -func (ctu *CronTaskUpdate) SetNillableType(s *string) *CronTaskUpdate { - if s != nil { - ctu.SetType(*s) +func (ctu *CronTaskUpdate) SetNillableType(mt *models.TaskType) *CronTaskUpdate { + if mt != nil { + ctu.SetType(*mt) } return ctu } @@ -83,28 +83,28 @@ func (ctu *CronTaskUpdate) AppendTargets(s []string) *CronTaskUpdate { } // SetStatus sets the "status" field. -func (ctu *CronTaskUpdate) SetStatus(s string) *CronTaskUpdate { - ctu.mutation.SetStatus(s) +func (ctu *CronTaskUpdate) SetStatus(ms models.TaskStatus) *CronTaskUpdate { + ctu.mutation.SetStatus(ms) return ctu } // SetNillableStatus sets the "status" field if the given value is not nil. -func (ctu *CronTaskUpdate) SetNillableStatus(s *string) *CronTaskUpdate { - if s != nil { - ctu.SetStatus(*s) +func (ctu *CronTaskUpdate) SetNillableStatus(ms *models.TaskStatus) *CronTaskUpdate { + if ms != nil { + ctu.SetStatus(*ms) } return ctu } // SetScheduledAfter sets the "scheduled_after" field. -func (ctu *CronTaskUpdate) SetScheduledAfter(i int) *CronTaskUpdate { +func (ctu *CronTaskUpdate) SetScheduledAfter(i int64) *CronTaskUpdate { ctu.mutation.ResetScheduledAfter() ctu.mutation.SetScheduledAfter(i) return ctu } // SetNillableScheduledAfter sets the "scheduled_after" field if the given value is not nil. -func (ctu *CronTaskUpdate) SetNillableScheduledAfter(i *int) *CronTaskUpdate { +func (ctu *CronTaskUpdate) SetNillableScheduledAfter(i *int64) *CronTaskUpdate { if i != nil { ctu.SetScheduledAfter(*i) } @@ -112,20 +112,20 @@ func (ctu *CronTaskUpdate) SetNillableScheduledAfter(i *int) *CronTaskUpdate { } // AddScheduledAfter adds i to the "scheduled_after" field. -func (ctu *CronTaskUpdate) AddScheduledAfter(i int) *CronTaskUpdate { +func (ctu *CronTaskUpdate) AddScheduledAfter(i int64) *CronTaskUpdate { ctu.mutation.AddScheduledAfter(i) return ctu } // SetLastRun sets the "last_run" field. -func (ctu *CronTaskUpdate) SetLastRun(i int) *CronTaskUpdate { +func (ctu *CronTaskUpdate) SetLastRun(i int64) *CronTaskUpdate { ctu.mutation.ResetLastRun() ctu.mutation.SetLastRun(i) return ctu } // SetNillableLastRun sets the "last_run" field if the given value is not nil. -func (ctu *CronTaskUpdate) SetNillableLastRun(i *int) *CronTaskUpdate { +func (ctu *CronTaskUpdate) SetNillableLastRun(i *int64) *CronTaskUpdate { if i != nil { ctu.SetLastRun(*i) } @@ -133,7 +133,7 @@ func (ctu *CronTaskUpdate) SetNillableLastRun(i *int) *CronTaskUpdate { } // AddLastRun adds i to the "last_run" field. -func (ctu *CronTaskUpdate) AddLastRun(i int) *CronTaskUpdate { +func (ctu *CronTaskUpdate) AddLastRun(i int64) *CronTaskUpdate { ctu.mutation.AddLastRun(i) return ctu } @@ -230,13 +230,13 @@ func (ctu *CronTaskUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := ctu.mutation.UpdatedAt(); ok { - _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ctu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(crontask.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(crontask.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ctu.mutation.GetType(); ok { - _spec.SetField(crontask.FieldType, field.TypeString, value) + _spec.SetField(crontask.FieldType, field.TypeEnum, value) } if value, ok := ctu.mutation.ReferenceID(); ok { _spec.SetField(crontask.FieldReferenceID, field.TypeString, value) @@ -250,19 +250,19 @@ func (ctu *CronTaskUpdate) sqlSave(ctx context.Context) (n int, err error) { }) } if value, ok := ctu.mutation.Status(); ok { - _spec.SetField(crontask.FieldStatus, field.TypeString, value) + _spec.SetField(crontask.FieldStatus, field.TypeEnum, value) } if value, ok := ctu.mutation.ScheduledAfter(); ok { - _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt, value) + _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt64, value) } if value, ok := ctu.mutation.AddedScheduledAfter(); ok { - _spec.AddField(crontask.FieldScheduledAfter, field.TypeInt, value) + _spec.AddField(crontask.FieldScheduledAfter, field.TypeInt64, value) } if value, ok := ctu.mutation.LastRun(); ok { - _spec.SetField(crontask.FieldLastRun, field.TypeInt, value) + _spec.SetField(crontask.FieldLastRun, field.TypeInt64, value) } if value, ok := ctu.mutation.AddedLastRun(); ok { - _spec.AddField(crontask.FieldLastRun, field.TypeInt, value) + _spec.AddField(crontask.FieldLastRun, field.TypeInt64, value) } if value, ok := ctu.mutation.Logs(); ok { _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) @@ -296,28 +296,28 @@ type CronTaskUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (ctuo *CronTaskUpdateOne) SetUpdatedAt(i int) *CronTaskUpdateOne { +func (ctuo *CronTaskUpdateOne) SetUpdatedAt(i int64) *CronTaskUpdateOne { ctuo.mutation.ResetUpdatedAt() ctuo.mutation.SetUpdatedAt(i) return ctuo } // AddUpdatedAt adds i to the "updated_at" field. -func (ctuo *CronTaskUpdateOne) AddUpdatedAt(i int) *CronTaskUpdateOne { +func (ctuo *CronTaskUpdateOne) AddUpdatedAt(i int64) *CronTaskUpdateOne { ctuo.mutation.AddUpdatedAt(i) return ctuo } // SetType sets the "type" field. -func (ctuo *CronTaskUpdateOne) SetType(s string) *CronTaskUpdateOne { - ctuo.mutation.SetType(s) +func (ctuo *CronTaskUpdateOne) SetType(mt models.TaskType) *CronTaskUpdateOne { + ctuo.mutation.SetType(mt) return ctuo } // SetNillableType sets the "type" field if the given value is not nil. -func (ctuo *CronTaskUpdateOne) SetNillableType(s *string) *CronTaskUpdateOne { - if s != nil { - ctuo.SetType(*s) +func (ctuo *CronTaskUpdateOne) SetNillableType(mt *models.TaskType) *CronTaskUpdateOne { + if mt != nil { + ctuo.SetType(*mt) } return ctuo } @@ -349,28 +349,28 @@ func (ctuo *CronTaskUpdateOne) AppendTargets(s []string) *CronTaskUpdateOne { } // SetStatus sets the "status" field. -func (ctuo *CronTaskUpdateOne) SetStatus(s string) *CronTaskUpdateOne { - ctuo.mutation.SetStatus(s) +func (ctuo *CronTaskUpdateOne) SetStatus(ms models.TaskStatus) *CronTaskUpdateOne { + ctuo.mutation.SetStatus(ms) return ctuo } // SetNillableStatus sets the "status" field if the given value is not nil. -func (ctuo *CronTaskUpdateOne) SetNillableStatus(s *string) *CronTaskUpdateOne { - if s != nil { - ctuo.SetStatus(*s) +func (ctuo *CronTaskUpdateOne) SetNillableStatus(ms *models.TaskStatus) *CronTaskUpdateOne { + if ms != nil { + ctuo.SetStatus(*ms) } return ctuo } // SetScheduledAfter sets the "scheduled_after" field. -func (ctuo *CronTaskUpdateOne) SetScheduledAfter(i int) *CronTaskUpdateOne { +func (ctuo *CronTaskUpdateOne) SetScheduledAfter(i int64) *CronTaskUpdateOne { ctuo.mutation.ResetScheduledAfter() ctuo.mutation.SetScheduledAfter(i) return ctuo } // SetNillableScheduledAfter sets the "scheduled_after" field if the given value is not nil. -func (ctuo *CronTaskUpdateOne) SetNillableScheduledAfter(i *int) *CronTaskUpdateOne { +func (ctuo *CronTaskUpdateOne) SetNillableScheduledAfter(i *int64) *CronTaskUpdateOne { if i != nil { ctuo.SetScheduledAfter(*i) } @@ -378,20 +378,20 @@ func (ctuo *CronTaskUpdateOne) SetNillableScheduledAfter(i *int) *CronTaskUpdate } // AddScheduledAfter adds i to the "scheduled_after" field. -func (ctuo *CronTaskUpdateOne) AddScheduledAfter(i int) *CronTaskUpdateOne { +func (ctuo *CronTaskUpdateOne) AddScheduledAfter(i int64) *CronTaskUpdateOne { ctuo.mutation.AddScheduledAfter(i) return ctuo } // SetLastRun sets the "last_run" field. -func (ctuo *CronTaskUpdateOne) SetLastRun(i int) *CronTaskUpdateOne { +func (ctuo *CronTaskUpdateOne) SetLastRun(i int64) *CronTaskUpdateOne { ctuo.mutation.ResetLastRun() ctuo.mutation.SetLastRun(i) return ctuo } // SetNillableLastRun sets the "last_run" field if the given value is not nil. -func (ctuo *CronTaskUpdateOne) SetNillableLastRun(i *int) *CronTaskUpdateOne { +func (ctuo *CronTaskUpdateOne) SetNillableLastRun(i *int64) *CronTaskUpdateOne { if i != nil { ctuo.SetLastRun(*i) } @@ -399,7 +399,7 @@ func (ctuo *CronTaskUpdateOne) SetNillableLastRun(i *int) *CronTaskUpdateOne { } // AddLastRun adds i to the "last_run" field. -func (ctuo *CronTaskUpdateOne) AddLastRun(i int) *CronTaskUpdateOne { +func (ctuo *CronTaskUpdateOne) AddLastRun(i int64) *CronTaskUpdateOne { ctuo.mutation.AddLastRun(i) return ctuo } @@ -526,13 +526,13 @@ func (ctuo *CronTaskUpdateOne) sqlSave(ctx context.Context) (_node *CronTask, er } } if value, ok := ctuo.mutation.UpdatedAt(); ok { - _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ctuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(crontask.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(crontask.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ctuo.mutation.GetType(); ok { - _spec.SetField(crontask.FieldType, field.TypeString, value) + _spec.SetField(crontask.FieldType, field.TypeEnum, value) } if value, ok := ctuo.mutation.ReferenceID(); ok { _spec.SetField(crontask.FieldReferenceID, field.TypeString, value) @@ -546,19 +546,19 @@ func (ctuo *CronTaskUpdateOne) sqlSave(ctx context.Context) (_node *CronTask, er }) } if value, ok := ctuo.mutation.Status(); ok { - _spec.SetField(crontask.FieldStatus, field.TypeString, value) + _spec.SetField(crontask.FieldStatus, field.TypeEnum, value) } if value, ok := ctuo.mutation.ScheduledAfter(); ok { - _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt, value) + _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt64, value) } if value, ok := ctuo.mutation.AddedScheduledAfter(); ok { - _spec.AddField(crontask.FieldScheduledAfter, field.TypeInt, value) + _spec.AddField(crontask.FieldScheduledAfter, field.TypeInt64, value) } if value, ok := ctuo.mutation.LastRun(); ok { - _spec.SetField(crontask.FieldLastRun, field.TypeInt, value) + _spec.SetField(crontask.FieldLastRun, field.TypeInt64, value) } if value, ok := ctuo.mutation.AddedLastRun(); ok { - _spec.AddField(crontask.FieldLastRun, field.TypeInt, value) + _spec.AddField(crontask.FieldLastRun, field.TypeInt64, value) } if value, ok := ctuo.mutation.Logs(); ok { _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index 6496f121..f5c10f59 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -11,10 +11,10 @@ var ( // AccountsColumns holds the columns for the "accounts" table. AccountsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, - {Name: "last_battle_time", Type: field.TypeInt}, - {Name: "account_created_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, + {Name: "last_battle_time", Type: field.TypeInt64}, + {Name: "account_created_at", Type: field.TypeInt64}, {Name: "realm", Type: field.TypeString, Size: 5}, {Name: "nickname", Type: field.TypeString}, {Name: "private", Type: field.TypeBool, Default: false}, @@ -59,10 +59,10 @@ var ( // AccountSnapshotsColumns holds the columns for the "account_snapshots" table. AccountSnapshotsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "type", Type: field.TypeEnum, Enums: []string{"live", "daily"}}, - {Name: "last_battle_time", Type: field.TypeInt}, + {Name: "last_battle_time", Type: field.TypeInt64}, {Name: "reference_id", Type: field.TypeString}, {Name: "rating_battles", Type: field.TypeInt}, {Name: "rating_frame", Type: field.TypeJSON}, @@ -109,12 +109,12 @@ var ( // AchievementsSnapshotsColumns holds the columns for the "achievements_snapshots" table. AchievementsSnapshotsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "type", Type: field.TypeEnum, Enums: []string{"live", "daily"}}, {Name: "reference_id", Type: field.TypeString}, {Name: "battles", Type: field.TypeInt}, - {Name: "last_battle_time", Type: field.TypeInt}, + {Name: "last_battle_time", Type: field.TypeInt64}, {Name: "data", Type: field.TypeJSON}, {Name: "account_id", Type: field.TypeString}, } @@ -152,8 +152,8 @@ var ( // AppConfigurationsColumns holds the columns for the "app_configurations" table. AppConfigurationsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "key", Type: field.TypeString, Unique: true}, {Name: "value", Type: field.TypeJSON}, {Name: "metadata", Type: field.TypeJSON, Nullable: true}, @@ -174,8 +174,8 @@ var ( // ApplicationCommandsColumns holds the columns for the "application_commands" table. ApplicationCommandsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "name", Type: field.TypeString, Unique: true}, {Name: "version", Type: field.TypeString}, {Name: "options_hash", Type: field.TypeString}, @@ -196,8 +196,8 @@ var ( // ClansColumns holds the columns for the "clans" table. ClansColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "tag", Type: field.TypeString}, {Name: "name", Type: field.TypeString}, {Name: "emblem_id", Type: field.TypeString, Nullable: true, Default: ""}, @@ -224,14 +224,14 @@ var ( // CronTasksColumns holds the columns for the "cron_tasks" table. CronTasksColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, - {Name: "type", Type: field.TypeString}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, + {Name: "type", Type: field.TypeEnum, Enums: []string{"UPDATE_CLANS", "RECORD_ACCOUNT_SESSIONS", "UPDATE_ACCOUNT_WN8", "UPDATE_ACCOUNT_ACHIEVEMENTS", "CLEANUP_DATABASE"}}, {Name: "reference_id", Type: field.TypeString}, {Name: "targets", Type: field.TypeJSON}, - {Name: "status", Type: field.TypeString}, - {Name: "scheduled_after", Type: field.TypeInt}, - {Name: "last_run", Type: field.TypeInt}, + {Name: "status", Type: field.TypeEnum, Enums: []string{"TASK_SCHEDULED", "TASK_IN_PROGRESS", "TASK_COMPLETE", "TASK_FAILED"}}, + {Name: "scheduled_after", Type: field.TypeInt64}, + {Name: "last_run", Type: field.TypeInt64}, {Name: "logs", Type: field.TypeJSON}, {Name: "data", Type: field.TypeJSON}, } @@ -266,8 +266,8 @@ var ( // UsersColumns holds the columns for the "users" table. UsersColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "permissions", Type: field.TypeString, Default: ""}, {Name: "feature_flags", Type: field.TypeJSON, Nullable: true}, } @@ -280,8 +280,8 @@ var ( // UserConnectionsColumns holds the columns for the "user_connections" table. UserConnectionsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "type", Type: field.TypeEnum, Enums: []string{"wargaming"}}, {Name: "reference_id", Type: field.TypeString}, {Name: "permissions", Type: field.TypeString, Nullable: true, Default: ""}, @@ -327,8 +327,8 @@ var ( // UserContentsColumns holds the columns for the "user_contents" table. UserContentsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "type", Type: field.TypeEnum, Enums: []string{"clan-background-image", "personal-background-image"}}, {Name: "reference_id", Type: field.TypeString}, {Name: "value", Type: field.TypeJSON}, @@ -374,10 +374,10 @@ var ( // UserSubscriptionsColumns holds the columns for the "user_subscriptions" table. UserSubscriptionsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "type", Type: field.TypeEnum, Enums: []string{"aftermath-pro", "aftermath-pro-clan", "aftermath-plus", "supporter", "verified-clan", "server-moderator", "content-moderator", "developer", "server-booster", "content-translator"}}, - {Name: "expires_at", Type: field.TypeInt}, + {Name: "expires_at", Type: field.TypeInt64}, {Name: "permissions", Type: field.TypeString}, {Name: "reference_id", Type: field.TypeString}, {Name: "user_id", Type: field.TypeString}, @@ -421,8 +421,8 @@ var ( // VehiclesColumns holds the columns for the "vehicles" table. VehiclesColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "tier", Type: field.TypeInt}, {Name: "localized_names", Type: field.TypeJSON}, } @@ -435,8 +435,8 @@ var ( // VehicleAveragesColumns holds the columns for the "vehicle_averages" table. VehicleAveragesColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "data", Type: field.TypeJSON}, } // VehicleAveragesTable holds the schema information for the "vehicle_averages" table. @@ -448,13 +448,13 @@ var ( // VehicleSnapshotsColumns holds the columns for the "vehicle_snapshots" table. VehicleSnapshotsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt}, - {Name: "updated_at", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, {Name: "type", Type: field.TypeEnum, Enums: []string{"live", "daily"}}, {Name: "vehicle_id", Type: field.TypeString}, {Name: "reference_id", Type: field.TypeString}, {Name: "battles", Type: field.TypeInt}, - {Name: "last_battle_time", Type: field.TypeInt}, + {Name: "last_battle_time", Type: field.TypeInt64}, {Name: "frame", Type: field.TypeJSON}, {Name: "account_id", Type: field.TypeString}, } diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go index dbf6c4bc..49983f2e 100644 --- a/internal/database/ent/db/mutation.go +++ b/internal/database/ent/db/mutation.go @@ -61,14 +61,14 @@ type AccountMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int - last_battle_time *int - addlast_battle_time *int - account_created_at *int - addaccount_created_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 + last_battle_time *int64 + addlast_battle_time *int64 + account_created_at *int64 + addaccount_created_at *int64 realm *string nickname *string private *bool @@ -194,13 +194,13 @@ func (m *AccountMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *AccountMutation) SetCreatedAt(i int) { +func (m *AccountMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *AccountMutation) CreatedAt() (r int, exists bool) { +func (m *AccountMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -211,7 +211,7 @@ func (m *AccountMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the Account entity. // If the Account object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *AccountMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -226,7 +226,7 @@ func (m *AccountMutation) OldCreatedAt(ctx context.Context) (v int, err error) { } // AddCreatedAt adds i to the "created_at" field. -func (m *AccountMutation) AddCreatedAt(i int) { +func (m *AccountMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -235,7 +235,7 @@ func (m *AccountMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *AccountMutation) AddedCreatedAt() (r int, exists bool) { +func (m *AccountMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -250,13 +250,13 @@ func (m *AccountMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *AccountMutation) SetUpdatedAt(i int) { +func (m *AccountMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *AccountMutation) UpdatedAt() (r int, exists bool) { +func (m *AccountMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -267,7 +267,7 @@ func (m *AccountMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the Account entity. // If the Account object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *AccountMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -282,7 +282,7 @@ func (m *AccountMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { } // AddUpdatedAt adds i to the "updated_at" field. -func (m *AccountMutation) AddUpdatedAt(i int) { +func (m *AccountMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -291,7 +291,7 @@ func (m *AccountMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *AccountMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *AccountMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -306,13 +306,13 @@ func (m *AccountMutation) ResetUpdatedAt() { } // SetLastBattleTime sets the "last_battle_time" field. -func (m *AccountMutation) SetLastBattleTime(i int) { +func (m *AccountMutation) SetLastBattleTime(i int64) { m.last_battle_time = &i m.addlast_battle_time = nil } // LastBattleTime returns the value of the "last_battle_time" field in the mutation. -func (m *AccountMutation) LastBattleTime() (r int, exists bool) { +func (m *AccountMutation) LastBattleTime() (r int64, exists bool) { v := m.last_battle_time if v == nil { return @@ -323,7 +323,7 @@ func (m *AccountMutation) LastBattleTime() (r int, exists bool) { // OldLastBattleTime returns the old "last_battle_time" field's value of the Account entity. // If the Account object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountMutation) OldLastBattleTime(ctx context.Context) (v int, err error) { +func (m *AccountMutation) OldLastBattleTime(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") } @@ -338,7 +338,7 @@ func (m *AccountMutation) OldLastBattleTime(ctx context.Context) (v int, err err } // AddLastBattleTime adds i to the "last_battle_time" field. -func (m *AccountMutation) AddLastBattleTime(i int) { +func (m *AccountMutation) AddLastBattleTime(i int64) { if m.addlast_battle_time != nil { *m.addlast_battle_time += i } else { @@ -347,7 +347,7 @@ func (m *AccountMutation) AddLastBattleTime(i int) { } // AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. -func (m *AccountMutation) AddedLastBattleTime() (r int, exists bool) { +func (m *AccountMutation) AddedLastBattleTime() (r int64, exists bool) { v := m.addlast_battle_time if v == nil { return @@ -362,13 +362,13 @@ func (m *AccountMutation) ResetLastBattleTime() { } // SetAccountCreatedAt sets the "account_created_at" field. -func (m *AccountMutation) SetAccountCreatedAt(i int) { +func (m *AccountMutation) SetAccountCreatedAt(i int64) { m.account_created_at = &i m.addaccount_created_at = nil } // AccountCreatedAt returns the value of the "account_created_at" field in the mutation. -func (m *AccountMutation) AccountCreatedAt() (r int, exists bool) { +func (m *AccountMutation) AccountCreatedAt() (r int64, exists bool) { v := m.account_created_at if v == nil { return @@ -379,7 +379,7 @@ func (m *AccountMutation) AccountCreatedAt() (r int, exists bool) { // OldAccountCreatedAt returns the old "account_created_at" field's value of the Account entity. // If the Account object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountMutation) OldAccountCreatedAt(ctx context.Context) (v int, err error) { +func (m *AccountMutation) OldAccountCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldAccountCreatedAt is only allowed on UpdateOne operations") } @@ -394,7 +394,7 @@ func (m *AccountMutation) OldAccountCreatedAt(ctx context.Context) (v int, err e } // AddAccountCreatedAt adds i to the "account_created_at" field. -func (m *AccountMutation) AddAccountCreatedAt(i int) { +func (m *AccountMutation) AddAccountCreatedAt(i int64) { if m.addaccount_created_at != nil { *m.addaccount_created_at += i } else { @@ -403,7 +403,7 @@ func (m *AccountMutation) AddAccountCreatedAt(i int) { } // AddedAccountCreatedAt returns the value that was added to the "account_created_at" field in this mutation. -func (m *AccountMutation) AddedAccountCreatedAt() (r int, exists bool) { +func (m *AccountMutation) AddedAccountCreatedAt() (r int64, exists bool) { v := m.addaccount_created_at if v == nil { return @@ -881,28 +881,28 @@ func (m *AccountMutation) OldField(ctx context.Context, name string) (ent.Value, func (m *AccountMutation) SetField(name string, value ent.Value) error { switch name { case account.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case account.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUpdatedAt(v) return nil case account.FieldLastBattleTime: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetLastBattleTime(v) return nil case account.FieldAccountCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -982,28 +982,28 @@ func (m *AccountMutation) AddedField(name string) (ent.Value, bool) { func (m *AccountMutation) AddField(name string, value ent.Value) error { switch name { case account.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case account.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddUpdatedAt(v) return nil case account.FieldLastBattleTime: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddLastBattleTime(v) return nil case account.FieldAccountCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -1233,13 +1233,13 @@ type AccountSnapshotMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 _type *models.SnapshotType - last_battle_time *int - addlast_battle_time *int + last_battle_time *int64 + addlast_battle_time *int64 reference_id *string rating_battles *int addrating_battles *int @@ -1360,13 +1360,13 @@ func (m *AccountSnapshotMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *AccountSnapshotMutation) SetCreatedAt(i int) { +func (m *AccountSnapshotMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *AccountSnapshotMutation) CreatedAt() (r int, exists bool) { +func (m *AccountSnapshotMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -1377,7 +1377,7 @@ func (m *AccountSnapshotMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the AccountSnapshot entity. // If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountSnapshotMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *AccountSnapshotMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -1392,7 +1392,7 @@ func (m *AccountSnapshotMutation) OldCreatedAt(ctx context.Context) (v int, err } // AddCreatedAt adds i to the "created_at" field. -func (m *AccountSnapshotMutation) AddCreatedAt(i int) { +func (m *AccountSnapshotMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -1401,7 +1401,7 @@ func (m *AccountSnapshotMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *AccountSnapshotMutation) AddedCreatedAt() (r int, exists bool) { +func (m *AccountSnapshotMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -1416,13 +1416,13 @@ func (m *AccountSnapshotMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *AccountSnapshotMutation) SetUpdatedAt(i int) { +func (m *AccountSnapshotMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *AccountSnapshotMutation) UpdatedAt() (r int, exists bool) { +func (m *AccountSnapshotMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -1433,7 +1433,7 @@ func (m *AccountSnapshotMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the AccountSnapshot entity. // If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *AccountSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -1448,7 +1448,7 @@ func (m *AccountSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int, err } // AddUpdatedAt adds i to the "updated_at" field. -func (m *AccountSnapshotMutation) AddUpdatedAt(i int) { +func (m *AccountSnapshotMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -1457,7 +1457,7 @@ func (m *AccountSnapshotMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *AccountSnapshotMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *AccountSnapshotMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -1508,13 +1508,13 @@ func (m *AccountSnapshotMutation) ResetType() { } // SetLastBattleTime sets the "last_battle_time" field. -func (m *AccountSnapshotMutation) SetLastBattleTime(i int) { +func (m *AccountSnapshotMutation) SetLastBattleTime(i int64) { m.last_battle_time = &i m.addlast_battle_time = nil } // LastBattleTime returns the value of the "last_battle_time" field in the mutation. -func (m *AccountSnapshotMutation) LastBattleTime() (r int, exists bool) { +func (m *AccountSnapshotMutation) LastBattleTime() (r int64, exists bool) { v := m.last_battle_time if v == nil { return @@ -1525,7 +1525,7 @@ func (m *AccountSnapshotMutation) LastBattleTime() (r int, exists bool) { // OldLastBattleTime returns the old "last_battle_time" field's value of the AccountSnapshot entity. // If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int, err error) { +func (m *AccountSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") } @@ -1540,7 +1540,7 @@ func (m *AccountSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int, } // AddLastBattleTime adds i to the "last_battle_time" field. -func (m *AccountSnapshotMutation) AddLastBattleTime(i int) { +func (m *AccountSnapshotMutation) AddLastBattleTime(i int64) { if m.addlast_battle_time != nil { *m.addlast_battle_time += i } else { @@ -1549,7 +1549,7 @@ func (m *AccountSnapshotMutation) AddLastBattleTime(i int) { } // AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. -func (m *AccountSnapshotMutation) AddedLastBattleTime() (r int, exists bool) { +func (m *AccountSnapshotMutation) AddedLastBattleTime() (r int64, exists bool) { v := m.addlast_battle_time if v == nil { return @@ -1978,14 +1978,14 @@ func (m *AccountSnapshotMutation) OldField(ctx context.Context, name string) (en func (m *AccountSnapshotMutation) SetField(name string, value ent.Value) error { switch name { case accountsnapshot.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case accountsnapshot.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -1999,7 +1999,7 @@ func (m *AccountSnapshotMutation) SetField(name string, value ent.Value) error { m.SetType(v) return nil case accountsnapshot.FieldLastBattleTime: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -2098,21 +2098,21 @@ func (m *AccountSnapshotMutation) AddedField(name string) (ent.Value, bool) { func (m *AccountSnapshotMutation) AddField(name string, value ent.Value) error { switch name { case accountsnapshot.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case accountsnapshot.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddUpdatedAt(v) return nil case accountsnapshot.FieldLastBattleTime: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -2273,16 +2273,16 @@ type AchievementsSnapshotMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 _type *models.SnapshotType reference_id *string battles *int addbattles *int - last_battle_time *int - addlast_battle_time *int + last_battle_time *int64 + addlast_battle_time *int64 data *types.AchievementsFrame clearedFields map[string]struct{} account *string @@ -2397,13 +2397,13 @@ func (m *AchievementsSnapshotMutation) IDs(ctx context.Context) ([]string, error } // SetCreatedAt sets the "created_at" field. -func (m *AchievementsSnapshotMutation) SetCreatedAt(i int) { +func (m *AchievementsSnapshotMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *AchievementsSnapshotMutation) CreatedAt() (r int, exists bool) { +func (m *AchievementsSnapshotMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -2414,7 +2414,7 @@ func (m *AchievementsSnapshotMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the AchievementsSnapshot entity. // If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AchievementsSnapshotMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *AchievementsSnapshotMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -2429,7 +2429,7 @@ func (m *AchievementsSnapshotMutation) OldCreatedAt(ctx context.Context) (v int, } // AddCreatedAt adds i to the "created_at" field. -func (m *AchievementsSnapshotMutation) AddCreatedAt(i int) { +func (m *AchievementsSnapshotMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -2438,7 +2438,7 @@ func (m *AchievementsSnapshotMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *AchievementsSnapshotMutation) AddedCreatedAt() (r int, exists bool) { +func (m *AchievementsSnapshotMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -2453,13 +2453,13 @@ func (m *AchievementsSnapshotMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *AchievementsSnapshotMutation) SetUpdatedAt(i int) { +func (m *AchievementsSnapshotMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *AchievementsSnapshotMutation) UpdatedAt() (r int, exists bool) { +func (m *AchievementsSnapshotMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -2470,7 +2470,7 @@ func (m *AchievementsSnapshotMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the AchievementsSnapshot entity. // If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AchievementsSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *AchievementsSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -2485,7 +2485,7 @@ func (m *AchievementsSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int, } // AddUpdatedAt adds i to the "updated_at" field. -func (m *AchievementsSnapshotMutation) AddUpdatedAt(i int) { +func (m *AchievementsSnapshotMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -2494,7 +2494,7 @@ func (m *AchievementsSnapshotMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *AchievementsSnapshotMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *AchievementsSnapshotMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -2673,13 +2673,13 @@ func (m *AchievementsSnapshotMutation) ResetBattles() { } // SetLastBattleTime sets the "last_battle_time" field. -func (m *AchievementsSnapshotMutation) SetLastBattleTime(i int) { +func (m *AchievementsSnapshotMutation) SetLastBattleTime(i int64) { m.last_battle_time = &i m.addlast_battle_time = nil } // LastBattleTime returns the value of the "last_battle_time" field in the mutation. -func (m *AchievementsSnapshotMutation) LastBattleTime() (r int, exists bool) { +func (m *AchievementsSnapshotMutation) LastBattleTime() (r int64, exists bool) { v := m.last_battle_time if v == nil { return @@ -2690,7 +2690,7 @@ func (m *AchievementsSnapshotMutation) LastBattleTime() (r int, exists bool) { // OldLastBattleTime returns the old "last_battle_time" field's value of the AchievementsSnapshot entity. // If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AchievementsSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int, err error) { +func (m *AchievementsSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") } @@ -2705,7 +2705,7 @@ func (m *AchievementsSnapshotMutation) OldLastBattleTime(ctx context.Context) (v } // AddLastBattleTime adds i to the "last_battle_time" field. -func (m *AchievementsSnapshotMutation) AddLastBattleTime(i int) { +func (m *AchievementsSnapshotMutation) AddLastBattleTime(i int64) { if m.addlast_battle_time != nil { *m.addlast_battle_time += i } else { @@ -2714,7 +2714,7 @@ func (m *AchievementsSnapshotMutation) AddLastBattleTime(i int) { } // AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. -func (m *AchievementsSnapshotMutation) AddedLastBattleTime() (r int, exists bool) { +func (m *AchievementsSnapshotMutation) AddedLastBattleTime() (r int64, exists bool) { v := m.addlast_battle_time if v == nil { return @@ -2909,14 +2909,14 @@ func (m *AchievementsSnapshotMutation) OldField(ctx context.Context, name string func (m *AchievementsSnapshotMutation) SetField(name string, value ent.Value) error { switch name { case achievementssnapshot.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case achievementssnapshot.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -2951,7 +2951,7 @@ func (m *AchievementsSnapshotMutation) SetField(name string, value ent.Value) er m.SetBattles(v) return nil case achievementssnapshot.FieldLastBattleTime: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -3010,14 +3010,14 @@ func (m *AchievementsSnapshotMutation) AddedField(name string) (ent.Value, bool) func (m *AchievementsSnapshotMutation) AddField(name string, value ent.Value) error { switch name { case achievementssnapshot.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case achievementssnapshot.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -3031,7 +3031,7 @@ func (m *AchievementsSnapshotMutation) AddField(name string, value ent.Value) er m.AddBattles(v) return nil case achievementssnapshot.FieldLastBattleTime: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -3172,10 +3172,10 @@ type AppConfigurationMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 key *string value *any metadata *map[string]interface{} @@ -3290,13 +3290,13 @@ func (m *AppConfigurationMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *AppConfigurationMutation) SetCreatedAt(i int) { +func (m *AppConfigurationMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *AppConfigurationMutation) CreatedAt() (r int, exists bool) { +func (m *AppConfigurationMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -3307,7 +3307,7 @@ func (m *AppConfigurationMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the AppConfiguration entity. // If the AppConfiguration object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AppConfigurationMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *AppConfigurationMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -3322,7 +3322,7 @@ func (m *AppConfigurationMutation) OldCreatedAt(ctx context.Context) (v int, err } // AddCreatedAt adds i to the "created_at" field. -func (m *AppConfigurationMutation) AddCreatedAt(i int) { +func (m *AppConfigurationMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -3331,7 +3331,7 @@ func (m *AppConfigurationMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *AppConfigurationMutation) AddedCreatedAt() (r int, exists bool) { +func (m *AppConfigurationMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -3346,13 +3346,13 @@ func (m *AppConfigurationMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *AppConfigurationMutation) SetUpdatedAt(i int) { +func (m *AppConfigurationMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *AppConfigurationMutation) UpdatedAt() (r int, exists bool) { +func (m *AppConfigurationMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -3363,7 +3363,7 @@ func (m *AppConfigurationMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the AppConfiguration entity. // If the AppConfiguration object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AppConfigurationMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *AppConfigurationMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -3378,7 +3378,7 @@ func (m *AppConfigurationMutation) OldUpdatedAt(ctx context.Context) (v int, err } // AddUpdatedAt adds i to the "updated_at" field. -func (m *AppConfigurationMutation) AddUpdatedAt(i int) { +func (m *AppConfigurationMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -3387,7 +3387,7 @@ func (m *AppConfigurationMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *AppConfigurationMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *AppConfigurationMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -3619,14 +3619,14 @@ func (m *AppConfigurationMutation) OldField(ctx context.Context, name string) (e func (m *AppConfigurationMutation) SetField(name string, value ent.Value) error { switch name { case appconfiguration.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case appconfiguration.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -3689,14 +3689,14 @@ func (m *AppConfigurationMutation) AddedField(name string) (ent.Value, bool) { func (m *AppConfigurationMutation) AddField(name string, value ent.Value) error { switch name { case appconfiguration.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case appconfiguration.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -3811,10 +3811,10 @@ type ApplicationCommandMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 name *string version *string options_hash *string @@ -3929,13 +3929,13 @@ func (m *ApplicationCommandMutation) IDs(ctx context.Context) ([]string, error) } // SetCreatedAt sets the "created_at" field. -func (m *ApplicationCommandMutation) SetCreatedAt(i int) { +func (m *ApplicationCommandMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *ApplicationCommandMutation) CreatedAt() (r int, exists bool) { +func (m *ApplicationCommandMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -3946,7 +3946,7 @@ func (m *ApplicationCommandMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the ApplicationCommand entity. // If the ApplicationCommand object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ApplicationCommandMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *ApplicationCommandMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -3961,7 +3961,7 @@ func (m *ApplicationCommandMutation) OldCreatedAt(ctx context.Context) (v int, e } // AddCreatedAt adds i to the "created_at" field. -func (m *ApplicationCommandMutation) AddCreatedAt(i int) { +func (m *ApplicationCommandMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -3970,7 +3970,7 @@ func (m *ApplicationCommandMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *ApplicationCommandMutation) AddedCreatedAt() (r int, exists bool) { +func (m *ApplicationCommandMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -3985,13 +3985,13 @@ func (m *ApplicationCommandMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *ApplicationCommandMutation) SetUpdatedAt(i int) { +func (m *ApplicationCommandMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *ApplicationCommandMutation) UpdatedAt() (r int, exists bool) { +func (m *ApplicationCommandMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -4002,7 +4002,7 @@ func (m *ApplicationCommandMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the ApplicationCommand entity. // If the ApplicationCommand object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ApplicationCommandMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *ApplicationCommandMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -4017,7 +4017,7 @@ func (m *ApplicationCommandMutation) OldUpdatedAt(ctx context.Context) (v int, e } // AddUpdatedAt adds i to the "updated_at" field. -func (m *ApplicationCommandMutation) AddUpdatedAt(i int) { +func (m *ApplicationCommandMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -4026,7 +4026,7 @@ func (m *ApplicationCommandMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *ApplicationCommandMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *ApplicationCommandMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -4245,14 +4245,14 @@ func (m *ApplicationCommandMutation) OldField(ctx context.Context, name string) func (m *ApplicationCommandMutation) SetField(name string, value ent.Value) error { switch name { case applicationcommand.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case applicationcommand.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -4315,14 +4315,14 @@ func (m *ApplicationCommandMutation) AddedField(name string) (ent.Value, bool) { func (m *ApplicationCommandMutation) AddField(name string, value ent.Value) error { switch name { case applicationcommand.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case applicationcommand.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -4428,10 +4428,10 @@ type ClanMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 tag *string name *string emblem_id *string @@ -4551,13 +4551,13 @@ func (m *ClanMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *ClanMutation) SetCreatedAt(i int) { +func (m *ClanMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *ClanMutation) CreatedAt() (r int, exists bool) { +func (m *ClanMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -4568,7 +4568,7 @@ func (m *ClanMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the Clan entity. // If the Clan object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -4583,7 +4583,7 @@ func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v int, err error) { } // AddCreatedAt adds i to the "created_at" field. -func (m *ClanMutation) AddCreatedAt(i int) { +func (m *ClanMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -4592,7 +4592,7 @@ func (m *ClanMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *ClanMutation) AddedCreatedAt() (r int, exists bool) { +func (m *ClanMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -4607,13 +4607,13 @@ func (m *ClanMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *ClanMutation) SetUpdatedAt(i int) { +func (m *ClanMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *ClanMutation) UpdatedAt() (r int, exists bool) { +func (m *ClanMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -4624,7 +4624,7 @@ func (m *ClanMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the Clan entity. // If the Clan object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -4639,7 +4639,7 @@ func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { } // AddUpdatedAt adds i to the "updated_at" field. -func (m *ClanMutation) AddUpdatedAt(i int) { +func (m *ClanMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -4648,7 +4648,7 @@ func (m *ClanMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *ClanMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *ClanMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -4992,14 +4992,14 @@ func (m *ClanMutation) OldField(ctx context.Context, name string) (ent.Value, er func (m *ClanMutation) SetField(name string, value ent.Value) error { switch name { case clan.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case clan.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -5069,14 +5069,14 @@ func (m *ClanMutation) AddedField(name string) (ent.Value, bool) { func (m *ClanMutation) AddField(name string, value ent.Value) error { switch name { case clan.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case clan.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -5230,19 +5230,19 @@ type CronTaskMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int - _type *string + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 + _type *models.TaskType reference_id *string targets *[]string appendtargets []string - status *string - scheduled_after *int - addscheduled_after *int - last_run *int - addlast_run *int + status *models.TaskStatus + scheduled_after *int64 + addscheduled_after *int64 + last_run *int64 + addlast_run *int64 logs *[]models.TaskLog appendlogs []models.TaskLog data *map[string]interface{} @@ -5357,13 +5357,13 @@ func (m *CronTaskMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *CronTaskMutation) SetCreatedAt(i int) { +func (m *CronTaskMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *CronTaskMutation) CreatedAt() (r int, exists bool) { +func (m *CronTaskMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -5374,7 +5374,7 @@ func (m *CronTaskMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -5389,7 +5389,7 @@ func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v int, err error) } // AddCreatedAt adds i to the "created_at" field. -func (m *CronTaskMutation) AddCreatedAt(i int) { +func (m *CronTaskMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -5398,7 +5398,7 @@ func (m *CronTaskMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *CronTaskMutation) AddedCreatedAt() (r int, exists bool) { +func (m *CronTaskMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -5413,13 +5413,13 @@ func (m *CronTaskMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *CronTaskMutation) SetUpdatedAt(i int) { +func (m *CronTaskMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *CronTaskMutation) UpdatedAt() (r int, exists bool) { +func (m *CronTaskMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -5430,7 +5430,7 @@ func (m *CronTaskMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -5445,7 +5445,7 @@ func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v int, err error) } // AddUpdatedAt adds i to the "updated_at" field. -func (m *CronTaskMutation) AddUpdatedAt(i int) { +func (m *CronTaskMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -5454,7 +5454,7 @@ func (m *CronTaskMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *CronTaskMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *CronTaskMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -5469,12 +5469,12 @@ func (m *CronTaskMutation) ResetUpdatedAt() { } // SetType sets the "type" field. -func (m *CronTaskMutation) SetType(s string) { - m._type = &s +func (m *CronTaskMutation) SetType(mt models.TaskType) { + m._type = &mt } // GetType returns the value of the "type" field in the mutation. -func (m *CronTaskMutation) GetType() (r string, exists bool) { +func (m *CronTaskMutation) GetType() (r models.TaskType, exists bool) { v := m._type if v == nil { return @@ -5485,7 +5485,7 @@ func (m *CronTaskMutation) GetType() (r string, exists bool) { // OldType returns the old "type" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldType(ctx context.Context) (v string, err error) { +func (m *CronTaskMutation) OldType(ctx context.Context) (v models.TaskType, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldType is only allowed on UpdateOne operations") } @@ -5592,12 +5592,12 @@ func (m *CronTaskMutation) ResetTargets() { } // SetStatus sets the "status" field. -func (m *CronTaskMutation) SetStatus(s string) { - m.status = &s +func (m *CronTaskMutation) SetStatus(ms models.TaskStatus) { + m.status = &ms } // Status returns the value of the "status" field in the mutation. -func (m *CronTaskMutation) Status() (r string, exists bool) { +func (m *CronTaskMutation) Status() (r models.TaskStatus, exists bool) { v := m.status if v == nil { return @@ -5608,7 +5608,7 @@ func (m *CronTaskMutation) Status() (r string, exists bool) { // OldStatus returns the old "status" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldStatus(ctx context.Context) (v string, err error) { +func (m *CronTaskMutation) OldStatus(ctx context.Context) (v models.TaskStatus, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldStatus is only allowed on UpdateOne operations") } @@ -5628,13 +5628,13 @@ func (m *CronTaskMutation) ResetStatus() { } // SetScheduledAfter sets the "scheduled_after" field. -func (m *CronTaskMutation) SetScheduledAfter(i int) { +func (m *CronTaskMutation) SetScheduledAfter(i int64) { m.scheduled_after = &i m.addscheduled_after = nil } // ScheduledAfter returns the value of the "scheduled_after" field in the mutation. -func (m *CronTaskMutation) ScheduledAfter() (r int, exists bool) { +func (m *CronTaskMutation) ScheduledAfter() (r int64, exists bool) { v := m.scheduled_after if v == nil { return @@ -5645,7 +5645,7 @@ func (m *CronTaskMutation) ScheduledAfter() (r int, exists bool) { // OldScheduledAfter returns the old "scheduled_after" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldScheduledAfter(ctx context.Context) (v int, err error) { +func (m *CronTaskMutation) OldScheduledAfter(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldScheduledAfter is only allowed on UpdateOne operations") } @@ -5660,7 +5660,7 @@ func (m *CronTaskMutation) OldScheduledAfter(ctx context.Context) (v int, err er } // AddScheduledAfter adds i to the "scheduled_after" field. -func (m *CronTaskMutation) AddScheduledAfter(i int) { +func (m *CronTaskMutation) AddScheduledAfter(i int64) { if m.addscheduled_after != nil { *m.addscheduled_after += i } else { @@ -5669,7 +5669,7 @@ func (m *CronTaskMutation) AddScheduledAfter(i int) { } // AddedScheduledAfter returns the value that was added to the "scheduled_after" field in this mutation. -func (m *CronTaskMutation) AddedScheduledAfter() (r int, exists bool) { +func (m *CronTaskMutation) AddedScheduledAfter() (r int64, exists bool) { v := m.addscheduled_after if v == nil { return @@ -5684,13 +5684,13 @@ func (m *CronTaskMutation) ResetScheduledAfter() { } // SetLastRun sets the "last_run" field. -func (m *CronTaskMutation) SetLastRun(i int) { +func (m *CronTaskMutation) SetLastRun(i int64) { m.last_run = &i m.addlast_run = nil } // LastRun returns the value of the "last_run" field in the mutation. -func (m *CronTaskMutation) LastRun() (r int, exists bool) { +func (m *CronTaskMutation) LastRun() (r int64, exists bool) { v := m.last_run if v == nil { return @@ -5701,7 +5701,7 @@ func (m *CronTaskMutation) LastRun() (r int, exists bool) { // OldLastRun returns the old "last_run" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldLastRun(ctx context.Context) (v int, err error) { +func (m *CronTaskMutation) OldLastRun(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastRun is only allowed on UpdateOne operations") } @@ -5716,7 +5716,7 @@ func (m *CronTaskMutation) OldLastRun(ctx context.Context) (v int, err error) { } // AddLastRun adds i to the "last_run" field. -func (m *CronTaskMutation) AddLastRun(i int) { +func (m *CronTaskMutation) AddLastRun(i int64) { if m.addlast_run != nil { *m.addlast_run += i } else { @@ -5725,7 +5725,7 @@ func (m *CronTaskMutation) AddLastRun(i int) { } // AddedLastRun returns the value that was added to the "last_run" field in this mutation. -func (m *CronTaskMutation) AddedLastRun() (r int, exists bool) { +func (m *CronTaskMutation) AddedLastRun() (r int64, exists bool) { v := m.addlast_run if v == nil { return @@ -5958,21 +5958,21 @@ func (m *CronTaskMutation) OldField(ctx context.Context, name string) (ent.Value func (m *CronTaskMutation) SetField(name string, value ent.Value) error { switch name { case crontask.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case crontask.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUpdatedAt(v) return nil case crontask.FieldType: - v, ok := value.(string) + v, ok := value.(models.TaskType) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -5993,21 +5993,21 @@ func (m *CronTaskMutation) SetField(name string, value ent.Value) error { m.SetTargets(v) return nil case crontask.FieldStatus: - v, ok := value.(string) + v, ok := value.(models.TaskStatus) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetStatus(v) return nil case crontask.FieldScheduledAfter: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetScheduledAfter(v) return nil case crontask.FieldLastRun: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -6073,28 +6073,28 @@ func (m *CronTaskMutation) AddedField(name string) (ent.Value, bool) { func (m *CronTaskMutation) AddField(name string, value ent.Value) error { switch name { case crontask.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case crontask.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddUpdatedAt(v) return nil case crontask.FieldScheduledAfter: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddScheduledAfter(v) return nil case crontask.FieldLastRun: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -6215,10 +6215,10 @@ type UserMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 permissions *string feature_flags *[]string appendfeature_flags []string @@ -6342,13 +6342,13 @@ func (m *UserMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *UserMutation) SetCreatedAt(i int) { +func (m *UserMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *UserMutation) CreatedAt() (r int, exists bool) { +func (m *UserMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -6359,7 +6359,7 @@ func (m *UserMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the User entity. // If the User object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *UserMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -6374,7 +6374,7 @@ func (m *UserMutation) OldCreatedAt(ctx context.Context) (v int, err error) { } // AddCreatedAt adds i to the "created_at" field. -func (m *UserMutation) AddCreatedAt(i int) { +func (m *UserMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -6383,7 +6383,7 @@ func (m *UserMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *UserMutation) AddedCreatedAt() (r int, exists bool) { +func (m *UserMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -6398,13 +6398,13 @@ func (m *UserMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *UserMutation) SetUpdatedAt(i int) { +func (m *UserMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *UserMutation) UpdatedAt() (r int, exists bool) { +func (m *UserMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -6415,7 +6415,7 @@ func (m *UserMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the User entity. // If the User object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *UserMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -6430,7 +6430,7 @@ func (m *UserMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { } // AddUpdatedAt adds i to the "updated_at" field. -func (m *UserMutation) AddUpdatedAt(i int) { +func (m *UserMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -6439,7 +6439,7 @@ func (m *UserMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *UserMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *UserMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -6806,14 +6806,14 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er func (m *UserMutation) SetField(name string, value ent.Value) error { switch name { case user.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case user.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -6869,14 +6869,14 @@ func (m *UserMutation) AddedField(name string) (ent.Value, bool) { func (m *UserMutation) AddField(name string, value ent.Value) error { switch name { case user.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case user.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -7076,10 +7076,10 @@ type UserConnectionMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 _type *models.ConnectionType reference_id *string permissions *string @@ -7197,13 +7197,13 @@ func (m *UserConnectionMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *UserConnectionMutation) SetCreatedAt(i int) { +func (m *UserConnectionMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *UserConnectionMutation) CreatedAt() (r int, exists bool) { +func (m *UserConnectionMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -7214,7 +7214,7 @@ func (m *UserConnectionMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the UserConnection entity. // If the UserConnection object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserConnectionMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *UserConnectionMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -7229,7 +7229,7 @@ func (m *UserConnectionMutation) OldCreatedAt(ctx context.Context) (v int, err e } // AddCreatedAt adds i to the "created_at" field. -func (m *UserConnectionMutation) AddCreatedAt(i int) { +func (m *UserConnectionMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -7238,7 +7238,7 @@ func (m *UserConnectionMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *UserConnectionMutation) AddedCreatedAt() (r int, exists bool) { +func (m *UserConnectionMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -7253,13 +7253,13 @@ func (m *UserConnectionMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *UserConnectionMutation) SetUpdatedAt(i int) { +func (m *UserConnectionMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *UserConnectionMutation) UpdatedAt() (r int, exists bool) { +func (m *UserConnectionMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -7270,7 +7270,7 @@ func (m *UserConnectionMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the UserConnection entity. // If the UserConnection object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserConnectionMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *UserConnectionMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -7285,7 +7285,7 @@ func (m *UserConnectionMutation) OldUpdatedAt(ctx context.Context) (v int, err e } // AddUpdatedAt adds i to the "updated_at" field. -func (m *UserConnectionMutation) AddUpdatedAt(i int) { +func (m *UserConnectionMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -7294,7 +7294,7 @@ func (m *UserConnectionMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *UserConnectionMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *UserConnectionMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -7652,14 +7652,14 @@ func (m *UserConnectionMutation) OldField(ctx context.Context, name string) (ent func (m *UserConnectionMutation) SetField(name string, value ent.Value) error { switch name { case userconnection.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case userconnection.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -7736,14 +7736,14 @@ func (m *UserConnectionMutation) AddedField(name string) (ent.Value, bool) { func (m *UserConnectionMutation) AddField(name string, value ent.Value) error { switch name { case userconnection.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case userconnection.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -7896,10 +7896,10 @@ type UserContentMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 _type *models.UserContentType reference_id *string value *any @@ -8017,13 +8017,13 @@ func (m *UserContentMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *UserContentMutation) SetCreatedAt(i int) { +func (m *UserContentMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *UserContentMutation) CreatedAt() (r int, exists bool) { +func (m *UserContentMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -8034,7 +8034,7 @@ func (m *UserContentMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the UserContent entity. // If the UserContent object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserContentMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *UserContentMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -8049,7 +8049,7 @@ func (m *UserContentMutation) OldCreatedAt(ctx context.Context) (v int, err erro } // AddCreatedAt adds i to the "created_at" field. -func (m *UserContentMutation) AddCreatedAt(i int) { +func (m *UserContentMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -8058,7 +8058,7 @@ func (m *UserContentMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *UserContentMutation) AddedCreatedAt() (r int, exists bool) { +func (m *UserContentMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -8073,13 +8073,13 @@ func (m *UserContentMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *UserContentMutation) SetUpdatedAt(i int) { +func (m *UserContentMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *UserContentMutation) UpdatedAt() (r int, exists bool) { +func (m *UserContentMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -8090,7 +8090,7 @@ func (m *UserContentMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the UserContent entity. // If the UserContent object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserContentMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *UserContentMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -8105,7 +8105,7 @@ func (m *UserContentMutation) OldUpdatedAt(ctx context.Context) (v int, err erro } // AddUpdatedAt adds i to the "updated_at" field. -func (m *UserContentMutation) AddUpdatedAt(i int) { +func (m *UserContentMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -8114,7 +8114,7 @@ func (m *UserContentMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *UserContentMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *UserContentMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -8446,14 +8446,14 @@ func (m *UserContentMutation) OldField(ctx context.Context, name string) (ent.Va func (m *UserContentMutation) SetField(name string, value ent.Value) error { switch name { case usercontent.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case usercontent.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -8530,14 +8530,14 @@ func (m *UserContentMutation) AddedField(name string) (ent.Value, bool) { func (m *UserContentMutation) AddField(name string, value ent.Value) error { switch name { case usercontent.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case usercontent.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -8675,13 +8675,13 @@ type UserSubscriptionMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 _type *models.SubscriptionType - expires_at *int - addexpires_at *int + expires_at *int64 + addexpires_at *int64 permissions *string reference_id *string clearedFields map[string]struct{} @@ -8797,13 +8797,13 @@ func (m *UserSubscriptionMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *UserSubscriptionMutation) SetCreatedAt(i int) { +func (m *UserSubscriptionMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *UserSubscriptionMutation) CreatedAt() (r int, exists bool) { +func (m *UserSubscriptionMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -8814,7 +8814,7 @@ func (m *UserSubscriptionMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the UserSubscription entity. // If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserSubscriptionMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *UserSubscriptionMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -8829,7 +8829,7 @@ func (m *UserSubscriptionMutation) OldCreatedAt(ctx context.Context) (v int, err } // AddCreatedAt adds i to the "created_at" field. -func (m *UserSubscriptionMutation) AddCreatedAt(i int) { +func (m *UserSubscriptionMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -8838,7 +8838,7 @@ func (m *UserSubscriptionMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *UserSubscriptionMutation) AddedCreatedAt() (r int, exists bool) { +func (m *UserSubscriptionMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -8853,13 +8853,13 @@ func (m *UserSubscriptionMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *UserSubscriptionMutation) SetUpdatedAt(i int) { +func (m *UserSubscriptionMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *UserSubscriptionMutation) UpdatedAt() (r int, exists bool) { +func (m *UserSubscriptionMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -8870,7 +8870,7 @@ func (m *UserSubscriptionMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the UserSubscription entity. // If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserSubscriptionMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *UserSubscriptionMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -8885,7 +8885,7 @@ func (m *UserSubscriptionMutation) OldUpdatedAt(ctx context.Context) (v int, err } // AddUpdatedAt adds i to the "updated_at" field. -func (m *UserSubscriptionMutation) AddUpdatedAt(i int) { +func (m *UserSubscriptionMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -8894,7 +8894,7 @@ func (m *UserSubscriptionMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *UserSubscriptionMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *UserSubscriptionMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -8945,13 +8945,13 @@ func (m *UserSubscriptionMutation) ResetType() { } // SetExpiresAt sets the "expires_at" field. -func (m *UserSubscriptionMutation) SetExpiresAt(i int) { +func (m *UserSubscriptionMutation) SetExpiresAt(i int64) { m.expires_at = &i m.addexpires_at = nil } // ExpiresAt returns the value of the "expires_at" field in the mutation. -func (m *UserSubscriptionMutation) ExpiresAt() (r int, exists bool) { +func (m *UserSubscriptionMutation) ExpiresAt() (r int64, exists bool) { v := m.expires_at if v == nil { return @@ -8962,7 +8962,7 @@ func (m *UserSubscriptionMutation) ExpiresAt() (r int, exists bool) { // OldExpiresAt returns the old "expires_at" field's value of the UserSubscription entity. // If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserSubscriptionMutation) OldExpiresAt(ctx context.Context) (v int, err error) { +func (m *UserSubscriptionMutation) OldExpiresAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations") } @@ -8977,7 +8977,7 @@ func (m *UserSubscriptionMutation) OldExpiresAt(ctx context.Context) (v int, err } // AddExpiresAt adds i to the "expires_at" field. -func (m *UserSubscriptionMutation) AddExpiresAt(i int) { +func (m *UserSubscriptionMutation) AddExpiresAt(i int64) { if m.addexpires_at != nil { *m.addexpires_at += i } else { @@ -8986,7 +8986,7 @@ func (m *UserSubscriptionMutation) AddExpiresAt(i int) { } // AddedExpiresAt returns the value that was added to the "expires_at" field in this mutation. -func (m *UserSubscriptionMutation) AddedExpiresAt() (r int, exists bool) { +func (m *UserSubscriptionMutation) AddedExpiresAt() (r int64, exists bool) { v := m.addexpires_at if v == nil { return @@ -9246,14 +9246,14 @@ func (m *UserSubscriptionMutation) OldField(ctx context.Context, name string) (e func (m *UserSubscriptionMutation) SetField(name string, value ent.Value) error { switch name { case usersubscription.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case usersubscription.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -9267,7 +9267,7 @@ func (m *UserSubscriptionMutation) SetField(name string, value ent.Value) error m.SetType(v) return nil case usersubscription.FieldExpiresAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -9335,21 +9335,21 @@ func (m *UserSubscriptionMutation) AddedField(name string) (ent.Value, bool) { func (m *UserSubscriptionMutation) AddField(name string, value ent.Value) error { switch name { case usersubscription.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case usersubscription.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddUpdatedAt(v) return nil case usersubscription.FieldExpiresAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -9487,10 +9487,10 @@ type VehicleMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 tier *int addtier *int localized_names *map[string]string @@ -9605,13 +9605,13 @@ func (m *VehicleMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *VehicleMutation) SetCreatedAt(i int) { +func (m *VehicleMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *VehicleMutation) CreatedAt() (r int, exists bool) { +func (m *VehicleMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -9622,7 +9622,7 @@ func (m *VehicleMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the Vehicle entity. // If the Vehicle object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *VehicleMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -9637,7 +9637,7 @@ func (m *VehicleMutation) OldCreatedAt(ctx context.Context) (v int, err error) { } // AddCreatedAt adds i to the "created_at" field. -func (m *VehicleMutation) AddCreatedAt(i int) { +func (m *VehicleMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -9646,7 +9646,7 @@ func (m *VehicleMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *VehicleMutation) AddedCreatedAt() (r int, exists bool) { +func (m *VehicleMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -9661,13 +9661,13 @@ func (m *VehicleMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *VehicleMutation) SetUpdatedAt(i int) { +func (m *VehicleMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *VehicleMutation) UpdatedAt() (r int, exists bool) { +func (m *VehicleMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -9678,7 +9678,7 @@ func (m *VehicleMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the Vehicle entity. // If the Vehicle object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *VehicleMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -9693,7 +9693,7 @@ func (m *VehicleMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { } // AddUpdatedAt adds i to the "updated_at" field. -func (m *VehicleMutation) AddUpdatedAt(i int) { +func (m *VehicleMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -9702,7 +9702,7 @@ func (m *VehicleMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *VehicleMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *VehicleMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -9898,14 +9898,14 @@ func (m *VehicleMutation) OldField(ctx context.Context, name string) (ent.Value, func (m *VehicleMutation) SetField(name string, value ent.Value) error { switch name { case vehicle.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case vehicle.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -9966,14 +9966,14 @@ func (m *VehicleMutation) AddedField(name string) (ent.Value, bool) { func (m *VehicleMutation) AddField(name string, value ent.Value) error { switch name { case vehicle.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case vehicle.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -10083,11 +10083,11 @@ type VehicleAverageMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int - data *map[string]frame.StatsFrame + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 + data *frame.StatsFrame clearedFields map[string]struct{} done bool oldValue func(context.Context) (*VehicleAverage, error) @@ -10199,13 +10199,13 @@ func (m *VehicleAverageMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *VehicleAverageMutation) SetCreatedAt(i int) { +func (m *VehicleAverageMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *VehicleAverageMutation) CreatedAt() (r int, exists bool) { +func (m *VehicleAverageMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -10216,7 +10216,7 @@ func (m *VehicleAverageMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the VehicleAverage entity. // If the VehicleAverage object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleAverageMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *VehicleAverageMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -10231,7 +10231,7 @@ func (m *VehicleAverageMutation) OldCreatedAt(ctx context.Context) (v int, err e } // AddCreatedAt adds i to the "created_at" field. -func (m *VehicleAverageMutation) AddCreatedAt(i int) { +func (m *VehicleAverageMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -10240,7 +10240,7 @@ func (m *VehicleAverageMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *VehicleAverageMutation) AddedCreatedAt() (r int, exists bool) { +func (m *VehicleAverageMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -10255,13 +10255,13 @@ func (m *VehicleAverageMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *VehicleAverageMutation) SetUpdatedAt(i int) { +func (m *VehicleAverageMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *VehicleAverageMutation) UpdatedAt() (r int, exists bool) { +func (m *VehicleAverageMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -10272,7 +10272,7 @@ func (m *VehicleAverageMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the VehicleAverage entity. // If the VehicleAverage object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleAverageMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *VehicleAverageMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -10287,7 +10287,7 @@ func (m *VehicleAverageMutation) OldUpdatedAt(ctx context.Context) (v int, err e } // AddUpdatedAt adds i to the "updated_at" field. -func (m *VehicleAverageMutation) AddUpdatedAt(i int) { +func (m *VehicleAverageMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -10296,7 +10296,7 @@ func (m *VehicleAverageMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *VehicleAverageMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *VehicleAverageMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -10311,12 +10311,12 @@ func (m *VehicleAverageMutation) ResetUpdatedAt() { } // SetData sets the "data" field. -func (m *VehicleAverageMutation) SetData(mf map[string]frame.StatsFrame) { - m.data = &mf +func (m *VehicleAverageMutation) SetData(ff frame.StatsFrame) { + m.data = &ff } // Data returns the value of the "data" field in the mutation. -func (m *VehicleAverageMutation) Data() (r map[string]frame.StatsFrame, exists bool) { +func (m *VehicleAverageMutation) Data() (r frame.StatsFrame, exists bool) { v := m.data if v == nil { return @@ -10327,7 +10327,7 @@ func (m *VehicleAverageMutation) Data() (r map[string]frame.StatsFrame, exists b // OldData returns the old "data" field's value of the VehicleAverage entity. // If the VehicleAverage object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleAverageMutation) OldData(ctx context.Context) (v map[string]frame.StatsFrame, err error) { +func (m *VehicleAverageMutation) OldData(ctx context.Context) (v frame.StatsFrame, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldData is only allowed on UpdateOne operations") } @@ -10429,21 +10429,21 @@ func (m *VehicleAverageMutation) OldField(ctx context.Context, name string) (ent func (m *VehicleAverageMutation) SetField(name string, value ent.Value) error { switch name { case vehicleaverage.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case vehicleaverage.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUpdatedAt(v) return nil case vehicleaverage.FieldData: - v, ok := value.(map[string]frame.StatsFrame) + v, ok := value.(frame.StatsFrame) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -10485,14 +10485,14 @@ func (m *VehicleAverageMutation) AddedField(name string) (ent.Value, bool) { func (m *VehicleAverageMutation) AddField(name string, value ent.Value) error { switch name { case vehicleaverage.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case vehicleaverage.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -10592,17 +10592,17 @@ type VehicleSnapshotMutation struct { op Op typ string id *string - created_at *int - addcreated_at *int - updated_at *int - addupdated_at *int + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 _type *models.SnapshotType vehicle_id *string reference_id *string battles *int addbattles *int - last_battle_time *int - addlast_battle_time *int + last_battle_time *int64 + addlast_battle_time *int64 frame *frame.StatsFrame clearedFields map[string]struct{} account *string @@ -10717,13 +10717,13 @@ func (m *VehicleSnapshotMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *VehicleSnapshotMutation) SetCreatedAt(i int) { +func (m *VehicleSnapshotMutation) SetCreatedAt(i int64) { m.created_at = &i m.addcreated_at = nil } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *VehicleSnapshotMutation) CreatedAt() (r int, exists bool) { +func (m *VehicleSnapshotMutation) CreatedAt() (r int64, exists bool) { v := m.created_at if v == nil { return @@ -10734,7 +10734,7 @@ func (m *VehicleSnapshotMutation) CreatedAt() (r int, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the VehicleSnapshot entity. // If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleSnapshotMutation) OldCreatedAt(ctx context.Context) (v int, err error) { +func (m *VehicleSnapshotMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -10749,7 +10749,7 @@ func (m *VehicleSnapshotMutation) OldCreatedAt(ctx context.Context) (v int, err } // AddCreatedAt adds i to the "created_at" field. -func (m *VehicleSnapshotMutation) AddCreatedAt(i int) { +func (m *VehicleSnapshotMutation) AddCreatedAt(i int64) { if m.addcreated_at != nil { *m.addcreated_at += i } else { @@ -10758,7 +10758,7 @@ func (m *VehicleSnapshotMutation) AddCreatedAt(i int) { } // AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *VehicleSnapshotMutation) AddedCreatedAt() (r int, exists bool) { +func (m *VehicleSnapshotMutation) AddedCreatedAt() (r int64, exists bool) { v := m.addcreated_at if v == nil { return @@ -10773,13 +10773,13 @@ func (m *VehicleSnapshotMutation) ResetCreatedAt() { } // SetUpdatedAt sets the "updated_at" field. -func (m *VehicleSnapshotMutation) SetUpdatedAt(i int) { +func (m *VehicleSnapshotMutation) SetUpdatedAt(i int64) { m.updated_at = &i m.addupdated_at = nil } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *VehicleSnapshotMutation) UpdatedAt() (r int, exists bool) { +func (m *VehicleSnapshotMutation) UpdatedAt() (r int64, exists bool) { v := m.updated_at if v == nil { return @@ -10790,7 +10790,7 @@ func (m *VehicleSnapshotMutation) UpdatedAt() (r int, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the VehicleSnapshot entity. // If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int, err error) { +func (m *VehicleSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -10805,7 +10805,7 @@ func (m *VehicleSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int, err } // AddUpdatedAt adds i to the "updated_at" field. -func (m *VehicleSnapshotMutation) AddUpdatedAt(i int) { +func (m *VehicleSnapshotMutation) AddUpdatedAt(i int64) { if m.addupdated_at != nil { *m.addupdated_at += i } else { @@ -10814,7 +10814,7 @@ func (m *VehicleSnapshotMutation) AddUpdatedAt(i int) { } // AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *VehicleSnapshotMutation) AddedUpdatedAt() (r int, exists bool) { +func (m *VehicleSnapshotMutation) AddedUpdatedAt() (r int64, exists bool) { v := m.addupdated_at if v == nil { return @@ -11029,13 +11029,13 @@ func (m *VehicleSnapshotMutation) ResetBattles() { } // SetLastBattleTime sets the "last_battle_time" field. -func (m *VehicleSnapshotMutation) SetLastBattleTime(i int) { +func (m *VehicleSnapshotMutation) SetLastBattleTime(i int64) { m.last_battle_time = &i m.addlast_battle_time = nil } // LastBattleTime returns the value of the "last_battle_time" field in the mutation. -func (m *VehicleSnapshotMutation) LastBattleTime() (r int, exists bool) { +func (m *VehicleSnapshotMutation) LastBattleTime() (r int64, exists bool) { v := m.last_battle_time if v == nil { return @@ -11046,7 +11046,7 @@ func (m *VehicleSnapshotMutation) LastBattleTime() (r int, exists bool) { // OldLastBattleTime returns the old "last_battle_time" field's value of the VehicleSnapshot entity. // If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int, err error) { +func (m *VehicleSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") } @@ -11061,7 +11061,7 @@ func (m *VehicleSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int, } // AddLastBattleTime adds i to the "last_battle_time" field. -func (m *VehicleSnapshotMutation) AddLastBattleTime(i int) { +func (m *VehicleSnapshotMutation) AddLastBattleTime(i int64) { if m.addlast_battle_time != nil { *m.addlast_battle_time += i } else { @@ -11070,7 +11070,7 @@ func (m *VehicleSnapshotMutation) AddLastBattleTime(i int) { } // AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. -func (m *VehicleSnapshotMutation) AddedLastBattleTime() (r int, exists bool) { +func (m *VehicleSnapshotMutation) AddedLastBattleTime() (r int64, exists bool) { v := m.addlast_battle_time if v == nil { return @@ -11272,14 +11272,14 @@ func (m *VehicleSnapshotMutation) OldField(ctx context.Context, name string) (en func (m *VehicleSnapshotMutation) SetField(name string, value ent.Value) error { switch name { case vehiclesnapshot.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case vehiclesnapshot.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -11321,7 +11321,7 @@ func (m *VehicleSnapshotMutation) SetField(name string, value ent.Value) error { m.SetBattles(v) return nil case vehiclesnapshot.FieldLastBattleTime: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -11380,14 +11380,14 @@ func (m *VehicleSnapshotMutation) AddedField(name string) (ent.Value, bool) { func (m *VehicleSnapshotMutation) AddField(name string, value ent.Value) error { switch name { case vehiclesnapshot.FieldCreatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddCreatedAt(v) return nil case vehiclesnapshot.FieldUpdatedAt: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -11401,7 +11401,7 @@ func (m *VehicleSnapshotMutation) AddField(name string, value ent.Value) error { m.AddBattles(v) return nil case vehiclesnapshot.FieldLastBattleTime: - v, ok := value.(int) + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } diff --git a/internal/database/ent/db/runtime.go b/internal/database/ent/db/runtime.go index 15bfe7e8..2b639c03 100644 --- a/internal/database/ent/db/runtime.go +++ b/internal/database/ent/db/runtime.go @@ -29,13 +29,13 @@ func init() { // accountDescCreatedAt is the schema descriptor for created_at field. accountDescCreatedAt := accountFields[1].Descriptor() // account.DefaultCreatedAt holds the default value on creation for the created_at field. - account.DefaultCreatedAt = accountDescCreatedAt.Default.(func() int) + account.DefaultCreatedAt = accountDescCreatedAt.Default.(func() int64) // accountDescUpdatedAt is the schema descriptor for updated_at field. accountDescUpdatedAt := accountFields[2].Descriptor() // account.DefaultUpdatedAt holds the default value on creation for the updated_at field. - account.DefaultUpdatedAt = accountDescUpdatedAt.Default.(func() int) + account.DefaultUpdatedAt = accountDescUpdatedAt.Default.(func() int64) // account.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - account.UpdateDefaultUpdatedAt = accountDescUpdatedAt.UpdateDefault.(func() int) + account.UpdateDefaultUpdatedAt = accountDescUpdatedAt.UpdateDefault.(func() int64) // accountDescRealm is the schema descriptor for realm field. accountDescRealm := accountFields[5].Descriptor() // account.RealmValidator is a validator for the "realm" field. It is called by the builders before save. @@ -67,13 +67,13 @@ func init() { // accountsnapshotDescCreatedAt is the schema descriptor for created_at field. accountsnapshotDescCreatedAt := accountsnapshotFields[1].Descriptor() // accountsnapshot.DefaultCreatedAt holds the default value on creation for the created_at field. - accountsnapshot.DefaultCreatedAt = accountsnapshotDescCreatedAt.Default.(func() int) + accountsnapshot.DefaultCreatedAt = accountsnapshotDescCreatedAt.Default.(func() int64) // accountsnapshotDescUpdatedAt is the schema descriptor for updated_at field. accountsnapshotDescUpdatedAt := accountsnapshotFields[2].Descriptor() // accountsnapshot.DefaultUpdatedAt holds the default value on creation for the updated_at field. - accountsnapshot.DefaultUpdatedAt = accountsnapshotDescUpdatedAt.Default.(func() int) + accountsnapshot.DefaultUpdatedAt = accountsnapshotDescUpdatedAt.Default.(func() int64) // accountsnapshot.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - accountsnapshot.UpdateDefaultUpdatedAt = accountsnapshotDescUpdatedAt.UpdateDefault.(func() int) + accountsnapshot.UpdateDefaultUpdatedAt = accountsnapshotDescUpdatedAt.UpdateDefault.(func() int64) // accountsnapshotDescAccountID is the schema descriptor for account_id field. accountsnapshotDescAccountID := accountsnapshotFields[5].Descriptor() // accountsnapshot.AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. @@ -91,13 +91,13 @@ func init() { // achievementssnapshotDescCreatedAt is the schema descriptor for created_at field. achievementssnapshotDescCreatedAt := achievementssnapshotFields[1].Descriptor() // achievementssnapshot.DefaultCreatedAt holds the default value on creation for the created_at field. - achievementssnapshot.DefaultCreatedAt = achievementssnapshotDescCreatedAt.Default.(func() int) + achievementssnapshot.DefaultCreatedAt = achievementssnapshotDescCreatedAt.Default.(func() int64) // achievementssnapshotDescUpdatedAt is the schema descriptor for updated_at field. achievementssnapshotDescUpdatedAt := achievementssnapshotFields[2].Descriptor() // achievementssnapshot.DefaultUpdatedAt holds the default value on creation for the updated_at field. - achievementssnapshot.DefaultUpdatedAt = achievementssnapshotDescUpdatedAt.Default.(func() int) + achievementssnapshot.DefaultUpdatedAt = achievementssnapshotDescUpdatedAt.Default.(func() int64) // achievementssnapshot.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - achievementssnapshot.UpdateDefaultUpdatedAt = achievementssnapshotDescUpdatedAt.UpdateDefault.(func() int) + achievementssnapshot.UpdateDefaultUpdatedAt = achievementssnapshotDescUpdatedAt.UpdateDefault.(func() int64) // achievementssnapshotDescAccountID is the schema descriptor for account_id field. achievementssnapshotDescAccountID := achievementssnapshotFields[4].Descriptor() // achievementssnapshot.AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. @@ -115,13 +115,13 @@ func init() { // appconfigurationDescCreatedAt is the schema descriptor for created_at field. appconfigurationDescCreatedAt := appconfigurationFields[1].Descriptor() // appconfiguration.DefaultCreatedAt holds the default value on creation for the created_at field. - appconfiguration.DefaultCreatedAt = appconfigurationDescCreatedAt.Default.(func() int) + appconfiguration.DefaultCreatedAt = appconfigurationDescCreatedAt.Default.(func() int64) // appconfigurationDescUpdatedAt is the schema descriptor for updated_at field. appconfigurationDescUpdatedAt := appconfigurationFields[2].Descriptor() // appconfiguration.DefaultUpdatedAt holds the default value on creation for the updated_at field. - appconfiguration.DefaultUpdatedAt = appconfigurationDescUpdatedAt.Default.(func() int) + appconfiguration.DefaultUpdatedAt = appconfigurationDescUpdatedAt.Default.(func() int64) // appconfiguration.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - appconfiguration.UpdateDefaultUpdatedAt = appconfigurationDescUpdatedAt.UpdateDefault.(func() int) + appconfiguration.UpdateDefaultUpdatedAt = appconfigurationDescUpdatedAt.UpdateDefault.(func() int64) // appconfigurationDescKey is the schema descriptor for key field. appconfigurationDescKey := appconfigurationFields[3].Descriptor() // appconfiguration.KeyValidator is a validator for the "key" field. It is called by the builders before save. @@ -135,13 +135,13 @@ func init() { // applicationcommandDescCreatedAt is the schema descriptor for created_at field. applicationcommandDescCreatedAt := applicationcommandFields[1].Descriptor() // applicationcommand.DefaultCreatedAt holds the default value on creation for the created_at field. - applicationcommand.DefaultCreatedAt = applicationcommandDescCreatedAt.Default.(func() int) + applicationcommand.DefaultCreatedAt = applicationcommandDescCreatedAt.Default.(func() int64) // applicationcommandDescUpdatedAt is the schema descriptor for updated_at field. applicationcommandDescUpdatedAt := applicationcommandFields[2].Descriptor() // applicationcommand.DefaultUpdatedAt holds the default value on creation for the updated_at field. - applicationcommand.DefaultUpdatedAt = applicationcommandDescUpdatedAt.Default.(func() int) + applicationcommand.DefaultUpdatedAt = applicationcommandDescUpdatedAt.Default.(func() int64) // applicationcommand.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - applicationcommand.UpdateDefaultUpdatedAt = applicationcommandDescUpdatedAt.UpdateDefault.(func() int) + applicationcommand.UpdateDefaultUpdatedAt = applicationcommandDescUpdatedAt.UpdateDefault.(func() int64) // applicationcommandDescName is the schema descriptor for name field. applicationcommandDescName := applicationcommandFields[3].Descriptor() // applicationcommand.NameValidator is a validator for the "name" field. It is called by the builders before save. @@ -163,13 +163,13 @@ func init() { // clanDescCreatedAt is the schema descriptor for created_at field. clanDescCreatedAt := clanFields[1].Descriptor() // clan.DefaultCreatedAt holds the default value on creation for the created_at field. - clan.DefaultCreatedAt = clanDescCreatedAt.Default.(func() int) + clan.DefaultCreatedAt = clanDescCreatedAt.Default.(func() int64) // clanDescUpdatedAt is the schema descriptor for updated_at field. clanDescUpdatedAt := clanFields[2].Descriptor() // clan.DefaultUpdatedAt holds the default value on creation for the updated_at field. - clan.DefaultUpdatedAt = clanDescUpdatedAt.Default.(func() int) + clan.DefaultUpdatedAt = clanDescUpdatedAt.Default.(func() int64) // clan.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - clan.UpdateDefaultUpdatedAt = clanDescUpdatedAt.UpdateDefault.(func() int) + clan.UpdateDefaultUpdatedAt = clanDescUpdatedAt.UpdateDefault.(func() int64) // clanDescTag is the schema descriptor for tag field. clanDescTag := clanFields[3].Descriptor() // clan.TagValidator is a validator for the "tag" field. It is called by the builders before save. @@ -187,25 +187,17 @@ func init() { // crontaskDescCreatedAt is the schema descriptor for created_at field. crontaskDescCreatedAt := crontaskFields[1].Descriptor() // crontask.DefaultCreatedAt holds the default value on creation for the created_at field. - crontask.DefaultCreatedAt = crontaskDescCreatedAt.Default.(func() int) + crontask.DefaultCreatedAt = crontaskDescCreatedAt.Default.(func() int64) // crontaskDescUpdatedAt is the schema descriptor for updated_at field. crontaskDescUpdatedAt := crontaskFields[2].Descriptor() // crontask.DefaultUpdatedAt holds the default value on creation for the updated_at field. - crontask.DefaultUpdatedAt = crontaskDescUpdatedAt.Default.(func() int) + crontask.DefaultUpdatedAt = crontaskDescUpdatedAt.Default.(func() int64) // crontask.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - crontask.UpdateDefaultUpdatedAt = crontaskDescUpdatedAt.UpdateDefault.(func() int) - // crontaskDescType is the schema descriptor for type field. - crontaskDescType := crontaskFields[3].Descriptor() - // crontask.TypeValidator is a validator for the "type" field. It is called by the builders before save. - crontask.TypeValidator = crontaskDescType.Validators[0].(func(string) error) + crontask.UpdateDefaultUpdatedAt = crontaskDescUpdatedAt.UpdateDefault.(func() int64) // crontaskDescReferenceID is the schema descriptor for reference_id field. crontaskDescReferenceID := crontaskFields[4].Descriptor() // crontask.ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. crontask.ReferenceIDValidator = crontaskDescReferenceID.Validators[0].(func(string) error) - // crontaskDescStatus is the schema descriptor for status field. - crontaskDescStatus := crontaskFields[6].Descriptor() - // crontask.StatusValidator is a validator for the "status" field. It is called by the builders before save. - crontask.StatusValidator = crontaskDescStatus.Validators[0].(func(string) error) // crontaskDescID is the schema descriptor for id field. crontaskDescID := crontaskFields[0].Descriptor() // crontask.DefaultID holds the default value on creation for the id field. @@ -215,13 +207,13 @@ func init() { // userDescCreatedAt is the schema descriptor for created_at field. userDescCreatedAt := userFields[1].Descriptor() // user.DefaultCreatedAt holds the default value on creation for the created_at field. - user.DefaultCreatedAt = userDescCreatedAt.Default.(func() int) + user.DefaultCreatedAt = userDescCreatedAt.Default.(func() int64) // userDescUpdatedAt is the schema descriptor for updated_at field. userDescUpdatedAt := userFields[2].Descriptor() // user.DefaultUpdatedAt holds the default value on creation for the updated_at field. - user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() int) + user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() int64) // user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() int) + user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() int64) // userDescPermissions is the schema descriptor for permissions field. userDescPermissions := userFields[3].Descriptor() // user.DefaultPermissions holds the default value on creation for the permissions field. @@ -231,13 +223,13 @@ func init() { // userconnectionDescCreatedAt is the schema descriptor for created_at field. userconnectionDescCreatedAt := userconnectionFields[1].Descriptor() // userconnection.DefaultCreatedAt holds the default value on creation for the created_at field. - userconnection.DefaultCreatedAt = userconnectionDescCreatedAt.Default.(func() int) + userconnection.DefaultCreatedAt = userconnectionDescCreatedAt.Default.(func() int64) // userconnectionDescUpdatedAt is the schema descriptor for updated_at field. userconnectionDescUpdatedAt := userconnectionFields[2].Descriptor() // userconnection.DefaultUpdatedAt holds the default value on creation for the updated_at field. - userconnection.DefaultUpdatedAt = userconnectionDescUpdatedAt.Default.(func() int) + userconnection.DefaultUpdatedAt = userconnectionDescUpdatedAt.Default.(func() int64) // userconnection.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - userconnection.UpdateDefaultUpdatedAt = userconnectionDescUpdatedAt.UpdateDefault.(func() int) + userconnection.UpdateDefaultUpdatedAt = userconnectionDescUpdatedAt.UpdateDefault.(func() int64) // userconnectionDescPermissions is the schema descriptor for permissions field. userconnectionDescPermissions := userconnectionFields[6].Descriptor() // userconnection.DefaultPermissions holds the default value on creation for the permissions field. @@ -251,13 +243,13 @@ func init() { // usercontentDescCreatedAt is the schema descriptor for created_at field. usercontentDescCreatedAt := usercontentFields[1].Descriptor() // usercontent.DefaultCreatedAt holds the default value on creation for the created_at field. - usercontent.DefaultCreatedAt = usercontentDescCreatedAt.Default.(func() int) + usercontent.DefaultCreatedAt = usercontentDescCreatedAt.Default.(func() int64) // usercontentDescUpdatedAt is the schema descriptor for updated_at field. usercontentDescUpdatedAt := usercontentFields[2].Descriptor() // usercontent.DefaultUpdatedAt holds the default value on creation for the updated_at field. - usercontent.DefaultUpdatedAt = usercontentDescUpdatedAt.Default.(func() int) + usercontent.DefaultUpdatedAt = usercontentDescUpdatedAt.Default.(func() int64) // usercontent.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - usercontent.UpdateDefaultUpdatedAt = usercontentDescUpdatedAt.UpdateDefault.(func() int) + usercontent.UpdateDefaultUpdatedAt = usercontentDescUpdatedAt.UpdateDefault.(func() int64) // usercontentDescID is the schema descriptor for id field. usercontentDescID := usercontentFields[0].Descriptor() // usercontent.DefaultID holds the default value on creation for the id field. @@ -267,13 +259,13 @@ func init() { // usersubscriptionDescCreatedAt is the schema descriptor for created_at field. usersubscriptionDescCreatedAt := usersubscriptionFields[1].Descriptor() // usersubscription.DefaultCreatedAt holds the default value on creation for the created_at field. - usersubscription.DefaultCreatedAt = usersubscriptionDescCreatedAt.Default.(func() int) + usersubscription.DefaultCreatedAt = usersubscriptionDescCreatedAt.Default.(func() int64) // usersubscriptionDescUpdatedAt is the schema descriptor for updated_at field. usersubscriptionDescUpdatedAt := usersubscriptionFields[2].Descriptor() // usersubscription.DefaultUpdatedAt holds the default value on creation for the updated_at field. - usersubscription.DefaultUpdatedAt = usersubscriptionDescUpdatedAt.Default.(func() int) + usersubscription.DefaultUpdatedAt = usersubscriptionDescUpdatedAt.Default.(func() int64) // usersubscription.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - usersubscription.UpdateDefaultUpdatedAt = usersubscriptionDescUpdatedAt.UpdateDefault.(func() int) + usersubscription.UpdateDefaultUpdatedAt = usersubscriptionDescUpdatedAt.UpdateDefault.(func() int64) // usersubscriptionDescUserID is the schema descriptor for user_id field. usersubscriptionDescUserID := usersubscriptionFields[5].Descriptor() // usersubscription.UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. @@ -295,13 +287,13 @@ func init() { // vehicleDescCreatedAt is the schema descriptor for created_at field. vehicleDescCreatedAt := vehicleFields[1].Descriptor() // vehicle.DefaultCreatedAt holds the default value on creation for the created_at field. - vehicle.DefaultCreatedAt = vehicleDescCreatedAt.Default.(func() int) + vehicle.DefaultCreatedAt = vehicleDescCreatedAt.Default.(func() int64) // vehicleDescUpdatedAt is the schema descriptor for updated_at field. vehicleDescUpdatedAt := vehicleFields[2].Descriptor() // vehicle.DefaultUpdatedAt holds the default value on creation for the updated_at field. - vehicle.DefaultUpdatedAt = vehicleDescUpdatedAt.Default.(func() int) + vehicle.DefaultUpdatedAt = vehicleDescUpdatedAt.Default.(func() int64) // vehicle.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - vehicle.UpdateDefaultUpdatedAt = vehicleDescUpdatedAt.UpdateDefault.(func() int) + vehicle.UpdateDefaultUpdatedAt = vehicleDescUpdatedAt.UpdateDefault.(func() int64) // vehicleDescTier is the schema descriptor for tier field. vehicleDescTier := vehicleFields[3].Descriptor() // vehicle.TierValidator is a validator for the "tier" field. It is called by the builders before save. @@ -325,25 +317,25 @@ func init() { // vehicleaverageDescCreatedAt is the schema descriptor for created_at field. vehicleaverageDescCreatedAt := vehicleaverageFields[1].Descriptor() // vehicleaverage.DefaultCreatedAt holds the default value on creation for the created_at field. - vehicleaverage.DefaultCreatedAt = vehicleaverageDescCreatedAt.Default.(func() int) + vehicleaverage.DefaultCreatedAt = vehicleaverageDescCreatedAt.Default.(func() int64) // vehicleaverageDescUpdatedAt is the schema descriptor for updated_at field. vehicleaverageDescUpdatedAt := vehicleaverageFields[2].Descriptor() // vehicleaverage.DefaultUpdatedAt holds the default value on creation for the updated_at field. - vehicleaverage.DefaultUpdatedAt = vehicleaverageDescUpdatedAt.Default.(func() int) + vehicleaverage.DefaultUpdatedAt = vehicleaverageDescUpdatedAt.Default.(func() int64) // vehicleaverage.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - vehicleaverage.UpdateDefaultUpdatedAt = vehicleaverageDescUpdatedAt.UpdateDefault.(func() int) + vehicleaverage.UpdateDefaultUpdatedAt = vehicleaverageDescUpdatedAt.UpdateDefault.(func() int64) vehiclesnapshotFields := schema.VehicleSnapshot{}.Fields() _ = vehiclesnapshotFields // vehiclesnapshotDescCreatedAt is the schema descriptor for created_at field. vehiclesnapshotDescCreatedAt := vehiclesnapshotFields[1].Descriptor() // vehiclesnapshot.DefaultCreatedAt holds the default value on creation for the created_at field. - vehiclesnapshot.DefaultCreatedAt = vehiclesnapshotDescCreatedAt.Default.(func() int) + vehiclesnapshot.DefaultCreatedAt = vehiclesnapshotDescCreatedAt.Default.(func() int64) // vehiclesnapshotDescUpdatedAt is the schema descriptor for updated_at field. vehiclesnapshotDescUpdatedAt := vehiclesnapshotFields[2].Descriptor() // vehiclesnapshot.DefaultUpdatedAt holds the default value on creation for the updated_at field. - vehiclesnapshot.DefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.Default.(func() int) + vehiclesnapshot.DefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.Default.(func() int64) // vehiclesnapshot.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - vehiclesnapshot.UpdateDefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.UpdateDefault.(func() int) + vehiclesnapshot.UpdateDefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.UpdateDefault.(func() int64) // vehiclesnapshotDescAccountID is the schema descriptor for account_id field. vehiclesnapshotDescAccountID := vehiclesnapshotFields[4].Descriptor() // vehiclesnapshot.AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. diff --git a/internal/database/ent/db/user.go b/internal/database/ent/db/user.go index f62dd2bb..e8a51556 100644 --- a/internal/database/ent/db/user.go +++ b/internal/database/ent/db/user.go @@ -18,9 +18,9 @@ type User struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Permissions holds the value of the "permissions" field. Permissions string `json:"permissions,omitempty"` // FeatureFlags holds the value of the "feature_flags" field. @@ -107,13 +107,13 @@ func (u *User) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - u.CreatedAt = int(value.Int64) + u.CreatedAt = value.Int64 } case user.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - u.UpdatedAt = int(value.Int64) + u.UpdatedAt = value.Int64 } case user.FieldPermissions: if value, ok := values[i].(*sql.NullString); !ok { diff --git a/internal/database/ent/db/user/user.go b/internal/database/ent/db/user/user.go index 9ee9bd12..b16e3e67 100644 --- a/internal/database/ent/db/user/user.go +++ b/internal/database/ent/db/user/user.go @@ -72,11 +72,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // DefaultPermissions holds the default value on creation for the "permissions" field. DefaultPermissions string ) diff --git a/internal/database/ent/db/user/where.go b/internal/database/ent/db/user/where.go index a0abc0ee..cf752542 100644 --- a/internal/database/ent/db/user/where.go +++ b/internal/database/ent/db/user/where.go @@ -64,12 +64,12 @@ func IDContainsFold(id string) predicate.User { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.User { +func CreatedAt(v int64) predicate.User { return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.User { +func UpdatedAt(v int64) predicate.User { return predicate.User(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -79,82 +79,82 @@ func Permissions(v string) predicate.User { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.User { +func CreatedAtEQ(v int64) predicate.User { return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.User { +func CreatedAtNEQ(v int64) predicate.User { return predicate.User(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.User { +func CreatedAtIn(vs ...int64) predicate.User { return predicate.User(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.User { +func CreatedAtNotIn(vs ...int64) predicate.User { return predicate.User(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.User { +func CreatedAtGT(v int64) predicate.User { return predicate.User(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.User { +func CreatedAtGTE(v int64) predicate.User { return predicate.User(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.User { +func CreatedAtLT(v int64) predicate.User { return predicate.User(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.User { +func CreatedAtLTE(v int64) predicate.User { return predicate.User(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.User { +func UpdatedAtEQ(v int64) predicate.User { return predicate.User(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.User { +func UpdatedAtNEQ(v int64) predicate.User { return predicate.User(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.User { +func UpdatedAtIn(vs ...int64) predicate.User { return predicate.User(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.User { +func UpdatedAtNotIn(vs ...int64) predicate.User { return predicate.User(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.User { +func UpdatedAtGT(v int64) predicate.User { return predicate.User(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.User { +func UpdatedAtGTE(v int64) predicate.User { return predicate.User(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.User { +func UpdatedAtLT(v int64) predicate.User { return predicate.User(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.User { +func UpdatedAtLTE(v int64) predicate.User { return predicate.User(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/user_create.go b/internal/database/ent/db/user_create.go index a5916908..1db7f19a 100644 --- a/internal/database/ent/db/user_create.go +++ b/internal/database/ent/db/user_create.go @@ -23,13 +23,13 @@ type UserCreate struct { } // SetCreatedAt sets the "created_at" field. -func (uc *UserCreate) SetCreatedAt(i int) *UserCreate { +func (uc *UserCreate) SetCreatedAt(i int64) *UserCreate { uc.mutation.SetCreatedAt(i) return uc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (uc *UserCreate) SetNillableCreatedAt(i *int) *UserCreate { +func (uc *UserCreate) SetNillableCreatedAt(i *int64) *UserCreate { if i != nil { uc.SetCreatedAt(*i) } @@ -37,13 +37,13 @@ func (uc *UserCreate) SetNillableCreatedAt(i *int) *UserCreate { } // SetUpdatedAt sets the "updated_at" field. -func (uc *UserCreate) SetUpdatedAt(i int) *UserCreate { +func (uc *UserCreate) SetUpdatedAt(i int64) *UserCreate { uc.mutation.SetUpdatedAt(i) return uc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (uc *UserCreate) SetNillableUpdatedAt(i *int) *UserCreate { +func (uc *UserCreate) SetNillableUpdatedAt(i *int64) *UserCreate { if i != nil { uc.SetUpdatedAt(*i) } @@ -217,11 +217,11 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := uc.mutation.CreatedAt(); ok { - _spec.SetField(user.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(user.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := uc.mutation.UpdatedAt(); ok { - _spec.SetField(user.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(user.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := uc.mutation.Permissions(); ok { diff --git a/internal/database/ent/db/user_query.go b/internal/database/ent/db/user_query.go index 5fef02bf..83ee3ea8 100644 --- a/internal/database/ent/db/user_query.go +++ b/internal/database/ent/db/user_query.go @@ -370,7 +370,7 @@ func (uq *UserQuery) WithContent(opts ...func(*UserContentQuery)) *UserQuery { // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -393,7 +393,7 @@ func (uq *UserQuery) GroupBy(field string, fields ...string) *UserGroupBy { // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.User.Query(). diff --git a/internal/database/ent/db/user_update.go b/internal/database/ent/db/user_update.go index 4d7932c2..d4ac1622 100644 --- a/internal/database/ent/db/user_update.go +++ b/internal/database/ent/db/user_update.go @@ -32,14 +32,14 @@ func (uu *UserUpdate) Where(ps ...predicate.User) *UserUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (uu *UserUpdate) SetUpdatedAt(i int) *UserUpdate { +func (uu *UserUpdate) SetUpdatedAt(i int64) *UserUpdate { uu.mutation.ResetUpdatedAt() uu.mutation.SetUpdatedAt(i) return uu } // AddUpdatedAt adds i to the "updated_at" field. -func (uu *UserUpdate) AddUpdatedAt(i int) *UserUpdate { +func (uu *UserUpdate) AddUpdatedAt(i int64) *UserUpdate { uu.mutation.AddUpdatedAt(i) return uu } @@ -235,10 +235,10 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := uu.mutation.UpdatedAt(); ok { - _spec.SetField(user.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(user.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := uu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(user.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(user.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := uu.mutation.Permissions(); ok { _spec.SetField(user.FieldPermissions, field.TypeString, value) @@ -410,14 +410,14 @@ type UserUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (uuo *UserUpdateOne) SetUpdatedAt(i int) *UserUpdateOne { +func (uuo *UserUpdateOne) SetUpdatedAt(i int64) *UserUpdateOne { uuo.mutation.ResetUpdatedAt() uuo.mutation.SetUpdatedAt(i) return uuo } // AddUpdatedAt adds i to the "updated_at" field. -func (uuo *UserUpdateOne) AddUpdatedAt(i int) *UserUpdateOne { +func (uuo *UserUpdateOne) AddUpdatedAt(i int64) *UserUpdateOne { uuo.mutation.AddUpdatedAt(i) return uuo } @@ -643,10 +643,10 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) } } if value, ok := uuo.mutation.UpdatedAt(); ok { - _spec.SetField(user.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(user.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := uuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(user.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(user.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := uuo.mutation.Permissions(); ok { _spec.SetField(user.FieldPermissions, field.TypeString, value) diff --git a/internal/database/ent/db/userconnection.go b/internal/database/ent/db/userconnection.go index c5b03752..100553b6 100644 --- a/internal/database/ent/db/userconnection.go +++ b/internal/database/ent/db/userconnection.go @@ -20,9 +20,9 @@ type UserConnection struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.ConnectionType `json:"type,omitempty"` // UserID holds the value of the "user_id" field. @@ -95,13 +95,13 @@ func (uc *UserConnection) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - uc.CreatedAt = int(value.Int64) + uc.CreatedAt = value.Int64 } case userconnection.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - uc.UpdatedAt = int(value.Int64) + uc.UpdatedAt = value.Int64 } case userconnection.FieldType: if value, ok := values[i].(*sql.NullString); !ok { diff --git a/internal/database/ent/db/userconnection/userconnection.go b/internal/database/ent/db/userconnection/userconnection.go index 1cb8085b..0b1c4114 100644 --- a/internal/database/ent/db/userconnection/userconnection.go +++ b/internal/database/ent/db/userconnection/userconnection.go @@ -66,11 +66,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // DefaultPermissions holds the default value on creation for the "permissions" field. DefaultPermissions string // DefaultID holds the default value on creation for the "id" field. diff --git a/internal/database/ent/db/userconnection/where.go b/internal/database/ent/db/userconnection/where.go index 6d1f373a..9726b22e 100644 --- a/internal/database/ent/db/userconnection/where.go +++ b/internal/database/ent/db/userconnection/where.go @@ -65,12 +65,12 @@ func IDContainsFold(id string) predicate.UserConnection { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.UserConnection { +func CreatedAt(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.UserConnection { +func UpdatedAt(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -90,82 +90,82 @@ func Permissions(v string) predicate.UserConnection { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.UserConnection { +func CreatedAtEQ(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.UserConnection { +func CreatedAtNEQ(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.UserConnection { +func CreatedAtIn(vs ...int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.UserConnection { +func CreatedAtNotIn(vs ...int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.UserConnection { +func CreatedAtGT(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.UserConnection { +func CreatedAtGTE(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.UserConnection { +func CreatedAtLT(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.UserConnection { +func CreatedAtLTE(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.UserConnection { +func UpdatedAtEQ(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.UserConnection { +func UpdatedAtNEQ(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.UserConnection { +func UpdatedAtIn(vs ...int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.UserConnection { +func UpdatedAtNotIn(vs ...int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.UserConnection { +func UpdatedAtGT(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.UserConnection { +func UpdatedAtGTE(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.UserConnection { +func UpdatedAtLT(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.UserConnection { +func UpdatedAtLTE(v int64) predicate.UserConnection { return predicate.UserConnection(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/userconnection_create.go b/internal/database/ent/db/userconnection_create.go index ee25aacf..8478c906 100644 --- a/internal/database/ent/db/userconnection_create.go +++ b/internal/database/ent/db/userconnection_create.go @@ -22,13 +22,13 @@ type UserConnectionCreate struct { } // SetCreatedAt sets the "created_at" field. -func (ucc *UserConnectionCreate) SetCreatedAt(i int) *UserConnectionCreate { +func (ucc *UserConnectionCreate) SetCreatedAt(i int64) *UserConnectionCreate { ucc.mutation.SetCreatedAt(i) return ucc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (ucc *UserConnectionCreate) SetNillableCreatedAt(i *int) *UserConnectionCreate { +func (ucc *UserConnectionCreate) SetNillableCreatedAt(i *int64) *UserConnectionCreate { if i != nil { ucc.SetCreatedAt(*i) } @@ -36,13 +36,13 @@ func (ucc *UserConnectionCreate) SetNillableCreatedAt(i *int) *UserConnectionCre } // SetUpdatedAt sets the "updated_at" field. -func (ucc *UserConnectionCreate) SetUpdatedAt(i int) *UserConnectionCreate { +func (ucc *UserConnectionCreate) SetUpdatedAt(i int64) *UserConnectionCreate { ucc.mutation.SetUpdatedAt(i) return ucc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (ucc *UserConnectionCreate) SetNillableUpdatedAt(i *int) *UserConnectionCreate { +func (ucc *UserConnectionCreate) SetNillableUpdatedAt(i *int64) *UserConnectionCreate { if i != nil { ucc.SetUpdatedAt(*i) } @@ -220,11 +220,11 @@ func (ucc *UserConnectionCreate) createSpec() (*UserConnection, *sqlgraph.Create _spec.ID.Value = id } if value, ok := ucc.mutation.CreatedAt(); ok { - _spec.SetField(userconnection.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(userconnection.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := ucc.mutation.UpdatedAt(); ok { - _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := ucc.mutation.GetType(); ok { diff --git a/internal/database/ent/db/userconnection_query.go b/internal/database/ent/db/userconnection_query.go index 020530b6..164e8e87 100644 --- a/internal/database/ent/db/userconnection_query.go +++ b/internal/database/ent/db/userconnection_query.go @@ -297,7 +297,7 @@ func (ucq *UserConnectionQuery) WithUser(opts ...func(*UserQuery)) *UserConnecti // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +320,7 @@ func (ucq *UserConnectionQuery) GroupBy(field string, fields ...string) *UserCon // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.UserConnection.Query(). diff --git a/internal/database/ent/db/userconnection_update.go b/internal/database/ent/db/userconnection_update.go index d7985deb..8744515f 100644 --- a/internal/database/ent/db/userconnection_update.go +++ b/internal/database/ent/db/userconnection_update.go @@ -29,14 +29,14 @@ func (ucu *UserConnectionUpdate) Where(ps ...predicate.UserConnection) *UserConn } // SetUpdatedAt sets the "updated_at" field. -func (ucu *UserConnectionUpdate) SetUpdatedAt(i int) *UserConnectionUpdate { +func (ucu *UserConnectionUpdate) SetUpdatedAt(i int64) *UserConnectionUpdate { ucu.mutation.ResetUpdatedAt() ucu.mutation.SetUpdatedAt(i) return ucu } // AddUpdatedAt adds i to the "updated_at" field. -func (ucu *UserConnectionUpdate) AddUpdatedAt(i int) *UserConnectionUpdate { +func (ucu *UserConnectionUpdate) AddUpdatedAt(i int64) *UserConnectionUpdate { ucu.mutation.AddUpdatedAt(i) return ucu } @@ -168,10 +168,10 @@ func (ucu *UserConnectionUpdate) sqlSave(ctx context.Context) (n int, err error) } } if value, ok := ucu.mutation.UpdatedAt(); ok { - _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ucu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(userconnection.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(userconnection.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ucu.mutation.GetType(); ok { _spec.SetField(userconnection.FieldType, field.TypeEnum, value) @@ -212,14 +212,14 @@ type UserConnectionUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (ucuo *UserConnectionUpdateOne) SetUpdatedAt(i int) *UserConnectionUpdateOne { +func (ucuo *UserConnectionUpdateOne) SetUpdatedAt(i int64) *UserConnectionUpdateOne { ucuo.mutation.ResetUpdatedAt() ucuo.mutation.SetUpdatedAt(i) return ucuo } // AddUpdatedAt adds i to the "updated_at" field. -func (ucuo *UserConnectionUpdateOne) AddUpdatedAt(i int) *UserConnectionUpdateOne { +func (ucuo *UserConnectionUpdateOne) AddUpdatedAt(i int64) *UserConnectionUpdateOne { ucuo.mutation.AddUpdatedAt(i) return ucuo } @@ -381,10 +381,10 @@ func (ucuo *UserConnectionUpdateOne) sqlSave(ctx context.Context) (_node *UserCo } } if value, ok := ucuo.mutation.UpdatedAt(); ok { - _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ucuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(userconnection.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(userconnection.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ucuo.mutation.GetType(); ok { _spec.SetField(userconnection.FieldType, field.TypeEnum, value) diff --git a/internal/database/ent/db/usercontent.go b/internal/database/ent/db/usercontent.go index f9c1b079..dfa12961 100644 --- a/internal/database/ent/db/usercontent.go +++ b/internal/database/ent/db/usercontent.go @@ -20,9 +20,9 @@ type UserContent struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.UserContentType `json:"type,omitempty"` // UserID holds the value of the "user_id" field. @@ -95,13 +95,13 @@ func (uc *UserContent) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - uc.CreatedAt = int(value.Int64) + uc.CreatedAt = value.Int64 } case usercontent.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - uc.UpdatedAt = int(value.Int64) + uc.UpdatedAt = value.Int64 } case usercontent.FieldType: if value, ok := values[i].(*sql.NullString); !ok { diff --git a/internal/database/ent/db/usercontent/usercontent.go b/internal/database/ent/db/usercontent/usercontent.go index c3ea1314..89a1d190 100644 --- a/internal/database/ent/db/usercontent/usercontent.go +++ b/internal/database/ent/db/usercontent/usercontent.go @@ -66,11 +66,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // DefaultID holds the default value on creation for the "id" field. DefaultID func() string ) diff --git a/internal/database/ent/db/usercontent/where.go b/internal/database/ent/db/usercontent/where.go index 41358635..b8133da2 100644 --- a/internal/database/ent/db/usercontent/where.go +++ b/internal/database/ent/db/usercontent/where.go @@ -65,12 +65,12 @@ func IDContainsFold(id string) predicate.UserContent { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.UserContent { +func CreatedAt(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.UserContent { +func UpdatedAt(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -85,82 +85,82 @@ func ReferenceID(v string) predicate.UserContent { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.UserContent { +func CreatedAtEQ(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.UserContent { +func CreatedAtNEQ(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.UserContent { +func CreatedAtIn(vs ...int64) predicate.UserContent { return predicate.UserContent(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.UserContent { +func CreatedAtNotIn(vs ...int64) predicate.UserContent { return predicate.UserContent(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.UserContent { +func CreatedAtGT(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.UserContent { +func CreatedAtGTE(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.UserContent { +func CreatedAtLT(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.UserContent { +func CreatedAtLTE(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.UserContent { +func UpdatedAtEQ(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.UserContent { +func UpdatedAtNEQ(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.UserContent { +func UpdatedAtIn(vs ...int64) predicate.UserContent { return predicate.UserContent(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.UserContent { +func UpdatedAtNotIn(vs ...int64) predicate.UserContent { return predicate.UserContent(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.UserContent { +func UpdatedAtGT(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.UserContent { +func UpdatedAtGTE(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.UserContent { +func UpdatedAtLT(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.UserContent { +func UpdatedAtLTE(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/usercontent_create.go b/internal/database/ent/db/usercontent_create.go index 50abe9e4..ac65c16b 100644 --- a/internal/database/ent/db/usercontent_create.go +++ b/internal/database/ent/db/usercontent_create.go @@ -22,13 +22,13 @@ type UserContentCreate struct { } // SetCreatedAt sets the "created_at" field. -func (ucc *UserContentCreate) SetCreatedAt(i int) *UserContentCreate { +func (ucc *UserContentCreate) SetCreatedAt(i int64) *UserContentCreate { ucc.mutation.SetCreatedAt(i) return ucc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (ucc *UserContentCreate) SetNillableCreatedAt(i *int) *UserContentCreate { +func (ucc *UserContentCreate) SetNillableCreatedAt(i *int64) *UserContentCreate { if i != nil { ucc.SetCreatedAt(*i) } @@ -36,13 +36,13 @@ func (ucc *UserContentCreate) SetNillableCreatedAt(i *int) *UserContentCreate { } // SetUpdatedAt sets the "updated_at" field. -func (ucc *UserContentCreate) SetUpdatedAt(i int) *UserContentCreate { +func (ucc *UserContentCreate) SetUpdatedAt(i int64) *UserContentCreate { ucc.mutation.SetUpdatedAt(i) return ucc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (ucc *UserContentCreate) SetNillableUpdatedAt(i *int) *UserContentCreate { +func (ucc *UserContentCreate) SetNillableUpdatedAt(i *int64) *UserContentCreate { if i != nil { ucc.SetUpdatedAt(*i) } @@ -214,11 +214,11 @@ func (ucc *UserContentCreate) createSpec() (*UserContent, *sqlgraph.CreateSpec) _spec.ID.Value = id } if value, ok := ucc.mutation.CreatedAt(); ok { - _spec.SetField(usercontent.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(usercontent.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := ucc.mutation.UpdatedAt(); ok { - _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := ucc.mutation.GetType(); ok { diff --git a/internal/database/ent/db/usercontent_query.go b/internal/database/ent/db/usercontent_query.go index 21683730..8c189429 100644 --- a/internal/database/ent/db/usercontent_query.go +++ b/internal/database/ent/db/usercontent_query.go @@ -297,7 +297,7 @@ func (ucq *UserContentQuery) WithUser(opts ...func(*UserQuery)) *UserContentQuer // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +320,7 @@ func (ucq *UserContentQuery) GroupBy(field string, fields ...string) *UserConten // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.UserContent.Query(). diff --git a/internal/database/ent/db/usercontent_update.go b/internal/database/ent/db/usercontent_update.go index a6d031e0..25bb317e 100644 --- a/internal/database/ent/db/usercontent_update.go +++ b/internal/database/ent/db/usercontent_update.go @@ -29,14 +29,14 @@ func (ucu *UserContentUpdate) Where(ps ...predicate.UserContent) *UserContentUpd } // SetUpdatedAt sets the "updated_at" field. -func (ucu *UserContentUpdate) SetUpdatedAt(i int) *UserContentUpdate { +func (ucu *UserContentUpdate) SetUpdatedAt(i int64) *UserContentUpdate { ucu.mutation.ResetUpdatedAt() ucu.mutation.SetUpdatedAt(i) return ucu } // AddUpdatedAt adds i to the "updated_at" field. -func (ucu *UserContentUpdate) AddUpdatedAt(i int) *UserContentUpdate { +func (ucu *UserContentUpdate) AddUpdatedAt(i int64) *UserContentUpdate { ucu.mutation.AddUpdatedAt(i) return ucu } @@ -148,10 +148,10 @@ func (ucu *UserContentUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := ucu.mutation.UpdatedAt(); ok { - _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ucu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(usercontent.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(usercontent.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ucu.mutation.GetType(); ok { _spec.SetField(usercontent.FieldType, field.TypeEnum, value) @@ -186,14 +186,14 @@ type UserContentUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (ucuo *UserContentUpdateOne) SetUpdatedAt(i int) *UserContentUpdateOne { +func (ucuo *UserContentUpdateOne) SetUpdatedAt(i int64) *UserContentUpdateOne { ucuo.mutation.ResetUpdatedAt() ucuo.mutation.SetUpdatedAt(i) return ucuo } // AddUpdatedAt adds i to the "updated_at" field. -func (ucuo *UserContentUpdateOne) AddUpdatedAt(i int) *UserContentUpdateOne { +func (ucuo *UserContentUpdateOne) AddUpdatedAt(i int64) *UserContentUpdateOne { ucuo.mutation.AddUpdatedAt(i) return ucuo } @@ -335,10 +335,10 @@ func (ucuo *UserContentUpdateOne) sqlSave(ctx context.Context) (_node *UserConte } } if value, ok := ucuo.mutation.UpdatedAt(); ok { - _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ucuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(usercontent.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(usercontent.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := ucuo.mutation.GetType(); ok { _spec.SetField(usercontent.FieldType, field.TypeEnum, value) diff --git a/internal/database/ent/db/usersubscription.go b/internal/database/ent/db/usersubscription.go index 573a557e..11cdb283 100644 --- a/internal/database/ent/db/usersubscription.go +++ b/internal/database/ent/db/usersubscription.go @@ -19,13 +19,13 @@ type UserSubscription struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.SubscriptionType `json:"type,omitempty"` // ExpiresAt holds the value of the "expires_at" field. - ExpiresAt int `json:"expires_at,omitempty"` + ExpiresAt int64 `json:"expires_at,omitempty"` // UserID holds the value of the "user_id" field. UserID string `json:"user_id,omitempty"` // Permissions holds the value of the "permissions" field. @@ -92,13 +92,13 @@ func (us *UserSubscription) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - us.CreatedAt = int(value.Int64) + us.CreatedAt = value.Int64 } case usersubscription.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - us.UpdatedAt = int(value.Int64) + us.UpdatedAt = value.Int64 } case usersubscription.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -110,7 +110,7 @@ func (us *UserSubscription) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field expires_at", values[i]) } else if value.Valid { - us.ExpiresAt = int(value.Int64) + us.ExpiresAt = value.Int64 } case usersubscription.FieldUserID: if value, ok := values[i].(*sql.NullString); !ok { diff --git a/internal/database/ent/db/usersubscription/usersubscription.go b/internal/database/ent/db/usersubscription/usersubscription.go index e8a6c86f..5a34f32b 100644 --- a/internal/database/ent/db/usersubscription/usersubscription.go +++ b/internal/database/ent/db/usersubscription/usersubscription.go @@ -66,11 +66,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. UserIDValidator func(string) error // PermissionsValidator is a validator for the "permissions" field. It is called by the builders before save. diff --git a/internal/database/ent/db/usersubscription/where.go b/internal/database/ent/db/usersubscription/where.go index 71f3025c..9111e4f8 100644 --- a/internal/database/ent/db/usersubscription/where.go +++ b/internal/database/ent/db/usersubscription/where.go @@ -65,17 +65,17 @@ func IDContainsFold(id string) predicate.UserSubscription { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.UserSubscription { +func CreatedAt(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.UserSubscription { +func UpdatedAt(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldUpdatedAt, v)) } // ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ. -func ExpiresAt(v int) predicate.UserSubscription { +func ExpiresAt(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldExpiresAt, v)) } @@ -95,82 +95,82 @@ func ReferenceID(v string) predicate.UserSubscription { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.UserSubscription { +func CreatedAtEQ(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.UserSubscription { +func CreatedAtNEQ(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.UserSubscription { +func CreatedAtIn(vs ...int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.UserSubscription { +func CreatedAtNotIn(vs ...int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.UserSubscription { +func CreatedAtGT(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.UserSubscription { +func CreatedAtGTE(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.UserSubscription { +func CreatedAtLT(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.UserSubscription { +func CreatedAtLTE(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.UserSubscription { +func UpdatedAtEQ(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.UserSubscription { +func UpdatedAtNEQ(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.UserSubscription { +func UpdatedAtIn(vs ...int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.UserSubscription { +func UpdatedAtNotIn(vs ...int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.UserSubscription { +func UpdatedAtGT(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.UserSubscription { +func UpdatedAtGTE(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.UserSubscription { +func UpdatedAtLT(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.UserSubscription { +func UpdatedAtLTE(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLTE(FieldUpdatedAt, v)) } @@ -205,42 +205,42 @@ func TypeNotIn(vs ...models.SubscriptionType) predicate.UserSubscription { } // ExpiresAtEQ applies the EQ predicate on the "expires_at" field. -func ExpiresAtEQ(v int) predicate.UserSubscription { +func ExpiresAtEQ(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldExpiresAt, v)) } // ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field. -func ExpiresAtNEQ(v int) predicate.UserSubscription { +func ExpiresAtNEQ(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNEQ(FieldExpiresAt, v)) } // ExpiresAtIn applies the In predicate on the "expires_at" field. -func ExpiresAtIn(vs ...int) predicate.UserSubscription { +func ExpiresAtIn(vs ...int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldIn(FieldExpiresAt, vs...)) } // ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field. -func ExpiresAtNotIn(vs ...int) predicate.UserSubscription { +func ExpiresAtNotIn(vs ...int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNotIn(FieldExpiresAt, vs...)) } // ExpiresAtGT applies the GT predicate on the "expires_at" field. -func ExpiresAtGT(v int) predicate.UserSubscription { +func ExpiresAtGT(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGT(FieldExpiresAt, v)) } // ExpiresAtGTE applies the GTE predicate on the "expires_at" field. -func ExpiresAtGTE(v int) predicate.UserSubscription { +func ExpiresAtGTE(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGTE(FieldExpiresAt, v)) } // ExpiresAtLT applies the LT predicate on the "expires_at" field. -func ExpiresAtLT(v int) predicate.UserSubscription { +func ExpiresAtLT(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLT(FieldExpiresAt, v)) } // ExpiresAtLTE applies the LTE predicate on the "expires_at" field. -func ExpiresAtLTE(v int) predicate.UserSubscription { +func ExpiresAtLTE(v int64) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLTE(FieldExpiresAt, v)) } diff --git a/internal/database/ent/db/usersubscription_create.go b/internal/database/ent/db/usersubscription_create.go index fe9f5eee..64971258 100644 --- a/internal/database/ent/db/usersubscription_create.go +++ b/internal/database/ent/db/usersubscription_create.go @@ -22,13 +22,13 @@ type UserSubscriptionCreate struct { } // SetCreatedAt sets the "created_at" field. -func (usc *UserSubscriptionCreate) SetCreatedAt(i int) *UserSubscriptionCreate { +func (usc *UserSubscriptionCreate) SetCreatedAt(i int64) *UserSubscriptionCreate { usc.mutation.SetCreatedAt(i) return usc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (usc *UserSubscriptionCreate) SetNillableCreatedAt(i *int) *UserSubscriptionCreate { +func (usc *UserSubscriptionCreate) SetNillableCreatedAt(i *int64) *UserSubscriptionCreate { if i != nil { usc.SetCreatedAt(*i) } @@ -36,13 +36,13 @@ func (usc *UserSubscriptionCreate) SetNillableCreatedAt(i *int) *UserSubscriptio } // SetUpdatedAt sets the "updated_at" field. -func (usc *UserSubscriptionCreate) SetUpdatedAt(i int) *UserSubscriptionCreate { +func (usc *UserSubscriptionCreate) SetUpdatedAt(i int64) *UserSubscriptionCreate { usc.mutation.SetUpdatedAt(i) return usc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (usc *UserSubscriptionCreate) SetNillableUpdatedAt(i *int) *UserSubscriptionCreate { +func (usc *UserSubscriptionCreate) SetNillableUpdatedAt(i *int64) *UserSubscriptionCreate { if i != nil { usc.SetUpdatedAt(*i) } @@ -56,7 +56,7 @@ func (usc *UserSubscriptionCreate) SetType(mt models.SubscriptionType) *UserSubs } // SetExpiresAt sets the "expires_at" field. -func (usc *UserSubscriptionCreate) SetExpiresAt(i int) *UserSubscriptionCreate { +func (usc *UserSubscriptionCreate) SetExpiresAt(i int64) *UserSubscriptionCreate { usc.mutation.SetExpiresAt(i) return usc } @@ -229,11 +229,11 @@ func (usc *UserSubscriptionCreate) createSpec() (*UserSubscription, *sqlgraph.Cr _spec.ID.Value = id } if value, ok := usc.mutation.CreatedAt(); ok { - _spec.SetField(usersubscription.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(usersubscription.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := usc.mutation.UpdatedAt(); ok { - _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := usc.mutation.GetType(); ok { @@ -241,7 +241,7 @@ func (usc *UserSubscriptionCreate) createSpec() (*UserSubscription, *sqlgraph.Cr _node.Type = value } if value, ok := usc.mutation.ExpiresAt(); ok { - _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt, value) + _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt64, value) _node.ExpiresAt = value } if value, ok := usc.mutation.Permissions(); ok { diff --git a/internal/database/ent/db/usersubscription_query.go b/internal/database/ent/db/usersubscription_query.go index dc392666..25027d45 100644 --- a/internal/database/ent/db/usersubscription_query.go +++ b/internal/database/ent/db/usersubscription_query.go @@ -297,7 +297,7 @@ func (usq *UserSubscriptionQuery) WithUser(opts ...func(*UserQuery)) *UserSubscr // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +320,7 @@ func (usq *UserSubscriptionQuery) GroupBy(field string, fields ...string) *UserS // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.UserSubscription.Query(). diff --git a/internal/database/ent/db/usersubscription_update.go b/internal/database/ent/db/usersubscription_update.go index ccb7e131..b2806f54 100644 --- a/internal/database/ent/db/usersubscription_update.go +++ b/internal/database/ent/db/usersubscription_update.go @@ -29,14 +29,14 @@ func (usu *UserSubscriptionUpdate) Where(ps ...predicate.UserSubscription) *User } // SetUpdatedAt sets the "updated_at" field. -func (usu *UserSubscriptionUpdate) SetUpdatedAt(i int) *UserSubscriptionUpdate { +func (usu *UserSubscriptionUpdate) SetUpdatedAt(i int64) *UserSubscriptionUpdate { usu.mutation.ResetUpdatedAt() usu.mutation.SetUpdatedAt(i) return usu } // AddUpdatedAt adds i to the "updated_at" field. -func (usu *UserSubscriptionUpdate) AddUpdatedAt(i int) *UserSubscriptionUpdate { +func (usu *UserSubscriptionUpdate) AddUpdatedAt(i int64) *UserSubscriptionUpdate { usu.mutation.AddUpdatedAt(i) return usu } @@ -56,14 +56,14 @@ func (usu *UserSubscriptionUpdate) SetNillableType(mt *models.SubscriptionType) } // SetExpiresAt sets the "expires_at" field. -func (usu *UserSubscriptionUpdate) SetExpiresAt(i int) *UserSubscriptionUpdate { +func (usu *UserSubscriptionUpdate) SetExpiresAt(i int64) *UserSubscriptionUpdate { usu.mutation.ResetExpiresAt() usu.mutation.SetExpiresAt(i) return usu } // SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. -func (usu *UserSubscriptionUpdate) SetNillableExpiresAt(i *int) *UserSubscriptionUpdate { +func (usu *UserSubscriptionUpdate) SetNillableExpiresAt(i *int64) *UserSubscriptionUpdate { if i != nil { usu.SetExpiresAt(*i) } @@ -71,7 +71,7 @@ func (usu *UserSubscriptionUpdate) SetNillableExpiresAt(i *int) *UserSubscriptio } // AddExpiresAt adds i to the "expires_at" field. -func (usu *UserSubscriptionUpdate) AddExpiresAt(i int) *UserSubscriptionUpdate { +func (usu *UserSubscriptionUpdate) AddExpiresAt(i int64) *UserSubscriptionUpdate { usu.mutation.AddExpiresAt(i) return usu } @@ -181,19 +181,19 @@ func (usu *UserSubscriptionUpdate) sqlSave(ctx context.Context) (n int, err erro } } if value, ok := usu.mutation.UpdatedAt(); ok { - _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := usu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := usu.mutation.GetType(); ok { _spec.SetField(usersubscription.FieldType, field.TypeEnum, value) } if value, ok := usu.mutation.ExpiresAt(); ok { - _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt, value) + _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt64, value) } if value, ok := usu.mutation.AddedExpiresAt(); ok { - _spec.AddField(usersubscription.FieldExpiresAt, field.TypeInt, value) + _spec.AddField(usersubscription.FieldExpiresAt, field.TypeInt64, value) } if value, ok := usu.mutation.Permissions(); ok { _spec.SetField(usersubscription.FieldPermissions, field.TypeString, value) @@ -222,14 +222,14 @@ type UserSubscriptionUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (usuo *UserSubscriptionUpdateOne) SetUpdatedAt(i int) *UserSubscriptionUpdateOne { +func (usuo *UserSubscriptionUpdateOne) SetUpdatedAt(i int64) *UserSubscriptionUpdateOne { usuo.mutation.ResetUpdatedAt() usuo.mutation.SetUpdatedAt(i) return usuo } // AddUpdatedAt adds i to the "updated_at" field. -func (usuo *UserSubscriptionUpdateOne) AddUpdatedAt(i int) *UserSubscriptionUpdateOne { +func (usuo *UserSubscriptionUpdateOne) AddUpdatedAt(i int64) *UserSubscriptionUpdateOne { usuo.mutation.AddUpdatedAt(i) return usuo } @@ -249,14 +249,14 @@ func (usuo *UserSubscriptionUpdateOne) SetNillableType(mt *models.SubscriptionTy } // SetExpiresAt sets the "expires_at" field. -func (usuo *UserSubscriptionUpdateOne) SetExpiresAt(i int) *UserSubscriptionUpdateOne { +func (usuo *UserSubscriptionUpdateOne) SetExpiresAt(i int64) *UserSubscriptionUpdateOne { usuo.mutation.ResetExpiresAt() usuo.mutation.SetExpiresAt(i) return usuo } // SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. -func (usuo *UserSubscriptionUpdateOne) SetNillableExpiresAt(i *int) *UserSubscriptionUpdateOne { +func (usuo *UserSubscriptionUpdateOne) SetNillableExpiresAt(i *int64) *UserSubscriptionUpdateOne { if i != nil { usuo.SetExpiresAt(*i) } @@ -264,7 +264,7 @@ func (usuo *UserSubscriptionUpdateOne) SetNillableExpiresAt(i *int) *UserSubscri } // AddExpiresAt adds i to the "expires_at" field. -func (usuo *UserSubscriptionUpdateOne) AddExpiresAt(i int) *UserSubscriptionUpdateOne { +func (usuo *UserSubscriptionUpdateOne) AddExpiresAt(i int64) *UserSubscriptionUpdateOne { usuo.mutation.AddExpiresAt(i) return usuo } @@ -404,19 +404,19 @@ func (usuo *UserSubscriptionUpdateOne) sqlSave(ctx context.Context) (_node *User } } if value, ok := usuo.mutation.UpdatedAt(); ok { - _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := usuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(usersubscription.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := usuo.mutation.GetType(); ok { _spec.SetField(usersubscription.FieldType, field.TypeEnum, value) } if value, ok := usuo.mutation.ExpiresAt(); ok { - _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt, value) + _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt64, value) } if value, ok := usuo.mutation.AddedExpiresAt(); ok { - _spec.AddField(usersubscription.FieldExpiresAt, field.TypeInt, value) + _spec.AddField(usersubscription.FieldExpiresAt, field.TypeInt64, value) } if value, ok := usuo.mutation.Permissions(); ok { _spec.SetField(usersubscription.FieldPermissions, field.TypeString, value) diff --git a/internal/database/ent/db/vehicle.go b/internal/database/ent/db/vehicle.go index 14f5ecf1..639c60e9 100644 --- a/internal/database/ent/db/vehicle.go +++ b/internal/database/ent/db/vehicle.go @@ -18,9 +18,9 @@ type Vehicle struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Tier holds the value of the "tier" field. Tier int `json:"tier,omitempty"` // LocalizedNames holds the value of the "localized_names" field. @@ -64,13 +64,13 @@ func (v *Vehicle) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - v.CreatedAt = int(value.Int64) + v.CreatedAt = value.Int64 } case vehicle.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - v.UpdatedAt = int(value.Int64) + v.UpdatedAt = value.Int64 } case vehicle.FieldTier: if value, ok := values[i].(*sql.NullInt64); !ok { diff --git a/internal/database/ent/db/vehicle/vehicle.go b/internal/database/ent/db/vehicle/vehicle.go index 236f55e0..5e417ea2 100644 --- a/internal/database/ent/db/vehicle/vehicle.go +++ b/internal/database/ent/db/vehicle/vehicle.go @@ -44,11 +44,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // TierValidator is a validator for the "tier" field. It is called by the builders before save. TierValidator func(int) error ) diff --git a/internal/database/ent/db/vehicle/where.go b/internal/database/ent/db/vehicle/where.go index c09e0846..5bf6a6dc 100644 --- a/internal/database/ent/db/vehicle/where.go +++ b/internal/database/ent/db/vehicle/where.go @@ -63,12 +63,12 @@ func IDContainsFold(id string) predicate.Vehicle { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.Vehicle { +func CreatedAt(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.Vehicle { +func UpdatedAt(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -78,82 +78,82 @@ func Tier(v int) predicate.Vehicle { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.Vehicle { +func CreatedAtEQ(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.Vehicle { +func CreatedAtNEQ(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.Vehicle { +func CreatedAtIn(vs ...int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.Vehicle { +func CreatedAtNotIn(vs ...int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.Vehicle { +func CreatedAtGT(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.Vehicle { +func CreatedAtGTE(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.Vehicle { +func CreatedAtLT(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.Vehicle { +func CreatedAtLTE(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.Vehicle { +func UpdatedAtEQ(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.Vehicle { +func UpdatedAtNEQ(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.Vehicle { +func UpdatedAtIn(vs ...int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.Vehicle { +func UpdatedAtNotIn(vs ...int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.Vehicle { +func UpdatedAtGT(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.Vehicle { +func UpdatedAtGTE(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.Vehicle { +func UpdatedAtLT(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.Vehicle { +func UpdatedAtLTE(v int64) predicate.Vehicle { return predicate.Vehicle(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/vehicle_create.go b/internal/database/ent/db/vehicle_create.go index 5487bf7b..b94dc90c 100644 --- a/internal/database/ent/db/vehicle_create.go +++ b/internal/database/ent/db/vehicle_create.go @@ -20,13 +20,13 @@ type VehicleCreate struct { } // SetCreatedAt sets the "created_at" field. -func (vc *VehicleCreate) SetCreatedAt(i int) *VehicleCreate { +func (vc *VehicleCreate) SetCreatedAt(i int64) *VehicleCreate { vc.mutation.SetCreatedAt(i) return vc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (vc *VehicleCreate) SetNillableCreatedAt(i *int) *VehicleCreate { +func (vc *VehicleCreate) SetNillableCreatedAt(i *int64) *VehicleCreate { if i != nil { vc.SetCreatedAt(*i) } @@ -34,13 +34,13 @@ func (vc *VehicleCreate) SetNillableCreatedAt(i *int) *VehicleCreate { } // SetUpdatedAt sets the "updated_at" field. -func (vc *VehicleCreate) SetUpdatedAt(i int) *VehicleCreate { +func (vc *VehicleCreate) SetUpdatedAt(i int64) *VehicleCreate { vc.mutation.SetUpdatedAt(i) return vc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (vc *VehicleCreate) SetNillableUpdatedAt(i *int) *VehicleCreate { +func (vc *VehicleCreate) SetNillableUpdatedAt(i *int64) *VehicleCreate { if i != nil { vc.SetUpdatedAt(*i) } @@ -165,11 +165,11 @@ func (vc *VehicleCreate) createSpec() (*Vehicle, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := vc.mutation.CreatedAt(); ok { - _spec.SetField(vehicle.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(vehicle.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := vc.mutation.UpdatedAt(); ok { - _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := vc.mutation.Tier(); ok { diff --git a/internal/database/ent/db/vehicle_query.go b/internal/database/ent/db/vehicle_query.go index 2eef76b0..6b09ce3b 100644 --- a/internal/database/ent/db/vehicle_query.go +++ b/internal/database/ent/db/vehicle_query.go @@ -261,7 +261,7 @@ func (vq *VehicleQuery) Clone() *VehicleQuery { // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +284,7 @@ func (vq *VehicleQuery) GroupBy(field string, fields ...string) *VehicleGroupBy // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.Vehicle.Query(). diff --git a/internal/database/ent/db/vehicle_update.go b/internal/database/ent/db/vehicle_update.go index 0459f716..f5cec932 100644 --- a/internal/database/ent/db/vehicle_update.go +++ b/internal/database/ent/db/vehicle_update.go @@ -28,14 +28,14 @@ func (vu *VehicleUpdate) Where(ps ...predicate.Vehicle) *VehicleUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (vu *VehicleUpdate) SetUpdatedAt(i int) *VehicleUpdate { +func (vu *VehicleUpdate) SetUpdatedAt(i int64) *VehicleUpdate { vu.mutation.ResetUpdatedAt() vu.mutation.SetUpdatedAt(i) return vu } // AddUpdatedAt adds i to the "updated_at" field. -func (vu *VehicleUpdate) AddUpdatedAt(i int) *VehicleUpdate { +func (vu *VehicleUpdate) AddUpdatedAt(i int64) *VehicleUpdate { vu.mutation.AddUpdatedAt(i) return vu } @@ -131,10 +131,10 @@ func (vu *VehicleUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := vu.mutation.UpdatedAt(); ok { - _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehicle.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(vehicle.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vu.mutation.Tier(); ok { _spec.SetField(vehicle.FieldTier, field.TypeInt, value) @@ -166,14 +166,14 @@ type VehicleUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (vuo *VehicleUpdateOne) SetUpdatedAt(i int) *VehicleUpdateOne { +func (vuo *VehicleUpdateOne) SetUpdatedAt(i int64) *VehicleUpdateOne { vuo.mutation.ResetUpdatedAt() vuo.mutation.SetUpdatedAt(i) return vuo } // AddUpdatedAt adds i to the "updated_at" field. -func (vuo *VehicleUpdateOne) AddUpdatedAt(i int) *VehicleUpdateOne { +func (vuo *VehicleUpdateOne) AddUpdatedAt(i int64) *VehicleUpdateOne { vuo.mutation.AddUpdatedAt(i) return vuo } @@ -299,10 +299,10 @@ func (vuo *VehicleUpdateOne) sqlSave(ctx context.Context) (_node *Vehicle, err e } } if value, ok := vuo.mutation.UpdatedAt(); ok { - _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehicle.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(vehicle.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vuo.mutation.Tier(); ok { _spec.SetField(vehicle.FieldTier, field.TypeInt, value) diff --git a/internal/database/ent/db/vehicleaverage.go b/internal/database/ent/db/vehicleaverage.go index 8b300a7e..f24062ab 100644 --- a/internal/database/ent/db/vehicleaverage.go +++ b/internal/database/ent/db/vehicleaverage.go @@ -19,11 +19,11 @@ type VehicleAverage struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Data holds the value of the "data" field. - Data map[string]frame.StatsFrame `json:"data,omitempty"` + Data frame.StatsFrame `json:"data,omitempty"` selectValues sql.SelectValues } @@ -63,13 +63,13 @@ func (va *VehicleAverage) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - va.CreatedAt = int(value.Int64) + va.CreatedAt = value.Int64 } case vehicleaverage.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - va.UpdatedAt = int(value.Int64) + va.UpdatedAt = value.Int64 } case vehicleaverage.FieldData: if value, ok := values[i].(*[]byte); !ok { diff --git a/internal/database/ent/db/vehicleaverage/vehicleaverage.go b/internal/database/ent/db/vehicleaverage/vehicleaverage.go index 4b90c1bc..ab45c826 100644 --- a/internal/database/ent/db/vehicleaverage/vehicleaverage.go +++ b/internal/database/ent/db/vehicleaverage/vehicleaverage.go @@ -41,11 +41,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 ) // OrderOption defines the ordering options for the VehicleAverage queries. diff --git a/internal/database/ent/db/vehicleaverage/where.go b/internal/database/ent/db/vehicleaverage/where.go index 08934c78..e9f20546 100644 --- a/internal/database/ent/db/vehicleaverage/where.go +++ b/internal/database/ent/db/vehicleaverage/where.go @@ -63,92 +63,92 @@ func IDContainsFold(id string) predicate.VehicleAverage { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.VehicleAverage { +func CreatedAt(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.VehicleAverage { +func UpdatedAt(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldEQ(FieldUpdatedAt, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.VehicleAverage { +func CreatedAtEQ(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.VehicleAverage { +func CreatedAtNEQ(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.VehicleAverage { +func CreatedAtIn(vs ...int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.VehicleAverage { +func CreatedAtNotIn(vs ...int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.VehicleAverage { +func CreatedAtGT(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.VehicleAverage { +func CreatedAtGTE(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.VehicleAverage { +func CreatedAtLT(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.VehicleAverage { +func CreatedAtLTE(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.VehicleAverage { +func UpdatedAtEQ(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.VehicleAverage { +func UpdatedAtNEQ(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.VehicleAverage { +func UpdatedAtIn(vs ...int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.VehicleAverage { +func UpdatedAtNotIn(vs ...int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.VehicleAverage { +func UpdatedAtGT(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.VehicleAverage { +func UpdatedAtGTE(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.VehicleAverage { +func UpdatedAtLT(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.VehicleAverage { +func UpdatedAtLTE(v int64) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/vehicleaverage_create.go b/internal/database/ent/db/vehicleaverage_create.go index 01704d15..1ed5f96c 100644 --- a/internal/database/ent/db/vehicleaverage_create.go +++ b/internal/database/ent/db/vehicleaverage_create.go @@ -21,13 +21,13 @@ type VehicleAverageCreate struct { } // SetCreatedAt sets the "created_at" field. -func (vac *VehicleAverageCreate) SetCreatedAt(i int) *VehicleAverageCreate { +func (vac *VehicleAverageCreate) SetCreatedAt(i int64) *VehicleAverageCreate { vac.mutation.SetCreatedAt(i) return vac } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (vac *VehicleAverageCreate) SetNillableCreatedAt(i *int) *VehicleAverageCreate { +func (vac *VehicleAverageCreate) SetNillableCreatedAt(i *int64) *VehicleAverageCreate { if i != nil { vac.SetCreatedAt(*i) } @@ -35,13 +35,13 @@ func (vac *VehicleAverageCreate) SetNillableCreatedAt(i *int) *VehicleAverageCre } // SetUpdatedAt sets the "updated_at" field. -func (vac *VehicleAverageCreate) SetUpdatedAt(i int) *VehicleAverageCreate { +func (vac *VehicleAverageCreate) SetUpdatedAt(i int64) *VehicleAverageCreate { vac.mutation.SetUpdatedAt(i) return vac } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (vac *VehicleAverageCreate) SetNillableUpdatedAt(i *int) *VehicleAverageCreate { +func (vac *VehicleAverageCreate) SetNillableUpdatedAt(i *int64) *VehicleAverageCreate { if i != nil { vac.SetUpdatedAt(*i) } @@ -49,8 +49,8 @@ func (vac *VehicleAverageCreate) SetNillableUpdatedAt(i *int) *VehicleAverageCre } // SetData sets the "data" field. -func (vac *VehicleAverageCreate) SetData(mf map[string]frame.StatsFrame) *VehicleAverageCreate { - vac.mutation.SetData(mf) +func (vac *VehicleAverageCreate) SetData(ff frame.StatsFrame) *VehicleAverageCreate { + vac.mutation.SetData(ff) return vac } @@ -152,11 +152,11 @@ func (vac *VehicleAverageCreate) createSpec() (*VehicleAverage, *sqlgraph.Create _spec.ID.Value = id } if value, ok := vac.mutation.CreatedAt(); ok { - _spec.SetField(vehicleaverage.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(vehicleaverage.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := vac.mutation.UpdatedAt(); ok { - _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := vac.mutation.Data(); ok { diff --git a/internal/database/ent/db/vehicleaverage_query.go b/internal/database/ent/db/vehicleaverage_query.go index 67b533b4..b5c7891d 100644 --- a/internal/database/ent/db/vehicleaverage_query.go +++ b/internal/database/ent/db/vehicleaverage_query.go @@ -261,7 +261,7 @@ func (vaq *VehicleAverageQuery) Clone() *VehicleAverageQuery { // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +284,7 @@ func (vaq *VehicleAverageQuery) GroupBy(field string, fields ...string) *Vehicle // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.VehicleAverage.Query(). diff --git a/internal/database/ent/db/vehicleaverage_update.go b/internal/database/ent/db/vehicleaverage_update.go index 787df205..199f96c2 100644 --- a/internal/database/ent/db/vehicleaverage_update.go +++ b/internal/database/ent/db/vehicleaverage_update.go @@ -29,21 +29,29 @@ func (vau *VehicleAverageUpdate) Where(ps ...predicate.VehicleAverage) *VehicleA } // SetUpdatedAt sets the "updated_at" field. -func (vau *VehicleAverageUpdate) SetUpdatedAt(i int) *VehicleAverageUpdate { +func (vau *VehicleAverageUpdate) SetUpdatedAt(i int64) *VehicleAverageUpdate { vau.mutation.ResetUpdatedAt() vau.mutation.SetUpdatedAt(i) return vau } // AddUpdatedAt adds i to the "updated_at" field. -func (vau *VehicleAverageUpdate) AddUpdatedAt(i int) *VehicleAverageUpdate { +func (vau *VehicleAverageUpdate) AddUpdatedAt(i int64) *VehicleAverageUpdate { vau.mutation.AddUpdatedAt(i) return vau } // SetData sets the "data" field. -func (vau *VehicleAverageUpdate) SetData(mf map[string]frame.StatsFrame) *VehicleAverageUpdate { - vau.mutation.SetData(mf) +func (vau *VehicleAverageUpdate) SetData(ff frame.StatsFrame) *VehicleAverageUpdate { + vau.mutation.SetData(ff) + return vau +} + +// SetNillableData sets the "data" field if the given value is not nil. +func (vau *VehicleAverageUpdate) SetNillableData(ff *frame.StatsFrame) *VehicleAverageUpdate { + if ff != nil { + vau.SetData(*ff) + } return vau } @@ -98,10 +106,10 @@ func (vau *VehicleAverageUpdate) sqlSave(ctx context.Context) (n int, err error) } } if value, ok := vau.mutation.UpdatedAt(); ok { - _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vau.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vau.mutation.Data(); ok { _spec.SetField(vehicleaverage.FieldData, field.TypeJSON, value) @@ -127,21 +135,29 @@ type VehicleAverageUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (vauo *VehicleAverageUpdateOne) SetUpdatedAt(i int) *VehicleAverageUpdateOne { +func (vauo *VehicleAverageUpdateOne) SetUpdatedAt(i int64) *VehicleAverageUpdateOne { vauo.mutation.ResetUpdatedAt() vauo.mutation.SetUpdatedAt(i) return vauo } // AddUpdatedAt adds i to the "updated_at" field. -func (vauo *VehicleAverageUpdateOne) AddUpdatedAt(i int) *VehicleAverageUpdateOne { +func (vauo *VehicleAverageUpdateOne) AddUpdatedAt(i int64) *VehicleAverageUpdateOne { vauo.mutation.AddUpdatedAt(i) return vauo } // SetData sets the "data" field. -func (vauo *VehicleAverageUpdateOne) SetData(mf map[string]frame.StatsFrame) *VehicleAverageUpdateOne { - vauo.mutation.SetData(mf) +func (vauo *VehicleAverageUpdateOne) SetData(ff frame.StatsFrame) *VehicleAverageUpdateOne { + vauo.mutation.SetData(ff) + return vauo +} + +// SetNillableData sets the "data" field if the given value is not nil. +func (vauo *VehicleAverageUpdateOne) SetNillableData(ff *frame.StatsFrame) *VehicleAverageUpdateOne { + if ff != nil { + vauo.SetData(*ff) + } return vauo } @@ -226,10 +242,10 @@ func (vauo *VehicleAverageUpdateOne) sqlSave(ctx context.Context) (_node *Vehicl } } if value, ok := vauo.mutation.UpdatedAt(); ok { - _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vauo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehicleaverage.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vauo.mutation.Data(); ok { _spec.SetField(vehicleaverage.FieldData, field.TypeJSON, value) diff --git a/internal/database/ent/db/vehiclesnapshot.go b/internal/database/ent/db/vehiclesnapshot.go index ae0d2d10..03565cf0 100644 --- a/internal/database/ent/db/vehiclesnapshot.go +++ b/internal/database/ent/db/vehiclesnapshot.go @@ -21,9 +21,9 @@ type VehicleSnapshot struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int `json:"created_at,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int `json:"updated_at,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.SnapshotType `json:"type,omitempty"` // AccountID holds the value of the "account_id" field. @@ -35,7 +35,7 @@ type VehicleSnapshot struct { // Battles holds the value of the "battles" field. Battles int `json:"battles,omitempty"` // LastBattleTime holds the value of the "last_battle_time" field. - LastBattleTime int `json:"last_battle_time,omitempty"` + LastBattleTime int64 `json:"last_battle_time,omitempty"` // Frame holds the value of the "frame" field. Frame frame.StatsFrame `json:"frame,omitempty"` // Edges holds the relations/edges for other nodes in the graph. @@ -100,13 +100,13 @@ func (vs *VehicleSnapshot) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - vs.CreatedAt = int(value.Int64) + vs.CreatedAt = value.Int64 } case vehiclesnapshot.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - vs.UpdatedAt = int(value.Int64) + vs.UpdatedAt = value.Int64 } case vehiclesnapshot.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -142,7 +142,7 @@ func (vs *VehicleSnapshot) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) } else if value.Valid { - vs.LastBattleTime = int(value.Int64) + vs.LastBattleTime = value.Int64 } case vehiclesnapshot.FieldFrame: if value, ok := values[i].(*[]byte); !ok { diff --git a/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go b/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go index aa8d81ed..0d13bf1a 100644 --- a/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go +++ b/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go @@ -72,11 +72,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int + DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int + DefaultUpdatedAt func() int64 // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int + UpdateDefaultUpdatedAt func() int64 // AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. AccountIDValidator func(string) error // VehicleIDValidator is a validator for the "vehicle_id" field. It is called by the builders before save. diff --git a/internal/database/ent/db/vehiclesnapshot/where.go b/internal/database/ent/db/vehiclesnapshot/where.go index 758156fe..d0489259 100644 --- a/internal/database/ent/db/vehiclesnapshot/where.go +++ b/internal/database/ent/db/vehiclesnapshot/where.go @@ -65,12 +65,12 @@ func IDContainsFold(id string) predicate.VehicleSnapshot { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int) predicate.VehicleSnapshot { +func CreatedAt(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int) predicate.VehicleSnapshot { +func UpdatedAt(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -95,87 +95,87 @@ func Battles(v int) predicate.VehicleSnapshot { } // LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. -func LastBattleTime(v int) predicate.VehicleSnapshot { +func LastBattleTime(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int) predicate.VehicleSnapshot { +func CreatedAtEQ(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int) predicate.VehicleSnapshot { +func CreatedAtNEQ(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int) predicate.VehicleSnapshot { +func CreatedAtIn(vs ...int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int) predicate.VehicleSnapshot { +func CreatedAtNotIn(vs ...int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int) predicate.VehicleSnapshot { +func CreatedAtGT(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int) predicate.VehicleSnapshot { +func CreatedAtGTE(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int) predicate.VehicleSnapshot { +func CreatedAtLT(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int) predicate.VehicleSnapshot { +func CreatedAtLTE(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int) predicate.VehicleSnapshot { +func UpdatedAtEQ(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int) predicate.VehicleSnapshot { +func UpdatedAtNEQ(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int) predicate.VehicleSnapshot { +func UpdatedAtIn(vs ...int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int) predicate.VehicleSnapshot { +func UpdatedAtNotIn(vs ...int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int) predicate.VehicleSnapshot { +func UpdatedAtGT(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int) predicate.VehicleSnapshot { +func UpdatedAtGTE(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int) predicate.VehicleSnapshot { +func UpdatedAtLT(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int) predicate.VehicleSnapshot { +func UpdatedAtLTE(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLTE(FieldUpdatedAt, v)) } @@ -445,42 +445,42 @@ func BattlesLTE(v int) predicate.VehicleSnapshot { } // LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. -func LastBattleTimeEQ(v int) predicate.VehicleSnapshot { +func LastBattleTimeEQ(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. -func LastBattleTimeNEQ(v int) predicate.VehicleSnapshot { +func LastBattleTimeNEQ(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNEQ(FieldLastBattleTime, v)) } // LastBattleTimeIn applies the In predicate on the "last_battle_time" field. -func LastBattleTimeIn(vs ...int) predicate.VehicleSnapshot { +func LastBattleTimeIn(vs ...int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldIn(FieldLastBattleTime, vs...)) } // LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. -func LastBattleTimeNotIn(vs ...int) predicate.VehicleSnapshot { +func LastBattleTimeNotIn(vs ...int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNotIn(FieldLastBattleTime, vs...)) } // LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. -func LastBattleTimeGT(v int) predicate.VehicleSnapshot { +func LastBattleTimeGT(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGT(FieldLastBattleTime, v)) } // LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. -func LastBattleTimeGTE(v int) predicate.VehicleSnapshot { +func LastBattleTimeGTE(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGTE(FieldLastBattleTime, v)) } // LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. -func LastBattleTimeLT(v int) predicate.VehicleSnapshot { +func LastBattleTimeLT(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLT(FieldLastBattleTime, v)) } // LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. -func LastBattleTimeLTE(v int) predicate.VehicleSnapshot { +func LastBattleTimeLTE(v int64) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLTE(FieldLastBattleTime, v)) } diff --git a/internal/database/ent/db/vehiclesnapshot_create.go b/internal/database/ent/db/vehiclesnapshot_create.go index 6120d27b..32c64243 100644 --- a/internal/database/ent/db/vehiclesnapshot_create.go +++ b/internal/database/ent/db/vehiclesnapshot_create.go @@ -23,13 +23,13 @@ type VehicleSnapshotCreate struct { } // SetCreatedAt sets the "created_at" field. -func (vsc *VehicleSnapshotCreate) SetCreatedAt(i int) *VehicleSnapshotCreate { +func (vsc *VehicleSnapshotCreate) SetCreatedAt(i int64) *VehicleSnapshotCreate { vsc.mutation.SetCreatedAt(i) return vsc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (vsc *VehicleSnapshotCreate) SetNillableCreatedAt(i *int) *VehicleSnapshotCreate { +func (vsc *VehicleSnapshotCreate) SetNillableCreatedAt(i *int64) *VehicleSnapshotCreate { if i != nil { vsc.SetCreatedAt(*i) } @@ -37,13 +37,13 @@ func (vsc *VehicleSnapshotCreate) SetNillableCreatedAt(i *int) *VehicleSnapshotC } // SetUpdatedAt sets the "updated_at" field. -func (vsc *VehicleSnapshotCreate) SetUpdatedAt(i int) *VehicleSnapshotCreate { +func (vsc *VehicleSnapshotCreate) SetUpdatedAt(i int64) *VehicleSnapshotCreate { vsc.mutation.SetUpdatedAt(i) return vsc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (vsc *VehicleSnapshotCreate) SetNillableUpdatedAt(i *int) *VehicleSnapshotCreate { +func (vsc *VehicleSnapshotCreate) SetNillableUpdatedAt(i *int64) *VehicleSnapshotCreate { if i != nil { vsc.SetUpdatedAt(*i) } @@ -81,7 +81,7 @@ func (vsc *VehicleSnapshotCreate) SetBattles(i int) *VehicleSnapshotCreate { } // SetLastBattleTime sets the "last_battle_time" field. -func (vsc *VehicleSnapshotCreate) SetLastBattleTime(i int) *VehicleSnapshotCreate { +func (vsc *VehicleSnapshotCreate) SetLastBattleTime(i int64) *VehicleSnapshotCreate { vsc.mutation.SetLastBattleTime(i) return vsc } @@ -248,11 +248,11 @@ func (vsc *VehicleSnapshotCreate) createSpec() (*VehicleSnapshot, *sqlgraph.Crea _spec.ID.Value = id } if value, ok := vsc.mutation.CreatedAt(); ok { - _spec.SetField(vehiclesnapshot.FieldCreatedAt, field.TypeInt, value) + _spec.SetField(vehiclesnapshot.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value } if value, ok := vsc.mutation.UpdatedAt(); ok { - _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value } if value, ok := vsc.mutation.GetType(); ok { @@ -272,7 +272,7 @@ func (vsc *VehicleSnapshotCreate) createSpec() (*VehicleSnapshot, *sqlgraph.Crea _node.Battles = value } if value, ok := vsc.mutation.LastBattleTime(); ok { - _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) _node.LastBattleTime = value } if value, ok := vsc.mutation.Frame(); ok { diff --git a/internal/database/ent/db/vehiclesnapshot_query.go b/internal/database/ent/db/vehiclesnapshot_query.go index 89f20225..c6c9730e 100644 --- a/internal/database/ent/db/vehiclesnapshot_query.go +++ b/internal/database/ent/db/vehiclesnapshot_query.go @@ -297,7 +297,7 @@ func (vsq *VehicleSnapshotQuery) WithAccount(opts ...func(*AccountQuery)) *Vehic // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +320,7 @@ func (vsq *VehicleSnapshotQuery) GroupBy(field string, fields ...string) *Vehicl // Example: // // var v []struct { -// CreatedAt int `json:"created_at,omitempty"` +// CreatedAt int64 `json:"created_at,omitempty"` // } // // client.VehicleSnapshot.Query(). diff --git a/internal/database/ent/db/vehiclesnapshot_update.go b/internal/database/ent/db/vehiclesnapshot_update.go index a80ac271..ec24d4d9 100644 --- a/internal/database/ent/db/vehiclesnapshot_update.go +++ b/internal/database/ent/db/vehiclesnapshot_update.go @@ -30,14 +30,14 @@ func (vsu *VehicleSnapshotUpdate) Where(ps ...predicate.VehicleSnapshot) *Vehicl } // SetUpdatedAt sets the "updated_at" field. -func (vsu *VehicleSnapshotUpdate) SetUpdatedAt(i int) *VehicleSnapshotUpdate { +func (vsu *VehicleSnapshotUpdate) SetUpdatedAt(i int64) *VehicleSnapshotUpdate { vsu.mutation.ResetUpdatedAt() vsu.mutation.SetUpdatedAt(i) return vsu } // AddUpdatedAt adds i to the "updated_at" field. -func (vsu *VehicleSnapshotUpdate) AddUpdatedAt(i int) *VehicleSnapshotUpdate { +func (vsu *VehicleSnapshotUpdate) AddUpdatedAt(i int64) *VehicleSnapshotUpdate { vsu.mutation.AddUpdatedAt(i) return vsu } @@ -92,14 +92,14 @@ func (vsu *VehicleSnapshotUpdate) AddBattles(i int) *VehicleSnapshotUpdate { } // SetLastBattleTime sets the "last_battle_time" field. -func (vsu *VehicleSnapshotUpdate) SetLastBattleTime(i int) *VehicleSnapshotUpdate { +func (vsu *VehicleSnapshotUpdate) SetLastBattleTime(i int64) *VehicleSnapshotUpdate { vsu.mutation.ResetLastBattleTime() vsu.mutation.SetLastBattleTime(i) return vsu } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (vsu *VehicleSnapshotUpdate) SetNillableLastBattleTime(i *int) *VehicleSnapshotUpdate { +func (vsu *VehicleSnapshotUpdate) SetNillableLastBattleTime(i *int64) *VehicleSnapshotUpdate { if i != nil { vsu.SetLastBattleTime(*i) } @@ -107,7 +107,7 @@ func (vsu *VehicleSnapshotUpdate) SetNillableLastBattleTime(i *int) *VehicleSnap } // AddLastBattleTime adds i to the "last_battle_time" field. -func (vsu *VehicleSnapshotUpdate) AddLastBattleTime(i int) *VehicleSnapshotUpdate { +func (vsu *VehicleSnapshotUpdate) AddLastBattleTime(i int64) *VehicleSnapshotUpdate { vsu.mutation.AddLastBattleTime(i) return vsu } @@ -198,10 +198,10 @@ func (vsu *VehicleSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error } } if value, ok := vsu.mutation.UpdatedAt(); ok { - _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vsu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vsu.mutation.GetType(); ok { _spec.SetField(vehiclesnapshot.FieldType, field.TypeEnum, value) @@ -216,10 +216,10 @@ func (vsu *VehicleSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error _spec.AddField(vehiclesnapshot.FieldBattles, field.TypeInt, value) } if value, ok := vsu.mutation.LastBattleTime(); ok { - _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := vsu.mutation.AddedLastBattleTime(); ok { - _spec.AddField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.AddField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := vsu.mutation.Frame(); ok { _spec.SetField(vehiclesnapshot.FieldFrame, field.TypeJSON, value) @@ -245,14 +245,14 @@ type VehicleSnapshotUpdateOne struct { } // SetUpdatedAt sets the "updated_at" field. -func (vsuo *VehicleSnapshotUpdateOne) SetUpdatedAt(i int) *VehicleSnapshotUpdateOne { +func (vsuo *VehicleSnapshotUpdateOne) SetUpdatedAt(i int64) *VehicleSnapshotUpdateOne { vsuo.mutation.ResetUpdatedAt() vsuo.mutation.SetUpdatedAt(i) return vsuo } // AddUpdatedAt adds i to the "updated_at" field. -func (vsuo *VehicleSnapshotUpdateOne) AddUpdatedAt(i int) *VehicleSnapshotUpdateOne { +func (vsuo *VehicleSnapshotUpdateOne) AddUpdatedAt(i int64) *VehicleSnapshotUpdateOne { vsuo.mutation.AddUpdatedAt(i) return vsuo } @@ -307,14 +307,14 @@ func (vsuo *VehicleSnapshotUpdateOne) AddBattles(i int) *VehicleSnapshotUpdateOn } // SetLastBattleTime sets the "last_battle_time" field. -func (vsuo *VehicleSnapshotUpdateOne) SetLastBattleTime(i int) *VehicleSnapshotUpdateOne { +func (vsuo *VehicleSnapshotUpdateOne) SetLastBattleTime(i int64) *VehicleSnapshotUpdateOne { vsuo.mutation.ResetLastBattleTime() vsuo.mutation.SetLastBattleTime(i) return vsuo } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (vsuo *VehicleSnapshotUpdateOne) SetNillableLastBattleTime(i *int) *VehicleSnapshotUpdateOne { +func (vsuo *VehicleSnapshotUpdateOne) SetNillableLastBattleTime(i *int64) *VehicleSnapshotUpdateOne { if i != nil { vsuo.SetLastBattleTime(*i) } @@ -322,7 +322,7 @@ func (vsuo *VehicleSnapshotUpdateOne) SetNillableLastBattleTime(i *int) *Vehicle } // AddLastBattleTime adds i to the "last_battle_time" field. -func (vsuo *VehicleSnapshotUpdateOne) AddLastBattleTime(i int) *VehicleSnapshotUpdateOne { +func (vsuo *VehicleSnapshotUpdateOne) AddLastBattleTime(i int64) *VehicleSnapshotUpdateOne { vsuo.mutation.AddLastBattleTime(i) return vsuo } @@ -443,10 +443,10 @@ func (vsuo *VehicleSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *Vehic } } if value, ok := vsuo.mutation.UpdatedAt(); ok { - _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vsuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt, value) + _spec.AddField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) } if value, ok := vsuo.mutation.GetType(); ok { _spec.SetField(vehiclesnapshot.FieldType, field.TypeEnum, value) @@ -461,10 +461,10 @@ func (vsuo *VehicleSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *Vehic _spec.AddField(vehiclesnapshot.FieldBattles, field.TypeInt, value) } if value, ok := vsuo.mutation.LastBattleTime(); ok { - _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := vsuo.mutation.AddedLastBattleTime(); ok { - _spec.AddField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt, value) + _spec.AddField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) } if value, ok := vsuo.mutation.Frame(); ok { _spec.SetField(vehiclesnapshot.FieldFrame, field.TypeJSON, value) diff --git a/internal/database/ent/migrate.go b/internal/database/ent/migrate.go deleted file mode 100644 index 39d7f1aa..00000000 --- a/internal/database/ent/migrate.go +++ /dev/null @@ -1,63 +0,0 @@ -package ent - -import ( - "database/sql" -) - -const migration = ` -CREATE TABLE 'accounts' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'last_battle_time' integer NOT NULL, 'account_created_at' integer NOT NULL, 'realm' text NOT NULL, 'nickname' text NOT NULL, 'private' bool NOT NULL DEFAULT (false), 'clan_id' text NULL, PRIMARY KEY ('id'), CONSTRAINT 'accounts_clans_accounts' FOREIGN KEY ('clan_id') REFERENCES 'clans' ('id') ON DELETE SET NULL); -CREATE INDEX 'account_id_last_battle_time' ON 'accounts' ('id', 'last_battle_time'); -CREATE INDEX 'account_realm' ON 'accounts' ('realm'); -CREATE INDEX 'account_realm_last_battle_time' ON 'accounts' ('realm', 'last_battle_time'); -CREATE INDEX 'account_clan_id' ON 'accounts' ('clan_id'); -CREATE TABLE 'account_snapshots' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'last_battle_time' integer NOT NULL, 'reference_id' text NOT NULL, 'rating_battles' integer NOT NULL, 'rating_frame' json NOT NULL, 'regular_battles' integer NOT NULL, 'regular_frame' json NOT NULL, 'account_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'account_snapshots_accounts_snapshots' FOREIGN KEY ('account_id') REFERENCES 'accounts' ('id') ON DELETE NO ACTION); -CREATE INDEX 'accountsnapshot_created_at' ON 'account_snapshots' ('created_at'); -CREATE INDEX 'accountsnapshot_type_account_id_created_at' ON 'account_snapshots' ('type', 'account_id', 'created_at'); -CREATE INDEX 'accountsnapshot_type_account_id_reference_id' ON 'account_snapshots' ('type', 'account_id', 'reference_id'); -CREATE INDEX 'accountsnapshot_type_account_id_reference_id_created_at' ON 'account_snapshots' ('type', 'account_id', 'reference_id', 'created_at'); -CREATE TABLE 'achievements_snapshots' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'reference_id' text NOT NULL, 'battles' integer NOT NULL, 'last_battle_time' integer NOT NULL, 'data' json NOT NULL, 'account_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'achievements_snapshots_accounts_achievement_snapshots' FOREIGN KEY ('account_id') REFERENCES 'accounts' ('id') ON DELETE NO ACTION); -CREATE INDEX 'achievementssnapshot_created_at' ON 'achievements_snapshots' ('created_at'); -CREATE INDEX 'achievementssnapshot_account_id_reference_id' ON 'achievements_snapshots' ('account_id', 'reference_id'); -CREATE INDEX 'achievementssnapshot_account_id_reference_id_created_at' ON 'achievements_snapshots' ('account_id', 'reference_id', 'created_at'); -CREATE TABLE 'app_configurations' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'key' text NOT NULL, 'value' json NOT NULL, 'metadata' json NULL, PRIMARY KEY ('id')); -CREATE UNIQUE INDEX 'app_configurations_key_key' ON 'app_configurations' ('key'); -CREATE INDEX 'appconfiguration_key' ON 'app_configurations' ('key'); -CREATE TABLE 'application_commands' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'name' text NOT NULL, 'version' text NOT NULL, 'options_hash' text NOT NULL, PRIMARY KEY ('id')); -CREATE UNIQUE INDEX 'application_commands_name_key' ON 'application_commands' ('name'); -CREATE INDEX 'applicationcommand_options_hash' ON 'application_commands' ('options_hash'); -CREATE TABLE 'clans' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'tag' text NOT NULL, 'name' text NOT NULL, 'emblem_id' text NULL DEFAULT (''), 'members' json NOT NULL, PRIMARY KEY ('id')); -CREATE INDEX 'clan_tag' ON 'clans' ('tag'); -CREATE INDEX 'clan_name' ON 'clans' ('name'); -CREATE TABLE 'cron_tasks' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'reference_id' text NOT NULL, 'targets' json NOT NULL, 'status' text NOT NULL, 'scheduled_after' integer NOT NULL, 'last_run' integer NOT NULL, 'logs' json NOT NULL, 'data' json NOT NULL, PRIMARY KEY ('id')); -CREATE INDEX 'crontask_reference_id' ON 'cron_tasks' ('reference_id'); -CREATE INDEX 'crontask_status_last_run' ON 'cron_tasks' ('status', 'last_run'); -CREATE INDEX 'crontask_status_created_at' ON 'cron_tasks' ('status', 'created_at'); -CREATE INDEX 'crontask_status_scheduled_after' ON 'cron_tasks' ('status', 'scheduled_after'); -CREATE TABLE 'users' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'permissions' text NOT NULL DEFAULT (''), 'feature_flags' json NULL, PRIMARY KEY ('id')); -CREATE TABLE 'user_connections' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'reference_id' text NOT NULL, 'permissions' text NULL DEFAULT (''), 'metadata' json NULL, 'user_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'user_connections_users_connections' FOREIGN KEY ('user_id') REFERENCES 'users' ('id') ON DELETE NO ACTION); -CREATE INDEX 'userconnection_user_id' ON 'user_connections' ('user_id'); -CREATE INDEX 'userconnection_type_user_id' ON 'user_connections' ('type', 'user_id'); -CREATE INDEX 'userconnection_reference_id' ON 'user_connections' ('reference_id'); -CREATE INDEX 'userconnection_type_reference_id' ON 'user_connections' ('type', 'reference_id'); -CREATE TABLE 'user_contents' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'reference_id' text NOT NULL, 'value' json NOT NULL, 'metadata' json NOT NULL, 'user_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'user_contents_users_content' FOREIGN KEY ('user_id') REFERENCES 'users' ('id') ON DELETE NO ACTION); -CREATE INDEX 'usercontent_user_id' ON 'user_contents' ('user_id'); -CREATE INDEX 'usercontent_type_user_id' ON 'user_contents' ('type', 'user_id'); -CREATE INDEX 'usercontent_reference_id' ON 'user_contents' ('reference_id'); -CREATE INDEX 'usercontent_type_reference_id' ON 'user_contents' ('type', 'reference_id'); -CREATE TABLE 'user_subscriptions' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'expires_at' integer NOT NULL, 'permissions' text NOT NULL, 'reference_id' text NOT NULL, 'user_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'user_subscriptions_users_subscriptions' FOREIGN KEY ('user_id') REFERENCES 'users' ('id') ON DELETE NO ACTION); -CREATE INDEX 'usersubscription_user_id' ON 'user_subscriptions' ('user_id'); -CREATE INDEX 'usersubscription_type_user_id' ON 'user_subscriptions' ('type', 'user_id'); -CREATE INDEX 'usersubscription_expires_at' ON 'user_subscriptions' ('expires_at'); -CREATE INDEX 'usersubscription_expires_at_user_id' ON 'user_subscriptions' ('expires_at', 'user_id'); -CREATE TABLE 'vehicles' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'tier' integer NOT NULL, 'localized_names' json NOT NULL, PRIMARY KEY ('id')); -CREATE TABLE 'vehicle_averages' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'data' json NOT NULL, PRIMARY KEY ('id')); -CREATE TABLE 'vehicle_snapshots' ('id' text NOT NULL, 'created_at' integer NOT NULL, 'updated_at' integer NOT NULL, 'type' text NOT NULL, 'vehicle_id' text NOT NULL, 'reference_id' text NOT NULL, 'battles' integer NOT NULL, 'last_battle_time' integer NOT NULL, 'frame' json NOT NULL, 'account_id' text NOT NULL, PRIMARY KEY ('id'), CONSTRAINT 'vehicle_snapshots_accounts_vehicle_snapshots' FOREIGN KEY ('account_id') REFERENCES 'accounts' ('id') ON DELETE NO ACTION); -CREATE INDEX 'vehiclesnapshot_created_at' ON 'vehicle_snapshots' ('created_at'); -CREATE INDEX 'vehiclesnapshot_vehicle_id_created_at' ON 'vehicle_snapshots' ('vehicle_id', 'created_at'); -CREATE INDEX 'vehiclesnapshot_account_id_created_at' ON 'vehicle_snapshots' ('account_id', 'created_at'); -CREATE INDEX 'vehiclesnapshot_account_id_type_created_at' ON 'vehicle_snapshots' ('account_id', 'type', 'created_at');` - -func RunMigrations(client *sql.DB) error { - _, err := client.Exec(migration) - return err -} diff --git a/internal/database/ent/migrate/main.go b/internal/database/ent/migrate/main.go index d0ab8587..de8b8a3d 100644 --- a/internal/database/ent/migrate/main.go +++ b/internal/database/ent/migrate/main.go @@ -33,7 +33,7 @@ func main() { log.Fatalln("migration name is required. Use: 'go run -mod=mod ent/migrate/main.go '") } // Generate migrations using Atlas support for MySQL (note the Ent dialect option passed above). - err = migrate.NamedDiff(ctx, "libsql+ws://0.0.0.0:8080", os.Args[1], opts...) + err = migrate.NamedDiff(ctx, os.Getenv("DATABASE_URL"), os.Args[1], opts...) if err != nil { log.Fatalf("failed generating migration file: %v", err) } diff --git a/internal/database/ent/schema/account.go b/internal/database/ent/schema/account.go index 4df6d398..1477f439 100644 --- a/internal/database/ent/schema/account.go +++ b/internal/database/ent/schema/account.go @@ -17,14 +17,14 @@ func (Account) Fields() []ent.Field { return []ent.Field{field.String("id"). Unique(). Immutable(), - field.Int("created_at"). + field.Int64("created_at"). Immutable(). DefaultFunc(timeNow), - field.Int("updated_at"). + field.Int64("updated_at"). DefaultFunc(timeNow). UpdateDefault(timeNow), - field.Int("last_battle_time"), - field.Int("account_created_at"), + field.Int64("last_battle_time"), + field.Int64("account_created_at"), // field.String("realm"). MinLen(2). diff --git a/internal/database/ent/schema/accountsnapshot.go b/internal/database/ent/schema/account_snapshot.go similarity index 97% rename from internal/database/ent/schema/accountsnapshot.go rename to internal/database/ent/schema/account_snapshot.go index 6a33c4c4..cd9391eb 100644 --- a/internal/database/ent/schema/accountsnapshot.go +++ b/internal/database/ent/schema/account_snapshot.go @@ -19,7 +19,7 @@ func (AccountSnapshot) Fields() []ent.Field { return append(defaultFields, field.Enum("type"). GoType(models.SnapshotType("")), - field.Int("last_battle_time"), + field.Int64("last_battle_time"), // field.String("account_id").NotEmpty().Immutable(), field.String("reference_id").NotEmpty(), diff --git a/internal/database/ent/schema/achievementssnapshot.go b/internal/database/ent/schema/achievements_snapshot.go similarity index 97% rename from internal/database/ent/schema/achievementssnapshot.go rename to internal/database/ent/schema/achievements_snapshot.go index bfd87368..26258e9f 100644 --- a/internal/database/ent/schema/achievementssnapshot.go +++ b/internal/database/ent/schema/achievements_snapshot.go @@ -23,7 +23,7 @@ func (AchievementsSnapshot) Fields() []ent.Field { field.String("reference_id").NotEmpty(), // field.Int("battles"), - field.Int("last_battle_time"), + field.Int64("last_battle_time"), field.JSON("data", types.AchievementsFrame{}), ) } diff --git a/internal/database/ent/schema/appconfiguration.go b/internal/database/ent/schema/app_configuration.go similarity index 100% rename from internal/database/ent/schema/appconfiguration.go rename to internal/database/ent/schema/app_configuration.go diff --git a/internal/database/ent/schema/applicationcommand.go b/internal/database/ent/schema/application_command.go similarity index 100% rename from internal/database/ent/schema/applicationcommand.go rename to internal/database/ent/schema/application_command.go diff --git a/internal/database/ent/schema/clan.go b/internal/database/ent/schema/clan.go index 9b953820..9aa1738c 100644 --- a/internal/database/ent/schema/clan.go +++ b/internal/database/ent/schema/clan.go @@ -36,10 +36,10 @@ func (Clan) Fields() []ent.Field { field.String("id"). Unique(). Immutable(), - field.Int("created_at"). + field.Int64("created_at"). Immutable(). DefaultFunc(timeNow), - field.Int("updated_at"). + field.Int64("updated_at"). DefaultFunc(timeNow). UpdateDefault(timeNow), // diff --git a/internal/database/ent/schema/crontask.go b/internal/database/ent/schema/cron_task.go similarity index 82% rename from internal/database/ent/schema/crontask.go rename to internal/database/ent/schema/cron_task.go index f214f55d..d329c600 100644 --- a/internal/database/ent/schema/crontask.go +++ b/internal/database/ent/schema/cron_task.go @@ -15,13 +15,15 @@ type CronTask struct { // Fields of the CronTask. func (CronTask) Fields() []ent.Field { return append(defaultFields, - field.String("type").NotEmpty(), + field.Enum("type"). + GoType(models.TaskType("")), field.String("reference_id").NotEmpty(), field.Strings("targets"), // - field.String("status").NotEmpty(), - field.Int("scheduled_after"), - field.Int("last_run"), + field.Enum("status"). + GoType(models.TaskStatus("")), + field.Int64("scheduled_after"), + field.Int64("last_run"), // field.JSON("logs", []models.TaskLog{}), field.JSON("data", map[string]any{}), diff --git a/internal/database/ent/schema/defaults.go b/internal/database/ent/schema/defaults.go index ddf92c1c..97d60c1d 100644 --- a/internal/database/ent/schema/defaults.go +++ b/internal/database/ent/schema/defaults.go @@ -13,12 +13,12 @@ var defaultFields = []ent.Field{ Unique(). Immutable(). DefaultFunc(cuid.New), - field.Int("created_at"). + field.Int64("created_at"). Immutable(). DefaultFunc(timeNow), - field.Int("updated_at"). + field.Int64("updated_at"). DefaultFunc(timeNow). UpdateDefault(timeNow), } -func timeNow() int { return int(time.Now().Unix()) } +func timeNow() int64 { return time.Now().Unix() } diff --git a/internal/database/ent/schema/user.go b/internal/database/ent/schema/user.go index 188f638f..f821f9b0 100644 --- a/internal/database/ent/schema/user.go +++ b/internal/database/ent/schema/user.go @@ -17,10 +17,10 @@ func (User) Fields() []ent.Field { field.String("id"). Unique(). Immutable(), - field.Int("created_at"). + field.Int64("created_at"). Immutable(). DefaultFunc(timeNow), - field.Int("updated_at"). + field.Int64("updated_at"). DefaultFunc(timeNow). UpdateDefault(timeNow), // diff --git a/internal/database/ent/schema/userconnection.go b/internal/database/ent/schema/user_connection.go similarity index 100% rename from internal/database/ent/schema/userconnection.go rename to internal/database/ent/schema/user_connection.go diff --git a/internal/database/ent/schema/usercontent.go b/internal/database/ent/schema/user_content.go similarity index 100% rename from internal/database/ent/schema/usercontent.go rename to internal/database/ent/schema/user_content.go diff --git a/internal/database/ent/schema/usersubscription.go b/internal/database/ent/schema/user_subscription.go similarity index 97% rename from internal/database/ent/schema/usersubscription.go rename to internal/database/ent/schema/user_subscription.go index 2394efdb..b156bea2 100644 --- a/internal/database/ent/schema/usersubscription.go +++ b/internal/database/ent/schema/user_subscription.go @@ -18,7 +18,7 @@ func (UserSubscription) Fields() []ent.Field { return append(defaultFields, field.Enum("type"). GoType(models.SubscriptionType("")), - field.Int("expires_at"), + field.Int64("expires_at"), // field.String("user_id").NotEmpty().Immutable(), field.String("permissions").NotEmpty(), diff --git a/internal/database/ent/schema/vehicle.go b/internal/database/ent/schema/vehicle.go index 525287a0..c66424b1 100644 --- a/internal/database/ent/schema/vehicle.go +++ b/internal/database/ent/schema/vehicle.go @@ -16,10 +16,10 @@ func (Vehicle) Fields() []ent.Field { field.String("id"). Unique(). Immutable(), - field.Int("created_at"). + field.Int64("created_at"). Immutable(). DefaultFunc(timeNow), - field.Int("updated_at"). + field.Int64("updated_at"). DefaultFunc(timeNow). UpdateDefault(timeNow), // diff --git a/internal/database/ent/schema/vehicleaverage.go b/internal/database/ent/schema/vehicle_average.go similarity index 85% rename from internal/database/ent/schema/vehicleaverage.go rename to internal/database/ent/schema/vehicle_average.go index edb1798a..eda545dd 100644 --- a/internal/database/ent/schema/vehicleaverage.go +++ b/internal/database/ent/schema/vehicle_average.go @@ -17,14 +17,14 @@ func (VehicleAverage) Fields() []ent.Field { field.String("id"). Unique(). Immutable(), - field.Int("created_at"). + field.Int64("created_at"). Immutable(). DefaultFunc(timeNow), - field.Int("updated_at"). + field.Int64("updated_at"). DefaultFunc(timeNow). UpdateDefault(timeNow), // - field.JSON("data", map[string]frame.StatsFrame{}), + field.JSON("data", frame.StatsFrame{}), } } diff --git a/internal/database/ent/schema/vehiclesnapshot.go b/internal/database/ent/schema/vehicle_snapshot.go similarity index 97% rename from internal/database/ent/schema/vehiclesnapshot.go rename to internal/database/ent/schema/vehicle_snapshot.go index 88dc1fa1..e8ee67e4 100644 --- a/internal/database/ent/schema/vehiclesnapshot.go +++ b/internal/database/ent/schema/vehicle_snapshot.go @@ -25,7 +25,7 @@ func (VehicleSnapshot) Fields() []ent.Field { field.String("reference_id").NotEmpty(), // field.Int("battles"), - field.Int("last_battle_time"), + field.Int64("last_battle_time"), field.JSON("frame", frame.StatsFrame{}), ) } diff --git a/internal/database/models/vehicle.go b/internal/database/models/vehicle.go index b9f04a4e..53787d30 100644 --- a/internal/database/models/vehicle.go +++ b/internal/database/models/vehicle.go @@ -3,12 +3,8 @@ package models import "golang.org/x/text/language" type Vehicle struct { - ID string - Tier int - Type string - Class string - Nation string - + ID string + Tier int LocalizedNames map[string]string } diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index b6f390e4..10636ec6 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -4,21 +4,13 @@ import ( "context" "time" + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" "github.com/cufee/aftermath/internal/database/models" ) -// import ( -// "context" -// "fmt" -// "strings" -// "time" - -// "github.com/cufee/aftermath/internal/database/prisma/db" -// "github.com/cufee/aftermath/internal/stats/frame" -// "github.com/rs/zerolog/log" -// "github.com/steebchen/prisma-client-go/runtime/transaction" -// ) - type getSnapshotQuery struct { vehicleIDs []string @@ -26,158 +18,6 @@ type getSnapshotQuery struct { createdBefore *time.Time } -// func (s getSnapshotQuery) accountParams(accountID, referenceID string, kind models.SnapshotType) []db.AccountSnapshotWhereParam { -// var params []db.AccountSnapshotWhereParam -// params = append(params, db.AccountSnapshot.Type.Equals(string(kind))) -// params = append(params, db.AccountSnapshot.AccountID.Equals(accountID)) -// params = append(params, db.AccountSnapshot.ReferenceID.Equals(referenceID)) - -// if s.createdAfter != nil { -// params = append(params, db.AccountSnapshot.CreatedAt.After(*s.createdAfter)) -// } -// if s.createdBefore != nil { -// params = append(params, db.AccountSnapshot.CreatedAt.Before(*s.createdBefore)) -// } - -// return params -// } - -// func (s getSnapshotQuery) vehiclesQuery(accountID, referenceID string, kind models.SnapshotType) (query string, params []interface{}) { -// var conditions []string -// var args []interface{} - -// // Mandatory conditions -// conditions = append(conditions, "type = ?") -// args = append(args, kind) - -// conditions = append(conditions, "accountId = ?") -// args = append(args, accountID) - -// conditions = append(conditions, "referenceId = ?") -// args = append(args, referenceID) - -// // Optional conditions -// if s.createdAfter != nil { -// conditions = append(conditions, "createdAt > ?") -// args = append(args, *s.createdAfter) -// } -// if s.createdBefore != nil { -// conditions = append(conditions, "createdAt < ?") -// args = append(args, *s.createdBefore) -// } - -// // Filter by vehicle IDs if provided -// if len(s.vehicleIDs) > 0 { -// placeholders := make([]string, len(s.vehicleIDs)) -// for i, id := range s.vehicleIDs { -// placeholders[i] = "?" -// args = append(args, id) -// } -// conditions = append(conditions, fmt.Sprintf("vehicleId IN (%s)", strings.Join(placeholders, ","))) -// } - -// // Determine the order by clause -// var orderBy string = "createdAt DESC" -// if s.createdAfter != nil { -// orderBy = "createdAt ASC" -// } - -// // Base query -// query = ` -// SELECT -// id, createdAt, type, lastBattleTime, accountId, vehicleId, referenceId, battles, frameEncoded -// FROM -// vehicle_snapshots -// WHERE -// %s -// ORDER BY -// %s -// ` - -// // Combine conditions into a single string -// conditionsStr := strings.Join(conditions, " AND ") -// query = fmt.Sprintf(query, conditionsStr, orderBy) - -// // Wrap the query to select the latest or earliest snapshot per vehicleId -// wrappedQuery := ` -// SELECT * FROM ( -// %s -// ) AS ordered_snapshots -// GROUP BY vehicleId -// ` - -// query = fmt.Sprintf(wrappedQuery, query) -// return query, args -// } - -// func (s getSnapshotQuery) manyAccountsQuery(accountIDs []string, kind models.SnapshotType) (query string, params []interface{}) { -// var conditions []string -// var args []interface{} - -// // Mandatory conditions -// conditions = append(conditions, "type = ?") -// args = append(args, kind) - -// // Optional conditions -// if s.createdAfter != nil { -// conditions = append(conditions, "createdAt > ?") -// args = append(args, *s.createdAfter) -// } -// if s.createdBefore != nil { -// conditions = append(conditions, "createdAt < ?") -// args = append(args, *s.createdBefore) -// } - -// // Filter by account IDs -// placeholders := make([]string, len(accountIDs)) -// for i := range accountIDs { -// placeholders[i] = "?" -// } -// conditions = append(conditions, fmt.Sprintf("accountId IN (%s)", strings.Join(placeholders, ","))) -// for _, id := range accountIDs { -// args = append(args, id) -// } - -// // Determine the order by clause -// var orderBy string = "createdAt DESC" -// if s.createdAfter != nil { -// orderBy = "createdAt ASC" -// } - -// // Combine conditions into a single string -// conditionsStr := strings.Join(conditions, " AND ") - -// // Base query -// query = fmt.Sprintf(` -// SELECT -// id, createdAt, type, lastBattleTime, accountId, referenceId, ratingBattles, ratingFrameEncoded, regularBattles, regularFrameEncoded -// FROM -// account_snapshots -// WHERE -// %s -// ORDER BY -// %s -// `, conditionsStr, orderBy) - -// // Wrap the query to select the latest snapshot per accountId -// wrappedQuery := fmt.Sprintf(` -// SELECT * FROM ( -// %s -// ) AS ordered_snapshots -// GROUP BY accountId -// `, query) - -// return wrappedQuery, args -// } - -// func (s getSnapshotQuery) accountOrder() []db.AccountSnapshotOrderByParam { -// order := db.DESC -// if s.createdAfter != nil { -// order = db.ASC -// } -// return []db.AccountSnapshotOrderByParam{db.AccountSnapshot.CreatedAt.Order(order)} -// } - type SnapshotQuery func(*getSnapshotQuery) func WithVehicleIDs(ids []string) SnapshotQuery { @@ -196,192 +36,173 @@ func WithCreatedBefore(before time.Time) SnapshotQuery { } } -// func (s models.VehicleSnapshot) FromModel(model db.models.VehicleSnapshotModel) (models.VehicleSnapshot, error) { -// s.ID = model.ID -// s.Type = models.SnapshotType(model.Type) -// s.CreatedAt = model.CreatedAt -// s.LastBattleTime = model.LastBattleTime +func toVehicleSnapshot(record *db.VehicleSnapshot) models.VehicleSnapshot { + return models.VehicleSnapshot{ + ID: record.ID, + Type: record.Type, + CreatedAt: time.Unix(record.CreatedAt, 0), + LastBattleTime: time.Unix(record.LastBattleTime, 0), + ReferenceID: record.ReferenceID, + AccountID: record.AccountID, + VehicleID: record.VehicleID, + Stats: record.Frame, + } +} -// s.AccountID = model.AccountID -// s.VehicleID = model.VehicleID -// s.ReferenceID = model.ReferenceID +func (c *libsqlClient) CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error { + if len(snapshots) < 1 { + return nil + } -// stats, err := frame.DecodeStatsFrame(model.FrameEncoded) -// if err != nil { -// return models.VehicleSnapshot{}, err -// } -// s.Stats = stats -// return s, nil -// } + var inserts []*db.VehicleSnapshotCreate + for _, data := range snapshots { + inserts = append(inserts, + c.db.VehicleSnapshot.Create(). + SetType(data.Type). + SetFrame(data.Stats). + SetAccountID(data.AccountID). + SetVehicleID(data.VehicleID). + SetReferenceID(data.ReferenceID). + SetBattles(int(data.Stats.Battles.Float())). + SetLastBattleTime(data.LastBattleTime.Unix()), + ) + } -func (c *libsqlClient) CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error { - // if len(snapshots) < 1 { - // return nil - // } - - // var transactions []transaction.Transaction - // for _, data := range snapshots { - // encoded, err := data.Stats.Encode() - // if err != nil { - // log.Err(err).Str("accountId", data.AccountID).Str("vehicleId", data.VehicleID).Msg("failed to encode a stats frame for vehicle snapthsot") - // continue - // } - - // transactions = append(transactions, c.prisma.models.VehicleSnapshot. - // CreateOne( - // db.models.VehicleSnapshot.CreatedAt.Set(data.CreatedAt), - // db.models.VehicleSnapshot.Type.Set(string(data.Type)), - // db.models.VehicleSnapshot.LastBattleTime.Set(data.LastBattleTime), - // db.models.VehicleSnapshot.AccountID.Set(data.AccountID), - // db.models.VehicleSnapshot.VehicleID.Set(data.VehicleID), - // db.models.VehicleSnapshot.ReferenceID.Set(data.ReferenceID), - // db.models.VehicleSnapshot.Battles.Set(int(data.Stats.Battles)), - // db.models.VehicleSnapshot.FrameEncoded.Set(encoded), - // ).Tx(), - // ) - // } - - // return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) - return nil + return c.db.VehicleSnapshot.CreateBulk(inserts...).Exec(ctx) } func (c *libsqlClient) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) { - // var query getSnapshotQuery - // for _, apply := range options { - // apply(&query) - // } - - // var models []db.models.VehicleSnapshotModel - // raw, args := query.vehiclesQuery(accountID, referenceID, kind) - // err := c.prisma.Prisma.Raw.QueryRaw(raw, args...).Exec(ctx, &models) - // if err != nil { - // return nil, err - // } + var query getSnapshotQuery + for _, apply := range options { + apply(&query) + } + var where []predicate.VehicleSnapshot + where = append(where, vehiclesnapshot.AccountID(accountID)) + where = append(where, vehiclesnapshot.ReferenceID(referenceID)) + where = append(where, vehiclesnapshot.TypeEQ(kind)) + + if query.createdAfter != nil { + where = append(where, vehiclesnapshot.CreatedAtGT(query.createdAfter.Unix())) + } + if query.createdAfter != nil { + where = append(where, vehiclesnapshot.CreatedAtLT(query.createdBefore.Unix())) + } + if query.vehicleIDs != nil { + where = append(where, vehiclesnapshot.VehicleIDIn(query.vehicleIDs...)) + } + + var records []*db.VehicleSnapshot + err := c.db.VehicleSnapshot.Query().Where(where...).GroupBy(vehiclesnapshot.FieldVehicleID).Scan(ctx, &records) + if err != nil && !IsNotFound(err) { + return nil, err + } var snapshots []models.VehicleSnapshot - // for _, model := range models { - // vehicle, err := models.VehicleSnapshot{}.FromModel(model) - // if err != nil { - // return nil, err - // } - // snapshots = append(snapshots, vehicle) - // } + for _, r := range records { + snapshots = append(snapshots, toVehicleSnapshot(r)) + } return snapshots, nil } -// func (s AccountSnapshot) FromModel(model *db.AccountSnapshotModel) (AccountSnapshot, error) { -// s.ID = model.ID -// s.Type = models.SnapshotType(model.Type) -// s.CreatedAt = model.CreatedAt -// s.AccountID = model.AccountID -// s.ReferenceID = model.ReferenceID -// s.LastBattleTime = model.LastBattleTime - -// rating, err := frame.DecodeStatsFrame(model.RatingFrameEncoded) -// if err != nil { -// return AccountSnapshot{}, err -// } -// s.RatingBattles = rating - -// regular, err := frame.DecodeStatsFrame(model.RegularFrameEncoded) -// if err != nil { -// return AccountSnapshot{}, err -// } -// s.RegularBattles = regular - -// return s, nil -// } +func toAccountSnapshot(record *db.AccountSnapshot) models.AccountSnapshot { + return models.AccountSnapshot{ + ID: record.ID, + Type: record.Type, + AccountID: record.AccountID, + ReferenceID: record.ReferenceID, + RatingBattles: record.RatingFrame, + RegularBattles: record.RegularFrame, + CreatedAt: time.Unix(record.CreatedAt, 0), + LastBattleTime: time.Unix(record.LastBattleTime, 0), + } +} func (c *libsqlClient) CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) error { - // if len(snapshots) < 1 { - // return nil - // } - - // var transactions []transaction.Transaction - // for _, data := range snapshots { - // ratingEncoded, err := data.RatingBattles.Encode() - // if err != nil { - // log.Err(err).Str("accountId", data.AccountID).Msg("failed to encode rating stats frame for account snapthsot") - // continue - // } - // regularEncoded, err := data.RegularBattles.Encode() - // if err != nil { - // log.Err(err).Str("accountId", data.AccountID).Msg("failed to encode regular stats frame for account snapthsot") - // continue - // } - - // transactions = append(transactions, c.prisma.AccountSnapshot. - // CreateOne( - // db.AccountSnapshot.CreatedAt.Set(data.CreatedAt), - // db.AccountSnapshot.Type.Set(string(data.Type)), - // db.AccountSnapshot.LastBattleTime.Set(data.LastBattleTime), - // db.AccountSnapshot.AccountID.Set(data.AccountID), - // db.AccountSnapshot.ReferenceID.Set(data.ReferenceID), - // db.AccountSnapshot.RatingBattles.Set(int(data.RatingBattles.Battles)), - // db.AccountSnapshot.RatingFrameEncoded.Set(ratingEncoded), - // db.AccountSnapshot.RegularBattles.Set(int(data.RegularBattles.Battles)), - // db.AccountSnapshot.RegularFrameEncoded.Set(regularEncoded), - // ).Tx(), - // ) - // } - - // return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) - return nil + if len(snapshots) < 1 { + return nil + } + + var inserts []*db.AccountSnapshotCreate + for _, s := range snapshots { + inserts = append(inserts, + c.db.AccountSnapshot.Create(). + SetAccountID(s.AccountID). + SetCreatedAt(s.CreatedAt.Unix()). + SetLastBattleTime(s.LastBattleTime.Unix()). + SetRatingBattles(int(s.RatingBattles.Battles.Float())). + SetRatingFrame(s.RatingBattles). + SetReferenceID(s.ReferenceID). + SetRegularBattles(int(s.RegularBattles.Battles)). + SetRegularFrame(s.RegularBattles). + SetType(s.Type), + ) + } + + return c.db.AccountSnapshot.CreateBulk(inserts...).Exec(ctx) } func (c *libsqlClient) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) { - // models, err := c.prisma.AccountSnapshot.FindMany(db.AccountSnapshot.AccountID.Equals(accountID)).OrderBy(db.AccountSnapshot.CreatedAt.Order(db.DESC)).Take(limit).Exec(ctx) - // if err != nil { - // return nil, err - // } + records, err := c.db.AccountSnapshot.Query().Where(accountsnapshot.AccountID(accountID)).Limit(limit).All(ctx) + if err != nil { + return nil, err + } var snapshots []models.AccountSnapshot - // for _, model := range models { - // s, err := AccountSnapshot{}.FromModel(&model) - // if err != nil { - // return nil, err - // } - // snapshots = append(snapshots, s) - // } + for _, r := range records { + snapshots = append(snapshots, toAccountSnapshot(r)) + } + return snapshots, nil } func (c *libsqlClient) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) (models.AccountSnapshot, error) { - // var query getSnapshotQuery - // for _, apply := range options { - // apply(&query) - // } + var query getSnapshotQuery + for _, apply := range options { + apply(&query) + } - // model, err := c.prisma.AccountSnapshot.FindFirst(query.accountParams(accountID, referenceID, kind)...).OrderBy(query.accountOrder()...).Exec(ctx) - // if err != nil { - // return AccountSnapshot{}, err - // } + var where []predicate.AccountSnapshot + where = append(where, accountsnapshot.AccountID(accountID), accountsnapshot.ReferenceID(referenceID), accountsnapshot.TypeEQ(kind)) + if query.createdAfter != nil { + where = append(where, accountsnapshot.CreatedAtGT(query.createdAfter.Unix())) + } + if query.createdBefore != nil { + where = append(where, accountsnapshot.CreatedAtLT(query.createdAfter.Unix())) + } - return models.AccountSnapshot{}, nil + record, err := c.db.AccountSnapshot.Query().Where(where...).First(ctx) + if err != nil { + return models.AccountSnapshot{}, err + } + + return toAccountSnapshot(record), nil } func (c *libsqlClient) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.AccountSnapshot, error) { - // var query getSnapshotQuery - // for _, apply := range options { - // apply(&query) - // } - - // var models []db.AccountSnapshotModel - // raw, args := query.manyAccountsQuery(accountIDs, kind) - // err := c.prisma.Prisma.Raw.QueryRaw(raw, args...).Exec(ctx, &models) - // if err != nil { - // return nil, err - // } + var query getSnapshotQuery + for _, apply := range options { + apply(&query) + } + + var where []predicate.AccountSnapshot + where = append(where, accountsnapshot.AccountIDIn(accountIDs...), accountsnapshot.TypeEQ(kind)) + if query.createdAfter != nil { + where = append(where, accountsnapshot.CreatedAtGT(query.createdAfter.Unix())) + } + if query.createdBefore != nil { + where = append(where, accountsnapshot.CreatedAtLT(query.createdAfter.Unix())) + } + + records, err := c.db.AccountSnapshot.Query().Where(where...).All(ctx) + if err != nil { + return nil, err + } var snapshots []models.AccountSnapshot - // for _, model := range models { - // snapshot, err := AccountSnapshot{}.FromModel(&model) - // if err != nil { - // return nil, err - // } - // snapshots = append(snapshots, snapshot) - // } + for _, r := range records { + snapshots = append(snapshots, toAccountSnapshot(r)) + } return snapshots, nil diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 73f95eb3..57f3904b 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -4,43 +4,49 @@ import ( "context" "time" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" "github.com/cufee/aftermath/internal/database/models" ) -// func (t Task) FromModel(model db.CronTaskModel) Task { -// t.ID = model.ID -// t.Type = TaskType(model.Type) - -// t.Status = TaskStatus(model.Status) -// t.ReferenceID = model.ReferenceID - -// t.LastRun = model.LastRun -// t.CreatedAt = model.CreatedAt -// t.UpdatedAt = model.UpdatedAt -// t.ScheduledAfter = model.ScheduledAfter - -// t.decodeData(model.DataEncoded) -// t.decodeLogs(model.LogsEncoded) -// t.decodeTargets(model.TargetsEncoded) -// return t -// } +func toCronTask(record *db.CronTask) models.Task { + return models.Task{ + ID: record.ID, + Type: record.Type, + CreatedAt: time.Unix(record.CreatedAt, 0), + UpdatedAt: time.Unix(record.UpdatedAt, 0), + ReferenceID: record.ReferenceID, + Targets: record.Targets, + Logs: record.Logs, + Status: record.Status, + ScheduledAfter: time.Unix(record.ScheduledAfter, 0), + LastRun: time.Unix(record.LastRun, 0), + Data: record.Data, + } +} /* Returns up limit tasks that have TaskStatusInProgress and were last updates 1+ hours ago */ func (c *libsqlClient) GetStaleTasks(ctx context.Context, limit int) ([]models.Task, error) { - // models, err := c.prisma.CronTask.FindMany( - // db.CronTask.Status.Equals(string(TaskStatusInProgress)), - // db.CronTask.LastRun.Before(time.Now().Add(time.Hour*-1)), - // ).OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)).Take(limit).Exec(ctx) - // if err != nil && !database.IsNotFound(err) { - // return nil, err - // } + records, err := c.db.CronTask.Query(). + Where( + crontask.StatusEQ(models.TaskStatusInProgress), + crontask.LastRunLT(time.Now().Add(time.Hour*-1).Unix()), + ). + Order(crontask.ByScheduledAfter(sql.OrderAsc())). + Limit(limit). + All(ctx) + if err != nil && !IsNotFound(err) { + return nil, err + } var tasks []models.Task - // for _, model := range models { - // tasks = append(tasks, Task{}.FromModel(model)) - // } + for _, r := range records { + tasks = append(tasks, toCronTask(r)) + } return tasks, nil } @@ -49,104 +55,89 @@ func (c *libsqlClient) GetStaleTasks(ctx context.Context, limit int) ([]models.T Returns all tasks that were created after createdAfter, sorted by ScheduledAfter (DESC) */ func (c *libsqlClient) GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) { - // var statusStr []string - // for _, s := range status { - // statusStr = append(statusStr, string(s)) - // } - - // params := []db.CronTaskWhereParam{db.CronTask.CreatedAt.After(createdAfter)} - // if len(statusStr) > 0 { - // params = append(params, db.CronTask.Status.In(statusStr)) - // } + var where []predicate.CronTask + where = append(where, crontask.CreatedAtGT(createdAfter.Unix())) + if len(status) > 0 { + where = append(where, crontask.StatusIn(status...)) + } - // models, err := c.prisma.CronTask.FindMany(params...).OrderBy(db.CronTask.ScheduledAfter.Order(db.DESC)).Exec(ctx) - // if err != nil && !database.IsNotFound(err) { - // return nil, err - // } + records, err := c.db.CronTask.Query().Where(where...).All(ctx) + if err != nil && !IsNotFound(err) { + return nil, err + } var tasks []models.Task - // for _, model := range models { - // tasks = append(tasks, Task{}.FromModel(model)) - // } + for _, r := range records { + tasks = append(tasks, toCronTask(r)) + } return tasks, nil } /* GetAndStartTasks retrieves up to limit number of tasks matching the referenceId and updates their status to in progress - - this func will block until all other calls to task update funcs are done */ func (c *libsqlClient) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task, error) { - // if limit < 1 { - // return nil, nil - // } - // if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { - // return nil, err - // } - // defer c.tasksUpdateSem.Release(1) - - // models, err := c.prisma.CronTask. - // FindMany( - // db.CronTask.Status.Equals(string(TaskStatusScheduled)), - // db.CronTask.ScheduledAfter.Lt(time.Now()), - // ). - // OrderBy(db.CronTask.ScheduledAfter.Order(db.ASC)). - // Take(limit). - // Exec(ctx) - // if err != nil { - // if database.IsNotFound(err) { - // return nil, nil - // } - // return nil, err - // } - // if len(models) < 1 { - // return nil, nil - // } - - // var ids []string + if limit < 1 { + return nil, nil + } + + tx, err := c.db.Tx(ctx) + if err != nil { + return nil, err + } + + records, err := tx.CronTask.Query().Where(crontask.StatusEQ(models.TaskStatusScheduled), crontask.ScheduledAfterLT(time.Now().Unix())).Order(crontask.ByScheduledAfter(sql.OrderAsc())).Limit(limit).All(ctx) + if err != nil { + if IsNotFound(err) { + return nil, nil + } + return nil, rollback(tx, err) + } + + var ids []string var tasks []models.Task - // for _, model := range models { - // tasks = append(tasks, Task{}.FromModel(model)) - // ids = append(ids, model.ID) - // } - - // _, err = c.prisma.CronTask. - // FindMany(db.CronTask.ID.In(ids)). - // Update(db.CronTask.Status.Set(string(TaskStatusInProgress)), db.CronTask.LastRun.Set(time.Now())). - // Exec(ctx) - // if err != nil { - // return nil, err - // } + for _, r := range records { + t := toCronTask(r) + t.OnUpdated() - return tasks, nil + t.Status = models.TaskStatusInProgress + tasks = append(tasks, t) + ids = append(ids, r.ID) + } + + err = tx.CronTask.Update().Where(crontask.IDIn(ids...)).SetStatus(models.TaskStatusInProgress).Exec(ctx) + if err != nil && !IsNotFound(err) { + return nil, rollback(tx, err) + } + + return tasks, tx.Commit() } func (c *libsqlClient) CreateTasks(ctx context.Context, tasks ...models.Task) error { - // if len(tasks) < 1 { - // return nil - // } - // // we do not block using c.tasksUpdateSem here because the order of ops in GetAndStartTasks makes this safe - - // var txns []db.PrismaTransaction - // for _, task := range tasks { - // task.OnCreated() - // txns = append(txns, c.prisma.CronTask.CreateOne( - // db.CronTask.Type.Set(string(task.Type)), - // db.CronTask.ReferenceID.Set(task.ReferenceID), - // db.CronTask.TargetsEncoded.Set(task.encodeTargets()), - // db.CronTask.Status.Set(string(TaskStatusScheduled)), - // db.CronTask.LastRun.Set(time.Now()), - // db.CronTask.ScheduledAfter.Set(task.ScheduledAfter), - // db.CronTask.LogsEncoded.Set(task.encodeLogs()), - // db.CronTask.DataEncoded.Set(task.encodeData()), - // ).Tx()) - // } - - // err := c.prisma.Prisma.Transaction(txns...).Exec(ctx) - // if err != nil { - // return err - // } - return nil + if len(tasks) < 1 { + return nil + } + + var inserts []*db.CronTaskCreate + for _, t := range tasks { + t.OnCreated() + t.OnUpdated() + + inserts = append(inserts, + c.db.CronTask.Create(). + SetType(t.Type). + SetData(t.Data). + SetLogs(t.Logs). + SetStatus(t.Status). + SetTargets(t.Targets). + SetLastRun(t.LastRun.Unix()). + SetReferenceID(t.ReferenceID). + SetScheduledAfter(t.ScheduledAfter.Unix()), + ) + } + + return c.db.CronTask.CreateBulk(inserts...).Exec(ctx) } /* @@ -155,35 +146,33 @@ UpdateTasks will update all tasks passed in - this func will block until all other calls to task update funcs are done */ func (c *libsqlClient) UpdateTasks(ctx context.Context, tasks ...models.Task) error { - // if len(tasks) < 1 { - // return nil - // } - - // if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { - // return err - // } - // defer c.tasksUpdateSem.Release(1) - - // var txns []db.PrismaTransaction - // for _, task := range tasks { - // task.OnUpdated() - // txns = append(txns, c.prisma.CronTask. - // FindUnique(db.CronTask.ID.Equals(task.ID)). - // Update( - // db.CronTask.TargetsEncoded.Set(task.encodeTargets()), - // db.CronTask.Status.Set(string(task.Status)), - // db.CronTask.LastRun.Set(task.LastRun), - // db.CronTask.ScheduledAfter.Set(task.ScheduledAfter), - // db.CronTask.LogsEncoded.Set(task.encodeLogs()), - // db.CronTask.DataEncoded.Set(task.encodeData()), - // ).Tx()) - // } - - // err := c.prisma.Prisma.Transaction(txns...).Exec(ctx) - // if err != nil { - // return err - // } - return nil + if len(tasks) < 1 { + return nil + } + + tx, err := c.db.Tx(ctx) + if err != nil { + return err + } + + for _, t := range tasks { + t.OnUpdated() + + err := tx.CronTask.UpdateOneID(t.ID). + SetData(t.Data). + SetLogs(t.Logs). + SetStatus(t.Status). + SetTargets(t.Targets). + SetLastRun(t.LastRun.Unix()). + SetScheduledAfter(t.ScheduledAfter.Unix()). + Exec(ctx) + + if err != nil { + return rollback(tx, err) + } + } + + return tx.Commit() } /* @@ -191,18 +180,19 @@ DeleteTasks will delete all tasks matching by ids - this func will block until all other calls to task update funcs are done */ func (c *libsqlClient) DeleteTasks(ctx context.Context, ids ...string) error { - // if len(ids) < 1 { - // return nil - // } - - // if err := c.tasksUpdateSem.Acquire(ctx, 1); err != nil { - // return nil - // } - // defer c.tasksUpdateSem.Release(1) - - // _, err := c.prisma.CronTask.FindMany(db.CronTask.ID.In(ids)).Delete().Exec(ctx) - // if err != nil { - // return err - // } - return nil + if len(ids) < 1 { + return nil + } + + tx, err := c.db.Tx(ctx) + if err != nil { + return err + } + + _, err = tx.CronTask.Delete().Where(crontask.IDIn(ids...)).Exec(ctx) + if err != nil { + return rollback(tx, err) + } + + return tx.Commit() } diff --git a/internal/database/users.go b/internal/database/users.go index 83772c6b..7f63688b 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -2,23 +2,62 @@ package database import ( "context" + "time" + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" ) -// func (c UserConnection) FromModel(model *db.UserConnectionModel) UserConnection { -// c.ID = model.ID -// c.UserID = model.UserID -// c.Metadata = make(map[string]any) -// c.ReferenceID = model.ReferenceID -// c.Permissions = permissions.Parse(model.Permissions, permissions.Blank) +func toUser(record *db.User, connections []*db.UserConnection, subscriptions []*db.UserSubscription, content []*db.UserContent) models.User { + user := models.User{ + ID: record.ID, + Permissions: permissions.Parse(record.Permissions, permissions.Blank), + } + for _, c := range connections { + user.Connections = append(user.Connections, toUserConnection(c)) + } + for _, s := range subscriptions { + user.Subscriptions = append(user.Subscriptions, toUserSubscription(s)) + } + // for _, s := range content { + // user.Subscriptions = append(user.Subscriptions, toUserSubscription(s)) + // } + return user +} + +func toUserConnection(record *db.UserConnection) models.UserConnection { + return models.UserConnection{ + ID: record.ID, + Type: record.Type, + UserID: record.UserID, + ReferenceID: record.ReferenceID, + Permissions: permissions.Parse(record.Permissions, permissions.Blank), + Metadata: record.Metadata, + } +} + +func toUserSubscription(record *db.UserSubscription) models.UserSubscription { + return models.UserSubscription{ + ID: record.ID, + Type: record.Type, + UserID: record.UserID, + ReferenceID: record.ReferenceID, + ExpiresAt: time.Unix(record.ExpiresAt, 0), + Permissions: permissions.Parse(record.Permissions, permissions.Blank), + } +} -// c.Type = ConnectionType(model.Type) -// if model.MetadataEncoded != nil { -// _ = encoding.DecodeGob(model.MetadataEncoded, &c.Metadata) +// func toUserContent(record *db.UserSubscription) models.UserConnection { +// return models.UserSubscription{ +// ID: record.ID, +// Type: record.Type, +// UserID: record.UserID, +// ReferenceID: record.ReferenceID, +// ExpiresAt: time.Unix(record.ExpiresAt, 0), +// Permissions: permissions.Parse(record.Permissions, permissions.Blank), // } -// return c // } type userGetOpts struct { @@ -50,20 +89,20 @@ Gets or creates a user with specified ID - assumes the ID is valid */ func (c *libsqlClient) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { - // user, err := c.GetUserByID(ctx, id, opts...) - // if err != nil { - // if database.IsNotFound(err) { - // model, err := c.prisma.User.CreateOne(db.User.ID.Set(id), db.User.Permissions.Set(permissions.User.Encode())).Exec(ctx) - // if err != nil { - // return User{}, err - // } - // user.ID = model.ID - // user.Permissions = permissions.Parse(model.Permissions, permissions.User) - // } - // return user, nil - // } + user, err := c.GetUserByID(ctx, id, opts...) + if err != nil && !IsNotFound(err) { + return models.User{}, err + } + + if IsNotFound(err) { + record, err := c.db.User.Create().SetID(id).SetPermissions(permissions.User.String()).Save(ctx) + if err != nil { + return models.User{}, err + } + user = toUser(record, nil, nil, nil) + } - return models.User{}, nil + return user, nil } /* @@ -71,107 +110,85 @@ Gets a user with specified ID - assumes the ID is valid */ func (c *libsqlClient) GetUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { - // var options userGetOpts - // for _, apply := range opts { - // apply(&options) - // } - - // var fields []db.UserRelationWith - // if options.subscriptions { - // fields = append(fields, db.User.Subscriptions.Fetch()) - // } - // if options.connections { - // fields = append(fields, db.User.Connections.Fetch()) - // } - // if options.content { - // fields = append(fields, db.User.Content.Fetch()) - // } - - // model, err := c.prisma.User.FindUnique(db.User.ID.Equals(id)).With(fields...).Exec(ctx) - // if err != nil { - // return User{}, err - // } + var options userGetOpts + for _, apply := range opts { + apply(&options) + } - var user models.User - // user.ID = model.ID - // user.Permissions = permissions.Parse(model.Permissions, permissions.User) + query := c.db.User.Query().Where(user.ID(id)) + if options.subscriptions { + query = query.WithSubscriptions() + } + if options.connections { + query = query.WithConnections() + } + if options.content { + query.WithContent() + } - // if options.connections { - // for _, cModel := range model.Connections() { - // user.Connections = append(user.Connections, UserConnection{}.FromModel(&cModel)) - // } - // } + record, err := query.Only(ctx) + if err != nil { + return models.User{}, err + } - return user, nil + return toUser(record, record.Edges.Connections, record.Edges.Subscriptions, record.Edges.Content), nil } func (c *libsqlClient) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) { - // model, err := c.prisma.User.UpsertOne(db.User.ID.Equals(userID)). - // Create(db.User.ID.Set(userID), db.User.Permissions.Set(perms.String())). - // Update(db.User.Permissions.Set(perms.String())).Exec(ctx) - // if err != nil { - // return User{}, err - // } - - var user models.User - // user.ID = model.ID - // user.Permissions = permissions.Parse(model.Permissions, permissions.User) - return user, nil -} + record, err := c.db.User.UpdateOneID(userID).SetPermissions(perms.String()).Save(ctx) + if err != nil && !IsNotFound(err) { + return models.User{}, err + } -func (c *libsqlClient) UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { - // if connection.ReferenceID == "" { - // return UserConnection{}, errors.New("connection referenceID cannot be left blank") - // } + if IsNotFound(err) { + record, err = c.db.User.Create().SetID(userID).SetPermissions(perms.String()).Save(ctx) + if err != nil { + return models.User{}, err + } + } - // encoded, err := encoding.EncodeGob(connection.Metadata) - // if err != nil { - // return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) - // } + return toUser(record, nil, nil, nil), nil +} - // model, err := c.prisma.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( - // db.UserConnection.ReferenceID.Set(connection.ReferenceID), - // db.UserConnection.Permissions.Set(connection.Permissions.Encode()), - // db.UserConnection.MetadataEncoded.Set(encoded), - // ).Exec(ctx) - // if err != nil { - // return UserConnection{}, err - // } +func (c *libsqlClient) CreateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { + record, err := c.db.UserConnection.Create(). + SetUserID(connection.UserID). + SetMetadata(connection.Metadata). + SetPermissions(connection.Permissions.String()). + SetReferenceID(connection.ReferenceID). + SetType(connection.Type). + Save(ctx) + if err != nil { + return models.UserConnection{}, err + } + return toUserConnection(record), err +} - // return connection.FromModel(model), nil - return models.UserConnection{}, nil +func (c *libsqlClient) UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { + record, err := c.db.UserConnection.UpdateOneID(connection.ID). + SetMetadata(connection.Metadata). + SetPermissions(connection.Permissions.String()). + SetReferenceID(connection.ReferenceID). + SetType(connection.Type). + Save(ctx) + if err != nil { + return models.UserConnection{}, err + } + return toUserConnection(record), err } func (c *libsqlClient) UpsertConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { - // if connection.ID != "" { - // return c.UpdateConnection(ctx, connection) - // } - // if connection.UserID == "" { - // return UserConnection{}, errors.New("connection userID cannot be left blank") - // } - // if connection.ReferenceID == "" { - // return UserConnection{}, errors.New("connection referenceID cannot be left blank") - // } - // if connection.Type == "" { - // return UserConnection{}, errors.New("connection Type cannot be left blank") - // } - - // encoded, err := encoding.EncodeGob(connection.Metadata) - // if err != nil { - // return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) - // } + if connection.ID == "" { + return c.CreateConnection(ctx, connection) + } - // model, err := c.prisma.UserConnection.CreateOne( - // db.UserConnection.User.Link(db.User.ID.Equals(connection.UserID)), - // db.UserConnection.Type.Set(string(connection.Type)), - // db.UserConnection.Permissions.Set(connection.Permissions.Encode()), - // db.UserConnection.ReferenceID.Set(connection.ReferenceID), - // db.UserConnection.MetadataEncoded.Set(encoded), - // ).Exec(ctx) - // if err != nil { - // return UserConnection{}, err - // } + connection, err := c.UpdateConnection(ctx, connection) + if err != nil && !IsNotFound(err) { + return models.UserConnection{}, err + } + if IsNotFound(err) { + return c.CreateConnection(ctx, connection) + } - // return connection.FromModel(model), nil - return models.UserConnection{}, nil + return connection, nil } diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index e5cc80df..bdf141dd 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -3,74 +3,88 @@ package database import ( "context" + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/ent/db/vehicle" "github.com/cufee/aftermath/internal/database/models" ) -// func (v Vehicle) FromModel(model db.VehicleModel) Vehicle { -// v.ID = model.ID -// v.Tier = model.Tier -// v.Type = model.Type -// v.Nation = model.Nation -// v.LocalizedNames = make(map[string]string) -// if model.LocalizedNamesEncoded != nil { -// err := encoding.DecodeGob(model.LocalizedNamesEncoded, &v.LocalizedNames) -// if err != nil { -// log.Err(err).Str("id", v.ID).Msg("failed to decode vehicle localized names") -// } -// } -// return v -// } - -// func (v Vehicle) EncodeNames() []byte { -// encoded, _ := encoding.EncodeGob(v.LocalizedNames) -// return encoded -// } +func toVehicle(record *db.Vehicle) models.Vehicle { + return models.Vehicle{ + ID: record.ID, + Tier: record.Tier, + LocalizedNames: record.LocalizedNames, + } +} func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) error { - // if len(vehicles) < 1 { - // return nil - // } - - // var transactions []transaction.Transaction - // for id, data := range vehicles { - // transactions = append(transactions, c.prisma.Vehicle. - // UpsertOne(db.Vehicle.ID.Equals(id)). - // Create( - // db.Vehicle.ID.Set(id), - // db.Vehicle.Tier.Set(data.Tier), - // db.Vehicle.Type.Set(data.Type), - // db.Vehicle.Class.Set(data.Class), - // db.Vehicle.Nation.Set(data.Nation), - // db.Vehicle.LocalizedNamesEncoded.Set(data.EncodeNames()), - // ). - // Update( - // db.Vehicle.Tier.Set(data.Tier), - // db.Vehicle.Type.Set(data.Type), - // db.Vehicle.Class.Set(data.Class), - // db.Vehicle.Nation.Set(data.Nation), - // db.Vehicle.LocalizedNamesEncoded.Set(data.EncodeNames()), - // ).Tx(), - // ) - // } - - // return c.prisma.Prisma.Transaction(transactions...).Exec(ctx) - return nil + if len(vehicles) < 1 { + return nil + } + + var ids []string + for id := range vehicles { + ids = append(ids, id) + } + + tx, err := c.db.Tx(ctx) + if err != nil { + return err + } + + records, err := tx.Vehicle.Query().Where(vehicle.IDIn(ids...)).All(ctx) + if err != nil { + return rollback(tx, err) + } + + for _, r := range records { + v, ok := vehicles[r.ID] + if !ok { + continue + } + + err := tx.Vehicle.UpdateOneID(v.ID). + SetTier(v.Tier). + SetLocalizedNames(v.LocalizedNames). + Exec(ctx) + if err != nil { + return rollback(tx, err) + } + + delete(vehicles, v.ID) + } + + var inserts []*db.VehicleCreate + for id, v := range vehicles { + inserts = append(inserts, + c.db.Vehicle.Create(). + SetID(id). + SetTier(v.Tier). + SetLocalizedNames(v.LocalizedNames), + ) + } + + err = tx.Vehicle.CreateBulk(inserts...).Exec(ctx) + if err != nil { + return rollback(tx, err) + } + + return tx.Commit() } func (c *libsqlClient) GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) { - // if len(ids) < 1 { - // return nil, nil - // } + if len(ids) < 1 { + return nil, nil + } - // models, err := c.prisma.Vehicle.FindMany(db.Vehicle.ID.In(ids)).Exec(ctx) - // if err != nil { - // return nil, err - // } + records, err := c.db.Vehicle.Query().Where(vehicle.IDIn(ids...)).All(ctx) + if err != nil && !IsNotFound(err) { + return nil, err + } vehicles := make(map[string]models.Vehicle) - // for _, model := range models { - // vehicles[model.ID] = vehicles[model.ID].FromModel(model) - // } + for _, r := range records { + vehicles[r.ID] = toVehicle(r) + } return vehicles, nil } diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 752715e3..b836a158 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -89,8 +89,8 @@ func (c *multiSourceClient) Account(ctx context.Context, id string) (models.Acco ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - errMap := c.database.UpsertAccounts(ctx, []models.Account{account}) - if err := errMap[id]; err != nil { + err := c.database.UpsertAccounts(ctx, []models.Account{account}) + if err != nil { log.Err(err).Msg("failed to update account cache") } }() @@ -172,8 +172,8 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - errMap := c.database.UpsertAccounts(ctx, []models.Account{stats.Account}) - if err := errMap[id]; err != nil { + err := c.database.UpsertAccounts(ctx, []models.Account{stats.Account}) + if err != nil { log.Err(err).Msg("failed to update account cache") } }() From c21b121ccd7fa37b81c3526beb128df5a87eb485 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 21:57:12 -0400 Subject: [PATCH 092/341] fixed incorrect edges --- .env.example | 2 +- docker-compose.yaml | 5 ++++- internal/database/accounts.go | 4 +--- internal/database/snapshots.go | 8 ++++---- internal/database/users.go | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index d205038b..cb24daea 100644 --- a/.env.example +++ b/.env.example @@ -9,7 +9,7 @@ TRAEFIK_HOST="local.amth.one" DATABASE_DIR="tmp/" # URL to a libsql instance # https://github.com/tursodatabase/libsql/blob/main/docs/DOCKER.md -DATABASE_URL="libsql://0.0.0.0:8080" +DATABASE_URL="libsql://0.0.0.0:8080?tls=0" # Init INIT_GLOBAL_ADMIN_USER="" # Discord user ID for a user who will be assigned permissions.GlobalAdmin on startup, can be left blank diff --git a/docker-compose.yaml b/docker-compose.yaml index 57f25438..516f579f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -30,7 +30,7 @@ services: - .env environment: - PORT=3000 - - DATABASE_URL=libsql://aftermath-database:8080 + - DATABASE_URL=libsql://aftermath-database:8080?tls=0 depends_on: aftermath-database: condition: service_started @@ -57,3 +57,6 @@ services: networks: dokploy-network: external: true + +volumes: + step-ca-home: diff --git a/internal/database/accounts.go b/internal/database/accounts.go index ef7be030..b6fed2cd 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -99,7 +99,6 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc SetNickname(update.Nickname). SetPrivate(update.Private). SetLastBattleTime(update.LastBattleTime.Unix()). - SetClanID(update.ClanID). Exec(ctx) if err != nil { return rollback(tx, err) @@ -117,8 +116,7 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc SetNickname(a.Nickname). SetPrivate(a.Private). SetAccountCreatedAt(a.CreatedAt.Unix()). - SetLastBattleTime(a.LastBattleTime.Unix()). - SetClanID(a.ClanID), + SetLastBattleTime(a.LastBattleTime.Unix()), ) } diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 10636ec6..6657cd5e 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -60,11 +60,11 @@ func (c *libsqlClient) CreateVehicleSnapshots(ctx context.Context, snapshots ... c.db.VehicleSnapshot.Create(). SetType(data.Type). SetFrame(data.Stats). - SetAccountID(data.AccountID). SetVehicleID(data.VehicleID). SetReferenceID(data.ReferenceID). SetBattles(int(data.Stats.Battles.Float())). - SetLastBattleTime(data.LastBattleTime.Unix()), + SetLastBattleTime(data.LastBattleTime.Unix()). + SetAccount(c.db.Account.GetX(ctx, data.AccountID)), ) } @@ -127,7 +127,7 @@ func (c *libsqlClient) CreateAccountSnapshots(ctx context.Context, snapshots ... for _, s := range snapshots { inserts = append(inserts, c.db.AccountSnapshot.Create(). - SetAccountID(s.AccountID). + SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here SetCreatedAt(s.CreatedAt.Unix()). SetLastBattleTime(s.LastBattleTime.Unix()). SetRatingBattles(int(s.RatingBattles.Battles.Float())). @@ -168,7 +168,7 @@ func (c *libsqlClient) GetAccountSnapshot(ctx context.Context, accountID, refere where = append(where, accountsnapshot.CreatedAtGT(query.createdAfter.Unix())) } if query.createdBefore != nil { - where = append(where, accountsnapshot.CreatedAtLT(query.createdAfter.Unix())) + where = append(where, accountsnapshot.CreatedAtLT(query.createdBefore.Unix())) } record, err := c.db.AccountSnapshot.Query().Where(where...).First(ctx) diff --git a/internal/database/users.go b/internal/database/users.go index 7f63688b..79563d63 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -152,10 +152,10 @@ func (c *libsqlClient) UpsertUserWithPermissions(ctx context.Context, userID str func (c *libsqlClient) CreateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { record, err := c.db.UserConnection.Create(). - SetUserID(connection.UserID). - SetMetadata(connection.Metadata). + SetUser(c.db.User.GetX(ctx, connection.UserID)). SetPermissions(connection.Permissions.String()). SetReferenceID(connection.ReferenceID). + SetMetadata(connection.Metadata). SetType(connection.Type). Save(ctx) if err != nil { From caf73ed209bce1bbfedd6fe9aefd78aed0e8a770 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 22:10:19 -0400 Subject: [PATCH 093/341] fixed command and entrypoint --- Dockerfile.migrate | 2 +- docker-compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 16909e31..35c14771 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -11,4 +11,4 @@ RUN go mod download RUN go generate ./... RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/atlas -ENTRYPOINT ["sh", "-c", 'atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://0.0.0.0:8080'] +ENTRYPOINT ["atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "libsql+ws://0.0.0.0:8080"] diff --git a/docker-compose.yaml b/docker-compose.yaml index 516f579f..d0af6e91 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,7 +18,7 @@ services: depends_on: aftermath-database: condition: service_started - command: sh -c 'migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080"' + command: atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080" networks: - dokploy-network From 59a6b3d27298848291893111c4c287038599086c Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 22:12:46 -0400 Subject: [PATCH 094/341] added absolute path --- Dockerfile.migrate | 2 +- docker-compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 35c14771..9dd12738 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -11,4 +11,4 @@ RUN go mod download RUN go generate ./... RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/atlas -ENTRYPOINT ["atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "libsql+ws://0.0.0.0:8080"] +ENTRYPOINT ["/bin/atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "libsql+ws://0.0.0.0:8080"] diff --git a/docker-compose.yaml b/docker-compose.yaml index d0af6e91..fc89055c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,7 +18,7 @@ services: depends_on: aftermath-database: condition: service_started - command: atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080" + command: /bin/atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080" networks: - dokploy-network From 9e232c91fd16029ca4807d85b96659102bc43d02 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 22:13:42 -0400 Subject: [PATCH 095/341] downloading mod cache --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ddcfd59f..0730ea53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,8 @@ COPY go.mod go.sum ./ RUN go mod download COPY ./ ./ -# generate the Prisma Client Go client + +RUN go mod download RUN go generate ./... # build a fully standalone binary with zero dependencies From 4a698d061bec4750adde232d493eff79cc934893 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 22:18:19 -0400 Subject: [PATCH 096/341] changed back to entrypoint --- Dockerfile.migrate | 4 +++- docker-compose.yaml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 9dd12738..77fb6709 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -6,9 +6,11 @@ WORKDIR /migrate COPY ./internal/database/ent/migrate/migrations /migrations RUN git clone https://github.com/Cufee/atlas-libsql.git + WORKDIR /migrate/atlas-libsql/cmd/atlas + RUN go mod download RUN go generate ./... RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/atlas -ENTRYPOINT ["/bin/atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "libsql+ws://0.0.0.0:8080"] +ENTRYPOINT ["atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "libsql+ws://0.0.0.0:8080"] diff --git a/docker-compose.yaml b/docker-compose.yaml index fc89055c..2b35ff2f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,7 +18,7 @@ services: depends_on: aftermath-database: condition: service_started - command: /bin/atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080" + entrypoint: /bin/atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080" networks: - dokploy-network From 6c7b065590fd25a15c0eff8d65229df251b2e484 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 22:18:35 -0400 Subject: [PATCH 097/341] removed abs path --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 2b35ff2f..1ecda3df 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,7 +18,7 @@ services: depends_on: aftermath-database: condition: service_started - entrypoint: /bin/atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080" + entrypoint: atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080" networks: - dokploy-network From 3133f12c255b7fedb7ec58734144414ff937c3f1 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 22:52:20 -0400 Subject: [PATCH 098/341] removed unused volume --- Dockerfile | 4 ---- docker-compose.yaml | 3 --- 2 files changed, 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0730ea53..f4d80704 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,6 @@ FROM golang:1.22.3-alpine as builder WORKDIR /workspace -# add go modules lockfiles -COPY go.mod go.sum ./ -RUN go mod download - COPY ./ ./ RUN go mod download diff --git a/docker-compose.yaml b/docker-compose.yaml index 1ecda3df..a9c0161b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -57,6 +57,3 @@ services: networks: dokploy-network: external: true - -volumes: - step-ca-home: From 0d82859089d1dfe02424c2232b7a92bf1d4c116c Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 22 Jun 2024 22:54:17 -0400 Subject: [PATCH 099/341] added user to database --- docker-compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index a9c0161b..c2fbd072 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,6 @@ services: aftermath-database: + user: root image: ghcr.io/tursodatabase/libsql-server:latest platform: linux/amd64 restart: always From c6603d55c581510ac2cf2b37342d84bcf41a86bc Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 08:54:52 -0400 Subject: [PATCH 100/341] setting task status on create --- internal/database/models/task.go | 46 +++++++------------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/internal/database/models/task.go b/internal/database/models/task.go index bb1ad319..b81c8f5a 100644 --- a/internal/database/models/task.go +++ b/internal/database/models/task.go @@ -1,11 +1,7 @@ package models import ( - "encoding/json" - "strings" "time" - - "github.com/cufee/aftermath/internal/encoding" ) type TaskType string @@ -82,44 +78,22 @@ func (t *Task) LogAttempt(log TaskLog) { } func (t *Task) OnCreated() { + t.Status = TaskStatusScheduled t.LastRun = time.Now() t.CreatedAt = time.Now() t.UpdatedAt = time.Now() + t.Logs = append(t.Logs, TaskLog{ + Comment: "task created", + Timestamp: time.Now(), + }) } func (t *Task) OnUpdated() { + t.LastRun = time.Now() t.UpdatedAt = time.Now() -} - -func (t *Task) encodeTargets() []byte { - return []byte(strings.Join(t.Targets, ";")) -} -func (t *Task) decodeTargets(targets []byte) { - if string(targets) != "" { - t.Targets = strings.Split(string(targets), ";") - } -} - -func (t *Task) encodeLogs() []byte { - if t.Logs == nil { - return []byte{} - } - data, _ := json.Marshal(t.Logs) - return data -} -func (t *Task) decodeLogs(logs []byte) { - _ = json.Unmarshal(logs, &t.Logs) -} - -func (t *Task) encodeData() []byte { - if t.Data == nil { - return []byte{} - } - data, _ := encoding.EncodeGob(t.Data) - return data -} -func (t *Task) decodeData(data []byte) { - t.Data = make(map[string]any) - _ = encoding.DecodeGob(data, &t.Data) + t.Logs = append(t.Logs, TaskLog{ + Comment: "task updated", + Timestamp: time.Now(), + }) } type TaskLog struct { From 4348037a5a14a11bac5e4ee33f00c8d82ba8bc3c Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 09:24:06 -0400 Subject: [PATCH 101/341] error handling fixes, minor refactor, snapshots endpoint --- cmds/core/scheduler/tasks/sessions.go | 17 +++++++--- cmds/core/scheduler/workers.go | 2 +- .../core/server/handlers/private/snapshots.go | 34 +++++++++++++++++++ internal/database/accounts.go | 15 +++----- internal/database/averages.go | 2 +- internal/database/cleanup.go | 8 ++--- internal/database/discord.go | 2 +- internal/database/ent/db/crontask/crontask.go | 2 +- internal/database/ent/db/migrate/schema.go | 2 +- internal/database/models/task.go | 14 ++++---- internal/database/snapshots.go | 2 +- internal/database/tasks.go | 9 ++--- internal/database/vehicles.go | 4 +-- main.go | 14 +++++--- 14 files changed, 83 insertions(+), 44 deletions(-) create mode 100644 cmds/core/server/handlers/private/snapshots.go diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index 93ea0273..b4d6803b 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -14,7 +14,7 @@ import ( ) func init() { - defaultHandlers[models.TaskTypeRecordSessions] = TaskHandler{ + defaultHandlers[models.TaskTypeRecordSnapshots] = TaskHandler{ Process: func(client core.Client, task models.Task) (string, error) { if task.Data == nil { return "no data provided", errors.New("no data provided") @@ -72,10 +72,10 @@ func init() { } } -func CreateSessionUpdateTasks(client core.Client, realm string) error { +func CreateRecordSnapshotsTasks(client core.Client, realm string) error { realm = strings.ToUpper(realm) task := models.Task{ - Type: models.TaskTypeRecordSessions, + Type: models.TaskTypeRecordSnapshots, ReferenceID: "realm_" + realm, ScheduledAfter: time.Now(), Data: map[string]any{ @@ -92,10 +92,17 @@ func CreateSessionUpdateTasks(client core.Client, realm string) error { return err } if len(accounts) < 1 { - return nil + return errors.New("no accounts on realm " + realm) } task.Targets = append(task.Targets, accounts...) // This update requires (2 + n) requests per n players - return client.Database().CreateTasks(ctx, splitTaskByTargets(task, 90)...) + tasks := splitTaskByTargets(task, 90) + err = client.Database().CreateTasks(ctx, tasks...) + if err != nil { + return err + } + + log.Debug().Int("count", len(tasks)).Msg("scheduled realm account snapshots tasks") + return nil } diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index 683027be..fed10db4 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -37,7 +37,7 @@ func RotateBackgroundPresetsWorker(client core.Client) func() { func CreateSessionTasksWorker(client core.Client, realm string) func() { return func() { - err := tasks.CreateSessionUpdateTasks(client, realm) + err := tasks.CreateRecordSnapshotsTasks(client, realm) if err != nil { log.Err(err).Str("realm", realm).Msg("failed to schedule session update tasks") } diff --git a/cmds/core/server/handlers/private/snapshots.go b/cmds/core/server/handlers/private/snapshots.go new file mode 100644 index 00000000..a90a1ec8 --- /dev/null +++ b/cmds/core/server/handlers/private/snapshots.go @@ -0,0 +1,34 @@ +package private + +import ( + "net/http" + "slices" + "strings" + + "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler/tasks" +) + +var validRealms = []string{"na", "eu", "as"} + +func SaveRealmSnapshots(client core.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + realm := r.PathValue("realm") + if realm == "" { + http.Error(w, "realm is required", http.StatusBadRequest) + return + } + if !slices.Contains(validRealms, strings.ToLower(realm)) { + http.Error(w, realm+" is not a valid realm", http.StatusBadRequest) + return + } + + err := tasks.CreateRecordSnapshotsTasks(client, realm) + if err != nil { + http.Error(w, "failed to create tasks: "+err.Error(), http.StatusBadRequest) + return + } + + w.Write([]byte("tasks scheduled")) + } +} diff --git a/internal/database/accounts.go b/internal/database/accounts.go index b6fed2cd..344dc12b 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -27,17 +27,12 @@ func toAccount(model *db.Account) models.Account { } func (c *libsqlClient) GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) { - result, err := c.db.Account.Query().Where(account.Realm(strings.ToLower(realm))).Select(account.FieldID).All(ctx) + result, err := c.db.Account.Query().Where(account.Realm(strings.ToUpper(realm))).IDs(ctx) if err != nil { return nil, err } - var accounts []string - for _, model := range result { - accounts = append(accounts, model.ID) - } - - return accounts, nil + return result, nil } func (c *libsqlClient) GetAccountByID(ctx context.Context, id string) (models.Account, error) { @@ -95,7 +90,7 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc } err = tx.Account.UpdateOneID(r.ID). - SetRealm(update.Realm). + SetRealm(strings.ToUpper(update.Realm)). SetNickname(update.Nickname). SetPrivate(update.Private). SetLastBattleTime(update.LastBattleTime.Unix()). @@ -112,7 +107,7 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc inserts = append(inserts, c.db.Account.Create(). SetID(a.ID). - SetRealm(a.Realm). + SetRealm(strings.ToUpper(a.Realm)). SetNickname(a.Nickname). SetPrivate(a.Private). SetAccountCreatedAt(a.CreatedAt.Unix()). @@ -129,7 +124,7 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc func (c *libsqlClient) AccountSetPrivate(ctx context.Context, id string, value bool) error { err := c.db.Account.UpdateOneID(id).SetPrivate(value).Exec(ctx) - if err != nil && !IsNotFound(err) { + if err != nil { return err } return nil diff --git a/internal/database/averages.go b/internal/database/averages.go index 881307b3..0d7a47ed 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -24,7 +24,7 @@ func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[s } existing, err := tx.VehicleAverage.Query().Where(vehicleaverage.IDIn(ids...)).All(ctx) - if err != nil { + if err != nil && !IsNotFound(err) { return rollback(tx, err) } diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go index 89216aa7..d79a7f7b 100644 --- a/internal/database/cleanup.go +++ b/internal/database/cleanup.go @@ -17,7 +17,7 @@ func (c *libsqlClient) DeleteExpiredTasks(ctx context.Context, expiration time.T } _, err = tx.CronTask.Delete().Where(crontask.CreatedAtLT(expiration.Unix())).Exec(ctx) - if err != nil && !IsNotFound(err) { + if err != nil { return rollback(tx, err) } return tx.Commit() @@ -25,17 +25,17 @@ func (c *libsqlClient) DeleteExpiredTasks(ctx context.Context, expiration time.T func (c *libsqlClient) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { _, err := c.db.AccountSnapshot.Delete().Where(accountsnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) - if err != nil && !IsNotFound(err) { + if err != nil { return err } _, err = c.db.VehicleSnapshot.Delete().Where(vehiclesnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) - if err != nil && !IsNotFound(err) { + if err != nil { return err } _, err = c.db.AchievementsSnapshot.Delete().Where(achievementssnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) - if err != nil && !IsNotFound(err) { + if err != nil { return err } diff --git a/internal/database/discord.go b/internal/database/discord.go index 91f084b3..84ae76e2 100644 --- a/internal/database/discord.go +++ b/internal/database/discord.go @@ -69,7 +69,7 @@ func (c *libsqlClient) UpsertCommands(ctx context.Context, commands ...models.Ap } existing, err := tx.ApplicationCommand.Query().Where(applicationcommand.IDIn(ids...)).All(ctx) - if err != nil { + if err != nil && !IsNotFound(err) { return rollback(tx, err) } diff --git a/internal/database/ent/db/crontask/crontask.go b/internal/database/ent/db/crontask/crontask.go index f3ba2093..c83a8067 100644 --- a/internal/database/ent/db/crontask/crontask.go +++ b/internal/database/ent/db/crontask/crontask.go @@ -79,7 +79,7 @@ var ( // TypeValidator is a validator for the "type" field enum values. It is called by the builders before save. func TypeValidator(_type models.TaskType) error { switch _type { - case "UPDATE_CLANS", "RECORD_ACCOUNT_SESSIONS", "UPDATE_ACCOUNT_WN8", "UPDATE_ACCOUNT_ACHIEVEMENTS", "CLEANUP_DATABASE": + case "UPDATE_CLANS", "RECORD_SNAPSHOTS", "CLEANUP_DATABASE": return nil default: return fmt.Errorf("crontask: invalid enum value for type field: %q", _type) diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index f5c10f59..700be463 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -226,7 +226,7 @@ var ( {Name: "id", Type: field.TypeString, Unique: true}, {Name: "created_at", Type: field.TypeInt64}, {Name: "updated_at", Type: field.TypeInt64}, - {Name: "type", Type: field.TypeEnum, Enums: []string{"UPDATE_CLANS", "RECORD_ACCOUNT_SESSIONS", "UPDATE_ACCOUNT_WN8", "UPDATE_ACCOUNT_ACHIEVEMENTS", "CLEANUP_DATABASE"}}, + {Name: "type", Type: field.TypeEnum, Enums: []string{"UPDATE_CLANS", "RECORD_SNAPSHOTS", "CLEANUP_DATABASE"}}, {Name: "reference_id", Type: field.TypeString}, {Name: "targets", Type: field.TypeJSON}, {Name: "status", Type: field.TypeEnum, Enums: []string{"TASK_SCHEDULED", "TASK_IN_PROGRESS", "TASK_COMPLETE", "TASK_FAILED"}}, diff --git a/internal/database/models/task.go b/internal/database/models/task.go index b81c8f5a..c11caf6a 100644 --- a/internal/database/models/task.go +++ b/internal/database/models/task.go @@ -7,10 +7,10 @@ import ( type TaskType string const ( - TaskTypeUpdateClans TaskType = "UPDATE_CLANS" - TaskTypeRecordSessions TaskType = "RECORD_ACCOUNT_SESSIONS" - TaskTypeUpdateAccountWN8 TaskType = "UPDATE_ACCOUNT_WN8" - TaskTypeRecordPlayerAchievements TaskType = "UPDATE_ACCOUNT_ACHIEVEMENTS" + TaskTypeUpdateClans TaskType = "UPDATE_CLANS" + TaskTypeRecordSnapshots TaskType = "RECORD_SNAPSHOTS" + // TaskTypeUpdateAccountWN8 TaskType = "UPDATE_ACCOUNT_WN8" + // TaskTypeRecordPlayerAchievements TaskType = "UPDATE_ACCOUNT_ACHIEVEMENTS" TaskTypeDatabaseCleanup TaskType = "CLEANUP_DATABASE" ) @@ -20,9 +20,9 @@ func (TaskType) Values() []string { var kinds []string for _, s := range []TaskType{ TaskTypeUpdateClans, - TaskTypeRecordSessions, - TaskTypeUpdateAccountWN8, - TaskTypeRecordPlayerAchievements, + TaskTypeRecordSnapshots, + // TaskTypeUpdateAccountWN8, + // TaskTypeRecordPlayerAchievements, // TaskTypeDatabaseCleanup, } { diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 6657cd5e..5539ca43 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -94,7 +94,7 @@ func (c *libsqlClient) GetVehicleSnapshots(ctx context.Context, accountID, refer var records []*db.VehicleSnapshot err := c.db.VehicleSnapshot.Query().Where(where...).GroupBy(vehiclesnapshot.FieldVehicleID).Scan(ctx, &records) - if err != nil && !IsNotFound(err) { + if err != nil { return nil, err } var snapshots []models.VehicleSnapshot diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 57f3904b..63cb365e 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -39,7 +39,7 @@ func (c *libsqlClient) GetStaleTasks(ctx context.Context, limit int) ([]models.T Order(crontask.ByScheduledAfter(sql.OrderAsc())). Limit(limit). All(ctx) - if err != nil && !IsNotFound(err) { + if err != nil { return nil, err } @@ -62,7 +62,7 @@ func (c *libsqlClient) GetRecentTasks(ctx context.Context, createdAfter time.Tim } records, err := c.db.CronTask.Query().Where(where...).All(ctx) - if err != nil && !IsNotFound(err) { + if err != nil { return nil, err } @@ -89,9 +89,6 @@ func (c *libsqlClient) GetAndStartTasks(ctx context.Context, limit int) ([]model records, err := tx.CronTask.Query().Where(crontask.StatusEQ(models.TaskStatusScheduled), crontask.ScheduledAfterLT(time.Now().Unix())).Order(crontask.ByScheduledAfter(sql.OrderAsc())).Limit(limit).All(ctx) if err != nil { - if IsNotFound(err) { - return nil, nil - } return nil, rollback(tx, err) } @@ -107,7 +104,7 @@ func (c *libsqlClient) GetAndStartTasks(ctx context.Context, limit int) ([]model } err = tx.CronTask.Update().Where(crontask.IDIn(ids...)).SetStatus(models.TaskStatusInProgress).Exec(ctx) - if err != nil && !IsNotFound(err) { + if err != nil { return nil, rollback(tx, err) } diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index bdf141dd..2cd036ab 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -32,7 +32,7 @@ func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]m } records, err := tx.Vehicle.Query().Where(vehicle.IDIn(ids...)).All(ctx) - if err != nil { + if err != nil && !IsNotFound(err) { return rollback(tx, err) } @@ -77,7 +77,7 @@ func (c *libsqlClient) GetVehicles(ctx context.Context, ids []string) (map[strin } records, err := c.db.Vehicle.Query().Where(vehicle.IDIn(ids...)).All(ctx) - if err != nil && !IsNotFound(err) { + if err != nil { return nil, err } diff --git a/main.go b/main.go index a83c4d65..ff1bb377 100644 --- a/main.go +++ b/main.go @@ -54,10 +54,16 @@ func main() { if e := os.Getenv("PRIVATE_SERVER_ENABLED"); e == "true" { port := os.Getenv("PRIVATE_SERVER_PORT") - servePrivate := server.NewServer(port, server.Handler{ - Path: "POST /accounts/load", - Func: private.LoadAccountsHandler(cacheCoreClient), - }) + servePrivate := server.NewServer(port, []server.Handler{ + { + Path: "POST /accounts/import", + Func: private.LoadAccountsHandler(cacheCoreClient), + }, + { + Path: "POST /snapshots/{realm}", + Func: private.SaveRealmSnapshots(cacheCoreClient), + }, + }...) log.Info().Str("port", port).Msg("starting a private server") go servePrivate() } From d702da57169d6189a107d87b8366a7758c2fa1d8 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 09:46:42 -0400 Subject: [PATCH 102/341] better logging --- cmds/core/scheduler/glossary.go | 12 ++++++++-- cmds/core/scheduler/workers.go | 10 ++++++++ cmds/core/server/handlers/private/accounts.go | 16 +++++++++++-- internal/database/accounts.go | 15 ++++++------ internal/database/averages.go | 15 ++++++------ internal/database/client.go | 24 ++++++++++++------- internal/database/vehicles.go | 15 ++++++------ internal/stats/fetch/multisource.go | 10 ++++++-- 8 files changed, 81 insertions(+), 36 deletions(-) diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go index 14327f15..41887f80 100644 --- a/cmds/core/scheduler/glossary.go +++ b/cmds/core/scheduler/glossary.go @@ -24,11 +24,15 @@ func UpdateAveragesWorker(client core.Client) func() { return } - err = client.Database().UpsertVehicleAverages(ctx, averages) + aErr, err := client.Database().UpsertVehicleAverages(ctx, averages) if err != nil { log.Err(err).Msg("failed to update averages cache") return } + if len(aErr) > 0 { + log.Error().Any("errors", aErr).Msg("failed to update some average cache") + return + } log.Info().Msg("averages cache updated") } @@ -57,11 +61,15 @@ func UpdateGlossaryWorker(client core.Client) func() { } } - err = client.Database().UpsertVehicles(ctx, vehicles) + vErr, err := client.Database().UpsertVehicles(ctx, vehicles) if err != nil { log.Err(err).Msg("failed to save vehicle glossary") return } + if len(vErr) > 0 { + log.Error().Any("errors", vErr).Msg("failed to save some vehicle glossaries") + return + } log.Info().Msg("glossary cache updated") } diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index fed10db4..430fa2aa 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -6,6 +6,7 @@ import ( "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/cmds/core/scheduler/tasks" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/rs/zerolog/log" ) @@ -48,6 +49,7 @@ func RunTasksWorker(queue *Queue) func() { return func() { activeWorkers := queue.ActiveWorkers() if activeWorkers >= queue.concurrencyLimit { + log.Debug().Int("active", activeWorkers).Int("limit", queue.concurrencyLimit).Msg("no available workers to process tasks") return } @@ -59,6 +61,10 @@ func RunTasksWorker(queue *Queue) func() { batchSize := queue.concurrencyLimit - activeWorkers tasks, err := queue.core.Database().GetAndStartTasks(ctx, batchSize*10) if err != nil { + if database.IsNotFound(err) { + log.Debug().Msg("no scheduled tasks to process") + return + } log.Err(err).Msg("failed to start scheduled tasks") return } @@ -81,6 +87,10 @@ func RestartTasksWorker(queue *Queue) func() { staleTasks, err := queue.core.Database().GetStaleTasks(ctx, 100) if err != nil { + if database.IsNotFound(err) { + log.Debug().Msg("no failed tasks to reschedule found") + return + } log.Err(err).Msg("failed to reschedule stale tasks") return } diff --git a/cmds/core/server/handlers/private/accounts.go b/cmds/core/server/handlers/private/accounts.go index e1128c0d..84bcaf6f 100644 --- a/cmds/core/server/handlers/private/accounts.go +++ b/cmds/core/server/handlers/private/accounts.go @@ -63,6 +63,8 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { batchSize := 50 var wg sync.WaitGroup sem := semaphore.NewWeighted(5) + errors := make(map[string]error) + var errorsMx sync.Mutex for realm, accounts := range accountsByRealm { for i := 0; i < len(accounts); i += batchSize { @@ -96,16 +98,26 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { inserts = append(inserts, fetch.WargamingToAccount(realm, account, types.ClanMember{}, false)) } - err = client.Database().UpsertAccounts(ctx, inserts) + accErr, err := client.Database().UpsertAccounts(ctx, inserts) if err != nil { log.Err(err).Msg("failed to upsert accounts") - return + } + if len(accErr) > 0 { + errorsMx.Lock() + for id, err := range accErr { + errors[id] = err + } + errorsMx.Unlock() } }(accounts[i:end], realm) } } wg.Wait() + + if len(errors) > 0 { + log.Error().Any("errors", errors).Msg("some account imports failed") + } log.Info().Int("count", len(accounts)-len(existing)).Msg("finished importing accounts") }() } diff --git a/internal/database/accounts.go b/internal/database/accounts.go index 344dc12b..20dc64f2 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -61,9 +61,9 @@ func (c *libsqlClient) GetAccounts(ctx context.Context, ids []string) ([]models. return accounts, nil } -func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Account) error { +func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Account) (map[string]error, error) { if len(accounts) < 1 { - return nil + return nil, nil } var ids []string @@ -75,14 +75,15 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc tx, err := c.db.Tx(ctx) if err != nil { - return err + return nil, err } records, err := tx.Account.Query().Where(account.IDIn(ids...)).All(ctx) if err != nil && !IsNotFound(err) { - return rollback(tx, err) + return nil, rollback(tx, err) } + errors := make(map[string]error) for _, r := range records { update, ok := accountsMap[r.ID] if !ok { @@ -96,7 +97,7 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc SetLastBattleTime(update.LastBattleTime.Unix()). Exec(ctx) if err != nil { - return rollback(tx, err) + errors[r.ID] = err } delete(accountsMap, r.ID) @@ -117,9 +118,9 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc err = c.db.Account.CreateBulk(inserts...).Exec(ctx) if err != nil { - return rollback(tx, err) + return errors, rollback(tx, err) } - return tx.Commit() + return errors, tx.Commit() } func (c *libsqlClient) AccountSetPrivate(ctx context.Context, id string, value bool) error { diff --git a/internal/database/averages.go b/internal/database/averages.go index 0d7a47ed..326530bd 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -8,9 +8,9 @@ import ( "github.com/cufee/aftermath/internal/stats/frame" ) -func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { +func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) (map[string]error, error) { if len(averages) < 1 { - return nil + return nil, nil } var ids []string @@ -20,14 +20,15 @@ func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[s tx, err := c.db.Tx(ctx) if err != nil { - return err + return nil, err } existing, err := tx.VehicleAverage.Query().Where(vehicleaverage.IDIn(ids...)).All(ctx) if err != nil && !IsNotFound(err) { - return rollback(tx, err) + return nil, rollback(tx, err) } + errors := make(map[string]error) for _, r := range existing { update, ok := averages[r.ID] if !ok { @@ -36,7 +37,7 @@ func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[s err := tx.VehicleAverage.UpdateOneID(r.ID).SetData(update).Exec(ctx) if err != nil { - return rollback(tx, err) + errors[r.ID] = err } delete(averages, r.ID) @@ -53,10 +54,10 @@ func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[s err = tx.VehicleAverage.CreateBulk(inserts...).Exec(ctx) if err != nil { - return rollback(tx, err) + return nil, rollback(tx, err) } - return tx.Commit() + return nil, tx.Commit() } func (c *libsqlClient) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { diff --git a/internal/database/client.go b/internal/database/client.go index e1b4024d..e531688e 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -19,35 +19,39 @@ import ( var _ Client = &libsqlClient{} type AccountsClient interface { + GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) GetAccountByID(ctx context.Context, id string) (models.Account, error) GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) - GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) - UpsertAccounts(ctx context.Context, accounts []models.Account) error AccountSetPrivate(ctx context.Context, id string, value bool) error + UpsertAccounts(ctx context.Context, accounts []models.Account) (map[string]error, error) } type GlossaryClient interface { - GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) - UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) - UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) error + GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) + + UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) (map[string]error, error) + UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) (map[string]error, error) } type UsersClient interface { GetUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) + UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) + UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) UpsertConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) - UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) } type SnapshotsClient interface { + GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) (models.AccountSnapshot, error) CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) error GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) - GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) (models.AccountSnapshot, error) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.AccountSnapshot, error) - CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error + GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) + CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error + DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error } @@ -55,10 +59,12 @@ type TasksClient interface { CreateTasks(ctx context.Context, tasks ...models.Task) error UpdateTasks(ctx context.Context, tasks ...models.Task) error DeleteTasks(ctx context.Context, ids ...string) error + GetStaleTasks(ctx context.Context, limit int) ([]models.Task, error) + GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task, error) + DeleteExpiredTasks(ctx context.Context, expiration time.Time) error - GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) } type DiscordDataClient interface { diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index 2cd036ab..b9e4bf75 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -16,9 +16,9 @@ func toVehicle(record *db.Vehicle) models.Vehicle { } } -func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) error { +func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) (map[string]error, error) { if len(vehicles) < 1 { - return nil + return nil, nil } var ids []string @@ -28,14 +28,15 @@ func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]m tx, err := c.db.Tx(ctx) if err != nil { - return err + return nil, err } records, err := tx.Vehicle.Query().Where(vehicle.IDIn(ids...)).All(ctx) if err != nil && !IsNotFound(err) { - return rollback(tx, err) + return nil, rollback(tx, err) } + errors := make(map[string]error) for _, r := range records { v, ok := vehicles[r.ID] if !ok { @@ -47,7 +48,7 @@ func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]m SetLocalizedNames(v.LocalizedNames). Exec(ctx) if err != nil { - return rollback(tx, err) + errors[v.ID] = err } delete(vehicles, v.ID) @@ -65,10 +66,10 @@ func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]m err = tx.Vehicle.CreateBulk(inserts...).Exec(ctx) if err != nil { - return rollback(tx, err) + return errors, rollback(tx, err) } - return tx.Commit() + return errors, tx.Commit() } func (c *libsqlClient) GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) { diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index b836a158..b8e700f3 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -89,10 +89,13 @@ func (c *multiSourceClient) Account(ctx context.Context, id string) (models.Acco ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - err := c.database.UpsertAccounts(ctx, []models.Account{account}) + aErr, err := c.database.UpsertAccounts(ctx, []models.Account{account}) if err != nil { log.Err(err).Msg("failed to update account cache") } + if err := aErr[account.ID]; err != nil { + log.Err(err).Msg("failed to update account cache") + } }() return account, nil @@ -172,10 +175,13 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - err := c.database.UpsertAccounts(ctx, []models.Account{stats.Account}) + aErr, err := c.database.UpsertAccounts(ctx, []models.Account{stats.Account}) if err != nil { log.Err(err).Msg("failed to update account cache") } + if err := aErr[stats.Account.ID]; err != nil { + log.Err(err).Msg("failed to update account cache") + } }() return stats, nil From e265f72d7d413ce515ee2c6b574fd1f9ac5db1f5 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 09:57:26 -0400 Subject: [PATCH 103/341] creating records individually to not loose good data --- internal/database/accounts.go | 25 +++++++++++-------------- internal/database/averages.go | 21 ++++++++------------- internal/database/vehicles.go | 20 ++++++++------------ 3 files changed, 27 insertions(+), 39 deletions(-) diff --git a/internal/database/accounts.go b/internal/database/accounts.go index 20dc64f2..016e8a7f 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -103,23 +103,20 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc delete(accountsMap, r.ID) } - var inserts []*db.AccountCreate for _, a := range accountsMap { - inserts = append(inserts, - c.db.Account.Create(). - SetID(a.ID). - SetRealm(strings.ToUpper(a.Realm)). - SetNickname(a.Nickname). - SetPrivate(a.Private). - SetAccountCreatedAt(a.CreatedAt.Unix()). - SetLastBattleTime(a.LastBattleTime.Unix()), - ) + err := tx.Account.Create(). + SetID(a.ID). + SetRealm(strings.ToUpper(a.Realm)). + SetNickname(a.Nickname). + SetPrivate(a.Private). + SetAccountCreatedAt(a.CreatedAt.Unix()). + SetLastBattleTime(a.LastBattleTime.Unix()). + Exec(ctx) + if err != nil { + errors[a.ID] = err + } } - err = c.db.Account.CreateBulk(inserts...).Exec(ctx) - if err != nil { - return errors, rollback(tx, err) - } return errors, tx.Commit() } diff --git a/internal/database/averages.go b/internal/database/averages.go index 326530bd..4b052755 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -3,7 +3,6 @@ package database import ( "context" - "github.com/cufee/aftermath/internal/database/ent/db" "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" "github.com/cufee/aftermath/internal/stats/frame" ) @@ -43,21 +42,17 @@ func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[s delete(averages, r.ID) } - var inserts []*db.VehicleAverageCreate for id, frame := range averages { - inserts = append(inserts, - c.db.VehicleAverage.Create(). - SetID(id). - SetData(frame), - ) - } - - err = tx.VehicleAverage.CreateBulk(inserts...).Exec(ctx) - if err != nil { - return nil, rollback(tx, err) + err := tx.VehicleAverage.Create(). + SetID(id). + SetData(frame). + Exec(ctx) + if err != nil { + errors[id] = err + } } - return nil, tx.Commit() + return errors, tx.Commit() } func (c *libsqlClient) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index b9e4bf75..fa77af11 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -54,19 +54,15 @@ func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]m delete(vehicles, v.ID) } - var inserts []*db.VehicleCreate for id, v := range vehicles { - inserts = append(inserts, - c.db.Vehicle.Create(). - SetID(id). - SetTier(v.Tier). - SetLocalizedNames(v.LocalizedNames), - ) - } - - err = tx.Vehicle.CreateBulk(inserts...).Exec(ctx) - if err != nil { - return errors, rollback(tx, err) + err := tx.Vehicle.Create(). + SetID(id). + SetTier(v.Tier). + SetLocalizedNames(v.LocalizedNames). + Exec(ctx) + if err != nil { + errors[id] = err + } } return errors, tx.Commit() From cf35b43e5eb5c8e95a6157be5ad96892c5170c7c Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 10:03:50 -0400 Subject: [PATCH 104/341] logging errors as strings --- cmds/core/scheduler/glossary.go | 12 ++++++++++-- cmds/core/server/handlers/private/accounts.go | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go index 41887f80..a5715a1d 100644 --- a/cmds/core/scheduler/glossary.go +++ b/cmds/core/scheduler/glossary.go @@ -30,7 +30,11 @@ func UpdateAveragesWorker(client core.Client) func() { return } if len(aErr) > 0 { - log.Error().Any("errors", aErr).Msg("failed to update some average cache") + event := log.Error() + for id, err := range aErr { + event.Str(id, err.Error()) + } + event.Msg("failed to update some average cache") return } @@ -67,7 +71,11 @@ func UpdateGlossaryWorker(client core.Client) func() { return } if len(vErr) > 0 { - log.Error().Any("errors", vErr).Msg("failed to save some vehicle glossaries") + event := log.Error() + for id, err := range vErr { + event.Str(id, err.Error()) + } + event.Msg("failed to save some vehicle glossaries") return } diff --git a/cmds/core/server/handlers/private/accounts.go b/cmds/core/server/handlers/private/accounts.go index 84bcaf6f..7284f238 100644 --- a/cmds/core/server/handlers/private/accounts.go +++ b/cmds/core/server/handlers/private/accounts.go @@ -116,7 +116,11 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { wg.Wait() if len(errors) > 0 { - log.Error().Any("errors", errors).Msg("some account imports failed") + event := log.Error() + for id, err := range errors { + event.Str(id, err.Error()) + } + event.Msg("some account imports failed") } log.Info().Int("count", len(accounts)-len(existing)).Msg("finished importing accounts") }() From 81035cd03313fd919a6b26ed29cea1a75cd88004 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 10:10:41 -0400 Subject: [PATCH 105/341] handling bad and private accounts --- cmds/core/server/handlers/private/accounts.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/cmds/core/server/handlers/private/accounts.go b/cmds/core/server/handlers/private/accounts.go index 7284f238..f87e96a0 100644 --- a/cmds/core/server/handlers/private/accounts.go +++ b/cmds/core/server/handlers/private/accounts.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "strconv" "sync" "github.com/cufee/aftermath/cmds/core" @@ -94,8 +95,22 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { } var inserts []models.Account - for _, account := range data { - inserts = append(inserts, fetch.WargamingToAccount(realm, account, types.ClanMember{}, false)) + for id, account := range data { + if id == "" && account.ID == 0 { + log.Warn().Str("reason", "id is blank").Msg("wargaming returned a bad account") + continue + } + + var private bool + if account.ID == 0 { + account.ID, _ = strconv.Atoi(id) + private = true + } + if account.Nickname == "" { + account.Nickname = "@Hidden" + private = true + } + inserts = append(inserts, fetch.WargamingToAccount(realm, account, types.ClanMember{}, private)) } accErr, err := client.Database().UpsertAccounts(ctx, inserts) From a89cf7bb6c78507681b7d8ddd6b24d1880bf2d62 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 10:28:41 -0400 Subject: [PATCH 106/341] removed most transactions, required transactions depend on a lock --- internal/database/accounts.go | 23 ++++++++--------------- internal/database/averages.go | 24 +++++++++--------------- internal/database/cleanup.go | 3 ++- internal/database/client.go | 17 +++++++++++++++-- internal/database/discord.go | 3 ++- internal/database/tasks.go | 8 +++++--- internal/database/vehicles.go | 23 ++++++++--------------- 7 files changed, 49 insertions(+), 52 deletions(-) diff --git a/internal/database/accounts.go b/internal/database/accounts.go index 016e8a7f..bd1c0911 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -73,14 +73,9 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc accountsMap[a.ID] = &a } - tx, err := c.db.Tx(ctx) - if err != nil { - return nil, err - } - - records, err := tx.Account.Query().Where(account.IDIn(ids...)).All(ctx) + records, err := c.db.Account.Query().Where(account.IDIn(ids...)).All(ctx) if err != nil && !IsNotFound(err) { - return nil, rollback(tx, err) + return nil, err } errors := make(map[string]error) @@ -90,7 +85,7 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc continue // this should never happen tho } - err = tx.Account.UpdateOneID(r.ID). + err = c.db.Account.UpdateOneID(r.ID). SetRealm(strings.ToUpper(update.Realm)). SetNickname(update.Nickname). SetPrivate(update.Private). @@ -103,21 +98,19 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc delete(accountsMap, r.ID) } + var writes []*db.AccountCreate for _, a := range accountsMap { - err := tx.Account.Create(). + writes = append(writes, c.db.Account.Create(). SetID(a.ID). SetRealm(strings.ToUpper(a.Realm)). SetNickname(a.Nickname). SetPrivate(a.Private). SetAccountCreatedAt(a.CreatedAt.Unix()). - SetLastBattleTime(a.LastBattleTime.Unix()). - Exec(ctx) - if err != nil { - errors[a.ID] = err - } + SetLastBattleTime(a.LastBattleTime.Unix()), + ) } - return errors, tx.Commit() + return errors, c.db.Account.CreateBulk(writes...).Exec(ctx) } func (c *libsqlClient) AccountSetPrivate(ctx context.Context, id string, value bool) error { diff --git a/internal/database/averages.go b/internal/database/averages.go index 4b052755..fe183062 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -3,6 +3,7 @@ package database import ( "context" + "github.com/cufee/aftermath/internal/database/ent/db" "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" "github.com/cufee/aftermath/internal/stats/frame" ) @@ -17,14 +18,9 @@ func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[s ids = append(ids, id) } - tx, err := c.db.Tx(ctx) - if err != nil { - return nil, err - } - - existing, err := tx.VehicleAverage.Query().Where(vehicleaverage.IDIn(ids...)).All(ctx) + existing, err := c.db.VehicleAverage.Query().Where(vehicleaverage.IDIn(ids...)).All(ctx) if err != nil && !IsNotFound(err) { - return nil, rollback(tx, err) + return nil, err } errors := make(map[string]error) @@ -34,7 +30,7 @@ func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[s continue // should never happen tho } - err := tx.VehicleAverage.UpdateOneID(r.ID).SetData(update).Exec(ctx) + err := c.db.VehicleAverage.UpdateOneID(r.ID).SetData(update).Exec(ctx) if err != nil { errors[r.ID] = err } @@ -42,17 +38,15 @@ func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[s delete(averages, r.ID) } + var writes []*db.VehicleAverageCreate for id, frame := range averages { - err := tx.VehicleAverage.Create(). + writes = append(writes, c.db.VehicleAverage.Create(). SetID(id). - SetData(frame). - Exec(ctx) - if err != nil { - errors[id] = err - } + SetData(frame), + ) } - return errors, tx.Commit() + return errors, c.db.VehicleAverage.CreateBulk(writes...).Exec(ctx) } func (c *libsqlClient) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go index d79a7f7b..fc2bf367 100644 --- a/internal/database/cleanup.go +++ b/internal/database/cleanup.go @@ -11,10 +11,11 @@ import ( ) func (c *libsqlClient) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { - tx, err := c.db.Tx(ctx) + tx, cancel, err := c.txWithLock(ctx) if err != nil { return err } + defer cancel() _, err = tx.CronTask.Delete().Where(crontask.CreatedAtLT(expiration.Unix())).Exec(ctx) if err != nil { diff --git a/internal/database/client.go b/internal/database/client.go index e531688e..16fe9700 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "sync" "time" "github.com/cufee/aftermath/internal/database/ent/db" @@ -87,13 +88,24 @@ type Client interface { } type libsqlClient struct { - db *db.Client + db *db.Client + transactionLock *sync.Mutex } func (c *libsqlClient) Disconnect() error { return c.db.Close() } +func (c *libsqlClient) txWithLock(ctx context.Context) (*db.Tx, func(), error) { + c.transactionLock.Lock() + tx, err := c.db.Tx(ctx) + if err != nil { + c.transactionLock.Unlock() + return tx, func() {}, nil + } + return tx, c.transactionLock.Unlock, nil +} + func NewLibSQLClient(primaryUrl string) (*libsqlClient, error) { driver, err := sql.Open("libsql", primaryUrl) if err != nil { @@ -102,7 +114,8 @@ func NewLibSQLClient(primaryUrl string) (*libsqlClient, error) { dbClient := db.NewClient(db.Driver(entsql.OpenDB(dialect.SQLite, driver))) return &libsqlClient{ - db: dbClient, + transactionLock: &sync.Mutex{}, + db: dbClient, }, nil } diff --git a/internal/database/discord.go b/internal/database/discord.go index 84ae76e2..783b2ff0 100644 --- a/internal/database/discord.go +++ b/internal/database/discord.go @@ -63,10 +63,11 @@ func (c *libsqlClient) UpsertCommands(ctx context.Context, commands ...models.Ap commandsMap[c.ID] = &c } - tx, err := c.db.Tx(ctx) + tx, cancel, err := c.txWithLock(ctx) if err != nil { return err } + defer cancel() existing, err := tx.ApplicationCommand.Query().Where(applicationcommand.IDIn(ids...)).All(ctx) if err != nil && !IsNotFound(err) { diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 63cb365e..adbbec47 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -82,7 +82,7 @@ func (c *libsqlClient) GetAndStartTasks(ctx context.Context, limit int) ([]model return nil, nil } - tx, err := c.db.Tx(ctx) + tx, err := c.db.BeginTx(ctx, &sql.TxOptions{}) if err != nil { return nil, err } @@ -147,10 +147,11 @@ func (c *libsqlClient) UpdateTasks(ctx context.Context, tasks ...models.Task) er return nil } - tx, err := c.db.Tx(ctx) + tx, cancel, err := c.txWithLock(ctx) if err != nil { return err } + defer cancel() for _, t := range tasks { t.OnUpdated() @@ -181,10 +182,11 @@ func (c *libsqlClient) DeleteTasks(ctx context.Context, ids ...string) error { return nil } - tx, err := c.db.Tx(ctx) + tx, cancel, err := c.txWithLock(ctx) if err != nil { return err } + defer cancel() _, err = tx.CronTask.Delete().Where(crontask.IDIn(ids...)).Exec(ctx) if err != nil { diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index fa77af11..317c4b2a 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -26,14 +26,9 @@ func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]m ids = append(ids, id) } - tx, err := c.db.Tx(ctx) - if err != nil { - return nil, err - } - - records, err := tx.Vehicle.Query().Where(vehicle.IDIn(ids...)).All(ctx) + records, err := c.db.Vehicle.Query().Where(vehicle.IDIn(ids...)).All(ctx) if err != nil && !IsNotFound(err) { - return nil, rollback(tx, err) + return nil, err } errors := make(map[string]error) @@ -43,7 +38,7 @@ func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]m continue } - err := tx.Vehicle.UpdateOneID(v.ID). + err := c.db.Vehicle.UpdateOneID(v.ID). SetTier(v.Tier). SetLocalizedNames(v.LocalizedNames). Exec(ctx) @@ -54,18 +49,16 @@ func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]m delete(vehicles, v.ID) } + var writes []*db.VehicleCreate for id, v := range vehicles { - err := tx.Vehicle.Create(). + writes = append(writes, c.db.Vehicle.Create(). SetID(id). SetTier(v.Tier). - SetLocalizedNames(v.LocalizedNames). - Exec(ctx) - if err != nil { - errors[id] = err - } + SetLocalizedNames(v.LocalizedNames), + ) } - return errors, tx.Commit() + return errors, c.db.Vehicle.CreateBulk(writes...).Exec(ctx) } func (c *libsqlClient) GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) { From 9760712f23528659fc76bf7363ce5f17e11e9396 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 10:37:38 -0400 Subject: [PATCH 107/341] added id indexes --- Taskfile.yaml | 2 +- internal/database/ent/db/migrate/schema.go | 5 ++++ .../20240623143618_added_indexes_on_id.sql | 30 +++++++++++++++++++ .../database/ent/migrate/migrations/atlas.sum | 3 +- internal/database/ent/schema/account.go | 1 + .../database/ent/schema/account_snapshot.go | 1 + .../ent/schema/achievements_snapshot.go | 1 + .../database/ent/schema/app_configuration.go | 1 + .../ent/schema/application_command.go | 1 + internal/database/ent/schema/clan.go | 1 + internal/database/ent/schema/cron_task.go | 1 + internal/database/ent/schema/user.go | 5 +++- .../database/ent/schema/user_connection.go | 3 +- internal/database/ent/schema/user_content.go | 1 + .../database/ent/schema/user_subscription.go | 1 + internal/database/ent/schema/vehicle.go | 5 +++- .../database/ent/schema/vehicle_average.go | 5 +++- .../database/ent/schema/vehicle_snapshot.go | 1 + 18 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 internal/database/ent/migrate/migrations/20240623143618_added_indexes_on_id.sql diff --git a/Taskfile.yaml b/Taskfile.yaml index cd2e67c1..b36ae7ec 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -29,7 +29,7 @@ tasks: db-migrate: desc: generate migrations - cmd: atlas migrate diff {{.CLI_ARGS}} --dir "file://internal/database/ent/migrate/migrations" --to "ent://internal/database/ent/schema" --dev-url "sqlite://file?mode=memory&_fk=1" + cmd: atlas migrate hash --dir "file://internal/database/ent/migrate/migrations" && atlas migrate diff {{.CLI_ARGS}} --dir "file://internal/database/ent/migrate/migrations" --to "ent://internal/database/ent/schema" --dev-url "sqlite://file?mode=memory&_fk=1" db-migrate-apply: desc: apply migrations using atlas cmd: ${ATLAS_TEMP_DIR} migrate apply --allow-dirty --dir "file://internal/database/ent/migrate/migrations" --tx-mode all --url "libsql+ws://0.0.0.0:8080" diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index 700be463..6be5f4e7 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -34,6 +34,11 @@ var ( }, }, Indexes: []*schema.Index{ + { + Name: "account_id", + Unique: false, + Columns: []*schema.Column{AccountsColumns[0]}, + }, { Name: "account_id_last_battle_time", Unique: false, diff --git a/internal/database/ent/migrate/migrations/20240623143618_added_indexes_on_id.sql b/internal/database/ent/migrate/migrations/20240623143618_added_indexes_on_id.sql new file mode 100644 index 00000000..7314180c --- /dev/null +++ b/internal/database/ent/migrate/migrations/20240623143618_added_indexes_on_id.sql @@ -0,0 +1,30 @@ +-- Create index "account_id" to table: "accounts" +CREATE INDEX `account_id` ON `accounts` (`id`); +-- Create index "accountsnapshot_id" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_id` ON `account_snapshots` (`id`); +-- Create index "achievementssnapshot_id" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_id` ON `achievements_snapshots` (`id`); +-- Create index "appconfiguration_id" to table: "app_configurations" +CREATE INDEX `appconfiguration_id` ON `app_configurations` (`id`); +-- Create index "applicationcommand_id" to table: "application_commands" +CREATE INDEX `applicationcommand_id` ON `application_commands` (`id`); +-- Create index "clan_id" to table: "clans" +CREATE INDEX `clan_id` ON `clans` (`id`); +-- Create index "crontask_id" to table: "cron_tasks" +CREATE INDEX `crontask_id` ON `cron_tasks` (`id`); +-- Create index "user_id" to table: "users" +CREATE INDEX `user_id` ON `users` (`id`); +-- Create index "userconnection_id" to table: "user_connections" +CREATE INDEX `userconnection_id` ON `user_connections` (`id`); +-- Create index "userconnection_reference_id_user_id_type" to table: "user_connections" +CREATE UNIQUE INDEX `userconnection_reference_id_user_id_type` ON `user_connections` (`reference_id`, `user_id`, `type`); +-- Create index "usercontent_id" to table: "user_contents" +CREATE INDEX `usercontent_id` ON `user_contents` (`id`); +-- Create index "usersubscription_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_id` ON `user_subscriptions` (`id`); +-- Create index "vehicle_id" to table: "vehicles" +CREATE INDEX `vehicle_id` ON `vehicles` (`id`); +-- Create index "vehicleaverage_id" to table: "vehicle_averages" +CREATE INDEX `vehicleaverage_id` ON `vehicle_averages` (`id`); +-- Create index "vehiclesnapshot_id" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_id` ON `vehicle_snapshots` (`id`); diff --git a/internal/database/ent/migrate/migrations/atlas.sum b/internal/database/ent/migrate/migrations/atlas.sum index 4c057533..7cf2c24b 100644 --- a/internal/database/ent/migrate/migrations/atlas.sum +++ b/internal/database/ent/migrate/migrations/atlas.sum @@ -1,2 +1,3 @@ -h1:lIjXgF0PT71x+0IXOVk9uiIK/23v6AlTegTXIaTyyZI= +h1:hbF7dyumL2NNWMkQkprSMNkXUEeHsM+GMYMBPXWdjW0= 20240622203812_init.sql h1:Rt6NGvXbPBEpjg1pUFqn97FV8X3X2d1x7J2zOAAxolc= +20240623143618_added_indexes_on_id.sql h1:o57d1Hc6+wGscJp3VIWnCNrmQkBSeSKTEVKRSWmsc6k= diff --git a/internal/database/ent/schema/account.go b/internal/database/ent/schema/account.go index 1477f439..ea686293 100644 --- a/internal/database/ent/schema/account.go +++ b/internal/database/ent/schema/account.go @@ -49,6 +49,7 @@ func (Account) Edges() []ent.Edge { func (Account) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("id", "last_battle_time"), index.Fields("realm"), index.Fields("realm", "last_battle_time"), diff --git a/internal/database/ent/schema/account_snapshot.go b/internal/database/ent/schema/account_snapshot.go index cd9391eb..67473711 100644 --- a/internal/database/ent/schema/account_snapshot.go +++ b/internal/database/ent/schema/account_snapshot.go @@ -41,6 +41,7 @@ func (AccountSnapshot) Edges() []ent.Edge { func (AccountSnapshot) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("created_at"), index.Fields("type", "account_id", "created_at"), index.Fields("type", "account_id", "reference_id"), diff --git a/internal/database/ent/schema/achievements_snapshot.go b/internal/database/ent/schema/achievements_snapshot.go index 26258e9f..70d56852 100644 --- a/internal/database/ent/schema/achievements_snapshot.go +++ b/internal/database/ent/schema/achievements_snapshot.go @@ -37,6 +37,7 @@ func (AchievementsSnapshot) Edges() []ent.Edge { func (AchievementsSnapshot) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("created_at"), index.Fields("account_id", "reference_id"), index.Fields("account_id", "reference_id", "created_at"), diff --git a/internal/database/ent/schema/app_configuration.go b/internal/database/ent/schema/app_configuration.go index 96ce0b6e..4b87a818 100644 --- a/internal/database/ent/schema/app_configuration.go +++ b/internal/database/ent/schema/app_configuration.go @@ -28,6 +28,7 @@ func (AppConfiguration) Edges() []ent.Edge { func (AppConfiguration) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("key"), } } diff --git a/internal/database/ent/schema/application_command.go b/internal/database/ent/schema/application_command.go index 71f68cf3..d15db084 100644 --- a/internal/database/ent/schema/application_command.go +++ b/internal/database/ent/schema/application_command.go @@ -40,6 +40,7 @@ func (ApplicationCommand) Edges() []ent.Edge { func (ApplicationCommand) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("options_hash"), } } diff --git a/internal/database/ent/schema/clan.go b/internal/database/ent/schema/clan.go index 9aa1738c..6ff58d0f 100644 --- a/internal/database/ent/schema/clan.go +++ b/internal/database/ent/schema/clan.go @@ -59,6 +59,7 @@ func (Clan) Edges() []ent.Edge { func (Clan) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("tag"), index.Fields("name"), } diff --git a/internal/database/ent/schema/cron_task.go b/internal/database/ent/schema/cron_task.go index d329c600..34a6640e 100644 --- a/internal/database/ent/schema/cron_task.go +++ b/internal/database/ent/schema/cron_task.go @@ -37,6 +37,7 @@ func (CronTask) Edges() []ent.Edge { func (CronTask) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("reference_id"), index.Fields("status", "last_run"), index.Fields("status", "created_at"), diff --git a/internal/database/ent/schema/user.go b/internal/database/ent/schema/user.go index f821f9b0..be20a1be 100644 --- a/internal/database/ent/schema/user.go +++ b/internal/database/ent/schema/user.go @@ -4,6 +4,7 @@ import ( "entgo.io/ent" "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" ) // User holds the schema definition for the User entity. @@ -39,5 +40,7 @@ func (User) Edges() []ent.Edge { } func (User) Indexes() []ent.Index { - return []ent.Index{} + return []ent.Index{ + index.Fields("id"), + } } diff --git a/internal/database/ent/schema/user_connection.go b/internal/database/ent/schema/user_connection.go index bc04fe6b..8b4a1a28 100644 --- a/internal/database/ent/schema/user_connection.go +++ b/internal/database/ent/schema/user_connection.go @@ -55,10 +55,11 @@ func (UserConnection) Edges() []ent.Edge { func (UserConnection) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("user_id"), index.Fields("type", "user_id"), index.Fields("reference_id"), index.Fields("type", "reference_id"), - // index.Fields("reference_id").Edges("user").Unique(), + index.Fields("reference_id", "user_id", "type").Unique(), } } diff --git a/internal/database/ent/schema/user_content.go b/internal/database/ent/schema/user_content.go index 9895f2b6..6cc371b0 100644 --- a/internal/database/ent/schema/user_content.go +++ b/internal/database/ent/schema/user_content.go @@ -35,6 +35,7 @@ func (UserContent) Edges() []ent.Edge { func (UserContent) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("user_id"), index.Fields("type", "user_id"), index.Fields("reference_id"), diff --git a/internal/database/ent/schema/user_subscription.go b/internal/database/ent/schema/user_subscription.go index b156bea2..ad0b5439 100644 --- a/internal/database/ent/schema/user_subscription.go +++ b/internal/database/ent/schema/user_subscription.go @@ -35,6 +35,7 @@ func (UserSubscription) Edges() []ent.Edge { func (UserSubscription) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("user_id"), index.Fields("type", "user_id"), index.Fields("expires_at"), diff --git a/internal/database/ent/schema/vehicle.go b/internal/database/ent/schema/vehicle.go index c66424b1..167259db 100644 --- a/internal/database/ent/schema/vehicle.go +++ b/internal/database/ent/schema/vehicle.go @@ -3,6 +3,7 @@ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" ) // Vehicle holds the schema definition for the Vehicle entity. @@ -37,5 +38,7 @@ func (Vehicle) Edges() []ent.Edge { } func (Vehicle) Indexes() []ent.Index { - return nil + return []ent.Index{ + index.Fields("id"), + } } diff --git a/internal/database/ent/schema/vehicle_average.go b/internal/database/ent/schema/vehicle_average.go index eda545dd..57d1a05a 100644 --- a/internal/database/ent/schema/vehicle_average.go +++ b/internal/database/ent/schema/vehicle_average.go @@ -3,6 +3,7 @@ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" "github.com/cufee/aftermath/internal/stats/frame" ) @@ -34,5 +35,7 @@ func (VehicleAverage) Edges() []ent.Edge { } func (VehicleAverage) Indexes() []ent.Index { - return nil + return []ent.Index{ + index.Fields("id"), + } } diff --git a/internal/database/ent/schema/vehicle_snapshot.go b/internal/database/ent/schema/vehicle_snapshot.go index e8ee67e4..13fc124f 100644 --- a/internal/database/ent/schema/vehicle_snapshot.go +++ b/internal/database/ent/schema/vehicle_snapshot.go @@ -39,6 +39,7 @@ func (VehicleSnapshot) Edges() []ent.Edge { func (VehicleSnapshot) Indexes() []ent.Index { return []ent.Index{ + index.Fields("id"), index.Fields("created_at"), index.Fields("vehicle_id", "created_at"), index.Fields("account_id", "created_at"), From 41ad490de0ef47bdba4d8e440ff49d8acc95b4cf Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 10:43:32 -0400 Subject: [PATCH 108/341] split up migration image into stages --- Dockerfile.migrate | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 77fb6709..20bf0a7e 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -1,10 +1,7 @@ -FROM golang:1.22.3-bookworm +FROM golang:1.22.3-bookworm as builder WORKDIR /migrate -# copy migrations and schema -COPY ./internal/database/ent/migrate/migrations /migrations - RUN git clone https://github.com/Cufee/atlas-libsql.git WORKDIR /migrate/atlas-libsql/cmd/atlas @@ -13,4 +10,12 @@ RUN go mod download RUN go generate ./... RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/atlas +FROM scratch + +WORKDIR /migrations + +# copy migrations and binary +COPY ./internal/database/ent/migrate/migrations /migrations +COPY --from=builder /bin/atlas /bin/atlas + ENTRYPOINT ["atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "libsql+ws://0.0.0.0:8080"] From 2738b5e2c2738ffe315bce0aaf720807c9543eb8 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 11:00:10 -0400 Subject: [PATCH 109/341] manually adding cache for faster builds --- Dockerfile | 7 ++++--- Dockerfile.migrate | 10 +++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index f4d80704..6aadb593 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,14 @@ FROM golang:1.22.3-alpine as builder WORKDIR /workspace -COPY ./ ./ +COPY go.mod go.sum ./ +RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download -RUN go mod download +COPY ./ ./ RUN go generate ./... # build a fully standalone binary with zero dependencies -RUN CGO_ENABLED=0 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=0 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 20bf0a7e..b5a3dc70 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -1,14 +1,10 @@ FROM golang:1.22.3-bookworm as builder WORKDIR /migrate +RUN --mount=type=cache,target=/migrate git clone https://github.com/Cufee/atlas-libsql.git . || git pull -RUN git clone https://github.com/Cufee/atlas-libsql.git - -WORKDIR /migrate/atlas-libsql/cmd/atlas - -RUN go mod download -RUN go generate ./... -RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/atlas +WORKDIR /migrate/cmd/atlas +RUN --mount=type=cache,target=/migrate --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/atlas FROM scratch From 55ba76b4fe139a8d46c357adca4d0c960bee5882 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 11:01:15 -0400 Subject: [PATCH 110/341] fixed copy path --- Dockerfile.migrate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.migrate b/Dockerfile.migrate index b5a3dc70..e79c8cb2 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -12,6 +12,6 @@ WORKDIR /migrations # copy migrations and binary COPY ./internal/database/ent/migrate/migrations /migrations -COPY --from=builder /bin/atlas /bin/atlas +COPY --from=builder /bin/atlas /bin/ ENTRYPOINT ["atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "libsql+ws://0.0.0.0:8080"] From c984d0c533c9e32273cb70f04334d4b5bf300712 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 11:03:51 -0400 Subject: [PATCH 111/341] added cache for generate --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6aadb593..0ae63ee9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY go.mod go.sum ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ -RUN go generate ./... +RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... # build a fully standalone binary with zero dependencies RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=0 GOOS=linux go build -o app . From b4527c8af43f9b36151f0645bae24718cd398fa7 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 11:10:53 -0400 Subject: [PATCH 112/341] fixed migration image --- Dockerfile.migrate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.migrate b/Dockerfile.migrate index e79c8cb2..11f076c3 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -6,7 +6,7 @@ RUN --mount=type=cache,target=/migrate git clone https://github.com/Cufee/atlas- WORKDIR /migrate/cmd/atlas RUN --mount=type=cache,target=/migrate --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/atlas -FROM scratch +FROM golang:1.22.3-bookworm WORKDIR /migrations From 79fab7a575b7349b2237c239718972b6b359ce69 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 11:18:18 -0400 Subject: [PATCH 113/341] added endpoint to restart tasks --- cmds/core/scheduler/cron.go | 2 +- cmds/core/scheduler/workers.go | 9 +++++---- cmds/core/server/handlers/private/tasks.go | 15 +++++++++++++++ main.go | 8 ++++---- 4 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 cmds/core/server/handlers/private/tasks.go diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go index 144954f7..f17bccac 100644 --- a/cmds/core/scheduler/cron.go +++ b/cmds/core/scheduler/cron.go @@ -14,7 +14,7 @@ func (queue *Queue) StartCronJobsAsync() { // Tasks c.Cron("* * * * *").Do(RunTasksWorker(queue)) // some tasks might be stuck due to a panic or restart, restart them - c.Cron("0 * * * *").Do(RestartTasksWorker(queue)) + c.Cron("0 * * * *").Do(RestartTasksWorker(queue.core)) c.Cron("0 5 * * *").Do(CreateCleanupTaskWorker(queue.core)) // delete expired documents // Glossary - Do it around the same time WG releases game updates diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index 430fa2aa..69b76c10 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -80,15 +80,15 @@ func RunTasksWorker(queue *Queue) func() { } } -func RestartTasksWorker(queue *Queue) func() { +func RestartTasksWorker(core core.Client) func() { return func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer cancel() - staleTasks, err := queue.core.Database().GetStaleTasks(ctx, 100) + staleTasks, err := core.Database().GetStaleTasks(ctx, 100) if err != nil { if database.IsNotFound(err) { - log.Debug().Msg("no failed tasks to reschedule found") + log.Debug().Msg("no stale tasks found") return } log.Err(err).Msg("failed to reschedule stale tasks") @@ -97,6 +97,7 @@ func RestartTasksWorker(queue *Queue) func() { log.Debug().Int("count", len(staleTasks)).Msg("fetched stale tasks from database") if len(staleTasks) < 1 { + log.Debug().Msg("no stale tasks found") return } @@ -108,7 +109,7 @@ func RestartTasksWorker(queue *Queue) func() { } log.Debug().Int("count", len(staleTasks)).Msg("updating stale tasks") - err = queue.core.Database().UpdateTasks(ctx, staleTasks...) + err = core.Database().UpdateTasks(ctx, staleTasks...) if err != nil { log.Err(err).Msg("failed to update stale tasks") return diff --git a/cmds/core/server/handlers/private/tasks.go b/cmds/core/server/handlers/private/tasks.go new file mode 100644 index 00000000..1a520aa1 --- /dev/null +++ b/cmds/core/server/handlers/private/tasks.go @@ -0,0 +1,15 @@ +package private + +import ( + "net/http" + + "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler" +) + +func RestartStaleTasks(client core.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + scheduler.RestartTasksWorker(client)() + w.Write([]byte("stale tasks restarted")) + } +} diff --git a/main.go b/main.go index ff1bb377..74e593d3 100644 --- a/main.go +++ b/main.go @@ -55,6 +55,10 @@ func main() { if e := os.Getenv("PRIVATE_SERVER_ENABLED"); e == "true" { port := os.Getenv("PRIVATE_SERVER_PORT") servePrivate := server.NewServer(port, []server.Handler{ + { + Path: "POST /tasks/restart", + Func: private.RestartStaleTasks(cacheCoreClient), + }, { Path: "POST /accounts/import", Func: private.LoadAccountsHandler(cacheCoreClient), @@ -108,10 +112,6 @@ func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Cli }() queue.StartCronJobsAsync() - // Some tasks should run on startup - // scheduler.UpdateGlossaryWorker(coreClient)() - // scheduler.UpdateAveragesWorker(coreClient)() - // scheduler.CreateSessionTasksWorker(coreClient, "AS")() } func coreClientsFromEnv() (core.Client, core.Client) { From 54182d90036fb9ee0248c842800e27c10bd83074 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 11:18:52 -0400 Subject: [PATCH 114/341] order of ops --- cmds/core/scheduler/workers.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index 69b76c10..50b6cce6 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -94,12 +94,11 @@ func RestartTasksWorker(core core.Client) func() { log.Err(err).Msg("failed to reschedule stale tasks") return } - log.Debug().Int("count", len(staleTasks)).Msg("fetched stale tasks from database") - if len(staleTasks) < 1 { log.Debug().Msg("no stale tasks found") return } + log.Debug().Int("count", len(staleTasks)).Msg("fetched stale tasks from database") now := time.Now() for i, task := range staleTasks { From b8ae6a237af1228c945e7f8504a47badcee99a25 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 11:23:16 -0400 Subject: [PATCH 115/341] removed memory limit, bumped reservation --- docker-compose.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index c2fbd072..2030bc75 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -42,9 +42,7 @@ services: deploy: resources: reservations: - memory: 128m - limits: - memory: 256m + memory: 512m networks: - dokploy-network hostname: aftermath-service From 99825ed3d52ab17ee9c431f6aff48063b3cb115b Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 12:05:20 -0400 Subject: [PATCH 116/341] properly passing args to goroutines --- cmds/core/server/handlers/private/accounts.go | 4 +-- internal/database/accounts.go | 2 +- internal/database/snapshots.go | 2 +- internal/logic/sessions.go | 21 ++++++++----- internal/stats/fetch/multisource.go | 30 +++++++++++-------- internal/stats/session.go | 6 ++-- 6 files changed, 37 insertions(+), 28 deletions(-) diff --git a/cmds/core/server/handlers/private/accounts.go b/cmds/core/server/handlers/private/accounts.go index f87e96a0..90ef4164 100644 --- a/cmds/core/server/handlers/private/accounts.go +++ b/cmds/core/server/handlers/private/accounts.go @@ -45,7 +45,7 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { w.Write([]byte(fmt.Sprintf("working on %d accounts", len(accounts)-len(existing)))) log.Info().Int("count", len(accounts)-len(existing)).Msg("importing accounts") - go func() { + go func(accounts []string, existing []models.Account) { existingMap := make(map[string]struct{}) for _, a := range existing { existingMap[a.ID] = struct{}{} @@ -138,6 +138,6 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { event.Msg("some account imports failed") } log.Info().Int("count", len(accounts)-len(existing)).Msg("finished importing accounts") - }() + }(accounts, existing) } } diff --git a/internal/database/accounts.go b/internal/database/accounts.go index bd1c0911..1bbc47f7 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -36,7 +36,7 @@ func (c *libsqlClient) GetRealmAccountIDs(ctx context.Context, realm string) ([] } func (c *libsqlClient) GetAccountByID(ctx context.Context, id string) (models.Account, error) { - result, err := c.db.Account.Query().Where(account.ID(id)).WithClan().Only(ctx) + result, err := c.db.Account.Query().Where(account.ID(id)).WithClan().First(ctx) if err != nil { return models.Account{}, err } diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 5539ca43..24905a1e 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -85,7 +85,7 @@ func (c *libsqlClient) GetVehicleSnapshots(ctx context.Context, accountID, refer if query.createdAfter != nil { where = append(where, vehiclesnapshot.CreatedAtGT(query.createdAfter.Unix())) } - if query.createdAfter != nil { + if query.createdBefore != nil { where = append(where, vehiclesnapshot.CreatedAtLT(query.createdBefore.Unix())) } if query.vehicleIDs != nil { diff --git a/internal/logic/sessions.go b/internal/logic/sessions.go index 36f17b94..c554f184 100644 --- a/internal/logic/sessions.go +++ b/internal/logic/sessions.go @@ -29,7 +29,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl return nil, errors.Wrap(err, "failed to fetch accounts") } - // existing snaphsots for accounts + // existing snapshots for accounts existingSnapshots, err := dbClient.GetManyAccountSnapshots(ctx, accountIDs, models.SnapshotTypeDaily) if err != nil && !database.IsNotFound(err) { return nil, errors.Wrap(err, "failed to get existing snapshots") @@ -40,7 +40,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } // make a new slice just in case some accounts were not returned/are private - var validAccouts []string + var validAccounts []string for _, id := range accountIDs { data, ok := accounts[id] if !ok { @@ -63,24 +63,26 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl // last snapshot is the same, we can skip it continue } - validAccouts = append(validAccouts, id) + validAccounts = append(validAccounts, id) } - if len(validAccouts) == 0 { + println("validAccounts", len(validAccounts)) + + if len(validAccounts) < 1 { return nil, nil } // clans are options-ish - clans, err := wgClient.BatchAccountClan(ctx, realm, validAccouts) + clans, err := wgClient.BatchAccountClan(ctx, realm, validAccounts) if err != nil { log.Err(err).Msg("failed to get batch account clans") clans = make(map[string]types.ClanMember) } - vehicleCh := make(chan retry.DataWithErr[vehicleResponseData], len(validAccouts)) + vehicleCh := make(chan retry.DataWithErr[vehicleResponseData], len(validAccounts)) var group sync.WaitGroup - group.Add(len(validAccouts)) - for _, id := range validAccouts { + group.Add(len(validAccounts)) + for _, id := range validAccounts { go func(id string) { defer group.Done() data, err := wgClient.AccountVehicles(ctx, realm, id) @@ -161,11 +163,14 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } } + println("account", len(snapshots)) + err = dbClient.CreateAccountSnapshots(ctx, snapshots...) if err != nil { return nil, errors.Wrap(err, "failed to save account snapshots to database") } + println("vehicles", len(vehicleSnapshots)) err = dbClient.CreateVehicleSnapshots(ctx, vehicleSnapshots...) if err != nil { return nil, errors.Wrap(err, "failed to save vehicle snapshots to database") diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index b8e700f3..92c3b7a9 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -85,8 +85,8 @@ func (c *multiSourceClient) Account(ctx context.Context, id string) (models.Acco } account := WargamingToAccount(realm, wgAccount.Data, clan, false) - go func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + go func(account models.Account) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() aErr, err := c.database.UpsertAccounts(ctx, []models.Account{account}) @@ -96,7 +96,7 @@ func (c *multiSourceClient) Account(ctx context.Context, id string) (models.Acco if err := aErr[account.ID]; err != nil { log.Err(err).Msg("failed to update account cache") } - }() + }(account) return account, nil } @@ -171,18 +171,18 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. stats.AddWN8(averages.Data) } - go func() { + go func(account models.Account) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - aErr, err := c.database.UpsertAccounts(ctx, []models.Account{stats.Account}) + aErr, err := c.database.UpsertAccounts(ctx, []models.Account{account}) if err != nil { log.Err(err).Msg("failed to update account cache") } - if err := aErr[stats.Account.ID]; err != nil { + if err := aErr[account.ID]; err != nil { log.Err(err).Msg("failed to update account cache") } - }() + }(stats.Account) return stats, nil } @@ -313,6 +313,11 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session defer group.Done() s, err := c.database.GetAccountSnapshot(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) accountSnapshot = retry.DataWithErr[models.AccountSnapshot]{Data: s, Err: err} + }() + + group.Add(1) + go func() { + defer group.Done() v, err := c.database.GetVehicleSnapshots(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) vehiclesSnapshots = retry.DataWithErr[[]models.VehicleSnapshot]{Data: v, Err: err} }() @@ -353,9 +358,9 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session career.RatingBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, 0) career.RegularBattles.Vehicles = make(map[string]frame.VehicleStatsFrame, len(vehiclesSnapshots.Data)) - snapshotsMap := make(map[string]int, len(vehiclesSnapshots.Data)) - for i, data := range vehiclesSnapshots.Data { - snapshotsMap[data.VehicleID] = i + snapshotsMap := make(map[string]*models.VehicleSnapshot, len(vehiclesSnapshots.Data)) + for _, data := range vehiclesSnapshots.Data { + snapshotsMap[data.VehicleID] = &data career.RegularBattles.Vehicles[data.VehicleID] = frame.VehicleStatsFrame{ LastBattleTime: data.LastBattleTime, @@ -365,16 +370,15 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session } for id, current := range current.Data.RegularBattles.Vehicles { - snapshotIndex, exists := snapshotsMap[id] + snapshot, exists := snapshotsMap[id] if !exists { session.RegularBattles.Vehicles[id] = current continue } - - snapshot := vehiclesSnapshots.Data[snapshotIndex] if current.Battles == 0 || current.Battles == snapshot.Stats.Battles { continue } + current.StatsFrame.Subtract(snapshot.Stats) session.RegularBattles.Vehicles[id] = current } diff --git a/internal/stats/session.go b/internal/stats/session.go index 22ac652a..f398291c 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -28,16 +28,16 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time if err != nil { return nil, meta, err } - go func() { + go func(id string) { // record a session in the background ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer cancel() - _, err := logic.RecordAccountSnapshots(ctx, r.wargaming, r.database, r.wargaming.RealmFromAccountID(accountId), false, accountId) + _, err := logic.RecordAccountSnapshots(ctx, r.wargaming, r.database, r.wargaming.RealmFromAccountID(id), false, id) if err != nil { log.Err(err).Str("accountId", accountId).Msg("failed to record account snapshot") } - }() + }(accountId) return nil, meta, ErrAccountNotTracked } return nil, meta, err From 221a1dd569683f2b5f4a91664bcf56a86ef6e56d Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 12:26:48 -0400 Subject: [PATCH 117/341] fixed snapshots queries --- cmds/discord/rest/client.go | 1 - internal/database/snapshots.go | 13 +++++++++---- internal/logic/sessions.go | 5 ----- internal/stats/session.go | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/cmds/discord/rest/client.go b/cmds/discord/rest/client.go index 730af69c..4307d565 100644 --- a/cmds/discord/rest/client.go +++ b/cmds/discord/rest/client.go @@ -130,7 +130,6 @@ func (c *Client) do(req *http.Request, target any) error { if message == "" { message = res.Status + ", response was not valid json" } - println(string(data)) return errors.New("discord error: " + message) } diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 24905a1e..611af829 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -4,6 +4,7 @@ import ( "context" "time" + "entgo.io/ent/dialect/sql" "github.com/cufee/aftermath/internal/database/ent/db" "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -82,7 +83,9 @@ func (c *libsqlClient) GetVehicleSnapshots(ctx context.Context, accountID, refer where = append(where, vehiclesnapshot.ReferenceID(referenceID)) where = append(where, vehiclesnapshot.TypeEQ(kind)) + order := vehiclesnapshot.ByCreatedAt(sql.OrderDesc()) if query.createdAfter != nil { + order = vehiclesnapshot.ByCreatedAt(sql.OrderAsc()) where = append(where, vehiclesnapshot.CreatedAtGT(query.createdAfter.Unix())) } if query.createdBefore != nil { @@ -92,8 +95,8 @@ func (c *libsqlClient) GetVehicleSnapshots(ctx context.Context, accountID, refer where = append(where, vehiclesnapshot.VehicleIDIn(query.vehicleIDs...)) } - var records []*db.VehicleSnapshot - err := c.db.VehicleSnapshot.Query().Where(where...).GroupBy(vehiclesnapshot.FieldVehicleID).Scan(ctx, &records) + var records db.VehicleSnapshots + err := c.db.VehicleSnapshot.Query().Where(where...).Order(order).GroupBy(vehiclesnapshot.FieldVehicleID).Aggregate(func(s *sql.Selector) string { return s.Select("*").String() }).Scan(ctx, &records) if err != nil { return nil, err } @@ -143,7 +146,7 @@ func (c *libsqlClient) CreateAccountSnapshots(ctx context.Context, snapshots ... } func (c *libsqlClient) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) { - records, err := c.db.AccountSnapshot.Query().Where(accountsnapshot.AccountID(accountID)).Limit(limit).All(ctx) + records, err := c.db.AccountSnapshot.Query().Where(accountsnapshot.AccountID(accountID)).Order(accountsnapshot.ByCreatedAt(sql.OrderDesc())).Limit(limit).All(ctx) if err != nil { return nil, err } @@ -163,15 +166,17 @@ func (c *libsqlClient) GetAccountSnapshot(ctx context.Context, accountID, refere } var where []predicate.AccountSnapshot + order := accountsnapshot.ByCreatedAt(sql.OrderDesc()) where = append(where, accountsnapshot.AccountID(accountID), accountsnapshot.ReferenceID(referenceID), accountsnapshot.TypeEQ(kind)) if query.createdAfter != nil { + order = accountsnapshot.ByCreatedAt(sql.OrderAsc()) where = append(where, accountsnapshot.CreatedAtGT(query.createdAfter.Unix())) } if query.createdBefore != nil { where = append(where, accountsnapshot.CreatedAtLT(query.createdBefore.Unix())) } - record, err := c.db.AccountSnapshot.Query().Where(where...).First(ctx) + record, err := c.db.AccountSnapshot.Query().Where(where...).Order(order).First(ctx) if err != nil { return models.AccountSnapshot{}, err } diff --git a/internal/logic/sessions.go b/internal/logic/sessions.go index c554f184..b816cd8e 100644 --- a/internal/logic/sessions.go +++ b/internal/logic/sessions.go @@ -66,8 +66,6 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl validAccounts = append(validAccounts, id) } - println("validAccounts", len(validAccounts)) - if len(validAccounts) < 1 { return nil, nil } @@ -163,14 +161,11 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } } - println("account", len(snapshots)) - err = dbClient.CreateAccountSnapshots(ctx, snapshots...) if err != nil { return nil, errors.Wrap(err, "failed to save account snapshots to database") } - println("vehicles", len(vehicleSnapshots)) err = dbClient.CreateVehicleSnapshots(ctx, vehicleSnapshots...) if err != nil { return nil, errors.Wrap(err, "failed to save vehicle snapshots to database") diff --git a/internal/stats/session.go b/internal/stats/session.go index f398291c..a4bfd6ed 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -35,7 +35,7 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time _, err := logic.RecordAccountSnapshots(ctx, r.wargaming, r.database, r.wargaming.RealmFromAccountID(id), false, id) if err != nil { - log.Err(err).Str("accountId", accountId).Msg("failed to record account snapshot") + log.Err(err).Str("accountId", id).Msg("failed to record account snapshot") } }(accountId) return nil, meta, ErrAccountNotTracked From 914d1d7f87bdcdb3ca56c3c04760968616e0c8e1 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 12:37:03 -0400 Subject: [PATCH 118/341] added panic recovery in tasks --- .env.example | 2 +- cmds/core/scheduler/queue.go | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index cb24daea..247efc77 100644 --- a/.env.example +++ b/.env.example @@ -39,7 +39,7 @@ DISCORD_ERROR_REPORT_WEBHOOK_URL="" # A Discord webhook URL for a channel where # Optional components SCHEDULER_ENABLED="true" # Scheduler is responsible for refreshing glossary cache and recording sessions -SCHEDULER_CONCURRENT_WORKERS="10" +SCHEDULER_CONCURRENT_WORKERS="5" PRIVATE_SERVER_ENABLED="true" # A private server can be used to access some managemenet endpoints PRIVATE_SERVER_PORT="9093" diff --git a/cmds/core/scheduler/queue.go b/cmds/core/scheduler/queue.go index d0bd8c8d..669a3c9c 100644 --- a/cmds/core/scheduler/queue.go +++ b/cmds/core/scheduler/queue.go @@ -59,13 +59,24 @@ func (q *Queue) Process(callback func(error), tasks ...models.Task) { for _, task := range tasks { wg.Add(1) go func(t models.Task) { - q.limiter <- struct{}{} defer func() { + if r := recover(); r != nil { + log.Error().Any("recover", r).Msg("panic in task handler") + t.Status = models.TaskStatusFailed + t.LogAttempt(models.TaskLog{ + Targets: t.Targets, + Timestamp: time.Now(), + Error: "task handler caused a panic", + }) + } else { + log.Debug().Msgf("finished processing task %s", t.ID) + } processedTasks <- t - wg.Done() <-q.limiter - log.Debug().Msgf("finished processing task %s", t.ID) + wg.Done() }() + + q.limiter <- struct{}{} log.Debug().Msgf("processing task %s", t.ID) handler, ok := q.handlers[t.Type] From e635b72a91fe07edbaefb3fb0fe57f9bb0d3c914 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:16:06 -0400 Subject: [PATCH 119/341] swapped to sqlite --- .env.example | 14 +++--------- Dockerfile.migrate | 16 ++------------ Taskfile.yaml | 10 +-------- docker-compose.yaml | 33 +++++++++------------------- internal/database/accounts.go | 10 ++++----- internal/database/averages.go | 4 ++-- internal/database/cleanup.go | 4 ++-- internal/database/client.go | 22 ++++++++----------- internal/database/discord.go | 6 ++--- internal/database/ent/schema/clan.go | 18 --------------- internal/database/snapshots.go | 12 +++++----- internal/database/tasks.go | 12 +++++----- internal/database/users.go | 12 +++++----- internal/database/vehicles.go | 4 ++-- main.go | 2 +- 15 files changed, 58 insertions(+), 121 deletions(-) diff --git a/.env.example b/.env.example index 247efc77..f49d5131 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,5 @@ -# Atlas is currently broken for libsql -# You need to clone and build https://github.com/Cufee/atlas-libsql -ATLAS_TEMP_DIR="path/to/atlas-libsql/cmd/atlas/binary" - -# This is not required for local deployment. When deploying with Dokploy, this is the domain aftermath service will be available on. -TRAEFIK_HOST="local.amth.one" - # Path for a bind volume when running with compose -DATABASE_DIR="tmp/" -# URL to a libsql instance -# https://github.com/tursodatabase/libsql/blob/main/docs/DOCKER.md -DATABASE_URL="libsql://0.0.0.0:8080?tls=0" +DATABASE_PATH="tmp/deb_local.db" # Init INIT_GLOBAL_ADMIN_USER="" # Discord user ID for a user who will be assigned permissions.GlobalAdmin on startup, can be left blank @@ -44,6 +34,8 @@ PRIVATE_SERVER_ENABLED="true" # A private server can be used to access some mana PRIVATE_SERVER_PORT="9093" # Misc configuration +# This is not required for local deployment using compose. When deploying with Dokploy, this is the domain aftermath service will be available on. +TRAEFIK_HOST="local.amth.one" LOG_LEVEL="debug" NETWORK="tcp" PORT="9092" diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 11f076c3..701982ba 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -1,17 +1,5 @@ -FROM golang:1.22.3-bookworm as builder +FROM arigaio/atlas:latest -WORKDIR /migrate -RUN --mount=type=cache,target=/migrate git clone https://github.com/Cufee/atlas-libsql.git . || git pull - -WORKDIR /migrate/cmd/atlas -RUN --mount=type=cache,target=/migrate --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/atlas - -FROM golang:1.22.3-bookworm - -WORKDIR /migrations - -# copy migrations and binary COPY ./internal/database/ent/migrate/migrations /migrations -COPY --from=builder /bin/atlas /bin/ -ENTRYPOINT ["atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "libsql+ws://0.0.0.0:8080"] +ENTRYPOINT ["atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "sqlite:///database.db?_fk=1"] diff --git a/Taskfile.yaml b/Taskfile.yaml index b36ae7ec..3d6889d7 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -3,10 +3,6 @@ version: "3" dotenv: - ".env" -vars: - ENT_CMD: go run -mod=mod entgo.io/ent/cmd/ent - ENT_DIRECTORY: internal/database/ent/schema - tasks: test: desc: runs tests @@ -19,10 +15,6 @@ tasks: go test -timeout 30s --count=1 -v -run {{ .CLI_ARGS }} fi - ent-new: - desc: init a new schema with a given name - cmd: '{{.ENT_CMD}} new --target {{.ENT_DIRECTORY}} {{.CLI_ARGS}}' - ent-generate: desc: generate go code for the ent schema directory cmd: go generate internal/database/ent @@ -32,7 +24,7 @@ tasks: cmd: atlas migrate hash --dir "file://internal/database/ent/migrate/migrations" && atlas migrate diff {{.CLI_ARGS}} --dir "file://internal/database/ent/migrate/migrations" --to "ent://internal/database/ent/schema" --dev-url "sqlite://file?mode=memory&_fk=1" db-migrate-apply: desc: apply migrations using atlas - cmd: ${ATLAS_TEMP_DIR} migrate apply --allow-dirty --dir "file://internal/database/ent/migrate/migrations" --tx-mode all --url "libsql+ws://0.0.0.0:8080" + cmd: atlas migrate apply --allow-dirty --dir "file://internal/database/ent/migrate/migrations" --tx-mode all --url "sqlite://${DATABASE_PATH}?_fk=1" dev: desc: Start a local dev server diff --git a/docker-compose.yaml b/docker-compose.yaml index 2030bc75..f9c25702 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,43 +1,30 @@ services: - aftermath-database: - user: root - image: ghcr.io/tursodatabase/libsql-server:latest - platform: linux/amd64 - restart: always - environment: - - SQLD_NODE=primary - volumes: - - ${DATABASE_DIR}:/var/lib/sqld - networks: - - dokploy-network - hostname: aftermath-database - aftermath-migrate: image: aftermath-migrate build: dockerfile: Dockerfile.migrate - depends_on: - aftermath-database: - condition: service_started - entrypoint: atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "libsql+ws://aftermath-database:8080" + entrypoint: atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "sqlite:///data/aftermath.db?_fk=1" + volumes: + - ${DATABASE_PATH}:/data/aftermath.db networks: - dokploy-network aftermath-service: - image: aftermath-service - build: - dockerfile: Dockerfile env_file: - .env environment: - PORT=3000 - - DATABASE_URL=libsql://aftermath-database:8080?tls=0 + - DATABASE_PATH=sqlite:///data/aftermath.db?_fk=1 depends_on: - aftermath-database: - condition: service_started aftermath-migrate: condition: service_completed_successfully entrypoint: ["app"] + volumes: + - ${DATABASE_PATH}:/data/aftermath.db + + image: aftermath-service + build: + dockerfile: Dockerfile restart: always deploy: resources: diff --git a/internal/database/accounts.go b/internal/database/accounts.go index 1bbc47f7..ee9007d5 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -26,7 +26,7 @@ func toAccount(model *db.Account) models.Account { return account } -func (c *libsqlClient) GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) { +func (c *client) GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) { result, err := c.db.Account.Query().Where(account.Realm(strings.ToUpper(realm))).IDs(ctx) if err != nil { return nil, err @@ -35,7 +35,7 @@ func (c *libsqlClient) GetRealmAccountIDs(ctx context.Context, realm string) ([] return result, nil } -func (c *libsqlClient) GetAccountByID(ctx context.Context, id string) (models.Account, error) { +func (c *client) GetAccountByID(ctx context.Context, id string) (models.Account, error) { result, err := c.db.Account.Query().Where(account.ID(id)).WithClan().First(ctx) if err != nil { return models.Account{}, err @@ -43,7 +43,7 @@ func (c *libsqlClient) GetAccountByID(ctx context.Context, id string) (models.Ac return toAccount(result), nil } -func (c *libsqlClient) GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) { +func (c *client) GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) { if len(ids) < 1 { return nil, nil } @@ -61,7 +61,7 @@ func (c *libsqlClient) GetAccounts(ctx context.Context, ids []string) ([]models. return accounts, nil } -func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Account) (map[string]error, error) { +func (c *client) UpsertAccounts(ctx context.Context, accounts []models.Account) (map[string]error, error) { if len(accounts) < 1 { return nil, nil } @@ -113,7 +113,7 @@ func (c *libsqlClient) UpsertAccounts(ctx context.Context, accounts []models.Acc return errors, c.db.Account.CreateBulk(writes...).Exec(ctx) } -func (c *libsqlClient) AccountSetPrivate(ctx context.Context, id string, value bool) error { +func (c *client) AccountSetPrivate(ctx context.Context, id string, value bool) error { err := c.db.Account.UpdateOneID(id).SetPrivate(value).Exec(ctx) if err != nil { return err diff --git a/internal/database/averages.go b/internal/database/averages.go index fe183062..e643beb4 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -8,7 +8,7 @@ import ( "github.com/cufee/aftermath/internal/stats/frame" ) -func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) (map[string]error, error) { +func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) (map[string]error, error) { if len(averages) < 1 { return nil, nil } @@ -49,7 +49,7 @@ func (c *libsqlClient) UpsertVehicleAverages(ctx context.Context, averages map[s return errors, c.db.VehicleAverage.CreateBulk(writes...).Exec(ctx) } -func (c *libsqlClient) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { +func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { if len(ids) < 1 { return nil, nil } diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go index fc2bf367..4340f281 100644 --- a/internal/database/cleanup.go +++ b/internal/database/cleanup.go @@ -10,7 +10,7 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" ) -func (c *libsqlClient) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { +func (c *client) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { tx, cancel, err := c.txWithLock(ctx) if err != nil { return err @@ -24,7 +24,7 @@ func (c *libsqlClient) DeleteExpiredTasks(ctx context.Context, expiration time.T return tx.Commit() } -func (c *libsqlClient) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { +func (c *client) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { _, err := c.db.AccountSnapshot.Delete().Where(accountsnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) if err != nil { return err diff --git a/internal/database/client.go b/internal/database/client.go index 16fe9700..f2cc1c77 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -2,7 +2,6 @@ package database import ( "context" - "database/sql" "fmt" "sync" "time" @@ -12,12 +11,10 @@ import ( "github.com/cufee/aftermath/internal/permissions" "github.com/cufee/aftermath/internal/stats/frame" - "entgo.io/ent/dialect" - entsql "entgo.io/ent/dialect/sql" - _ "github.com/tursodatabase/libsql-client-go/libsql" + _ "github.com/mattn/go-sqlite3" ) -var _ Client = &libsqlClient{} +var _ Client = &client{} type AccountsClient interface { GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) @@ -87,16 +84,16 @@ type Client interface { Disconnect() error } -type libsqlClient struct { +type client struct { db *db.Client transactionLock *sync.Mutex } -func (c *libsqlClient) Disconnect() error { +func (c *client) Disconnect() error { return c.db.Close() } -func (c *libsqlClient) txWithLock(ctx context.Context) (*db.Tx, func(), error) { +func (c *client) txWithLock(ctx context.Context) (*db.Tx, func(), error) { c.transactionLock.Lock() tx, err := c.db.Tx(ctx) if err != nil { @@ -106,16 +103,15 @@ func (c *libsqlClient) txWithLock(ctx context.Context) (*db.Tx, func(), error) { return tx, c.transactionLock.Unlock, nil } -func NewLibSQLClient(primaryUrl string) (*libsqlClient, error) { - driver, err := sql.Open("libsql", primaryUrl) +func NewSQLiteClient(filePath string) (*client, error) { + c, err := db.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&_fk=1", filePath)) if err != nil { return nil, err } - dbClient := db.NewClient(db.Driver(entsql.OpenDB(dialect.SQLite, driver))) - return &libsqlClient{ + return &client{ transactionLock: &sync.Mutex{}, - db: dbClient, + db: c, }, nil } diff --git a/internal/database/discord.go b/internal/database/discord.go index 783b2ff0..c78498ee 100644 --- a/internal/database/discord.go +++ b/internal/database/discord.go @@ -18,7 +18,7 @@ func toApplicationCommand(record *db.ApplicationCommand) models.ApplicationComma } -func (c *libsqlClient) GetCommandsByID(ctx context.Context, commandIDs ...string) ([]models.ApplicationCommand, error) { +func (c *client) GetCommandsByID(ctx context.Context, commandIDs ...string) ([]models.ApplicationCommand, error) { if len(commandIDs) < 1 { return nil, nil } @@ -35,7 +35,7 @@ func (c *libsqlClient) GetCommandsByID(ctx context.Context, commandIDs ...string return commands, nil } -func (c *libsqlClient) GetCommandsByHash(ctx context.Context, commandHashes ...string) ([]models.ApplicationCommand, error) { +func (c *client) GetCommandsByHash(ctx context.Context, commandHashes ...string) ([]models.ApplicationCommand, error) { if len(commandHashes) < 1 { return nil, nil } @@ -51,7 +51,7 @@ func (c *libsqlClient) GetCommandsByHash(ctx context.Context, commandHashes ...s return commands, nil } -func (c *libsqlClient) UpsertCommands(ctx context.Context, commands ...models.ApplicationCommand) error { +func (c *client) UpsertCommands(ctx context.Context, commands ...models.ApplicationCommand) error { if len(commands) < 1 { return nil } diff --git a/internal/database/ent/schema/clan.go b/internal/database/ent/schema/clan.go index 6ff58d0f..0aa605e3 100644 --- a/internal/database/ent/schema/clan.go +++ b/internal/database/ent/schema/clan.go @@ -7,24 +7,6 @@ import ( "entgo.io/ent/schema/index" ) -// model Clan { -// id String @id -// createdAt DateTime -// updatedAt DateTime @updatedAt - -// tag String -// name String -// emblemId String @default("") - -// accounts Account[] -// membersString String - -// recordUpdatedAt DateTime @updatedAt - -// @@index([tag]) -// @@map("account_clans") -// } - // Clan holds the schema definition for the Clan entity. type Clan struct { ent.Schema diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 611af829..88cb5465 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -50,7 +50,7 @@ func toVehicleSnapshot(record *db.VehicleSnapshot) models.VehicleSnapshot { } } -func (c *libsqlClient) CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error { +func (c *client) CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error { if len(snapshots) < 1 { return nil } @@ -72,7 +72,7 @@ func (c *libsqlClient) CreateVehicleSnapshots(ctx context.Context, snapshots ... return c.db.VehicleSnapshot.CreateBulk(inserts...).Exec(ctx) } -func (c *libsqlClient) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) { +func (c *client) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) { var query getSnapshotQuery for _, apply := range options { apply(&query) @@ -121,7 +121,7 @@ func toAccountSnapshot(record *db.AccountSnapshot) models.AccountSnapshot { } } -func (c *libsqlClient) CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) error { +func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) error { if len(snapshots) < 1 { return nil } @@ -145,7 +145,7 @@ func (c *libsqlClient) CreateAccountSnapshots(ctx context.Context, snapshots ... return c.db.AccountSnapshot.CreateBulk(inserts...).Exec(ctx) } -func (c *libsqlClient) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) { +func (c *client) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) { records, err := c.db.AccountSnapshot.Query().Where(accountsnapshot.AccountID(accountID)).Order(accountsnapshot.ByCreatedAt(sql.OrderDesc())).Limit(limit).All(ctx) if err != nil { return nil, err @@ -159,7 +159,7 @@ func (c *libsqlClient) GetLastAccountSnapshots(ctx context.Context, accountID st return snapshots, nil } -func (c *libsqlClient) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) (models.AccountSnapshot, error) { +func (c *client) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) (models.AccountSnapshot, error) { var query getSnapshotQuery for _, apply := range options { apply(&query) @@ -184,7 +184,7 @@ func (c *libsqlClient) GetAccountSnapshot(ctx context.Context, accountID, refere return toAccountSnapshot(record), nil } -func (c *libsqlClient) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.AccountSnapshot, error) { +func (c *client) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.AccountSnapshot, error) { var query getSnapshotQuery for _, apply := range options { apply(&query) diff --git a/internal/database/tasks.go b/internal/database/tasks.go index adbbec47..56de971a 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -30,7 +30,7 @@ func toCronTask(record *db.CronTask) models.Task { /* Returns up limit tasks that have TaskStatusInProgress and were last updates 1+ hours ago */ -func (c *libsqlClient) GetStaleTasks(ctx context.Context, limit int) ([]models.Task, error) { +func (c *client) GetStaleTasks(ctx context.Context, limit int) ([]models.Task, error) { records, err := c.db.CronTask.Query(). Where( crontask.StatusEQ(models.TaskStatusInProgress), @@ -54,7 +54,7 @@ func (c *libsqlClient) GetStaleTasks(ctx context.Context, limit int) ([]models.T /* Returns all tasks that were created after createdAfter, sorted by ScheduledAfter (DESC) */ -func (c *libsqlClient) GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) { +func (c *client) GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) { var where []predicate.CronTask where = append(where, crontask.CreatedAtGT(createdAfter.Unix())) if len(status) > 0 { @@ -77,7 +77,7 @@ func (c *libsqlClient) GetRecentTasks(ctx context.Context, createdAfter time.Tim /* GetAndStartTasks retrieves up to limit number of tasks matching the referenceId and updates their status to in progress */ -func (c *libsqlClient) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task, error) { +func (c *client) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task, error) { if limit < 1 { return nil, nil } @@ -111,7 +111,7 @@ func (c *libsqlClient) GetAndStartTasks(ctx context.Context, limit int) ([]model return tasks, tx.Commit() } -func (c *libsqlClient) CreateTasks(ctx context.Context, tasks ...models.Task) error { +func (c *client) CreateTasks(ctx context.Context, tasks ...models.Task) error { if len(tasks) < 1 { return nil } @@ -142,7 +142,7 @@ UpdateTasks will update all tasks passed in - the following fields will be replaced: targets, status, leastRun, scheduleAfterm logs, data - this func will block until all other calls to task update funcs are done */ -func (c *libsqlClient) UpdateTasks(ctx context.Context, tasks ...models.Task) error { +func (c *client) UpdateTasks(ctx context.Context, tasks ...models.Task) error { if len(tasks) < 1 { return nil } @@ -177,7 +177,7 @@ func (c *libsqlClient) UpdateTasks(ctx context.Context, tasks ...models.Task) er DeleteTasks will delete all tasks matching by ids - this func will block until all other calls to task update funcs are done */ -func (c *libsqlClient) DeleteTasks(ctx context.Context, ids ...string) error { +func (c *client) DeleteTasks(ctx context.Context, ids ...string) error { if len(ids) < 1 { return nil } diff --git a/internal/database/users.go b/internal/database/users.go index 79563d63..e044b34e 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -88,7 +88,7 @@ func WithContent() userGetOption { Gets or creates a user with specified ID - assumes the ID is valid */ -func (c *libsqlClient) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { +func (c *client) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { user, err := c.GetUserByID(ctx, id, opts...) if err != nil && !IsNotFound(err) { return models.User{}, err @@ -109,7 +109,7 @@ func (c *libsqlClient) GetOrCreateUserByID(ctx context.Context, id string, opts Gets a user with specified ID - assumes the ID is valid */ -func (c *libsqlClient) GetUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { +func (c *client) GetUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { var options userGetOpts for _, apply := range opts { apply(&options) @@ -134,7 +134,7 @@ func (c *libsqlClient) GetUserByID(ctx context.Context, id string, opts ...userG return toUser(record, record.Edges.Connections, record.Edges.Subscriptions, record.Edges.Content), nil } -func (c *libsqlClient) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) { +func (c *client) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) { record, err := c.db.User.UpdateOneID(userID).SetPermissions(perms.String()).Save(ctx) if err != nil && !IsNotFound(err) { return models.User{}, err @@ -150,7 +150,7 @@ func (c *libsqlClient) UpsertUserWithPermissions(ctx context.Context, userID str return toUser(record, nil, nil, nil), nil } -func (c *libsqlClient) CreateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { +func (c *client) CreateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { record, err := c.db.UserConnection.Create(). SetUser(c.db.User.GetX(ctx, connection.UserID)). SetPermissions(connection.Permissions.String()). @@ -164,7 +164,7 @@ func (c *libsqlClient) CreateConnection(ctx context.Context, connection models.U return toUserConnection(record), err } -func (c *libsqlClient) UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { +func (c *client) UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { record, err := c.db.UserConnection.UpdateOneID(connection.ID). SetMetadata(connection.Metadata). SetPermissions(connection.Permissions.String()). @@ -177,7 +177,7 @@ func (c *libsqlClient) UpdateConnection(ctx context.Context, connection models.U return toUserConnection(record), err } -func (c *libsqlClient) UpsertConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { +func (c *client) UpsertConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { if connection.ID == "" { return c.CreateConnection(ctx, connection) } diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index 317c4b2a..dde1c3b3 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -16,7 +16,7 @@ func toVehicle(record *db.Vehicle) models.Vehicle { } } -func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) (map[string]error, error) { +func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) (map[string]error, error) { if len(vehicles) < 1 { return nil, nil } @@ -61,7 +61,7 @@ func (c *libsqlClient) UpsertVehicles(ctx context.Context, vehicles map[string]m return errors, c.db.Vehicle.CreateBulk(writes...).Exec(ctx) } -func (c *libsqlClient) GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) { +func (c *client) GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) { if len(ids) < 1 { return nil, nil } diff --git a/main.go b/main.go index 74e593d3..25fbfc3d 100644 --- a/main.go +++ b/main.go @@ -116,7 +116,7 @@ func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Cli func coreClientsFromEnv() (core.Client, core.Client) { // Dependencies - dbClient, err := database.NewLibSQLClient(os.Getenv("DATABASE_URL")) + dbClient, err := database.NewSQLiteClient(os.Getenv("DATABASE_PATH")) if err != nil { log.Fatal().Msgf("database#NewClient failed %s", err) } From 8248e9471a83f19f84ae95088540b9b4f6f439de Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:16:38 -0400 Subject: [PATCH 120/341] tidy --- go.mod | 2 +- go.sum | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 330fc17a..bf5fd037 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/joho/godotenv v1.5.1 github.com/lucsky/cuid v1.2.1 + github.com/mattn/go-sqlite3 v1.14.22 github.com/nlpodyssey/gopickle v0.3.0 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 @@ -39,7 +40,6 @@ require ( github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 844c602f..24ee26f5 100644 --- a/go.sum +++ b/go.sum @@ -67,23 +67,17 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -94,10 +88,6 @@ github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -158,8 +148,6 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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= From d85e5f183824befe246fdf72856c931789cb1272 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:18:12 -0400 Subject: [PATCH 121/341] fixed command --- Dockerfile.migrate | 2 +- docker-compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 701982ba..8f065468 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -2,4 +2,4 @@ FROM arigaio/atlas:latest COPY ./internal/database/ent/migrate/migrations /migrations -ENTRYPOINT ["atlas", "migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "sqlite:///database.db?_fk=1"] +ENTRYPOINT ["migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "sqlite:///database.db?_fk=1"] diff --git a/docker-compose.yaml b/docker-compose.yaml index f9c25702..28597efb 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,7 +3,7 @@ services: image: aftermath-migrate build: dockerfile: Dockerfile.migrate - entrypoint: atlas migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "sqlite:///data/aftermath.db?_fk=1" + entrypoint: migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "sqlite:///data/aftermath.db?_fk=1" volumes: - ${DATABASE_PATH}:/data/aftermath.db networks: From 8d775a547cd1210cacafb72fdd8eb2bd90fa3a0c Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:29:48 -0400 Subject: [PATCH 122/341] fixed migrations, more granular cache --- Dockerfile | 3 ++- Dockerfile.migrate | 2 -- docker-compose.yaml | 7 +++---- main.go | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0ae63ee9..0108ab65 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,10 +6,11 @@ COPY go.mod go.sum ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db go generate ./internal/database/ent RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=0 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db CGO_ENABLED=0 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 8f065468..5c67306d 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -1,5 +1,3 @@ FROM arigaio/atlas:latest COPY ./internal/database/ent/migrate/migrations /migrations - -ENTRYPOINT ["migrate", "apply","--allow-dirty" ,"--dir" "file:///migrations", ,"--tx-mode" ,"all", "--url", "sqlite:///database.db?_fk=1"] diff --git a/docker-compose.yaml b/docker-compose.yaml index 28597efb..36f8a3e9 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,9 +3,9 @@ services: image: aftermath-migrate build: dockerfile: Dockerfile.migrate - entrypoint: migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "sqlite:///data/aftermath.db?_fk=1" + command: migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "sqlite:///data/aftermath.db?_fk=1" volumes: - - ${DATABASE_PATH}:/data/aftermath.db + - ${DATABASE_PATH}:/data networks: - dokploy-network @@ -20,7 +20,7 @@ services: condition: service_completed_successfully entrypoint: ["app"] volumes: - - ${DATABASE_PATH}:/data/aftermath.db + - ${DATABASE_PATH}:/data image: aftermath-service build: @@ -32,7 +32,6 @@ services: memory: 512m networks: - dokploy-network - hostname: aftermath-service labels: - "traefik.enable=true" - "traefik.http.routers.aftermath-monorepo-compose.rule=Host(`${TRAEFIK_HOST}`)" diff --git a/main.go b/main.go index 25fbfc3d..17a72aef 100644 --- a/main.go +++ b/main.go @@ -116,7 +116,7 @@ func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Cli func coreClientsFromEnv() (core.Client, core.Client) { // Dependencies - dbClient, err := database.NewSQLiteClient(os.Getenv("DATABASE_PATH")) + dbClient, err := database.NewSQLiteClient(os.Getenv("DATABASE_PATH") + "/aftermath.db") if err != nil { log.Fatal().Msgf("database#NewClient failed %s", err) } From 8a2e96bdc939b83a5154ca0579b4677d1e9171c2 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:31:33 -0400 Subject: [PATCH 123/341] cleaned up generate --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0108ab65..43d7832e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,10 @@ COPY go.mod go.sum ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db go generate ./internal/database/ent -RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent go generate ./internal/database/ent # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db CGO_ENABLED=0 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent CGO_ENABLED=0 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch From 58410f768240930e2bd6d1d89802c2f0d8199a12 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:32:14 -0400 Subject: [PATCH 124/341] enabled cgo --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 43d7832e..deb85f2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22.3-alpine as builder +FROM golang:1.22.3-bookworm as builder WORKDIR /workspace @@ -9,7 +9,7 @@ COPY ./ ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent go generate ./internal/database/ent # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent CGO_ENABLED=0 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent CGO_ENABLED=1 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch From b19e2d1b34708b6ba110530cc1972de7cbc24f44 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:38:19 -0400 Subject: [PATCH 125/341] pulling db name from env --- Dockerfile | 4 +- Taskfile.yaml | 2 +- docker-compose.yaml | 1 - internal/database/ent/db/migrate/schema.go | 76 ++++++++++++++++++++++ main.go | 3 +- 5 files changed, 81 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index deb85f2d..e9a93afe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,10 +6,10 @@ COPY go.mod go.sum ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent go generate ./internal/database/ent +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db go generate /workspaceinternal/database/ent # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent CGO_ENABLED=1 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db CGO_ENABLED=1 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch diff --git a/Taskfile.yaml b/Taskfile.yaml index 3d6889d7..c407bc9e 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -24,7 +24,7 @@ tasks: cmd: atlas migrate hash --dir "file://internal/database/ent/migrate/migrations" && atlas migrate diff {{.CLI_ARGS}} --dir "file://internal/database/ent/migrate/migrations" --to "ent://internal/database/ent/schema" --dev-url "sqlite://file?mode=memory&_fk=1" db-migrate-apply: desc: apply migrations using atlas - cmd: atlas migrate apply --allow-dirty --dir "file://internal/database/ent/migrate/migrations" --tx-mode all --url "sqlite://${DATABASE_PATH}?_fk=1" + cmd: atlas migrate apply --allow-dirty --dir "file://internal/database/ent/migrate/migrations" --tx-mode all --url "sqlite://${DATABASE_PATH}/${DATABASE_NAME}?_fk=1" dev: desc: Start a local dev server diff --git a/docker-compose.yaml b/docker-compose.yaml index 36f8a3e9..e0fc5d38 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,7 +18,6 @@ services: depends_on: aftermath-migrate: condition: service_completed_successfully - entrypoint: ["app"] volumes: - ${DATABASE_PATH}:/data diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index 6be5f4e7..29a7f768 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -89,6 +89,11 @@ var ( }, }, Indexes: []*schema.Index{ + { + Name: "accountsnapshot_id", + Unique: false, + Columns: []*schema.Column{AccountSnapshotsColumns[0]}, + }, { Name: "accountsnapshot_created_at", Unique: false, @@ -137,6 +142,11 @@ var ( }, }, Indexes: []*schema.Index{ + { + Name: "achievementssnapshot_id", + Unique: false, + Columns: []*schema.Column{AchievementsSnapshotsColumns[0]}, + }, { Name: "achievementssnapshot_created_at", Unique: false, @@ -169,6 +179,11 @@ var ( Columns: AppConfigurationsColumns, PrimaryKey: []*schema.Column{AppConfigurationsColumns[0]}, Indexes: []*schema.Index{ + { + Name: "appconfiguration_id", + Unique: false, + Columns: []*schema.Column{AppConfigurationsColumns[0]}, + }, { Name: "appconfiguration_key", Unique: false, @@ -191,6 +206,11 @@ var ( Columns: ApplicationCommandsColumns, PrimaryKey: []*schema.Column{ApplicationCommandsColumns[0]}, Indexes: []*schema.Index{ + { + Name: "applicationcommand_id", + Unique: false, + Columns: []*schema.Column{ApplicationCommandsColumns[0]}, + }, { Name: "applicationcommand_options_hash", Unique: false, @@ -214,6 +234,11 @@ var ( Columns: ClansColumns, PrimaryKey: []*schema.Column{ClansColumns[0]}, Indexes: []*schema.Index{ + { + Name: "clan_id", + Unique: false, + Columns: []*schema.Column{ClansColumns[0]}, + }, { Name: "clan_tag", Unique: false, @@ -246,6 +271,11 @@ var ( Columns: CronTasksColumns, PrimaryKey: []*schema.Column{CronTasksColumns[0]}, Indexes: []*schema.Index{ + { + Name: "crontask_id", + Unique: false, + Columns: []*schema.Column{CronTasksColumns[0]}, + }, { Name: "crontask_reference_id", Unique: false, @@ -281,6 +311,13 @@ var ( Name: "users", Columns: UsersColumns, PrimaryKey: []*schema.Column{UsersColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "user_id", + Unique: false, + Columns: []*schema.Column{UsersColumns[0]}, + }, + }, } // UserConnectionsColumns holds the columns for the "user_connections" table. UserConnectionsColumns = []*schema.Column{ @@ -307,6 +344,11 @@ var ( }, }, Indexes: []*schema.Index{ + { + Name: "userconnection_id", + Unique: false, + Columns: []*schema.Column{UserConnectionsColumns[0]}, + }, { Name: "userconnection_user_id", Unique: false, @@ -327,6 +369,11 @@ var ( Unique: false, Columns: []*schema.Column{UserConnectionsColumns[3], UserConnectionsColumns[4]}, }, + { + Name: "userconnection_reference_id_user_id_type", + Unique: true, + Columns: []*schema.Column{UserConnectionsColumns[4], UserConnectionsColumns[7], UserConnectionsColumns[3]}, + }, }, } // UserContentsColumns holds the columns for the "user_contents" table. @@ -354,6 +401,11 @@ var ( }, }, Indexes: []*schema.Index{ + { + Name: "usercontent_id", + Unique: false, + Columns: []*schema.Column{UserContentsColumns[0]}, + }, { Name: "usercontent_user_id", Unique: false, @@ -401,6 +453,11 @@ var ( }, }, Indexes: []*schema.Index{ + { + Name: "usersubscription_id", + Unique: false, + Columns: []*schema.Column{UserSubscriptionsColumns[0]}, + }, { Name: "usersubscription_user_id", Unique: false, @@ -436,6 +493,13 @@ var ( Name: "vehicles", Columns: VehiclesColumns, PrimaryKey: []*schema.Column{VehiclesColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "vehicle_id", + Unique: false, + Columns: []*schema.Column{VehiclesColumns[0]}, + }, + }, } // VehicleAveragesColumns holds the columns for the "vehicle_averages" table. VehicleAveragesColumns = []*schema.Column{ @@ -449,6 +513,13 @@ var ( Name: "vehicle_averages", Columns: VehicleAveragesColumns, PrimaryKey: []*schema.Column{VehicleAveragesColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "vehicleaverage_id", + Unique: false, + Columns: []*schema.Column{VehicleAveragesColumns[0]}, + }, + }, } // VehicleSnapshotsColumns holds the columns for the "vehicle_snapshots" table. VehicleSnapshotsColumns = []*schema.Column{ @@ -477,6 +548,11 @@ var ( }, }, Indexes: []*schema.Index{ + { + Name: "vehiclesnapshot_id", + Unique: false, + Columns: []*schema.Column{VehicleSnapshotsColumns[0]}, + }, { Name: "vehiclesnapshot_created_at", Unique: false, diff --git a/main.go b/main.go index 17a72aef..4abd36e7 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "embed" "io/fs" + "path/filepath" "os" "strconv" @@ -116,7 +117,7 @@ func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Cli func coreClientsFromEnv() (core.Client, core.Client) { // Dependencies - dbClient, err := database.NewSQLiteClient(os.Getenv("DATABASE_PATH") + "/aftermath.db") + dbClient, err := database.NewSQLiteClient(filepath.Join(os.Getenv("DATABASE_PATH"), os.Getenv("DATABASE_NAME"))) if err != nil { log.Fatal().Msgf("database#NewClient failed %s", err) } From d893690de3a79b3e8a0788717c49d7cfc887692c Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:39:32 -0400 Subject: [PATCH 126/341] fixed path and name --- docker-compose.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index e0fc5d38..4f186335 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,7 +14,8 @@ services: - .env environment: - PORT=3000 - - DATABASE_PATH=sqlite:///data/aftermath.db?_fk=1 + - DATABASE_NAME=aftermath.db + - DATABASE_PATH=/data depends_on: aftermath-migrate: condition: service_completed_successfully From 6db281216715f5e1013b7a3c6ecab5d60850a492 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:39:57 -0400 Subject: [PATCH 127/341] typo --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e9a93afe..3198670a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY go.mod go.sum ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db go generate /workspaceinternal/database/ent +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db go generate internal/database/ent # build a fully standalone binary with zero dependencies RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db CGO_ENABLED=1 GOOS=linux go build -o app . From aa9ac9744808625749b3f96fbebda096fad674c6 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:41:20 -0400 Subject: [PATCH 128/341] removed all manual cache --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3198670a..071d1b3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,13 @@ FROM golang:1.22.3-bookworm as builder WORKDIR /workspace COPY go.mod go.sum ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download +RUN go mod download COPY ./ ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db go generate internal/database/ent +RUN go generate internal/database/ent # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent/db CGO_ENABLED=1 GOOS=linux go build -o app . +RUN CGO_ENABLED=1 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch From f4e7f09a53912ac53358591cdab27bae823a4cd3 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:41:57 -0400 Subject: [PATCH 129/341] generate --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 071d1b3b..db7a5955 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY go.mod go.sum ./ RUN go mod download COPY ./ ./ -RUN go generate internal/database/ent +RUN go generate ./... # build a fully standalone binary with zero dependencies RUN CGO_ENABLED=1 GOOS=linux go build -o app . From 1e8c8d27524e1ad3a86b48de0c30afca5f4f6b80 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:43:19 -0400 Subject: [PATCH 130/341] removed generate as a layer --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index db7a5955..a71d533c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,12 @@ FROM golang:1.22.3-bookworm as builder WORKDIR /workspace COPY go.mod go.sum ./ -RUN go mod download +RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ -RUN go generate ./... # build a fully standalone binary with zero dependencies -RUN CGO_ENABLED=1 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch From 04aca895d76f6b66906a5005cec199cceaae0eb7 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:46:33 -0400 Subject: [PATCH 131/341] temoved cache --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a71d533c..241108a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,12 +3,12 @@ FROM golang:1.22.3-bookworm as builder WORKDIR /workspace COPY go.mod go.sum ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download +RUN go mod download COPY ./ ./ # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o app . +RUN CGO_ENABLED=1 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch From 1f1e9c6d1ba674fe677357c83ee2ea4b544c992b Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:47:37 -0400 Subject: [PATCH 132/341] added back generate --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 241108a4..0c9ffd25 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,14 +7,14 @@ RUN go mod download COPY ./ ./ +RUN go generate ./... + # build a fully standalone binary with zero dependencies RUN CGO_ENABLED=1 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch -WORKDIR /app - ENV TZ=Europe/Berlin ENV ZONEINFO=/zoneinfo.zip COPY --from=builder /workspace/app /usr/bin/ From 880eaff71197b82ef57ff7e47c8a08bd6f78fa9f Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:48:57 -0400 Subject: [PATCH 133/341] set entrypoint back? --- docker-compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index 4f186335..32fddff9 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -16,6 +16,7 @@ services: - PORT=3000 - DATABASE_NAME=aftermath.db - DATABASE_PATH=/data + entrypoint: ["app"] depends_on: aftermath-migrate: condition: service_completed_successfully From 632978a6f22561d814681cf45d4f39afaee980c3 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:51:10 -0400 Subject: [PATCH 134/341] ensuring a directory exists --- main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.go b/main.go index 4abd36e7..b340021a 100644 --- a/main.go +++ b/main.go @@ -116,6 +116,11 @@ func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Cli } func coreClientsFromEnv() (core.Client, core.Client) { + err := os.MkdirAll(os.Getenv("DATABASE_PATH"), os.ModePerm) + if err != nil { + log.Fatal().Msgf("os#MkdirAll failed %s", err) + } + // Dependencies dbClient, err := database.NewSQLiteClient(filepath.Join(os.Getenv("DATABASE_PATH"), os.Getenv("DATABASE_NAME"))) if err != nil { From 6b070c54b98f15e52813e46b46fb199e15d94542 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:52:20 -0400 Subject: [PATCH 135/341] added mod cache back --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0c9ffd25..03ea8afd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,14 +3,14 @@ FROM golang:1.22.3-bookworm as builder WORKDIR /workspace COPY go.mod go.sum ./ -RUN go mod download +RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ -RUN go generate ./... +RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... # build a fully standalone binary with zero dependencies -RUN CGO_ENABLED=1 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch From ca95f1f407e39bd39421393e28275151dd9bac83 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:58:12 -0400 Subject: [PATCH 136/341] catching panic --- internal/database/client.go | 9 ++++++++- main.go | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/database/client.go b/internal/database/client.go index f2cc1c77..ac557d0d 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -10,6 +10,7 @@ import ( "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" "github.com/cufee/aftermath/internal/stats/frame" + "github.com/rs/zerolog/log" _ "github.com/mattn/go-sqlite3" ) @@ -104,7 +105,13 @@ func (c *client) txWithLock(ctx context.Context) (*db.Tx, func(), error) { } func NewSQLiteClient(filePath string) (*client, error) { - c, err := db.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&_fk=1", filePath)) + defer func() { + if r := recover(); r != nil { + log.Fatal().Interface("error", r).Stack().Msg("NewSQLiteClient panic") + } + }() + + c, err := db.Open("sqlite3", fmt.Sprintf("file: %s?_fk=1", filePath)) if err != nil { return nil, err } diff --git a/main.go b/main.go index b340021a..3111fe1b 100644 --- a/main.go +++ b/main.go @@ -50,7 +50,7 @@ func main() { discordHandler, err := discord.NewRouterHandler(liveCoreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) if err != nil { - panic(err) + log.Fatal().Msgf("discord#NewRouterHandler failed %s", err) } if e := os.Getenv("PRIVATE_SERVER_ENABLED"); e == "true" { From 282e9ac42b999ba5d2550ee563024457a8b24296 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 13:59:41 -0400 Subject: [PATCH 137/341] typo --- internal/database/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/database/client.go b/internal/database/client.go index ac557d0d..eecf20c0 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -111,7 +111,7 @@ func NewSQLiteClient(filePath string) (*client, error) { } }() - c, err := db.Open("sqlite3", fmt.Sprintf("file: %s?_fk=1", filePath)) + c, err := db.Open("sqlite3", fmt.Sprintf("file://%s?_fk=1", filePath)) if err != nil { return nil, err } From 6c556e40d87e91a0b2086ade8cf7d75699f29e77 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:02:16 -0400 Subject: [PATCH 138/341] split up COPY --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03ea8afd..14ef3f9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,9 +5,10 @@ WORKDIR /workspace COPY go.mod go.sum ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download -COPY ./ ./ +COPY ./internal/database/ent internal/database/ent +RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate internal/database/ent -RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... +COPY ./ ./ # build a fully standalone binary with zero dependencies RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o app . From db2f8cd3596b575d332e9ce5bf3f14ebf8dfb47e Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:03:12 -0400 Subject: [PATCH 139/341] relative path --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 14ef3f9a..9829accd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY go.mod go.sum ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./internal/database/ent internal/database/ent -RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate internal/database/ent +RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./internal/database/ent COPY ./ ./ From 11b69e562fb8984f760f31c0897f738558cfbae3 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:04:48 -0400 Subject: [PATCH 140/341] trying to add cache --- Dockerfile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9829accd..91f74fdc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,13 +5,12 @@ WORKDIR /workspace COPY go.mod go.sum ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download -COPY ./internal/database/ent internal/database/ent -RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./internal/database/ent - COPY ./ ./ +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent go generate ./internal/database/ent + # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent CGO_ENABLED=1 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch From cb2d45bff4e39a83ca155717a1c2e64ce09b8f05 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:05:19 -0400 Subject: [PATCH 141/341] removed cache --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 91f74fdc..03ea8afd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,10 +7,10 @@ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent go generate ./internal/database/ent +RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/workspace/internal/database/ent CGO_ENABLED=1 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o app . # Make a scratch container with required files and binary FROM scratch From 5b26813987afb652d121a5b686631dcc1fb28892 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:08:00 -0400 Subject: [PATCH 142/341] accepting path from env --- docker-compose.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 32fddff9..c7c26a1c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,7 +3,7 @@ services: image: aftermath-migrate build: dockerfile: Dockerfile.migrate - command: migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "sqlite:///data/aftermath.db?_fk=1" + command: migrate apply --allow-dirty --dir "file:///migrations" --tx-mode all --url "sqlite:///data/${DATABASE_NAME}?_fk=1" volumes: - ${DATABASE_PATH}:/data networks: @@ -14,7 +14,6 @@ services: - .env environment: - PORT=3000 - - DATABASE_NAME=aftermath.db - DATABASE_PATH=/data entrypoint: ["app"] depends_on: From f8a9bcbc621c3b4d85e6e33420ad1c1a0a259abc Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:12:48 -0400 Subject: [PATCH 143/341] changed entrypoint --- Dockerfile | 6 +++--- docker-compose.yaml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03ea8afd..0ed1469d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,15 +10,15 @@ COPY ./ ./ RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o app . +RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . # Make a scratch container with required files and binary FROM scratch ENV TZ=Europe/Berlin ENV ZONEINFO=/zoneinfo.zip -COPY --from=builder /workspace/app /usr/bin/ +COPY --from=builder /bin/aftermath /usr/bin/ COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip / COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -ENTRYPOINT ["app"] +ENTRYPOINT ["aftermath"] diff --git a/docker-compose.yaml b/docker-compose.yaml index c7c26a1c..e808c713 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,7 +15,6 @@ services: environment: - PORT=3000 - DATABASE_PATH=/data - entrypoint: ["app"] depends_on: aftermath-migrate: condition: service_completed_successfully From 8922c1833b826163fefca80cdc97050ffcde9497 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:15:16 -0400 Subject: [PATCH 144/341] removed all cache --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0ed1469d..0dab5580 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,14 +3,14 @@ FROM golang:1.22.3-bookworm as builder WORKDIR /workspace COPY go.mod go.sum ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download +RUN go mod download COPY ./ ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... +RUN go generate ./... # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . +RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . # Make a scratch container with required files and binary FROM scratch From 952c4a611d4b463a9eecf27c282b85df1edf83d8 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:20:59 -0400 Subject: [PATCH 145/341] added cmd --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0dab5580..4614dea7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,4 +21,4 @@ COPY --from=builder /bin/aftermath /usr/bin/ COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip / COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -ENTRYPOINT ["aftermath"] +CMD [ "aftermath" ] From 72e3a871d15fc8371fcd59b9aaa29e6559b364ea Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:21:18 -0400 Subject: [PATCH 146/341] added back cache --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4614dea7..c182f37b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,14 +3,14 @@ FROM golang:1.22.3-bookworm as builder WORKDIR /workspace COPY go.mod go.sum ./ -RUN go mod download +RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ -RUN go generate ./... +RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... # build a fully standalone binary with zero dependencies -RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . +RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . # Make a scratch container with required files and binary FROM scratch From 5cf3a66b8138da2e57d6577350046705506adf3d Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:22:41 -0400 Subject: [PATCH 147/341] removed pointless step --- Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c182f37b..7a25b7d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,8 +7,6 @@ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ -RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... - # build a fully standalone binary with zero dependencies RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . From 231e4a4ec448c10c087c6e94b60d04de64d942bd Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:23:42 -0400 Subject: [PATCH 148/341] idk at this point --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7a25b7d5..e1446fcb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ FROM scratch ENV TZ=Europe/Berlin ENV ZONEINFO=/zoneinfo.zip -COPY --from=builder /bin/aftermath /usr/bin/ +COPY --from=builder /bin/aftermath /usr/bin/aftermath COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip / COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ From ddad6ad6f6d148c42b550b9b4da0122d56b0e1a8 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:33:23 -0400 Subject: [PATCH 149/341] switched final image --- Dockerfile | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index e1446fcb..1f1802a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,15 +8,12 @@ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . +RUN --mount=type=cache,target=$GOPATH/pkg/mod GOOS=linux go build -a -installsuffix cgo -o /bin/aftermath . # Make a scratch container with required files and binary -FROM scratch +FROM debian:stable-slim ENV TZ=Europe/Berlin -ENV ZONEINFO=/zoneinfo.zip COPY --from=builder /bin/aftermath /usr/bin/aftermath -COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip / -COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -CMD [ "aftermath" ] +ENTRYPOINT [ "aftermath" ] From 09adb5aac04abbb62e895df39e08833456d1edf4 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:35:48 -0400 Subject: [PATCH 150/341] added certs and time --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1f1802a4..2816f085 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,12 +8,15 @@ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod GOOS=linux go build -a -installsuffix cgo -o /bin/aftermath . +RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . # Make a scratch container with required files and binary FROM debian:stable-slim ENV TZ=Europe/Berlin +ENV ZONEINFO=/zoneinfo.zip COPY --from=builder /bin/aftermath /usr/bin/aftermath +COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip / +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ ENTRYPOINT [ "aftermath" ] From c4742f03264e5b11c3db619f11c5ed263d17e7e9 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 14:45:28 -0400 Subject: [PATCH 151/341] updated wg proxy --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bf5fd037..dbc963ed 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 entgo.io/ent v0.13.1 github.com/bwmarrin/discordgo v0.28.1 - github.com/cufee/am-wg-proxy-next/v2 v2.1.3 + github.com/cufee/am-wg-proxy-next/v2 v2.1.4 github.com/disintegration/imaging v1.6.2 github.com/fogleman/gg v1.3.0 github.com/go-co-op/gocron v1.37.0 diff --git a/go.sum b/go.sum index 24ee26f5..4f848a8b 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cufee/am-wg-proxy-next/v2 v2.1.3 h1:qTzzgEapbI/z81R3nXoiiITfBZeHQ1cnebzSNgaoZlQ= -github.com/cufee/am-wg-proxy-next/v2 v2.1.3/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= +github.com/cufee/am-wg-proxy-next/v2 v2.1.4 h1:2EtRrktt2eXcomZB868J2QYuxI1NmwyFrd9EqORD21Y= +github.com/cufee/am-wg-proxy-next/v2 v2.1.4/go.mod h1:xrgMXzAMU4p5AW1++nnpofNFZLOiSUP8vnezzzG92Zw= 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= From 13d39dd9fc73b38076086b280493c957e6e2e00b Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 18:21:20 -0400 Subject: [PATCH 152/341] saving a snapshot when account exists, but no snapshots --- internal/stats/session.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/internal/stats/session.go b/internal/stats/session.go index a4bfd6ed..3d7cd416 100644 --- a/internal/stats/session.go +++ b/internal/stats/session.go @@ -2,6 +2,7 @@ package stats import ( "context" + "errors" "slices" "time" @@ -55,6 +56,18 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time session, career, err := r.fetchClient.SessionStats(ctx, accountId, from, fetch.WithWN8()) stop() if err != nil { + if errors.Is(fetch.ErrSessionNotFound, err) { + go func(id string) { + // record a session in the background + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + _, err := logic.RecordAccountSnapshots(ctx, r.wargaming, r.database, r.wargaming.RealmFromAccountID(id), false, id) + if err != nil { + log.Err(err).Str("accountId", id).Msg("failed to record account snapshot") + } + }(accountId) + } return nil, meta, err } meta.Stats["career"] = career From f09d7f2223e04b33df8e5a1d67ce4f1b983c74ea Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 18:36:28 -0400 Subject: [PATCH 153/341] changed the logo slightly --- internal/stats/render/common/logo.go | 4 ++-- render_test.go | 14 ++------------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/internal/stats/render/common/logo.go b/internal/stats/render/common/logo.go index 8de5033e..5f25f173 100644 --- a/internal/stats/render/common/logo.go +++ b/internal/stats/render/common/logo.go @@ -26,8 +26,8 @@ func (opts LogoSizingOptions) Width() int { func DefaultLogoOptions() LogoSizingOptions { return LogoSizingOptions{ Gap: 4, - Jump: 6, - Lines: 9, + Jump: 7, + Lines: 7, LineStep: 12, LineWidth: 6, } diff --git a/render_test.go b/render_test.go index a6770d64..6dc761be 100644 --- a/render_test.go +++ b/render_test.go @@ -2,7 +2,6 @@ package main import ( "context" - "image/png" "os" "testing" "time" @@ -10,11 +9,11 @@ import ( "github.com/cufee/aftermath/internal/stats" "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" - "github.com/disintegration/imaging" - "github.com/fogleman/gg" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "golang.org/x/text/language" + + _ "github.com/joho/godotenv/autoload" ) func TestRenderSession(t *testing.T) { @@ -26,15 +25,6 @@ func TestRenderSession(t *testing.T) { coreClient, _ := coreClientsFromEnv() defer coreClient.Database().Disconnect() - rating, _ := assets.GetLoadedImage("rating-calibration") - c := imaging.PasteCenter(gg.NewContext(int(float64(rating.Bounds().Dx())*1.5), int(float64(rating.Bounds().Dy())*1.5)).Image(), rating) - - fc, err := os.Create("tmp/rating-calibration.png") - assert.NoError(t, err, "failed to create a file") - defer fc.Close() - - png.Encode(fc, c) - bgImage, ok := assets.GetLoadedImage("bg-default") assert.True(t, ok, "failed to load a background image") From c95d6b2d12d445de601a56f1612d683b1a7fb94b Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 19:06:29 -0400 Subject: [PATCH 154/341] moved files around, versioned all stats packages --- cmds/core/client.go | 4 ++-- cmds/core/server/handlers/private/accounts.go | 2 +- cmds/discord/commands/session.go | 4 ++-- internal/logic/sessions.go | 2 +- internal/stats/fetch/{ => v1}/client.go | 0 internal/stats/fetch/{ => v1}/convert.go | 0 internal/stats/fetch/{ => v1}/errors.go | 0 internal/stats/fetch/{ => v1}/multisource.go | 0 internal/stats/fetch/{ => v1}/replay/errors.go | 0 internal/stats/fetch/{ => v1}/replay/meta.go | 0 internal/stats/fetch/{ => v1}/replay/replay.go | 0 internal/stats/fetch/{ => v1}/replay/results.go | 0 internal/stats/fetch/{ => v1}/replay/unpack.go | 0 internal/stats/prepare/common/{ => v1}/card.go | 0 internal/stats/prepare/common/{ => v1}/convert.go | 0 internal/stats/prepare/common/{ => v1}/highlights.go | 0 internal/stats/prepare/common/{ => v1}/options.go | 0 internal/stats/prepare/common/{ => v1}/tags.go | 0 internal/stats/prepare/period/{ => v1}/card.go | 4 ++-- internal/stats/prepare/period/{ => v1}/constants.go | 2 +- internal/stats/prepare/period/{ => v1}/preset.go | 2 +- internal/stats/prepare/replay/{ => v1}/constants.go | 2 +- internal/stats/prepare/session/{ => v1}/card.go | 4 ++-- internal/stats/prepare/session/{ => v1}/constants.go | 2 +- internal/stats/prepare/session/{ => v1}/preset.go | 2 +- internal/stats/render/common/{ => v1}/badges.go | 0 internal/stats/render/common/{ => v1}/block.go | 0 internal/stats/render/common/{ => v1}/calculations.go | 0 internal/stats/render/common/{ => v1}/colors.go | 0 internal/stats/render/common/{ => v1}/constants.go | 0 internal/stats/render/common/{ => v1}/footer.go | 0 internal/stats/render/common/{ => v1}/header.go | 0 internal/stats/render/common/{ => v1}/images.go | 0 internal/stats/render/common/{ => v1}/logo.go | 0 internal/stats/render/common/{ => v1}/player_title.go | 0 internal/stats/render/common/{ => v1}/strings.go | 0 internal/stats/render/common/{ => v1}/style.go | 0 .../stats/render/common/{ => v1}/tier_percentage.go | 0 internal/stats/render/common/{ => v1}/wn8.go | 0 internal/stats/render/period/{ => v1}/block.go | 6 +++--- internal/stats/render/period/{ => v1}/cards.go | 8 ++++---- internal/stats/render/period/{ => v1}/constants.go | 6 +++--- internal/stats/render/period/{ => v1}/image.go | 4 ++-- internal/stats/render/replay/{ => v1}/blocks.go.todo | 2 +- internal/stats/render/replay/{ => v1}/cards.go.todo | 0 .../stats/render/replay/{ => v1}/constants.go.todo | 0 internal/stats/render/replay/{ => v1}/elements.go.todo | 0 internal/stats/render/replay/{ => v1}/preset.go.todo | 0 internal/stats/render/segments.go | 2 +- internal/stats/render/session/{ => v1}/blocks.go | 6 +++--- internal/stats/render/session/{ => v1}/cards.go | 8 ++++---- internal/stats/render/session/{ => v1}/constants.go | 2 +- internal/stats/render/session/{ => v1}/icons.go | 2 +- internal/stats/render/session/{ => v1}/image.go | 4 ++-- internal/stats/{ => renderer/v1}/errors.go | 2 +- internal/stats/{ => renderer/v1}/image.go | 4 ++-- internal/stats/{ => renderer/v1}/metadata.go | 4 ++-- internal/stats/{ => renderer/v1}/period.go | 10 +++++----- internal/stats/{ => renderer/v1}/renderer.go | 4 ++-- internal/stats/renderer/v1/replay.go | 1 + internal/stats/{ => renderer/v1}/session.go | 10 +++++----- internal/stats/replay.go | 1 - main.go | 10 +++++----- render_test.go | 2 +- 64 files changed, 64 insertions(+), 64 deletions(-) rename internal/stats/fetch/{ => v1}/client.go (100%) rename internal/stats/fetch/{ => v1}/convert.go (100%) rename internal/stats/fetch/{ => v1}/errors.go (100%) rename internal/stats/fetch/{ => v1}/multisource.go (100%) rename internal/stats/fetch/{ => v1}/replay/errors.go (100%) rename internal/stats/fetch/{ => v1}/replay/meta.go (100%) rename internal/stats/fetch/{ => v1}/replay/replay.go (100%) rename internal/stats/fetch/{ => v1}/replay/results.go (100%) rename internal/stats/fetch/{ => v1}/replay/unpack.go (100%) rename internal/stats/prepare/common/{ => v1}/card.go (100%) rename internal/stats/prepare/common/{ => v1}/convert.go (100%) rename internal/stats/prepare/common/{ => v1}/highlights.go (100%) rename internal/stats/prepare/common/{ => v1}/options.go (100%) rename internal/stats/prepare/common/{ => v1}/tags.go (100%) rename internal/stats/prepare/period/{ => v1}/card.go (95%) rename internal/stats/prepare/period/{ => v1}/constants.go (93%) rename internal/stats/prepare/period/{ => v1}/preset.go (94%) rename internal/stats/prepare/replay/{ => v1}/constants.go (98%) rename internal/stats/prepare/session/{ => v1}/card.go (98%) rename internal/stats/prepare/session/{ => v1}/constants.go (94%) rename internal/stats/prepare/session/{ => v1}/preset.go (93%) rename internal/stats/render/common/{ => v1}/badges.go (100%) rename internal/stats/render/common/{ => v1}/block.go (100%) rename internal/stats/render/common/{ => v1}/calculations.go (100%) rename internal/stats/render/common/{ => v1}/colors.go (100%) rename internal/stats/render/common/{ => v1}/constants.go (100%) rename internal/stats/render/common/{ => v1}/footer.go (100%) rename internal/stats/render/common/{ => v1}/header.go (100%) rename internal/stats/render/common/{ => v1}/images.go (100%) rename internal/stats/render/common/{ => v1}/logo.go (100%) rename internal/stats/render/common/{ => v1}/player_title.go (100%) rename internal/stats/render/common/{ => v1}/strings.go (100%) rename internal/stats/render/common/{ => v1}/style.go (100%) rename internal/stats/render/common/{ => v1}/tier_percentage.go (100%) rename internal/stats/render/common/{ => v1}/wn8.go (100%) rename internal/stats/render/period/{ => v1}/block.go (94%) rename internal/stats/render/period/{ => v1}/cards.go (97%) rename internal/stats/render/period/{ => v1}/constants.go (95%) rename internal/stats/render/period/{ => v1}/image.go (81%) rename internal/stats/render/replay/{ => v1}/blocks.go.todo (98%) rename internal/stats/render/replay/{ => v1}/cards.go.todo (100%) rename internal/stats/render/replay/{ => v1}/constants.go.todo (100%) rename internal/stats/render/replay/{ => v1}/elements.go.todo (100%) rename internal/stats/render/replay/{ => v1}/preset.go.todo (100%) rename internal/stats/render/session/{ => v1}/blocks.go (97%) rename internal/stats/render/session/{ => v1}/cards.go (98%) rename internal/stats/render/session/{ => v1}/constants.go (98%) rename internal/stats/render/session/{ => v1}/icons.go (93%) rename internal/stats/render/session/{ => v1}/image.go (81%) rename internal/stats/{ => renderer/v1}/errors.go (85%) rename internal/stats/{ => renderer/v1}/image.go (87%) rename internal/stats/{ => renderer/v1}/metadata.go (90%) rename internal/stats/{ => renderer/v1}/period.go (91%) rename internal/stats/{ => renderer/v1}/renderer.go (92%) create mode 100644 internal/stats/renderer/v1/replay.go rename internal/stats/{ => renderer/v1}/session.go (95%) delete mode 100644 internal/stats/replay.go diff --git a/cmds/core/client.go b/cmds/core/client.go index 05d6060a..3653c1d1 100644 --- a/cmds/core/client.go +++ b/cmds/core/client.go @@ -3,8 +3,8 @@ package core import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/wargaming" - "github.com/cufee/aftermath/internal/stats" - "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + stats "github.com/cufee/aftermath/internal/stats/renderer/v1" "golang.org/x/text/language" ) diff --git a/cmds/core/server/handlers/private/accounts.go b/cmds/core/server/handlers/private/accounts.go index 90ef4164..0b8e3945 100644 --- a/cmds/core/server/handlers/private/accounts.go +++ b/cmds/core/server/handlers/private/accounts.go @@ -11,7 +11,7 @@ import ( "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/am-wg-proxy-next/v2/types" "github.com/rs/zerolog/log" "golang.org/x/sync/semaphore" diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go index 591b7267..0fd10023 100644 --- a/cmds/discord/commands/session.go +++ b/cmds/discord/commands/session.go @@ -10,10 +10,10 @@ import ( "github.com/cufee/aftermath/cmds/discord/common" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats" - "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" + stats "github.com/cufee/aftermath/internal/stats/renderer/v1" "github.com/pkg/errors" ) diff --git a/internal/logic/sessions.go b/internal/logic/sessions.go index b816cd8e..80795daa 100644 --- a/internal/logic/sessions.go +++ b/internal/logic/sessions.go @@ -10,7 +10,7 @@ import ( "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/retry" - "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/am-wg-proxy-next/v2/types" "github.com/pkg/errors" "github.com/rs/zerolog/log" diff --git a/internal/stats/fetch/client.go b/internal/stats/fetch/v1/client.go similarity index 100% rename from internal/stats/fetch/client.go rename to internal/stats/fetch/v1/client.go diff --git a/internal/stats/fetch/convert.go b/internal/stats/fetch/v1/convert.go similarity index 100% rename from internal/stats/fetch/convert.go rename to internal/stats/fetch/v1/convert.go diff --git a/internal/stats/fetch/errors.go b/internal/stats/fetch/v1/errors.go similarity index 100% rename from internal/stats/fetch/errors.go rename to internal/stats/fetch/v1/errors.go diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/v1/multisource.go similarity index 100% rename from internal/stats/fetch/multisource.go rename to internal/stats/fetch/v1/multisource.go diff --git a/internal/stats/fetch/replay/errors.go b/internal/stats/fetch/v1/replay/errors.go similarity index 100% rename from internal/stats/fetch/replay/errors.go rename to internal/stats/fetch/v1/replay/errors.go diff --git a/internal/stats/fetch/replay/meta.go b/internal/stats/fetch/v1/replay/meta.go similarity index 100% rename from internal/stats/fetch/replay/meta.go rename to internal/stats/fetch/v1/replay/meta.go diff --git a/internal/stats/fetch/replay/replay.go b/internal/stats/fetch/v1/replay/replay.go similarity index 100% rename from internal/stats/fetch/replay/replay.go rename to internal/stats/fetch/v1/replay/replay.go diff --git a/internal/stats/fetch/replay/results.go b/internal/stats/fetch/v1/replay/results.go similarity index 100% rename from internal/stats/fetch/replay/results.go rename to internal/stats/fetch/v1/replay/results.go diff --git a/internal/stats/fetch/replay/unpack.go b/internal/stats/fetch/v1/replay/unpack.go similarity index 100% rename from internal/stats/fetch/replay/unpack.go rename to internal/stats/fetch/v1/replay/unpack.go diff --git a/internal/stats/prepare/common/card.go b/internal/stats/prepare/common/v1/card.go similarity index 100% rename from internal/stats/prepare/common/card.go rename to internal/stats/prepare/common/v1/card.go diff --git a/internal/stats/prepare/common/convert.go b/internal/stats/prepare/common/v1/convert.go similarity index 100% rename from internal/stats/prepare/common/convert.go rename to internal/stats/prepare/common/v1/convert.go diff --git a/internal/stats/prepare/common/highlights.go b/internal/stats/prepare/common/v1/highlights.go similarity index 100% rename from internal/stats/prepare/common/highlights.go rename to internal/stats/prepare/common/v1/highlights.go diff --git a/internal/stats/prepare/common/options.go b/internal/stats/prepare/common/v1/options.go similarity index 100% rename from internal/stats/prepare/common/options.go rename to internal/stats/prepare/common/v1/options.go diff --git a/internal/stats/prepare/common/tags.go b/internal/stats/prepare/common/v1/tags.go similarity index 100% rename from internal/stats/prepare/common/tags.go rename to internal/stats/prepare/common/v1/tags.go diff --git a/internal/stats/prepare/period/card.go b/internal/stats/prepare/period/v1/card.go similarity index 95% rename from internal/stats/prepare/period/card.go rename to internal/stats/prepare/period/v1/card.go index 7fcffe72..ce1e0407 100644 --- a/internal/stats/prepare/period/card.go +++ b/internal/stats/prepare/period/v1/card.go @@ -5,8 +5,8 @@ import ( "math" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats/fetch" - "github.com/cufee/aftermath/internal/stats/prepare/common" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + "github.com/cufee/aftermath/internal/stats/prepare/common/v1" ) func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]models.Vehicle, opts ...common.Option) (Cards, error) { diff --git a/internal/stats/prepare/period/constants.go b/internal/stats/prepare/period/v1/constants.go similarity index 93% rename from internal/stats/prepare/period/constants.go rename to internal/stats/prepare/period/v1/constants.go index 7b4a70c2..eebf62fc 100644 --- a/internal/stats/prepare/period/constants.go +++ b/internal/stats/prepare/period/v1/constants.go @@ -1,7 +1,7 @@ package period import ( - "github.com/cufee/aftermath/internal/stats/prepare/common" + "github.com/cufee/aftermath/internal/stats/prepare/common/v1" ) const TagAvgTier common.Tag = "avg_tier" diff --git a/internal/stats/prepare/period/preset.go b/internal/stats/prepare/period/v1/preset.go similarity index 94% rename from internal/stats/prepare/period/preset.go rename to internal/stats/prepare/period/v1/preset.go index dfe9b336..fe41b8ae 100644 --- a/internal/stats/prepare/period/preset.go +++ b/internal/stats/prepare/period/v1/preset.go @@ -3,7 +3,7 @@ package period import ( "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/frame" - "github.com/cufee/aftermath/internal/stats/prepare/common" + "github.com/cufee/aftermath/internal/stats/prepare/common/v1" ) func presetToBlock(preset common.Tag, stats frame.StatsFrame, vehicles map[string]frame.VehicleStatsFrame, glossary map[string]models.Vehicle) (common.StatsBlock[BlockData], error) { diff --git a/internal/stats/prepare/replay/constants.go b/internal/stats/prepare/replay/v1/constants.go similarity index 98% rename from internal/stats/prepare/replay/constants.go rename to internal/stats/prepare/replay/v1/constants.go index e30da8e9..365fd831 100644 --- a/internal/stats/prepare/replay/constants.go +++ b/internal/stats/prepare/replay/v1/constants.go @@ -1,6 +1,6 @@ package replay -import "github.com/cufee/aftermath/internal/stats/prepare/common" +import "github.com/cufee/aftermath/internal/stats/prepare/common/v1" const ( TagDamageBlocked common.Tag = "blocked" diff --git a/internal/stats/prepare/session/card.go b/internal/stats/prepare/session/v1/card.go similarity index 98% rename from internal/stats/prepare/session/card.go rename to internal/stats/prepare/session/v1/card.go index ea7ede65..b74511bc 100644 --- a/internal/stats/prepare/session/card.go +++ b/internal/stats/prepare/session/v1/card.go @@ -5,9 +5,9 @@ import ( "slices" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/aftermath/internal/stats/frame" - "github.com/cufee/aftermath/internal/stats/prepare/common" + "github.com/cufee/aftermath/internal/stats/prepare/common/v1" "golang.org/x/text/language" ) diff --git a/internal/stats/prepare/session/constants.go b/internal/stats/prepare/session/v1/constants.go similarity index 94% rename from internal/stats/prepare/session/constants.go rename to internal/stats/prepare/session/v1/constants.go index 6e8a4533..b663a651 100644 --- a/internal/stats/prepare/session/constants.go +++ b/internal/stats/prepare/session/v1/constants.go @@ -2,7 +2,7 @@ package session import ( "github.com/cufee/aftermath/internal/stats/frame" - "github.com/cufee/aftermath/internal/stats/prepare/common" + "github.com/cufee/aftermath/internal/stats/prepare/common/v1" ) var unratedOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagWinrate}, {common.TagWN8}, {common.TagAvgDamage, common.TagDamageRatio}} diff --git a/internal/stats/prepare/session/preset.go b/internal/stats/prepare/session/v1/preset.go similarity index 93% rename from internal/stats/prepare/session/preset.go rename to internal/stats/prepare/session/v1/preset.go index 5c496b0c..2df59e61 100644 --- a/internal/stats/prepare/session/preset.go +++ b/internal/stats/prepare/session/v1/preset.go @@ -2,7 +2,7 @@ package session import ( "github.com/cufee/aftermath/internal/stats/frame" - "github.com/cufee/aftermath/internal/stats/prepare/common" + "github.com/cufee/aftermath/internal/stats/prepare/common/v1" ) func presetToBlock(preset common.Tag, session, career frame.StatsFrame) (common.StatsBlock[BlockData], error) { diff --git a/internal/stats/render/common/badges.go b/internal/stats/render/common/v1/badges.go similarity index 100% rename from internal/stats/render/common/badges.go rename to internal/stats/render/common/v1/badges.go diff --git a/internal/stats/render/common/block.go b/internal/stats/render/common/v1/block.go similarity index 100% rename from internal/stats/render/common/block.go rename to internal/stats/render/common/v1/block.go diff --git a/internal/stats/render/common/calculations.go b/internal/stats/render/common/v1/calculations.go similarity index 100% rename from internal/stats/render/common/calculations.go rename to internal/stats/render/common/v1/calculations.go diff --git a/internal/stats/render/common/colors.go b/internal/stats/render/common/v1/colors.go similarity index 100% rename from internal/stats/render/common/colors.go rename to internal/stats/render/common/v1/colors.go diff --git a/internal/stats/render/common/constants.go b/internal/stats/render/common/v1/constants.go similarity index 100% rename from internal/stats/render/common/constants.go rename to internal/stats/render/common/v1/constants.go diff --git a/internal/stats/render/common/footer.go b/internal/stats/render/common/v1/footer.go similarity index 100% rename from internal/stats/render/common/footer.go rename to internal/stats/render/common/v1/footer.go diff --git a/internal/stats/render/common/header.go b/internal/stats/render/common/v1/header.go similarity index 100% rename from internal/stats/render/common/header.go rename to internal/stats/render/common/v1/header.go diff --git a/internal/stats/render/common/images.go b/internal/stats/render/common/v1/images.go similarity index 100% rename from internal/stats/render/common/images.go rename to internal/stats/render/common/v1/images.go diff --git a/internal/stats/render/common/logo.go b/internal/stats/render/common/v1/logo.go similarity index 100% rename from internal/stats/render/common/logo.go rename to internal/stats/render/common/v1/logo.go diff --git a/internal/stats/render/common/player_title.go b/internal/stats/render/common/v1/player_title.go similarity index 100% rename from internal/stats/render/common/player_title.go rename to internal/stats/render/common/v1/player_title.go diff --git a/internal/stats/render/common/strings.go b/internal/stats/render/common/v1/strings.go similarity index 100% rename from internal/stats/render/common/strings.go rename to internal/stats/render/common/v1/strings.go diff --git a/internal/stats/render/common/style.go b/internal/stats/render/common/v1/style.go similarity index 100% rename from internal/stats/render/common/style.go rename to internal/stats/render/common/v1/style.go diff --git a/internal/stats/render/common/tier_percentage.go b/internal/stats/render/common/v1/tier_percentage.go similarity index 100% rename from internal/stats/render/common/tier_percentage.go rename to internal/stats/render/common/v1/tier_percentage.go diff --git a/internal/stats/render/common/wn8.go b/internal/stats/render/common/v1/wn8.go similarity index 100% rename from internal/stats/render/common/wn8.go rename to internal/stats/render/common/v1/wn8.go diff --git a/internal/stats/render/period/block.go b/internal/stats/render/period/v1/block.go similarity index 94% rename from internal/stats/render/period/block.go rename to internal/stats/render/period/v1/block.go index 2fd79d80..96057065 100644 --- a/internal/stats/render/period/block.go +++ b/internal/stats/render/period/v1/block.go @@ -1,9 +1,9 @@ package period import ( - prepare "github.com/cufee/aftermath/internal/stats/prepare/common" - "github.com/cufee/aftermath/internal/stats/prepare/period" - "github.com/cufee/aftermath/internal/stats/render/common" + prepare "github.com/cufee/aftermath/internal/stats/prepare/common/v1" + "github.com/cufee/aftermath/internal/stats/prepare/period/v1" + "github.com/cufee/aftermath/internal/stats/render/common/v1" ) func statsBlocksToColumnBlock(style overviewStyle, statsBlocks []prepare.StatsBlock[period.BlockData]) (common.Block, error) { diff --git a/internal/stats/render/period/cards.go b/internal/stats/render/period/v1/cards.go similarity index 97% rename from internal/stats/render/period/cards.go rename to internal/stats/render/period/v1/cards.go index 0074d6fc..b03cc4b0 100644 --- a/internal/stats/render/period/cards.go +++ b/internal/stats/render/period/v1/cards.go @@ -6,11 +6,11 @@ import ( "github.com/pkg/errors" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats/fetch" - prepare "github.com/cufee/aftermath/internal/stats/prepare/common" - "github.com/cufee/aftermath/internal/stats/prepare/period" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + prepare "github.com/cufee/aftermath/internal/stats/prepare/common/v1" + "github.com/cufee/aftermath/internal/stats/prepare/period/v1" "github.com/cufee/aftermath/internal/stats/render" - "github.com/cufee/aftermath/internal/stats/render/common" + "github.com/cufee/aftermath/internal/stats/render/common/v1" "github.com/rs/zerolog/log" ) diff --git a/internal/stats/render/period/constants.go b/internal/stats/render/period/v1/constants.go similarity index 95% rename from internal/stats/render/period/constants.go rename to internal/stats/render/period/v1/constants.go index 74837a58..ed174590 100644 --- a/internal/stats/render/period/constants.go +++ b/internal/stats/render/period/v1/constants.go @@ -1,9 +1,9 @@ package period import ( - prepare "github.com/cufee/aftermath/internal/stats/prepare/common" - "github.com/cufee/aftermath/internal/stats/prepare/period" - "github.com/cufee/aftermath/internal/stats/render/common" + prepare "github.com/cufee/aftermath/internal/stats/prepare/common/v1" + "github.com/cufee/aftermath/internal/stats/prepare/period/v1" + "github.com/cufee/aftermath/internal/stats/render/common/v1" ) type overviewStyle struct { diff --git a/internal/stats/render/period/image.go b/internal/stats/render/period/v1/image.go similarity index 81% rename from internal/stats/render/period/image.go rename to internal/stats/render/period/v1/image.go index 47f9f5aa..f7e67e10 100644 --- a/internal/stats/render/period/image.go +++ b/internal/stats/render/period/v1/image.go @@ -4,8 +4,8 @@ import ( "image" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats/fetch" - "github.com/cufee/aftermath/internal/stats/prepare/period" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + "github.com/cufee/aftermath/internal/stats/prepare/period/v1" "github.com/cufee/aftermath/internal/stats/render" ) diff --git a/internal/stats/render/replay/blocks.go.todo b/internal/stats/render/replay/v1/blocks.go.todo similarity index 98% rename from internal/stats/render/replay/blocks.go.todo rename to internal/stats/render/replay/v1/blocks.go.todo index ab08e47b..1759c735 100644 --- a/internal/stats/render/replay/blocks.go.todo +++ b/internal/stats/render/replay/v1/blocks.go.todo @@ -7,7 +7,7 @@ import ( "github.com/cufee/aftermath-core/dataprep" "github.com/cufee/aftermath-core/dataprep/replay" prepare "github.com/cufee/aftermath/internal/stats/prepare/replay" - "github.com/cufee/aftermath/internal/stats/render/common" + "github.com/cufee/aftermath/internal/stats/render/common/v1" ) func newTitleBlock(replay *prepare.Replay, width float64, printer func(string) string) common.Block { diff --git a/internal/stats/render/replay/cards.go.todo b/internal/stats/render/replay/v1/cards.go.todo similarity index 100% rename from internal/stats/render/replay/cards.go.todo rename to internal/stats/render/replay/v1/cards.go.todo diff --git a/internal/stats/render/replay/constants.go.todo b/internal/stats/render/replay/v1/constants.go.todo similarity index 100% rename from internal/stats/render/replay/constants.go.todo rename to internal/stats/render/replay/v1/constants.go.todo diff --git a/internal/stats/render/replay/elements.go.todo b/internal/stats/render/replay/v1/elements.go.todo similarity index 100% rename from internal/stats/render/replay/elements.go.todo rename to internal/stats/render/replay/v1/elements.go.todo diff --git a/internal/stats/render/replay/preset.go.todo b/internal/stats/render/replay/v1/preset.go.todo similarity index 100% rename from internal/stats/render/replay/preset.go.todo rename to internal/stats/render/replay/v1/preset.go.todo diff --git a/internal/stats/render/segments.go b/internal/stats/render/segments.go index 222d8eaf..76ad27ec 100644 --- a/internal/stats/render/segments.go +++ b/internal/stats/render/segments.go @@ -5,7 +5,7 @@ import ( "github.com/pkg/errors" - "github.com/cufee/aftermath/internal/stats/render/common" + "github.com/cufee/aftermath/internal/stats/render/common/v1" ) type Segments struct { diff --git a/internal/stats/render/session/blocks.go b/internal/stats/render/session/v1/blocks.go similarity index 97% rename from internal/stats/render/session/blocks.go rename to internal/stats/render/session/v1/blocks.go index 8f7bc811..6dba350c 100644 --- a/internal/stats/render/session/blocks.go +++ b/internal/stats/render/session/v1/blocks.go @@ -4,10 +4,10 @@ import ( "image/color" "github.com/cufee/aftermath/internal/stats/frame" - prepare "github.com/cufee/aftermath/internal/stats/prepare/common" - "github.com/cufee/aftermath/internal/stats/prepare/session" + prepare "github.com/cufee/aftermath/internal/stats/prepare/common/v1" + "github.com/cufee/aftermath/internal/stats/prepare/session/v1" "github.com/cufee/aftermath/internal/stats/render/assets" - "github.com/cufee/aftermath/internal/stats/render/common" + "github.com/cufee/aftermath/internal/stats/render/common/v1" "github.com/disintegration/imaging" "github.com/fogleman/gg" ) diff --git a/internal/stats/render/session/cards.go b/internal/stats/render/session/v1/cards.go similarity index 98% rename from internal/stats/render/session/cards.go rename to internal/stats/render/session/v1/cards.go index 28dd1dca..6d129ef4 100644 --- a/internal/stats/render/session/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -5,11 +5,11 @@ import ( "strings" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/aftermath/internal/stats/frame" - prepare "github.com/cufee/aftermath/internal/stats/prepare/common" - "github.com/cufee/aftermath/internal/stats/prepare/session" - "github.com/cufee/aftermath/internal/stats/render/common" + prepare "github.com/cufee/aftermath/internal/stats/prepare/common/v1" + "github.com/cufee/aftermath/internal/stats/prepare/session/v1" + "github.com/cufee/aftermath/internal/stats/render/common/v1" "github.com/cufee/aftermath/internal/stats/render" ) diff --git a/internal/stats/render/session/constants.go b/internal/stats/render/session/v1/constants.go similarity index 98% rename from internal/stats/render/session/constants.go rename to internal/stats/render/session/v1/constants.go index e85ffc62..8ef10208 100644 --- a/internal/stats/render/session/constants.go +++ b/internal/stats/render/session/v1/constants.go @@ -3,7 +3,7 @@ package session import ( "image/color" - "github.com/cufee/aftermath/internal/stats/render/common" + "github.com/cufee/aftermath/internal/stats/render/common/v1" ) type blockStyle struct { diff --git a/internal/stats/render/session/icons.go b/internal/stats/render/session/v1/icons.go similarity index 93% rename from internal/stats/render/session/icons.go rename to internal/stats/render/session/v1/icons.go index 33a87c4e..1684fbaf 100644 --- a/internal/stats/render/session/icons.go +++ b/internal/stats/render/session/v1/icons.go @@ -3,7 +3,7 @@ package session import ( "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/aftermath/internal/stats/render/assets" - "github.com/cufee/aftermath/internal/stats/render/common" + "github.com/cufee/aftermath/internal/stats/render/common/v1" ) var iconsCache = make(map[string]common.Block, 6) diff --git a/internal/stats/render/session/image.go b/internal/stats/render/session/v1/image.go similarity index 81% rename from internal/stats/render/session/image.go rename to internal/stats/render/session/v1/image.go index 65ea4196..3ea7aef5 100644 --- a/internal/stats/render/session/image.go +++ b/internal/stats/render/session/v1/image.go @@ -4,8 +4,8 @@ import ( "image" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats/fetch" - "github.com/cufee/aftermath/internal/stats/prepare/session" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + "github.com/cufee/aftermath/internal/stats/prepare/session/v1" "github.com/cufee/aftermath/internal/stats/render" ) diff --git a/internal/stats/errors.go b/internal/stats/renderer/v1/errors.go similarity index 85% rename from internal/stats/errors.go rename to internal/stats/renderer/v1/errors.go index 4bcd0ddf..a09051b0 100644 --- a/internal/stats/errors.go +++ b/internal/stats/renderer/v1/errors.go @@ -1,4 +1,4 @@ -package stats +package renderer import ( "github.com/pkg/errors" diff --git a/internal/stats/image.go b/internal/stats/renderer/v1/image.go similarity index 87% rename from internal/stats/image.go rename to internal/stats/renderer/v1/image.go index 7e46305c..d82f9507 100644 --- a/internal/stats/image.go +++ b/internal/stats/renderer/v1/image.go @@ -1,4 +1,4 @@ -package stats +package renderer import ( "image" @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" - "github.com/cufee/aftermath/internal/stats/render/common" + "github.com/cufee/aftermath/internal/stats/render/common/v1" ) type Image interface { diff --git a/internal/stats/metadata.go b/internal/stats/renderer/v1/metadata.go similarity index 90% rename from internal/stats/metadata.go rename to internal/stats/renderer/v1/metadata.go index 78c6a2a8..e5ffd158 100644 --- a/internal/stats/metadata.go +++ b/internal/stats/renderer/v1/metadata.go @@ -1,9 +1,9 @@ -package stats +package renderer import ( "time" - "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/fetch/v1" ) type Metadata struct { diff --git a/internal/stats/period.go b/internal/stats/renderer/v1/period.go similarity index 91% rename from internal/stats/period.go rename to internal/stats/renderer/v1/period.go index 96b518f7..6e499c7d 100644 --- a/internal/stats/period.go +++ b/internal/stats/renderer/v1/period.go @@ -1,4 +1,4 @@ -package stats +package renderer import ( "context" @@ -6,11 +6,11 @@ import ( "time" "github.com/cufee/aftermath/internal/localization" - "github.com/cufee/aftermath/internal/stats/fetch" - "github.com/cufee/aftermath/internal/stats/prepare/common" - prepare "github.com/cufee/aftermath/internal/stats/prepare/period" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + "github.com/cufee/aftermath/internal/stats/prepare/common/v1" + prepare "github.com/cufee/aftermath/internal/stats/prepare/period/v1" options "github.com/cufee/aftermath/internal/stats/render" - render "github.com/cufee/aftermath/internal/stats/render/period" + render "github.com/cufee/aftermath/internal/stats/render/period/v1" ) func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, opts ...options.Option) (Image, Metadata, error) { diff --git a/internal/stats/renderer.go b/internal/stats/renderer/v1/renderer.go similarity index 92% rename from internal/stats/renderer.go rename to internal/stats/renderer/v1/renderer.go index 9c144826..2b2023da 100644 --- a/internal/stats/renderer.go +++ b/internal/stats/renderer/v1/renderer.go @@ -1,4 +1,4 @@ -package stats +package renderer import ( "context" @@ -6,7 +6,7 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/wargaming" - "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/aftermath/internal/stats/render" "golang.org/x/text/language" ) diff --git a/internal/stats/renderer/v1/replay.go b/internal/stats/renderer/v1/replay.go new file mode 100644 index 00000000..35803f64 --- /dev/null +++ b/internal/stats/renderer/v1/replay.go @@ -0,0 +1 @@ +package renderer diff --git a/internal/stats/session.go b/internal/stats/renderer/v1/session.go similarity index 95% rename from internal/stats/session.go rename to internal/stats/renderer/v1/session.go index 3d7cd416..cc6b9b5a 100644 --- a/internal/stats/session.go +++ b/internal/stats/renderer/v1/session.go @@ -1,4 +1,4 @@ -package stats +package renderer import ( "context" @@ -9,11 +9,11 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/logic" - "github.com/cufee/aftermath/internal/stats/fetch" - "github.com/cufee/aftermath/internal/stats/prepare/common" - prepare "github.com/cufee/aftermath/internal/stats/prepare/session" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + "github.com/cufee/aftermath/internal/stats/prepare/common/v1" + prepare "github.com/cufee/aftermath/internal/stats/prepare/session/v1" options "github.com/cufee/aftermath/internal/stats/render" - render "github.com/cufee/aftermath/internal/stats/render/session" + render "github.com/cufee/aftermath/internal/stats/render/session/v1" "github.com/rs/zerolog/log" ) diff --git a/internal/stats/replay.go b/internal/stats/replay.go deleted file mode 100644 index 43b4fd56..00000000 --- a/internal/stats/replay.go +++ /dev/null @@ -1 +0,0 @@ -package stats diff --git a/main.go b/main.go index 3111fe1b..e51ab75d 100644 --- a/main.go +++ b/main.go @@ -20,10 +20,10 @@ import ( "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/logic" - "github.com/cufee/aftermath/internal/stats/fetch" + "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/aftermath/internal/stats/render/assets" - render "github.com/cufee/aftermath/internal/stats/render/common" + render "github.com/cufee/aftermath/internal/stats/render/common/v1" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -57,15 +57,15 @@ func main() { port := os.Getenv("PRIVATE_SERVER_PORT") servePrivate := server.NewServer(port, []server.Handler{ { - Path: "POST /tasks/restart", + Path: "POST /v1/tasks/restart", Func: private.RestartStaleTasks(cacheCoreClient), }, { - Path: "POST /accounts/import", + Path: "POST /v1/accounts/import", Func: private.LoadAccountsHandler(cacheCoreClient), }, { - Path: "POST /snapshots/{realm}", + Path: "POST /v1/snapshots/{realm}", Func: private.SaveRealmSnapshots(cacheCoreClient), }, }...) diff --git a/render_test.go b/render_test.go index 6dc761be..810b5f5c 100644 --- a/render_test.go +++ b/render_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/cufee/aftermath/internal/stats" "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" + stats "github.com/cufee/aftermath/internal/stats/renderer/v1" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "golang.org/x/text/language" From f3ab9e83605ee1763d8cb1423bec96bcea7c2b20 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 23 Jun 2024 19:13:17 -0400 Subject: [PATCH 155/341] more versioning --- cmds/discord/commands/session.go | 2 +- cmds/discord/commands/stats.go | 2 +- .../stats/render/{ => common/v1}/options.go | 2 +- internal/stats/render/common/v1/segments.go | 80 ++++++++++++++++++ internal/stats/render/period/v1/cards.go | 7 +- internal/stats/render/period/v1/image.go | 6 +- internal/stats/render/segments.go | 82 ------------------- internal/stats/render/session/v1/cards.go | 6 +- internal/stats/render/session/v1/image.go | 6 +- internal/stats/renderer/v1/period.go | 8 +- internal/stats/renderer/v1/renderer.go | 6 +- internal/stats/renderer/v1/session.go | 2 +- render_test.go | 4 +- 13 files changed, 104 insertions(+), 109 deletions(-) rename internal/stats/render/{ => common/v1}/options.go (96%) create mode 100644 internal/stats/render/common/v1/segments.go delete mode 100644 internal/stats/render/segments.go diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go index 0fd10023..b81e0e78 100644 --- a/cmds/discord/commands/session.go +++ b/cmds/discord/commands/session.go @@ -11,8 +11,8 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch/v1" - "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" + render "github.com/cufee/aftermath/internal/stats/render/common/v1" stats "github.com/cufee/aftermath/internal/stats/renderer/v1" "github.com/pkg/errors" ) diff --git a/cmds/discord/commands/stats.go b/cmds/discord/commands/stats.go index 70f57879..505c21ef 100644 --- a/cmds/discord/commands/stats.go +++ b/cmds/discord/commands/stats.go @@ -10,8 +10,8 @@ import ( "github.com/cufee/aftermath/cmds/discord/common" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" + render "github.com/cufee/aftermath/internal/stats/render/common/v1" ) func init() { diff --git a/internal/stats/render/options.go b/internal/stats/render/common/v1/options.go similarity index 96% rename from internal/stats/render/options.go rename to internal/stats/render/common/v1/options.go index 014a43c4..c334d085 100644 --- a/internal/stats/render/options.go +++ b/internal/stats/render/common/v1/options.go @@ -1,4 +1,4 @@ -package render +package common import ( "image" diff --git a/internal/stats/render/common/v1/segments.go b/internal/stats/render/common/v1/segments.go new file mode 100644 index 00000000..869548f5 --- /dev/null +++ b/internal/stats/render/common/v1/segments.go @@ -0,0 +1,80 @@ +package common + +import ( + "image" + + "github.com/pkg/errors" +) + +type Segments struct { + header []Block + content []Block + footer []Block +} + +func (s *Segments) AddHeader(blocks ...Block) { + s.header = append(s.header, blocks...) +} + +func (s *Segments) AddContent(blocks ...Block) { + s.content = append(s.content, blocks...) +} + +func (s *Segments) AddFooter(blocks ...Block) { + s.footer = append(s.footer, blocks...) +} + +func (s *Segments) Render(opts ...Option) (image.Image, error) { + if len(s.content) < 1 { + return nil, errors.New("segments.content cannot be empty") + } + + options := DefaultOptions() + for _, apply := range opts { + apply(&options) + } + + var frameParts []Block + + mainSegment := NewBlocksContent( + Style{ + Direction: DirectionVertical, + AlignItems: AlignItemsCenter, + PaddingX: 20, + PaddingY: 20, + Gap: 10, + }, s.content...) + mainSegmentImg, err := mainSegment.Render() + if err != nil { + return nil, err + } + mainSegmentImg = AddBackground(mainSegmentImg, options.Background, Style{Blur: 10, BorderRadius: 42.5}) + + frameParts = append(frameParts, NewImageContent(Style{}, mainSegmentImg)) + if len(s.header) > 0 { + frameParts = append(frameParts, NewBlocksContent( + Style{ + Direction: DirectionVertical, + AlignItems: AlignItemsCenter, + Gap: 10, + }, s.header...)) + } + if len(s.footer) > 0 { + frameParts = append(frameParts, NewBlocksContent( + Style{ + Direction: DirectionVertical, + AlignItems: AlignItemsCenter, + Gap: 10, + }, s.footer...)) + } + + frame := NewBlocksContent( + Style{ + Direction: DirectionVertical, + AlignItems: AlignItemsCenter, + Gap: 5, + }, + frameParts..., + ) + return frame.Render() +} diff --git a/internal/stats/render/period/v1/cards.go b/internal/stats/render/period/v1/cards.go index b03cc4b0..2fa066d9 100644 --- a/internal/stats/render/period/v1/cards.go +++ b/internal/stats/render/period/v1/cards.go @@ -9,19 +9,18 @@ import ( "github.com/cufee/aftermath/internal/stats/fetch/v1" prepare "github.com/cufee/aftermath/internal/stats/prepare/common/v1" "github.com/cufee/aftermath/internal/stats/prepare/period/v1" - "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/common/v1" "github.com/rs/zerolog/log" ) -func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs []models.UserSubscription, opts render.Options) (render.Segments, error) { +func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs []models.UserSubscription, opts common.Options) (common.Segments, error) { if len(cards.Overview.Blocks) == 0 && len(cards.Highlights) == 0 { log.Error().Msg("player cards slice is 0 length, this should not happen") - return render.Segments{}, errors.New("no cards provided") + return common.Segments{}, errors.New("no cards provided") } - var segments render.Segments + var segments common.Segments // Calculate minimal card width to fit all the content var cardWidth float64 diff --git a/internal/stats/render/period/v1/image.go b/internal/stats/render/period/v1/image.go index f7e67e10..e8f0c56e 100644 --- a/internal/stats/render/period/v1/image.go +++ b/internal/stats/render/period/v1/image.go @@ -6,11 +6,11 @@ import ( "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/aftermath/internal/stats/prepare/period/v1" - "github.com/cufee/aftermath/internal/stats/render" + "github.com/cufee/aftermath/internal/stats/render/common/v1" ) -func CardsToImage(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs []models.UserSubscription, opts ...render.Option) (image.Image, error) { - o := render.DefaultOptions() +func CardsToImage(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs []models.UserSubscription, opts ...common.Option) (image.Image, error) { + o := common.DefaultOptions() for _, apply := range opts { apply(&o) } diff --git a/internal/stats/render/segments.go b/internal/stats/render/segments.go deleted file mode 100644 index 76ad27ec..00000000 --- a/internal/stats/render/segments.go +++ /dev/null @@ -1,82 +0,0 @@ -package render - -import ( - "image" - - "github.com/pkg/errors" - - "github.com/cufee/aftermath/internal/stats/render/common/v1" -) - -type Segments struct { - header []common.Block - content []common.Block - footer []common.Block -} - -func (s *Segments) AddHeader(blocks ...common.Block) { - s.header = append(s.header, blocks...) -} - -func (s *Segments) AddContent(blocks ...common.Block) { - s.content = append(s.content, blocks...) -} - -func (s *Segments) AddFooter(blocks ...common.Block) { - s.footer = append(s.footer, blocks...) -} - -func (s *Segments) Render(opts ...Option) (image.Image, error) { - if len(s.content) < 1 { - return nil, errors.New("segments.content cannot be empty") - } - - options := DefaultOptions() - for _, apply := range opts { - apply(&options) - } - - var frameParts []common.Block - - mainSegment := common.NewBlocksContent( - common.Style{ - Direction: common.DirectionVertical, - AlignItems: common.AlignItemsCenter, - PaddingX: 20, - PaddingY: 20, - Gap: 10, - }, s.content...) - mainSegmentImg, err := mainSegment.Render() - if err != nil { - return nil, err - } - mainSegmentImg = common.AddBackground(mainSegmentImg, options.Background, common.Style{Blur: 10, BorderRadius: 42.5}) - - frameParts = append(frameParts, common.NewImageContent(common.Style{}, mainSegmentImg)) - if len(s.header) > 0 { - frameParts = append(frameParts, common.NewBlocksContent( - common.Style{ - Direction: common.DirectionVertical, - AlignItems: common.AlignItemsCenter, - Gap: 10, - }, s.header...)) - } - if len(s.footer) > 0 { - frameParts = append(frameParts, common.NewBlocksContent( - common.Style{ - Direction: common.DirectionVertical, - AlignItems: common.AlignItemsCenter, - Gap: 10, - }, s.footer...)) - } - - frame := common.NewBlocksContent( - common.Style{ - Direction: common.DirectionVertical, - AlignItems: common.AlignItemsCenter, - Gap: 5, - }, - frameParts..., - ) - return frame.Render() -} diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index 6d129ef4..2eed140d 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -10,11 +10,9 @@ import ( prepare "github.com/cufee/aftermath/internal/stats/prepare/common/v1" "github.com/cufee/aftermath/internal/stats/prepare/session/v1" "github.com/cufee/aftermath/internal/stats/render/common/v1" - - "github.com/cufee/aftermath/internal/stats/render" ) -func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []models.UserSubscription, opts render.Options) (render.Segments, error) { +func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []models.UserSubscription, opts common.Options) (common.Segments, error) { var ( // primary cards // when there are some unrated battles or no battles at all @@ -28,7 +26,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card shouldRenderUnratedVehicles = len(cards.Unrated.Vehicles) > 0 ) - var segments render.Segments + var segments common.Segments var primaryColumn []common.Block var secondaryColumn []common.Block diff --git a/internal/stats/render/session/v1/image.go b/internal/stats/render/session/v1/image.go index 3ea7aef5..69d5de1b 100644 --- a/internal/stats/render/session/v1/image.go +++ b/internal/stats/render/session/v1/image.go @@ -6,11 +6,11 @@ import ( "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/aftermath/internal/stats/prepare/session/v1" - "github.com/cufee/aftermath/internal/stats/render" + "github.com/cufee/aftermath/internal/stats/render/common/v1" ) -func CardsToImage(session, career fetch.AccountStatsOverPeriod, cards session.Cards, subs []models.UserSubscription, opts ...render.Option) (image.Image, error) { - o := render.DefaultOptions() +func CardsToImage(session, career fetch.AccountStatsOverPeriod, cards session.Cards, subs []models.UserSubscription, opts ...common.Option) (image.Image, error) { + o := common.DefaultOptions() for _, apply := range opts { apply(&o) } diff --git a/internal/stats/renderer/v1/period.go b/internal/stats/renderer/v1/period.go index 6e499c7d..bea9d49f 100644 --- a/internal/stats/renderer/v1/period.go +++ b/internal/stats/renderer/v1/period.go @@ -7,13 +7,13 @@ import ( "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/stats/fetch/v1" - "github.com/cufee/aftermath/internal/stats/prepare/common/v1" + pcommon "github.com/cufee/aftermath/internal/stats/prepare/common/v1" prepare "github.com/cufee/aftermath/internal/stats/prepare/period/v1" - options "github.com/cufee/aftermath/internal/stats/render" + rcommon "github.com/cufee/aftermath/internal/stats/render/common/v1" render "github.com/cufee/aftermath/internal/stats/render/period/v1" ) -func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, opts ...options.Option) (Image, Metadata, error) { +func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, opts ...rcommon.Option) (Image, Metadata, error) { meta := Metadata{Stats: make(map[string]fetch.AccountStatsOverPeriod)} printer, err := localization.NewPrinter("stats", r.locale) @@ -47,7 +47,7 @@ func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, stop() stop = meta.Timer("prepare#NewCards") - cards, err := prepare.NewCards(stats, glossary, common.WithPrinter(printer, r.locale)) + cards, err := prepare.NewCards(stats, glossary, pcommon.WithPrinter(printer, r.locale)) stop() if err != nil { return nil, meta, err diff --git a/internal/stats/renderer/v1/renderer.go b/internal/stats/renderer/v1/renderer.go index 2b2023da..fab17367 100644 --- a/internal/stats/renderer/v1/renderer.go +++ b/internal/stats/renderer/v1/renderer.go @@ -7,7 +7,7 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/stats/fetch/v1" - "github.com/cufee/aftermath/internal/stats/render" + "github.com/cufee/aftermath/internal/stats/render/common/v1" "golang.org/x/text/language" ) @@ -21,8 +21,8 @@ type renderer struct { } type Renderer interface { - Period(ctx context.Context, accountId string, from time.Time, opts ...render.Option) (Image, Metadata, error) - Session(ctx context.Context, accountId string, from time.Time, opts ...render.Option) (Image, Metadata, error) + Period(ctx context.Context, accountId string, from time.Time, opts ...common.Option) (Image, Metadata, error) + Session(ctx context.Context, accountId string, from time.Time, opts ...common.Option) (Image, Metadata, error) // Replay(accountId string, from time.Time) (image.Image, error) } diff --git a/internal/stats/renderer/v1/session.go b/internal/stats/renderer/v1/session.go index cc6b9b5a..f1123e8b 100644 --- a/internal/stats/renderer/v1/session.go +++ b/internal/stats/renderer/v1/session.go @@ -12,7 +12,7 @@ import ( "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/aftermath/internal/stats/prepare/common/v1" prepare "github.com/cufee/aftermath/internal/stats/prepare/session/v1" - options "github.com/cufee/aftermath/internal/stats/render" + options "github.com/cufee/aftermath/internal/stats/render/common/v1" render "github.com/cufee/aftermath/internal/stats/render/session/v1" "github.com/rs/zerolog/log" ) diff --git a/render_test.go b/render_test.go index 810b5f5c..016202cf 100644 --- a/render_test.go +++ b/render_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/cufee/aftermath/internal/stats/render" "github.com/cufee/aftermath/internal/stats/render/assets" + "github.com/cufee/aftermath/internal/stats/render/common/v1" stats "github.com/cufee/aftermath/internal/stats/renderer/v1" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" @@ -29,7 +29,7 @@ func TestRenderSession(t *testing.T) { assert.True(t, ok, "failed to load a background image") renderer := stats.NewRenderer(coreClient.Fetch(), coreClient.Database(), coreClient.Wargaming(), language.English) - image, _, err := renderer.Session(context.Background(), "1013072123", time.Now(), render.WithBackground(bgImage)) + image, _, err := renderer.Session(context.Background(), "1013072123", time.Now(), common.WithBackground(bgImage)) assert.NoError(t, err, "failed to render a session image") assert.NotNil(t, image, "image is nil") From f1fc4415ee8eb0ba691d527c1fa0f164c1ba7f88 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 07:38:42 -0400 Subject: [PATCH 156/341] added mod command, rolled back wg proxy changes --- .env.example | 4 ++-- cmds/discord/commands/manage.go | 36 ++++++++++++++++++++++++++++++--- cmds/discord/rest/client.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- internal/database/client.go | 1 + internal/database/tasks.go | 18 +++++++++++++++++ 7 files changed, 58 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index f49d5131..fbe664d0 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ -# Path for a bind volume when running with compose -DATABASE_PATH="tmp/deb_local.db" +DATABASE_PATH="/absolute/path/" +DATABASE_NAME="aftermath.db" # Init INIT_GLOBAL_ADMIN_USER="" # Discord user ID for a user who will be assigned permissions.GlobalAdmin on startup, can be left blank diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index 34f9d6a6..1c0a3bbe 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -2,6 +2,7 @@ package commands import ( "encoding/json" + "strings" "time" "github.com/bwmarrin/discordgo" @@ -38,6 +39,9 @@ func init() { ).Required(), builder.NewOption("hours", discordgo.ApplicationCommandOptionNumber).Required(), ), + builder.NewOption("details", discordgo.ApplicationCommandOptionSubCommand).Options( + builder.NewOption("id", discordgo.ApplicationCommandOptionString).Required(), + ), ), builder.NewOption("snapshots", discordgo.ApplicationCommandOptionSubCommandGroup).Options( builder.NewOption("view", discordgo.ApplicationCommandOptionSubCommand).Options( @@ -119,14 +123,40 @@ func init() { return ctx.Reply("No recent tasks with status " + status) } + var ids []string + for _, t := range tasks { + ids = append(ids, t.ID) + } + + content := strings.Join(ids, "\n") + if len(content) > 1990 { + content = content[:1990] + } + return ctx.Reply("```" + content + "```") + + case "tasks_details": + id, _ := opts.Value("id").(string) + if id == "" { + return ctx.Reply("id cannot be blank") + } + + tasks, err := ctx.Core.Database().GetTasks(ctx.Context, id) + if err != nil { + return ctx.Reply("Database#GetTasks: " + err.Error()) + } + if len(tasks) < 1 { + return ctx.Reply("No recent task found") + } + bytes, err := json.MarshalIndent(tasks, "", " ") if err != nil { return ctx.Reply("json.Marshal: " + err.Error()) } - if len(string(bytes)) > 1990 { - return ctx.ReplyFmt("Too many tasks to show - %d", len(tasks)) + content := string(bytes) + if len(content) > 1990 { + content = content[:1990] } - return ctx.Reply("```" + string(bytes) + "```") + return ctx.Reply("```" + content + "```") default: return ctx.Reply("invalid subcommand, thought this should never happen") diff --git a/cmds/discord/rest/client.go b/cmds/discord/rest/client.go index 4307d565..e5b829f8 100644 --- a/cmds/discord/rest/client.go +++ b/cmds/discord/rest/client.go @@ -26,7 +26,7 @@ type Client struct { func NewClient(token string) (*Client, error) { client := &Client{ token: token, - http: http.Client{Timeout: time.Millisecond * 1000}, + http: http.Client{Timeout: time.Millisecond * 5000}, // discord is very slow sometimes } _, err := client.lookupApplicationID() diff --git a/go.mod b/go.mod index dbc963ed..bf5fd037 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 entgo.io/ent v0.13.1 github.com/bwmarrin/discordgo v0.28.1 - github.com/cufee/am-wg-proxy-next/v2 v2.1.4 + github.com/cufee/am-wg-proxy-next/v2 v2.1.3 github.com/disintegration/imaging v1.6.2 github.com/fogleman/gg v1.3.0 github.com/go-co-op/gocron v1.37.0 diff --git a/go.sum b/go.sum index 4f848a8b..24ee26f5 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cufee/am-wg-proxy-next/v2 v2.1.4 h1:2EtRrktt2eXcomZB868J2QYuxI1NmwyFrd9EqORD21Y= -github.com/cufee/am-wg-proxy-next/v2 v2.1.4/go.mod h1:xrgMXzAMU4p5AW1++nnpofNFZLOiSUP8vnezzzG92Zw= +github.com/cufee/am-wg-proxy-next/v2 v2.1.3 h1:qTzzgEapbI/z81R3nXoiiITfBZeHQ1cnebzSNgaoZlQ= +github.com/cufee/am-wg-proxy-next/v2 v2.1.3/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= 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= diff --git a/internal/database/client.go b/internal/database/client.go index eecf20c0..e8258b80 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -56,6 +56,7 @@ type SnapshotsClient interface { type TasksClient interface { CreateTasks(ctx context.Context, tasks ...models.Task) error + GetTasks(ctx context.Context, ids ...string) ([]models.Task, error) UpdateTasks(ctx context.Context, tasks ...models.Task) error DeleteTasks(ctx context.Context, ids ...string) error diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 56de971a..0b63828a 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -137,6 +137,24 @@ func (c *client) CreateTasks(ctx context.Context, tasks ...models.Task) error { return c.db.CronTask.CreateBulk(inserts...).Exec(ctx) } +func (c *client) GetTasks(ctx context.Context, ids ...string) ([]models.Task, error) { + if len(ids) < 1 { + return nil, nil + } + + records, err := c.db.CronTask.Query().Where(crontask.IDIn(ids...)).All(ctx) + if err != nil { + return nil, err + } + + var tasks []models.Task + for _, r := range records { + tasks = append(tasks, toCronTask(r)) + } + + return tasks, nil +} + /* UpdateTasks will update all tasks passed in - the following fields will be replaced: targets, status, leastRun, scheduleAfterm logs, data From 00b958317464b6eadbc4cd63f1da17bb622f5635 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 07:43:21 -0400 Subject: [PATCH 157/341] removed indents --- cmds/discord/commands/manage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index 1c0a3bbe..0eba3870 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -148,7 +148,7 @@ func init() { return ctx.Reply("No recent task found") } - bytes, err := json.MarshalIndent(tasks, "", " ") + bytes, err := json.Marshal(tasks) if err != nil { return ctx.Reply("json.Marshal: " + err.Error()) } From a1293f636be5080c15ad79a4a7bd9295a39b2c81 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 07:50:08 -0400 Subject: [PATCH 158/341] sending result as txt file --- cmds/discord/commands/manage.go | 20 +++++++------------- internal/database/models/task.go | 8 -------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index 0eba3870..ce5ee5bc 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -1,7 +1,9 @@ package commands import ( + "bytes" "encoding/json" + "fmt" "strings" "time" @@ -35,6 +37,7 @@ func init() { builder.NewOption("status", discordgo.ApplicationCommandOptionString).Choices( builder.NewChoice("failed", string(models.TaskStatusFailed)), builder.NewChoice("complete", string(models.TaskStatusComplete)), + builder.NewChoice("scheduled", string(models.TaskStatusScheduled)), builder.NewChoice("in-progress", string(models.TaskStatusInProgress)), ).Required(), builder.NewOption("hours", discordgo.ApplicationCommandOptionNumber).Required(), @@ -123,16 +126,11 @@ func init() { return ctx.Reply("No recent tasks with status " + status) } - var ids []string + ids := []string{fmt.Sprintf("total: %d", len(tasks))} for _, t := range tasks { ids = append(ids, t.ID) } - - content := strings.Join(ids, "\n") - if len(content) > 1990 { - content = content[:1990] - } - return ctx.Reply("```" + content + "```") + return ctx.File(bytes.NewBufferString(strings.Join(ids, "\n")), "tasks.txt") case "tasks_details": id, _ := opts.Value("id").(string) @@ -148,15 +146,11 @@ func init() { return ctx.Reply("No recent task found") } - bytes, err := json.Marshal(tasks) + data, err := json.MarshalIndent(tasks, "", " ") if err != nil { return ctx.Reply("json.Marshal: " + err.Error()) } - content := string(bytes) - if len(content) > 1990 { - content = content[:1990] - } - return ctx.Reply("```" + content + "```") + return ctx.File(bytes.NewReader(data), "tasks.txt") default: return ctx.Reply("invalid subcommand, thought this should never happen") diff --git a/internal/database/models/task.go b/internal/database/models/task.go index c11caf6a..77adbaab 100644 --- a/internal/database/models/task.go +++ b/internal/database/models/task.go @@ -82,18 +82,10 @@ func (t *Task) OnCreated() { t.LastRun = time.Now() t.CreatedAt = time.Now() t.UpdatedAt = time.Now() - t.Logs = append(t.Logs, TaskLog{ - Comment: "task created", - Timestamp: time.Now(), - }) } func (t *Task) OnUpdated() { t.LastRun = time.Now() t.UpdatedAt = time.Now() - t.Logs = append(t.Logs, TaskLog{ - Comment: "task updated", - Timestamp: time.Now(), - }) } type TaskLog struct { From 13de1de827fe5458cd1f5da8065b3acde4e739a4 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 08:13:50 -0400 Subject: [PATCH 159/341] inserting snapshot individually to avoid SQL errors --- cmds/discord/commands/manage.go | 22 +++++++--- internal/database/client.go | 4 +- internal/database/snapshots.go | 71 +++++++++++++++++---------------- internal/logic/sessions.go | 22 +++++++--- 4 files changed, 72 insertions(+), 47 deletions(-) diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index ce5ee5bc..ec1d926c 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "strings" "time" "github.com/bwmarrin/discordgo" @@ -126,11 +125,24 @@ func init() { return ctx.Reply("No recent tasks with status " + status) } - ids := []string{fmt.Sprintf("total: %d", len(tasks))} + content := fmt.Sprintf("total: %d\n", len(tasks)) + var reduced []map[string]any for _, t := range tasks { - ids = append(ids, t.ID) + reduced = append(reduced, map[string]any{ + "id": t.ID, + "type": t.Type, + "targets": len(t.Targets), + "referenceID": t.ReferenceID, + "lastRun": t.LastRun, + }) } - return ctx.File(bytes.NewBufferString(strings.Join(ids, "\n")), "tasks.txt") + data, err := json.MarshalIndent(reduced, "", " ") + if err != nil { + return ctx.Reply("json.Marshal: " + err.Error()) + } + content += string(data) + + return ctx.File(bytes.NewBufferString(content), "tasks.json") case "tasks_details": id, _ := opts.Value("id").(string) @@ -150,7 +162,7 @@ func init() { if err != nil { return ctx.Reply("json.Marshal: " + err.Error()) } - return ctx.File(bytes.NewReader(data), "tasks.txt") + return ctx.File(bytes.NewReader(data), "tasks.json") default: return ctx.Reply("invalid subcommand, thought this should never happen") diff --git a/internal/database/client.go b/internal/database/client.go index e8258b80..b3c93c9a 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -44,12 +44,12 @@ type UsersClient interface { type SnapshotsClient interface { GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) (models.AccountSnapshot, error) - CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) error + CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) (map[string]error, error) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.AccountSnapshot, error) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) - CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error + CreateAccountVehicleSnapshots(ctx context.Context, accountID string, snapshots ...models.VehicleSnapshot) (map[string]error, error) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error } diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 88cb5465..cd344cf7 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -50,26 +50,28 @@ func toVehicleSnapshot(record *db.VehicleSnapshot) models.VehicleSnapshot { } } -func (c *client) CreateVehicleSnapshots(ctx context.Context, snapshots ...models.VehicleSnapshot) error { +func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID string, snapshots ...models.VehicleSnapshot) (map[string]error, error) { if len(snapshots) < 1 { - return nil + return nil, nil } - var inserts []*db.VehicleSnapshotCreate + var errors = make(map[string]error) for _, data := range snapshots { - inserts = append(inserts, - c.db.VehicleSnapshot.Create(). - SetType(data.Type). - SetFrame(data.Stats). - SetVehicleID(data.VehicleID). - SetReferenceID(data.ReferenceID). - SetBattles(int(data.Stats.Battles.Float())). - SetLastBattleTime(data.LastBattleTime.Unix()). - SetAccount(c.db.Account.GetX(ctx, data.AccountID)), - ) - } - - return c.db.VehicleSnapshot.CreateBulk(inserts...).Exec(ctx) + err := c.db.VehicleSnapshot.Create(). + SetType(data.Type). + SetFrame(data.Stats). + SetVehicleID(data.VehicleID). + SetReferenceID(data.ReferenceID). + SetBattles(int(data.Stats.Battles.Float())). + SetLastBattleTime(data.LastBattleTime.Unix()). + SetAccount(c.db.Account.GetX(ctx, accountID)). + Exec(ctx) + if err != nil { + errors[data.VehicleID] = err + } + } + + return errors, nil } func (c *client) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) { @@ -121,28 +123,29 @@ func toAccountSnapshot(record *db.AccountSnapshot) models.AccountSnapshot { } } -func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) error { +func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) (map[string]error, error) { if len(snapshots) < 1 { - return nil + return nil, nil } - var inserts []*db.AccountSnapshotCreate + var errors = make(map[string]error) for _, s := range snapshots { - inserts = append(inserts, - c.db.AccountSnapshot.Create(). - SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here - SetCreatedAt(s.CreatedAt.Unix()). - SetLastBattleTime(s.LastBattleTime.Unix()). - SetRatingBattles(int(s.RatingBattles.Battles.Float())). - SetRatingFrame(s.RatingBattles). - SetReferenceID(s.ReferenceID). - SetRegularBattles(int(s.RegularBattles.Battles)). - SetRegularFrame(s.RegularBattles). - SetType(s.Type), - ) - } - - return c.db.AccountSnapshot.CreateBulk(inserts...).Exec(ctx) + err := c.db.AccountSnapshot.Create(). + SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here + SetCreatedAt(s.CreatedAt.Unix()). + SetLastBattleTime(s.LastBattleTime.Unix()). + SetRatingBattles(int(s.RatingBattles.Battles.Float())). + SetRatingFrame(s.RatingBattles). + SetReferenceID(s.ReferenceID). + SetRegularBattles(int(s.RegularBattles.Battles)). + SetRegularFrame(s.RegularBattles). + SetType(s.Type).Exec(ctx) + if err != nil { + errors[s.AccountID] = err + } + } + + return errors, nil } func (c *client) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) { diff --git a/internal/logic/sessions.go b/internal/logic/sessions.go index 80795daa..ee34a601 100644 --- a/internal/logic/sessions.go +++ b/internal/logic/sessions.go @@ -93,7 +93,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl var accountErrors = make(map[string]error) var accountUpdates []models.Account var snapshots []models.AccountSnapshot - var vehicleSnapshots []models.VehicleSnapshot + var vehicleSnapshots = make(map[string][]models.VehicleSnapshot) for result := range vehicleCh { // there is only 1 key in this map for id, vehicles := range result.Data { @@ -148,7 +148,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl continue } - vehicleSnapshots = append(vehicleSnapshots, models.VehicleSnapshot{ + vehicleSnapshots[stats.Account.ID] = append(vehicleSnapshots[stats.Account.ID], models.VehicleSnapshot{ CreatedAt: createdAt, Type: models.SnapshotTypeDaily, LastBattleTime: vehicle.LastBattleTime, @@ -161,14 +161,24 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } } - err = dbClient.CreateAccountSnapshots(ctx, snapshots...) + aErr, err := dbClient.CreateAccountSnapshots(ctx, snapshots...) if err != nil { return nil, errors.Wrap(err, "failed to save account snapshots to database") } + for id, err := range aErr { + if err != nil { + accountErrors[id] = err + } + } - err = dbClient.CreateVehicleSnapshots(ctx, vehicleSnapshots...) - if err != nil { - return nil, errors.Wrap(err, "failed to save vehicle snapshots to database") + for accountId, vehicles := range vehicleSnapshots { + vErr, err := dbClient.CreateAccountVehicleSnapshots(ctx, accountId, vehicles...) + if err != nil { + return nil, errors.Wrap(err, "failed to save vehicle snapshots to database") + } + if len(vErr) > 0 { + accountErrors[accountId] = errors.Errorf("failed to insert %d vehicle snapshots", len(vErr)) + } } return accountErrors, nil From 55e1c68d6b763025cd0d344e1eefdc0514d19601 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 08:18:30 -0400 Subject: [PATCH 160/341] upserting accounts --- internal/logic/sessions.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/internal/logic/sessions.go b/internal/logic/sessions.go index ee34a601..13c2ec95 100644 --- a/internal/logic/sessions.go +++ b/internal/logic/sessions.go @@ -112,18 +112,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } stats := fetch.WargamingToStats(realm, accounts[id], clans[id], vehicles) - accountUpdates = append(accountUpdates, models.Account{ - Realm: stats.Realm, - ID: stats.Account.ID, - Nickname: stats.Account.Nickname, - - Private: false, - CreatedAt: stats.Account.CreatedAt, - LastBattleTime: stats.LastBattleTime, - - ClanID: stats.Account.ClanID, - ClanTag: stats.Account.ClanTag, - }) + accountUpdates = append(accountUpdates, stats.Account) snapshots = append(snapshots, models.AccountSnapshot{ Type: models.SnapshotTypeDaily, CreatedAt: createdAt, @@ -181,5 +170,18 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } } + uErr, err := dbClient.UpsertAccounts(ctx, accountUpdates) + // these errors are not critical, we log and continue + if err != nil { + log.Err(err).Msg("failed to upsert accounts") + } + if len(uErr) > 0 { + var errors = make(map[string]string) + for id, err := range uErr { + errors[id] = err.Error() + } + log.Error().Any("errors", errors).Msg("failed to upsert some accounts") + } + return accountErrors, nil } From c0db8f9ab28ffdf703fc2e010f197ecb8f2e87ef Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 08:30:20 -0400 Subject: [PATCH 161/341] added stack traces --- cmds/core/scheduler/queue.go | 3 ++- cmds/discord/router/handler.go | 3 ++- internal/database/client.go | 3 ++- main.go | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cmds/core/scheduler/queue.go b/cmds/core/scheduler/queue.go index 669a3c9c..5eca5e38 100644 --- a/cmds/core/scheduler/queue.go +++ b/cmds/core/scheduler/queue.go @@ -2,6 +2,7 @@ package scheduler import ( "context" + "runtime/debug" "sync" "time" @@ -61,7 +62,7 @@ func (q *Queue) Process(callback func(error), tasks ...models.Task) { go func(t models.Task) { defer func() { if r := recover(); r != nil { - log.Error().Any("recover", r).Msg("panic in task handler") + log.Error().Str("stack", string(debug.Stack())).Msg("panic in task handler") t.Status = models.TaskStatusFailed t.LogAttempt(models.TaskLog{ Targets: t.Targets, diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 327f1858..53d76f72 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "net/http" + "runtime/debug" "sync" "time" @@ -210,7 +211,7 @@ func (router *Router) startInteractionHandlerAsync(interaction discordgo.Interac go func() { defer func() { if r := recover(); r != nil { - log.Error().Stack().Any("recover", r).Msg("panic in interaction handler") + log.Error().Str("stack", string(debug.Stack())).Msg("panic in interaction handler") state.mx.Lock() router.sendInteractionReply(interaction, state, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed. Please try again in a few seconds."}) state.mx.Unlock() diff --git a/internal/database/client.go b/internal/database/client.go index b3c93c9a..1fb6be9d 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -3,6 +3,7 @@ package database import ( "context" "fmt" + "runtime/debug" "sync" "time" @@ -108,7 +109,7 @@ func (c *client) txWithLock(ctx context.Context) (*db.Tx, func(), error) { func NewSQLiteClient(filePath string) (*client, error) { defer func() { if r := recover(); r != nil { - log.Fatal().Interface("error", r).Stack().Msg("NewSQLiteClient panic") + log.Fatal().Interface("error", r).Str("stack", string(debug.Stack())).Msg("NewSQLiteClient panic") } }() diff --git a/main.go b/main.go index e51ab75d..0b37f0bb 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "embed" "io/fs" "path/filepath" + "runtime/debug" "os" "strconv" @@ -108,7 +109,7 @@ func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Cli defer func() { if r := recover(); r != nil { - log.Error().Interface("error", r).Stack().Msg("scheduler panic") + log.Error().Str("stack", string(debug.Stack())).Interface("error", r).Stack().Msg("scheduler panic") } }() From b0143b5514b1828e3fad59c43b707090bdba685f Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 08:34:32 -0400 Subject: [PATCH 162/341] pulling 1 task per worker --- cmds/core/scheduler/tasks/sessions.go | 2 +- cmds/core/scheduler/workers.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index b4d6803b..0aaac75f 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -97,7 +97,7 @@ func CreateRecordSnapshotsTasks(client core.Client, realm string) error { task.Targets = append(task.Targets, accounts...) // This update requires (2 + n) requests per n players - tasks := splitTaskByTargets(task, 90) + tasks := splitTaskByTargets(task, 50) err = client.Database().CreateTasks(ctx, tasks...) if err != nil { return err diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index 50b6cce6..20328053 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -59,7 +59,7 @@ func RunTasksWorker(queue *Queue) func() { // each task worked handles 1 task at a time, but tasks might be very fast // for now, we queue up 10 tasks per worker, this can be adjusted later/smarter batchSize := queue.concurrencyLimit - activeWorkers - tasks, err := queue.core.Database().GetAndStartTasks(ctx, batchSize*10) + tasks, err := queue.core.Database().GetAndStartTasks(ctx, batchSize) if err != nil { if database.IsNotFound(err) { log.Debug().Msg("no scheduled tasks to process") From 04c6ffc23d9342dcac561237f13704ce6b00d640 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 08:55:15 -0400 Subject: [PATCH 163/341] more frequent scheduler --- cmds/core/scheduler/cron.go | 2 +- cmds/core/scheduler/workers.go | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go index f17bccac..36970b68 100644 --- a/cmds/core/scheduler/cron.go +++ b/cmds/core/scheduler/cron.go @@ -12,7 +12,7 @@ func (queue *Queue) StartCronJobsAsync() { c := gocron.NewScheduler(time.UTC) // Tasks - c.Cron("* * * * *").Do(RunTasksWorker(queue)) + c.CronWithSeconds("*/15 * * * * *").Do(RunTasksWorker(queue)) // some tasks might be stuck due to a panic or restart, restart them c.Cron("0 * * * *").Do(RestartTasksWorker(queue.core)) c.Cron("0 5 * * *").Do(CreateCleanupTaskWorker(queue.core)) // delete expired documents diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index 20328053..078ad939 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -56,8 +56,6 @@ func RunTasksWorker(queue *Queue) func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - // each task worked handles 1 task at a time, but tasks might be very fast - // for now, we queue up 10 tasks per worker, this can be adjusted later/smarter batchSize := queue.concurrencyLimit - activeWorkers tasks, err := queue.core.Database().GetAndStartTasks(ctx, batchSize) if err != nil { @@ -68,13 +66,17 @@ func RunTasksWorker(queue *Queue) func() { log.Err(err).Msg("failed to start scheduled tasks") return } + if len(tasks) < 1 { + log.Debug().Msg("no scheduled tasks to process") + return + } queue.Process(func(err error) { if err != nil { log.Err(err).Msg("failed to process tasks") return } - // if the queue is now empty, we can run the next batch of tasks right away + // try to start the next task batch right away RunTasksWorker(queue) }, tasks...) } From fc2d1d21beeea3a86afdd46a8ebc26fa761d7a42 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 12:01:16 -0400 Subject: [PATCH 164/341] refactored database connections and transactions --- cmds/core/scheduler/tasks/sessions.go | 10 +- internal/database/accounts.go | 58 ++++++------ internal/database/averages.go | 40 ++++---- internal/database/cleanup.go | 15 +-- internal/database/client.go | 54 ++++++----- internal/database/discord.go | 73 +++++++-------- internal/database/ent/db/client.go | 26 ++++++ internal/database/ent/db/tx.go | 26 ++++++ internal/database/ent/generate.go | 2 +- internal/database/snapshots.go | 64 ++++++------- internal/database/tasks.go | 129 +++++++++++--------------- internal/database/vehicles.go | 48 +++++----- main.go | 36 +++---- 13 files changed, 309 insertions(+), 272 deletions(-) diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/scheduler/tasks/sessions.go index 0aaac75f..0125d1ea 100644 --- a/cmds/core/scheduler/tasks/sessions.go +++ b/cmds/core/scheduler/tasks/sessions.go @@ -21,15 +21,15 @@ func init() { } realm, ok := task.Data["realm"].(string) if !ok { - task.Data["triesLeft"] = int(0) // do not retry + task.Data["triesLeft"] = int64(0) // do not retry return "invalid realm", errors.New("invalid realm") } if len(task.Targets) > 100 { - task.Data["triesLeft"] = int(0) // do not retry + task.Data["triesLeft"] = int64(0) // do not retry return "cannot process 100+ accounts at a time", errors.New("invalid targets length") } if len(task.Targets) < 1 { - task.Data["triesLeft"] = int(0) // do not retry + task.Data["triesLeft"] = int64(0) // do not retry return "target ids cannot be left blank", errors.New("invalid targets length") } forceUpdate, _ := task.Data["force"].(bool) @@ -56,7 +56,7 @@ func init() { return "retrying failed accounts", errors.New("some accounts failed") }, ShouldRetry: func(task *models.Task) bool { - triesLeft, ok := task.Data["triesLeft"].(int) + triesLeft, ok := task.Data["triesLeft"].(int64) if !ok { return false } @@ -80,7 +80,7 @@ func CreateRecordSnapshotsTasks(client core.Client, realm string) error { ScheduledAfter: time.Now(), Data: map[string]any{ "realm": realm, - "triesLeft": int(3), + "triesLeft": int64(3), }, } diff --git a/internal/database/accounts.go b/internal/database/accounts.go index ee9007d5..55c9ecf8 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -79,38 +79,40 @@ func (c *client) UpsertAccounts(ctx context.Context, accounts []models.Account) } errors := make(map[string]error) - for _, r := range records { - update, ok := accountsMap[r.ID] - if !ok { - continue // this should never happen tho + return errors, c.withTx(ctx, func(tx *db.Tx) error { + for _, r := range records { + update, ok := accountsMap[r.ID] + if !ok { + continue // this should never happen tho + } + + err = c.db.Account.UpdateOneID(r.ID). + SetRealm(strings.ToUpper(update.Realm)). + SetNickname(update.Nickname). + SetPrivate(update.Private). + SetLastBattleTime(update.LastBattleTime.Unix()). + Exec(ctx) + if err != nil { + errors[r.ID] = err + } + + delete(accountsMap, r.ID) } - err = c.db.Account.UpdateOneID(r.ID). - SetRealm(strings.ToUpper(update.Realm)). - SetNickname(update.Nickname). - SetPrivate(update.Private). - SetLastBattleTime(update.LastBattleTime.Unix()). - Exec(ctx) - if err != nil { - errors[r.ID] = err + var writes []*db.AccountCreate + for _, a := range accountsMap { + writes = append(writes, c.db.Account.Create(). + SetID(a.ID). + SetRealm(strings.ToUpper(a.Realm)). + SetNickname(a.Nickname). + SetPrivate(a.Private). + SetAccountCreatedAt(a.CreatedAt.Unix()). + SetLastBattleTime(a.LastBattleTime.Unix()), + ) } - delete(accountsMap, r.ID) - } - - var writes []*db.AccountCreate - for _, a := range accountsMap { - writes = append(writes, c.db.Account.Create(). - SetID(a.ID). - SetRealm(strings.ToUpper(a.Realm)). - SetNickname(a.Nickname). - SetPrivate(a.Private). - SetAccountCreatedAt(a.CreatedAt.Unix()). - SetLastBattleTime(a.LastBattleTime.Unix()), - ) - } - - return errors, c.db.Account.CreateBulk(writes...).Exec(ctx) + return c.db.Account.CreateBulk(writes...).Exec(ctx) + }) } func (c *client) AccountSetPrivate(ctx context.Context, id string, value bool) error { diff --git a/internal/database/averages.go b/internal/database/averages.go index e643beb4..6dffe796 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -24,29 +24,31 @@ func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string] } errors := make(map[string]error) - for _, r := range existing { - update, ok := averages[r.ID] - if !ok { - continue // should never happen tho - } + return errors, c.withTx(ctx, func(tx *db.Tx) error { + for _, r := range existing { + update, ok := averages[r.ID] + if !ok { + continue // should never happen tho + } - err := c.db.VehicleAverage.UpdateOneID(r.ID).SetData(update).Exec(ctx) - if err != nil { - errors[r.ID] = err - } + err := c.db.VehicleAverage.UpdateOneID(r.ID).SetData(update).Exec(ctx) + if err != nil { + errors[r.ID] = err + } - delete(averages, r.ID) - } + delete(averages, r.ID) + } - var writes []*db.VehicleAverageCreate - for id, frame := range averages { - writes = append(writes, c.db.VehicleAverage.Create(). - SetID(id). - SetData(frame), - ) - } + var writes []*db.VehicleAverageCreate + for id, frame := range averages { + writes = append(writes, c.db.VehicleAverage.Create(). + SetID(id). + SetData(frame), + ) + } - return errors, c.db.VehicleAverage.CreateBulk(writes...).Exec(ctx) + return c.db.VehicleAverage.CreateBulk(writes...).Exec(ctx) + }) } func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go index 4340f281..05d130f5 100644 --- a/internal/database/cleanup.go +++ b/internal/database/cleanup.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/cufee/aftermath/internal/database/ent/db" "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" "github.com/cufee/aftermath/internal/database/ent/db/crontask" @@ -11,17 +12,11 @@ import ( ) func (c *client) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { - tx, cancel, err := c.txWithLock(ctx) - if err != nil { + err := c.withTx(ctx, func(tx *db.Tx) error { + _, err := tx.CronTask.Delete().Where(crontask.CreatedAtLT(expiration.Unix())).Exec(ctx) return err - } - defer cancel() - - _, err = tx.CronTask.Delete().Where(crontask.CreatedAtLT(expiration.Unix())).Exec(ctx) - if err != nil { - return rollback(tx, err) - } - return tx.Commit() + }) + return err } func (c *client) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { diff --git a/internal/database/client.go b/internal/database/client.go index 1fb6be9d..d3884a84 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "runtime/debug" - "sync" "time" "github.com/cufee/aftermath/internal/database/ent/db" @@ -88,22 +87,34 @@ type Client interface { } type client struct { - db *db.Client - transactionLock *sync.Mutex + db *db.Client } -func (c *client) Disconnect() error { - return c.db.Close() -} - -func (c *client) txWithLock(ctx context.Context) (*db.Tx, func(), error) { - c.transactionLock.Lock() +func (c *client) withTx(ctx context.Context, fn func(tx *db.Tx) error) error { tx, err := c.db.Tx(ctx) if err != nil { - c.transactionLock.Unlock() - return tx, func() {}, nil + return err } - return tx, c.transactionLock.Unlock, nil + defer func() { + if v := recover(); v != nil { + tx.Rollback() + panic(v) + } + }() + if err := fn(tx); err != nil { + if rerr := tx.Rollback(); rerr != nil { + err = fmt.Errorf("%w: rolling back transaction: %v", err, rerr) + } + return err + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction: %w", err) + } + return nil +} + +func (c *client) Disconnect() error { + return c.db.Close() } func NewSQLiteClient(filePath string) (*client, error) { @@ -118,17 +129,14 @@ func NewSQLiteClient(filePath string) (*client, error) { return nil, err } + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _, err = c.ExecContext(ctx, "PRAGMA journal_mode=WAL") + if err != nil { + return nil, err + } + return &client{ - transactionLock: &sync.Mutex{}, - db: c, + db: c, }, nil } - -// rollback calls to tx.Rollback and wraps the given error -// with the rollback error if occurred. -func rollback(tx *db.Tx, err error) error { - if rerr := tx.Rollback(); rerr != nil { - err = fmt.Errorf("%w: %v", err, rerr) - } - return err -} diff --git a/internal/database/discord.go b/internal/database/discord.go index c78498ee..d7f1ebd9 100644 --- a/internal/database/discord.go +++ b/internal/database/discord.go @@ -63,50 +63,41 @@ func (c *client) UpsertCommands(ctx context.Context, commands ...models.Applicat commandsMap[c.ID] = &c } - tx, cancel, err := c.txWithLock(ctx) - if err != nil { - return err - } - defer cancel() - - existing, err := tx.ApplicationCommand.Query().Where(applicationcommand.IDIn(ids...)).All(ctx) - if err != nil && !IsNotFound(err) { - return rollback(tx, err) - } - - for _, c := range existing { - update, ok := commandsMap[c.ID] - if !ok { - continue + return c.withTx(ctx, func(tx *db.Tx) error { + existing, err := tx.ApplicationCommand.Query().Where(applicationcommand.IDIn(ids...)).All(ctx) + if err != nil && !IsNotFound(err) { + return err } - err := tx.ApplicationCommand.UpdateOneID(c.ID). - SetName(update.Name). - SetVersion(update.Version). - SetOptionsHash(update.Hash). - Exec(ctx) - if err != nil { - return rollback(tx, err) + for _, c := range existing { + update, ok := commandsMap[c.ID] + if !ok { + continue + } + + err := tx.ApplicationCommand.UpdateOneID(c.ID). + SetName(update.Name). + SetVersion(update.Version). + SetOptionsHash(update.Hash). + Exec(ctx) + if err != nil { + return err + } + + delete(commandsMap, c.ID) } - delete(commandsMap, c.ID) - } - - var inserts []*db.ApplicationCommandCreate - for _, cmd := range commandsMap { - inserts = append(inserts, - c.db.ApplicationCommand.Create(). - SetID(cmd.ID). - SetName(cmd.Name). - SetVersion(cmd.Version). - SetOptionsHash(cmd.Hash), - ) - } - - err = tx.ApplicationCommand.CreateBulk(inserts...).Exec(ctx) - if err != nil { - return rollback(tx, err) - } + var inserts []*db.ApplicationCommandCreate + for _, cmd := range commandsMap { + inserts = append(inserts, + c.db.ApplicationCommand.Create(). + SetID(cmd.ID). + SetName(cmd.Name). + SetVersion(cmd.Version). + SetOptionsHash(cmd.Hash), + ) + } - return tx.Commit() + return tx.ApplicationCommand.CreateBulk(inserts...).Exec(ctx) + }) } diff --git a/internal/database/ent/db/client.go b/internal/database/ent/db/client.go index 4b53ddbc..9d443fe2 100644 --- a/internal/database/ent/db/client.go +++ b/internal/database/ent/db/client.go @@ -29,6 +29,8 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/vehicle" "github.com/cufee/aftermath/internal/database/ent/db/vehicleaverage" "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" + + stdsql "database/sql" ) // Client is the client that holds all ent builders. @@ -2414,3 +2416,27 @@ type ( UserSubscription, Vehicle, VehicleAverage, VehicleSnapshot []ent.Interceptor } ) + +// ExecContext allows calling the underlying ExecContext method of the driver if it is supported by it. +// See, database/sql#DB.ExecContext for more information. +func (c *config) ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error) { + ex, ok := c.driver.(interface { + ExecContext(context.Context, string, ...any) (stdsql.Result, error) + }) + if !ok { + return nil, fmt.Errorf("Driver.ExecContext is not supported") + } + return ex.ExecContext(ctx, query, args...) +} + +// QueryContext allows calling the underlying QueryContext method of the driver if it is supported by it. +// See, database/sql#DB.QueryContext for more information. +func (c *config) QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error) { + q, ok := c.driver.(interface { + QueryContext(context.Context, string, ...any) (*stdsql.Rows, error) + }) + if !ok { + return nil, fmt.Errorf("Driver.QueryContext is not supported") + } + return q.QueryContext(ctx, query, args...) +} diff --git a/internal/database/ent/db/tx.go b/internal/database/ent/db/tx.go index a9856134..f0ab91bb 100644 --- a/internal/database/ent/db/tx.go +++ b/internal/database/ent/db/tx.go @@ -4,6 +4,8 @@ package db import ( "context" + stdsql "database/sql" + "fmt" "sync" "entgo.io/ent/dialect" @@ -247,3 +249,27 @@ func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error } var _ dialect.Driver = (*txDriver)(nil) + +// ExecContext allows calling the underlying ExecContext method of the transaction if it is supported by it. +// See, database/sql#Tx.ExecContext for more information. +func (tx *txDriver) ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error) { + ex, ok := tx.tx.(interface { + ExecContext(context.Context, string, ...any) (stdsql.Result, error) + }) + if !ok { + return nil, fmt.Errorf("Tx.ExecContext is not supported") + } + return ex.ExecContext(ctx, query, args...) +} + +// QueryContext allows calling the underlying QueryContext method of the transaction if it is supported by it. +// See, database/sql#Tx.QueryContext for more information. +func (tx *txDriver) QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error) { + q, ok := tx.tx.(interface { + QueryContext(context.Context, string, ...any) (*stdsql.Rows, error) + }) + if !ok { + return nil, fmt.Errorf("Tx.QueryContext is not supported") + } + return q.QueryContext(ctx, query, args...) +} diff --git a/internal/database/ent/generate.go b/internal/database/ent/generate.go index ce73767e..f92ce718 100644 --- a/internal/database/ent/generate.go +++ b/internal/database/ent/generate.go @@ -1,3 +1,3 @@ package ent -//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration --target ./db ./schema +//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration,sql/execquery --target ./db ./schema diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index cd344cf7..34613a5c 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -56,22 +56,23 @@ func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID st } var errors = make(map[string]error) - for _, data := range snapshots { - err := c.db.VehicleSnapshot.Create(). - SetType(data.Type). - SetFrame(data.Stats). - SetVehicleID(data.VehicleID). - SetReferenceID(data.ReferenceID). - SetBattles(int(data.Stats.Battles.Float())). - SetLastBattleTime(data.LastBattleTime.Unix()). - SetAccount(c.db.Account.GetX(ctx, accountID)). - Exec(ctx) - if err != nil { - errors[data.VehicleID] = err + return errors, c.withTx(ctx, func(tx *db.Tx) error { + for _, data := range snapshots { + err := c.db.VehicleSnapshot.Create(). + SetType(data.Type). + SetFrame(data.Stats). + SetVehicleID(data.VehicleID). + SetReferenceID(data.ReferenceID). + SetBattles(int(data.Stats.Battles.Float())). + SetLastBattleTime(data.LastBattleTime.Unix()). + SetAccount(c.db.Account.GetX(ctx, accountID)). + Exec(ctx) + if err != nil { + errors[data.VehicleID] = err + } } - } - - return errors, nil + return nil + }) } func (c *client) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) { @@ -129,23 +130,24 @@ func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...models } var errors = make(map[string]error) - for _, s := range snapshots { - err := c.db.AccountSnapshot.Create(). - SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here - SetCreatedAt(s.CreatedAt.Unix()). - SetLastBattleTime(s.LastBattleTime.Unix()). - SetRatingBattles(int(s.RatingBattles.Battles.Float())). - SetRatingFrame(s.RatingBattles). - SetReferenceID(s.ReferenceID). - SetRegularBattles(int(s.RegularBattles.Battles)). - SetRegularFrame(s.RegularBattles). - SetType(s.Type).Exec(ctx) - if err != nil { - errors[s.AccountID] = err + return errors, c.withTx(ctx, func(tx *db.Tx) error { + for _, s := range snapshots { + err := c.db.AccountSnapshot.Create(). + SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here + SetCreatedAt(s.CreatedAt.Unix()). + SetLastBattleTime(s.LastBattleTime.Unix()). + SetRatingBattles(int(s.RatingBattles.Battles.Float())). + SetRatingFrame(s.RatingBattles). + SetReferenceID(s.ReferenceID). + SetRegularBattles(int(s.RegularBattles.Battles)). + SetRegularFrame(s.RegularBattles). + SetType(s.Type).Exec(ctx) + if err != nil { + errors[s.AccountID] = err + } } - } - - return errors, nil + return nil + }) } func (c *client) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) { diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 0b63828a..19276620 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -82,59 +82,51 @@ func (c *client) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task return nil, nil } - tx, err := c.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return nil, err - } - - records, err := tx.CronTask.Query().Where(crontask.StatusEQ(models.TaskStatusScheduled), crontask.ScheduledAfterLT(time.Now().Unix())).Order(crontask.ByScheduledAfter(sql.OrderAsc())).Limit(limit).All(ctx) - if err != nil { - return nil, rollback(tx, err) - } - - var ids []string var tasks []models.Task - for _, r := range records { - t := toCronTask(r) - t.OnUpdated() + return tasks, c.withTx(ctx, func(tx *db.Tx) error { + records, err := tx.CronTask.Query().Where(crontask.StatusEQ(models.TaskStatusScheduled), crontask.ScheduledAfterLT(time.Now().Unix())).Order(crontask.ByScheduledAfter(sql.OrderAsc())).Limit(limit).All(ctx) + if err != nil { + return err + } - t.Status = models.TaskStatusInProgress - tasks = append(tasks, t) - ids = append(ids, r.ID) - } + var ids []string + for _, r := range records { + t := toCronTask(r) + t.OnUpdated() - err = tx.CronTask.Update().Where(crontask.IDIn(ids...)).SetStatus(models.TaskStatusInProgress).Exec(ctx) - if err != nil { - return nil, rollback(tx, err) - } + t.Status = models.TaskStatusInProgress + tasks = append(tasks, t) + ids = append(ids, r.ID) + } - return tasks, tx.Commit() + return tx.CronTask.Update().Where(crontask.IDIn(ids...)).SetStatus(models.TaskStatusInProgress).Exec(ctx) + }) } func (c *client) CreateTasks(ctx context.Context, tasks ...models.Task) error { if len(tasks) < 1 { return nil } - - var inserts []*db.CronTaskCreate - for _, t := range tasks { - t.OnCreated() - t.OnUpdated() - - inserts = append(inserts, - c.db.CronTask.Create(). - SetType(t.Type). - SetData(t.Data). - SetLogs(t.Logs). - SetStatus(t.Status). - SetTargets(t.Targets). - SetLastRun(t.LastRun.Unix()). - SetReferenceID(t.ReferenceID). - SetScheduledAfter(t.ScheduledAfter.Unix()), - ) - } - - return c.db.CronTask.CreateBulk(inserts...).Exec(ctx) + return c.withTx(ctx, func(tx *db.Tx) error { + var inserts []*db.CronTaskCreate + for _, t := range tasks { + t.OnCreated() + t.OnUpdated() + + inserts = append(inserts, + c.db.CronTask.Create(). + SetType(t.Type). + SetData(t.Data). + SetLogs(t.Logs). + SetStatus(t.Status). + SetTargets(t.Targets). + SetLastRun(t.LastRun.Unix()). + SetReferenceID(t.ReferenceID). + SetScheduledAfter(t.ScheduledAfter.Unix()), + ) + } + return c.db.CronTask.CreateBulk(inserts...).Exec(ctx) + }) } func (c *client) GetTasks(ctx context.Context, ids ...string) ([]models.Task, error) { @@ -165,30 +157,24 @@ func (c *client) UpdateTasks(ctx context.Context, tasks ...models.Task) error { return nil } - tx, cancel, err := c.txWithLock(ctx) - if err != nil { - return err - } - defer cancel() - - for _, t := range tasks { - t.OnUpdated() + return c.withTx(ctx, func(tx *db.Tx) error { + for _, t := range tasks { + t.OnUpdated() - err := tx.CronTask.UpdateOneID(t.ID). - SetData(t.Data). - SetLogs(t.Logs). - SetStatus(t.Status). - SetTargets(t.Targets). - SetLastRun(t.LastRun.Unix()). - SetScheduledAfter(t.ScheduledAfter.Unix()). - Exec(ctx) - - if err != nil { - return rollback(tx, err) + err := tx.CronTask.UpdateOneID(t.ID). + SetData(t.Data). + SetLogs(t.Logs). + SetStatus(t.Status). + SetTargets(t.Targets). + SetLastRun(t.LastRun.Unix()). + SetScheduledAfter(t.ScheduledAfter.Unix()). + Exec(ctx) + if err != nil { + return err + } } - } - - return tx.Commit() + return nil + }) } /* @@ -200,16 +186,9 @@ func (c *client) DeleteTasks(ctx context.Context, ids ...string) error { return nil } - tx, cancel, err := c.txWithLock(ctx) - if err != nil { + return c.withTx(ctx, func(tx *db.Tx) error { + _, err := tx.CronTask.Delete().Where(crontask.IDIn(ids...)).Exec(ctx) return err - } - defer cancel() - - _, err = tx.CronTask.Delete().Where(crontask.IDIn(ids...)).Exec(ctx) - if err != nil { - return rollback(tx, err) - } + }) - return tx.Commit() } diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index dde1c3b3..4abd2648 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -32,33 +32,35 @@ func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]models. } errors := make(map[string]error) - for _, r := range records { - v, ok := vehicles[r.ID] - if !ok { - continue - } + return errors, c.withTx(ctx, func(tx *db.Tx) error { + for _, r := range records { + v, ok := vehicles[r.ID] + if !ok { + continue + } - err := c.db.Vehicle.UpdateOneID(v.ID). - SetTier(v.Tier). - SetLocalizedNames(v.LocalizedNames). - Exec(ctx) - if err != nil { - errors[v.ID] = err - } + err := c.db.Vehicle.UpdateOneID(v.ID). + SetTier(v.Tier). + SetLocalizedNames(v.LocalizedNames). + Exec(ctx) + if err != nil { + errors[v.ID] = err + } - delete(vehicles, v.ID) - } + delete(vehicles, v.ID) + } - var writes []*db.VehicleCreate - for id, v := range vehicles { - writes = append(writes, c.db.Vehicle.Create(). - SetID(id). - SetTier(v.Tier). - SetLocalizedNames(v.LocalizedNames), - ) - } + var writes []*db.VehicleCreate + for id, v := range vehicles { + writes = append(writes, c.db.Vehicle.Create(). + SetID(id). + SetTier(v.Tier). + SetLocalizedNames(v.LocalizedNames), + ) + } - return errors, c.db.Vehicle.CreateBulk(writes...).Exec(ctx) + return c.db.Vehicle.CreateBulk(writes...).Exec(ctx) + }) } func (c *client) GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) { diff --git a/main.go b/main.go index 0b37f0bb..2a28f4dc 100644 --- a/main.go +++ b/main.go @@ -44,7 +44,7 @@ func main() { loadStaticAssets(static) liveCoreClient, cacheCoreClient := coreClientsFromEnv() - startSchedulerFromEnvAsync(cacheCoreClient.Database(), cacheCoreClient.Wargaming()) + startSchedulerFromEnvAsync(cacheCoreClient.Wargaming()) // Load some init options to registered admin accounts and etc logic.ApplyInitOptions(liveCoreClient.Database()) @@ -83,7 +83,7 @@ func main() { servePublic() } -func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Client) { +func startSchedulerFromEnvAsync(wgClient wargaming.Client) { if os.Getenv("SCHEDULER_ENABLED") != "true" { return } @@ -94,11 +94,11 @@ func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Cli } // Fetch client - client, err := fetch.NewMultiSourceClient(wgClient, bsClient, dbClient) + client, err := fetch.NewMultiSourceClient(wgClient, bsClient, newDatabaseClientFromEnv()) if err != nil { log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } - coreClient := core.NewClient(client, wgClient, dbClient) + coreClient := core.NewClient(client, wgClient, newDatabaseClientFromEnv()) // Queue taskQueueConcurrency := 10 @@ -117,16 +117,6 @@ func startSchedulerFromEnvAsync(dbClient database.Client, wgClient wargaming.Cli } func coreClientsFromEnv() (core.Client, core.Client) { - err := os.MkdirAll(os.Getenv("DATABASE_PATH"), os.ModePerm) - if err != nil { - log.Fatal().Msgf("os#MkdirAll failed %s", err) - } - - // Dependencies - dbClient, err := database.NewSQLiteClient(filepath.Join(os.Getenv("DATABASE_PATH"), os.Getenv("DATABASE_NAME"))) - if err != nil { - log.Fatal().Msgf("database#NewClient failed %s", err) - } bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) if err != nil { log.Fatal().Msgf("failed to init a blitzstars client %s", err) @@ -135,12 +125,12 @@ func coreClientsFromEnv() (core.Client, core.Client) { liveClient, cacheClient := wargamingClientsFromEnv() // Fetch client - fetchClient, err := fetch.NewMultiSourceClient(cacheClient, bsClient, dbClient) + fetchClient, err := fetch.NewMultiSourceClient(cacheClient, bsClient, newDatabaseClientFromEnv()) if err != nil { log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } - return core.NewClient(fetchClient, liveClient, dbClient), core.NewClient(fetchClient, cacheClient, dbClient) + return core.NewClient(fetchClient, liveClient, newDatabaseClientFromEnv()), core.NewClient(fetchClient, cacheClient, newDatabaseClientFromEnv()) } func loadStaticAssets(static fs.FS) { @@ -173,3 +163,17 @@ func wargamingClientsFromEnv() (wargaming.Client, wargaming.Client) { return liveClient, cacheClient } + +func newDatabaseClientFromEnv() database.Client { + err := os.MkdirAll(os.Getenv("DATABASE_PATH"), os.ModePerm) + if err != nil { + log.Fatal().Msgf("os#MkdirAll failed %s", err) + } + + client, err := database.NewSQLiteClient(filepath.Join(os.Getenv("DATABASE_PATH"), os.Getenv("DATABASE_NAME"))) + if err != nil { + log.Fatal().Msgf("database#NewClient failed %s", err) + } + + return client +} From 9f847a4f77d3773d84b2056c0776b52c7338fc4e Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 14:14:03 -0400 Subject: [PATCH 165/341] rewrite of queue/scheduler --- cmds/core/queue/queue.go | 220 ++++++++++++++++++ cmds/core/scheduler/cron.go | 43 ---- cmds/core/scheduler/glossary.go | 84 ------- cmds/core/scheduler/queue.go | 139 ----------- cmds/core/scheduler/scheduler.go | 58 +++++ cmds/core/scheduler/workers.go | 114 ++++++--- .../core/server/handlers/private/snapshots.go | 2 +- cmds/core/{scheduler => }/tasks/cleanup.go | 0 cmds/core/{scheduler => }/tasks/handler.go | 0 cmds/core/{scheduler => }/tasks/sessions.go | 0 cmds/core/{scheduler => }/tasks/split.go | 0 internal/database/client.go | 1 + internal/database/models/task.go | 6 +- internal/database/tasks.go | 36 +++ main.go | 101 ++++++-- 15 files changed, 471 insertions(+), 333 deletions(-) create mode 100644 cmds/core/queue/queue.go delete mode 100644 cmds/core/scheduler/cron.go delete mode 100644 cmds/core/scheduler/glossary.go delete mode 100644 cmds/core/scheduler/queue.go create mode 100644 cmds/core/scheduler/scheduler.go rename cmds/core/{scheduler => }/tasks/cleanup.go (100%) rename cmds/core/{scheduler => }/tasks/handler.go (100%) rename cmds/core/{scheduler => }/tasks/sessions.go (100%) rename cmds/core/{scheduler => }/tasks/split.go (100%) diff --git a/cmds/core/queue/queue.go b/cmds/core/queue/queue.go new file mode 100644 index 00000000..a07ca7a8 --- /dev/null +++ b/cmds/core/queue/queue.go @@ -0,0 +1,220 @@ +package queue + +import ( + "context" + "fmt" + "runtime/debug" + "sync" + "time" + + "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler" + "github.com/cufee/aftermath/cmds/core/tasks" + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +type queue struct { + queueLock *sync.Mutex + queued chan models.Task + handlers map[models.TaskType]tasks.TaskHandler + + workerLimit int + workerLimiter chan struct{} + workerTimeout time.Duration + + activeTasks map[string]struct{} + activeTasksMx *sync.Mutex + newCoreClient func() (core.Client, error) +} + +func New(workerLimit int, newCoreClient func() (core.Client, error)) *queue { + return &queue{ + newCoreClient: newCoreClient, + handlers: make(map[models.TaskType]tasks.TaskHandler), + + workerLimit: workerLimit, + workerLimiter: make(chan struct{}, workerLimit), + + activeTasksMx: &sync.Mutex{}, + activeTasks: make(map[string]struct{}), + + queueLock: &sync.Mutex{}, + queued: make(chan models.Task, workerLimit*2), + } +} + +func (q *queue) SetHandlers(handlers map[models.TaskType]tasks.TaskHandler) error { + for t, h := range handlers { + if _, ok := q.handlers[t]; ok { + return fmt.Errorf("handler for %s is already registered", t) + } + q.handlers[t] = h + } + return nil +} + +func (q *queue) Start(ctx context.Context) (func(), error) { + qctx, qCancel := context.WithCancel(ctx) + + coreClint, err := q.newCoreClient() + if err != nil { + qCancel() + return nil, err + } + + s := scheduler.New() + // on cron, pull and enqueue available tasks + s.Add("* * * * *", func() { + pctx, cancel := context.WithTimeout(qctx, time.Second*5) + defer cancel() + err := q.pullAndEnqueueTasks(pctx, coreClint.Database()) + if err != nil { + log.Err(err).Msg("failed to pull tasks") + } + }) + stopScheduler, err := s.Start(qctx) + if err != nil { + qCancel() + return nil, err + } + + go q.startWorkers(qctx, func(_ string) { + if len(q.queued) < q.workerLimit { + err := q.pullAndEnqueueTasks(qctx, coreClint.Database()) + if err != nil { + log.Err(err).Msg("failed to pull tasks") + } + } + }) + + return func() { + qCancel() + stopScheduler() + + q.activeTasksMx.Lock() + defer q.activeTasksMx.Unlock() + + var abandonedIDs []string + for id := range q.activeTasks { + abandonedIDs = append(abandonedIDs, id) + } + + cctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + err = coreClint.Database().AbandonTasks(cctx, abandonedIDs...) + if err != nil { + log.Err(err).Msg("failed to abandon tasks") + } + }, nil +} + +func (q *queue) Enqueue(task models.Task) { + q.queued <- task +} + +func (q *queue) pullAndEnqueueTasks(ctx context.Context, db database.Client) error { + if ok := q.queueLock.TryLock(); !ok { + return errors.New("queue is locked") + } + defer q.queueLock.Unlock() + + tasks, err := db.GetAndStartTasks(ctx, q.workerLimit) + if err != nil && !database.IsNotFound(err) { + return err + } + + go func(tasks []models.Task) { + for _, t := range tasks { + q.Enqueue(t) + } + }(tasks) + + return nil +} + +func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { + for { + select { + case task := <-q.queued: + go func() { + q.workerLimiter <- struct{}{} + q.activeTasksMx.Lock() + q.activeTasks[task.ID] = struct{}{} + q.activeTasksMx.Unlock() + defer func() { + <-q.workerLimiter + q.activeTasksMx.Lock() + delete(q.activeTasks, task.ID) + q.activeTasksMx.Unlock() + if onComplete != nil { + onComplete(task.ID) + } + }() + + defer func() { + if r := recover(); r != nil { + event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) + defer event.Msg("panic in queue worker") + + coreClient, err := q.newCoreClient() + if err != nil { + event.AnErr("core", err).Str("additional", "failed to create a core client") + return + } + task.Status = models.TaskStatusFailed + task.LogAttempt(models.TaskLog{ + Timestamp: time.Now(), + Comment: "task caused a panic in worker handler", + }) + err = coreClient.Database().UpdateTasks(context.Background(), task) + if err != nil { + event.AnErr("updateTasks", err).Str("additional", "failed to update a task") + } + } + }() + + wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) + defer cancel() + + handler, ok := q.handlers[task.Type] + if !ok { + log.Error().Str("type", string(task.Type)).Msg("task missing a handler") + return + } + + coreClient, err := q.newCoreClient() + if err != nil { + log.Err(err).Msg("failed to create a new core client for a task worker") + return + } + msg, err := handler.Process(coreClient, task) + task.Status = models.TaskStatusComplete + task.LogAttempt(models.TaskLog{ + Timestamp: time.Now(), + Comment: msg, + Error: err, + }) + + if err != nil { + if handler.ShouldRetry(&task) { + task.Status = models.TaskStatusScheduled + } else { + task.Status = models.TaskStatusFailed + } + } + + err = coreClient.Database().UpdateTasks(wctx, task) + if err != nil { + log.Err(err).Msg("failed to update a task") + } + }() + + case <-ctx.Done(): + return + } + } +} diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go deleted file mode 100644 index 36970b68..00000000 --- a/cmds/core/scheduler/cron.go +++ /dev/null @@ -1,43 +0,0 @@ -package scheduler - -import ( - "time" - - "github.com/go-co-op/gocron" - "github.com/rs/zerolog/log" -) - -func (queue *Queue) StartCronJobsAsync() { - defer log.Info().Msg("started cron scheduler") - - c := gocron.NewScheduler(time.UTC) - // Tasks - c.CronWithSeconds("*/15 * * * * *").Do(RunTasksWorker(queue)) - // some tasks might be stuck due to a panic or restart, restart them - c.Cron("0 * * * *").Do(RestartTasksWorker(queue.core)) - c.Cron("0 5 * * *").Do(CreateCleanupTaskWorker(queue.core)) // delete expired documents - - // Glossary - Do it around the same time WG releases game updates - c.Cron("0 10 * * *").Do(UpdateGlossaryWorker(queue.core)) - c.Cron("0 12 * * *").Do(UpdateGlossaryWorker(queue.core)) - // c.AddFunc("40 9 * * 0", updateAchievementsWorker) - - // Averages - Update averages once daily - c.Cron("0 0 * * *").Do(UpdateAveragesWorker(queue.core)) - - // Sessions - c.Cron("0 9 * * *").Do(CreateSessionTasksWorker(queue.core, "NA")) // NA - c.Cron("0 1 * * *").Do(CreateSessionTasksWorker(queue.core, "EU")) // EU - c.Cron("0 18 * * *").Do(CreateSessionTasksWorker(queue.core, "AS")) // Asia - - // Refresh WN8 - // "45 9 * * *" // NA - // "45 1 * * *" // EU - // "45 18 * * *" // Asia - - // Configurations - c.Cron("0 0 */7 * *").Do(RotateBackgroundPresetsWorker(queue.core)) - - // Start the Cron job scheduler - c.StartAsync() -} diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go deleted file mode 100644 index a5715a1d..00000000 --- a/cmds/core/scheduler/glossary.go +++ /dev/null @@ -1,84 +0,0 @@ -package scheduler - -import ( - "context" - "time" - - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/internal/database/models" - "github.com/rs/zerolog/log" - "golang.org/x/text/language" -) - -func UpdateAveragesWorker(client core.Client) func() { - // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails - return func() { - log.Info().Msg("updating tank averages cache") - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) - defer cancel() - - averages, err := client.Fetch().CurrentTankAverages(ctx) - if err != nil { - log.Err(err).Msg("failed to update averages cache") - return - } - - aErr, err := client.Database().UpsertVehicleAverages(ctx, averages) - if err != nil { - log.Err(err).Msg("failed to update averages cache") - return - } - if len(aErr) > 0 { - event := log.Error() - for id, err := range aErr { - event.Str(id, err.Error()) - } - event.Msg("failed to update some average cache") - return - } - - log.Info().Msg("averages cache updated") - } -} - -func UpdateGlossaryWorker(client core.Client) func() { - // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails - return func() { - log.Info().Msg("updating glossary cache") - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - defer cancel() - - glossary, err := client.Wargaming().CompleteVehicleGlossary(ctx, "eu", "en") - if err != nil { - log.Err(err).Msg("failed to get vehicle glossary") - return - } - - vehicles := make(map[string]models.Vehicle) - for id, data := range glossary { - vehicles[id] = models.Vehicle{ - ID: id, - Tier: data.Tier, - LocalizedNames: map[string]string{language.English.String(): data.Name}, - } - } - - vErr, err := client.Database().UpsertVehicles(ctx, vehicles) - if err != nil { - log.Err(err).Msg("failed to save vehicle glossary") - return - } - if len(vErr) > 0 { - event := log.Error() - for id, err := range vErr { - event.Str(id, err.Error()) - } - event.Msg("failed to save some vehicle glossaries") - return - } - - log.Info().Msg("glossary cache updated") - } -} diff --git a/cmds/core/scheduler/queue.go b/cmds/core/scheduler/queue.go deleted file mode 100644 index 5eca5e38..00000000 --- a/cmds/core/scheduler/queue.go +++ /dev/null @@ -1,139 +0,0 @@ -package scheduler - -import ( - "context" - "runtime/debug" - "sync" - "time" - - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/scheduler/tasks" - "github.com/cufee/aftermath/internal/database/models" - "github.com/rs/zerolog/log" -) - -type Queue struct { - limiter chan struct{} - concurrencyLimit int - lastTaskRun time.Time - - handlers map[models.TaskType]tasks.TaskHandler - core core.Client -} - -func (q *Queue) ConcurrencyLimit() int { - return q.concurrencyLimit -} - -func (q *Queue) ActiveWorkers() int { - return len(q.limiter) -} - -func (q *Queue) LastTaskRun() time.Time { - return q.lastTaskRun -} - -func NewQueue(client core.Client, handlers map[models.TaskType]tasks.TaskHandler, concurrencyLimit int) *Queue { - return &Queue{ - core: client, - handlers: handlers, - concurrencyLimit: concurrencyLimit, - limiter: make(chan struct{}, concurrencyLimit), - } -} - -func (q *Queue) Process(callback func(error), tasks ...models.Task) { - var err error - if callback != nil { - defer callback(err) - } - if len(tasks) == 0 { - log.Debug().Msg("no tasks to process") - return - } - - log.Debug().Msgf("processing %d tasks", len(tasks)) - - var wg sync.WaitGroup - q.lastTaskRun = time.Now() - processedTasks := make(chan models.Task, len(tasks)) - for _, task := range tasks { - wg.Add(1) - go func(t models.Task) { - defer func() { - if r := recover(); r != nil { - log.Error().Str("stack", string(debug.Stack())).Msg("panic in task handler") - t.Status = models.TaskStatusFailed - t.LogAttempt(models.TaskLog{ - Targets: t.Targets, - Timestamp: time.Now(), - Error: "task handler caused a panic", - }) - } else { - log.Debug().Msgf("finished processing task %s", t.ID) - } - processedTasks <- t - <-q.limiter - wg.Done() - }() - - q.limiter <- struct{}{} - log.Debug().Msgf("processing task %s", t.ID) - - handler, ok := q.handlers[t.Type] - if !ok { - t.Status = models.TaskStatusFailed - t.LogAttempt(models.TaskLog{ - Targets: t.Targets, - Timestamp: time.Now(), - Error: "missing task type handler", - }) - return - } - t.LastRun = time.Now() - - attempt := models.TaskLog{ - Targets: t.Targets, - Timestamp: time.Now(), - } - - message, err := handler.Process(q.core, t) - attempt.Comment = message - if err != nil { - attempt.Error = err.Error() - t.Status = models.TaskStatusFailed - } else { - t.Status = models.TaskStatusComplete - } - t.LogAttempt(attempt) - }(task) - } - - wg.Wait() - close(processedTasks) - - rescheduledCount := 0 - processedSlice := make([]models.Task, 0, len(processedTasks)) - for task := range processedTasks { - handler, ok := q.handlers[task.Type] - if !ok { - continue - } - - if task.Status == models.TaskStatusFailed && handler.ShouldRetry(&task) { - rescheduledCount++ - task.Status = models.TaskStatusScheduled - } - processedSlice = append(processedSlice, task) - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - - err = q.core.Database().UpdateTasks(ctx, processedSlice...) - if err != nil { - return - } - - log.Debug().Msgf("processed %d tasks, %d rescheduled", len(processedSlice), rescheduledCount) -} diff --git a/cmds/core/scheduler/scheduler.go b/cmds/core/scheduler/scheduler.go new file mode 100644 index 00000000..ae1ac46d --- /dev/null +++ b/cmds/core/scheduler/scheduler.go @@ -0,0 +1,58 @@ +package scheduler + +import ( + "context" + "time" + + "github.com/cufee/aftermath/cmds/core" + "github.com/go-co-op/gocron" +) + +type scheduler struct { + cron *gocron.Scheduler + jobs []*gocron.Job +} + +func New() *scheduler { + return &scheduler{cron: gocron.NewScheduler(time.UTC)} +} + +func (s *scheduler) Add(cron string, fn func()) (*gocron.Job, error) { + j, err := s.cron.Cron(cron).Do(fn) + if err != nil { + return nil, err + } + s.jobs = append(s.jobs, j) + return j, nil +} + +func (s *scheduler) Start(ctx context.Context) (func(), error) { + go s.cron.StartBlocking() + return s.cron.Stop, nil +} + +func RegisterDefaultTasks(s *scheduler, coreClient core.Client) { + s.Add("0 * * * *", RestartTasksWorker(coreClient)) + s.Add("0 5 * * *", CreateCleanupTaskWorker(coreClient)) // delete expired documents + + // Glossary - Do it around the same time WG releases game updates + s.Add("0 10 * * *", UpdateGlossaryWorker(coreClient)) + s.Add("0 12 * * *", UpdateGlossaryWorker(coreClient)) + // c.AddFunc("40 9 * * 0", updateAchievementsWorker) + + // Averages - Update averages once daily + s.Add("0 0 * * *", UpdateAveragesWorker(coreClient)) + + // Sessions + s.Add("0 9 * * *", CreateSessionTasksWorker(coreClient, "NA")) // NA + s.Add("0 1 * * *", CreateSessionTasksWorker(coreClient, "EU")) // EU + s.Add("0 18 * * *", CreateSessionTasksWorker(coreClient, "AS")) // Asia + + // Refresh WN8 + // "45 9 * * *" // NA + // "45 1 * * *" // EU + // "45 18 * * *" // Asia + + // Configurations + s.Add("0 0 */7 * *", RotateBackgroundPresetsWorker(coreClient)) +} diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go index 078ad939..bbd8b2eb 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmds/core/scheduler/workers.go @@ -5,7 +5,9 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/scheduler/tasks" + "github.com/cufee/aftermath/cmds/core/tasks" + "golang.org/x/text/language" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/rs/zerolog/log" @@ -45,43 +47,6 @@ func CreateSessionTasksWorker(client core.Client, realm string) func() { } } -func RunTasksWorker(queue *Queue) func() { - return func() { - activeWorkers := queue.ActiveWorkers() - if activeWorkers >= queue.concurrencyLimit { - log.Debug().Int("active", activeWorkers).Int("limit", queue.concurrencyLimit).Msg("no available workers to process tasks") - return - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - - batchSize := queue.concurrencyLimit - activeWorkers - tasks, err := queue.core.Database().GetAndStartTasks(ctx, batchSize) - if err != nil { - if database.IsNotFound(err) { - log.Debug().Msg("no scheduled tasks to process") - return - } - log.Err(err).Msg("failed to start scheduled tasks") - return - } - if len(tasks) < 1 { - log.Debug().Msg("no scheduled tasks to process") - return - } - - queue.Process(func(err error) { - if err != nil { - log.Err(err).Msg("failed to process tasks") - return - } - // try to start the next task batch right away - RunTasksWorker(queue) - }, tasks...) - } -} - func RestartTasksWorker(core core.Client) func() { return func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) @@ -118,3 +83,76 @@ func RestartTasksWorker(core core.Client) func() { log.Debug().Int("count", len(staleTasks)).Msg("rescheduled stale tasks") } } + +func UpdateAveragesWorker(client core.Client) func() { + // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails + return func() { + log.Info().Msg("updating tank averages cache") + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) + defer cancel() + + averages, err := client.Fetch().CurrentTankAverages(ctx) + if err != nil { + log.Err(err).Msg("failed to update averages cache") + return + } + + aErr, err := client.Database().UpsertVehicleAverages(ctx, averages) + if err != nil { + log.Err(err).Msg("failed to update averages cache") + return + } + if len(aErr) > 0 { + event := log.Error() + for id, err := range aErr { + event.Str(id, err.Error()) + } + event.Msg("failed to update some average cache") + return + } + + log.Info().Msg("averages cache updated") + } +} + +func UpdateGlossaryWorker(client core.Client) func() { + // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails + return func() { + log.Info().Msg("updating glossary cache") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + glossary, err := client.Wargaming().CompleteVehicleGlossary(ctx, "eu", "en") + if err != nil { + log.Err(err).Msg("failed to get vehicle glossary") + return + } + + vehicles := make(map[string]models.Vehicle) + for id, data := range glossary { + vehicles[id] = models.Vehicle{ + ID: id, + Tier: data.Tier, + LocalizedNames: map[string]string{language.English.String(): data.Name}, + } + } + + vErr, err := client.Database().UpsertVehicles(ctx, vehicles) + if err != nil { + log.Err(err).Msg("failed to save vehicle glossary") + return + } + if len(vErr) > 0 { + event := log.Error() + for id, err := range vErr { + event.Str(id, err.Error()) + } + event.Msg("failed to save some vehicle glossaries") + return + } + + log.Info().Msg("glossary cache updated") + } +} diff --git a/cmds/core/server/handlers/private/snapshots.go b/cmds/core/server/handlers/private/snapshots.go index a90a1ec8..c56b563d 100644 --- a/cmds/core/server/handlers/private/snapshots.go +++ b/cmds/core/server/handlers/private/snapshots.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/scheduler/tasks" + "github.com/cufee/aftermath/cmds/core/tasks" ) var validRealms = []string{"na", "eu", "as"} diff --git a/cmds/core/scheduler/tasks/cleanup.go b/cmds/core/tasks/cleanup.go similarity index 100% rename from cmds/core/scheduler/tasks/cleanup.go rename to cmds/core/tasks/cleanup.go diff --git a/cmds/core/scheduler/tasks/handler.go b/cmds/core/tasks/handler.go similarity index 100% rename from cmds/core/scheduler/tasks/handler.go rename to cmds/core/tasks/handler.go diff --git a/cmds/core/scheduler/tasks/sessions.go b/cmds/core/tasks/sessions.go similarity index 100% rename from cmds/core/scheduler/tasks/sessions.go rename to cmds/core/tasks/sessions.go diff --git a/cmds/core/scheduler/tasks/split.go b/cmds/core/tasks/split.go similarity index 100% rename from cmds/core/scheduler/tasks/split.go rename to cmds/core/tasks/split.go diff --git a/internal/database/client.go b/internal/database/client.go index d3884a84..ee81bfb2 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -59,6 +59,7 @@ type TasksClient interface { GetTasks(ctx context.Context, ids ...string) ([]models.Task, error) UpdateTasks(ctx context.Context, tasks ...models.Task) error DeleteTasks(ctx context.Context, ids ...string) error + AbandonTasks(ctx context.Context, ids ...string) error GetStaleTasks(ctx context.Context, limit int) ([]models.Task, error) GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) diff --git a/internal/database/models/task.go b/internal/database/models/task.go index 77adbaab..b19bee9c 100644 --- a/internal/database/models/task.go +++ b/internal/database/models/task.go @@ -89,15 +89,13 @@ func (t *Task) OnUpdated() { } type TaskLog struct { - Targets []string `json:"targets" bson:"targets"` Timestamp time.Time `json:"timestamp" bson:"timestamp"` Comment string `json:"result" bson:"result"` - Error string `json:"error" bson:"error"` + Error error `json:"error" bson:"error"` } -func NewAttemptLog(task Task, comment, err string) TaskLog { +func NewAttemptLog(task Task, comment string, err error) TaskLog { return TaskLog{ - Targets: task.Targets, Timestamp: time.Now(), Comment: comment, Error: err, diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 19276620..34aca9f9 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -103,6 +103,42 @@ func (c *client) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task }) } +/* +AbandonTasks retrieves tasks by ids and marks them as scheduled, adding a log noting it was abandoned +*/ +func (c *client) AbandonTasks(ctx context.Context, ids ...string) error { + if len(ids) < 1 { + return nil + } + + var tasks []models.Task + err := c.withTx(ctx, func(tx *db.Tx) error { + records, err := tx.CronTask.Query().Where(crontask.IDIn(ids...)).All(ctx) + if err != nil { + return err + } + + now := time.Now() + for _, r := range records { + t := toCronTask(r) + t.OnUpdated() + + t.Status = models.TaskStatusScheduled + t.LogAttempt(models.TaskLog{ + Comment: "task was abandoned", + Timestamp: now, + }) + tasks = append(tasks, t) + } + return nil + }) + if err != nil { + return err + } + + return c.UpdateTasks(ctx, tasks...) +} + func (c *client) CreateTasks(ctx context.Context, tasks ...models.Task) error { if len(tasks) < 1 { return nil diff --git a/main.go b/main.go index 2a28f4dc..eb40181f 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,27 @@ package main import ( + "context" "embed" + "fmt" "io/fs" + "os/signal" "path/filepath" - "runtime/debug" + "strconv" + "syscall" "os" - "strconv" "time" "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/queue" "github.com/cufee/aftermath/cmds/core/scheduler" - "github.com/cufee/aftermath/cmds/core/scheduler/tasks" + "github.com/cufee/aftermath/cmds/core/server" "github.com/cufee/aftermath/cmds/core/server/handlers/private" "github.com/cufee/aftermath/cmds/discord" "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/localization" @@ -37,6 +42,9 @@ import ( var static embed.FS func main() { + globalCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + // Logger level, _ := zerolog.ParseLevel(os.Getenv("LOG_LEVEL")) zerolog.SetGlobalLevel(level) @@ -44,7 +52,17 @@ func main() { loadStaticAssets(static) liveCoreClient, cacheCoreClient := coreClientsFromEnv() - startSchedulerFromEnvAsync(cacheCoreClient.Wargaming()) + stopQueue, err := startQueueFromEnv(globalCtx, cacheCoreClient.Wargaming()) + if err != nil { + log.Fatal().Err(err).Msg("startQueueFromEnv failed") + } + defer stopQueue() + + stopScheduler, err := startSchedulerFromEnv(globalCtx, cacheCoreClient) + if err != nil { + log.Fatal().Err(err).Msg("startSchedulerFromEnv failed") + } + defer stopScheduler() // Load some init options to registered admin accounts and etc logic.ApplyInitOptions(liveCoreClient.Database()) @@ -80,40 +98,61 @@ func main() { Func: discordHandler, }) log.Info().Str("port", port).Msg("starting a public server") - servePublic() + go servePublic() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + + sig := <-c + cancel() + log.Info().Msgf("received %s, exiting", sig.String()) } -func startSchedulerFromEnvAsync(wgClient wargaming.Client) { +func startSchedulerFromEnv(ctx context.Context, coreClient core.Client) (func(), error) { if os.Getenv("SCHEDULER_ENABLED") != "true" { - return + return func() {}, nil } + s := scheduler.New() + scheduler.RegisterDefaultTasks(s, coreClient) + return s.Start(ctx) +} +func startQueueFromEnv(ctx context.Context, wgClient wargaming.Client) (func(), error) { bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) if err != nil { log.Fatal().Msgf("failed to init a blitzstars client %s", err) } // Fetch client - client, err := fetch.NewMultiSourceClient(wgClient, bsClient, newDatabaseClientFromEnv()) + fetchDBClient, err := newDatabaseClientFromEnv() + if err != nil { + log.Fatal().Err(err).Msg("failed to create a database client") + } + client, err := fetch.NewMultiSourceClient(wgClient, bsClient, fetchDBClient) if err != nil { log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } - coreClient := core.NewClient(client, wgClient, newDatabaseClientFromEnv()) - // Queue - taskQueueConcurrency := 10 + // Queue - pulls tasks from database and runs the logic + queueWorkerLimit := 10 if v, err := strconv.Atoi(os.Getenv("SCHEDULER_CONCURRENT_WORKERS")); err == nil { - taskQueueConcurrency = v + queueWorkerLimit = v } - queue := scheduler.NewQueue(coreClient, tasks.DefaultHandlers(), taskQueueConcurrency) - - defer func() { - if r := recover(); r != nil { - log.Error().Str("stack", string(debug.Stack())).Interface("error", r).Stack().Msg("scheduler panic") + q := queue.New(queueWorkerLimit, func() (core.Client, error) { + // make a new database client and re-use the core client deps from before + dbClient, err := newDatabaseClientFromEnv() + if err != nil { + return nil, err } + return core.NewClient(client, wgClient, dbClient), nil + }) + + go func() { + time.Sleep(time.Second * 5) + q.Enqueue(models.Task{}) }() - queue.StartCronJobsAsync() + return q.Start(ctx) } func coreClientsFromEnv() (core.Client, core.Client) { @@ -125,12 +164,25 @@ func coreClientsFromEnv() (core.Client, core.Client) { liveClient, cacheClient := wargamingClientsFromEnv() // Fetch client - fetchClient, err := fetch.NewMultiSourceClient(cacheClient, bsClient, newDatabaseClientFromEnv()) + liveDBClient, err := newDatabaseClientFromEnv() + if err != nil { + log.Fatal().Err(err).Msg("newDatabaseClientFromEnv failed") + } + liveFetchClient, err := fetch.NewMultiSourceClient(liveClient, bsClient, liveDBClient) if err != nil { log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } - return core.NewClient(fetchClient, liveClient, newDatabaseClientFromEnv()), core.NewClient(fetchClient, cacheClient, newDatabaseClientFromEnv()) + cacheDBClient, err := newDatabaseClientFromEnv() + if err != nil { + log.Fatal().Err(err).Msg("newDatabaseClientFromEnv failed") + } + cacheFetchClient, err := fetch.NewMultiSourceClient(cacheClient, bsClient, cacheDBClient) + if err != nil { + log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) + } + + return core.NewClient(liveFetchClient, liveClient, liveDBClient), core.NewClient(cacheFetchClient, cacheClient, cacheDBClient) } func loadStaticAssets(static fs.FS) { @@ -164,16 +216,17 @@ func wargamingClientsFromEnv() (wargaming.Client, wargaming.Client) { return liveClient, cacheClient } -func newDatabaseClientFromEnv() database.Client { +func newDatabaseClientFromEnv() (database.Client, error) { err := os.MkdirAll(os.Getenv("DATABASE_PATH"), os.ModePerm) if err != nil { - log.Fatal().Msgf("os#MkdirAll failed %s", err) + return nil, fmt.Errorf("os#MkdirAll failed %w", err) } client, err := database.NewSQLiteClient(filepath.Join(os.Getenv("DATABASE_PATH"), os.Getenv("DATABASE_NAME"))) if err != nil { - log.Fatal().Msgf("database#NewClient failed %s", err) + + return nil, fmt.Errorf("database#NewClient failed %w", err) } - return client + return client, nil } From d2ddd2164100f349ba33ea8d07283305822bcc4b Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 14:27:46 -0400 Subject: [PATCH 166/341] changes error type, added timeout --- cmds/core/queue/queue.go | 25 ++++++++++++++++++------- internal/database/models/task.go | 4 ++-- main.go | 7 +------ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/cmds/core/queue/queue.go b/cmds/core/queue/queue.go index a07ca7a8..33f5b6ad 100644 --- a/cmds/core/queue/queue.go +++ b/cmds/core/queue/queue.go @@ -36,6 +36,7 @@ func New(workerLimit int, newCoreClient func() (core.Client, error)) *queue { handlers: make(map[models.TaskType]tasks.TaskHandler), workerLimit: workerLimit, + workerTimeout: time.Second * 15, workerLimiter: make(chan struct{}, workerLimit), activeTasksMx: &sync.Mutex{}, @@ -180,23 +181,33 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) defer cancel() - handler, ok := q.handlers[task.Type] - if !ok { - log.Error().Str("type", string(task.Type)).Msg("task missing a handler") - return - } - coreClient, err := q.newCoreClient() if err != nil { log.Err(err).Msg("failed to create a new core client for a task worker") return } + + handler, ok := q.handlers[task.Type] + if !ok { + task.Status = models.TaskStatusFailed + task.LogAttempt(models.TaskLog{ + Error: "task missing a handler", + Comment: "task missing a handler", + Timestamp: time.Now(), + }) + err := coreClient.Database().UpdateTasks(wctx, task) + if err != nil { + log.Err(err).Msg("failed to update a task") + } + return + } + msg, err := handler.Process(coreClient, task) task.Status = models.TaskStatusComplete task.LogAttempt(models.TaskLog{ + Error: err.Error(), Timestamp: time.Now(), Comment: msg, - Error: err, }) if err != nil { diff --git a/internal/database/models/task.go b/internal/database/models/task.go index b19bee9c..d2d569bb 100644 --- a/internal/database/models/task.go +++ b/internal/database/models/task.go @@ -91,13 +91,13 @@ func (t *Task) OnUpdated() { type TaskLog struct { Timestamp time.Time `json:"timestamp" bson:"timestamp"` Comment string `json:"result" bson:"result"` - Error error `json:"error" bson:"error"` + Error string `json:"error" bson:"error"` } func NewAttemptLog(task Task, comment string, err error) TaskLog { return TaskLog{ Timestamp: time.Now(), Comment: comment, - Error: err, + Error: err.Error(), } } diff --git a/main.go b/main.go index eb40181f..7f1d65b4 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,6 @@ import ( "github.com/cufee/aftermath/cmds/core/server/handlers/private" "github.com/cufee/aftermath/cmds/discord" "github.com/cufee/aftermath/internal/database" - "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" "github.com/cufee/aftermath/internal/localization" @@ -146,11 +145,7 @@ func startQueueFromEnv(ctx context.Context, wgClient wargaming.Client) (func(), } return core.NewClient(client, wgClient, dbClient), nil }) - - go func() { - time.Sleep(time.Second * 5) - q.Enqueue(models.Task{}) - }() + // q.SetHandlers(tasks.DefaultHandlers()) return q.Start(ctx) } From 830d90900c4238b98d6dbefc1b3609b05d92eb15 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 14:31:23 -0400 Subject: [PATCH 167/341] added debug logs --- cmds/core/queue/queue.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmds/core/queue/queue.go b/cmds/core/queue/queue.go index 33f5b6ad..ab697a23 100644 --- a/cmds/core/queue/queue.go +++ b/cmds/core/queue/queue.go @@ -123,10 +123,13 @@ func (q *queue) pullAndEnqueueTasks(ctx context.Context, db database.Client) err } defer q.queueLock.Unlock() + log.Debug().Msg("pulling available tasks into queue") + tasks, err := db.GetAndStartTasks(ctx, q.workerLimit) if err != nil && !database.IsNotFound(err) { return err } + log.Debug().Msgf("pulled %d tasks into queue", len(tasks)) go func(tasks []models.Task) { for _, t := range tasks { @@ -156,6 +159,9 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { } }() + log.Debug().Str("taskId", task.ID).Msg("worker started processing a task") + defer log.Debug().Str("taskId", task.ID).Msg("worker finished processing a task") + defer func() { if r := recover(); r != nil { event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) From f8cf13bb723b29631609a83c9faa12f113647b13 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 14:32:12 -0400 Subject: [PATCH 168/341] added task handlers --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 7f1d65b4..073f7caa 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "github.com/cufee/aftermath/cmds/core" "github.com/cufee/aftermath/cmds/core/queue" "github.com/cufee/aftermath/cmds/core/scheduler" + "github.com/cufee/aftermath/cmds/core/tasks" "github.com/cufee/aftermath/cmds/core/server" "github.com/cufee/aftermath/cmds/core/server/handlers/private" @@ -145,7 +146,7 @@ func startQueueFromEnv(ctx context.Context, wgClient wargaming.Client) (func(), } return core.NewClient(client, wgClient, dbClient), nil }) - // q.SetHandlers(tasks.DefaultHandlers()) + q.SetHandlers(tasks.DefaultHandlers()) return q.Start(ctx) } From 0b0fe63a55e92871d1e744e9769e57d8ecd7757a Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 15:00:42 -0400 Subject: [PATCH 169/341] added application level locks --- internal/database/client.go | 21 +++++++++++++++------ internal/database/users.go | 12 ++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/internal/database/client.go b/internal/database/client.go index ee81bfb2..458a86f5 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "runtime/debug" + "sync" "time" "github.com/cufee/aftermath/internal/database/ent/db" @@ -88,30 +89,37 @@ type Client interface { } type client struct { - db *db.Client + db *db.Client + writeLock *sync.Mutex } func (c *client) withTx(ctx context.Context, fn func(tx *db.Tx) error) error { + c.writeLock.Lock() + defer c.writeLock.Unlock() + + var err error tx, err := c.db.Tx(ctx) if err != nil { return err } + defer func() { if v := recover(); v != nil { tx.Rollback() - panic(v) + err = fmt.Errorf("%v", v) } }() - if err := fn(tx); err != nil { + + if err = fn(tx); err != nil { if rerr := tx.Rollback(); rerr != nil { err = fmt.Errorf("%w: rolling back transaction: %v", err, rerr) } return err } - if err := tx.Commit(); err != nil { + if err = tx.Commit(); err != nil { return fmt.Errorf("committing transaction: %w", err) } - return nil + return err } func (c *client) Disconnect() error { @@ -138,6 +146,7 @@ func NewSQLiteClient(filePath string) (*client, error) { } return &client{ - db: c, + writeLock: &sync.Mutex{}, + db: c, }, nil } diff --git a/internal/database/users.go b/internal/database/users.go index e044b34e..19c25e07 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -95,6 +95,9 @@ func (c *client) GetOrCreateUserByID(ctx context.Context, id string, opts ...use } if IsNotFound(err) { + c.writeLock.Lock() + defer c.writeLock.Unlock() + record, err := c.db.User.Create().SetID(id).SetPermissions(permissions.User.String()).Save(ctx) if err != nil { return models.User{}, err @@ -135,6 +138,9 @@ func (c *client) GetUserByID(ctx context.Context, id string, opts ...userGetOpti } func (c *client) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) { + c.writeLock.Lock() + defer c.writeLock.Unlock() + record, err := c.db.User.UpdateOneID(userID).SetPermissions(perms.String()).Save(ctx) if err != nil && !IsNotFound(err) { return models.User{}, err @@ -151,6 +157,9 @@ func (c *client) UpsertUserWithPermissions(ctx context.Context, userID string, p } func (c *client) CreateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { + c.writeLock.Lock() + defer c.writeLock.Unlock() + record, err := c.db.UserConnection.Create(). SetUser(c.db.User.GetX(ctx, connection.UserID)). SetPermissions(connection.Permissions.String()). @@ -165,6 +174,9 @@ func (c *client) CreateConnection(ctx context.Context, connection models.UserCon } func (c *client) UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { + c.writeLock.Lock() + defer c.writeLock.Unlock() + record, err := c.db.UserConnection.UpdateOneID(connection.ID). SetMetadata(connection.Metadata). SetPermissions(connection.Permissions.String()). From 7580d592a2f6378b168b0108d4d44c99a3e7b811 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 15:08:18 -0400 Subject: [PATCH 170/341] moved back to a single client --- main.go | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/main.go b/main.go index 073f7caa..9fa06838 100644 --- a/main.go +++ b/main.go @@ -50,9 +50,13 @@ func main() { zerolog.SetGlobalLevel(level) loadStaticAssets(static) + db, err := newDatabaseClientFromEnv() + if err != nil { + log.Fatal().Err(err).Msg("newDatabaseClientFromEnv failed") + } - liveCoreClient, cacheCoreClient := coreClientsFromEnv() - stopQueue, err := startQueueFromEnv(globalCtx, cacheCoreClient.Wargaming()) + liveCoreClient, cacheCoreClient := coreClientsFromEnv(db) + stopQueue, err := startQueueFromEnv(globalCtx, db, cacheCoreClient.Wargaming()) if err != nil { log.Fatal().Err(err).Msg("startQueueFromEnv failed") } @@ -117,18 +121,14 @@ func startSchedulerFromEnv(ctx context.Context, coreClient core.Client) (func(), return s.Start(ctx) } -func startQueueFromEnv(ctx context.Context, wgClient wargaming.Client) (func(), error) { +func startQueueFromEnv(ctx context.Context, db database.Client, wgClient wargaming.Client) (func(), error) { bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) if err != nil { log.Fatal().Msgf("failed to init a blitzstars client %s", err) } // Fetch client - fetchDBClient, err := newDatabaseClientFromEnv() - if err != nil { - log.Fatal().Err(err).Msg("failed to create a database client") - } - client, err := fetch.NewMultiSourceClient(wgClient, bsClient, fetchDBClient) + client, err := fetch.NewMultiSourceClient(wgClient, bsClient, db) if err != nil { log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } @@ -138,20 +138,17 @@ func startQueueFromEnv(ctx context.Context, wgClient wargaming.Client) (func(), if v, err := strconv.Atoi(os.Getenv("SCHEDULER_CONCURRENT_WORKERS")); err == nil { queueWorkerLimit = v } + + coreClient := core.NewClient(client, wgClient, db) q := queue.New(queueWorkerLimit, func() (core.Client, error) { - // make a new database client and re-use the core client deps from before - dbClient, err := newDatabaseClientFromEnv() - if err != nil { - return nil, err - } - return core.NewClient(client, wgClient, dbClient), nil + return coreClient, nil }) - q.SetHandlers(tasks.DefaultHandlers()) + q.SetHandlers(tasks.DefaultHandlers()) return q.Start(ctx) } -func coreClientsFromEnv() (core.Client, core.Client) { +func coreClientsFromEnv(db database.Client) (core.Client, core.Client) { bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) if err != nil { log.Fatal().Msgf("failed to init a blitzstars client %s", err) @@ -160,25 +157,17 @@ func coreClientsFromEnv() (core.Client, core.Client) { liveClient, cacheClient := wargamingClientsFromEnv() // Fetch client - liveDBClient, err := newDatabaseClientFromEnv() - if err != nil { - log.Fatal().Err(err).Msg("newDatabaseClientFromEnv failed") - } - liveFetchClient, err := fetch.NewMultiSourceClient(liveClient, bsClient, liveDBClient) + liveFetchClient, err := fetch.NewMultiSourceClient(liveClient, bsClient, db) if err != nil { log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } - cacheDBClient, err := newDatabaseClientFromEnv() - if err != nil { - log.Fatal().Err(err).Msg("newDatabaseClientFromEnv failed") - } - cacheFetchClient, err := fetch.NewMultiSourceClient(cacheClient, bsClient, cacheDBClient) + cacheFetchClient, err := fetch.NewMultiSourceClient(cacheClient, bsClient, db) if err != nil { log.Fatal().Msgf("fetch#NewMultiSourceClient failed %s", err) } - return core.NewClient(liveFetchClient, liveClient, liveDBClient), core.NewClient(cacheFetchClient, cacheClient, cacheDBClient) + return core.NewClient(liveFetchClient, liveClient, db), core.NewClient(cacheFetchClient, cacheClient, db) } func loadStaticAssets(static fs.FS) { From 11256bbf1cab299c41354e1ea1db42d2722ea17f Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 15:28:19 -0400 Subject: [PATCH 171/341] removed recover --- cmds/core/queue/queue.go | 43 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/cmds/core/queue/queue.go b/cmds/core/queue/queue.go index ab697a23..e5a10523 100644 --- a/cmds/core/queue/queue.go +++ b/cmds/core/queue/queue.go @@ -3,7 +3,6 @@ package queue import ( "context" "fmt" - "runtime/debug" "sync" "time" @@ -162,27 +161,27 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { log.Debug().Str("taskId", task.ID).Msg("worker started processing a task") defer log.Debug().Str("taskId", task.ID).Msg("worker finished processing a task") - defer func() { - if r := recover(); r != nil { - event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) - defer event.Msg("panic in queue worker") - - coreClient, err := q.newCoreClient() - if err != nil { - event.AnErr("core", err).Str("additional", "failed to create a core client") - return - } - task.Status = models.TaskStatusFailed - task.LogAttempt(models.TaskLog{ - Timestamp: time.Now(), - Comment: "task caused a panic in worker handler", - }) - err = coreClient.Database().UpdateTasks(context.Background(), task) - if err != nil { - event.AnErr("updateTasks", err).Str("additional", "failed to update a task") - } - } - }() + // defer func() { + // if r := recover(); r != nil { + // event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) + // defer event.Msg("panic in queue worker") + + // coreClient, err := q.newCoreClient() + // if err != nil { + // event.AnErr("core", err).Str("additional", "failed to create a core client") + // return + // } + // task.Status = models.TaskStatusFailed + // task.LogAttempt(models.TaskLog{ + // Timestamp: time.Now(), + // Comment: "task caused a panic in worker handler", + // }) + // err = coreClient.Database().UpdateTasks(context.Background(), task) + // if err != nil { + // event.AnErr("updateTasks", err).Str("additional", "failed to update a task") + // } + // } + // }() wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) defer cancel() From 548d50cb657d1e64e162d11ee2bf75b563680989 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 15:33:34 -0400 Subject: [PATCH 172/341] fixed a panic --- cmds/core/queue/queue.go | 51 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/cmds/core/queue/queue.go b/cmds/core/queue/queue.go index e5a10523..4982b19f 100644 --- a/cmds/core/queue/queue.go +++ b/cmds/core/queue/queue.go @@ -3,6 +3,7 @@ package queue import ( "context" "fmt" + "runtime/debug" "sync" "time" @@ -161,27 +162,27 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { log.Debug().Str("taskId", task.ID).Msg("worker started processing a task") defer log.Debug().Str("taskId", task.ID).Msg("worker finished processing a task") - // defer func() { - // if r := recover(); r != nil { - // event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) - // defer event.Msg("panic in queue worker") - - // coreClient, err := q.newCoreClient() - // if err != nil { - // event.AnErr("core", err).Str("additional", "failed to create a core client") - // return - // } - // task.Status = models.TaskStatusFailed - // task.LogAttempt(models.TaskLog{ - // Timestamp: time.Now(), - // Comment: "task caused a panic in worker handler", - // }) - // err = coreClient.Database().UpdateTasks(context.Background(), task) - // if err != nil { - // event.AnErr("updateTasks", err).Str("additional", "failed to update a task") - // } - // } - // }() + defer func() { + if r := recover(); r != nil { + event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) + defer event.Msg("panic in queue worker") + + coreClient, err := q.newCoreClient() + if err != nil { + event.AnErr("core", err).Str("additional", "failed to create a core client") + return + } + task.Status = models.TaskStatusFailed + task.LogAttempt(models.TaskLog{ + Timestamp: time.Now(), + Comment: "task caused a panic in worker handler", + }) + err = coreClient.Database().UpdateTasks(context.Background(), task) + if err != nil { + event.AnErr("updateTasks", err).Str("additional", "failed to update a task") + } + } + }() wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) defer cancel() @@ -209,19 +210,19 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { msg, err := handler.Process(coreClient, task) task.Status = models.TaskStatusComplete - task.LogAttempt(models.TaskLog{ - Error: err.Error(), + l := models.TaskLog{ Timestamp: time.Now(), Comment: msg, - }) - + } if err != nil { + l.Error = err.Error() if handler.ShouldRetry(&task) { task.Status = models.TaskStatusScheduled } else { task.Status = models.TaskStatusFailed } } + task.LogAttempt(l) err = coreClient.Database().UpdateTasks(wctx, task) if err != nil { From 6c0cefe2dbbcbe995933dcdd10451896a23164a4 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 15:40:09 -0400 Subject: [PATCH 173/341] minor tweaks, sorting tasks --- cmds/core/queue/errors.go | 5 +++++ cmds/core/queue/queue.go | 8 ++++---- internal/database/tasks.go | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 cmds/core/queue/errors.go diff --git a/cmds/core/queue/errors.go b/cmds/core/queue/errors.go new file mode 100644 index 00000000..49a1e479 --- /dev/null +++ b/cmds/core/queue/errors.go @@ -0,0 +1,5 @@ +package queue + +import "github.com/pkg/errors" + +var ErrQueueLocked = errors.New("queue is locked") diff --git a/cmds/core/queue/queue.go b/cmds/core/queue/queue.go index 4982b19f..f96657a3 100644 --- a/cmds/core/queue/queue.go +++ b/cmds/core/queue/queue.go @@ -2,6 +2,7 @@ package queue import ( "context" + "errors" "fmt" "runtime/debug" "sync" @@ -12,7 +13,6 @@ import ( "github.com/cufee/aftermath/cmds/core/tasks" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" - "github.com/pkg/errors" "github.com/rs/zerolog/log" ) @@ -72,7 +72,7 @@ func (q *queue) Start(ctx context.Context) (func(), error) { pctx, cancel := context.WithTimeout(qctx, time.Second*5) defer cancel() err := q.pullAndEnqueueTasks(pctx, coreClint.Database()) - if err != nil { + if err != nil && !errors.Is(ErrQueueLocked, err) { log.Err(err).Msg("failed to pull tasks") } }) @@ -85,7 +85,7 @@ func (q *queue) Start(ctx context.Context) (func(), error) { go q.startWorkers(qctx, func(_ string) { if len(q.queued) < q.workerLimit { err := q.pullAndEnqueueTasks(qctx, coreClint.Database()) - if err != nil { + if err != nil && !errors.Is(ErrQueueLocked, err) { log.Err(err).Msg("failed to pull tasks") } } @@ -119,7 +119,7 @@ func (q *queue) Enqueue(task models.Task) { func (q *queue) pullAndEnqueueTasks(ctx context.Context, db database.Client) error { if ok := q.queueLock.TryLock(); !ok { - return errors.New("queue is locked") + return ErrQueueLocked } defer q.queueLock.Unlock() diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 34aca9f9..e6ca2bf2 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -61,7 +61,7 @@ func (c *client) GetRecentTasks(ctx context.Context, createdAfter time.Time, sta where = append(where, crontask.StatusIn(status...)) } - records, err := c.db.CronTask.Query().Where(where...).All(ctx) + records, err := c.db.CronTask.Query().Where(where...).Order(crontask.ByLastRun(sql.OrderDesc())).All(ctx) if err != nil { return nil, err } From de5abeb1513cc051627b25413375011141a6986b Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 15:46:19 -0400 Subject: [PATCH 174/341] upgrade, fixed a test --- go.mod | 21 ++++------ go.sum | 60 +++++++++------------------ internal/database/ent/migrate/main.go | 1 - render_test.go | 6 ++- 4 files changed, 34 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index bf5fd037..c2c64c4a 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/cufee/aftermath go 1.22.3 require ( - ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 + ariga.io/atlas v0.24.0 entgo.io/ent v0.13.1 github.com/bwmarrin/discordgo v0.28.1 - github.com/cufee/am-wg-proxy-next/v2 v2.1.3 + github.com/cufee/am-wg-proxy-next/v2 v2.1.5 github.com/disintegration/imaging v1.6.2 github.com/fogleman/gg v1.3.0 github.com/go-co-op/gocron v1.37.0 @@ -18,7 +18,6 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.9.0 - github.com/tursodatabase/libsql-client-go v0.0.0-20240416075003-747366ff79c4 go.dedis.ch/protobuf v1.0.11 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/image v0.17.0 @@ -28,25 +27,23 @@ require ( ) require ( - github.com/agext/levenshtein v1.2.1 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-openapi/inflect v0.19.0 // indirect + github.com/go-openapi/inflect v0.21.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/hashicorp/hcl/v2 v2.13.0 // indirect - github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 // indirect + github.com/hashicorp/hcl/v2 v2.21.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/zclconf/go-cty v1.8.0 // indirect + github.com/zclconf/go-cty v1.14.4 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/sys v0.21.0 // indirect - nhooyr.io/websocket v1.8.10 // indirect + golang.org/x/tools v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index 24ee26f5..b4bd05ca 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,19 @@ -ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 h1:GwdJbXydHCYPedeeLt4x/lrlIISQ4JTH1mRWuE5ZZ14= -ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43/go.mod h1:uj3pm+hUTVN/X5yfdBexHlZv+1Xu5u5ZbZx7+CDavNU= +ariga.io/atlas v0.24.0 h1:ssK25BIfVbVIYt+STIfqgrlEpfJrLtKtdWU2/U23YP4= +ariga.io/atlas v0.24.0/go.mod h1:uSfJty48trd+YIWkRp7OENP5Wjgy6KzVW6JHv6tko2o= entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE= entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= -github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cufee/am-wg-proxy-next/v2 v2.1.3 h1:qTzzgEapbI/z81R3nXoiiITfBZeHQ1cnebzSNgaoZlQ= -github.com/cufee/am-wg-proxy-next/v2 v2.1.3/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= +github.com/cufee/am-wg-proxy-next/v2 v2.1.5 h1:F16XkZ8l+vZcRtgSYfxIphldsGBAc4+kWcIly3F3x+U= +github.com/cufee/am-wg-proxy-next/v2 v2.1.5/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= 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= @@ -25,16 +23,13 @@ github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= -github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= -github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk= +github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 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/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -43,8 +38,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= -github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= +github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= +github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -55,10 +50,6 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM= -github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM= github.com/lucsky/cuid v1.2.1 h1:MtJrL2OFhvYufUIn48d35QGXyeTC8tn0upumW9WwTHg= github.com/lucsky/cuid v1.2.1/go.mod h1:QaaJqckboimOmhRSJXSx/+IT+VTfxfPGSo/6mfgUfmE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -69,8 +60,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.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -86,8 +77,6 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -97,12 +86,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tursodatabase/libsql-client-go v0.0.0-20240416075003-747366ff79c4 h1:wNN8t3qiLLzFiETD4jL086WemAgQLfARClUx2Jfk78w= -github.com/tursodatabase/libsql-client-go v0.0.0-20240416075003-747366ff79c4/go.mod h1:2Fu26tjM011BLeR5+jwTfs6DX/fNMEWV/3CBZvggrA4= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= -github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= +github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= @@ -116,7 +103,6 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= @@ -127,13 +113,10 @@ golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= @@ -142,13 +125,12 @@ golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -157,5 +139,3 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 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= -nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= -nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/internal/database/ent/migrate/main.go b/internal/database/ent/migrate/main.go index de8b8a3d..5932cafc 100644 --- a/internal/database/ent/migrate/main.go +++ b/internal/database/ent/migrate/main.go @@ -11,7 +11,6 @@ import ( "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql/schema" "github.com/cufee/aftermath/internal/database/ent/db/migrate" - _ "github.com/tursodatabase/libsql-client-go/libsql" ) func main() { diff --git a/render_test.go b/render_test.go index 016202cf..866144a3 100644 --- a/render_test.go +++ b/render_test.go @@ -22,7 +22,11 @@ func TestRenderSession(t *testing.T) { zerolog.SetGlobalLevel(level) loadStaticAssets(static) - coreClient, _ := coreClientsFromEnv() + + db, err := newDatabaseClientFromEnv() + assert.NoError(t, err) + + coreClient, _ := coreClientsFromEnv(db) defer coreClient.Database().Disconnect() bgImage, ok := assets.GetLoadedImage("bg-default") From b139c61df3ea82444ba3b40ef58244a063810a3d Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 15:54:02 -0400 Subject: [PATCH 175/341] removed logs that exposed tokens --- cmds/discord/rest/client.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmds/discord/rest/client.go b/cmds/discord/rest/client.go index e5b829f8..74cdc95f 100644 --- a/cmds/discord/rest/client.go +++ b/cmds/discord/rest/client.go @@ -11,7 +11,6 @@ import ( "time" "github.com/pkg/errors" - "github.com/rs/zerolog/log" "github.com/bwmarrin/discordgo" ) @@ -110,8 +109,6 @@ func partHeader(contentDisposition string, contentType string) textproto.MIMEHea } func (c *Client) do(req *http.Request, target any) error { - log.Debug().Str("url", req.URL.String()).Str("contentType", req.Header.Get("Content-Type")).Msg("sending a request to Discord API") - res, err := c.http.Do(req) if err != nil { return err @@ -140,8 +137,6 @@ func (c *Client) do(req *http.Request, target any) error { return fmt.Errorf("failed to decode response body :%w", err) } } - - log.Debug().Str("url", req.URL.String()).Str("contentType", req.Header.Get("Content-Type")).Msg("request finished successfully") return nil } From 25bbc0445bb91e9a65578180fdb95aa04c24f2d2 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 16:02:57 -0400 Subject: [PATCH 176/341] more optimistic timeout --- cmds/core/queue/queue.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmds/core/queue/queue.go b/cmds/core/queue/queue.go index f96657a3..4eab7c86 100644 --- a/cmds/core/queue/queue.go +++ b/cmds/core/queue/queue.go @@ -36,7 +36,7 @@ func New(workerLimit int, newCoreClient func() (core.Client, error)) *queue { handlers: make(map[models.TaskType]tasks.TaskHandler), workerLimit: workerLimit, - workerTimeout: time.Second * 15, + workerTimeout: time.Second * 30, workerLimiter: make(chan struct{}, workerLimit), activeTasksMx: &sync.Mutex{}, @@ -184,9 +184,6 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { } }() - wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) - defer cancel() - coreClient, err := q.newCoreClient() if err != nil { log.Err(err).Msg("failed to create a new core client for a task worker") @@ -201,7 +198,7 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { Comment: "task missing a handler", Timestamp: time.Now(), }) - err := coreClient.Database().UpdateTasks(wctx, task) + err := coreClient.Database().UpdateTasks(ctx, task) if err != nil { log.Err(err).Msg("failed to update a task") } @@ -224,6 +221,9 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { } task.LogAttempt(l) + wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) + defer cancel() + err = coreClient.Database().UpdateTasks(wctx, task) if err != nil { log.Err(err).Msg("failed to update a task") From a8c9fbe320e724dcaf8bfdd68f2ccbc19a06e1f2 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 24 Jun 2024 16:04:20 -0400 Subject: [PATCH 177/341] made context specifically before each call --- cmds/core/queue/queue.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmds/core/queue/queue.go b/cmds/core/queue/queue.go index 4eab7c86..e2f13203 100644 --- a/cmds/core/queue/queue.go +++ b/cmds/core/queue/queue.go @@ -177,7 +177,11 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { Timestamp: time.Now(), Comment: "task caused a panic in worker handler", }) - err = coreClient.Database().UpdateTasks(context.Background(), task) + + uctx, cancel := context.WithTimeout(ctx, q.workerTimeout) + defer cancel() + + err = coreClient.Database().UpdateTasks(uctx, task) if err != nil { event.AnErr("updateTasks", err).Str("additional", "failed to update a task") } @@ -198,7 +202,11 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { Comment: "task missing a handler", Timestamp: time.Now(), }) - err := coreClient.Database().UpdateTasks(ctx, task) + + uctx, cancel := context.WithTimeout(ctx, q.workerTimeout) + defer cancel() + + err := coreClient.Database().UpdateTasks(uctx, task) if err != nil { log.Err(err).Msg("failed to update a task") } From ee7bc4694946f5e2c844fd3d1bf2e9d3c27702d8 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 11:53:49 -0400 Subject: [PATCH 178/341] added timings --- cmds/discord/commands/session.go | 10 ++++++-- cmds/discord/commands/stats.go | 10 ++++++-- cmds/discord/common/context.go | 5 ++-- internal/stats/render/session/v1/cards.go | 30 ++++++++++++++++++++--- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go index b81e0e78..86a6c644 100644 --- a/cmds/discord/commands/session.go +++ b/cmds/discord/commands/session.go @@ -63,7 +63,7 @@ func init() { // TODO: Get user background } - image, _, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), accountID, options.PeriodStart, render.WithBackground(background)) + image, meta, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), accountID, options.PeriodStart, render.WithBackground(background)) if err != nil { if errors.Is(err, stats.ErrAccountNotTracked) || (errors.Is(err, fetch.ErrSessionNotFound) && options.Days < 1) { return ctx.Reply("session_error_account_was_not_tracked") @@ -80,7 +80,13 @@ func init() { return ctx.Err(err) } - return ctx.File(&buf, "session_command_by_aftermath.png") + var timings = []string{"```"} + for name, duration := range meta.Timings { + timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) + } + timings = append(timings, "```") + + return ctx.File(&buf, "session_command_by_aftermath.png", timings...) }), ) } diff --git a/cmds/discord/commands/stats.go b/cmds/discord/commands/stats.go index 505c21ef..b7ee55c6 100644 --- a/cmds/discord/commands/stats.go +++ b/cmds/discord/commands/stats.go @@ -60,7 +60,7 @@ func init() { // TODO: Get user background } - image, _, err := ctx.Core.Render(ctx.Locale).Period(context.Background(), accountID, options.PeriodStart, render.WithBackground(background)) + image, meta, err := ctx.Core.Render(ctx.Locale).Period(context.Background(), accountID, options.PeriodStart, render.WithBackground(background)) if err != nil { return ctx.Err(err) } @@ -71,7 +71,13 @@ func init() { return ctx.Err(err) } - return ctx.File(&buf, "stats_command_by_aftermath.png") + var timings = []string{"```"} + for name, duration := range meta.Timings { + timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) + } + timings = append(timings, "```") + + return ctx.File(&buf, "stats_command_by_aftermath.png", timings...) }), ) } diff --git a/cmds/discord/common/context.go b/cmds/discord/common/context.go index 4fa0c75d..9a09bd6d 100644 --- a/cmds/discord/common/context.go +++ b/cmds/discord/common/context.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "strings" "github.com/pkg/errors" @@ -112,11 +113,11 @@ func (c *Context) ReplyFmt(key string, args ...any) error { return c.Message(fmt.Sprintf(c.Localize(key), args...)) } -func (c *Context) File(r io.Reader, name string) error { +func (c *Context) File(r io.Reader, name string, message ...string) error { if r == nil { return errors.New("bad Context#File call with nil io.Reader") } - return c.respond(discordgo.InteractionResponseData{Files: []*discordgo.File{{Reader: r, Name: name}}}) + return c.respond(discordgo.InteractionResponseData{Files: []*discordgo.File{{Reader: r, Name: name}}, Content: strings.Join(message, "\n")}) } func (c *Context) isCommand() bool { diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index 2eed140d..855148c2 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -10,6 +10,7 @@ import ( prepare "github.com/cufee/aftermath/internal/stats/prepare/common/v1" "github.com/cufee/aftermath/internal/stats/prepare/session/v1" "github.com/cufee/aftermath/internal/stats/render/common/v1" + "github.com/pkg/errors" ) func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []models.UserSubscription, opts common.Options) (common.Segments, error) { @@ -199,17 +200,38 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // } + // we are done with the primary column at this point and can render it as an image in order to have access to final height + primaryColumnBlock := common.NewBlocksContent(overviewColumnStyle(primaryCardWidth), primaryColumn...) + primaryColumnImage, err := primaryColumnBlock.Render() + if err != nil { + return common.Segments{}, err + } + // unrated vehicles + var totalSecondaryCardsHeight float64 + var primaryCardHeight = float64(primaryColumnImage.Bounds().Dy()) if shouldRenderUnratedVehicles { for i, vehicle := range cards.Unrated.Vehicles { - secondaryColumn = append(secondaryColumn, makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth)) - if i == len(cards.Unrated.Vehicles)-1 { - secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) + vehicleCard := makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth) + vehicleCardImage, err := vehicleCard.Render() + if err != nil { + return common.Segments{}, errors.Wrapf(err, "failed to render a vehicle card for %s", vehicle.Title) } + + height := float64(vehicleCardImage.Bounds().Dy()) + // stop rendering cards when the total column height is larger than the primary column and there are at least 3 vehicles + if totalSecondaryCardsHeight+height > primaryCardHeight && i >= 3 { + break + } + secondaryColumn = append(secondaryColumn, common.NewImageContent(common.Style{}, vehicleCardImage)) + totalSecondaryCardsHeight += height + overviewCardStyle(0).Gap + } + if len(cards.Unrated.Vehicles) > 0 { + secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) } } - columns := []common.Block{common.NewBlocksContent(overviewColumnStyle(primaryCardWidth), primaryColumn...)} + columns := []common.Block{common.NewImageContent(common.Style{}, primaryColumnImage)} if len(secondaryColumn) > 0 { columns = append(columns, common.NewBlocksContent(overviewColumnStyle(secondaryCardWidth), secondaryColumn...)) } From 1bb71688bc980737ef5a813d82d536dbbff76744 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 13:06:10 -0400 Subject: [PATCH 179/341] added custom font type --- internal/stats/render/assets/load.go | 36 ++++---- internal/stats/render/common/v1/badges.go | 16 ++-- internal/stats/render/common/v1/block.go | 6 +- internal/stats/render/common/v1/constants.go | 62 +++++++------- internal/stats/render/common/v1/footer.go | 2 +- internal/stats/render/common/v1/header.go | 2 +- .../stats/render/common/v1/player_title.go | 6 +- internal/stats/render/common/v1/segments.go | 84 +++++++++++++------ internal/stats/render/common/v1/strings.go | 10 +-- internal/stats/render/common/v1/style.go | 4 +- .../stats/render/common/v1/tier_percentage.go | 2 +- internal/stats/render/period/v1/cards.go | 16 ++-- internal/stats/render/period/v1/constants.go | 14 ++-- internal/stats/render/session/v1/cards.go | 71 +++++++--------- internal/stats/render/session/v1/constants.go | 30 +++---- 15 files changed, 193 insertions(+), 168 deletions(-) diff --git a/internal/stats/render/assets/load.go b/internal/stats/render/assets/load.go index 79a68b71..36940806 100644 --- a/internal/stats/render/assets/load.go +++ b/internal/stats/render/assets/load.go @@ -13,10 +13,9 @@ import ( "github.com/cufee/aftermath/internal/files" "github.com/golang/freetype/truetype" "github.com/rs/zerolog/log" - "golang.org/x/image/font" ) -var fontsMap = make(map[string]*truetype.Font) +var fontsMap = make(map[string][]byte) var imagesMap = make(map[string]image.Image) func LoadAssets(assets fs.FS, directory string) error { @@ -38,19 +37,19 @@ func LoadAssets(assets fs.FS, directory string) error { return nil } -func loadFonts(files map[string][]byte) (map[string]*truetype.Font, error) { - fonts := make(map[string]*truetype.Font) +func loadFonts(files map[string][]byte) (map[string][]byte, error) { + fonts := make(map[string][]byte) for path, data := range files { if !strings.HasSuffix(path, ".ttf") { continue } - font, err := truetype.Parse(data) + _, err := truetype.Parse(data) if err != nil { return nil, err } - fonts[strings.Split(filepath.Base(path), ".")[0]] = font + fonts[strings.Split(filepath.Base(path), ".")[0]] = data log.Debug().Str("path", path).Msg("loaded font") } @@ -79,18 +78,19 @@ func loadImages(files map[string][]byte) (map[string]image.Image, error) { return imagesMap, nil } -func GetLoadedFontFaces(name string, sizes ...float64) (map[float64]font.Face, bool) { - loadedFont, ok := fontsMap[name] - if !ok { - return nil, false - } - faces := make(map[float64]font.Face) - for _, size := range sizes { - faces[size] = truetype.NewFace(loadedFont, &truetype.Options{ - Size: size, - }) - } - return faces, true +func GetLoadedFontFace(name string) ([]byte, bool) { + f, ok := fontsMap[name] + return f, ok + // if !ok { + // return nil, false + // } + // faces := make(map[float64]font.Face) + // for _, size := range sizes { + // faces[size] = truetype.NewFace(loadedFont, &truetype.Options{ + // Size: size, + // }) + // } + // return faces, true } func GetLoadedImage(name string) (image.Image, bool) { diff --git a/internal/stats/render/common/v1/badges.go b/internal/stats/render/common/v1/badges.go index 7b2fa492..a674f8e7 100644 --- a/internal/stats/render/common/v1/badges.go +++ b/internal/stats/render/common/v1/badges.go @@ -57,7 +57,7 @@ var ( Style: subscriptionPillStyle{ Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Height: 32}, Icon: Style{Width: 16, Height: 16, BackgroundColor: TextSubscriptionPlus}, - Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 5}, + Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 5}, }, } userSubscriptionPlus = &subscriptionHeader{ @@ -66,7 +66,7 @@ var ( Style: subscriptionPillStyle{ Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, Icon: Style{Width: 24, Height: 24, BackgroundColor: TextSubscriptionPlus}, - Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 5}, + Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 5}, }, } userSubscriptionPro = &subscriptionHeader{ @@ -75,7 +75,7 @@ var ( Style: subscriptionPillStyle{ Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, Icon: Style{Width: 24, Height: 24, BackgroundColor: TextSubscriptionPremium}, - Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 5}, + Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 5}, }, } // Clans @@ -101,7 +101,7 @@ var ( Style: subscriptionPillStyle{ Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: color.RGBA{64, 32, 128, 180}, BorderRadius: 15, PaddingX: 6, PaddingY: 5, Gap: 5, Height: 32}, Icon: Style{Width: 20, Height: 20, BackgroundColor: TextPrimary}, - Text: Style{Font: &FontSmall, FontColor: TextPrimary, PaddingX: 5}, + Text: Style{Font: FontSmall, FontColor: TextPrimary, PaddingX: 5}, }, } subscriptionServerModerator = &subscriptionHeader{ @@ -110,7 +110,7 @@ var ( Style: subscriptionPillStyle{ Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, Icon: Style{Width: 20, Height: 20}, - Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 2}, + Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 2}, }, } subscriptionContentModerator = &subscriptionHeader{ @@ -119,7 +119,7 @@ var ( Style: subscriptionPillStyle{ Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, Icon: Style{Width: 20, Height: 20}, - Text: Style{Font: &FontSmall, FontColor: TextSecondary, PaddingX: 2}, + Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 2}, }, } subscriptionServerBooster = &subscriptionHeader{ @@ -128,7 +128,7 @@ var ( Style: subscriptionPillStyle{ Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, Icon: Style{Width: 20, Height: 20}, - Text: Style{Font: &FontSmall, FontColor: TextSecondary}, + Text: Style{Font: FontSmall, FontColor: TextSecondary}, }, } subscriptionTranslator = &subscriptionHeader{ @@ -137,7 +137,7 @@ var ( Style: subscriptionPillStyle{ Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, Icon: Style{Width: 20, Height: 20, BackgroundColor: TextPrimary}, - Text: Style{Font: &FontSmall, FontColor: TextSecondary}, + Text: Style{Font: FontSmall, FontColor: TextSecondary}, }, } ) diff --git a/internal/stats/render/common/v1/block.go b/internal/stats/render/common/v1/block.go index c5b20853..307ef9ba 100644 --- a/internal/stats/render/common/v1/block.go +++ b/internal/stats/render/common/v1/block.go @@ -53,15 +53,15 @@ func NewTextContent(style Style, value string) Block { } func (content contentText) Render(style Style) (image.Image, error) { - if style.Font == nil { + if !style.Font.Valid() { return nil, errors.New("font not set") } - size := MeasureString(content.value, *style.Font) + size := MeasureString(content.value, style.Font) ctx := gg.NewContext(int(size.TotalWidth+(style.PaddingX*2)), int(size.TotalHeight+(style.PaddingY*2))) // Render text - ctx.SetFontFace(*style.Font) + ctx.SetFontFace(style.Font.Face()) ctx.SetColor(style.FontColor) var lastX, lastY float64 = style.PaddingX, style.PaddingY + 1 diff --git a/internal/stats/render/common/v1/constants.go b/internal/stats/render/common/v1/constants.go index e637e251..bc1c3a25 100644 --- a/internal/stats/render/common/v1/constants.go +++ b/internal/stats/render/common/v1/constants.go @@ -3,20 +3,21 @@ package common import ( "image/color" + "github.com/golang/freetype/truetype" "github.com/pkg/errors" + "golang.org/x/image/font" "github.com/cufee/aftermath/internal/stats/render/assets" - "golang.org/x/image/font" ) var DiscordBackgroundColor = color.RGBA{49, 51, 56, 255} var ( - FontXL font.Face - Font2XL font.Face - FontLarge font.Face - FontMedium font.Face - FontSmall font.Face + FontXL Font + Font2XL Font + FontLarge Font + FontMedium Font + FontSmall Font TextPrimary = color.RGBA{255, 255, 255, 255} TextSecondary = color.RGBA{204, 204, 204, 255} @@ -37,35 +38,38 @@ var ( BorderRadiusXS = 10.0 ) -var fontCache map[float64]font.Face +type Font struct { + size float64 + data []byte +} -func InitLoadedAssets() error { - var ok bool - fontCache, ok = assets.GetLoadedFontFaces("default", 36, 32, 24, 18, 14) - if !ok { - return errors.New("default font not found") - } - FontXL = fontCache[32] - Font2XL = fontCache[36] - FontLarge = fontCache[24] - FontMedium = fontCache[18] - FontSmall = fontCache[14] +func (f *Font) Valid() bool { + return f.data != nil && f.size > 0 +} - return nil +func (f *Font) Face() font.Face { + ttf, _ := truetype.Parse(f.data) + return truetype.NewFace(ttf, &truetype.Options{ + Size: f.size, + }) } -func GetCustomFont(size float64) (font.Face, bool) { - if f, ok := fontCache[size]; ok { - return f, true - } +// var fontCache map[float64]font.Face - newCache, ok := assets.GetLoadedFontFaces("default", size) +func InitLoadedAssets() error { + // var ok bool + // fontCache, ok = assets.GetLoadedFontFaces("default", 36, 32, 24, 18, 14) + fontData, ok := assets.GetLoadedFontFace("default") if !ok { - return nil, false - } - for size, font := range newCache { - fontCache[size] = font + return errors.New("default font not found") } - return newCache[size], true + FontXL = Font{32, fontData} + Font2XL = Font{36, fontData} + + FontLarge = Font{24, fontData} + FontMedium = Font{18, fontData} + FontSmall = Font{14, fontData} + + return nil } diff --git a/internal/stats/render/common/v1/footer.go b/internal/stats/render/common/v1/footer.go index 99051184..16cff210 100644 --- a/internal/stats/render/common/v1/footer.go +++ b/internal/stats/render/common/v1/footer.go @@ -12,5 +12,5 @@ func NewFooterCard(text string) Block { BackgroundColor: backgroundColor, BorderRadius: BorderRadiusSM, // Debug: true, - }, NewTextContent(Style{Font: &FontSmall, FontColor: TextSecondary}, text)) + }, NewTextContent(Style{Font: FontSmall, FontColor: TextSecondary}, text)) } diff --git a/internal/stats/render/common/v1/header.go b/internal/stats/render/common/v1/header.go index 24c8945c..fc599a61 100644 --- a/internal/stats/render/common/v1/header.go +++ b/internal/stats/render/common/v1/header.go @@ -22,7 +22,7 @@ func NewHeaderCard(width float64, subscriptions []models.UserSubscription, promo // Users without a subscription get promo text var textBlocks []Block for _, text := range promoText { - textBlocks = append(textBlocks, NewTextContent(Style{Font: &FontMedium, FontColor: TextPrimary}, text)) + textBlocks = append(textBlocks, NewTextContent(Style{Font: FontMedium, FontColor: TextPrimary}, text)) } cards = append(cards, NewBlocksContent(Style{ Direction: DirectionVertical, diff --git a/internal/stats/render/common/v1/player_title.go b/internal/stats/render/common/v1/player_title.go index 7636f5a9..eceb3f6f 100644 --- a/internal/stats/render/common/v1/player_title.go +++ b/internal/stats/render/common/v1/player_title.go @@ -29,8 +29,8 @@ func DefaultPlayerTitleStyle(containerStyle Style) TitleCardStyle { return TitleCardStyle{ Container: containerStyle, - Nickname: Style{Font: &FontLarge, FontColor: TextPrimary}, - ClanTag: Style{Font: &FontMedium, FontColor: TextSecondary, PaddingX: 10, PaddingY: 5, BackgroundColor: clanTagBackgroundColor, BorderRadius: BorderRadiusSM}, + Nickname: Style{Font: FontLarge, FontColor: TextPrimary}, + ClanTag: Style{Font: FontMedium, FontColor: TextSecondary, PaddingX: 10, PaddingY: 5, BackgroundColor: clanTagBackgroundColor, BorderRadius: BorderRadiusSM}, } } @@ -71,7 +71,7 @@ func newClanTagBlock(style Style, clanTag string, subs []models.UserSubscription } var blocks []Block - blocks = append(blocks, NewTextContent(Style{Font: &FontMedium, FontColor: TextSecondary}, clanTag)) + blocks = append(blocks, NewTextContent(Style{Font: FontMedium, FontColor: TextSecondary}, clanTag)) if sub := ClanSubscriptionsBadges(subs); sub != nil { iconBlock, err := sub.Block() if err == nil { diff --git a/internal/stats/render/common/v1/segments.go b/internal/stats/render/common/v1/segments.go index 869548f5..fc6dd829 100644 --- a/internal/stats/render/common/v1/segments.go +++ b/internal/stats/render/common/v1/segments.go @@ -2,7 +2,9 @@ package common import ( "image" + "sync" + "github.com/cufee/aftermath/internal/retry" "github.com/pkg/errors" ) @@ -34,38 +36,66 @@ func (s *Segments) Render(opts ...Option) (image.Image, error) { apply(&options) } - var frameParts []Block + var wg sync.WaitGroup + var headerBlock *Block + var contentBlock retry.DataWithErr[Block] + var footerBlock *Block - mainSegment := NewBlocksContent( - Style{ - Direction: DirectionVertical, - AlignItems: AlignItemsCenter, - PaddingX: 20, - PaddingY: 20, - Gap: 10, - }, s.content...) - mainSegmentImg, err := mainSegment.Render() - if err != nil { - return nil, err - } - mainSegmentImg = AddBackground(mainSegmentImg, options.Background, Style{Blur: 10, BorderRadius: 42.5}) - - frameParts = append(frameParts, NewImageContent(Style{}, mainSegmentImg)) - if len(s.header) > 0 { - frameParts = append(frameParts, NewBlocksContent( + wg.Add(3) + go func() { + defer wg.Done() + mainSegment := NewBlocksContent( Style{ Direction: DirectionVertical, AlignItems: AlignItemsCenter, + PaddingX: 20, + PaddingY: 20, Gap: 10, - }, s.header...)) + }, s.content...) + mainSegmentImg, err := mainSegment.Render() + if err != nil { + contentBlock = retry.DataWithErr[Block]{Err: err} + return + } + mainSegmentBlock := NewImageContent(Style{}, AddBackground(mainSegmentImg, options.Background, Style{Blur: 10, BorderRadius: 42.5})) + contentBlock = retry.DataWithErr[Block]{Data: mainSegmentBlock} + }() + go func() { + defer wg.Done() + if len(s.header) > 0 { + header := NewBlocksContent( + Style{ + Direction: DirectionVertical, + AlignItems: AlignItemsCenter, + Gap: 10, + }, s.header...) + headerBlock = &header + } + }() + go func() { + defer wg.Done() + if len(s.footer) > 0 { + footer := NewBlocksContent( + Style{ + Direction: DirectionVertical, + AlignItems: AlignItemsCenter, + Gap: 10, + }, s.footer...) + footerBlock = &footer + } + }() + wg.Wait() + + var frameBlocks []Block + if headerBlock != nil { + frameBlocks = append(frameBlocks, *headerBlock) } - if len(s.footer) > 0 { - frameParts = append(frameParts, NewBlocksContent( - Style{ - Direction: DirectionVertical, - AlignItems: AlignItemsCenter, - Gap: 10, - }, s.footer...)) + if contentBlock.Err != nil { + return nil, contentBlock.Err + } + frameBlocks = append(frameBlocks, contentBlock.Data) + if footerBlock != nil { + frameBlocks = append(frameBlocks, *footerBlock) } frame := NewBlocksContent( @@ -74,7 +104,7 @@ func (s *Segments) Render(opts ...Option) (image.Image, error) { AlignItems: AlignItemsCenter, Gap: 5, }, - frameParts..., + frameBlocks..., ) return frame.Render() } diff --git a/internal/stats/render/common/v1/strings.go b/internal/stats/render/common/v1/strings.go index c6a3ba15..4c4eaa89 100644 --- a/internal/stats/render/common/v1/strings.go +++ b/internal/stats/render/common/v1/strings.go @@ -4,7 +4,6 @@ import ( "strings" "github.com/fogleman/gg" - "golang.org/x/image/font" ) type stringSize struct { @@ -14,20 +13,21 @@ type stringSize struct { LineHeight float64 } -func MeasureString(text string, font font.Face) stringSize { - if font == nil { +func MeasureString(text string, font Font) stringSize { + if !font.Valid() { return stringSize{} } if text == "" { return stringSize{} } + face := font.Face() measureCtx := gg.NewContext(1, 1) - measureCtx.SetFontFace(font) + measureCtx.SetFontFace(face) var result stringSize // Account for font descender height - result.LineOffset = float64(font.Metrics().Descent>>6) * 2 + result.LineOffset = float64(face.Metrics().Descent>>6) * 2 for _, line := range strings.Split(text, "\n") { w, h := measureCtx.MeasureString(line) diff --git a/internal/stats/render/common/v1/style.go b/internal/stats/render/common/v1/style.go index ad1f69e6..f0ed9eb9 100644 --- a/internal/stats/render/common/v1/style.go +++ b/internal/stats/render/common/v1/style.go @@ -2,8 +2,6 @@ package common import ( "image/color" - - "golang.org/x/image/font" ) type alignItemsValue int @@ -29,7 +27,7 @@ const ( ) type Style struct { - Font *font.Face + Font Font FontColor color.Color JustifyContent justifyContentValue diff --git a/internal/stats/render/common/v1/tier_percentage.go b/internal/stats/render/common/v1/tier_percentage.go index d6238cc8..2a6eabaf 100644 --- a/internal/stats/render/common/v1/tier_percentage.go +++ b/internal/stats/render/common/v1/tier_percentage.go @@ -25,7 +25,7 @@ func NewTierPercentageCard(style Style, vehicles map[string]frame.VehicleStatsFr BackgroundColor: shade, Width: style.Width / float64(elements), JustifyContent: JustifyContentCenter, - }, NewTextContent(Style{Font: &FontMedium, FontColor: TextPrimary}, fmt.Sprint(i)))) + }, NewTextContent(Style{Font: FontMedium, FontColor: TextPrimary}, fmt.Sprint(i)))) } return NewBlocksContent(style, blocks...) diff --git a/internal/stats/render/period/v1/cards.go b/internal/stats/render/period/v1/cards.go index 2fa066d9..e3f4a350 100644 --- a/internal/stats/render/period/v1/cards.go +++ b/internal/stats/render/period/v1/cards.go @@ -28,8 +28,8 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs { { titleStyle := common.DefaultPlayerTitleStyle(titleCardStyle(cardWidth)) - clanSize := common.MeasureString(stats.Account.ClanTag, *titleStyle.ClanTag.Font) - nameSize := common.MeasureString(stats.Account.Nickname, *titleStyle.Nickname.Font) + clanSize := common.MeasureString(stats.Account.ClanTag, titleStyle.ClanTag.Font) + nameSize := common.MeasureString(stats.Account.Nickname, titleStyle.Nickname.Font) cardWidth = common.Max(cardWidth, titleStyle.TotalPaddingAndGaps()+nameSize.TotalWidth+clanSize.TotalWidth*2) } { @@ -42,8 +42,8 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs if block.Tag == prepare.TagWN8 { label = common.GetWN8TierName(block.Value.Float()) } - labelSize := common.MeasureString(label, *labelStyle.Font) - valueSize := common.MeasureString(block.Value.String(), *valueStyle.Font) + labelSize := common.MeasureString(label, labelStyle.Font) + valueSize := common.MeasureString(block.Value.String(), valueStyle.Font) overviewColumnWidth = common.Max(overviewColumnWidth, common.Max(labelSize.TotalWidth, valueSize.TotalWidth)+(rowStyle.container.PaddingX*2)) } @@ -61,15 +61,15 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs var highlightBlocksMaxCount, highlightTitleMaxWidth, highlightBlockMaxSize float64 for _, highlight := range cards.Highlights { // Title and tank name - metaSize := common.MeasureString(highlight.Meta, *highlightStyle.cardTitle.Font) - titleSize := common.MeasureString(highlight.Title, *highlightStyle.tankName.Font) + metaSize := common.MeasureString(highlight.Meta, highlightStyle.cardTitle.Font) + titleSize := common.MeasureString(highlight.Title, highlightStyle.tankName.Font) highlightTitleMaxWidth = common.Max(highlightTitleMaxWidth, metaSize.TotalWidth, titleSize.TotalWidth) // Blocks highlightBlocksMaxCount = common.Max(highlightBlocksMaxCount, float64(len(highlight.Blocks))) for _, block := range highlight.Blocks { - labelSize := common.MeasureString(block.Label, *highlightStyle.blockLabel.Font) - valueSize := common.MeasureString(block.Value.String(), *highlightStyle.blockValue.Font) + labelSize := common.MeasureString(block.Label, highlightStyle.blockLabel.Font) + valueSize := common.MeasureString(block.Value.String(), highlightStyle.blockValue.Font) highlightBlockMaxSize = common.Max(highlightBlockMaxSize, valueSize.TotalWidth, labelSize.TotalWidth) } } diff --git a/internal/stats/render/period/v1/constants.go b/internal/stats/render/period/v1/constants.go index ed174590..4534c18e 100644 --- a/internal/stats/render/period/v1/constants.go +++ b/internal/stats/render/period/v1/constants.go @@ -22,11 +22,11 @@ type highlightStyle struct { func (s *overviewStyle) block(block prepare.StatsBlock[period.BlockData]) (common.Style, common.Style) { switch block.Data.Flavor { case period.BlockFlavorSpecial: - return common.Style{FontColor: common.TextPrimary, Font: &common.FontXL}, common.Style{FontColor: common.TextAlt, Font: &common.FontSmall} + return common.Style{FontColor: common.TextPrimary, Font: common.FontXL}, common.Style{FontColor: common.TextAlt, Font: common.FontSmall} case period.BlockFlavorSecondary: - return common.Style{FontColor: common.TextSecondary, Font: &common.FontMedium}, common.Style{FontColor: common.TextAlt, Font: &common.FontSmall} + return common.Style{FontColor: common.TextSecondary, Font: common.FontMedium}, common.Style{FontColor: common.TextAlt, Font: common.FontSmall} default: - return common.Style{FontColor: common.TextPrimary, Font: &common.FontLarge}, common.Style{FontColor: common.TextAlt, Font: &common.FontSmall} + return common.Style{FontColor: common.TextPrimary, Font: common.FontLarge}, common.Style{FontColor: common.TextAlt, Font: common.FontSmall} } } @@ -92,9 +92,9 @@ func highlightCardStyle(containerStyle common.Style) highlightStyle { return highlightStyle{ container: container, - cardTitle: common.Style{Font: &common.FontSmall, FontColor: common.TextSecondary}, - tankName: common.Style{Font: &common.FontMedium, FontColor: common.TextPrimary}, - blockValue: common.Style{Font: &common.FontMedium, FontColor: common.TextPrimary}, - blockLabel: common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, + cardTitle: common.Style{Font: common.FontSmall, FontColor: common.TextSecondary}, + tankName: common.Style{Font: common.FontMedium, FontColor: common.TextPrimary}, + blockValue: common.Style{Font: common.FontMedium, FontColor: common.TextPrimary}, + blockLabel: common.Style{Font: common.FontSmall, FontColor: common.TextAlt}, } } diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index 855148c2..ff530a8e 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -10,16 +10,16 @@ import ( prepare "github.com/cufee/aftermath/internal/stats/prepare/common/v1" "github.com/cufee/aftermath/internal/stats/prepare/session/v1" "github.com/cufee/aftermath/internal/stats/render/common/v1" - "github.com/pkg/errors" ) func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []models.UserSubscription, opts common.Options) (common.Segments, error) { var ( + renderVehiclesCount = 3 // minimum number of vehicle cards // primary cards // when there are some unrated battles or no battles at all shouldRenderUnratedOverview = session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 // when there are 3 vehicle cards and no rating overview cards or there are 6 vehicle cards and some rating battles - shouldRenderUnratedHighlights = (session.RegularBattles.Battles > 0 && session.RatingBattles.Battles < 1 && len(cards.Unrated.Vehicles) > 3) || + shouldRenderUnratedHighlights = (session.RegularBattles.Battles > 0 && session.RatingBattles.Battles < 1 && len(cards.Unrated.Vehicles) > renderVehiclesCount) || (session.RegularBattles.Battles > 0 && len(cards.Unrated.Vehicles) > 6) shouldRenderRatingOverview = session.RatingBattles.Battles > 0 shouldRenderRatingVehicles = len(cards.Unrated.Vehicles) == 0 @@ -27,6 +27,17 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card shouldRenderUnratedVehicles = len(cards.Unrated.Vehicles) > 0 ) + // try to make the columns height roughly similar to primary column + if shouldRenderUnratedHighlights && len(cards.Unrated.Highlights) > 1 { + renderVehiclesCount += len(cards.Unrated.Highlights) + } + if shouldRenderRatingOverview { + renderVehiclesCount += 2 + } + if shouldRenderRatingVehicles { + renderVehiclesCount += len(cards.Rating.Vehicles) + } + var segments common.Segments var primaryColumn []common.Block var secondaryColumn []common.Block @@ -44,13 +55,13 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card { titleStyle := common.DefaultPlayerTitleStyle(playerNameCardStyle(0)) - clanSize := common.MeasureString(session.Account.ClanTag, *titleStyle.ClanTag.Font) - nameSize := common.MeasureString(session.Account.Nickname, *titleStyle.Nickname.Font) + clanSize := common.MeasureString(session.Account.ClanTag, titleStyle.ClanTag.Font) + nameSize := common.MeasureString(session.Account.Nickname, titleStyle.Nickname.Font) primaryCardWidth = common.Max(primaryCardWidth, titleStyle.TotalPaddingAndGaps()+nameSize.TotalWidth+clanSize.TotalWidth*2) } { for _, text := range opts.PromoText { - size := common.MeasureString(text, *promoTextStyle.Font) + size := common.MeasureString(text, promoTextStyle.Font) totalFrameWidth = common.Max(size.TotalWidth, totalFrameWidth) } } @@ -88,7 +99,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card if shouldRenderRatingVehicles { for _, card := range cards.Rating.Vehicles { // [title] [session] - titleSize := common.MeasureString(card.Title, *ratingVehicleCardTitleStyle.Font) + titleSize := common.MeasureString(card.Title, ratingVehicleCardTitleStyle.Font) presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, ratingVehicleBlockStyle.session, ratingVehicleBlockStyle.career, ratingVehicleBlockStyle.label, ratingVehicleBlocksRowStyle(0)) // add the gap and card padding, the gap here accounts for title being inline with content contentWidth += ratingVehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)) + ratingVehicleCardStyle(0).PaddingX*2 + titleSize.TotalWidth @@ -104,8 +115,8 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card for _, card := range cards.Unrated.Highlights { // [card label] [session] // [title] [label] - labelSize := common.MeasureString(card.Meta, *highlightCardTitleTextStyle.Font) - titleSize := common.MeasureString(card.Title, *highlightVehicleNameTextStyle.Font) + labelSize := common.MeasureString(card.Meta, highlightCardTitleTextStyle.Font) + titleSize := common.MeasureString(card.Title, highlightVehicleNameTextStyle.Font) presetBlockWidth, contentWidth := highlightedVehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, highlightedVehicleBlockRowStyle(0)) // add the gap and card padding, the gap here accounts for title/label being inline with content @@ -131,7 +142,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, vehicleBlocksRowStyle(0)) contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)-1) + vehicleCardStyle(0).PaddingX*2 - titleSize := common.MeasureString(card.Title, *vehicleCardTitleTextStyle.Font) + titleSize := common.MeasureString(card.Title, vehicleCardTitleTextStyle.Font) secondaryCardWidth = common.Max(secondaryCardWidth, contentWidth, titleSize.TotalWidth+vehicleCardStyle(0).PaddingX*2) for key, width := range presetBlockWidth { @@ -200,38 +211,20 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // } - // we are done with the primary column at this point and can render it as an image in order to have access to final height - primaryColumnBlock := common.NewBlocksContent(overviewColumnStyle(primaryCardWidth), primaryColumn...) - primaryColumnImage, err := primaryColumnBlock.Render() - if err != nil { - return common.Segments{}, err - } - // unrated vehicles - var totalSecondaryCardsHeight float64 - var primaryCardHeight = float64(primaryColumnImage.Bounds().Dy()) if shouldRenderUnratedVehicles { for i, vehicle := range cards.Unrated.Vehicles { - vehicleCard := makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth) - vehicleCardImage, err := vehicleCard.Render() - if err != nil { - return common.Segments{}, errors.Wrapf(err, "failed to render a vehicle card for %s", vehicle.Title) - } - - height := float64(vehicleCardImage.Bounds().Dy()) - // stop rendering cards when the total column height is larger than the primary column and there are at least 3 vehicles - if totalSecondaryCardsHeight+height > primaryCardHeight && i >= 3 { + if i >= renderVehiclesCount { break } - secondaryColumn = append(secondaryColumn, common.NewImageContent(common.Style{}, vehicleCardImage)) - totalSecondaryCardsHeight += height + overviewCardStyle(0).Gap - } - if len(cards.Unrated.Vehicles) > 0 { - secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) + secondaryColumn = append(secondaryColumn, makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth)) + if i == len(cards.Unrated.Vehicles)-1 { + secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) + } } } - columns := []common.Block{common.NewImageContent(common.Style{}, primaryColumnImage)} + columns := []common.Block{common.NewBlocksContent(overviewColumnStyle(primaryCardWidth), primaryColumn...)} if len(secondaryColumn) > 0 { columns = append(columns, common.NewBlocksContent(overviewColumnStyle(secondaryCardWidth), secondaryColumn...)) } @@ -247,15 +240,15 @@ func vehicleBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], sessionS for _, block := range blocks { var width float64 { - size := common.MeasureString(block.Data.Session.String(), *sessionStyle.Font) + size := common.MeasureString(block.Data.Session.String(), sessionStyle.Font) width = common.Max(width, size.TotalWidth+sessionStyle.PaddingX*2) } { - size := common.MeasureString(block.Data.Career.String(), *careerStyle.Font) + size := common.MeasureString(block.Data.Career.String(), careerStyle.Font) width = common.Max(width, size.TotalWidth+careerStyle.PaddingX*2) } { - size := common.MeasureString(block.Label, *labelStyle.Font) + size := common.MeasureString(block.Label, labelStyle.Font) width = common.Max(width, size.TotalWidth+labelStyle.PaddingX*2+vehicleLegendLabelContainer.PaddingX*2) } maxBlockWidth = common.Max(maxBlockWidth, width) @@ -280,11 +273,11 @@ func highlightedVehicleBlocksWidth(blocks []prepare.StatsBlock[session.BlockData for _, block := range blocks { var width float64 { - size := common.MeasureString(block.Data.Session.String(), *sessionStyle.Font) + size := common.MeasureString(block.Data.Session.String(), sessionStyle.Font) width = common.Max(width, size.TotalWidth+sessionStyle.PaddingX*2) } { - size := common.MeasureString(block.Label, *labelStyle.Font) + size := common.MeasureString(block.Label, labelStyle.Font) width = common.Max(width, size.TotalWidth+labelStyle.PaddingX*2) } maxBlockWidth = common.Max(maxBlockWidth, width) @@ -307,7 +300,7 @@ func overviewColumnBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], s for _, block := range blocks { // adjust width if this column includes a special icon if block.Tag == prepare.TagWN8 { - tierNameSize := common.MeasureString(common.GetWN8TierName(block.Value.Float()), *overviewSpecialRatingLabelStyle(nil).Font) + tierNameSize := common.MeasureString(common.GetWN8TierName(block.Value.Float()), overviewSpecialRatingLabelStyle(nil).Font) tierNameWithPadding := tierNameSize.TotalWidth + overviewSpecialRatingPillStyle(nil).PaddingX*2 presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], specialRatingIconSize, tierNameWithPadding) } diff --git a/internal/stats/render/session/v1/constants.go b/internal/stats/render/session/v1/constants.go index 8ef10208..8215b2e9 100644 --- a/internal/stats/render/session/v1/constants.go +++ b/internal/stats/render/session/v1/constants.go @@ -21,7 +21,7 @@ var ( var ( specialRatingColumnStyle = common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, Gap: 5} - promoTextStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextPrimary} + promoTextStyle = common.Style{Font: common.FontMedium, FontColor: common.TextPrimary} ) func frameStyle() common.Style { @@ -30,14 +30,14 @@ func frameStyle() common.Style { var ( overviewStatsBlockStyle = blockStyle{ - common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, - common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, - common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, + common.Style{Font: common.FontLarge, FontColor: common.TextPrimary}, + common.Style{Font: common.FontMedium, FontColor: common.TextSecondary}, + common.Style{Font: common.FontSmall, FontColor: common.TextAlt}, } ) func overviewSpecialRatingLabelStyle(color color.Color) common.Style { - return common.Style{FontColor: color, Font: &common.FontSmall} + return common.Style{FontColor: color, Font: common.FontSmall} } func overviewSpecialRatingPillStyle(color color.Color) common.Style { @@ -91,11 +91,11 @@ var ( PaddingY: 5, PaddingX: 10, } - vehicleCardTitleTextStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextAlt} + vehicleCardTitleTextStyle = common.Style{Font: common.FontMedium, FontColor: common.TextAlt} vehicleBlockStyle = blockStyle{ - common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, - common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, - common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, + common.Style{Font: common.FontLarge, FontColor: common.TextPrimary}, + common.Style{Font: common.FontMedium, FontColor: common.TextSecondary}, + common.Style{Font: common.FontSmall, FontColor: common.TextAlt}, } ) @@ -132,11 +132,11 @@ func vehicleBlocksRowStyle(width float64) common.Style { var ( ratingVehicleCardTitleContainerStyle = common.Style{Direction: common.DirectionHorizontal, Gap: 10, JustifyContent: common.JustifyContentSpaceBetween} - ratingVehicleCardTitleStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary, PaddingX: 5} + ratingVehicleCardTitleStyle = common.Style{Font: common.FontMedium, FontColor: common.TextSecondary, PaddingX: 5} ratingVehicleBlockStyle = blockStyle{ - common.Style{Font: &common.FontLarge, FontColor: common.TextPrimary}, - common.Style{Font: &common.FontMedium, FontColor: common.TextSecondary}, - common.Style{Font: &common.FontSmall, FontColor: common.TextAlt}, + common.Style{Font: common.FontLarge, FontColor: common.TextPrimary}, + common.Style{Font: common.FontMedium, FontColor: common.TextSecondary}, + common.Style{Font: common.FontSmall, FontColor: common.TextAlt}, } ) @@ -149,8 +149,8 @@ func ratingVehicleBlocksRowStyle(width float64) common.Style { } var ( - highlightCardTitleTextStyle = common.Style{Font: &common.FontSmall, FontColor: common.TextSecondary} - highlightVehicleNameTextStyle = common.Style{Font: &common.FontMedium, FontColor: common.TextPrimary} + highlightCardTitleTextStyle = common.Style{Font: common.FontSmall, FontColor: common.TextSecondary} + highlightVehicleNameTextStyle = common.Style{Font: common.FontMedium, FontColor: common.TextPrimary} ) func highlightedVehicleCardStyle(width float64) common.Style { From 5d4aa00e5243eb1f7278e22a141c28fbdc966549 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 14:20:46 -0400 Subject: [PATCH 180/341] removed implicit inits --- cmds/discord/router/handler.go | 17 +- internal/stats/render/assets/load.go | 10 -- internal/stats/render/common/v1/badges.go | 110 ------------ internal/stats/render/common/v1/block.go | 65 ++++++- internal/stats/render/common/v1/constants.go | 75 -------- internal/stats/render/common/v1/font.go | 26 +++ internal/stats/render/common/v1/footer.go | 2 +- internal/stats/render/common/v1/header.go | 2 +- internal/stats/render/common/v1/images.go | 2 +- internal/stats/render/common/v1/init.go | 167 ++++++++++++++++++ .../stats/render/common/v1/player_title.go | 6 +- .../stats/render/common/v1/tier_percentage.go | 2 +- internal/stats/render/period/v1/constants.go | 14 +- internal/stats/render/session/v1/blocks.go | 14 +- internal/stats/render/session/v1/cards.go | 73 ++++---- internal/stats/render/session/v1/constants.go | 71 +++++--- 16 files changed, 371 insertions(+), 285 deletions(-) delete mode 100644 internal/stats/render/common/v1/constants.go create mode 100644 internal/stats/render/common/v1/font.go create mode 100644 internal/stats/render/common/v1/init.go diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 53d76f72..0dd79565 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "net/http" - "runtime/debug" "sync" "time" @@ -209,14 +208,14 @@ func (router *Router) startInteractionHandlerAsync(interaction discordgo.Interac responseCh := make(chan discordgo.InteractionResponseData) go func() { - defer func() { - if r := recover(); r != nil { - log.Error().Str("stack", string(debug.Stack())).Msg("panic in interaction handler") - state.mx.Lock() - router.sendInteractionReply(interaction, state, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed. Please try again in a few seconds."}) - state.mx.Unlock() - } - }() + // defer func() { + // if r := recover(); r != nil { + // log.Error().Str("stack", string(debug.Stack())).Msg("panic in interaction handler") + // state.mx.Lock() + // router.sendInteractionReply(interaction, state, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed. Please try again in a few seconds."}) + // state.mx.Unlock() + // } + // }() router.handleInteraction(workerCtx, cancelWorker, interaction, command, responseCh) }() diff --git a/internal/stats/render/assets/load.go b/internal/stats/render/assets/load.go index 36940806..abb60925 100644 --- a/internal/stats/render/assets/load.go +++ b/internal/stats/render/assets/load.go @@ -81,16 +81,6 @@ func loadImages(files map[string][]byte) (map[string]image.Image, error) { func GetLoadedFontFace(name string) ([]byte, bool) { f, ok := fontsMap[name] return f, ok - // if !ok { - // return nil, false - // } - // faces := make(map[float64]font.Face) - // for _, size := range sizes { - // faces[size] = truetype.NewFace(loadedFont, &truetype.Options{ - // Size: size, - // }) - // } - // return faces, true } func GetLoadedImage(name string) (image.Image, bool) { diff --git a/internal/stats/render/common/v1/badges.go b/internal/stats/render/common/v1/badges.go index a674f8e7..3aa53484 100644 --- a/internal/stats/render/common/v1/badges.go +++ b/internal/stats/render/common/v1/badges.go @@ -1,7 +1,6 @@ package common import ( - "image/color" "slices" "github.com/pkg/errors" @@ -33,115 +32,6 @@ func (sub subscriptionHeader) Block() (Block, error) { return Block{}, errors.New("tier icon not found") } -var ( - subscriptionWeight = map[models.SubscriptionType]int{ - models.SubscriptionTypeDeveloper: 999, - // Moderators - models.SubscriptionTypeServerModerator: 99, - models.SubscriptionTypeContentModerator: 98, - // Paid - models.SubscriptionTypePro: 89, - models.SubscriptionTypeProClan: 88, - models.SubscriptionTypePlus: 79, - // - models.SubscriptionTypeSupporter: 29, - models.SubscriptionTypeServerBooster: 28, - // - models.SubscriptionTypeVerifiedClan: 19, - } - - // Personal - userSubscriptionSupporter = &subscriptionHeader{ - Name: "Supporter", - Icon: "images/icons/fire", - Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Height: 32}, - Icon: Style{Width: 16, Height: 16, BackgroundColor: TextSubscriptionPlus}, - Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 5}, - }, - } - userSubscriptionPlus = &subscriptionHeader{ - Name: "Aftermath+", - Icon: "images/icons/star", - Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, - Icon: Style{Width: 24, Height: 24, BackgroundColor: TextSubscriptionPlus}, - Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 5}, - }, - } - userSubscriptionPro = &subscriptionHeader{ - Name: "Aftermath Pro", - Icon: "images/icons/star", - Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, - Icon: Style{Width: 24, Height: 24, BackgroundColor: TextSubscriptionPremium}, - Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 5}, - }, - } - // Clans - clanSubscriptionVerified = &subscriptionHeader{ - Icon: "images/icons/verify", - Style: subscriptionPillStyle{ - Icon: Style{Width: 28, Height: 28, BackgroundColor: TextAlt}, - Container: Style{Direction: DirectionHorizontal}, - }, - } - clanSubscriptionPro = &subscriptionHeader{ - Icon: "images/icons/star-multiple", - Style: subscriptionPillStyle{ - Icon: Style{Width: 28, Height: 28, BackgroundColor: TextAlt}, - Container: Style{Direction: DirectionHorizontal}, - }, - } - - // Community - subscriptionDeveloper = &subscriptionHeader{ - Name: "Developer", - Icon: "images/icons/github", - Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: color.RGBA{64, 32, 128, 180}, BorderRadius: 15, PaddingX: 6, PaddingY: 5, Gap: 5, Height: 32}, - Icon: Style{Width: 20, Height: 20, BackgroundColor: TextPrimary}, - Text: Style{Font: FontSmall, FontColor: TextPrimary, PaddingX: 5}, - }, - } - subscriptionServerModerator = &subscriptionHeader{ - Name: "Community Moderator", - Icon: "images/icons/logo-128", - Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, - Icon: Style{Width: 20, Height: 20}, - Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 2}, - }, - } - subscriptionContentModerator = &subscriptionHeader{ - Name: "Moderator", - Icon: "images/icons/logo-128", - Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, - Icon: Style{Width: 20, Height: 20}, - Text: Style{Font: FontSmall, FontColor: TextSecondary, PaddingX: 2}, - }, - } - subscriptionServerBooster = &subscriptionHeader{ - Name: "Booster", - Icon: "images/icons/discord-booster", - Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, - Icon: Style{Width: 20, Height: 20}, - Text: Style{Font: FontSmall, FontColor: TextSecondary}, - }, - } - subscriptionTranslator = &subscriptionHeader{ - Name: "Translator", - Icon: "images/icons/translator", - Style: subscriptionPillStyle{ - Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, - Icon: Style{Width: 20, Height: 20, BackgroundColor: TextPrimary}, - Text: Style{Font: FontSmall, FontColor: TextSecondary}, - }, - } -) - func SubscriptionsBadges(subscriptions []models.UserSubscription) ([]Block, error) { slices.SortFunc(subscriptions, func(i, j models.UserSubscription) int { return subscriptionWeight[j.Type] - subscriptionWeight[i.Type] diff --git a/internal/stats/render/common/v1/block.go b/internal/stats/render/common/v1/block.go index 307ef9ba..86d07cb9 100644 --- a/internal/stats/render/common/v1/block.go +++ b/internal/stats/render/common/v1/block.go @@ -1,10 +1,14 @@ package common import ( + "context" + "fmt" "image" "strings" + "sync" "github.com/pkg/errors" + "github.com/rs/zerolog/log" "github.com/disintegration/imaging" "github.com/fogleman/gg" @@ -12,6 +16,10 @@ import ( type blockContentType int +func (t blockContentType) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%d", t)), nil +} + const ( BlockContentTypeText blockContentType = iota BlockContentTypeImage @@ -54,7 +62,7 @@ func NewTextContent(style Style, value string) Block { func (content contentText) Render(style Style) (image.Image, error) { if !style.Font.Valid() { - return nil, errors.New("font not set") + // return nil, errors.New("font not valid") } size := MeasureString(content.value, style.Font) @@ -94,6 +102,15 @@ func NewBlocksContent(style Style, blocks ...Block) Block { } func (content contentBlocks) Render(style Style) (image.Image, error) { + if len(content.blocks) < 1 { + return nil, errors.New("block content cannot be empty") + } + + // avoid the overhead of mutex and goroutines if it is not required + if len(content.blocks) > 1 { + return content.renderAsync(style) + } + var images []image.Image for _, block := range content.blocks { img, err := block.Render() @@ -105,6 +122,48 @@ func (content contentBlocks) Render(style Style) (image.Image, error) { return renderImages(images, style) } +func (content contentBlocks) renderAsync(style Style) (image.Image, error) { + var mx sync.Mutex + var wg sync.WaitGroup + images := make([]image.Image, len(content.blocks)) + + errCh := make(chan error) + ctx, cancel := context.WithCancel(context.Background()) + + for i, block := range content.blocks { + wg.Add(1) + go func(i int, b Block) { + defer wg.Done() + + img, err := b.Render() + if err != nil { + select { + case errCh <- err: + cancel() + default: + log.Err(err).Any("type", b.content.Type()).Msg("unhandled error while rendering a block") + } + return + } + mx.Lock() + images[i] = img + mx.Unlock() + }(i, block) + } + go func() { + wg.Wait() + cancel() + close(errCh) + }() + + select { + case <-ctx.Done(): + return renderImages(images, style) + case err := <-errCh: + return nil, err + } +} + func (content contentBlocks) Type() blockContentType { return BlockContentTypeBlocks } @@ -120,6 +179,10 @@ func NewImageContent(style Style, image image.Image) Block { } func (content contentImage) Render(style Style) (image.Image, error) { + if content.image == nil { + return nil, errors.New("image content cannot be nil") + } + if style.Width == 0 { style.Width = float64(content.image.Bounds().Dx()) } diff --git a/internal/stats/render/common/v1/constants.go b/internal/stats/render/common/v1/constants.go deleted file mode 100644 index bc1c3a25..00000000 --- a/internal/stats/render/common/v1/constants.go +++ /dev/null @@ -1,75 +0,0 @@ -package common - -import ( - "image/color" - - "github.com/golang/freetype/truetype" - "github.com/pkg/errors" - "golang.org/x/image/font" - - "github.com/cufee/aftermath/internal/stats/render/assets" -) - -var DiscordBackgroundColor = color.RGBA{49, 51, 56, 255} - -var ( - FontXL Font - Font2XL Font - FontLarge Font - FontMedium Font - FontSmall Font - - TextPrimary = color.RGBA{255, 255, 255, 255} - TextSecondary = color.RGBA{204, 204, 204, 255} - TextAlt = color.RGBA{150, 150, 150, 255} - - TextSubscriptionPlus = color.RGBA{72, 167, 250, 255} - TextSubscriptionPremium = color.RGBA{255, 223, 0, 255} - - DefaultCardColor = color.RGBA{10, 10, 10, 180} - DefaultCardColorNoAlpha = color.RGBA{10, 10, 10, 255} - - ColorAftermathRed = color.RGBA{255, 0, 120, 255} - ColorAftermathBlue = color.RGBA{90, 90, 255, 255} - - BorderRadiusLG = 25.0 - BorderRadiusMD = 20.0 - BorderRadiusSM = 15.0 - BorderRadiusXS = 10.0 -) - -type Font struct { - size float64 - data []byte -} - -func (f *Font) Valid() bool { - return f.data != nil && f.size > 0 -} - -func (f *Font) Face() font.Face { - ttf, _ := truetype.Parse(f.data) - return truetype.NewFace(ttf, &truetype.Options{ - Size: f.size, - }) -} - -// var fontCache map[float64]font.Face - -func InitLoadedAssets() error { - // var ok bool - // fontCache, ok = assets.GetLoadedFontFaces("default", 36, 32, 24, 18, 14) - fontData, ok := assets.GetLoadedFontFace("default") - if !ok { - return errors.New("default font not found") - } - - FontXL = Font{32, fontData} - Font2XL = Font{36, fontData} - - FontLarge = Font{24, fontData} - FontMedium = Font{18, fontData} - FontSmall = Font{14, fontData} - - return nil -} diff --git a/internal/stats/render/common/v1/font.go b/internal/stats/render/common/v1/font.go new file mode 100644 index 00000000..6d548c4b --- /dev/null +++ b/internal/stats/render/common/v1/font.go @@ -0,0 +1,26 @@ +package common + +import ( + "github.com/golang/freetype/truetype" + "golang.org/x/image/font" +) + +type Font struct { + size float64 + data []byte +} + +func (f *Font) Size() float64 { + return f.size +} + +func (f *Font) Valid() bool { + return f.data != nil && f.size > 0 +} + +func (f *Font) Face() font.Face { + ttf, _ := truetype.Parse(f.data) + return truetype.NewFace(ttf, &truetype.Options{ + Size: f.size, + }) +} diff --git a/internal/stats/render/common/v1/footer.go b/internal/stats/render/common/v1/footer.go index 16cff210..0bf10acf 100644 --- a/internal/stats/render/common/v1/footer.go +++ b/internal/stats/render/common/v1/footer.go @@ -12,5 +12,5 @@ func NewFooterCard(text string) Block { BackgroundColor: backgroundColor, BorderRadius: BorderRadiusSM, // Debug: true, - }, NewTextContent(Style{Font: FontSmall, FontColor: TextSecondary}, text)) + }, NewTextContent(Style{Font: FontSmall(), FontColor: TextSecondary}, text)) } diff --git a/internal/stats/render/common/v1/header.go b/internal/stats/render/common/v1/header.go index fc599a61..fb891b27 100644 --- a/internal/stats/render/common/v1/header.go +++ b/internal/stats/render/common/v1/header.go @@ -22,7 +22,7 @@ func NewHeaderCard(width float64, subscriptions []models.UserSubscription, promo // Users without a subscription get promo text var textBlocks []Block for _, text := range promoText { - textBlocks = append(textBlocks, NewTextContent(Style{Font: FontMedium, FontColor: TextPrimary}, text)) + textBlocks = append(textBlocks, NewTextContent(Style{Font: FontMedium(), FontColor: TextPrimary}, text)) } cards = append(cards, NewBlocksContent(Style{ Direction: DirectionVertical, diff --git a/internal/stats/render/common/v1/images.go b/internal/stats/render/common/v1/images.go index 4074fb5e..c28f1ef7 100644 --- a/internal/stats/render/common/v1/images.go +++ b/internal/stats/render/common/v1/images.go @@ -34,7 +34,7 @@ func AddBackground(content, background image.Image, style Style) image.Image { } func renderImages(images []image.Image, style Style) (image.Image, error) { - if len(images) == 0 { + if len(images) < 1 { return nil, errors.New("no images to render") } diff --git a/internal/stats/render/common/v1/init.go b/internal/stats/render/common/v1/init.go new file mode 100644 index 00000000..3d14784c --- /dev/null +++ b/internal/stats/render/common/v1/init.go @@ -0,0 +1,167 @@ +package common + +import ( + "image/color" + + "github.com/pkg/errors" + + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/render/assets" +) + +var DiscordBackgroundColor = color.RGBA{49, 51, 56, 255} + +var ( + FontXL func() Font + Font2XL func() Font + FontLarge func() Font + FontMedium func() Font + FontSmall func() Font + + TextPrimary = color.RGBA{255, 255, 255, 255} + TextSecondary = color.RGBA{204, 204, 204, 255} + TextAlt = color.RGBA{150, 150, 150, 255} + + TextSubscriptionPlus = color.RGBA{72, 167, 250, 255} + TextSubscriptionPremium = color.RGBA{255, 223, 0, 255} + + DefaultCardColor = color.RGBA{10, 10, 10, 180} + DefaultCardColorNoAlpha = color.RGBA{10, 10, 10, 255} + + ColorAftermathRed = color.RGBA{255, 0, 120, 255} + ColorAftermathBlue = color.RGBA{90, 90, 255, 255} + + BorderRadiusLG = 25.0 + BorderRadiusMD = 20.0 + BorderRadiusSM = 15.0 + BorderRadiusXS = 10.0 +) + +var ( + subscriptionWeight = map[models.SubscriptionType]int{ + models.SubscriptionTypeDeveloper: 999, + // Moderators + models.SubscriptionTypeServerModerator: 99, + models.SubscriptionTypeContentModerator: 98, + // Paid + models.SubscriptionTypePro: 89, + models.SubscriptionTypeProClan: 88, + models.SubscriptionTypePlus: 79, + // + models.SubscriptionTypeSupporter: 29, + models.SubscriptionTypeServerBooster: 28, + // + models.SubscriptionTypeVerifiedClan: 19, + } +) + +var ( + userSubscriptionSupporter, userSubscriptionPlus, userSubscriptionPro, clanSubscriptionVerified, clanSubscriptionPro, subscriptionDeveloper, subscriptionServerModerator, subscriptionContentModerator, subscriptionServerBooster, subscriptionTranslator *subscriptionHeader +) + +func InitLoadedAssets() error { + fontData, ok := assets.GetLoadedFontFace("default") + if !ok { + return errors.New("default font not found") + } + + FontXL = func() Font { return Font{size: 32, data: fontData} } + Font2XL = func() Font { return Font{size: 36, data: fontData} } + + FontLarge = func() Font { return Font{size: 24, data: fontData} } + FontMedium = func() Font { return Font{size: 18, data: fontData} } + FontSmall = func() Font { return Font{size: 14, data: fontData} } + + // Personal + userSubscriptionSupporter = &subscriptionHeader{ + Name: "Supporter", + Icon: "images/icons/fire", + Style: subscriptionPillStyle{ + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Height: 32}, + Icon: Style{Width: 16, Height: 16, BackgroundColor: TextSubscriptionPlus}, + Text: Style{Font: FontSmall(), FontColor: TextSecondary, PaddingX: 5}, + }, + } + userSubscriptionPlus = &subscriptionHeader{ + Name: "Aftermath+", + Icon: "images/icons/star", + Style: subscriptionPillStyle{ + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, + Icon: Style{Width: 24, Height: 24, BackgroundColor: TextSubscriptionPlus}, + Text: Style{Font: FontSmall(), FontColor: TextSecondary, PaddingX: 5}, + }, + } + userSubscriptionPro = &subscriptionHeader{ + Name: "Aftermath Pro", + Icon: "images/icons/star", + Style: subscriptionPillStyle{ + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 5, PaddingY: 5, Height: 32}, + Icon: Style{Width: 24, Height: 24, BackgroundColor: TextSubscriptionPremium}, + Text: Style{Font: FontSmall(), FontColor: TextSecondary, PaddingX: 5}, + }, + } + // Clans + clanSubscriptionVerified = &subscriptionHeader{ + Icon: "images/icons/verify", + Style: subscriptionPillStyle{ + Icon: Style{Width: 28, Height: 28, BackgroundColor: TextAlt}, + Container: Style{Direction: DirectionHorizontal}, + }, + } + clanSubscriptionPro = &subscriptionHeader{ + Icon: "images/icons/star-multiple", + Style: subscriptionPillStyle{ + Icon: Style{Width: 28, Height: 28, BackgroundColor: TextAlt}, + Container: Style{Direction: DirectionHorizontal}, + }, + } + + // Community + subscriptionDeveloper = &subscriptionHeader{ + Name: "Developer", + Icon: "images/icons/github", + Style: subscriptionPillStyle{ + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: color.RGBA{64, 32, 128, 180}, BorderRadius: 15, PaddingX: 6, PaddingY: 5, Gap: 5, Height: 32}, + Icon: Style{Width: 20, Height: 20, BackgroundColor: TextPrimary}, + Text: Style{Font: FontSmall(), FontColor: TextPrimary, PaddingX: 5}, + }, + } + subscriptionServerModerator = &subscriptionHeader{ + Name: "Community Moderator", + Icon: "images/icons/logo-128", + Style: subscriptionPillStyle{ + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, + Icon: Style{Width: 20, Height: 20}, + Text: Style{Font: FontSmall(), FontColor: TextSecondary, PaddingX: 2}, + }, + } + subscriptionContentModerator = &subscriptionHeader{ + Name: "Moderator", + Icon: "images/icons/logo-128", + Style: subscriptionPillStyle{ + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 7, PaddingY: 5, Gap: 5, Height: 32}, + Icon: Style{Width: 20, Height: 20}, + Text: Style{Font: FontSmall(), FontColor: TextSecondary, PaddingX: 2}, + }, + } + subscriptionServerBooster = &subscriptionHeader{ + Name: "Booster", + Icon: "images/icons/discord-booster", + Style: subscriptionPillStyle{ + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, + Icon: Style{Width: 20, Height: 20}, + Text: Style{Font: FontSmall(), FontColor: TextSecondary}, + }, + } + subscriptionTranslator = &subscriptionHeader{ + Name: "Translator", + Icon: "images/icons/translator", + Style: subscriptionPillStyle{ + Container: Style{Direction: DirectionHorizontal, AlignItems: AlignItemsCenter, BackgroundColor: DefaultCardColorNoAlpha, BorderRadius: 15, PaddingX: 10, PaddingY: 5, Gap: 5, Height: 32}, + Icon: Style{Width: 20, Height: 20, BackgroundColor: TextPrimary}, + Text: Style{Font: FontSmall(), FontColor: TextSecondary}, + }, + } + + return nil +} diff --git a/internal/stats/render/common/v1/player_title.go b/internal/stats/render/common/v1/player_title.go index eceb3f6f..22601331 100644 --- a/internal/stats/render/common/v1/player_title.go +++ b/internal/stats/render/common/v1/player_title.go @@ -29,8 +29,8 @@ func DefaultPlayerTitleStyle(containerStyle Style) TitleCardStyle { return TitleCardStyle{ Container: containerStyle, - Nickname: Style{Font: FontLarge, FontColor: TextPrimary}, - ClanTag: Style{Font: FontMedium, FontColor: TextSecondary, PaddingX: 10, PaddingY: 5, BackgroundColor: clanTagBackgroundColor, BorderRadius: BorderRadiusSM}, + Nickname: Style{Font: FontLarge(), FontColor: TextPrimary}, + ClanTag: Style{Font: FontMedium(), FontColor: TextSecondary, PaddingX: 10, PaddingY: 5, BackgroundColor: clanTagBackgroundColor, BorderRadius: BorderRadiusSM}, } } @@ -71,7 +71,7 @@ func newClanTagBlock(style Style, clanTag string, subs []models.UserSubscription } var blocks []Block - blocks = append(blocks, NewTextContent(Style{Font: FontMedium, FontColor: TextSecondary}, clanTag)) + blocks = append(blocks, NewTextContent(Style{Font: FontMedium(), FontColor: TextSecondary}, clanTag)) if sub := ClanSubscriptionsBadges(subs); sub != nil { iconBlock, err := sub.Block() if err == nil { diff --git a/internal/stats/render/common/v1/tier_percentage.go b/internal/stats/render/common/v1/tier_percentage.go index 2a6eabaf..96f19f1a 100644 --- a/internal/stats/render/common/v1/tier_percentage.go +++ b/internal/stats/render/common/v1/tier_percentage.go @@ -25,7 +25,7 @@ func NewTierPercentageCard(style Style, vehicles map[string]frame.VehicleStatsFr BackgroundColor: shade, Width: style.Width / float64(elements), JustifyContent: JustifyContentCenter, - }, NewTextContent(Style{Font: FontMedium, FontColor: TextPrimary}, fmt.Sprint(i)))) + }, NewTextContent(Style{Font: FontMedium(), FontColor: TextPrimary}, fmt.Sprint(i)))) } return NewBlocksContent(style, blocks...) diff --git a/internal/stats/render/period/v1/constants.go b/internal/stats/render/period/v1/constants.go index 4534c18e..1418e0a3 100644 --- a/internal/stats/render/period/v1/constants.go +++ b/internal/stats/render/period/v1/constants.go @@ -22,11 +22,11 @@ type highlightStyle struct { func (s *overviewStyle) block(block prepare.StatsBlock[period.BlockData]) (common.Style, common.Style) { switch block.Data.Flavor { case period.BlockFlavorSpecial: - return common.Style{FontColor: common.TextPrimary, Font: common.FontXL}, common.Style{FontColor: common.TextAlt, Font: common.FontSmall} + return common.Style{FontColor: common.TextPrimary, Font: common.FontXL()}, common.Style{FontColor: common.TextAlt, Font: common.FontSmall()} case period.BlockFlavorSecondary: - return common.Style{FontColor: common.TextSecondary, Font: common.FontMedium}, common.Style{FontColor: common.TextAlt, Font: common.FontSmall} + return common.Style{FontColor: common.TextSecondary, Font: common.FontMedium()}, common.Style{FontColor: common.TextAlt, Font: common.FontSmall()} default: - return common.Style{FontColor: common.TextPrimary, Font: common.FontLarge}, common.Style{FontColor: common.TextAlt, Font: common.FontSmall} + return common.Style{FontColor: common.TextPrimary, Font: common.FontLarge()}, common.Style{FontColor: common.TextAlt, Font: common.FontSmall()} } } @@ -92,9 +92,9 @@ func highlightCardStyle(containerStyle common.Style) highlightStyle { return highlightStyle{ container: container, - cardTitle: common.Style{Font: common.FontSmall, FontColor: common.TextSecondary}, - tankName: common.Style{Font: common.FontMedium, FontColor: common.TextPrimary}, - blockValue: common.Style{Font: common.FontMedium, FontColor: common.TextPrimary}, - blockLabel: common.Style{Font: common.FontSmall, FontColor: common.TextAlt}, + cardTitle: common.Style{Font: common.FontSmall(), FontColor: common.TextSecondary}, + tankName: common.Style{Font: common.FontMedium(), FontColor: common.TextPrimary}, + blockValue: common.Style{Font: common.FontMedium(), FontColor: common.TextPrimary}, + blockLabel: common.Style{Font: common.FontSmall(), FontColor: common.TextAlt}, } } diff --git a/internal/stats/render/session/v1/blocks.go b/internal/stats/render/session/v1/blocks.go index 6dba350c..64220f45 100644 --- a/internal/stats/render/session/v1/blocks.go +++ b/internal/stats/render/session/v1/blocks.go @@ -13,6 +13,7 @@ import ( ) func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width float64) common.Block { + blockStyle := vehicleBlockStyle() switch block.Tag { case prepare.TagWN8: ratingColors := common.GetWN8Colors(block.Value.Float()) @@ -30,13 +31,13 @@ func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width pillColor = color.Transparent } column = append(column, common.NewBlocksContent(overviewColumnStyle(width), - common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), + common.NewTextContent(blockStyle.session, block.Data.Session.String()), common.NewBlocksContent( overviewSpecialRatingPillStyle(pillColor), common.NewTextContent(overviewSpecialRatingLabelStyle(ratingColors.Content), common.GetWN8TierName(block.Value.Float())), ), )) - return common.NewBlocksContent(specialRatingColumnStyle, column...) + return common.NewBlocksContent(specialRatingColumnStyle(), column...) case prepare.TagRankedRating: var column []common.Block @@ -45,15 +46,14 @@ func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width column = append(column, icon) } column = append(column, common.NewBlocksContent(overviewColumnStyle(width), - blockWithDoubleVehicleIcon(common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), block.Data.Session, block.Data.Career), + blockWithDoubleVehicleIcon(common.NewTextContent(blockStyle.session, block.Data.Session.String()), block.Data.Session, block.Data.Career), )) - return common.NewBlocksContent(specialRatingColumnStyle, column...) + return common.NewBlocksContent(specialRatingColumnStyle(), column...) default: return common.NewBlocksContent(statsBlockStyle(width), - common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), - // common.NewTextContent(vehicleBlockStyle.career, block.Data.Career.String()), - common.NewTextContent(vehicleBlockStyle.label, block.Label), + common.NewTextContent(blockStyle.session, block.Data.Session.String()), + common.NewTextContent(blockStyle.label, block.Label), ) } } diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index ff530a8e..9e1a344a 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -61,7 +61,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } { for _, text := range opts.PromoText { - size := common.MeasureString(text, promoTextStyle.Font) + size := common.MeasureString(text, promoTextStyle().Font) totalFrameWidth = common.Max(size.TotalWidth, totalFrameWidth) } } @@ -69,7 +69,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card if shouldRenderUnratedOverview { var totalContentWidth float64 = overviewCardStyle(0).Gap * float64(len(cards.Unrated.Overview.Blocks)-1) for _, column := range cards.Unrated.Overview.Blocks { - styleWithIconOffset := overviewStatsBlockStyle + styleWithIconOffset := overviewStatsBlockStyle() styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) @@ -84,7 +84,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card if shouldRenderRatingOverview { var totalContentWidth float64 = overviewCardStyle(0).Gap * float64(len(cards.Rating.Overview.Blocks)-1) for _, column := range cards.Unrated.Overview.Blocks { - styleWithIconOffset := overviewStatsBlockStyle + styleWithIconOffset := overviewStatsBlockStyle() styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) @@ -99,8 +99,9 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card if shouldRenderRatingVehicles { for _, card := range cards.Rating.Vehicles { // [title] [session] - titleSize := common.MeasureString(card.Title, ratingVehicleCardTitleStyle.Font) - presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, ratingVehicleBlockStyle.session, ratingVehicleBlockStyle.career, ratingVehicleBlockStyle.label, ratingVehicleBlocksRowStyle(0)) + style := ratingVehicleBlockStyle() + titleSize := common.MeasureString(card.Title, ratingVehicleCardTitleStyle().Font) + presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, style.session, style.career, style.label, ratingVehicleBlocksRowStyle(0)) // add the gap and card padding, the gap here accounts for title being inline with content contentWidth += ratingVehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)) + ratingVehicleCardStyle(0).PaddingX*2 + titleSize.TotalWidth @@ -115,10 +116,12 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card for _, card := range cards.Unrated.Highlights { // [card label] [session] // [title] [label] - labelSize := common.MeasureString(card.Meta, highlightCardTitleTextStyle.Font) - titleSize := common.MeasureString(card.Title, highlightVehicleNameTextStyle.Font) + titleStyle := highlightCardTitleTextStyle() + labelSize := common.MeasureString(card.Meta, titleStyle.Font) + titleSize := common.MeasureString(card.Title, titleStyle.Font) - presetBlockWidth, contentWidth := highlightedVehicleBlocksWidth(card.Blocks, vehicleBlockStyle.session, vehicleBlockStyle.career, vehicleBlockStyle.label, highlightedVehicleBlockRowStyle(0)) + style := vehicleBlockStyle() + presetBlockWidth, contentWidth := highlightedVehicleBlocksWidth(card.Blocks, style.session, style.career, style.label, highlightedVehicleBlockRowStyle(0)) // add the gap and card padding, the gap here accounts for title/label being inline with content contentWidth += highlightedVehicleBlockRowStyle(0).Gap*float64(len(card.Blocks)) + highlightedVehicleCardStyle(0).Gap + highlightedVehicleCardStyle(0).PaddingX*2 + common.Max(titleSize.TotalWidth, labelSize.TotalWidth) primaryCardWidth = common.Max(primaryCardWidth, contentWidth) @@ -134,7 +137,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // [session] // [career ] - styleWithIconOffset := vehicleBlockStyle + styleWithIconOffset := vehicleBlockStyle() // icon is only on one side, so we divide by 2 styleWithIconOffset.label.PaddingX += vehicleComparisonIconSize / 2 styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize / 2 @@ -142,7 +145,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, vehicleBlocksRowStyle(0)) contentWidth += vehicleBlocksRowStyle(0).Gap*float64(len(card.Blocks)-1) + vehicleCardStyle(0).PaddingX*2 - titleSize := common.MeasureString(card.Title, vehicleCardTitleTextStyle.Font) + titleSize := common.MeasureString(card.Title, vehicleCardTitleTextStyle().Font) secondaryCardWidth = common.Max(secondaryCardWidth, contentWidth, titleSize.TotalWidth+vehicleCardStyle(0).PaddingX*2) for key, width := range presetBlockWidth { @@ -188,20 +191,24 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } // player title + println("making player title") primaryColumn = append(primaryColumn, common.NewPlayerTitleCard(common.DefaultPlayerTitleStyle(playerNameCardStyle(primaryCardWidth)), session.Account.Nickname, session.Account.ClanTag, subs), ) // overview cards if shouldRenderUnratedOverview { + println("making unrated overview") primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, overviewCardStyle(primaryCardWidth))) } if shouldRenderRatingOverview { + println("making rating overview") primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, overviewRatingCardStyle(primaryCardWidth))) } // highlights if shouldRenderUnratedHighlights { + println("making highlights") for _, vehicle := range cards.Unrated.Highlights { primaryColumn = append(primaryColumn, makeVehicleHighlightCard(vehicle, highlightCardBlockSizes, primaryCardWidth)) } @@ -213,6 +220,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // unrated vehicles if shouldRenderUnratedVehicles { + println("making unrated vehicles") for i, vehicle := range cards.Unrated.Vehicles { if i >= renderVehiclesCount { break @@ -315,16 +323,17 @@ func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, var vehicleWN8 frame.Value = frame.InvalidValue var content []common.Block for _, block := range vehicle.Blocks { + style := vehicleBlockStyle() var blockContent []common.Block if blockShouldHaveCompareIcon(block) { - blockContent = append(blockContent, blockWithVehicleIcon(common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), block.Data.Session, block.Data.Career)) + blockContent = append(blockContent, blockWithVehicleIcon(common.NewTextContent(style.session, block.Data.Session.String()), block.Data.Session, block.Data.Career)) } else { - blockContent = append(blockContent, common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String())) + blockContent = append(blockContent, common.NewTextContent(style.session, block.Data.Session.String())) } - style := statsBlockStyle(blockSizes[block.Tag.String()]) + containerStyle := statsBlockStyle(blockSizes[block.Tag.String()]) content = append(content, - common.NewBlocksContent(style, blockContent...), + common.NewBlocksContent(containerStyle, blockContent...), ) if block.Tag == prepare.TagWN8 { @@ -336,7 +345,7 @@ func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, titleStyle.Width -= vehicleCardStyle(0).PaddingX * 2 return common.NewBlocksContent(vehicleCardStyle(cardWidth), common.NewBlocksContent(titleStyle, - common.NewTextContent(vehicleCardTitleTextStyle, fmt.Sprintf("%s %s", vehicle.Meta, vehicle.Title)), // name and tier + common.NewTextContent(vehicleCardTitleTextStyle(), fmt.Sprintf("%s %s", vehicle.Meta, vehicle.Title)), // name and tier vehicleWN8Icon(vehicleWN8), ), common.NewBlocksContent(vehicleBlocksRowStyle(0), content...), @@ -345,19 +354,20 @@ func makeVehicleCard(vehicle session.VehicleCard, blockSizes map[string]float64, func makeVehicleHighlightCard(vehicle session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { var content []common.Block + style := vehicleBlockStyle() for _, block := range vehicle.Blocks { content = append(content, common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), - common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), - common.NewTextContent(vehicleBlockStyle.label, block.Label), + common.NewTextContent(style.session, block.Data.Session.String()), + common.NewTextContent(style.label, block.Label), ), ) } return common.NewBlocksContent(highlightedVehicleCardStyle(cardWidth), common.NewBlocksContent(common.Style{Direction: common.DirectionVertical}, - common.NewTextContent(highlightCardTitleTextStyle, vehicle.Meta), - common.NewTextContent(highlightVehicleNameTextStyle, vehicle.Title), + common.NewTextContent(highlightCardTitleTextStyle(), vehicle.Meta), + common.NewTextContent(highlightVehicleNameTextStyle(), vehicle.Title), ), common.NewBlocksContent(highlightedVehicleBlockRowStyle(0), content...), ) @@ -365,42 +375,45 @@ func makeVehicleHighlightCard(vehicle session.VehicleCard, blockSizes map[string func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string]float64, cardWidth float64) common.Block { var content []common.Block + style := vehicleBlockStyle() for _, block := range reference.Blocks { - label := common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label)) + label := common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(style.label, block.Label)) if blockShouldHaveCompareIcon(block) { - label = blockWithVehicleIcon(common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(vehicleBlockStyle.label, block.Label)), frame.InvalidValue, frame.InvalidValue) + label = blockWithVehicleIcon(common.NewBlocksContent(vehicleLegendLabelContainer, common.NewTextContent(style.label, block.Label)), frame.InvalidValue, frame.InvalidValue) } containerStyle := statsBlockStyle(blockSizes[block.Tag.String()]) content = append(content, common.NewBlocksContent(containerStyle, label), ) } - style := vehicleCardStyle(cardWidth) - style.BackgroundColor = nil - style.PaddingY = 0 - style.PaddingX = 0 - return common.NewBlocksContent(style, common.NewBlocksContent(vehicleBlocksRowStyle(0), content...)) + containerStyle := vehicleCardStyle(cardWidth) + containerStyle.BackgroundColor = nil + containerStyle.PaddingY = 0 + containerStyle.PaddingX = 0 + return common.NewBlocksContent(containerStyle, common.NewBlocksContent(vehicleBlocksRowStyle(0), content...)) } func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, style common.Style) common.Block { // made all columns the same width for things to be centered columnWidth := (style.Width - style.Gap*float64(len(card.Blocks)-1) - style.PaddingX*2) / float64(len(card.Blocks)) var content []common.Block // add a blank block to balance the offset added from icons + blockStyle := vehicleBlockStyle() for _, column := range card.Blocks { var columnContent []common.Block for _, block := range column { + println("making block", block.Label) var col common.Block if block.Tag == prepare.TagWN8 || block.Tag == prepare.TagRankedRating { col = makeSpecialRatingColumn(block, blockSizes[block.Tag.String()]) } else if blockShouldHaveCompareIcon(block) { col = common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), - blockWithDoubleVehicleIcon(common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), block.Data.Session, block.Data.Career), - common.NewTextContent(vehicleBlockStyle.label, block.Label), + blockWithDoubleVehicleIcon(common.NewTextContent(blockStyle.session, block.Data.Session.String()), block.Data.Session, block.Data.Career), + common.NewTextContent(blockStyle.label, block.Label), ) } else { col = common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), - common.NewTextContent(vehicleBlockStyle.session, block.Data.Session.String()), - common.NewTextContent(vehicleBlockStyle.label, block.Label), + common.NewTextContent(blockStyle.session, block.Data.Session.String()), + common.NewTextContent(blockStyle.label, block.Label), ) } columnContent = append(columnContent, col) diff --git a/internal/stats/render/session/v1/constants.go b/internal/stats/render/session/v1/constants.go index 8215b2e9..bdbfd3e1 100644 --- a/internal/stats/render/session/v1/constants.go +++ b/internal/stats/render/session/v1/constants.go @@ -19,25 +19,27 @@ var ( minPrimaryCardWidth = 300.0 // making the primary card too small looks bad if there are no battles in a session ) -var ( - specialRatingColumnStyle = common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, Gap: 5} - promoTextStyle = common.Style{Font: common.FontMedium, FontColor: common.TextPrimary} -) +func specialRatingColumnStyle() common.Style { + return common.Style{Direction: common.DirectionVertical, AlignItems: common.AlignItemsCenter, Gap: 5} +} +func promoTextStyle() common.Style { + return common.Style{Font: common.FontMedium(), FontColor: common.TextPrimary} +} func frameStyle() common.Style { return common.Style{Gap: 10, Direction: common.DirectionHorizontal} } -var ( - overviewStatsBlockStyle = blockStyle{ - common.Style{Font: common.FontLarge, FontColor: common.TextPrimary}, - common.Style{Font: common.FontMedium, FontColor: common.TextSecondary}, - common.Style{Font: common.FontSmall, FontColor: common.TextAlt}, +func overviewStatsBlockStyle() blockStyle { + return blockStyle{ + common.Style{Font: common.FontLarge(), FontColor: common.TextPrimary}, + common.Style{Font: common.FontMedium(), FontColor: common.TextSecondary}, + common.Style{Font: common.FontSmall(), FontColor: common.TextAlt}, } -) +} func overviewSpecialRatingLabelStyle(color color.Color) common.Style { - return common.Style{FontColor: color, Font: common.FontSmall} + return common.Style{FontColor: color, Font: common.FontSmall()} } func overviewSpecialRatingPillStyle(color color.Color) common.Style { @@ -91,14 +93,19 @@ var ( PaddingY: 5, PaddingX: 10, } - vehicleCardTitleTextStyle = common.Style{Font: common.FontMedium, FontColor: common.TextAlt} - vehicleBlockStyle = blockStyle{ - common.Style{Font: common.FontLarge, FontColor: common.TextPrimary}, - common.Style{Font: common.FontMedium, FontColor: common.TextSecondary}, - common.Style{Font: common.FontSmall, FontColor: common.TextAlt}, - } ) +func vehicleCardTitleTextStyle() common.Style { + return common.Style{Font: common.FontMedium(), FontColor: common.TextAlt} +} +func vehicleBlockStyle() blockStyle { + return blockStyle{ + common.Style{Font: common.FontLarge(), FontColor: common.TextPrimary}, + common.Style{Font: common.FontMedium(), FontColor: common.TextSecondary}, + common.Style{Font: common.FontSmall(), FontColor: common.TextAlt}, + } +} + func vehicleCardTitleContainerStyle(width float64) common.Style { return common.Style{ JustifyContent: common.JustifyContentSpaceBetween, @@ -130,15 +137,19 @@ func vehicleBlocksRowStyle(width float64) common.Style { } } -var ( - ratingVehicleCardTitleContainerStyle = common.Style{Direction: common.DirectionHorizontal, Gap: 10, JustifyContent: common.JustifyContentSpaceBetween} - ratingVehicleCardTitleStyle = common.Style{Font: common.FontMedium, FontColor: common.TextSecondary, PaddingX: 5} - ratingVehicleBlockStyle = blockStyle{ - common.Style{Font: common.FontLarge, FontColor: common.TextPrimary}, - common.Style{Font: common.FontMedium, FontColor: common.TextSecondary}, - common.Style{Font: common.FontSmall, FontColor: common.TextAlt}, +func ratingVehicleCardTitleContainerStyle() common.Style { + return common.Style{Direction: common.DirectionHorizontal, Gap: 10, JustifyContent: common.JustifyContentSpaceBetween} +} +func ratingVehicleCardTitleStyle() common.Style { + return common.Style{Font: common.FontMedium(), FontColor: common.TextSecondary, PaddingX: 5} +} +func ratingVehicleBlockStyle() blockStyle { + return blockStyle{ + common.Style{Font: common.FontLarge(), FontColor: common.TextPrimary}, + common.Style{Font: common.FontMedium(), FontColor: common.TextSecondary}, + common.Style{Font: common.FontSmall(), FontColor: common.TextAlt}, } -) +} func ratingVehicleCardStyle(width float64) common.Style { return defaultCardStyle(width) @@ -148,10 +159,12 @@ func ratingVehicleBlocksRowStyle(width float64) common.Style { return vehicleBlocksRowStyle(width) } -var ( - highlightCardTitleTextStyle = common.Style{Font: common.FontSmall, FontColor: common.TextSecondary} - highlightVehicleNameTextStyle = common.Style{Font: common.FontMedium, FontColor: common.TextPrimary} -) +func highlightCardTitleTextStyle() common.Style { + return common.Style{Font: common.FontSmall(), FontColor: common.TextSecondary} +} +func highlightVehicleNameTextStyle() common.Style { + return common.Style{Font: common.FontMedium(), FontColor: common.TextPrimary} +} func highlightedVehicleCardStyle(width float64) common.Style { style := defaultCardStyle(width) From 7c5c1850726cedd7b01411731866469263552c6c Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 14:32:28 -0400 Subject: [PATCH 181/341] fixed width calculations --- internal/stats/render/session/v1/cards.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index 9e1a344a..14348137 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -116,14 +116,13 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card for _, card := range cards.Unrated.Highlights { // [card label] [session] // [title] [label] - titleStyle := highlightCardTitleTextStyle() - labelSize := common.MeasureString(card.Meta, titleStyle.Font) - titleSize := common.MeasureString(card.Title, titleStyle.Font) + labelSize := common.MeasureString(card.Meta, highlightCardTitleTextStyle().Font) + titleSize := common.MeasureString(card.Title, highlightVehicleNameTextStyle().Font) style := vehicleBlockStyle() presetBlockWidth, contentWidth := highlightedVehicleBlocksWidth(card.Blocks, style.session, style.career, style.label, highlightedVehicleBlockRowStyle(0)) // add the gap and card padding, the gap here accounts for title/label being inline with content - contentWidth += highlightedVehicleBlockRowStyle(0).Gap*float64(len(card.Blocks)) + highlightedVehicleCardStyle(0).Gap + highlightedVehicleCardStyle(0).PaddingX*2 + common.Max(titleSize.TotalWidth, labelSize.TotalWidth) + contentWidth += highlightedVehicleBlockRowStyle(0).Gap*float64(len(card.Blocks)-1) + highlightedVehicleCardStyle(0).Gap + highlightedVehicleCardStyle(0).PaddingX*2 + common.Max(titleSize.TotalWidth, labelSize.TotalWidth) primaryCardWidth = common.Max(primaryCardWidth, contentWidth) for key, width := range presetBlockWidth { @@ -226,9 +225,9 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card break } secondaryColumn = append(secondaryColumn, makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth)) - if i == len(cards.Unrated.Vehicles)-1 { - secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) - } + } + if len(cards.Unrated.Vehicles) > 0 { + secondaryColumn = append(secondaryColumn, makeVehicleLegendCard(cards.Unrated.Vehicles[0], secondaryCardBlockSizes, secondaryCardWidth)) } } From 869a0311eb6bddec9634a1d63692389902d04572 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 14:42:49 -0400 Subject: [PATCH 182/341] cleaner card numbers --- internal/stats/render/session/v1/cards.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index 14348137..d0d59417 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -14,12 +14,12 @@ import ( func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Cards, subs []models.UserSubscription, opts common.Options) (common.Segments, error) { var ( - renderVehiclesCount = 3 // minimum number of vehicle cards + renderUnratedVehiclesCount = 3 // minimum number of vehicle cards // primary cards // when there are some unrated battles or no battles at all shouldRenderUnratedOverview = session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 // when there are 3 vehicle cards and no rating overview cards or there are 6 vehicle cards and some rating battles - shouldRenderUnratedHighlights = (session.RegularBattles.Battles > 0 && session.RatingBattles.Battles < 1 && len(cards.Unrated.Vehicles) > renderVehiclesCount) || + shouldRenderUnratedHighlights = (session.RegularBattles.Battles > 0 && session.RatingBattles.Battles < 1 && len(cards.Unrated.Vehicles) > renderUnratedVehiclesCount) || (session.RegularBattles.Battles > 0 && len(cards.Unrated.Vehicles) > 6) shouldRenderRatingOverview = session.RatingBattles.Battles > 0 shouldRenderRatingVehicles = len(cards.Unrated.Vehicles) == 0 @@ -28,14 +28,14 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card ) // try to make the columns height roughly similar to primary column - if shouldRenderUnratedHighlights && len(cards.Unrated.Highlights) > 1 { - renderVehiclesCount += len(cards.Unrated.Highlights) + if shouldRenderUnratedHighlights { + renderUnratedVehiclesCount += len(cards.Unrated.Highlights) } if shouldRenderRatingOverview { - renderVehiclesCount += 2 + renderUnratedVehiclesCount += 1 } if shouldRenderRatingVehicles { - renderVehiclesCount += len(cards.Rating.Vehicles) + renderUnratedVehiclesCount += len(cards.Rating.Vehicles) } var segments common.Segments @@ -221,7 +221,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card if shouldRenderUnratedVehicles { println("making unrated vehicles") for i, vehicle := range cards.Unrated.Vehicles { - if i >= renderVehiclesCount { + if i >= renderUnratedVehiclesCount { break } secondaryColumn = append(secondaryColumn, makeVehicleCard(vehicle, secondaryCardBlockSizes, secondaryCardWidth)) From 9cb21867823ef6cb71eaaa4e1d39ab0d76f3597b Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 18:06:34 -0400 Subject: [PATCH 183/341] added refresh buttons --- cmds/core/tasks/cleanup.go | 5 + cmds/discord/commands/builder/command.go | 3 +- cmds/discord/commands/common.go | 24 + cmds/discord/commands/interactions.go | 107 ++ cmds/discord/commands/link.go | 6 +- cmds/discord/commands/manage.go | 50 +- cmds/discord/commands/options.go | 2 +- cmds/discord/commands/session.go | 42 +- cmds/discord/commands/stats.go | 38 +- cmds/discord/common/context.go | 29 +- cmds/discord/common/reply.go | 74 ++ cmds/discord/discord.go | 1 + cmds/discord/middleware/middleware.go | 2 +- cmds/discord/router/handler.go | 13 +- go.sum | 10 + .../{discord.go => application_commands.go} | 0 internal/database/cleanup.go | 9 + internal/database/client.go | 4 + internal/database/discord_interactions.go | 58 ++ internal/database/ent/db/client.go | 195 +++- .../database/ent/db/discordinteraction.go | 215 ++++ .../discordinteraction/discordinteraction.go | 152 +++ .../ent/db/discordinteraction/where.go | 503 +++++++++ .../ent/db/discordinteraction_create.go | 370 +++++++ .../ent/db/discordinteraction_delete.go | 88 ++ .../ent/db/discordinteraction_query.go | 605 +++++++++++ .../ent/db/discordinteraction_update.go | 454 ++++++++ internal/database/ent/db/ent.go | 2 + internal/database/ent/db/hook/hook.go | 12 + internal/database/ent/db/migrate/schema.go | 55 + internal/database/ent/db/mutation.go | 970 +++++++++++++++++- .../database/ent/db/predicate/predicate.go | 3 + internal/database/ent/db/runtime.go | 29 + internal/database/ent/db/tx.go | 3 + internal/database/ent/db/user.go | 24 +- internal/database/ent/db/user/user.go | 30 + internal/database/ent/db/user/where.go | 23 + internal/database/ent/db/user_create.go | 32 + internal/database/ent/db/user_query.go | 108 +- internal/database/ent/db/user_update.go | 163 +++ .../ent/migrate/migrations/20240626213554.sql | 12 + .../database/ent/migrate/migrations/atlas.sum | 3 +- .../ent/schema/application_command.go | 13 - .../ent/schema/discord_interaction.go | 41 + internal/database/ent/schema/user.go | 1 + .../database/ent/schema/user_connection.go | 21 - .../database/models/discord_interaction.go | 51 + internal/permissions/actions.go | 5 +- internal/permissions/roles.go | 8 +- internal/stats/render/common/v1/options.go | 22 +- 50 files changed, 4516 insertions(+), 174 deletions(-) create mode 100644 cmds/discord/commands/common.go create mode 100644 cmds/discord/commands/interactions.go create mode 100644 cmds/discord/common/reply.go rename internal/database/{discord.go => application_commands.go} (100%) create mode 100644 internal/database/discord_interactions.go create mode 100644 internal/database/ent/db/discordinteraction.go create mode 100644 internal/database/ent/db/discordinteraction/discordinteraction.go create mode 100644 internal/database/ent/db/discordinteraction/where.go create mode 100644 internal/database/ent/db/discordinteraction_create.go create mode 100644 internal/database/ent/db/discordinteraction_delete.go create mode 100644 internal/database/ent/db/discordinteraction_query.go create mode 100644 internal/database/ent/db/discordinteraction_update.go create mode 100644 internal/database/ent/migrate/migrations/20240626213554.sql create mode 100644 internal/database/ent/schema/discord_interaction.go create mode 100644 internal/database/models/discord_interaction.go diff --git a/cmds/core/tasks/cleanup.go b/cmds/core/tasks/cleanup.go index 18b28de1..efdcf6d9 100644 --- a/cmds/core/tasks/cleanup.go +++ b/cmds/core/tasks/cleanup.go @@ -34,6 +34,11 @@ func init() { return "failed to delete expired tasks", err } + err = client.Database().DeleteExpiredInteractions(ctx, time.Unix(taskExpiration, 0)) + if err != nil { + return "failed to delete expired interactions", err + } + err = client.Database().DeleteExpiredSnapshots(ctx, time.Unix(snapshotExpiration, 0)) if err != nil { return "failed to delete expired snapshots", err diff --git a/cmds/discord/commands/builder/command.go b/cmds/discord/commands/builder/command.go index 691f95b5..3eb8d470 100644 --- a/cmds/discord/commands/builder/command.go +++ b/cmds/discord/commands/builder/command.go @@ -104,8 +104,9 @@ func (c Builder) GuildOnly() Builder { return c } -func (c Builder) ComponentType() Builder { +func (c Builder) ComponentType(matchFn func(string) bool) Builder { c.kind = CommandTypeComponent + c.match = matchFn return c } diff --git a/cmds/discord/commands/common.go b/cmds/discord/commands/common.go new file mode 100644 index 00000000..56438bb7 --- /dev/null +++ b/cmds/discord/commands/common.go @@ -0,0 +1,24 @@ +package commands + +import ( + "github.com/bwmarrin/discordgo" + "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/internal/database/models" +) + +func saveInteractionData(ctx *common.Context, command string, opts models.DiscordInteractionOptions) (discordgo.MessageComponent, error) { + interaction := models.DiscordInteraction{ + Command: command, + Locale: ctx.Locale, + UserID: ctx.User.ID, + ReferenceID: ctx.InteractionID(), + Type: models.InteractionTypeStats, + Options: opts, + } + err := ctx.Core.Database().CreateDiscordInteraction(ctx.Context, interaction) + if err != nil { + return nil, err + } + + return newStatsRefreshButton(interaction), nil +} diff --git a/cmds/discord/commands/interactions.go b/cmds/discord/commands/interactions.go new file mode 100644 index 00000000..7cbf31fd --- /dev/null +++ b/cmds/discord/commands/interactions.go @@ -0,0 +1,107 @@ +package commands + +import ( + "bytes" + "context" + "fmt" + "strings" + + "github.com/bwmarrin/discordgo" + "github.com/cufee/aftermath/cmds/discord/commands/builder" + "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/permissions" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + render "github.com/cufee/aftermath/internal/stats/render/common/v1" + + "github.com/cufee/aftermath/internal/stats/renderer/v1" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +func newStatsRefreshButton(data models.DiscordInteraction) discordgo.MessageComponent { + return discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{discordgo.Button{ + Style: discordgo.SecondaryButton, + Emoji: &discordgo.ComponentEmoji{ + Name: "🔃", + }, + CustomID: fmt.Sprintf("refresh_stats_from_button_%s", data.ReferenceID), + }}, + } +} + +func init() { + Loaded.add( + builder.NewCommand("refresh_stats_from_button"). + ComponentType(func(customID string) bool { + return strings.HasPrefix(customID, "refresh_stats_from_button_") + }). + Handler(func(ctx *common.Context) error { + data, ok := ctx.ComponentData() + if !ok { + return ctx.Error("failed to get component data on interaction command") + } + interactionID := strings.ReplaceAll(data.CustomID, "refresh_stats_from_button_", "") + if interactionID == "" { + return ctx.Error("failed to get interaction id from custom id") + } + + interaction, err := ctx.Core.Database().GetDiscordInteraction(ctx.Context, interactionID) + if err != nil { + return ctx.Reply().Send("refresh_interaction_expired") + } + + var image renderer.Image + var meta renderer.Metadata + switch interaction.Command { + case "stats": + img, mt, err := ctx.Core.Render(ctx.Locale).Period(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, render.WithBackground(interaction.Options.BackgroundImageURL)) + if err != nil { + return ctx.Err(err) + } + image = img + meta = mt + + case "session": + img, mt, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, render.WithBackground(interaction.Options.BackgroundImageURL)) + if err != nil { + if errors.Is(err, fetch.ErrSessionNotFound) || errors.Is(err, renderer.ErrAccountNotTracked) { + return ctx.Reply().Send("refresh_interaction_expired") + } + return ctx.Err(err) + } + image = img + meta = mt + + default: + log.Error().Str("customId", data.CustomID).Str("command", interaction.Command).Msg("received an unexpected component interaction callback") + return ctx.Reply().Send("refresh_interaction_expired") + } + + button, saveErr := saveInteractionData(ctx, interaction.Command, interaction.Options) + if saveErr != nil { + // nil button will not cause an error and will be ignored + log.Err(err).Str("interactionId", ctx.ID()).Str("command", "session").Msg("failed to save discord interaction") + } + + var buf bytes.Buffer + err = image.PNG(&buf) + if err != nil { + return ctx.Err(err) + } + + var timings []string + if ctx.User.Permissions.Has(permissions.UseDebugFeatures) { + timings = append(timings, "```") + for name, duration := range meta.Timings { + timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) + } + timings = append(timings, "```") + } + + return ctx.Reply().File(&buf, interaction.Command+"_command_by_aftermath.png").Component(button).Text(timings...).Send() + }), + ) +} diff --git a/cmds/discord/commands/link.go b/cmds/discord/commands/link.go index ea4c873b..63d801e8 100644 --- a/cmds/discord/commands/link.go +++ b/cmds/discord/commands/link.go @@ -38,13 +38,13 @@ func init() { options := getDefaultStatsOptions(ctx) message, valid := options.Validate(ctx) if !valid { - return ctx.Reply(message) + return ctx.Reply().Send(message) } account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { - return ctx.ReplyFmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)) + return ctx.Reply().Fmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() } return ctx.Err(err) } @@ -64,7 +64,7 @@ func init() { return ctx.Err(err) } - return ctx.ReplyFmt("command_link_linked_successfully_fmt", account.Nickname, strings.ToUpper(options.Server)) + return ctx.Reply().Fmt("command_link_linked_successfully_fmt", account.Nickname, strings.ToUpper(options.Server)).Send() }), ) } diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index ec1d926c..f75996f0 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -19,7 +19,7 @@ func init() { Loaded.add( builder.NewCommand("manage"). // ExclusiveToGuilds(os.Getenv("DISCORD_PRIMARY_GUILD_ID")). - Middleware(middleware.RequirePermissions(permissions.ContentModerator)). + Middleware(middleware.RequirePermissions(permissions.UseDebugFeatures)). Options( builder.NewOption("users", discordgo.ApplicationCommandOptionSubCommandGroup).Options( builder.NewOption("lookup", discordgo.ApplicationCommandOptionSubCommand).Options( @@ -59,36 +59,36 @@ func init() { userID, _ := opts.Value("user").(string) result, err := ctx.Core.Database().GetUserByID(ctx.Context, userID, database.WithConnections()) if err != nil { - return ctx.Reply("Database#GetUserByID: " + err.Error()) + return ctx.Reply().Send("Database#GetUserByID: " + err.Error()) } data, err := json.MarshalIndent(result, "", " ") if err != nil { - return ctx.Reply("MarshalIndent: " + err.Error()) + return ctx.Reply().Send("MarshalIndent: " + err.Error()) } - return ctx.Reply("```" + string(data) + "```") + return ctx.Reply().Send("```" + string(data) + "```") case "accounts_search": nickname, _ := opts.Value("nickname").(string) server, _ := opts.Value("server").(string) result, err := ctx.Core.Fetch().Search(ctx.Context, nickname, server) if err != nil { - return ctx.Reply("Fetch#Search: " + err.Error()) + return ctx.Reply().Send("Fetch#Search: " + err.Error()) } data, err := json.MarshalIndent(result, "", " ") if err != nil { - return ctx.Reply("MarshalIndent: " + err.Error()) + return ctx.Reply().Send("MarshalIndent: " + err.Error()) } - return ctx.Reply("```" + string(data) + "```") + return ctx.Reply().Send("```" + string(data) + "```") case "snapshots_view": accountId, ok := opts.Value("account_id").(string) if !ok { - return ctx.Reply("invalid accountId, failed to cast to string") + return ctx.Reply().Send("invalid accountId, failed to cast to string") } snapshots, err := ctx.Core.Database().GetLastAccountSnapshots(ctx.Context, accountId, 3) if err != nil { - return ctx.Reply("GetLastAccountSnapshots: " + err.Error()) + return ctx.Reply().Send("GetLastAccountSnapshots: " + err.Error()) } var data []map[string]string @@ -106,11 +106,15 @@ func init() { bytes, err := json.MarshalIndent(data, "", " ") if err != nil { - return ctx.Reply("json.Marshal: " + err.Error()) + return ctx.Reply().Send("json.Marshal: " + err.Error()) } - return ctx.Reply("```" + string(bytes) + "```") + return ctx.Reply().Send("```" + string(bytes) + "```") case "tasks_view": + if !ctx.User.Permissions.Has(permissions.ViewTaskLogs) { + ctx.Reply().Send("You do not have access to this sub-command.") + } + hours, _ := opts.Value("hours").(float64) status, _ := opts.Value("status").(string) if hours < 1 { @@ -119,10 +123,10 @@ func init() { tasks, err := ctx.Core.Database().GetRecentTasks(ctx.Context, time.Now().Add(time.Hour*time.Duration(hours)*-1), models.TaskStatus(status)) if err != nil { - return ctx.Reply("Database#GetRecentTasks: " + err.Error()) + return ctx.Reply().Send("Database#GetRecentTasks: " + err.Error()) } if len(tasks) < 1 { - return ctx.Reply("No recent tasks with status " + status) + return ctx.Reply().Send("No recent tasks with status " + status) } content := fmt.Sprintf("total: %d\n", len(tasks)) @@ -138,34 +142,38 @@ func init() { } data, err := json.MarshalIndent(reduced, "", " ") if err != nil { - return ctx.Reply("json.Marshal: " + err.Error()) + return ctx.Reply().Send("json.Marshal: " + err.Error()) } content += string(data) - return ctx.File(bytes.NewBufferString(content), "tasks.json") + return ctx.Reply().File(bytes.NewBufferString(content), "tasks.json").Send() case "tasks_details": + if !ctx.User.Permissions.Has(permissions.ViewTaskLogs) { + ctx.Reply().Send("You do not have access to this sub-command.") + } + id, _ := opts.Value("id").(string) if id == "" { - return ctx.Reply("id cannot be blank") + return ctx.Reply().Send("id cannot be blank") } tasks, err := ctx.Core.Database().GetTasks(ctx.Context, id) if err != nil { - return ctx.Reply("Database#GetTasks: " + err.Error()) + return ctx.Reply().Send("Database#GetTasks: " + err.Error()) } if len(tasks) < 1 { - return ctx.Reply("No recent task found") + return ctx.Reply().Send("No recent task found") } data, err := json.MarshalIndent(tasks, "", " ") if err != nil { - return ctx.Reply("json.Marshal: " + err.Error()) + return ctx.Reply().Send("json.Marshal: " + err.Error()) } - return ctx.File(bytes.NewReader(data), "tasks.json") + return ctx.Reply().File(bytes.NewReader(data), "tasks.json").Send() default: - return ctx.Reply("invalid subcommand, thought this should never happen") + return ctx.Reply().Send("invalid subcommand, thought this should never happen") } }), ) diff --git a/cmds/discord/commands/options.go b/cmds/discord/commands/options.go index 0511a87e..e8528249 100644 --- a/cmds/discord/commands/options.go +++ b/cmds/discord/commands/options.go @@ -74,7 +74,7 @@ func (o statsOptions) Validate(ctx *common.Context) (string, bool) { } if o.UserID != "" && o.UserID == ctx.User.ID { // mentioning self is redundant - this should not prevent the command from working though - ctx.Reply("stats_error_mentioned_self_non_blocking") + ctx.Reply().Send("stats_error_mentioned_self_non_blocking") } return "", true } diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go index 86a6c644..7a1562ad 100644 --- a/cmds/discord/commands/session.go +++ b/cmds/discord/commands/session.go @@ -10,11 +10,12 @@ import ( "github.com/cufee/aftermath/cmds/discord/common" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/permissions" "github.com/cufee/aftermath/internal/stats/fetch/v1" - "github.com/cufee/aftermath/internal/stats/render/assets" render "github.com/cufee/aftermath/internal/stats/render/common/v1" stats "github.com/cufee/aftermath/internal/stats/renderer/v1" "github.com/pkg/errors" + "github.com/rs/zerolog/log" ) func init() { @@ -25,11 +26,11 @@ func init() { options := getDefaultStatsOptions(ctx) message, valid := options.Validate(ctx) if !valid { - return ctx.Reply(message) + return ctx.Reply().Send(message) } var accountID string - background, _ := assets.GetLoadedImage("bg-default") + var backgroundURL string switch { case options.UserID != "": @@ -37,7 +38,7 @@ func init() { mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithSubscriptions(), database.WithContent()) defaultAccount, hasDefaultAccount := mentionedUser.Connection(models.ConnectionTypeWargaming) if !hasDefaultAccount { - return ctx.Reply("stats_error_connection_not_found_vague") + return ctx.Reply().Send("stats_error_connection_not_found_vague") } accountID = defaultAccount.ReferenceID // TODO: Get user background @@ -47,7 +48,7 @@ func init() { account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { - return ctx.ReplyFmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)) + return ctx.Reply().Fmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() } return ctx.Err(err) } @@ -56,37 +57,50 @@ func init() { default: defaultAccount, hasDefaultAccount := ctx.User.Connection(models.ConnectionTypeWargaming) if !hasDefaultAccount { - return ctx.Reply("stats_error_nickname_or_server_missing") + return ctx.Reply().Send("stats_error_nickname_or_server_missing") } // command used without options, but user has a default connection accountID = defaultAccount.ReferenceID // TODO: Get user background } - image, meta, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), accountID, options.PeriodStart, render.WithBackground(background)) + image, meta, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), accountID, options.PeriodStart, render.WithBackground(backgroundURL)) if err != nil { if errors.Is(err, stats.ErrAccountNotTracked) || (errors.Is(err, fetch.ErrSessionNotFound) && options.Days < 1) { - return ctx.Reply("session_error_account_was_not_tracked") + return ctx.Reply().Send("session_error_account_was_not_tracked") } if errors.Is(err, fetch.ErrSessionNotFound) { - return ctx.Reply("session_error_no_session_for_period") + return ctx.Reply().Send("session_error_no_session_for_period") } return ctx.Err(err) } + button, saveErr := saveInteractionData(ctx, "session", models.DiscordInteractionOptions{ + BackgroundImageURL: backgroundURL, + PeriodStart: options.PeriodStart, + AccountID: accountID, + }) + if saveErr != nil { + // nil button will not cause an error and will be ignored + log.Err(err).Str("interactionId", ctx.ID()).Str("command", "session").Msg("failed to save discord interaction") + } + var buf bytes.Buffer err = image.PNG(&buf) if err != nil { return ctx.Err(err) } - var timings = []string{"```"} - for name, duration := range meta.Timings { - timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) + var timings []string + if ctx.User.Permissions.Has(permissions.UseDebugFeatures) { + timings = append(timings, "```") + for name, duration := range meta.Timings { + timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) + } + timings = append(timings, "```") } - timings = append(timings, "```") - return ctx.File(&buf, "session_command_by_aftermath.png", timings...) + return ctx.Reply().File(&buf, "session_command_by_aftermath.png").Component(button).Text(timings...).Send() }), ) } diff --git a/cmds/discord/commands/stats.go b/cmds/discord/commands/stats.go index b7ee55c6..0b3ded0a 100644 --- a/cmds/discord/commands/stats.go +++ b/cmds/discord/commands/stats.go @@ -10,8 +10,9 @@ import ( "github.com/cufee/aftermath/cmds/discord/common" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" - "github.com/cufee/aftermath/internal/stats/render/assets" + "github.com/cufee/aftermath/internal/permissions" render "github.com/cufee/aftermath/internal/stats/render/common/v1" + "github.com/rs/zerolog/log" ) func init() { @@ -22,11 +23,11 @@ func init() { options := getDefaultStatsOptions(ctx) message, valid := options.Validate(ctx) if !valid { - return ctx.Reply(message) + return ctx.Reply().Send(message) } var accountID string - background, _ := assets.GetLoadedImage("bg-default") + var backgroundURL = "static://bg-default" switch { case options.UserID != "": @@ -34,7 +35,7 @@ func init() { mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithSubscriptions(), database.WithContent()) defaultAccount, hasDefaultAccount := mentionedUser.Connection(models.ConnectionTypeWargaming) if !hasDefaultAccount { - return ctx.Reply("stats_error_connection_not_found_vague") + return ctx.Reply().Send("stats_error_connection_not_found_vague") } accountID = defaultAccount.ReferenceID // TODO: Get user background @@ -44,7 +45,7 @@ func init() { account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { - return ctx.ReplyFmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)) + return ctx.Reply().Fmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() } return ctx.Err(err) } @@ -53,31 +54,44 @@ func init() { default: defaultAccount, hasDefaultAccount := ctx.User.Connection(models.ConnectionTypeWargaming) if !hasDefaultAccount { - return ctx.Reply("stats_error_nickname_or_server_missing") + return ctx.Reply().Send("stats_error_nickname_or_server_missing") } // command used without options, but user has a default connection accountID = defaultAccount.ReferenceID // TODO: Get user background } - image, meta, err := ctx.Core.Render(ctx.Locale).Period(context.Background(), accountID, options.PeriodStart, render.WithBackground(background)) + image, meta, err := ctx.Core.Render(ctx.Locale).Period(context.Background(), accountID, options.PeriodStart, render.WithBackground(backgroundURL)) if err != nil { return ctx.Err(err) } + button, saveErr := saveInteractionData(ctx, "stats", models.DiscordInteractionOptions{ + BackgroundImageURL: backgroundURL, + PeriodStart: options.PeriodStart, + AccountID: accountID, + }) + if saveErr != nil { + // nil button will not cause an error and will be ignored + log.Err(err).Str("interactionId", ctx.ID()).Str("command", "session").Msg("failed to save discord interaction") + } + var buf bytes.Buffer err = image.PNG(&buf) if err != nil { return ctx.Err(err) } - var timings = []string{"```"} - for name, duration := range meta.Timings { - timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) + var timings []string + if ctx.User.Permissions.Has(permissions.UseDebugFeatures) { + timings = append(timings, "```") + for name, duration := range meta.Timings { + timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) + } + timings = append(timings, "```") } - timings = append(timings, "```") - return ctx.File(&buf, "stats_command_by_aftermath.png", timings...) + return ctx.Reply().File(&buf, "stats_command_by_aftermath.png").Component(button).Text(timings...).Send() }), ) } diff --git a/cmds/discord/common/context.go b/cmds/discord/common/context.go index 9a09bd6d..004b05ac 100644 --- a/cmds/discord/common/context.go +++ b/cmds/discord/common/context.go @@ -2,9 +2,6 @@ package common import ( "context" - "fmt" - "io" - "strings" "github.com/pkg/errors" @@ -88,36 +85,22 @@ func (c *Context) respond(data discordgo.InteractionResponseData) error { return nil } -func (c *Context) Message(message string) error { - if message == "" { - return errors.New("bad reply call with blank message") - } - return c.respond(discordgo.InteractionResponseData{Content: message}) +func (c *Context) InteractionID() string { + return c.interaction.ID } -func (c *Context) Reply(key string) error { - return c.Message(c.Localize(key)) +func (c *Context) Reply() reply { + return reply{ctx: c} } func (c *Context) Err(err error) error { log.Err(err).Str("interactionId", c.interaction.ID).Msg("error while handling an interaction") - return c.Reply("common_error_unhandled_not_reported") + return c.Reply().Send("common_error_unhandled_not_reported") } func (c *Context) Error(message string) error { log.Error().Str("message", message).Str("interactionId", c.interaction.ID).Msg("error while handling an interaction") - return c.Reply("common_error_unhandled_not_reported") -} - -func (c *Context) ReplyFmt(key string, args ...any) error { - return c.Message(fmt.Sprintf(c.Localize(key), args...)) -} - -func (c *Context) File(r io.Reader, name string, message ...string) error { - if r == nil { - return errors.New("bad Context#File call with nil io.Reader") - } - return c.respond(discordgo.InteractionResponseData{Files: []*discordgo.File{{Reader: r, Name: name}}, Content: strings.Join(message, "\n")}) + return c.Reply().Send("common_error_unhandled_not_reported") } func (c *Context) isCommand() bool { diff --git a/cmds/discord/common/reply.go b/cmds/discord/common/reply.go new file mode 100644 index 00000000..526cc416 --- /dev/null +++ b/cmds/discord/common/reply.go @@ -0,0 +1,74 @@ +package common + +import ( + "fmt" + "io" + "strings" + + "github.com/bwmarrin/discordgo" +) + +type reply struct { + ctx *Context + + text []string + files []*discordgo.File + components []discordgo.MessageComponent + embeds []*discordgo.MessageEmbed +} + +func (r reply) Text(message ...string) reply { + r.text = append(r.text, message...) + return r +} + +func (r reply) Fmt(format string, args ...any) reply { + r.text = append(r.text, fmt.Sprintf(format, args...)) + return r +} + +func (r reply) File(reader io.Reader, name string) reply { + if reader == nil { + return r + } + r.files = append(r.files, &discordgo.File{Reader: reader, Name: name}) + return r +} + +func (r reply) Component(components ...discordgo.MessageComponent) reply { + for _, c := range components { + if c == nil { + continue + } + r.components = append(r.components, c) + } + return r +} + +func (r reply) Embed(embeds ...*discordgo.MessageEmbed) reply { + for _, e := range embeds { + if e == nil { + continue + } + r.embeds = append(r.embeds, e) + } + return r +} + +func (r reply) Send(content ...string) error { + r.text = append(r.text, content...) + return r.ctx.respond(r.data(r.ctx.Localize)) +} + +func (r reply) data(localePrinter func(string) string) discordgo.InteractionResponseData { + var content []string + for _, t := range r.text { + content = append(content, localePrinter(t)) + } + return discordgo.InteractionResponseData{ + Content: strings.Join(content, "\n"), + Components: r.components, + Embeds: r.embeds, + Files: r.files, + } +} diff --git a/cmds/discord/discord.go b/cmds/discord/discord.go index 48388227..ab4076ac 100644 --- a/cmds/discord/discord.go +++ b/cmds/discord/discord.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/discord/commands" "github.com/cufee/aftermath/cmds/discord/router" ) diff --git a/cmds/discord/middleware/middleware.go b/cmds/discord/middleware/middleware.go index 2ceb941d..634e6a93 100644 --- a/cmds/discord/middleware/middleware.go +++ b/cmds/discord/middleware/middleware.go @@ -11,7 +11,7 @@ func RequirePermissions(required permissions.Permissions) MiddlewareFunc { return func(ctx *common.Context, next func(*common.Context) error) func(*common.Context) error { if !ctx.User.Permissions.Has(required) { return func(ctx *common.Context) error { - return ctx.Reply("common_error_command_missing_permissions") + return ctx.Reply().Send("common_error_command_missing_permissions") } } return next diff --git a/cmds/discord/router/handler.go b/cmds/discord/router/handler.go index 0dd79565..721ea022 100644 --- a/cmds/discord/router/handler.go +++ b/cmds/discord/router/handler.go @@ -247,7 +247,7 @@ func (r *Router) sendInteractionReply(interaction discordgo.Interaction, state * var handler func() error switch interaction.Type { - case discordgo.InteractionApplicationCommand: + case discordgo.InteractionApplicationCommand, discordgo.InteractionMessageComponent: if state.replied || state.acked { // We already replied to this interaction - edit the message handler = func() error { @@ -260,6 +260,17 @@ func (r *Router) sendInteractionReply(interaction discordgo.Interaction, state * return r.restClient.SendInteractionResponse(interaction.ID, interaction.Token, payload) } } + default: + log.Error().Stack().Any("state", state).Any("data", data).Str("id", interaction.ID).Msg("unknown interaction type received") + if state.replied || state.acked { + handler = func() error { + return r.restClient.UpdateInteractionResponse(interaction.AppID, interaction.Token, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed."}) + } + } else { + handler = func() error { + return r.restClient.SendInteractionResponse(interaction.ID, interaction.Token, discordgo.InteractionResponse{Data: &discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed."}, Type: discordgo.InteractionResponseChannelMessageWithSource}) + } + } } err := handler() diff --git a/go.sum b/go.sum index b4bd05ca..ee4f6f7a 100644 --- a/go.sum +++ b/go.sum @@ -58,17 +58,23 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -77,6 +83,10 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/internal/database/discord.go b/internal/database/application_commands.go similarity index 100% rename from internal/database/discord.go rename to internal/database/application_commands.go diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go index 05d130f5..c6706d6f 100644 --- a/internal/database/cleanup.go +++ b/internal/database/cleanup.go @@ -8,6 +8,7 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" ) @@ -19,6 +20,14 @@ func (c *client) DeleteExpiredTasks(ctx context.Context, expiration time.Time) e return err } +func (c *client) DeleteExpiredInteractions(ctx context.Context, expiration time.Time) error { + err := c.withTx(ctx, func(tx *db.Tx) error { + _, err := tx.DiscordInteraction.Delete().Where(discordinteraction.CreatedAtLT(expiration.Unix())).Exec(ctx) + return err + }) + return err +} + func (c *client) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { _, err := c.db.AccountSnapshot.Delete().Where(accountsnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) if err != nil { diff --git a/internal/database/client.go b/internal/database/client.go index 458a86f5..0c5f7770 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -72,6 +72,10 @@ type TasksClient interface { type DiscordDataClient interface { UpsertCommands(ctx context.Context, commands ...models.ApplicationCommand) error GetCommandsByID(ctx context.Context, commandIDs ...string) ([]models.ApplicationCommand, error) + + CreateDiscordInteraction(ctx context.Context, data models.DiscordInteraction) error + GetDiscordInteraction(ctx context.Context, referenceID string) (models.DiscordInteraction, error) + DeleteExpiredInteractions(ctx context.Context, expiration time.Time) error } type Client interface { diff --git a/internal/database/discord_interactions.go b/internal/database/discord_interactions.go new file mode 100644 index 00000000..ce81fe5f --- /dev/null +++ b/internal/database/discord_interactions.go @@ -0,0 +1,58 @@ +package database + +import ( + "context" + "time" + + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/models" + "github.com/pkg/errors" + "golang.org/x/text/language" +) + +func (c client) CreateDiscordInteraction(ctx context.Context, data models.DiscordInteraction) error { + user, err := c.db.User.Get(ctx, data.UserID) + if err != nil { + return errors.Wrap(err, "failed to get user") + } + + return c.withTx(ctx, func(tx *db.Tx) error { + return c.db.DiscordInteraction.Create(). + SetCommand(data.Command). + SetLocale(data.Locale.String()). + SetOptions(data.Options). + SetReferenceID(data.ReferenceID). + SetType(data.Type). + SetUser(user).Exec(ctx) + }) +} + +func toDiscordInteraction(record *db.DiscordInteraction) models.DiscordInteraction { + locale, err := language.Parse(record.Locale) + if err != nil { + locale = language.English + } + return models.DiscordInteraction{ + ID: record.ID, + CreatedAt: time.Unix(record.CreatedAt, 0), + + UserID: record.UserID, + Command: record.Command, + ReferenceID: record.ReferenceID, + + Type: record.Type, + Locale: locale, + + Options: record.Options, + } +} + +func (c client) GetDiscordInteraction(ctx context.Context, referenceID string) (models.DiscordInteraction, error) { + interaction, err := c.db.DiscordInteraction.Query().Where(discordinteraction.ReferenceID(referenceID)).First(ctx) + if err != nil { + return models.DiscordInteraction{}, err + } + + return toDiscordInteraction(interaction), nil +} diff --git a/internal/database/ent/db/client.go b/internal/database/ent/db/client.go index 9d443fe2..9bceec9e 100644 --- a/internal/database/ent/db/client.go +++ b/internal/database/ent/db/client.go @@ -22,6 +22,7 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" "github.com/cufee/aftermath/internal/database/ent/db/clan" "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -52,6 +53,8 @@ type Client struct { Clan *ClanClient // CronTask is the client for interacting with the CronTask builders. CronTask *CronTaskClient + // DiscordInteraction is the client for interacting with the DiscordInteraction builders. + DiscordInteraction *DiscordInteractionClient // User is the client for interacting with the User builders. User *UserClient // UserConnection is the client for interacting with the UserConnection builders. @@ -84,6 +87,7 @@ func (c *Client) init() { c.ApplicationCommand = NewApplicationCommandClient(c.config) c.Clan = NewClanClient(c.config) c.CronTask = NewCronTaskClient(c.config) + c.DiscordInteraction = NewDiscordInteractionClient(c.config) c.User = NewUserClient(c.config) c.UserConnection = NewUserConnectionClient(c.config) c.UserContent = NewUserContentClient(c.config) @@ -190,6 +194,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { ApplicationCommand: NewApplicationCommandClient(cfg), Clan: NewClanClient(cfg), CronTask: NewCronTaskClient(cfg), + DiscordInteraction: NewDiscordInteractionClient(cfg), User: NewUserClient(cfg), UserConnection: NewUserConnectionClient(cfg), UserContent: NewUserContentClient(cfg), @@ -223,6 +228,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) ApplicationCommand: NewApplicationCommandClient(cfg), Clan: NewClanClient(cfg), CronTask: NewCronTaskClient(cfg), + DiscordInteraction: NewDiscordInteractionClient(cfg), User: NewUserClient(cfg), UserConnection: NewUserConnectionClient(cfg), UserContent: NewUserContentClient(cfg), @@ -260,9 +266,9 @@ func (c *Client) Close() error { func (c *Client) Use(hooks ...Hook) { for _, n := range []interface{ Use(...Hook) }{ c.Account, c.AccountSnapshot, c.AchievementsSnapshot, c.AppConfiguration, - c.ApplicationCommand, c.Clan, c.CronTask, c.User, c.UserConnection, - c.UserContent, c.UserSubscription, c.Vehicle, c.VehicleAverage, - c.VehicleSnapshot, + c.ApplicationCommand, c.Clan, c.CronTask, c.DiscordInteraction, c.User, + c.UserConnection, c.UserContent, c.UserSubscription, c.Vehicle, + c.VehicleAverage, c.VehicleSnapshot, } { n.Use(hooks...) } @@ -273,9 +279,9 @@ func (c *Client) Use(hooks ...Hook) { func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ c.Account, c.AccountSnapshot, c.AchievementsSnapshot, c.AppConfiguration, - c.ApplicationCommand, c.Clan, c.CronTask, c.User, c.UserConnection, - c.UserContent, c.UserSubscription, c.Vehicle, c.VehicleAverage, - c.VehicleSnapshot, + c.ApplicationCommand, c.Clan, c.CronTask, c.DiscordInteraction, c.User, + c.UserConnection, c.UserContent, c.UserSubscription, c.Vehicle, + c.VehicleAverage, c.VehicleSnapshot, } { n.Intercept(interceptors...) } @@ -298,6 +304,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { return c.Clan.mutate(ctx, m) case *CronTaskMutation: return c.CronTask.mutate(ctx, m) + case *DiscordInteractionMutation: + return c.DiscordInteraction.mutate(ctx, m) case *UserMutation: return c.User.mutate(ctx, m) case *UserConnectionMutation: @@ -1360,6 +1368,155 @@ func (c *CronTaskClient) mutate(ctx context.Context, m *CronTaskMutation) (Value } } +// DiscordInteractionClient is a client for the DiscordInteraction schema. +type DiscordInteractionClient struct { + config +} + +// NewDiscordInteractionClient returns a client for the DiscordInteraction from the given config. +func NewDiscordInteractionClient(c config) *DiscordInteractionClient { + return &DiscordInteractionClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `discordinteraction.Hooks(f(g(h())))`. +func (c *DiscordInteractionClient) Use(hooks ...Hook) { + c.hooks.DiscordInteraction = append(c.hooks.DiscordInteraction, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `discordinteraction.Intercept(f(g(h())))`. +func (c *DiscordInteractionClient) Intercept(interceptors ...Interceptor) { + c.inters.DiscordInteraction = append(c.inters.DiscordInteraction, interceptors...) +} + +// Create returns a builder for creating a DiscordInteraction entity. +func (c *DiscordInteractionClient) Create() *DiscordInteractionCreate { + mutation := newDiscordInteractionMutation(c.config, OpCreate) + return &DiscordInteractionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of DiscordInteraction entities. +func (c *DiscordInteractionClient) CreateBulk(builders ...*DiscordInteractionCreate) *DiscordInteractionCreateBulk { + return &DiscordInteractionCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *DiscordInteractionClient) MapCreateBulk(slice any, setFunc func(*DiscordInteractionCreate, int)) *DiscordInteractionCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &DiscordInteractionCreateBulk{err: fmt.Errorf("calling to DiscordInteractionClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*DiscordInteractionCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &DiscordInteractionCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for DiscordInteraction. +func (c *DiscordInteractionClient) Update() *DiscordInteractionUpdate { + mutation := newDiscordInteractionMutation(c.config, OpUpdate) + return &DiscordInteractionUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *DiscordInteractionClient) UpdateOne(di *DiscordInteraction) *DiscordInteractionUpdateOne { + mutation := newDiscordInteractionMutation(c.config, OpUpdateOne, withDiscordInteraction(di)) + return &DiscordInteractionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *DiscordInteractionClient) UpdateOneID(id string) *DiscordInteractionUpdateOne { + mutation := newDiscordInteractionMutation(c.config, OpUpdateOne, withDiscordInteractionID(id)) + return &DiscordInteractionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for DiscordInteraction. +func (c *DiscordInteractionClient) Delete() *DiscordInteractionDelete { + mutation := newDiscordInteractionMutation(c.config, OpDelete) + return &DiscordInteractionDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *DiscordInteractionClient) DeleteOne(di *DiscordInteraction) *DiscordInteractionDeleteOne { + return c.DeleteOneID(di.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *DiscordInteractionClient) DeleteOneID(id string) *DiscordInteractionDeleteOne { + builder := c.Delete().Where(discordinteraction.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &DiscordInteractionDeleteOne{builder} +} + +// Query returns a query builder for DiscordInteraction. +func (c *DiscordInteractionClient) Query() *DiscordInteractionQuery { + return &DiscordInteractionQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeDiscordInteraction}, + inters: c.Interceptors(), + } +} + +// Get returns a DiscordInteraction entity by its id. +func (c *DiscordInteractionClient) Get(ctx context.Context, id string) (*DiscordInteraction, error) { + return c.Query().Where(discordinteraction.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *DiscordInteractionClient) GetX(ctx context.Context, id string) *DiscordInteraction { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a DiscordInteraction. +func (c *DiscordInteractionClient) QueryUser(di *DiscordInteraction) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := di.ID + step := sqlgraph.NewStep( + sqlgraph.From(discordinteraction.Table, discordinteraction.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, discordinteraction.UserTable, discordinteraction.UserColumn), + ) + fromV = sqlgraph.Neighbors(di.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *DiscordInteractionClient) Hooks() []Hook { + return c.hooks.DiscordInteraction +} + +// Interceptors returns the client interceptors. +func (c *DiscordInteractionClient) Interceptors() []Interceptor { + return c.inters.DiscordInteraction +} + +func (c *DiscordInteractionClient) mutate(ctx context.Context, m *DiscordInteractionMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&DiscordInteractionCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&DiscordInteractionUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&DiscordInteractionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&DiscordInteractionDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown DiscordInteraction mutation op: %q", m.Op()) + } +} + // UserClient is a client for the User schema. type UserClient struct { config @@ -1468,6 +1625,22 @@ func (c *UserClient) GetX(ctx context.Context, id string) *User { return obj } +// QueryDiscordInteractions queries the discord_interactions edge of a User. +func (c *UserClient) QueryDiscordInteractions(u *User) *DiscordInteractionQuery { + query := (&DiscordInteractionClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(discordinteraction.Table, discordinteraction.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.DiscordInteractionsTable, user.DiscordInteractionsColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + // QuerySubscriptions queries the subscriptions edge of a User. func (c *UserClient) QuerySubscriptions(u *User) *UserSubscriptionQuery { query := (&UserSubscriptionClient{config: c.config}).Query() @@ -2407,13 +2580,15 @@ func (c *VehicleSnapshotClient) mutate(ctx context.Context, m *VehicleSnapshotMu type ( hooks struct { Account, AccountSnapshot, AchievementsSnapshot, AppConfiguration, - ApplicationCommand, Clan, CronTask, User, UserConnection, UserContent, - UserSubscription, Vehicle, VehicleAverage, VehicleSnapshot []ent.Hook + ApplicationCommand, Clan, CronTask, DiscordInteraction, User, UserConnection, + UserContent, UserSubscription, Vehicle, VehicleAverage, + VehicleSnapshot []ent.Hook } inters struct { Account, AccountSnapshot, AchievementsSnapshot, AppConfiguration, - ApplicationCommand, Clan, CronTask, User, UserConnection, UserContent, - UserSubscription, Vehicle, VehicleAverage, VehicleSnapshot []ent.Interceptor + ApplicationCommand, Clan, CronTask, DiscordInteraction, User, UserConnection, + UserContent, UserSubscription, Vehicle, VehicleAverage, + VehicleSnapshot []ent.Interceptor } ) diff --git a/internal/database/ent/db/discordinteraction.go b/internal/database/ent/db/discordinteraction.go new file mode 100644 index 00000000..9ac0f690 --- /dev/null +++ b/internal/database/ent/db/discordinteraction.go @@ -0,0 +1,215 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/models" +) + +// DiscordInteraction is the model entity for the DiscordInteraction schema. +type DiscordInteraction struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt int64 `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt int64 `json:"updated_at,omitempty"` + // Command holds the value of the "command" field. + Command string `json:"command,omitempty"` + // UserID holds the value of the "user_id" field. + UserID string `json:"user_id,omitempty"` + // ReferenceID holds the value of the "reference_id" field. + ReferenceID string `json:"reference_id,omitempty"` + // Type holds the value of the "type" field. + Type models.DiscordInteractionType `json:"type,omitempty"` + // Locale holds the value of the "locale" field. + Locale string `json:"locale,omitempty"` + // Options holds the value of the "options" field. + Options models.DiscordInteractionOptions `json:"options,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the DiscordInteractionQuery when eager-loading is set. + Edges DiscordInteractionEdges `json:"edges"` + selectValues sql.SelectValues +} + +// DiscordInteractionEdges holds the relations/edges for other nodes in the graph. +type DiscordInteractionEdges struct { + // User holds the value of the user edge. + User *User `json:"user,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e DiscordInteractionEdges) UserOrErr() (*User, error) { + if e.User != nil { + return e.User, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: user.Label} + } + return nil, &NotLoadedError{edge: "user"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*DiscordInteraction) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case discordinteraction.FieldOptions: + values[i] = new([]byte) + case discordinteraction.FieldCreatedAt, discordinteraction.FieldUpdatedAt: + values[i] = new(sql.NullInt64) + case discordinteraction.FieldID, discordinteraction.FieldCommand, discordinteraction.FieldUserID, discordinteraction.FieldReferenceID, discordinteraction.FieldType, discordinteraction.FieldLocale: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the DiscordInteraction fields. +func (di *DiscordInteraction) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case discordinteraction.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + di.ID = value.String + } + case discordinteraction.FieldCreatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + di.CreatedAt = value.Int64 + } + case discordinteraction.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + di.UpdatedAt = value.Int64 + } + case discordinteraction.FieldCommand: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field command", values[i]) + } else if value.Valid { + di.Command = value.String + } + case discordinteraction.FieldUserID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field user_id", values[i]) + } else if value.Valid { + di.UserID = value.String + } + case discordinteraction.FieldReferenceID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reference_id", values[i]) + } else if value.Valid { + di.ReferenceID = value.String + } + case discordinteraction.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + di.Type = models.DiscordInteractionType(value.String) + } + case discordinteraction.FieldLocale: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field locale", values[i]) + } else if value.Valid { + di.Locale = value.String + } + case discordinteraction.FieldOptions: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field options", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &di.Options); err != nil { + return fmt.Errorf("unmarshal field options: %w", err) + } + } + default: + di.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the DiscordInteraction. +// This includes values selected through modifiers, order, etc. +func (di *DiscordInteraction) Value(name string) (ent.Value, error) { + return di.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the DiscordInteraction entity. +func (di *DiscordInteraction) QueryUser() *UserQuery { + return NewDiscordInteractionClient(di.config).QueryUser(di) +} + +// Update returns a builder for updating this DiscordInteraction. +// Note that you need to call DiscordInteraction.Unwrap() before calling this method if this DiscordInteraction +// was returned from a transaction, and the transaction was committed or rolled back. +func (di *DiscordInteraction) Update() *DiscordInteractionUpdateOne { + return NewDiscordInteractionClient(di.config).UpdateOne(di) +} + +// Unwrap unwraps the DiscordInteraction entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (di *DiscordInteraction) Unwrap() *DiscordInteraction { + _tx, ok := di.config.driver.(*txDriver) + if !ok { + panic("db: DiscordInteraction is not a transactional entity") + } + di.config.driver = _tx.drv + return di +} + +// String implements the fmt.Stringer. +func (di *DiscordInteraction) String() string { + var builder strings.Builder + builder.WriteString("DiscordInteraction(") + builder.WriteString(fmt.Sprintf("id=%v, ", di.ID)) + builder.WriteString("created_at=") + builder.WriteString(fmt.Sprintf("%v", di.CreatedAt)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(fmt.Sprintf("%v", di.UpdatedAt)) + builder.WriteString(", ") + builder.WriteString("command=") + builder.WriteString(di.Command) + builder.WriteString(", ") + builder.WriteString("user_id=") + builder.WriteString(di.UserID) + builder.WriteString(", ") + builder.WriteString("reference_id=") + builder.WriteString(di.ReferenceID) + builder.WriteString(", ") + builder.WriteString("type=") + builder.WriteString(fmt.Sprintf("%v", di.Type)) + builder.WriteString(", ") + builder.WriteString("locale=") + builder.WriteString(di.Locale) + builder.WriteString(", ") + builder.WriteString("options=") + builder.WriteString(fmt.Sprintf("%v", di.Options)) + builder.WriteByte(')') + return builder.String() +} + +// DiscordInteractions is a parsable slice of DiscordInteraction. +type DiscordInteractions []*DiscordInteraction diff --git a/internal/database/ent/db/discordinteraction/discordinteraction.go b/internal/database/ent/db/discordinteraction/discordinteraction.go new file mode 100644 index 00000000..3bad3cbe --- /dev/null +++ b/internal/database/ent/db/discordinteraction/discordinteraction.go @@ -0,0 +1,152 @@ +// Code generated by ent, DO NOT EDIT. + +package discordinteraction + +import ( + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/models" +) + +const ( + // Label holds the string label denoting the discordinteraction type in the database. + Label = "discord_interaction" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldCommand holds the string denoting the command field in the database. + FieldCommand = "command" + // FieldUserID holds the string denoting the user_id field in the database. + FieldUserID = "user_id" + // FieldReferenceID holds the string denoting the reference_id field in the database. + FieldReferenceID = "reference_id" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldLocale holds the string denoting the locale field in the database. + FieldLocale = "locale" + // FieldOptions holds the string denoting the options field in the database. + FieldOptions = "options" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // Table holds the table name of the discordinteraction in the database. + Table = "discord_interactions" + // UserTable is the table that holds the user relation/edge. + UserTable = "discord_interactions" + // UserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + UserInverseTable = "users" + // UserColumn is the table column denoting the user relation/edge. + UserColumn = "user_id" +) + +// Columns holds all SQL columns for discordinteraction fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldCommand, + FieldUserID, + FieldReferenceID, + FieldType, + FieldLocale, + FieldOptions, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() int64 + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() int64 + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() int64 + // CommandValidator is a validator for the "command" field. It is called by the builders before save. + CommandValidator func(string) error + // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. + UserIDValidator func(string) error + // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + ReferenceIDValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save. +func TypeValidator(_type models.DiscordInteractionType) error { + switch _type { + case "stats": + return nil + default: + return fmt.Errorf("discordinteraction: invalid enum value for type field: %q", _type) + } +} + +// OrderOption defines the ordering options for the DiscordInteraction queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByCommand orders the results by the command field. +func ByCommand(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCommand, opts...).ToFunc() +} + +// ByUserID orders the results by the user_id field. +func ByUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserID, opts...).ToFunc() +} + +// ByReferenceID orders the results by the reference_id field. +func ByReferenceID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReferenceID, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByLocale orders the results by the locale field. +func ByLocale(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLocale, opts...).ToFunc() +} + +// ByUserField orders the results by user field. +func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...)) + } +} +func newUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) +} diff --git a/internal/database/ent/db/discordinteraction/where.go b/internal/database/ent/db/discordinteraction/where.go new file mode 100644 index 00000000..1b38d92e --- /dev/null +++ b/internal/database/ent/db/discordinteraction/where.go @@ -0,0 +1,503 @@ +// Code generated by ent, DO NOT EDIT. + +package discordinteraction + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Command applies equality check predicate on the "command" field. It's identical to CommandEQ. +func Command(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldCommand, v)) +} + +// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. +func UserID(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldUserID, v)) +} + +// ReferenceID applies equality check predicate on the "reference_id" field. It's identical to ReferenceIDEQ. +func ReferenceID(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldReferenceID, v)) +} + +// Locale applies equality check predicate on the "locale" field. It's identical to LocaleEQ. +func Locale(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldLocale, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v int64) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// CommandEQ applies the EQ predicate on the "command" field. +func CommandEQ(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldCommand, v)) +} + +// CommandNEQ applies the NEQ predicate on the "command" field. +func CommandNEQ(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNEQ(FieldCommand, v)) +} + +// CommandIn applies the In predicate on the "command" field. +func CommandIn(vs ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldIn(FieldCommand, vs...)) +} + +// CommandNotIn applies the NotIn predicate on the "command" field. +func CommandNotIn(vs ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNotIn(FieldCommand, vs...)) +} + +// CommandGT applies the GT predicate on the "command" field. +func CommandGT(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGT(FieldCommand, v)) +} + +// CommandGTE applies the GTE predicate on the "command" field. +func CommandGTE(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGTE(FieldCommand, v)) +} + +// CommandLT applies the LT predicate on the "command" field. +func CommandLT(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLT(FieldCommand, v)) +} + +// CommandLTE applies the LTE predicate on the "command" field. +func CommandLTE(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLTE(FieldCommand, v)) +} + +// CommandContains applies the Contains predicate on the "command" field. +func CommandContains(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldContains(FieldCommand, v)) +} + +// CommandHasPrefix applies the HasPrefix predicate on the "command" field. +func CommandHasPrefix(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldHasPrefix(FieldCommand, v)) +} + +// CommandHasSuffix applies the HasSuffix predicate on the "command" field. +func CommandHasSuffix(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldHasSuffix(FieldCommand, v)) +} + +// CommandEqualFold applies the EqualFold predicate on the "command" field. +func CommandEqualFold(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEqualFold(FieldCommand, v)) +} + +// CommandContainsFold applies the ContainsFold predicate on the "command" field. +func CommandContainsFold(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldContainsFold(FieldCommand, v)) +} + +// UserIDEQ applies the EQ predicate on the "user_id" field. +func UserIDEQ(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldUserID, v)) +} + +// UserIDNEQ applies the NEQ predicate on the "user_id" field. +func UserIDNEQ(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNEQ(FieldUserID, v)) +} + +// UserIDIn applies the In predicate on the "user_id" field. +func UserIDIn(vs ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldIn(FieldUserID, vs...)) +} + +// UserIDNotIn applies the NotIn predicate on the "user_id" field. +func UserIDNotIn(vs ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNotIn(FieldUserID, vs...)) +} + +// UserIDGT applies the GT predicate on the "user_id" field. +func UserIDGT(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGT(FieldUserID, v)) +} + +// UserIDGTE applies the GTE predicate on the "user_id" field. +func UserIDGTE(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGTE(FieldUserID, v)) +} + +// UserIDLT applies the LT predicate on the "user_id" field. +func UserIDLT(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLT(FieldUserID, v)) +} + +// UserIDLTE applies the LTE predicate on the "user_id" field. +func UserIDLTE(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLTE(FieldUserID, v)) +} + +// UserIDContains applies the Contains predicate on the "user_id" field. +func UserIDContains(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldContains(FieldUserID, v)) +} + +// UserIDHasPrefix applies the HasPrefix predicate on the "user_id" field. +func UserIDHasPrefix(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldHasPrefix(FieldUserID, v)) +} + +// UserIDHasSuffix applies the HasSuffix predicate on the "user_id" field. +func UserIDHasSuffix(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldHasSuffix(FieldUserID, v)) +} + +// UserIDEqualFold applies the EqualFold predicate on the "user_id" field. +func UserIDEqualFold(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEqualFold(FieldUserID, v)) +} + +// UserIDContainsFold applies the ContainsFold predicate on the "user_id" field. +func UserIDContainsFold(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldContainsFold(FieldUserID, v)) +} + +// ReferenceIDEQ applies the EQ predicate on the "reference_id" field. +func ReferenceIDEQ(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldReferenceID, v)) +} + +// ReferenceIDNEQ applies the NEQ predicate on the "reference_id" field. +func ReferenceIDNEQ(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNEQ(FieldReferenceID, v)) +} + +// ReferenceIDIn applies the In predicate on the "reference_id" field. +func ReferenceIDIn(vs ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldIn(FieldReferenceID, vs...)) +} + +// ReferenceIDNotIn applies the NotIn predicate on the "reference_id" field. +func ReferenceIDNotIn(vs ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNotIn(FieldReferenceID, vs...)) +} + +// ReferenceIDGT applies the GT predicate on the "reference_id" field. +func ReferenceIDGT(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGT(FieldReferenceID, v)) +} + +// ReferenceIDGTE applies the GTE predicate on the "reference_id" field. +func ReferenceIDGTE(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGTE(FieldReferenceID, v)) +} + +// ReferenceIDLT applies the LT predicate on the "reference_id" field. +func ReferenceIDLT(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLT(FieldReferenceID, v)) +} + +// ReferenceIDLTE applies the LTE predicate on the "reference_id" field. +func ReferenceIDLTE(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLTE(FieldReferenceID, v)) +} + +// ReferenceIDContains applies the Contains predicate on the "reference_id" field. +func ReferenceIDContains(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldContains(FieldReferenceID, v)) +} + +// ReferenceIDHasPrefix applies the HasPrefix predicate on the "reference_id" field. +func ReferenceIDHasPrefix(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldHasPrefix(FieldReferenceID, v)) +} + +// ReferenceIDHasSuffix applies the HasSuffix predicate on the "reference_id" field. +func ReferenceIDHasSuffix(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldHasSuffix(FieldReferenceID, v)) +} + +// ReferenceIDEqualFold applies the EqualFold predicate on the "reference_id" field. +func ReferenceIDEqualFold(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEqualFold(FieldReferenceID, v)) +} + +// ReferenceIDContainsFold applies the ContainsFold predicate on the "reference_id" field. +func ReferenceIDContainsFold(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldContainsFold(FieldReferenceID, v)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v models.DiscordInteractionType) predicate.DiscordInteraction { + vc := v + return predicate.DiscordInteraction(sql.FieldEQ(FieldType, vc)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v models.DiscordInteractionType) predicate.DiscordInteraction { + vc := v + return predicate.DiscordInteraction(sql.FieldNEQ(FieldType, vc)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...models.DiscordInteractionType) predicate.DiscordInteraction { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.DiscordInteraction(sql.FieldIn(FieldType, v...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...models.DiscordInteractionType) predicate.DiscordInteraction { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.DiscordInteraction(sql.FieldNotIn(FieldType, v...)) +} + +// LocaleEQ applies the EQ predicate on the "locale" field. +func LocaleEQ(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEQ(FieldLocale, v)) +} + +// LocaleNEQ applies the NEQ predicate on the "locale" field. +func LocaleNEQ(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNEQ(FieldLocale, v)) +} + +// LocaleIn applies the In predicate on the "locale" field. +func LocaleIn(vs ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldIn(FieldLocale, vs...)) +} + +// LocaleNotIn applies the NotIn predicate on the "locale" field. +func LocaleNotIn(vs ...string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldNotIn(FieldLocale, vs...)) +} + +// LocaleGT applies the GT predicate on the "locale" field. +func LocaleGT(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGT(FieldLocale, v)) +} + +// LocaleGTE applies the GTE predicate on the "locale" field. +func LocaleGTE(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldGTE(FieldLocale, v)) +} + +// LocaleLT applies the LT predicate on the "locale" field. +func LocaleLT(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLT(FieldLocale, v)) +} + +// LocaleLTE applies the LTE predicate on the "locale" field. +func LocaleLTE(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldLTE(FieldLocale, v)) +} + +// LocaleContains applies the Contains predicate on the "locale" field. +func LocaleContains(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldContains(FieldLocale, v)) +} + +// LocaleHasPrefix applies the HasPrefix predicate on the "locale" field. +func LocaleHasPrefix(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldHasPrefix(FieldLocale, v)) +} + +// LocaleHasSuffix applies the HasSuffix predicate on the "locale" field. +func LocaleHasSuffix(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldHasSuffix(FieldLocale, v)) +} + +// LocaleEqualFold applies the EqualFold predicate on the "locale" field. +func LocaleEqualFold(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldEqualFold(FieldLocale, v)) +} + +// LocaleContainsFold applies the ContainsFold predicate on the "locale" field. +func LocaleContainsFold(v string) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.FieldContainsFold(FieldLocale, v)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.DiscordInteraction { + return predicate.DiscordInteraction(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). +func HasUserWith(preds ...predicate.User) predicate.DiscordInteraction { + return predicate.DiscordInteraction(func(s *sql.Selector) { + step := newUserStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.DiscordInteraction) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.DiscordInteraction) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.DiscordInteraction) predicate.DiscordInteraction { + return predicate.DiscordInteraction(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/discordinteraction_create.go b/internal/database/ent/db/discordinteraction_create.go new file mode 100644 index 00000000..45583113 --- /dev/null +++ b/internal/database/ent/db/discordinteraction_create.go @@ -0,0 +1,370 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/ent/db/user" + "github.com/cufee/aftermath/internal/database/models" +) + +// DiscordInteractionCreate is the builder for creating a DiscordInteraction entity. +type DiscordInteractionCreate struct { + config + mutation *DiscordInteractionMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (dic *DiscordInteractionCreate) SetCreatedAt(i int64) *DiscordInteractionCreate { + dic.mutation.SetCreatedAt(i) + return dic +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (dic *DiscordInteractionCreate) SetNillableCreatedAt(i *int64) *DiscordInteractionCreate { + if i != nil { + dic.SetCreatedAt(*i) + } + return dic +} + +// SetUpdatedAt sets the "updated_at" field. +func (dic *DiscordInteractionCreate) SetUpdatedAt(i int64) *DiscordInteractionCreate { + dic.mutation.SetUpdatedAt(i) + return dic +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (dic *DiscordInteractionCreate) SetNillableUpdatedAt(i *int64) *DiscordInteractionCreate { + if i != nil { + dic.SetUpdatedAt(*i) + } + return dic +} + +// SetCommand sets the "command" field. +func (dic *DiscordInteractionCreate) SetCommand(s string) *DiscordInteractionCreate { + dic.mutation.SetCommand(s) + return dic +} + +// SetUserID sets the "user_id" field. +func (dic *DiscordInteractionCreate) SetUserID(s string) *DiscordInteractionCreate { + dic.mutation.SetUserID(s) + return dic +} + +// SetReferenceID sets the "reference_id" field. +func (dic *DiscordInteractionCreate) SetReferenceID(s string) *DiscordInteractionCreate { + dic.mutation.SetReferenceID(s) + return dic +} + +// SetType sets the "type" field. +func (dic *DiscordInteractionCreate) SetType(mit models.DiscordInteractionType) *DiscordInteractionCreate { + dic.mutation.SetType(mit) + return dic +} + +// SetLocale sets the "locale" field. +func (dic *DiscordInteractionCreate) SetLocale(s string) *DiscordInteractionCreate { + dic.mutation.SetLocale(s) + return dic +} + +// SetOptions sets the "options" field. +func (dic *DiscordInteractionCreate) SetOptions(mio models.DiscordInteractionOptions) *DiscordInteractionCreate { + dic.mutation.SetOptions(mio) + return dic +} + +// SetID sets the "id" field. +func (dic *DiscordInteractionCreate) SetID(s string) *DiscordInteractionCreate { + dic.mutation.SetID(s) + return dic +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (dic *DiscordInteractionCreate) SetNillableID(s *string) *DiscordInteractionCreate { + if s != nil { + dic.SetID(*s) + } + return dic +} + +// SetUser sets the "user" edge to the User entity. +func (dic *DiscordInteractionCreate) SetUser(u *User) *DiscordInteractionCreate { + return dic.SetUserID(u.ID) +} + +// Mutation returns the DiscordInteractionMutation object of the builder. +func (dic *DiscordInteractionCreate) Mutation() *DiscordInteractionMutation { + return dic.mutation +} + +// Save creates the DiscordInteraction in the database. +func (dic *DiscordInteractionCreate) Save(ctx context.Context) (*DiscordInteraction, error) { + dic.defaults() + return withHooks(ctx, dic.sqlSave, dic.mutation, dic.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (dic *DiscordInteractionCreate) SaveX(ctx context.Context) *DiscordInteraction { + v, err := dic.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (dic *DiscordInteractionCreate) Exec(ctx context.Context) error { + _, err := dic.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (dic *DiscordInteractionCreate) ExecX(ctx context.Context) { + if err := dic.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (dic *DiscordInteractionCreate) defaults() { + if _, ok := dic.mutation.CreatedAt(); !ok { + v := discordinteraction.DefaultCreatedAt() + dic.mutation.SetCreatedAt(v) + } + if _, ok := dic.mutation.UpdatedAt(); !ok { + v := discordinteraction.DefaultUpdatedAt() + dic.mutation.SetUpdatedAt(v) + } + if _, ok := dic.mutation.ID(); !ok { + v := discordinteraction.DefaultID() + dic.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (dic *DiscordInteractionCreate) check() error { + if _, ok := dic.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "DiscordInteraction.created_at"`)} + } + if _, ok := dic.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "DiscordInteraction.updated_at"`)} + } + if _, ok := dic.mutation.Command(); !ok { + return &ValidationError{Name: "command", err: errors.New(`db: missing required field "DiscordInteraction.command"`)} + } + if v, ok := dic.mutation.Command(); ok { + if err := discordinteraction.CommandValidator(v); err != nil { + return &ValidationError{Name: "command", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.command": %w`, err)} + } + } + if _, ok := dic.mutation.UserID(); !ok { + return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "DiscordInteraction.user_id"`)} + } + if v, ok := dic.mutation.UserID(); ok { + if err := discordinteraction.UserIDValidator(v); err != nil { + return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.user_id": %w`, err)} + } + } + if _, ok := dic.mutation.ReferenceID(); !ok { + return &ValidationError{Name: "reference_id", err: errors.New(`db: missing required field "DiscordInteraction.reference_id"`)} + } + if v, ok := dic.mutation.ReferenceID(); ok { + if err := discordinteraction.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.reference_id": %w`, err)} + } + } + if _, ok := dic.mutation.GetType(); !ok { + return &ValidationError{Name: "type", err: errors.New(`db: missing required field "DiscordInteraction.type"`)} + } + if v, ok := dic.mutation.GetType(); ok { + if err := discordinteraction.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.type": %w`, err)} + } + } + if _, ok := dic.mutation.Locale(); !ok { + return &ValidationError{Name: "locale", err: errors.New(`db: missing required field "DiscordInteraction.locale"`)} + } + if _, ok := dic.mutation.Options(); !ok { + return &ValidationError{Name: "options", err: errors.New(`db: missing required field "DiscordInteraction.options"`)} + } + if _, ok := dic.mutation.UserID(); !ok { + return &ValidationError{Name: "user", err: errors.New(`db: missing required edge "DiscordInteraction.user"`)} + } + return nil +} + +func (dic *DiscordInteractionCreate) sqlSave(ctx context.Context) (*DiscordInteraction, error) { + if err := dic.check(); err != nil { + return nil, err + } + _node, _spec := dic.createSpec() + if err := sqlgraph.CreateNode(ctx, dic.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected DiscordInteraction.ID type: %T", _spec.ID.Value) + } + } + dic.mutation.id = &_node.ID + dic.mutation.done = true + return _node, nil +} + +func (dic *DiscordInteractionCreate) createSpec() (*DiscordInteraction, *sqlgraph.CreateSpec) { + var ( + _node = &DiscordInteraction{config: dic.config} + _spec = sqlgraph.NewCreateSpec(discordinteraction.Table, sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString)) + ) + if id, ok := dic.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := dic.mutation.CreatedAt(); ok { + _spec.SetField(discordinteraction.FieldCreatedAt, field.TypeInt64, value) + _node.CreatedAt = value + } + if value, ok := dic.mutation.UpdatedAt(); ok { + _spec.SetField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) + _node.UpdatedAt = value + } + if value, ok := dic.mutation.Command(); ok { + _spec.SetField(discordinteraction.FieldCommand, field.TypeString, value) + _node.Command = value + } + if value, ok := dic.mutation.ReferenceID(); ok { + _spec.SetField(discordinteraction.FieldReferenceID, field.TypeString, value) + _node.ReferenceID = value + } + if value, ok := dic.mutation.GetType(); ok { + _spec.SetField(discordinteraction.FieldType, field.TypeEnum, value) + _node.Type = value + } + if value, ok := dic.mutation.Locale(); ok { + _spec.SetField(discordinteraction.FieldLocale, field.TypeString, value) + _node.Locale = value + } + if value, ok := dic.mutation.Options(); ok { + _spec.SetField(discordinteraction.FieldOptions, field.TypeJSON, value) + _node.Options = value + } + if nodes := dic.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: discordinteraction.UserTable, + Columns: []string{discordinteraction.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.UserID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// DiscordInteractionCreateBulk is the builder for creating many DiscordInteraction entities in bulk. +type DiscordInteractionCreateBulk struct { + config + err error + builders []*DiscordInteractionCreate +} + +// Save creates the DiscordInteraction entities in the database. +func (dicb *DiscordInteractionCreateBulk) Save(ctx context.Context) ([]*DiscordInteraction, error) { + if dicb.err != nil { + return nil, dicb.err + } + specs := make([]*sqlgraph.CreateSpec, len(dicb.builders)) + nodes := make([]*DiscordInteraction, len(dicb.builders)) + mutators := make([]Mutator, len(dicb.builders)) + for i := range dicb.builders { + func(i int, root context.Context) { + builder := dicb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*DiscordInteractionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, dicb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, dicb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, dicb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (dicb *DiscordInteractionCreateBulk) SaveX(ctx context.Context) []*DiscordInteraction { + v, err := dicb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (dicb *DiscordInteractionCreateBulk) Exec(ctx context.Context) error { + _, err := dicb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (dicb *DiscordInteractionCreateBulk) ExecX(ctx context.Context) { + if err := dicb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/discordinteraction_delete.go b/internal/database/ent/db/discordinteraction_delete.go new file mode 100644 index 00000000..461aeb8e --- /dev/null +++ b/internal/database/ent/db/discordinteraction_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// DiscordInteractionDelete is the builder for deleting a DiscordInteraction entity. +type DiscordInteractionDelete struct { + config + hooks []Hook + mutation *DiscordInteractionMutation +} + +// Where appends a list predicates to the DiscordInteractionDelete builder. +func (did *DiscordInteractionDelete) Where(ps ...predicate.DiscordInteraction) *DiscordInteractionDelete { + did.mutation.Where(ps...) + return did +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (did *DiscordInteractionDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, did.sqlExec, did.mutation, did.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (did *DiscordInteractionDelete) ExecX(ctx context.Context) int { + n, err := did.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (did *DiscordInteractionDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(discordinteraction.Table, sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString)) + if ps := did.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, did.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + did.mutation.done = true + return affected, err +} + +// DiscordInteractionDeleteOne is the builder for deleting a single DiscordInteraction entity. +type DiscordInteractionDeleteOne struct { + did *DiscordInteractionDelete +} + +// Where appends a list predicates to the DiscordInteractionDelete builder. +func (dido *DiscordInteractionDeleteOne) Where(ps ...predicate.DiscordInteraction) *DiscordInteractionDeleteOne { + dido.did.mutation.Where(ps...) + return dido +} + +// Exec executes the deletion query. +func (dido *DiscordInteractionDeleteOne) Exec(ctx context.Context) error { + n, err := dido.did.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{discordinteraction.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (dido *DiscordInteractionDeleteOne) ExecX(ctx context.Context) { + if err := dido.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/discordinteraction_query.go b/internal/database/ent/db/discordinteraction_query.go new file mode 100644 index 00000000..bd085984 --- /dev/null +++ b/internal/database/ent/db/discordinteraction_query.go @@ -0,0 +1,605 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/user" +) + +// DiscordInteractionQuery is the builder for querying DiscordInteraction entities. +type DiscordInteractionQuery struct { + config + ctx *QueryContext + order []discordinteraction.OrderOption + inters []Interceptor + predicates []predicate.DiscordInteraction + withUser *UserQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the DiscordInteractionQuery builder. +func (diq *DiscordInteractionQuery) Where(ps ...predicate.DiscordInteraction) *DiscordInteractionQuery { + diq.predicates = append(diq.predicates, ps...) + return diq +} + +// Limit the number of records to be returned by this query. +func (diq *DiscordInteractionQuery) Limit(limit int) *DiscordInteractionQuery { + diq.ctx.Limit = &limit + return diq +} + +// Offset to start from. +func (diq *DiscordInteractionQuery) Offset(offset int) *DiscordInteractionQuery { + diq.ctx.Offset = &offset + return diq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (diq *DiscordInteractionQuery) Unique(unique bool) *DiscordInteractionQuery { + diq.ctx.Unique = &unique + return diq +} + +// Order specifies how the records should be ordered. +func (diq *DiscordInteractionQuery) Order(o ...discordinteraction.OrderOption) *DiscordInteractionQuery { + diq.order = append(diq.order, o...) + return diq +} + +// QueryUser chains the current query on the "user" edge. +func (diq *DiscordInteractionQuery) QueryUser() *UserQuery { + query := (&UserClient{config: diq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := diq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := diq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(discordinteraction.Table, discordinteraction.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, discordinteraction.UserTable, discordinteraction.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(diq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first DiscordInteraction entity from the query. +// Returns a *NotFoundError when no DiscordInteraction was found. +func (diq *DiscordInteractionQuery) First(ctx context.Context) (*DiscordInteraction, error) { + nodes, err := diq.Limit(1).All(setContextOp(ctx, diq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{discordinteraction.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (diq *DiscordInteractionQuery) FirstX(ctx context.Context) *DiscordInteraction { + node, err := diq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first DiscordInteraction ID from the query. +// Returns a *NotFoundError when no DiscordInteraction ID was found. +func (diq *DiscordInteractionQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = diq.Limit(1).IDs(setContextOp(ctx, diq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{discordinteraction.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (diq *DiscordInteractionQuery) FirstIDX(ctx context.Context) string { + id, err := diq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single DiscordInteraction entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one DiscordInteraction entity is found. +// Returns a *NotFoundError when no DiscordInteraction entities are found. +func (diq *DiscordInteractionQuery) Only(ctx context.Context) (*DiscordInteraction, error) { + nodes, err := diq.Limit(2).All(setContextOp(ctx, diq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{discordinteraction.Label} + default: + return nil, &NotSingularError{discordinteraction.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (diq *DiscordInteractionQuery) OnlyX(ctx context.Context) *DiscordInteraction { + node, err := diq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only DiscordInteraction ID in the query. +// Returns a *NotSingularError when more than one DiscordInteraction ID is found. +// Returns a *NotFoundError when no entities are found. +func (diq *DiscordInteractionQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = diq.Limit(2).IDs(setContextOp(ctx, diq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{discordinteraction.Label} + default: + err = &NotSingularError{discordinteraction.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (diq *DiscordInteractionQuery) OnlyIDX(ctx context.Context) string { + id, err := diq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of DiscordInteractions. +func (diq *DiscordInteractionQuery) All(ctx context.Context) ([]*DiscordInteraction, error) { + ctx = setContextOp(ctx, diq.ctx, "All") + if err := diq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*DiscordInteraction, *DiscordInteractionQuery]() + return withInterceptors[[]*DiscordInteraction](ctx, diq, qr, diq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (diq *DiscordInteractionQuery) AllX(ctx context.Context) []*DiscordInteraction { + nodes, err := diq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of DiscordInteraction IDs. +func (diq *DiscordInteractionQuery) IDs(ctx context.Context) (ids []string, err error) { + if diq.ctx.Unique == nil && diq.path != nil { + diq.Unique(true) + } + ctx = setContextOp(ctx, diq.ctx, "IDs") + if err = diq.Select(discordinteraction.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (diq *DiscordInteractionQuery) IDsX(ctx context.Context) []string { + ids, err := diq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (diq *DiscordInteractionQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, diq.ctx, "Count") + if err := diq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, diq, querierCount[*DiscordInteractionQuery](), diq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (diq *DiscordInteractionQuery) CountX(ctx context.Context) int { + count, err := diq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (diq *DiscordInteractionQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, diq.ctx, "Exist") + switch _, err := diq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (diq *DiscordInteractionQuery) ExistX(ctx context.Context) bool { + exist, err := diq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the DiscordInteractionQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (diq *DiscordInteractionQuery) Clone() *DiscordInteractionQuery { + if diq == nil { + return nil + } + return &DiscordInteractionQuery{ + config: diq.config, + ctx: diq.ctx.Clone(), + order: append([]discordinteraction.OrderOption{}, diq.order...), + inters: append([]Interceptor{}, diq.inters...), + predicates: append([]predicate.DiscordInteraction{}, diq.predicates...), + withUser: diq.withUser.Clone(), + // clone intermediate query. + sql: diq.sql.Clone(), + path: diq.path, + } +} + +// WithUser tells the query-builder to eager-load the nodes that are connected to +// the "user" edge. The optional arguments are used to configure the query builder of the edge. +func (diq *DiscordInteractionQuery) WithUser(opts ...func(*UserQuery)) *DiscordInteractionQuery { + query := (&UserClient{config: diq.config}).Query() + for _, opt := range opts { + opt(query) + } + diq.withUser = query + return diq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt int64 `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.DiscordInteraction.Query(). +// GroupBy(discordinteraction.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (diq *DiscordInteractionQuery) GroupBy(field string, fields ...string) *DiscordInteractionGroupBy { + diq.ctx.Fields = append([]string{field}, fields...) + grbuild := &DiscordInteractionGroupBy{build: diq} + grbuild.flds = &diq.ctx.Fields + grbuild.label = discordinteraction.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt int64 `json:"created_at,omitempty"` +// } +// +// client.DiscordInteraction.Query(). +// Select(discordinteraction.FieldCreatedAt). +// Scan(ctx, &v) +func (diq *DiscordInteractionQuery) Select(fields ...string) *DiscordInteractionSelect { + diq.ctx.Fields = append(diq.ctx.Fields, fields...) + sbuild := &DiscordInteractionSelect{DiscordInteractionQuery: diq} + sbuild.label = discordinteraction.Label + sbuild.flds, sbuild.scan = &diq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a DiscordInteractionSelect configured with the given aggregations. +func (diq *DiscordInteractionQuery) Aggregate(fns ...AggregateFunc) *DiscordInteractionSelect { + return diq.Select().Aggregate(fns...) +} + +func (diq *DiscordInteractionQuery) prepareQuery(ctx context.Context) error { + for _, inter := range diq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, diq); err != nil { + return err + } + } + } + for _, f := range diq.ctx.Fields { + if !discordinteraction.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if diq.path != nil { + prev, err := diq.path(ctx) + if err != nil { + return err + } + diq.sql = prev + } + return nil +} + +func (diq *DiscordInteractionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*DiscordInteraction, error) { + var ( + nodes = []*DiscordInteraction{} + _spec = diq.querySpec() + loadedTypes = [1]bool{ + diq.withUser != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*DiscordInteraction).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &DiscordInteraction{config: diq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, diq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := diq.withUser; query != nil { + if err := diq.loadUser(ctx, query, nodes, nil, + func(n *DiscordInteraction, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (diq *DiscordInteractionQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*DiscordInteraction, init func(*DiscordInteraction), assign func(*DiscordInteraction, *User)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*DiscordInteraction) + for i := range nodes { + fk := nodes[i].UserID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (diq *DiscordInteractionQuery) sqlCount(ctx context.Context) (int, error) { + _spec := diq.querySpec() + _spec.Node.Columns = diq.ctx.Fields + if len(diq.ctx.Fields) > 0 { + _spec.Unique = diq.ctx.Unique != nil && *diq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, diq.driver, _spec) +} + +func (diq *DiscordInteractionQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(discordinteraction.Table, discordinteraction.Columns, sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString)) + _spec.From = diq.sql + if unique := diq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if diq.path != nil { + _spec.Unique = true + } + if fields := diq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, discordinteraction.FieldID) + for i := range fields { + if fields[i] != discordinteraction.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if diq.withUser != nil { + _spec.Node.AddColumnOnce(discordinteraction.FieldUserID) + } + } + if ps := diq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := diq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := diq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := diq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (diq *DiscordInteractionQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(diq.driver.Dialect()) + t1 := builder.Table(discordinteraction.Table) + columns := diq.ctx.Fields + if len(columns) == 0 { + columns = discordinteraction.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if diq.sql != nil { + selector = diq.sql + selector.Select(selector.Columns(columns...)...) + } + if diq.ctx.Unique != nil && *diq.ctx.Unique { + selector.Distinct() + } + for _, p := range diq.predicates { + p(selector) + } + for _, p := range diq.order { + p(selector) + } + if offset := diq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := diq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// DiscordInteractionGroupBy is the group-by builder for DiscordInteraction entities. +type DiscordInteractionGroupBy struct { + selector + build *DiscordInteractionQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (digb *DiscordInteractionGroupBy) Aggregate(fns ...AggregateFunc) *DiscordInteractionGroupBy { + digb.fns = append(digb.fns, fns...) + return digb +} + +// Scan applies the selector query and scans the result into the given value. +func (digb *DiscordInteractionGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, digb.build.ctx, "GroupBy") + if err := digb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*DiscordInteractionQuery, *DiscordInteractionGroupBy](ctx, digb.build, digb, digb.build.inters, v) +} + +func (digb *DiscordInteractionGroupBy) sqlScan(ctx context.Context, root *DiscordInteractionQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(digb.fns)) + for _, fn := range digb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*digb.flds)+len(digb.fns)) + for _, f := range *digb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*digb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := digb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// DiscordInteractionSelect is the builder for selecting fields of DiscordInteraction entities. +type DiscordInteractionSelect struct { + *DiscordInteractionQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (dis *DiscordInteractionSelect) Aggregate(fns ...AggregateFunc) *DiscordInteractionSelect { + dis.fns = append(dis.fns, fns...) + return dis +} + +// Scan applies the selector query and scans the result into the given value. +func (dis *DiscordInteractionSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, dis.ctx, "Select") + if err := dis.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*DiscordInteractionQuery, *DiscordInteractionSelect](ctx, dis.DiscordInteractionQuery, dis, dis.inters, v) +} + +func (dis *DiscordInteractionSelect) sqlScan(ctx context.Context, root *DiscordInteractionQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(dis.fns)) + for _, fn := range dis.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*dis.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := dis.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/database/ent/db/discordinteraction_update.go b/internal/database/ent/db/discordinteraction_update.go new file mode 100644 index 00000000..3b039729 --- /dev/null +++ b/internal/database/ent/db/discordinteraction_update.go @@ -0,0 +1,454 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/models" +) + +// DiscordInteractionUpdate is the builder for updating DiscordInteraction entities. +type DiscordInteractionUpdate struct { + config + hooks []Hook + mutation *DiscordInteractionMutation +} + +// Where appends a list predicates to the DiscordInteractionUpdate builder. +func (diu *DiscordInteractionUpdate) Where(ps ...predicate.DiscordInteraction) *DiscordInteractionUpdate { + diu.mutation.Where(ps...) + return diu +} + +// SetUpdatedAt sets the "updated_at" field. +func (diu *DiscordInteractionUpdate) SetUpdatedAt(i int64) *DiscordInteractionUpdate { + diu.mutation.ResetUpdatedAt() + diu.mutation.SetUpdatedAt(i) + return diu +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (diu *DiscordInteractionUpdate) AddUpdatedAt(i int64) *DiscordInteractionUpdate { + diu.mutation.AddUpdatedAt(i) + return diu +} + +// SetCommand sets the "command" field. +func (diu *DiscordInteractionUpdate) SetCommand(s string) *DiscordInteractionUpdate { + diu.mutation.SetCommand(s) + return diu +} + +// SetNillableCommand sets the "command" field if the given value is not nil. +func (diu *DiscordInteractionUpdate) SetNillableCommand(s *string) *DiscordInteractionUpdate { + if s != nil { + diu.SetCommand(*s) + } + return diu +} + +// SetReferenceID sets the "reference_id" field. +func (diu *DiscordInteractionUpdate) SetReferenceID(s string) *DiscordInteractionUpdate { + diu.mutation.SetReferenceID(s) + return diu +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (diu *DiscordInteractionUpdate) SetNillableReferenceID(s *string) *DiscordInteractionUpdate { + if s != nil { + diu.SetReferenceID(*s) + } + return diu +} + +// SetType sets the "type" field. +func (diu *DiscordInteractionUpdate) SetType(mit models.DiscordInteractionType) *DiscordInteractionUpdate { + diu.mutation.SetType(mit) + return diu +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (diu *DiscordInteractionUpdate) SetNillableType(mit *models.DiscordInteractionType) *DiscordInteractionUpdate { + if mit != nil { + diu.SetType(*mit) + } + return diu +} + +// SetLocale sets the "locale" field. +func (diu *DiscordInteractionUpdate) SetLocale(s string) *DiscordInteractionUpdate { + diu.mutation.SetLocale(s) + return diu +} + +// SetNillableLocale sets the "locale" field if the given value is not nil. +func (diu *DiscordInteractionUpdate) SetNillableLocale(s *string) *DiscordInteractionUpdate { + if s != nil { + diu.SetLocale(*s) + } + return diu +} + +// SetOptions sets the "options" field. +func (diu *DiscordInteractionUpdate) SetOptions(mio models.DiscordInteractionOptions) *DiscordInteractionUpdate { + diu.mutation.SetOptions(mio) + return diu +} + +// SetNillableOptions sets the "options" field if the given value is not nil. +func (diu *DiscordInteractionUpdate) SetNillableOptions(mio *models.DiscordInteractionOptions) *DiscordInteractionUpdate { + if mio != nil { + diu.SetOptions(*mio) + } + return diu +} + +// Mutation returns the DiscordInteractionMutation object of the builder. +func (diu *DiscordInteractionUpdate) Mutation() *DiscordInteractionMutation { + return diu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (diu *DiscordInteractionUpdate) Save(ctx context.Context) (int, error) { + diu.defaults() + return withHooks(ctx, diu.sqlSave, diu.mutation, diu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (diu *DiscordInteractionUpdate) SaveX(ctx context.Context) int { + affected, err := diu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (diu *DiscordInteractionUpdate) Exec(ctx context.Context) error { + _, err := diu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (diu *DiscordInteractionUpdate) ExecX(ctx context.Context) { + if err := diu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (diu *DiscordInteractionUpdate) defaults() { + if _, ok := diu.mutation.UpdatedAt(); !ok { + v := discordinteraction.UpdateDefaultUpdatedAt() + diu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (diu *DiscordInteractionUpdate) check() error { + if v, ok := diu.mutation.Command(); ok { + if err := discordinteraction.CommandValidator(v); err != nil { + return &ValidationError{Name: "command", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.command": %w`, err)} + } + } + if v, ok := diu.mutation.ReferenceID(); ok { + if err := discordinteraction.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.reference_id": %w`, err)} + } + } + if v, ok := diu.mutation.GetType(); ok { + if err := discordinteraction.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.type": %w`, err)} + } + } + if _, ok := diu.mutation.UserID(); diu.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "DiscordInteraction.user"`) + } + return nil +} + +func (diu *DiscordInteractionUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := diu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(discordinteraction.Table, discordinteraction.Columns, sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString)) + if ps := diu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := diu.mutation.UpdatedAt(); ok { + _spec.SetField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) + } + if value, ok := diu.mutation.AddedUpdatedAt(); ok { + _spec.AddField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) + } + if value, ok := diu.mutation.Command(); ok { + _spec.SetField(discordinteraction.FieldCommand, field.TypeString, value) + } + if value, ok := diu.mutation.ReferenceID(); ok { + _spec.SetField(discordinteraction.FieldReferenceID, field.TypeString, value) + } + if value, ok := diu.mutation.GetType(); ok { + _spec.SetField(discordinteraction.FieldType, field.TypeEnum, value) + } + if value, ok := diu.mutation.Locale(); ok { + _spec.SetField(discordinteraction.FieldLocale, field.TypeString, value) + } + if value, ok := diu.mutation.Options(); ok { + _spec.SetField(discordinteraction.FieldOptions, field.TypeJSON, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, diu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{discordinteraction.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + diu.mutation.done = true + return n, nil +} + +// DiscordInteractionUpdateOne is the builder for updating a single DiscordInteraction entity. +type DiscordInteractionUpdateOne struct { + config + fields []string + hooks []Hook + mutation *DiscordInteractionMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (diuo *DiscordInteractionUpdateOne) SetUpdatedAt(i int64) *DiscordInteractionUpdateOne { + diuo.mutation.ResetUpdatedAt() + diuo.mutation.SetUpdatedAt(i) + return diuo +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (diuo *DiscordInteractionUpdateOne) AddUpdatedAt(i int64) *DiscordInteractionUpdateOne { + diuo.mutation.AddUpdatedAt(i) + return diuo +} + +// SetCommand sets the "command" field. +func (diuo *DiscordInteractionUpdateOne) SetCommand(s string) *DiscordInteractionUpdateOne { + diuo.mutation.SetCommand(s) + return diuo +} + +// SetNillableCommand sets the "command" field if the given value is not nil. +func (diuo *DiscordInteractionUpdateOne) SetNillableCommand(s *string) *DiscordInteractionUpdateOne { + if s != nil { + diuo.SetCommand(*s) + } + return diuo +} + +// SetReferenceID sets the "reference_id" field. +func (diuo *DiscordInteractionUpdateOne) SetReferenceID(s string) *DiscordInteractionUpdateOne { + diuo.mutation.SetReferenceID(s) + return diuo +} + +// SetNillableReferenceID sets the "reference_id" field if the given value is not nil. +func (diuo *DiscordInteractionUpdateOne) SetNillableReferenceID(s *string) *DiscordInteractionUpdateOne { + if s != nil { + diuo.SetReferenceID(*s) + } + return diuo +} + +// SetType sets the "type" field. +func (diuo *DiscordInteractionUpdateOne) SetType(mit models.DiscordInteractionType) *DiscordInteractionUpdateOne { + diuo.mutation.SetType(mit) + return diuo +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (diuo *DiscordInteractionUpdateOne) SetNillableType(mit *models.DiscordInteractionType) *DiscordInteractionUpdateOne { + if mit != nil { + diuo.SetType(*mit) + } + return diuo +} + +// SetLocale sets the "locale" field. +func (diuo *DiscordInteractionUpdateOne) SetLocale(s string) *DiscordInteractionUpdateOne { + diuo.mutation.SetLocale(s) + return diuo +} + +// SetNillableLocale sets the "locale" field if the given value is not nil. +func (diuo *DiscordInteractionUpdateOne) SetNillableLocale(s *string) *DiscordInteractionUpdateOne { + if s != nil { + diuo.SetLocale(*s) + } + return diuo +} + +// SetOptions sets the "options" field. +func (diuo *DiscordInteractionUpdateOne) SetOptions(mio models.DiscordInteractionOptions) *DiscordInteractionUpdateOne { + diuo.mutation.SetOptions(mio) + return diuo +} + +// SetNillableOptions sets the "options" field if the given value is not nil. +func (diuo *DiscordInteractionUpdateOne) SetNillableOptions(mio *models.DiscordInteractionOptions) *DiscordInteractionUpdateOne { + if mio != nil { + diuo.SetOptions(*mio) + } + return diuo +} + +// Mutation returns the DiscordInteractionMutation object of the builder. +func (diuo *DiscordInteractionUpdateOne) Mutation() *DiscordInteractionMutation { + return diuo.mutation +} + +// Where appends a list predicates to the DiscordInteractionUpdate builder. +func (diuo *DiscordInteractionUpdateOne) Where(ps ...predicate.DiscordInteraction) *DiscordInteractionUpdateOne { + diuo.mutation.Where(ps...) + return diuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (diuo *DiscordInteractionUpdateOne) Select(field string, fields ...string) *DiscordInteractionUpdateOne { + diuo.fields = append([]string{field}, fields...) + return diuo +} + +// Save executes the query and returns the updated DiscordInteraction entity. +func (diuo *DiscordInteractionUpdateOne) Save(ctx context.Context) (*DiscordInteraction, error) { + diuo.defaults() + return withHooks(ctx, diuo.sqlSave, diuo.mutation, diuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (diuo *DiscordInteractionUpdateOne) SaveX(ctx context.Context) *DiscordInteraction { + node, err := diuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (diuo *DiscordInteractionUpdateOne) Exec(ctx context.Context) error { + _, err := diuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (diuo *DiscordInteractionUpdateOne) ExecX(ctx context.Context) { + if err := diuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (diuo *DiscordInteractionUpdateOne) defaults() { + if _, ok := diuo.mutation.UpdatedAt(); !ok { + v := discordinteraction.UpdateDefaultUpdatedAt() + diuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (diuo *DiscordInteractionUpdateOne) check() error { + if v, ok := diuo.mutation.Command(); ok { + if err := discordinteraction.CommandValidator(v); err != nil { + return &ValidationError{Name: "command", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.command": %w`, err)} + } + } + if v, ok := diuo.mutation.ReferenceID(); ok { + if err := discordinteraction.ReferenceIDValidator(v); err != nil { + return &ValidationError{Name: "reference_id", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.reference_id": %w`, err)} + } + } + if v, ok := diuo.mutation.GetType(); ok { + if err := discordinteraction.TypeValidator(v); err != nil { + return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "DiscordInteraction.type": %w`, err)} + } + } + if _, ok := diuo.mutation.UserID(); diuo.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "DiscordInteraction.user"`) + } + return nil +} + +func (diuo *DiscordInteractionUpdateOne) sqlSave(ctx context.Context) (_node *DiscordInteraction, err error) { + if err := diuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(discordinteraction.Table, discordinteraction.Columns, sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString)) + id, ok := diuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "DiscordInteraction.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := diuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, discordinteraction.FieldID) + for _, f := range fields { + if !discordinteraction.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != discordinteraction.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := diuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := diuo.mutation.UpdatedAt(); ok { + _spec.SetField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) + } + if value, ok := diuo.mutation.AddedUpdatedAt(); ok { + _spec.AddField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) + } + if value, ok := diuo.mutation.Command(); ok { + _spec.SetField(discordinteraction.FieldCommand, field.TypeString, value) + } + if value, ok := diuo.mutation.ReferenceID(); ok { + _spec.SetField(discordinteraction.FieldReferenceID, field.TypeString, value) + } + if value, ok := diuo.mutation.GetType(); ok { + _spec.SetField(discordinteraction.FieldType, field.TypeEnum, value) + } + if value, ok := diuo.mutation.Locale(); ok { + _spec.SetField(discordinteraction.FieldLocale, field.TypeString, value) + } + if value, ok := diuo.mutation.Options(); ok { + _spec.SetField(discordinteraction.FieldOptions, field.TypeJSON, value) + } + _node = &DiscordInteraction{config: diuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, diuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{discordinteraction.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + diuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/ent.go b/internal/database/ent/db/ent.go index bcaa9136..22bd881e 100644 --- a/internal/database/ent/db/ent.go +++ b/internal/database/ent/db/ent.go @@ -19,6 +19,7 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" "github.com/cufee/aftermath/internal/database/ent/db/clan" "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -93,6 +94,7 @@ func checkColumn(table, column string) error { applicationcommand.Table: applicationcommand.ValidColumn, clan.Table: clan.ValidColumn, crontask.Table: crontask.ValidColumn, + discordinteraction.Table: discordinteraction.ValidColumn, user.Table: user.ValidColumn, userconnection.Table: userconnection.ValidColumn, usercontent.Table: usercontent.ValidColumn, diff --git a/internal/database/ent/db/hook/hook.go b/internal/database/ent/db/hook/hook.go index fe0ba69f..f3a9e239 100644 --- a/internal/database/ent/db/hook/hook.go +++ b/internal/database/ent/db/hook/hook.go @@ -93,6 +93,18 @@ func (f CronTaskFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, erro return nil, fmt.Errorf("unexpected mutation type %T. expect *db.CronTaskMutation", m) } +// The DiscordInteractionFunc type is an adapter to allow the use of ordinary +// function as DiscordInteraction mutator. +type DiscordInteractionFunc func(context.Context, *db.DiscordInteractionMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f DiscordInteractionFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.DiscordInteractionMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.DiscordInteractionMutation", m) +} + // The UserFunc type is an adapter to allow the use of ordinary // function as User mutator. type UserFunc func(context.Context, *db.UserMutation) (db.Value, error) diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index 29a7f768..9432fc41 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -298,6 +298,59 @@ var ( }, }, } + // DiscordInteractionsColumns holds the columns for the "discord_interactions" table. + DiscordInteractionsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeInt64}, + {Name: "updated_at", Type: field.TypeInt64}, + {Name: "command", Type: field.TypeString}, + {Name: "reference_id", Type: field.TypeString}, + {Name: "type", Type: field.TypeEnum, Enums: []string{"stats"}}, + {Name: "locale", Type: field.TypeString}, + {Name: "options", Type: field.TypeJSON}, + {Name: "user_id", Type: field.TypeString}, + } + // DiscordInteractionsTable holds the schema information for the "discord_interactions" table. + DiscordInteractionsTable = &schema.Table{ + Name: "discord_interactions", + Columns: DiscordInteractionsColumns, + PrimaryKey: []*schema.Column{DiscordInteractionsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "discord_interactions_users_discord_interactions", + Columns: []*schema.Column{DiscordInteractionsColumns[8]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "discordinteraction_id", + Unique: false, + Columns: []*schema.Column{DiscordInteractionsColumns[0]}, + }, + { + Name: "discordinteraction_command", + Unique: false, + Columns: []*schema.Column{DiscordInteractionsColumns[3]}, + }, + { + Name: "discordinteraction_user_id", + Unique: false, + Columns: []*schema.Column{DiscordInteractionsColumns[8]}, + }, + { + Name: "discordinteraction_user_id_type", + Unique: false, + Columns: []*schema.Column{DiscordInteractionsColumns[8], DiscordInteractionsColumns[5]}, + }, + { + Name: "discordinteraction_reference_id", + Unique: false, + Columns: []*schema.Column{DiscordInteractionsColumns[4]}, + }, + }, + } // UsersColumns holds the columns for the "users" table. UsersColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, @@ -584,6 +637,7 @@ var ( ApplicationCommandsTable, ClansTable, CronTasksTable, + DiscordInteractionsTable, UsersTable, UserConnectionsTable, UserContentsTable, @@ -598,6 +652,7 @@ func init() { AccountsTable.ForeignKeys[0].RefTable = ClansTable AccountSnapshotsTable.ForeignKeys[0].RefTable = AccountsTable AchievementsSnapshotsTable.ForeignKeys[0].RefTable = AccountsTable + DiscordInteractionsTable.ForeignKeys[0].RefTable = UsersTable UserConnectionsTable.ForeignKeys[0].RefTable = UsersTable UserContentsTable.ForeignKeys[0].RefTable = UsersTable UserSubscriptionsTable.ForeignKeys[0].RefTable = UsersTable diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go index 49983f2e..d34605bc 100644 --- a/internal/database/ent/db/mutation.go +++ b/internal/database/ent/db/mutation.go @@ -17,6 +17,7 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" "github.com/cufee/aftermath/internal/database/ent/db/clan" "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/predicate" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" @@ -46,6 +47,7 @@ const ( TypeApplicationCommand = "ApplicationCommand" TypeClan = "Clan" TypeCronTask = "CronTask" + TypeDiscordInteraction = "DiscordInteraction" TypeUser = "User" TypeUserConnection = "UserConnection" TypeUserContent = "UserContent" @@ -6209,32 +6211,868 @@ func (m *CronTaskMutation) ResetEdge(name string) error { return fmt.Errorf("unknown CronTask edge %s", name) } +// DiscordInteractionMutation represents an operation that mutates the DiscordInteraction nodes in the graph. +type DiscordInteractionMutation struct { + config + op Op + typ string + id *string + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 + command *string + reference_id *string + _type *models.DiscordInteractionType + locale *string + options *models.DiscordInteractionOptions + clearedFields map[string]struct{} + user *string + cleareduser bool + done bool + oldValue func(context.Context) (*DiscordInteraction, error) + predicates []predicate.DiscordInteraction +} + +var _ ent.Mutation = (*DiscordInteractionMutation)(nil) + +// discordinteractionOption allows management of the mutation configuration using functional options. +type discordinteractionOption func(*DiscordInteractionMutation) + +// newDiscordInteractionMutation creates new mutation for the DiscordInteraction entity. +func newDiscordInteractionMutation(c config, op Op, opts ...discordinteractionOption) *DiscordInteractionMutation { + m := &DiscordInteractionMutation{ + config: c, + op: op, + typ: TypeDiscordInteraction, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withDiscordInteractionID sets the ID field of the mutation. +func withDiscordInteractionID(id string) discordinteractionOption { + return func(m *DiscordInteractionMutation) { + var ( + err error + once sync.Once + value *DiscordInteraction + ) + m.oldValue = func(ctx context.Context) (*DiscordInteraction, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().DiscordInteraction.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withDiscordInteraction sets the old DiscordInteraction of the mutation. +func withDiscordInteraction(node *DiscordInteraction) discordinteractionOption { + return func(m *DiscordInteractionMutation) { + m.oldValue = func(context.Context) (*DiscordInteraction, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m DiscordInteractionMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m DiscordInteractionMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of DiscordInteraction entities. +func (m *DiscordInteractionMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *DiscordInteractionMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *DiscordInteractionMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().DiscordInteraction.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *DiscordInteractionMutation) SetCreatedAt(i int64) { + m.created_at = &i + m.addcreated_at = nil +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *DiscordInteractionMutation) CreatedAt() (r int64, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DiscordInteractionMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// AddCreatedAt adds i to the "created_at" field. +func (m *DiscordInteractionMutation) AddCreatedAt(i int64) { + if m.addcreated_at != nil { + *m.addcreated_at += i + } else { + m.addcreated_at = &i + } +} + +// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. +func (m *DiscordInteractionMutation) AddedCreatedAt() (r int64, exists bool) { + v := m.addcreated_at + if v == nil { + return + } + return *v, true +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *DiscordInteractionMutation) ResetCreatedAt() { + m.created_at = nil + m.addcreated_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *DiscordInteractionMutation) SetUpdatedAt(i int64) { + m.updated_at = &i + m.addupdated_at = nil +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *DiscordInteractionMutation) UpdatedAt() (r int64, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DiscordInteractionMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// AddUpdatedAt adds i to the "updated_at" field. +func (m *DiscordInteractionMutation) AddUpdatedAt(i int64) { + if m.addupdated_at != nil { + *m.addupdated_at += i + } else { + m.addupdated_at = &i + } +} + +// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. +func (m *DiscordInteractionMutation) AddedUpdatedAt() (r int64, exists bool) { + v := m.addupdated_at + if v == nil { + return + } + return *v, true +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *DiscordInteractionMutation) ResetUpdatedAt() { + m.updated_at = nil + m.addupdated_at = nil +} + +// SetCommand sets the "command" field. +func (m *DiscordInteractionMutation) SetCommand(s string) { + m.command = &s +} + +// Command returns the value of the "command" field in the mutation. +func (m *DiscordInteractionMutation) Command() (r string, exists bool) { + v := m.command + if v == nil { + return + } + return *v, true +} + +// OldCommand returns the old "command" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DiscordInteractionMutation) OldCommand(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCommand is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCommand requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCommand: %w", err) + } + return oldValue.Command, nil +} + +// ResetCommand resets all changes to the "command" field. +func (m *DiscordInteractionMutation) ResetCommand() { + m.command = nil +} + +// SetUserID sets the "user_id" field. +func (m *DiscordInteractionMutation) SetUserID(s string) { + m.user = &s +} + +// UserID returns the value of the "user_id" field in the mutation. +func (m *DiscordInteractionMutation) UserID() (r string, exists bool) { + v := m.user + if v == nil { + return + } + return *v, true +} + +// OldUserID returns the old "user_id" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DiscordInteractionMutation) OldUserID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserID: %w", err) + } + return oldValue.UserID, nil +} + +// ResetUserID resets all changes to the "user_id" field. +func (m *DiscordInteractionMutation) ResetUserID() { + m.user = nil +} + +// SetReferenceID sets the "reference_id" field. +func (m *DiscordInteractionMutation) SetReferenceID(s string) { + m.reference_id = &s +} + +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *DiscordInteractionMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id + if v == nil { + return + } + return *v, true +} + +// OldReferenceID returns the old "reference_id" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DiscordInteractionMutation) OldReferenceID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferenceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + } + return oldValue.ReferenceID, nil +} + +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *DiscordInteractionMutation) ResetReferenceID() { + m.reference_id = nil +} + +// SetType sets the "type" field. +func (m *DiscordInteractionMutation) SetType(mit models.DiscordInteractionType) { + m._type = &mit +} + +// GetType returns the value of the "type" field in the mutation. +func (m *DiscordInteractionMutation) GetType() (r models.DiscordInteractionType, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DiscordInteractionMutation) OldType(ctx context.Context) (v models.DiscordInteractionType, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ResetType resets all changes to the "type" field. +func (m *DiscordInteractionMutation) ResetType() { + m._type = nil +} + +// SetLocale sets the "locale" field. +func (m *DiscordInteractionMutation) SetLocale(s string) { + m.locale = &s +} + +// Locale returns the value of the "locale" field in the mutation. +func (m *DiscordInteractionMutation) Locale() (r string, exists bool) { + v := m.locale + if v == nil { + return + } + return *v, true +} + +// OldLocale returns the old "locale" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DiscordInteractionMutation) OldLocale(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLocale is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLocale requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLocale: %w", err) + } + return oldValue.Locale, nil +} + +// ResetLocale resets all changes to the "locale" field. +func (m *DiscordInteractionMutation) ResetLocale() { + m.locale = nil +} + +// SetOptions sets the "options" field. +func (m *DiscordInteractionMutation) SetOptions(mio models.DiscordInteractionOptions) { + m.options = &mio +} + +// Options returns the value of the "options" field in the mutation. +func (m *DiscordInteractionMutation) Options() (r models.DiscordInteractionOptions, exists bool) { + v := m.options + if v == nil { + return + } + return *v, true +} + +// OldOptions returns the old "options" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DiscordInteractionMutation) OldOptions(ctx context.Context) (v models.DiscordInteractionOptions, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldOptions is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldOptions requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldOptions: %w", err) + } + return oldValue.Options, nil +} + +// ResetOptions resets all changes to the "options" field. +func (m *DiscordInteractionMutation) ResetOptions() { + m.options = nil +} + +// ClearUser clears the "user" edge to the User entity. +func (m *DiscordInteractionMutation) ClearUser() { + m.cleareduser = true + m.clearedFields[discordinteraction.FieldUserID] = struct{}{} +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *DiscordInteractionMutation) UserCleared() bool { + return m.cleareduser +} + +// UserIDs returns the "user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserID instead. It exists only for internal usage by the builders. +func (m *DiscordInteractionMutation) UserIDs() (ids []string) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *DiscordInteractionMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// Where appends a list predicates to the DiscordInteractionMutation builder. +func (m *DiscordInteractionMutation) Where(ps ...predicate.DiscordInteraction) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the DiscordInteractionMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *DiscordInteractionMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.DiscordInteraction, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *DiscordInteractionMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *DiscordInteractionMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (DiscordInteraction). +func (m *DiscordInteractionMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *DiscordInteractionMutation) Fields() []string { + fields := make([]string, 0, 8) + if m.created_at != nil { + fields = append(fields, discordinteraction.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, discordinteraction.FieldUpdatedAt) + } + if m.command != nil { + fields = append(fields, discordinteraction.FieldCommand) + } + if m.user != nil { + fields = append(fields, discordinteraction.FieldUserID) + } + if m.reference_id != nil { + fields = append(fields, discordinteraction.FieldReferenceID) + } + if m._type != nil { + fields = append(fields, discordinteraction.FieldType) + } + if m.locale != nil { + fields = append(fields, discordinteraction.FieldLocale) + } + if m.options != nil { + fields = append(fields, discordinteraction.FieldOptions) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *DiscordInteractionMutation) Field(name string) (ent.Value, bool) { + switch name { + case discordinteraction.FieldCreatedAt: + return m.CreatedAt() + case discordinteraction.FieldUpdatedAt: + return m.UpdatedAt() + case discordinteraction.FieldCommand: + return m.Command() + case discordinteraction.FieldUserID: + return m.UserID() + case discordinteraction.FieldReferenceID: + return m.ReferenceID() + case discordinteraction.FieldType: + return m.GetType() + case discordinteraction.FieldLocale: + return m.Locale() + case discordinteraction.FieldOptions: + return m.Options() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *DiscordInteractionMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case discordinteraction.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case discordinteraction.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case discordinteraction.FieldCommand: + return m.OldCommand(ctx) + case discordinteraction.FieldUserID: + return m.OldUserID(ctx) + case discordinteraction.FieldReferenceID: + return m.OldReferenceID(ctx) + case discordinteraction.FieldType: + return m.OldType(ctx) + case discordinteraction.FieldLocale: + return m.OldLocale(ctx) + case discordinteraction.FieldOptions: + return m.OldOptions(ctx) + } + return nil, fmt.Errorf("unknown DiscordInteraction field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *DiscordInteractionMutation) SetField(name string, value ent.Value) error { + switch name { + case discordinteraction.FieldCreatedAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case discordinteraction.FieldUpdatedAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case discordinteraction.FieldCommand: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCommand(v) + return nil + case discordinteraction.FieldUserID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserID(v) + return nil + case discordinteraction.FieldReferenceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferenceID(v) + return nil + case discordinteraction.FieldType: + v, ok := value.(models.DiscordInteractionType) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case discordinteraction.FieldLocale: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLocale(v) + return nil + case discordinteraction.FieldOptions: + v, ok := value.(models.DiscordInteractionOptions) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOptions(v) + return nil + } + return fmt.Errorf("unknown DiscordInteraction field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *DiscordInteractionMutation) AddedFields() []string { + var fields []string + if m.addcreated_at != nil { + fields = append(fields, discordinteraction.FieldCreatedAt) + } + if m.addupdated_at != nil { + fields = append(fields, discordinteraction.FieldUpdatedAt) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *DiscordInteractionMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case discordinteraction.FieldCreatedAt: + return m.AddedCreatedAt() + case discordinteraction.FieldUpdatedAt: + return m.AddedUpdatedAt() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *DiscordInteractionMutation) AddField(name string, value ent.Value) error { + switch name { + case discordinteraction.FieldCreatedAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCreatedAt(v) + return nil + case discordinteraction.FieldUpdatedAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown DiscordInteraction numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *DiscordInteractionMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *DiscordInteractionMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *DiscordInteractionMutation) ClearField(name string) error { + return fmt.Errorf("unknown DiscordInteraction nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *DiscordInteractionMutation) ResetField(name string) error { + switch name { + case discordinteraction.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case discordinteraction.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case discordinteraction.FieldCommand: + m.ResetCommand() + return nil + case discordinteraction.FieldUserID: + m.ResetUserID() + return nil + case discordinteraction.FieldReferenceID: + m.ResetReferenceID() + return nil + case discordinteraction.FieldType: + m.ResetType() + return nil + case discordinteraction.FieldLocale: + m.ResetLocale() + return nil + case discordinteraction.FieldOptions: + m.ResetOptions() + return nil + } + return fmt.Errorf("unknown DiscordInteraction field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *DiscordInteractionMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.user != nil { + edges = append(edges, discordinteraction.EdgeUser) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *DiscordInteractionMutation) AddedIDs(name string) []ent.Value { + switch name { + case discordinteraction.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *DiscordInteractionMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *DiscordInteractionMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *DiscordInteractionMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.cleareduser { + edges = append(edges, discordinteraction.EdgeUser) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *DiscordInteractionMutation) EdgeCleared(name string) bool { + switch name { + case discordinteraction.EdgeUser: + return m.cleareduser + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *DiscordInteractionMutation) ClearEdge(name string) error { + switch name { + case discordinteraction.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown DiscordInteraction unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *DiscordInteractionMutation) ResetEdge(name string) error { + switch name { + case discordinteraction.EdgeUser: + m.ResetUser() + return nil + } + return fmt.Errorf("unknown DiscordInteraction edge %s", name) +} + // UserMutation represents an operation that mutates the User nodes in the graph. type UserMutation struct { config - op Op - typ string - id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 - permissions *string - feature_flags *[]string - appendfeature_flags []string - clearedFields map[string]struct{} - subscriptions map[string]struct{} - removedsubscriptions map[string]struct{} - clearedsubscriptions bool - connections map[string]struct{} - removedconnections map[string]struct{} - clearedconnections bool - content map[string]struct{} - removedcontent map[string]struct{} - clearedcontent bool - done bool - oldValue func(context.Context) (*User, error) - predicates []predicate.User + op Op + typ string + id *string + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 + permissions *string + feature_flags *[]string + appendfeature_flags []string + clearedFields map[string]struct{} + discord_interactions map[string]struct{} + removeddiscord_interactions map[string]struct{} + cleareddiscord_interactions bool + subscriptions map[string]struct{} + removedsubscriptions map[string]struct{} + clearedsubscriptions bool + connections map[string]struct{} + removedconnections map[string]struct{} + clearedconnections bool + content map[string]struct{} + removedcontent map[string]struct{} + clearedcontent bool + done bool + oldValue func(context.Context) (*User, error) + predicates []predicate.User } var _ ent.Mutation = (*UserMutation)(nil) @@ -6554,6 +7392,60 @@ func (m *UserMutation) ResetFeatureFlags() { delete(m.clearedFields, user.FieldFeatureFlags) } +// AddDiscordInteractionIDs adds the "discord_interactions" edge to the DiscordInteraction entity by ids. +func (m *UserMutation) AddDiscordInteractionIDs(ids ...string) { + if m.discord_interactions == nil { + m.discord_interactions = make(map[string]struct{}) + } + for i := range ids { + m.discord_interactions[ids[i]] = struct{}{} + } +} + +// ClearDiscordInteractions clears the "discord_interactions" edge to the DiscordInteraction entity. +func (m *UserMutation) ClearDiscordInteractions() { + m.cleareddiscord_interactions = true +} + +// DiscordInteractionsCleared reports if the "discord_interactions" edge to the DiscordInteraction entity was cleared. +func (m *UserMutation) DiscordInteractionsCleared() bool { + return m.cleareddiscord_interactions +} + +// RemoveDiscordInteractionIDs removes the "discord_interactions" edge to the DiscordInteraction entity by IDs. +func (m *UserMutation) RemoveDiscordInteractionIDs(ids ...string) { + if m.removeddiscord_interactions == nil { + m.removeddiscord_interactions = make(map[string]struct{}) + } + for i := range ids { + delete(m.discord_interactions, ids[i]) + m.removeddiscord_interactions[ids[i]] = struct{}{} + } +} + +// RemovedDiscordInteractions returns the removed IDs of the "discord_interactions" edge to the DiscordInteraction entity. +func (m *UserMutation) RemovedDiscordInteractionsIDs() (ids []string) { + for id := range m.removeddiscord_interactions { + ids = append(ids, id) + } + return +} + +// DiscordInteractionsIDs returns the "discord_interactions" edge IDs in the mutation. +func (m *UserMutation) DiscordInteractionsIDs() (ids []string) { + for id := range m.discord_interactions { + ids = append(ids, id) + } + return +} + +// ResetDiscordInteractions resets all changes to the "discord_interactions" edge. +func (m *UserMutation) ResetDiscordInteractions() { + m.discord_interactions = nil + m.cleareddiscord_interactions = false + m.removeddiscord_interactions = nil +} + // AddSubscriptionIDs adds the "subscriptions" edge to the UserSubscription entity by ids. func (m *UserMutation) AddSubscriptionIDs(ids ...string) { if m.subscriptions == nil { @@ -6936,7 +7828,10 @@ func (m *UserMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *UserMutation) AddedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) + if m.discord_interactions != nil { + edges = append(edges, user.EdgeDiscordInteractions) + } if m.subscriptions != nil { edges = append(edges, user.EdgeSubscriptions) } @@ -6953,6 +7848,12 @@ func (m *UserMutation) AddedEdges() []string { // name in this mutation. func (m *UserMutation) AddedIDs(name string) []ent.Value { switch name { + case user.EdgeDiscordInteractions: + ids := make([]ent.Value, 0, len(m.discord_interactions)) + for id := range m.discord_interactions { + ids = append(ids, id) + } + return ids case user.EdgeSubscriptions: ids := make([]ent.Value, 0, len(m.subscriptions)) for id := range m.subscriptions { @@ -6977,7 +7878,10 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value { // RemovedEdges returns all edge names that were removed in this mutation. func (m *UserMutation) RemovedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) + if m.removeddiscord_interactions != nil { + edges = append(edges, user.EdgeDiscordInteractions) + } if m.removedsubscriptions != nil { edges = append(edges, user.EdgeSubscriptions) } @@ -6994,6 +7898,12 @@ func (m *UserMutation) RemovedEdges() []string { // the given name in this mutation. func (m *UserMutation) RemovedIDs(name string) []ent.Value { switch name { + case user.EdgeDiscordInteractions: + ids := make([]ent.Value, 0, len(m.removeddiscord_interactions)) + for id := range m.removeddiscord_interactions { + ids = append(ids, id) + } + return ids case user.EdgeSubscriptions: ids := make([]ent.Value, 0, len(m.removedsubscriptions)) for id := range m.removedsubscriptions { @@ -7018,7 +7928,10 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value { // ClearedEdges returns all edge names that were cleared in this mutation. func (m *UserMutation) ClearedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) + if m.cleareddiscord_interactions { + edges = append(edges, user.EdgeDiscordInteractions) + } if m.clearedsubscriptions { edges = append(edges, user.EdgeSubscriptions) } @@ -7035,6 +7948,8 @@ func (m *UserMutation) ClearedEdges() []string { // was cleared in this mutation. func (m *UserMutation) EdgeCleared(name string) bool { switch name { + case user.EdgeDiscordInteractions: + return m.cleareddiscord_interactions case user.EdgeSubscriptions: return m.clearedsubscriptions case user.EdgeConnections: @@ -7057,6 +7972,9 @@ func (m *UserMutation) ClearEdge(name string) error { // It returns an error if the edge is not defined in the schema. func (m *UserMutation) ResetEdge(name string) error { switch name { + case user.EdgeDiscordInteractions: + m.ResetDiscordInteractions() + return nil case user.EdgeSubscriptions: m.ResetSubscriptions() return nil diff --git a/internal/database/ent/db/predicate/predicate.go b/internal/database/ent/db/predicate/predicate.go index 899084f2..9814087f 100644 --- a/internal/database/ent/db/predicate/predicate.go +++ b/internal/database/ent/db/predicate/predicate.go @@ -27,6 +27,9 @@ type Clan func(*sql.Selector) // CronTask is the predicate function for crontask builders. type CronTask func(*sql.Selector) +// DiscordInteraction is the predicate function for discordinteraction builders. +type DiscordInteraction func(*sql.Selector) + // User is the predicate function for user builders. type User func(*sql.Selector) diff --git a/internal/database/ent/db/runtime.go b/internal/database/ent/db/runtime.go index 2b639c03..c760c687 100644 --- a/internal/database/ent/db/runtime.go +++ b/internal/database/ent/db/runtime.go @@ -10,6 +10,7 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" "github.com/cufee/aftermath/internal/database/ent/db/clan" "github.com/cufee/aftermath/internal/database/ent/db/crontask" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -202,6 +203,34 @@ func init() { crontaskDescID := crontaskFields[0].Descriptor() // crontask.DefaultID holds the default value on creation for the id field. crontask.DefaultID = crontaskDescID.Default.(func() string) + discordinteractionFields := schema.DiscordInteraction{}.Fields() + _ = discordinteractionFields + // discordinteractionDescCreatedAt is the schema descriptor for created_at field. + discordinteractionDescCreatedAt := discordinteractionFields[1].Descriptor() + // discordinteraction.DefaultCreatedAt holds the default value on creation for the created_at field. + discordinteraction.DefaultCreatedAt = discordinteractionDescCreatedAt.Default.(func() int64) + // discordinteractionDescUpdatedAt is the schema descriptor for updated_at field. + discordinteractionDescUpdatedAt := discordinteractionFields[2].Descriptor() + // discordinteraction.DefaultUpdatedAt holds the default value on creation for the updated_at field. + discordinteraction.DefaultUpdatedAt = discordinteractionDescUpdatedAt.Default.(func() int64) + // discordinteraction.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + discordinteraction.UpdateDefaultUpdatedAt = discordinteractionDescUpdatedAt.UpdateDefault.(func() int64) + // discordinteractionDescCommand is the schema descriptor for command field. + discordinteractionDescCommand := discordinteractionFields[3].Descriptor() + // discordinteraction.CommandValidator is a validator for the "command" field. It is called by the builders before save. + discordinteraction.CommandValidator = discordinteractionDescCommand.Validators[0].(func(string) error) + // discordinteractionDescUserID is the schema descriptor for user_id field. + discordinteractionDescUserID := discordinteractionFields[4].Descriptor() + // discordinteraction.UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. + discordinteraction.UserIDValidator = discordinteractionDescUserID.Validators[0].(func(string) error) + // discordinteractionDescReferenceID is the schema descriptor for reference_id field. + discordinteractionDescReferenceID := discordinteractionFields[5].Descriptor() + // discordinteraction.ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. + discordinteraction.ReferenceIDValidator = discordinteractionDescReferenceID.Validators[0].(func(string) error) + // discordinteractionDescID is the schema descriptor for id field. + discordinteractionDescID := discordinteractionFields[0].Descriptor() + // discordinteraction.DefaultID holds the default value on creation for the id field. + discordinteraction.DefaultID = discordinteractionDescID.Default.(func() string) userFields := schema.User{}.Fields() _ = userFields // userDescCreatedAt is the schema descriptor for created_at field. diff --git a/internal/database/ent/db/tx.go b/internal/database/ent/db/tx.go index f0ab91bb..9852c615 100644 --- a/internal/database/ent/db/tx.go +++ b/internal/database/ent/db/tx.go @@ -28,6 +28,8 @@ type Tx struct { Clan *ClanClient // CronTask is the client for interacting with the CronTask builders. CronTask *CronTaskClient + // DiscordInteraction is the client for interacting with the DiscordInteraction builders. + DiscordInteraction *DiscordInteractionClient // User is the client for interacting with the User builders. User *UserClient // UserConnection is the client for interacting with the UserConnection builders. @@ -180,6 +182,7 @@ func (tx *Tx) init() { tx.ApplicationCommand = NewApplicationCommandClient(tx.config) tx.Clan = NewClanClient(tx.config) tx.CronTask = NewCronTaskClient(tx.config) + tx.DiscordInteraction = NewDiscordInteractionClient(tx.config) tx.User = NewUserClient(tx.config) tx.UserConnection = NewUserConnectionClient(tx.config) tx.UserContent = NewUserContentClient(tx.config) diff --git a/internal/database/ent/db/user.go b/internal/database/ent/db/user.go index e8a51556..0c537425 100644 --- a/internal/database/ent/db/user.go +++ b/internal/database/ent/db/user.go @@ -33,6 +33,8 @@ type User struct { // UserEdges holds the relations/edges for other nodes in the graph. type UserEdges struct { + // DiscordInteractions holds the value of the discord_interactions edge. + DiscordInteractions []*DiscordInteraction `json:"discord_interactions,omitempty"` // Subscriptions holds the value of the subscriptions edge. Subscriptions []*UserSubscription `json:"subscriptions,omitempty"` // Connections holds the value of the connections edge. @@ -41,13 +43,22 @@ type UserEdges struct { Content []*UserContent `json:"content,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [3]bool + loadedTypes [4]bool +} + +// DiscordInteractionsOrErr returns the DiscordInteractions value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) DiscordInteractionsOrErr() ([]*DiscordInteraction, error) { + if e.loadedTypes[0] { + return e.DiscordInteractions, nil + } + return nil, &NotLoadedError{edge: "discord_interactions"} } // SubscriptionsOrErr returns the Subscriptions value or an error if the edge // was not loaded in eager-loading. func (e UserEdges) SubscriptionsOrErr() ([]*UserSubscription, error) { - if e.loadedTypes[0] { + if e.loadedTypes[1] { return e.Subscriptions, nil } return nil, &NotLoadedError{edge: "subscriptions"} @@ -56,7 +67,7 @@ func (e UserEdges) SubscriptionsOrErr() ([]*UserSubscription, error) { // ConnectionsOrErr returns the Connections value or an error if the edge // was not loaded in eager-loading. func (e UserEdges) ConnectionsOrErr() ([]*UserConnection, error) { - if e.loadedTypes[1] { + if e.loadedTypes[2] { return e.Connections, nil } return nil, &NotLoadedError{edge: "connections"} @@ -65,7 +76,7 @@ func (e UserEdges) ConnectionsOrErr() ([]*UserConnection, error) { // ContentOrErr returns the Content value or an error if the edge // was not loaded in eager-loading. func (e UserEdges) ContentOrErr() ([]*UserContent, error) { - if e.loadedTypes[2] { + if e.loadedTypes[3] { return e.Content, nil } return nil, &NotLoadedError{edge: "content"} @@ -142,6 +153,11 @@ func (u *User) Value(name string) (ent.Value, error) { return u.selectValues.Get(name) } +// QueryDiscordInteractions queries the "discord_interactions" edge of the User entity. +func (u *User) QueryDiscordInteractions() *DiscordInteractionQuery { + return NewUserClient(u.config).QueryDiscordInteractions(u) +} + // QuerySubscriptions queries the "subscriptions" edge of the User entity. func (u *User) QuerySubscriptions() *UserSubscriptionQuery { return NewUserClient(u.config).QuerySubscriptions(u) diff --git a/internal/database/ent/db/user/user.go b/internal/database/ent/db/user/user.go index b16e3e67..524bdf47 100644 --- a/internal/database/ent/db/user/user.go +++ b/internal/database/ent/db/user/user.go @@ -20,6 +20,8 @@ const ( FieldPermissions = "permissions" // FieldFeatureFlags holds the string denoting the feature_flags field in the database. FieldFeatureFlags = "feature_flags" + // EdgeDiscordInteractions holds the string denoting the discord_interactions edge name in mutations. + EdgeDiscordInteractions = "discord_interactions" // EdgeSubscriptions holds the string denoting the subscriptions edge name in mutations. EdgeSubscriptions = "subscriptions" // EdgeConnections holds the string denoting the connections edge name in mutations. @@ -28,6 +30,13 @@ const ( EdgeContent = "content" // Table holds the table name of the user in the database. Table = "users" + // DiscordInteractionsTable is the table that holds the discord_interactions relation/edge. + DiscordInteractionsTable = "discord_interactions" + // DiscordInteractionsInverseTable is the table name for the DiscordInteraction entity. + // It exists in this package in order to avoid circular dependency with the "discordinteraction" package. + DiscordInteractionsInverseTable = "discord_interactions" + // DiscordInteractionsColumn is the table column denoting the discord_interactions relation/edge. + DiscordInteractionsColumn = "user_id" // SubscriptionsTable is the table that holds the subscriptions relation/edge. SubscriptionsTable = "user_subscriptions" // SubscriptionsInverseTable is the table name for the UserSubscription entity. @@ -104,6 +113,20 @@ func ByPermissions(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldPermissions, opts...).ToFunc() } +// ByDiscordInteractionsCount orders the results by discord_interactions count. +func ByDiscordInteractionsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newDiscordInteractionsStep(), opts...) + } +} + +// ByDiscordInteractions orders the results by discord_interactions terms. +func ByDiscordInteractions(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newDiscordInteractionsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + // BySubscriptionsCount orders the results by subscriptions count. func BySubscriptionsCount(opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { @@ -145,6 +168,13 @@ func ByContent(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { sqlgraph.OrderByNeighborTerms(s, newContentStep(), append([]sql.OrderTerm{term}, terms...)...) } } +func newDiscordInteractionsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(DiscordInteractionsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, DiscordInteractionsTable, DiscordInteractionsColumn), + ) +} func newSubscriptionsStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), diff --git a/internal/database/ent/db/user/where.go b/internal/database/ent/db/user/where.go index cf752542..c3a5e922 100644 --- a/internal/database/ent/db/user/where.go +++ b/internal/database/ent/db/user/where.go @@ -233,6 +233,29 @@ func FeatureFlagsNotNil() predicate.User { return predicate.User(sql.FieldNotNull(FieldFeatureFlags)) } +// HasDiscordInteractions applies the HasEdge predicate on the "discord_interactions" edge. +func HasDiscordInteractions() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, DiscordInteractionsTable, DiscordInteractionsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasDiscordInteractionsWith applies the HasEdge predicate on the "discord_interactions" edge with a given conditions (other predicates). +func HasDiscordInteractionsWith(preds ...predicate.DiscordInteraction) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newDiscordInteractionsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // HasSubscriptions applies the HasEdge predicate on the "subscriptions" edge. func HasSubscriptions() predicate.User { return predicate.User(func(s *sql.Selector) { diff --git a/internal/database/ent/db/user_create.go b/internal/database/ent/db/user_create.go index 1db7f19a..1aa4e5e8 100644 --- a/internal/database/ent/db/user_create.go +++ b/internal/database/ent/db/user_create.go @@ -9,6 +9,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -76,6 +77,21 @@ func (uc *UserCreate) SetID(s string) *UserCreate { return uc } +// AddDiscordInteractionIDs adds the "discord_interactions" edge to the DiscordInteraction entity by IDs. +func (uc *UserCreate) AddDiscordInteractionIDs(ids ...string) *UserCreate { + uc.mutation.AddDiscordInteractionIDs(ids...) + return uc +} + +// AddDiscordInteractions adds the "discord_interactions" edges to the DiscordInteraction entity. +func (uc *UserCreate) AddDiscordInteractions(d ...*DiscordInteraction) *UserCreate { + ids := make([]string, len(d)) + for i := range d { + ids[i] = d[i].ID + } + return uc.AddDiscordInteractionIDs(ids...) +} + // AddSubscriptionIDs adds the "subscriptions" edge to the UserSubscription entity by IDs. func (uc *UserCreate) AddSubscriptionIDs(ids ...string) *UserCreate { uc.mutation.AddSubscriptionIDs(ids...) @@ -232,6 +248,22 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.SetField(user.FieldFeatureFlags, field.TypeJSON, value) _node.FeatureFlags = value } + if nodes := uc.mutation.DiscordInteractionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.DiscordInteractionsTable, + Columns: []string{user.DiscordInteractionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } if nodes := uc.mutation.SubscriptionsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/internal/database/ent/db/user_query.go b/internal/database/ent/db/user_query.go index 83ee3ea8..1416b0f7 100644 --- a/internal/database/ent/db/user_query.go +++ b/internal/database/ent/db/user_query.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/predicate" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" @@ -21,13 +22,14 @@ import ( // UserQuery is the builder for querying User entities. type UserQuery struct { config - ctx *QueryContext - order []user.OrderOption - inters []Interceptor - predicates []predicate.User - withSubscriptions *UserSubscriptionQuery - withConnections *UserConnectionQuery - withContent *UserContentQuery + ctx *QueryContext + order []user.OrderOption + inters []Interceptor + predicates []predicate.User + withDiscordInteractions *DiscordInteractionQuery + withSubscriptions *UserSubscriptionQuery + withConnections *UserConnectionQuery + withContent *UserContentQuery // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -64,6 +66,28 @@ func (uq *UserQuery) Order(o ...user.OrderOption) *UserQuery { return uq } +// QueryDiscordInteractions chains the current query on the "discord_interactions" edge. +func (uq *UserQuery) QueryDiscordInteractions() *DiscordInteractionQuery { + query := (&DiscordInteractionClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(discordinteraction.Table, discordinteraction.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.DiscordInteractionsTable, user.DiscordInteractionsColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // QuerySubscriptions chains the current query on the "subscriptions" edge. func (uq *UserQuery) QuerySubscriptions() *UserSubscriptionQuery { query := (&UserSubscriptionClient{config: uq.config}).Query() @@ -317,20 +341,32 @@ func (uq *UserQuery) Clone() *UserQuery { return nil } return &UserQuery{ - config: uq.config, - ctx: uq.ctx.Clone(), - order: append([]user.OrderOption{}, uq.order...), - inters: append([]Interceptor{}, uq.inters...), - predicates: append([]predicate.User{}, uq.predicates...), - withSubscriptions: uq.withSubscriptions.Clone(), - withConnections: uq.withConnections.Clone(), - withContent: uq.withContent.Clone(), + config: uq.config, + ctx: uq.ctx.Clone(), + order: append([]user.OrderOption{}, uq.order...), + inters: append([]Interceptor{}, uq.inters...), + predicates: append([]predicate.User{}, uq.predicates...), + withDiscordInteractions: uq.withDiscordInteractions.Clone(), + withSubscriptions: uq.withSubscriptions.Clone(), + withConnections: uq.withConnections.Clone(), + withContent: uq.withContent.Clone(), // clone intermediate query. sql: uq.sql.Clone(), path: uq.path, } } +// WithDiscordInteractions tells the query-builder to eager-load the nodes that are connected to +// the "discord_interactions" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithDiscordInteractions(opts ...func(*DiscordInteractionQuery)) *UserQuery { + query := (&DiscordInteractionClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withDiscordInteractions = query + return uq +} + // WithSubscriptions tells the query-builder to eager-load the nodes that are connected to // the "subscriptions" edge. The optional arguments are used to configure the query builder of the edge. func (uq *UserQuery) WithSubscriptions(opts ...func(*UserSubscriptionQuery)) *UserQuery { @@ -442,7 +478,8 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e var ( nodes = []*User{} _spec = uq.querySpec() - loadedTypes = [3]bool{ + loadedTypes = [4]bool{ + uq.withDiscordInteractions != nil, uq.withSubscriptions != nil, uq.withConnections != nil, uq.withContent != nil, @@ -466,6 +503,15 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e if len(nodes) == 0 { return nodes, nil } + if query := uq.withDiscordInteractions; query != nil { + if err := uq.loadDiscordInteractions(ctx, query, nodes, + func(n *User) { n.Edges.DiscordInteractions = []*DiscordInteraction{} }, + func(n *User, e *DiscordInteraction) { + n.Edges.DiscordInteractions = append(n.Edges.DiscordInteractions, e) + }); err != nil { + return nil, err + } + } if query := uq.withSubscriptions; query != nil { if err := uq.loadSubscriptions(ctx, query, nodes, func(n *User) { n.Edges.Subscriptions = []*UserSubscription{} }, @@ -490,6 +536,36 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e return nodes, nil } +func (uq *UserQuery) loadDiscordInteractions(ctx context.Context, query *DiscordInteractionQuery, nodes []*User, init func(*User), assign func(*User, *DiscordInteraction)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(discordinteraction.FieldUserID) + } + query.Where(predicate.DiscordInteraction(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.DiscordInteractionsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.UserID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (uq *UserQuery) loadSubscriptions(ctx context.Context, query *UserSubscriptionQuery, nodes []*User, init func(*User), assign func(*User, *UserSubscription)) error { fks := make([]driver.Value, 0, len(nodes)) nodeids := make(map[string]*User) diff --git a/internal/database/ent/db/user_update.go b/internal/database/ent/db/user_update.go index d4ac1622..4a19da4a 100644 --- a/internal/database/ent/db/user_update.go +++ b/internal/database/ent/db/user_update.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/predicate" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" @@ -76,6 +77,21 @@ func (uu *UserUpdate) ClearFeatureFlags() *UserUpdate { return uu } +// AddDiscordInteractionIDs adds the "discord_interactions" edge to the DiscordInteraction entity by IDs. +func (uu *UserUpdate) AddDiscordInteractionIDs(ids ...string) *UserUpdate { + uu.mutation.AddDiscordInteractionIDs(ids...) + return uu +} + +// AddDiscordInteractions adds the "discord_interactions" edges to the DiscordInteraction entity. +func (uu *UserUpdate) AddDiscordInteractions(d ...*DiscordInteraction) *UserUpdate { + ids := make([]string, len(d)) + for i := range d { + ids[i] = d[i].ID + } + return uu.AddDiscordInteractionIDs(ids...) +} + // AddSubscriptionIDs adds the "subscriptions" edge to the UserSubscription entity by IDs. func (uu *UserUpdate) AddSubscriptionIDs(ids ...string) *UserUpdate { uu.mutation.AddSubscriptionIDs(ids...) @@ -126,6 +142,27 @@ func (uu *UserUpdate) Mutation() *UserMutation { return uu.mutation } +// ClearDiscordInteractions clears all "discord_interactions" edges to the DiscordInteraction entity. +func (uu *UserUpdate) ClearDiscordInteractions() *UserUpdate { + uu.mutation.ClearDiscordInteractions() + return uu +} + +// RemoveDiscordInteractionIDs removes the "discord_interactions" edge to DiscordInteraction entities by IDs. +func (uu *UserUpdate) RemoveDiscordInteractionIDs(ids ...string) *UserUpdate { + uu.mutation.RemoveDiscordInteractionIDs(ids...) + return uu +} + +// RemoveDiscordInteractions removes "discord_interactions" edges to DiscordInteraction entities. +func (uu *UserUpdate) RemoveDiscordInteractions(d ...*DiscordInteraction) *UserUpdate { + ids := make([]string, len(d)) + for i := range d { + ids[i] = d[i].ID + } + return uu.RemoveDiscordInteractionIDs(ids...) +} + // ClearSubscriptions clears all "subscriptions" edges to the UserSubscription entity. func (uu *UserUpdate) ClearSubscriptions() *UserUpdate { uu.mutation.ClearSubscriptions() @@ -254,6 +291,51 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { if uu.mutation.FeatureFlagsCleared() { _spec.ClearField(user.FieldFeatureFlags, field.TypeJSON) } + if uu.mutation.DiscordInteractionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.DiscordInteractionsTable, + Columns: []string{user.DiscordInteractionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.RemovedDiscordInteractionsIDs(); len(nodes) > 0 && !uu.mutation.DiscordInteractionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.DiscordInteractionsTable, + Columns: []string{user.DiscordInteractionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.DiscordInteractionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.DiscordInteractionsTable, + Columns: []string{user.DiscordInteractionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if uu.mutation.SubscriptionsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -454,6 +536,21 @@ func (uuo *UserUpdateOne) ClearFeatureFlags() *UserUpdateOne { return uuo } +// AddDiscordInteractionIDs adds the "discord_interactions" edge to the DiscordInteraction entity by IDs. +func (uuo *UserUpdateOne) AddDiscordInteractionIDs(ids ...string) *UserUpdateOne { + uuo.mutation.AddDiscordInteractionIDs(ids...) + return uuo +} + +// AddDiscordInteractions adds the "discord_interactions" edges to the DiscordInteraction entity. +func (uuo *UserUpdateOne) AddDiscordInteractions(d ...*DiscordInteraction) *UserUpdateOne { + ids := make([]string, len(d)) + for i := range d { + ids[i] = d[i].ID + } + return uuo.AddDiscordInteractionIDs(ids...) +} + // AddSubscriptionIDs adds the "subscriptions" edge to the UserSubscription entity by IDs. func (uuo *UserUpdateOne) AddSubscriptionIDs(ids ...string) *UserUpdateOne { uuo.mutation.AddSubscriptionIDs(ids...) @@ -504,6 +601,27 @@ func (uuo *UserUpdateOne) Mutation() *UserMutation { return uuo.mutation } +// ClearDiscordInteractions clears all "discord_interactions" edges to the DiscordInteraction entity. +func (uuo *UserUpdateOne) ClearDiscordInteractions() *UserUpdateOne { + uuo.mutation.ClearDiscordInteractions() + return uuo +} + +// RemoveDiscordInteractionIDs removes the "discord_interactions" edge to DiscordInteraction entities by IDs. +func (uuo *UserUpdateOne) RemoveDiscordInteractionIDs(ids ...string) *UserUpdateOne { + uuo.mutation.RemoveDiscordInteractionIDs(ids...) + return uuo +} + +// RemoveDiscordInteractions removes "discord_interactions" edges to DiscordInteraction entities. +func (uuo *UserUpdateOne) RemoveDiscordInteractions(d ...*DiscordInteraction) *UserUpdateOne { + ids := make([]string, len(d)) + for i := range d { + ids[i] = d[i].ID + } + return uuo.RemoveDiscordInteractionIDs(ids...) +} + // ClearSubscriptions clears all "subscriptions" edges to the UserSubscription entity. func (uuo *UserUpdateOne) ClearSubscriptions() *UserUpdateOne { uuo.mutation.ClearSubscriptions() @@ -662,6 +780,51 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) if uuo.mutation.FeatureFlagsCleared() { _spec.ClearField(user.FieldFeatureFlags, field.TypeJSON) } + if uuo.mutation.DiscordInteractionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.DiscordInteractionsTable, + Columns: []string{user.DiscordInteractionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.RemovedDiscordInteractionsIDs(); len(nodes) > 0 && !uuo.mutation.DiscordInteractionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.DiscordInteractionsTable, + Columns: []string{user.DiscordInteractionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.DiscordInteractionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.DiscordInteractionsTable, + Columns: []string{user.DiscordInteractionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(discordinteraction.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if uuo.mutation.SubscriptionsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/internal/database/ent/migrate/migrations/20240626213554.sql b/internal/database/ent/migrate/migrations/20240626213554.sql new file mode 100644 index 00000000..378ea14d --- /dev/null +++ b/internal/database/ent/migrate/migrations/20240626213554.sql @@ -0,0 +1,12 @@ +-- Create "discord_interactions" table +CREATE TABLE `discord_interactions` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `command` text NOT NULL, `reference_id` text NOT NULL, `type` text NOT NULL, `locale` text NOT NULL, `options` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `discord_interactions_users_discord_interactions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Create index "discordinteraction_id" to table: "discord_interactions" +CREATE INDEX `discordinteraction_id` ON `discord_interactions` (`id`); +-- Create index "discordinteraction_command" to table: "discord_interactions" +CREATE INDEX `discordinteraction_command` ON `discord_interactions` (`command`); +-- Create index "discordinteraction_user_id" to table: "discord_interactions" +CREATE INDEX `discordinteraction_user_id` ON `discord_interactions` (`user_id`); +-- Create index "discordinteraction_user_id_type" to table: "discord_interactions" +CREATE INDEX `discordinteraction_user_id_type` ON `discord_interactions` (`user_id`, `type`); +-- Create index "discordinteraction_reference_id" to table: "discord_interactions" +CREATE INDEX `discordinteraction_reference_id` ON `discord_interactions` (`reference_id`); diff --git a/internal/database/ent/migrate/migrations/atlas.sum b/internal/database/ent/migrate/migrations/atlas.sum index 7cf2c24b..cdc03f33 100644 --- a/internal/database/ent/migrate/migrations/atlas.sum +++ b/internal/database/ent/migrate/migrations/atlas.sum @@ -1,3 +1,4 @@ -h1:hbF7dyumL2NNWMkQkprSMNkXUEeHsM+GMYMBPXWdjW0= +h1:mPthURxB95TzDLQ5/ssWicoIRBsv760ADaBxoBMbLjQ= 20240622203812_init.sql h1:Rt6NGvXbPBEpjg1pUFqn97FV8X3X2d1x7J2zOAAxolc= 20240623143618_added_indexes_on_id.sql h1:o57d1Hc6+wGscJp3VIWnCNrmQkBSeSKTEVKRSWmsc6k= +20240626213554.sql h1:sKGMP6giStLCW5lUK8VyaLxmiPRQTxT+y6xRkax1juM= diff --git a/internal/database/ent/schema/application_command.go b/internal/database/ent/schema/application_command.go index d15db084..e4cd39b3 100644 --- a/internal/database/ent/schema/application_command.go +++ b/internal/database/ent/schema/application_command.go @@ -6,19 +6,6 @@ import ( "entgo.io/ent/schema/index" ) -// model ApplicationCommand { -// id String @id -// createdAt DateTime @default(now()) -// updatedAt DateTime @updatedAt - -// name String -// version String -// optionsHash String - -// @@index([optionsHash]) -// @@map("application_commands") -// } - // ApplicationCommand holds the schema definition for the ApplicationCommand entity. type ApplicationCommand struct { ent.Schema diff --git a/internal/database/ent/schema/discord_interaction.go b/internal/database/ent/schema/discord_interaction.go new file mode 100644 index 00000000..8b5d1beb --- /dev/null +++ b/internal/database/ent/schema/discord_interaction.go @@ -0,0 +1,41 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/cufee/aftermath/internal/database/models" +) + +type DiscordInteraction struct { + ent.Schema +} + +func (DiscordInteraction) Fields() []ent.Field { + return append(defaultFields, + field.String("command").NotEmpty(), + field.String("user_id").NotEmpty().Immutable(), + field.String("reference_id").NotEmpty(), + field.Enum("type"). + GoType(models.DiscordInteractionType("")), + field.String("locale"), + field.JSON("options", models.DiscordInteractionOptions{}), + ) +} + +func (DiscordInteraction) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("user", User.Type).Ref("discord_interactions").Field("user_id").Required().Immutable().Unique(), + } +} + +func (DiscordInteraction) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("id"), + index.Fields("command"), + index.Fields("user_id"), + index.Fields("user_id", "type"), + index.Fields("reference_id"), + } +} diff --git a/internal/database/ent/schema/user.go b/internal/database/ent/schema/user.go index be20a1be..6ae361e9 100644 --- a/internal/database/ent/schema/user.go +++ b/internal/database/ent/schema/user.go @@ -33,6 +33,7 @@ func (User) Fields() []ent.Field { // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ + edge.To("discord_interactions", DiscordInteraction.Type), edge.To("subscriptions", UserSubscription.Type), edge.To("connections", UserConnection.Type), edge.To("content", UserContent.Type), diff --git a/internal/database/ent/schema/user_connection.go b/internal/database/ent/schema/user_connection.go index 8b4a1a28..93073ab2 100644 --- a/internal/database/ent/schema/user_connection.go +++ b/internal/database/ent/schema/user_connection.go @@ -8,27 +8,6 @@ import ( "github.com/cufee/aftermath/internal/database/models" ) -// model UserConnection { -// id String @id @default(cuid()) -// createdAt DateTime @default(now()) -// updatedAt DateTime @updatedAt - -// user User @relation(fields: [userId], references: [id]) -// userId String - -// type String -// permissions String -// referenceId String - -// metadataEncoded Bytes - -// @@index([userId]) -// @@index([type, userId]) -// @@index([referenceId]) -// @@index([type, referenceId]) -// @@map("user_connections") -// } - // UserConnection holds the schema definition for the UserConnection entity. type UserConnection struct { ent.Schema diff --git a/internal/database/models/discord_interaction.go b/internal/database/models/discord_interaction.go new file mode 100644 index 00000000..f031317b --- /dev/null +++ b/internal/database/models/discord_interaction.go @@ -0,0 +1,51 @@ +package models + +import ( + "slices" + "time" + + "golang.org/x/text/language" +) + +type DiscordInteractionType string + +const ( + InteractionTypeStats = "stats" +) + +var discordInteractionTypes = []DiscordInteractionType{ + InteractionTypeStats, +} + +// Values provides list valid values for Enum. +func (DiscordInteractionType) Values() []string { + var kinds []string + for _, s := range discordInteractionTypes { + kinds = append(kinds, string(s)) + } + return kinds +} + +func (s DiscordInteractionType) Valid() bool { + return slices.Contains(discordInteractionTypes, s) +} + +type DiscordInteraction struct { + ID string + CreatedAt time.Time + + UserID string + Command string + ReferenceID string + + Type DiscordInteractionType + Locale language.Tag + + Options DiscordInteractionOptions +} + +type DiscordInteractionOptions struct { + BackgroundImageURL string + PeriodStart time.Time + AccountID string +} diff --git a/internal/permissions/actions.go b/internal/permissions/actions.go index 9c5b3b23..c9389344 100644 --- a/internal/permissions/actions.go +++ b/internal/permissions/actions.go @@ -57,6 +57,7 @@ var ( ) var ( - // Admin - ViewLogs Permissions = fromLsh(60) + // Technical / Debugging + UseDebugFeatures = fromLsh(61) + ViewTaskLogs = fromLsh(62) ) diff --git a/internal/permissions/roles.go b/internal/permissions/roles.go index 364b861d..f4558ef5 100644 --- a/internal/permissions/roles.go +++ b/internal/permissions/roles.go @@ -16,6 +16,7 @@ var ( Add(CreateSoftUserRestriction) GlobalModerator = ContentModerator. + Add(UseDebugFeatures). Add(ViewUserSubscriptions). Add(CreateUserSubscription). Add(ExtendUserSubscription). @@ -30,6 +31,7 @@ var ( Add(CreateHardUserRestriction) GlobalAdmin = GlobalModerator. + Add(DebugAccess). Add(CreateGlobalBackgroundPreset). Add(UpdateGlobalBackgroundPreset). Add(RemoveGlobalBackgroundPreset). @@ -50,6 +52,8 @@ var ( Add(UpdateSoftRestriction). Add(UpdateHardRestriction). Add(RemoveSoftRestriction). - Add(RemoveHardRestriction). - Add(ViewLogs) + Add(RemoveHardRestriction) + + DebugAccess = UseDebugFeatures. + Add(ViewTaskLogs) ) diff --git a/internal/stats/render/common/v1/options.go b/internal/stats/render/common/v1/options.go index c334d085..24a80543 100644 --- a/internal/stats/render/common/v1/options.go +++ b/internal/stats/render/common/v1/options.go @@ -2,6 +2,9 @@ package common import ( "image" + "strings" + + "github.com/cufee/aftermath/internal/stats/render/assets" ) type Options struct { @@ -21,8 +24,23 @@ func WithPromoText(text ...string) Option { } } -func WithBackground(bg image.Image) Option { +func WithBackground(bgURL string) Option { + if bgURL == "" { + bgURL = "static://bg-default" + } + + var image image.Image + if strings.HasPrefix(bgURL, "static://") { + img, ok := assets.GetLoadedImage(strings.ReplaceAll(bgURL, "static://", "")) + if ok { + image = img + } + } + + if image == nil { + return func(o *Options) {} + } return func(o *Options) { - o.Background = bg + o.Background = image } } From 2a0a56c55b481a81db492e5e19e07f3e38e356b1 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 18:14:36 -0400 Subject: [PATCH 184/341] proper localization --- cmds/discord/commands/interactions.go | 6 +++--- internal/stats/render/session/v1/cards.go | 6 ------ render_test.go | 6 +----- static/localization/en/discord.yaml | 4 ++++ 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/cmds/discord/commands/interactions.go b/cmds/discord/commands/interactions.go index 7cbf31fd..23431a03 100644 --- a/cmds/discord/commands/interactions.go +++ b/cmds/discord/commands/interactions.go @@ -50,7 +50,7 @@ func init() { interaction, err := ctx.Core.Database().GetDiscordInteraction(ctx.Context, interactionID) if err != nil { - return ctx.Reply().Send("refresh_interaction_expired") + return ctx.Reply().Send("stats_refresh_interaction_error_expired") } var image renderer.Image @@ -68,7 +68,7 @@ func init() { img, mt, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, render.WithBackground(interaction.Options.BackgroundImageURL)) if err != nil { if errors.Is(err, fetch.ErrSessionNotFound) || errors.Is(err, renderer.ErrAccountNotTracked) { - return ctx.Reply().Send("refresh_interaction_expired") + return ctx.Reply().Send("stats_refresh_interaction_error_expired") } return ctx.Err(err) } @@ -77,7 +77,7 @@ func init() { default: log.Error().Str("customId", data.CustomID).Str("command", interaction.Command).Msg("received an unexpected component interaction callback") - return ctx.Reply().Send("refresh_interaction_expired") + return ctx.Reply().Send("stats_refresh_interaction_error_expired") } button, saveErr := saveInteractionData(ctx, interaction.Command, interaction.Options) diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index d0d59417..72afa921 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -190,24 +190,20 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } // player title - println("making player title") primaryColumn = append(primaryColumn, common.NewPlayerTitleCard(common.DefaultPlayerTitleStyle(playerNameCardStyle(primaryCardWidth)), session.Account.Nickname, session.Account.ClanTag, subs), ) // overview cards if shouldRenderUnratedOverview { - println("making unrated overview") primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, overviewCardStyle(primaryCardWidth))) } if shouldRenderRatingOverview { - println("making rating overview") primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, overviewRatingCardStyle(primaryCardWidth))) } // highlights if shouldRenderUnratedHighlights { - println("making highlights") for _, vehicle := range cards.Unrated.Highlights { primaryColumn = append(primaryColumn, makeVehicleHighlightCard(vehicle, highlightCardBlockSizes, primaryCardWidth)) } @@ -219,7 +215,6 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // unrated vehicles if shouldRenderUnratedVehicles { - println("making unrated vehicles") for i, vehicle := range cards.Unrated.Vehicles { if i >= renderUnratedVehiclesCount { break @@ -400,7 +395,6 @@ func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, for _, column := range card.Blocks { var columnContent []common.Block for _, block := range column { - println("making block", block.Label) var col common.Block if block.Tag == prepare.TagWN8 || block.Tag == prepare.TagRankedRating { col = makeSpecialRatingColumn(block, blockSizes[block.Tag.String()]) diff --git a/render_test.go b/render_test.go index 866144a3..77756364 100644 --- a/render_test.go +++ b/render_test.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/cufee/aftermath/internal/stats/render/assets" "github.com/cufee/aftermath/internal/stats/render/common/v1" stats "github.com/cufee/aftermath/internal/stats/renderer/v1" "github.com/rs/zerolog" @@ -29,11 +28,8 @@ func TestRenderSession(t *testing.T) { coreClient, _ := coreClientsFromEnv(db) defer coreClient.Database().Disconnect() - bgImage, ok := assets.GetLoadedImage("bg-default") - assert.True(t, ok, "failed to load a background image") - renderer := stats.NewRenderer(coreClient.Fetch(), coreClient.Database(), coreClient.Wargaming(), language.English) - image, _, err := renderer.Session(context.Background(), "1013072123", time.Now(), common.WithBackground(bgImage)) + image, _, err := renderer.Session(context.Background(), "1013072123", time.Now(), common.WithBackground("")) assert.NoError(t, err, "failed to render a session image") assert.NotNil(t, image, "image is nil") diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml index c62ca987..19885bba 100755 --- a/static/localization/en/discord.yaml +++ b/static/localization/en/discord.yaml @@ -139,6 +139,10 @@ value: "Aftermath does not yet have a session for this period. Try changing the number of days or using `/stats` instead." context: This account is being tracked, but there is no session available +- key: stats_refresh_interaction_error_expired + value: "This refresh button has expired. Please use the command instead." + context: Failed to find required data in order to refresh stats/session + # /background errors - key: background_error_payment_required value: "This feature of Aftermath is only available for users with an active subscription.\nYou can subscribe by using the `/subscribe` command or pick a background using `/fancy` instead." From 77bbe10e2d3d768441c11bd831ed1c6e243bb99d Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 18:38:52 -0400 Subject: [PATCH 185/341] custom refresh button icon --- cmds/discord/commands/interactions.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmds/discord/commands/interactions.go b/cmds/discord/commands/interactions.go index 23431a03..edc30dba 100644 --- a/cmds/discord/commands/interactions.go +++ b/cmds/discord/commands/interactions.go @@ -25,7 +25,8 @@ func newStatsRefreshButton(data models.DiscordInteraction) discordgo.MessageComp Components: []discordgo.MessageComponent{discordgo.Button{ Style: discordgo.SecondaryButton, Emoji: &discordgo.ComponentEmoji{ - Name: "🔃", + ID: "1255647885723435048", + Name: "aftermath_refresh", }, CustomID: fmt.Sprintf("refresh_stats_from_button_%s", data.ReferenceID), }}, From e667d35e74a6554b5afea1361d86828e9b5664b4 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 19:20:45 -0400 Subject: [PATCH 186/341] added content and restrictions --- cmds/discord/commands/interactions.go | 2 +- cmds/discord/commands/link.go | 4 +- cmds/discord/commands/manage.go | 4 +- cmds/discord/commands/session.go | 14 ++-- cmds/discord/commands/stats.go | 4 +- cmds/discord/common/reply.go | 2 +- cmds/discord/middleware/middleware.go | 2 +- internal/database/ent/db/migrate/schema.go | 2 +- internal/database/ent/db/mutation.go | 12 ++-- internal/database/ent/db/usercontent.go | 16 ++--- .../ent/db/usercontent/usercontent.go | 5 ++ internal/database/ent/db/usercontent/where.go | 70 +++++++++++++++++++ .../database/ent/db/usercontent_create.go | 6 +- .../database/ent/db/usercontent_update.go | 28 ++++++-- .../ent/migrate/migrations/20240626225519.sql | 22 ++++++ .../database/ent/migrate/migrations/atlas.sum | 3 +- internal/database/ent/schema/user_content.go | 2 +- internal/database/models/user.go | 34 ++++++++- internal/database/models/user_content.go | 46 ++++++++++++ internal/database/models/user_restriction.go | 32 +++++++++ ...scription.go => user_subscription copy.go} | 30 -------- internal/database/users.go | 30 ++++---- 22 files changed, 286 insertions(+), 84 deletions(-) create mode 100644 internal/database/ent/migrate/migrations/20240626225519.sql create mode 100644 internal/database/models/user_content.go create mode 100644 internal/database/models/user_restriction.go rename internal/database/models/{user_subscription.go => user_subscription copy.go} (75%) diff --git a/cmds/discord/commands/interactions.go b/cmds/discord/commands/interactions.go index edc30dba..b75dd28b 100644 --- a/cmds/discord/commands/interactions.go +++ b/cmds/discord/commands/interactions.go @@ -94,7 +94,7 @@ func init() { } var timings []string - if ctx.User.Permissions.Has(permissions.UseDebugFeatures) { + if ctx.User.HasPermission(permissions.UseDebugFeatures) { timings = append(timings, "```") for name, duration := range meta.Timings { timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) diff --git a/cmds/discord/commands/link.go b/cmds/discord/commands/link.go index 63d801e8..1866735d 100644 --- a/cmds/discord/commands/link.go +++ b/cmds/discord/commands/link.go @@ -44,7 +44,7 @@ func init() { account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { - return ctx.Reply().Fmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() + return ctx.Reply().Format("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() } return ctx.Err(err) } @@ -64,7 +64,7 @@ func init() { return ctx.Err(err) } - return ctx.Reply().Fmt("command_link_linked_successfully_fmt", account.Nickname, strings.ToUpper(options.Server)).Send() + return ctx.Reply().Format("command_link_linked_successfully_fmt", account.Nickname, strings.ToUpper(options.Server)).Send() }), ) } diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index f75996f0..5fd07cbd 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -111,7 +111,7 @@ func init() { return ctx.Reply().Send("```" + string(bytes) + "```") case "tasks_view": - if !ctx.User.Permissions.Has(permissions.ViewTaskLogs) { + if !ctx.User.HasPermission(permissions.ViewTaskLogs) { ctx.Reply().Send("You do not have access to this sub-command.") } @@ -149,7 +149,7 @@ func init() { return ctx.Reply().File(bytes.NewBufferString(content), "tasks.json").Send() case "tasks_details": - if !ctx.User.Permissions.Has(permissions.ViewTaskLogs) { + if !ctx.User.HasPermission(permissions.ViewTaskLogs) { ctx.Reply().Send("You do not have access to this sub-command.") } diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go index 7a1562ad..641a95bd 100644 --- a/cmds/discord/commands/session.go +++ b/cmds/discord/commands/session.go @@ -41,14 +41,17 @@ func init() { return ctx.Reply().Send("stats_error_connection_not_found_vague") } accountID = defaultAccount.ReferenceID - // TODO: Get user background + background, ok := mentionedUser.Content(models.UserContentTypePersonalBackground) + if ok { + backgroundURL = background.Value + } case options.Nickname != "" && options.Server != "": // nickname provided and server selected - lookup the account account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { - return ctx.Reply().Fmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() + return ctx.Reply().Format("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() } return ctx.Err(err) } @@ -61,7 +64,10 @@ func init() { } // command used without options, but user has a default connection accountID = defaultAccount.ReferenceID - // TODO: Get user background + background, ok := ctx.User.Content(models.UserContentTypePersonalBackground) + if ok { + backgroundURL = background.Value + } } image, meta, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), accountID, options.PeriodStart, render.WithBackground(backgroundURL)) @@ -92,7 +98,7 @@ func init() { } var timings []string - if ctx.User.Permissions.Has(permissions.UseDebugFeatures) { + if ctx.User.HasPermission(permissions.UseDebugFeatures) { timings = append(timings, "```") for name, duration := range meta.Timings { timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) diff --git a/cmds/discord/commands/stats.go b/cmds/discord/commands/stats.go index 0b3ded0a..ef5eca5f 100644 --- a/cmds/discord/commands/stats.go +++ b/cmds/discord/commands/stats.go @@ -45,7 +45,7 @@ func init() { account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { - return ctx.Reply().Fmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() + return ctx.Reply().Format("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() } return ctx.Err(err) } @@ -83,7 +83,7 @@ func init() { } var timings []string - if ctx.User.Permissions.Has(permissions.UseDebugFeatures) { + if ctx.User.HasPermission(permissions.UseDebugFeatures) { timings = append(timings, "```") for name, duration := range meta.Timings { timings = append(timings, fmt.Sprintf("%s: %v", name, duration.Milliseconds())) diff --git a/cmds/discord/common/reply.go b/cmds/discord/common/reply.go index 526cc416..457a253e 100644 --- a/cmds/discord/common/reply.go +++ b/cmds/discord/common/reply.go @@ -22,7 +22,7 @@ func (r reply) Text(message ...string) reply { return r } -func (r reply) Fmt(format string, args ...any) reply { +func (r reply) Format(format string, args ...any) reply { r.text = append(r.text, fmt.Sprintf(format, args...)) return r } diff --git a/cmds/discord/middleware/middleware.go b/cmds/discord/middleware/middleware.go index 634e6a93..26db204e 100644 --- a/cmds/discord/middleware/middleware.go +++ b/cmds/discord/middleware/middleware.go @@ -9,7 +9,7 @@ type MiddlewareFunc func(*common.Context, func(*common.Context) error) func(*com func RequirePermissions(required permissions.Permissions) MiddlewareFunc { return func(ctx *common.Context, next func(*common.Context) error) func(*common.Context) error { - if !ctx.User.Permissions.Has(required) { + if !ctx.User.HasPermission(required) { return func(ctx *common.Context) error { return ctx.Reply().Send("common_error_command_missing_permissions") } diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index 9432fc41..b198249e 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -436,7 +436,7 @@ var ( {Name: "updated_at", Type: field.TypeInt64}, {Name: "type", Type: field.TypeEnum, Enums: []string{"clan-background-image", "personal-background-image"}}, {Name: "reference_id", Type: field.TypeString}, - {Name: "value", Type: field.TypeJSON}, + {Name: "value", Type: field.TypeString}, {Name: "metadata", Type: field.TypeJSON}, {Name: "user_id", Type: field.TypeString}, } diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go index d34605bc..1dcee88e 100644 --- a/internal/database/ent/db/mutation.go +++ b/internal/database/ent/db/mutation.go @@ -8820,7 +8820,7 @@ type UserContentMutation struct { addupdated_at *int64 _type *models.UserContentType reference_id *string - value *any + value *string metadata *map[string]interface{} clearedFields map[string]struct{} user *string @@ -9155,12 +9155,12 @@ func (m *UserContentMutation) ResetReferenceID() { } // SetValue sets the "value" field. -func (m *UserContentMutation) SetValue(a any) { - m.value = &a +func (m *UserContentMutation) SetValue(s string) { + m.value = &s } // Value returns the value of the "value" field in the mutation. -func (m *UserContentMutation) Value() (r any, exists bool) { +func (m *UserContentMutation) Value() (r string, exists bool) { v := m.value if v == nil { return @@ -9171,7 +9171,7 @@ func (m *UserContentMutation) Value() (r any, exists bool) { // OldValue returns the old "value" field's value of the UserContent entity. // If the UserContent object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserContentMutation) OldValue(ctx context.Context) (v any, err error) { +func (m *UserContentMutation) OldValue(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldValue is only allowed on UpdateOne operations") } @@ -9399,7 +9399,7 @@ func (m *UserContentMutation) SetField(name string, value ent.Value) error { m.SetReferenceID(v) return nil case usercontent.FieldValue: - v, ok := value.(any) + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } diff --git a/internal/database/ent/db/usercontent.go b/internal/database/ent/db/usercontent.go index dfa12961..f60b64d1 100644 --- a/internal/database/ent/db/usercontent.go +++ b/internal/database/ent/db/usercontent.go @@ -30,7 +30,7 @@ type UserContent struct { // ReferenceID holds the value of the "reference_id" field. ReferenceID string `json:"reference_id,omitempty"` // Value holds the value of the "value" field. - Value any `json:"value,omitempty"` + Value string `json:"value,omitempty"` // Metadata holds the value of the "metadata" field. Metadata map[string]interface{} `json:"metadata,omitempty"` // Edges holds the relations/edges for other nodes in the graph. @@ -64,11 +64,11 @@ func (*UserContent) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case usercontent.FieldValue, usercontent.FieldMetadata: + case usercontent.FieldMetadata: values[i] = new([]byte) case usercontent.FieldCreatedAt, usercontent.FieldUpdatedAt: values[i] = new(sql.NullInt64) - case usercontent.FieldID, usercontent.FieldType, usercontent.FieldUserID, usercontent.FieldReferenceID: + case usercontent.FieldID, usercontent.FieldType, usercontent.FieldUserID, usercontent.FieldReferenceID, usercontent.FieldValue: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) @@ -122,12 +122,10 @@ func (uc *UserContent) assignValues(columns []string, values []any) error { uc.ReferenceID = value.String } case usercontent.FieldValue: - if value, ok := values[i].(*[]byte); !ok { + if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field value", values[i]) - } else if value != nil && len(*value) > 0 { - if err := json.Unmarshal(*value, &uc.Value); err != nil { - return fmt.Errorf("unmarshal field value: %w", err) - } + } else if value.Valid { + uc.Value = value.String } case usercontent.FieldMetadata: if value, ok := values[i].(*[]byte); !ok { @@ -194,7 +192,7 @@ func (uc *UserContent) String() string { builder.WriteString(uc.ReferenceID) builder.WriteString(", ") builder.WriteString("value=") - builder.WriteString(fmt.Sprintf("%v", uc.Value)) + builder.WriteString(uc.Value) builder.WriteString(", ") builder.WriteString("metadata=") builder.WriteString(fmt.Sprintf("%v", uc.Metadata)) diff --git a/internal/database/ent/db/usercontent/usercontent.go b/internal/database/ent/db/usercontent/usercontent.go index 89a1d190..a05b1dcb 100644 --- a/internal/database/ent/db/usercontent/usercontent.go +++ b/internal/database/ent/db/usercontent/usercontent.go @@ -118,6 +118,11 @@ func ByReferenceID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldReferenceID, opts...).ToFunc() } +// ByValue orders the results by the value field. +func ByValue(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldValue, opts...).ToFunc() +} + // ByUserField orders the results by user field. func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { diff --git a/internal/database/ent/db/usercontent/where.go b/internal/database/ent/db/usercontent/where.go index b8133da2..db3d4033 100644 --- a/internal/database/ent/db/usercontent/where.go +++ b/internal/database/ent/db/usercontent/where.go @@ -84,6 +84,11 @@ func ReferenceID(v string) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldReferenceID, v)) } +// Value applies equality check predicate on the "value" field. It's identical to ValueEQ. +func Value(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldValue, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v int64) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldCreatedAt, v)) @@ -324,6 +329,71 @@ func ReferenceIDContainsFold(v string) predicate.UserContent { return predicate.UserContent(sql.FieldContainsFold(FieldReferenceID, v)) } +// ValueEQ applies the EQ predicate on the "value" field. +func ValueEQ(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldEQ(FieldValue, v)) +} + +// ValueNEQ applies the NEQ predicate on the "value" field. +func ValueNEQ(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldNEQ(FieldValue, v)) +} + +// ValueIn applies the In predicate on the "value" field. +func ValueIn(vs ...string) predicate.UserContent { + return predicate.UserContent(sql.FieldIn(FieldValue, vs...)) +} + +// ValueNotIn applies the NotIn predicate on the "value" field. +func ValueNotIn(vs ...string) predicate.UserContent { + return predicate.UserContent(sql.FieldNotIn(FieldValue, vs...)) +} + +// ValueGT applies the GT predicate on the "value" field. +func ValueGT(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldGT(FieldValue, v)) +} + +// ValueGTE applies the GTE predicate on the "value" field. +func ValueGTE(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldGTE(FieldValue, v)) +} + +// ValueLT applies the LT predicate on the "value" field. +func ValueLT(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldLT(FieldValue, v)) +} + +// ValueLTE applies the LTE predicate on the "value" field. +func ValueLTE(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldLTE(FieldValue, v)) +} + +// ValueContains applies the Contains predicate on the "value" field. +func ValueContains(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldContains(FieldValue, v)) +} + +// ValueHasPrefix applies the HasPrefix predicate on the "value" field. +func ValueHasPrefix(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldHasPrefix(FieldValue, v)) +} + +// ValueHasSuffix applies the HasSuffix predicate on the "value" field. +func ValueHasSuffix(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldHasSuffix(FieldValue, v)) +} + +// ValueEqualFold applies the EqualFold predicate on the "value" field. +func ValueEqualFold(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldEqualFold(FieldValue, v)) +} + +// ValueContainsFold applies the ContainsFold predicate on the "value" field. +func ValueContainsFold(v string) predicate.UserContent { + return predicate.UserContent(sql.FieldContainsFold(FieldValue, v)) +} + // HasUser applies the HasEdge predicate on the "user" edge. func HasUser() predicate.UserContent { return predicate.UserContent(func(s *sql.Selector) { diff --git a/internal/database/ent/db/usercontent_create.go b/internal/database/ent/db/usercontent_create.go index ac65c16b..d127d2a3 100644 --- a/internal/database/ent/db/usercontent_create.go +++ b/internal/database/ent/db/usercontent_create.go @@ -68,8 +68,8 @@ func (ucc *UserContentCreate) SetReferenceID(s string) *UserContentCreate { } // SetValue sets the "value" field. -func (ucc *UserContentCreate) SetValue(a any) *UserContentCreate { - ucc.mutation.SetValue(a) +func (ucc *UserContentCreate) SetValue(s string) *UserContentCreate { + ucc.mutation.SetValue(s) return ucc } @@ -230,7 +230,7 @@ func (ucc *UserContentCreate) createSpec() (*UserContent, *sqlgraph.CreateSpec) _node.ReferenceID = value } if value, ok := ucc.mutation.Value(); ok { - _spec.SetField(usercontent.FieldValue, field.TypeJSON, value) + _spec.SetField(usercontent.FieldValue, field.TypeString, value) _node.Value = value } if value, ok := ucc.mutation.Metadata(); ok { diff --git a/internal/database/ent/db/usercontent_update.go b/internal/database/ent/db/usercontent_update.go index 25bb317e..93054653 100644 --- a/internal/database/ent/db/usercontent_update.go +++ b/internal/database/ent/db/usercontent_update.go @@ -70,8 +70,16 @@ func (ucu *UserContentUpdate) SetNillableReferenceID(s *string) *UserContentUpda } // SetValue sets the "value" field. -func (ucu *UserContentUpdate) SetValue(a any) *UserContentUpdate { - ucu.mutation.SetValue(a) +func (ucu *UserContentUpdate) SetValue(s string) *UserContentUpdate { + ucu.mutation.SetValue(s) + return ucu +} + +// SetNillableValue sets the "value" field if the given value is not nil. +func (ucu *UserContentUpdate) SetNillableValue(s *string) *UserContentUpdate { + if s != nil { + ucu.SetValue(*s) + } return ucu } @@ -160,7 +168,7 @@ func (ucu *UserContentUpdate) sqlSave(ctx context.Context) (n int, err error) { _spec.SetField(usercontent.FieldReferenceID, field.TypeString, value) } if value, ok := ucu.mutation.Value(); ok { - _spec.SetField(usercontent.FieldValue, field.TypeJSON, value) + _spec.SetField(usercontent.FieldValue, field.TypeString, value) } if value, ok := ucu.mutation.Metadata(); ok { _spec.SetField(usercontent.FieldMetadata, field.TypeJSON, value) @@ -227,8 +235,16 @@ func (ucuo *UserContentUpdateOne) SetNillableReferenceID(s *string) *UserContent } // SetValue sets the "value" field. -func (ucuo *UserContentUpdateOne) SetValue(a any) *UserContentUpdateOne { - ucuo.mutation.SetValue(a) +func (ucuo *UserContentUpdateOne) SetValue(s string) *UserContentUpdateOne { + ucuo.mutation.SetValue(s) + return ucuo +} + +// SetNillableValue sets the "value" field if the given value is not nil. +func (ucuo *UserContentUpdateOne) SetNillableValue(s *string) *UserContentUpdateOne { + if s != nil { + ucuo.SetValue(*s) + } return ucuo } @@ -347,7 +363,7 @@ func (ucuo *UserContentUpdateOne) sqlSave(ctx context.Context) (_node *UserConte _spec.SetField(usercontent.FieldReferenceID, field.TypeString, value) } if value, ok := ucuo.mutation.Value(); ok { - _spec.SetField(usercontent.FieldValue, field.TypeJSON, value) + _spec.SetField(usercontent.FieldValue, field.TypeString, value) } if value, ok := ucuo.mutation.Metadata(); ok { _spec.SetField(usercontent.FieldMetadata, field.TypeJSON, value) diff --git a/internal/database/ent/migrate/migrations/20240626225519.sql b/internal/database/ent/migrate/migrations/20240626225519.sql new file mode 100644 index 00000000..f59ccc1c --- /dev/null +++ b/internal/database/ent/migrate/migrations/20240626225519.sql @@ -0,0 +1,22 @@ +-- Disable the enforcement of foreign-keys constraints +PRAGMA foreign_keys = off; +-- Create "new_user_contents" table +CREATE TABLE `new_user_contents` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `value` text NOT NULL, `metadata` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_contents_users_content` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Copy rows from old table "user_contents" to new temporary table "new_user_contents" +INSERT INTO `new_user_contents` (`id`, `created_at`, `updated_at`, `type`, `reference_id`, `value`, `metadata`, `user_id`) SELECT `id`, `created_at`, `updated_at`, `type`, `reference_id`, `value`, `metadata`, `user_id` FROM `user_contents`; +-- Drop "user_contents" table after copying rows +DROP TABLE `user_contents`; +-- Rename temporary table "new_user_contents" to "user_contents" +ALTER TABLE `new_user_contents` RENAME TO `user_contents`; +-- Create index "usercontent_id" to table: "user_contents" +CREATE INDEX `usercontent_id` ON `user_contents` (`id`); +-- Create index "usercontent_user_id" to table: "user_contents" +CREATE INDEX `usercontent_user_id` ON `user_contents` (`user_id`); +-- Create index "usercontent_type_user_id" to table: "user_contents" +CREATE INDEX `usercontent_type_user_id` ON `user_contents` (`type`, `user_id`); +-- Create index "usercontent_reference_id" to table: "user_contents" +CREATE INDEX `usercontent_reference_id` ON `user_contents` (`reference_id`); +-- Create index "usercontent_type_reference_id" to table: "user_contents" +CREATE INDEX `usercontent_type_reference_id` ON `user_contents` (`type`, `reference_id`); +-- Enable back the enforcement of foreign-keys constraints +PRAGMA foreign_keys = on; diff --git a/internal/database/ent/migrate/migrations/atlas.sum b/internal/database/ent/migrate/migrations/atlas.sum index cdc03f33..39f948c4 100644 --- a/internal/database/ent/migrate/migrations/atlas.sum +++ b/internal/database/ent/migrate/migrations/atlas.sum @@ -1,4 +1,5 @@ -h1:mPthURxB95TzDLQ5/ssWicoIRBsv760ADaBxoBMbLjQ= +h1:2kxjClSNsbJy3F8cZByPAFN8hIABwG/6FLKLZnieiUs= 20240622203812_init.sql h1:Rt6NGvXbPBEpjg1pUFqn97FV8X3X2d1x7J2zOAAxolc= 20240623143618_added_indexes_on_id.sql h1:o57d1Hc6+wGscJp3VIWnCNrmQkBSeSKTEVKRSWmsc6k= 20240626213554.sql h1:sKGMP6giStLCW5lUK8VyaLxmiPRQTxT+y6xRkax1juM= +20240626225519.sql h1:/ARpBIJ80fqBpTK0WH4d5+3Z2kOh86SWlOGSu1PTOFc= diff --git a/internal/database/ent/schema/user_content.go b/internal/database/ent/schema/user_content.go index 6cc371b0..b68d635a 100644 --- a/internal/database/ent/schema/user_content.go +++ b/internal/database/ent/schema/user_content.go @@ -21,7 +21,7 @@ func (UserContent) Fields() []ent.Field { field.String("user_id").Immutable(), field.String("reference_id"), // - field.Any("value"), + field.String("value"), field.JSON("metadata", map[string]any{}), ) } diff --git a/internal/database/models/user.go b/internal/database/models/user.go index ae6172c4..efdfe56a 100644 --- a/internal/database/models/user.go +++ b/internal/database/models/user.go @@ -1,16 +1,39 @@ package models -import "github.com/cufee/aftermath/internal/permissions" +import ( + "github.com/cufee/aftermath/internal/permissions" +) type User struct { ID string Permissions permissions.Permissions + Uploads []UserContent Connections []UserConnection + Restrictions []UserRestriction Subscriptions []UserSubscription } +func (u User) HasPermission(value permissions.Permissions) bool { + perms := u.Permissions + for _, c := range u.Connections { + perms.Add(c.Permissions) + } + for _, s := range u.Subscriptions { + perms.Add(s.Permissions) + } + for _, r := range u.Restrictions { + switch r.Type { + case RestrictionTypePartial: + perms.Remove(r.Restriction) + default: + return false + } + } + return perms.Has(value) +} + func (u User) Connection(kind ConnectionType) (UserConnection, bool) { for _, connection := range u.Connections { if connection.Type == kind { @@ -28,3 +51,12 @@ func (u User) Subscription(kind SubscriptionType) (UserSubscription, bool) { } return UserSubscription{}, false } + +func (u User) Content(kind UserContentType) (UserContent, bool) { + for _, content := range u.Uploads { + if content.Type == kind { + return content, true + } + } + return UserContent{}, false +} diff --git a/internal/database/models/user_content.go b/internal/database/models/user_content.go new file mode 100644 index 00000000..eff23973 --- /dev/null +++ b/internal/database/models/user_content.go @@ -0,0 +1,46 @@ +package models + +import "time" + +type UserContentType string + +const ( + UserContentTypeClanBackground = UserContentType("clan-background-image") + UserContentTypePersonalBackground = UserContentType("personal-background-image") +) + +func (t UserContentType) Valid() bool { + switch t { + case UserContentTypeClanBackground: + return true + case UserContentTypePersonalBackground: + return true + default: + return false + } +} + +// Values provides list valid values for Enum. +func (UserContentType) Values() []string { + var kinds []string + for _, s := range []UserContentType{ + UserContentTypeClanBackground, + UserContentTypePersonalBackground, + } { + kinds = append(kinds, string(s)) + } + return kinds +} + +type UserContent struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + + Type UserContentType + UserID string + ReferenceID string + + Value string + Meta map[string]any +} diff --git a/internal/database/models/user_restriction.go b/internal/database/models/user_restriction.go new file mode 100644 index 00000000..9a53729b --- /dev/null +++ b/internal/database/models/user_restriction.go @@ -0,0 +1,32 @@ +package models + +import ( + "time" + + "github.com/cufee/aftermath/internal/permissions" +) + +type UserRestrictionType string + +const ( + RestrictionTypePartial UserRestrictionType = "partial" // restrict a specific permissions.Permissions value + RestrictionTypeComplete UserRestrictionType = "complete" // restricts user from using any and all features +) + +func (r *UserRestrictionType) Values() []string { + return []string{string(RestrictionTypePartial), string(RestrictionTypeComplete)} +} + +type UserRestriction struct { + ID string + Type UserRestrictionType + UserID string + + Restriction permissions.Permissions + PublicReason string + ModeratorComment string + + ExpiresAt time.Time + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/database/models/user_subscription.go b/internal/database/models/user_subscription copy.go similarity index 75% rename from internal/database/models/user_subscription.go rename to internal/database/models/user_subscription copy.go index 07926bf3..00207004 100644 --- a/internal/database/models/user_subscription.go +++ b/internal/database/models/user_subscription copy.go @@ -66,36 +66,6 @@ func (s SubscriptionType) Valid() bool { return slices.Contains(AllSubscriptionTypes, s) } -type UserContentType string - -const ( - UserContentTypeClanBackground = UserContentType("clan-background-image") - UserContentTypePersonalBackground = UserContentType("personal-background-image") -) - -func (t UserContentType) Valid() bool { - switch t { - case UserContentTypeClanBackground: - return true - case UserContentTypePersonalBackground: - return true - default: - return false - } -} - -// Values provides list valid values for Enum. -func (UserContentType) Values() []string { - var kinds []string - for _, s := range []UserContentType{ - UserContentTypeClanBackground, - UserContentTypePersonalBackground, - } { - kinds = append(kinds, string(s)) - } - return kinds -} - type UserSubscription struct { ID string Type SubscriptionType diff --git a/internal/database/users.go b/internal/database/users.go index 19c25e07..839ff321 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -21,9 +21,9 @@ func toUser(record *db.User, connections []*db.UserConnection, subscriptions []* for _, s := range subscriptions { user.Subscriptions = append(user.Subscriptions, toUserSubscription(s)) } - // for _, s := range content { - // user.Subscriptions = append(user.Subscriptions, toUserSubscription(s)) - // } + for _, c := range content { + user.Uploads = append(user.Uploads, toUserContent(c)) + } return user } @@ -49,16 +49,20 @@ func toUserSubscription(record *db.UserSubscription) models.UserSubscription { } } -// func toUserContent(record *db.UserSubscription) models.UserConnection { -// return models.UserSubscription{ -// ID: record.ID, -// Type: record.Type, -// UserID: record.UserID, -// ReferenceID: record.ReferenceID, -// ExpiresAt: time.Unix(record.ExpiresAt, 0), -// Permissions: permissions.Parse(record.Permissions, permissions.Blank), -// } -// } +func toUserContent(record *db.UserContent) models.UserContent { + return models.UserContent{ + ID: record.ID, + Type: record.Type, + UserID: record.UserID, + ReferenceID: record.ReferenceID, + + Value: record.Value, + Meta: record.Metadata, + + CreatedAt: time.Unix(record.CreatedAt, 0), + UpdatedAt: time.Unix(record.UpdatedAt, 0), + } +} type userGetOpts struct { content bool From 3ebc8604dae931a89897d0135e5bb60b4a16d7a3 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 19:37:34 -0400 Subject: [PATCH 187/341] added a secondary discord endpoint --- cmds/discord/commands/interactions.go | 2 +- cmds/discord/commands/link.go | 2 +- cmds/discord/commands/load.go | 3 +- cmds/discord/commands/manage.go | 3 +- cmds/discord/commands/session.go | 2 +- cmds/discord/commands/stats.go | 2 +- cmds/discord/discord.go | 9 ++-- internal/database/ent/db/migrate/schema.go | 2 +- .../ent/migrate/migrations/20240626233300.sql | 2 + .../database/ent/migrate/migrations/atlas.sum | 3 +- .../ent/schema/application_command.go | 2 +- main.go | 54 +++++++++++++++---- 12 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 internal/database/ent/migrate/migrations/20240626233300.sql diff --git a/cmds/discord/commands/interactions.go b/cmds/discord/commands/interactions.go index b75dd28b..07957f63 100644 --- a/cmds/discord/commands/interactions.go +++ b/cmds/discord/commands/interactions.go @@ -34,7 +34,7 @@ func newStatsRefreshButton(data models.DiscordInteraction) discordgo.MessageComp } func init() { - Loaded.add( + LoadedPublic.add( builder.NewCommand("refresh_stats_from_button"). ComponentType(func(customID string) bool { return strings.HasPrefix(customID, "refresh_stats_from_button_") diff --git a/cmds/discord/commands/link.go b/cmds/discord/commands/link.go index 1866735d..7a6b0f7e 100644 --- a/cmds/discord/commands/link.go +++ b/cmds/discord/commands/link.go @@ -11,7 +11,7 @@ import ( ) func init() { - Loaded.add( + LoadedPublic.add( builder.NewCommand("link"). Options( builder.NewOption("nickname", discordgo.ApplicationCommandOptionString). diff --git a/cmds/discord/commands/load.go b/cmds/discord/commands/load.go index 0d5199ec..0940e41e 100644 --- a/cmds/discord/commands/load.go +++ b/cmds/discord/commands/load.go @@ -16,4 +16,5 @@ func (c *commandInit) Compose() []builder.Command { return commands } -var Loaded commandInit +var LoadedPublic commandInit +var LoadedInternal commandInit diff --git a/cmds/discord/commands/manage.go b/cmds/discord/commands/manage.go index 5fd07cbd..4b567039 100644 --- a/cmds/discord/commands/manage.go +++ b/cmds/discord/commands/manage.go @@ -16,9 +16,8 @@ import ( ) func init() { - Loaded.add( + LoadedInternal.add( builder.NewCommand("manage"). - // ExclusiveToGuilds(os.Getenv("DISCORD_PRIMARY_GUILD_ID")). Middleware(middleware.RequirePermissions(permissions.UseDebugFeatures)). Options( builder.NewOption("users", discordgo.ApplicationCommandOptionSubCommandGroup).Options( diff --git a/cmds/discord/commands/session.go b/cmds/discord/commands/session.go index 641a95bd..e97b2ffb 100644 --- a/cmds/discord/commands/session.go +++ b/cmds/discord/commands/session.go @@ -19,7 +19,7 @@ import ( ) func init() { - Loaded.add( + LoadedPublic.add( builder.NewCommand("session"). Options(defaultStatsOptions...). Handler(func(ctx *common.Context) error { diff --git a/cmds/discord/commands/stats.go b/cmds/discord/commands/stats.go index ef5eca5f..0dfdc547 100644 --- a/cmds/discord/commands/stats.go +++ b/cmds/discord/commands/stats.go @@ -16,7 +16,7 @@ import ( ) func init() { - Loaded.add( + LoadedPublic.add( builder.NewCommand("stats"). Options(defaultStatsOptions...). Handler(func(ctx *common.Context) error { diff --git a/cmds/discord/discord.go b/cmds/discord/discord.go index ab4076ac..1c841493 100644 --- a/cmds/discord/discord.go +++ b/cmds/discord/discord.go @@ -5,20 +5,17 @@ import ( "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/discord/commands" + "github.com/cufee/aftermath/cmds/discord/commands/builder" "github.com/cufee/aftermath/cmds/discord/router" ) -func NewRouterHandler(coreClient core.Client, token string, publicKey string) (http.HandlerFunc, error) { +func NewRouterHandler(coreClient core.Client, token string, publicKey string, commands ...builder.Command) (http.HandlerFunc, error) { rt, err := router.NewRouter(coreClient, token, publicKey) if err != nil { return nil, err } - rt.LoadCommands(commands.Loaded.Compose()...) - - // should always be loaded last - // rt.LoadMiddleware(middleware.FetchUser(common.ContextKeyUser, coreClient.DB)) + rt.LoadCommands(commands...) err = rt.UpdateLoadedCommands() if err != nil { diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index b198249e..716a4a3c 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -196,7 +196,7 @@ var ( {Name: "id", Type: field.TypeString, Unique: true}, {Name: "created_at", Type: field.TypeInt64}, {Name: "updated_at", Type: field.TypeInt64}, - {Name: "name", Type: field.TypeString, Unique: true}, + {Name: "name", Type: field.TypeString}, {Name: "version", Type: field.TypeString}, {Name: "options_hash", Type: field.TypeString}, } diff --git a/internal/database/ent/migrate/migrations/20240626233300.sql b/internal/database/ent/migrate/migrations/20240626233300.sql new file mode 100644 index 00000000..235896cb --- /dev/null +++ b/internal/database/ent/migrate/migrations/20240626233300.sql @@ -0,0 +1,2 @@ +-- Drop index "application_commands_name_key" from table: "application_commands" +DROP INDEX `application_commands_name_key`; diff --git a/internal/database/ent/migrate/migrations/atlas.sum b/internal/database/ent/migrate/migrations/atlas.sum index 39f948c4..54f7f426 100644 --- a/internal/database/ent/migrate/migrations/atlas.sum +++ b/internal/database/ent/migrate/migrations/atlas.sum @@ -1,5 +1,6 @@ -h1:2kxjClSNsbJy3F8cZByPAFN8hIABwG/6FLKLZnieiUs= +h1:CT0057YWfzRHEDECr8CPEZlaBJOat31j3qFdme4zb/8= 20240622203812_init.sql h1:Rt6NGvXbPBEpjg1pUFqn97FV8X3X2d1x7J2zOAAxolc= 20240623143618_added_indexes_on_id.sql h1:o57d1Hc6+wGscJp3VIWnCNrmQkBSeSKTEVKRSWmsc6k= 20240626213554.sql h1:sKGMP6giStLCW5lUK8VyaLxmiPRQTxT+y6xRkax1juM= 20240626225519.sql h1:/ARpBIJ80fqBpTK0WH4d5+3Z2kOh86SWlOGSu1PTOFc= +20240626233300.sql h1:Klr1WjPuZR6NtWPeWwIUHOokVyEEWXbo80DVxNaCzjo= diff --git a/internal/database/ent/schema/application_command.go b/internal/database/ent/schema/application_command.go index e4cd39b3..ffffd492 100644 --- a/internal/database/ent/schema/application_command.go +++ b/internal/database/ent/schema/application_command.go @@ -14,7 +14,7 @@ type ApplicationCommand struct { // Fields of the ApplicationCommand. func (ApplicationCommand) Fields() []ent.Field { return append(defaultFields, - field.String("name").Unique().NotEmpty(), + field.String("name").NotEmpty(), field.String("version").NotEmpty(), field.String("options_hash").NotEmpty(), ) diff --git a/main.go b/main.go index 9fa06838..6ef9d117 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "github.com/cufee/aftermath/cmds/core/queue" "github.com/cufee/aftermath/cmds/core/scheduler" "github.com/cufee/aftermath/cmds/core/tasks" + "github.com/cufee/aftermath/cmds/discord/commands" "github.com/cufee/aftermath/cmds/core/server" "github.com/cufee/aftermath/cmds/core/server/handlers/private" @@ -71,11 +72,6 @@ func main() { // Load some init options to registered admin accounts and etc logic.ApplyInitOptions(liveCoreClient.Database()) - discordHandler, err := discord.NewRouterHandler(liveCoreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) - if err != nil { - log.Fatal().Msgf("discord#NewRouterHandler failed %s", err) - } - if e := os.Getenv("PRIVATE_SERVER_ENABLED"); e == "true" { port := os.Getenv("PRIVATE_SERVER_PORT") servePrivate := server.NewServer(port, []server.Handler{ @@ -96,11 +92,12 @@ func main() { go servePrivate() } + discordHandlers := discordHandlersFromEnv(liveCoreClient) + // /discord/public/callback + // /discord/internal/callback + port := os.Getenv("PORT") - servePublic := server.NewServer(port, server.Handler{ - Path: "POST /discord/callback", - Func: discordHandler, - }) + servePublic := server.NewServer(port, discordHandlers...) log.Info().Str("port", port).Msg("starting a public server") go servePublic() @@ -112,6 +109,45 @@ func main() { log.Info().Msgf("received %s, exiting", sig.String()) } +func discordHandlersFromEnv(coreClient core.Client) []server.Handler { + var handlers []server.Handler + + // main Discord with all the user-facing command + { + if os.Getenv("DISCORD_TOKEN") == "" || os.Getenv("DISCORD_PUBLIC_KEY") == "" { + log.Fatal().Msg("DISCORD_TOKEN and DISCORD_PUBLIC_KEY are required") + + } + mainDiscordHandler, err := discord.NewRouterHandler(coreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY"), commands.LoadedPublic.Compose()...) + if err != nil { + log.Fatal().Msgf("discord#NewRouterHandler failed %s", err) + } + handlers = append(handlers, server.Handler{ + Path: "POST /discord/public/callback", + Func: mainDiscordHandler, + }) + } + + // secondary Discord bot with mod/admin commands - permissions are still checked + if token := os.Getenv("INTERNAL_DISCORD_TOKEN"); token != "" { + publicKey := os.Getenv("INTERNAL_DISCORD_PUBLIC_KEY") + if publicKey == "" { + log.Fatal().Msg("discordHandlersFromEnv failed missing INTERNAL_DISCORD_PUBLIC_KEY") + } + + internalDiscordHandler, err := discord.NewRouterHandler(coreClient, token, publicKey, commands.LoadedInternal.Compose()...) + if err != nil { + log.Fatal().Msgf("discord#NewRouterHandler failed %s", err) + } + handlers = append(handlers, server.Handler{ + Path: "POST /discord/internal/callback", + Func: internalDiscordHandler, + }) + } + + return handlers +} + func startSchedulerFromEnv(ctx context.Context, coreClient core.Client) (func(), error) { if os.Getenv("SCHEDULER_ENABLED") != "true" { return func() {}, nil From 8c6a42d90b5a3d813586b237958ba43f31f6f3c0 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 20:58:22 -0400 Subject: [PATCH 188/341] added asset cli, renamed dir --- .gitignore | 2 +- cmd/assets/README.md | 2 + cmd/assets/main.go | 39 +++++ cmd/assets/news.go | 148 ++++++++++++++++++ {cmds => cmd}/core/client.go | 0 {cmds => cmd}/core/queue/errors.go | 0 {cmds => cmd}/core/queue/queue.go | 6 +- {cmds => cmd}/core/scheduler/scheduler.go | 2 +- {cmds => cmd}/core/scheduler/workers.go | 4 +- .../core/server/handlers/private/accounts.go | 2 +- .../core/server/handlers/private/snapshots.go | 4 +- .../core/server/handlers/private/tasks.go | 4 +- {cmds => cmd}/core/server/server.go | 0 {cmds => cmd}/core/tasks/cleanup.go | 2 +- {cmds => cmd}/core/tasks/handler.go | 2 +- {cmds => cmd}/core/tasks/sessions.go | 2 +- {cmds => cmd}/core/tasks/split.go | 0 .../discord/commands/builder/command.go | 4 +- .../discord/commands/builder/common.go | 0 .../discord/commands/builder/option.go | 2 +- .../discord/commands/builder/option_choice.go | 2 +- .../discord/commands/builder/params.go | 0 {cmds => cmd}/discord/commands/common.go | 2 +- .../discord/commands/interactions.go | 4 +- {cmds => cmd}/discord/commands/link.go | 4 +- {cmds => cmd}/discord/commands/load.go | 2 +- {cmds => cmd}/discord/commands/manage.go | 6 +- {cmds => cmd}/discord/commands/options.go | 4 +- {cmds => cmd}/discord/commands/session.go | 4 +- {cmds => cmd}/discord/commands/stats.go | 4 +- {cmds => cmd}/discord/common/context.go | 4 +- {cmds => cmd}/discord/common/locale.go | 0 {cmds => cmd}/discord/common/pointer.go | 0 {cmds => cmd}/discord/common/reply.go | 0 {cmds => cmd}/discord/discord.go | 6 +- .../discord/middleware/middleware.go | 2 +- {cmds => cmd}/discord/rest/client.go | 0 {cmds => cmd}/discord/rest/commands.go | 0 {cmds => cmd}/discord/rest/interactions.go | 0 {cmds => cmd}/discord/router/handler.go | 4 +- {cmds => cmd}/discord/router/router.go | 8 +- go.mod | 2 + go.sum | 12 ++ main.go | 18 +-- 44 files changed, 258 insertions(+), 55 deletions(-) create mode 100644 cmd/assets/README.md create mode 100644 cmd/assets/main.go create mode 100644 cmd/assets/news.go rename {cmds => cmd}/core/client.go (100%) rename {cmds => cmd}/core/queue/errors.go (100%) rename {cmds => cmd}/core/queue/queue.go (97%) rename {cmds => cmd}/core/scheduler/scheduler.go (97%) rename {cmds => cmd}/core/scheduler/workers.go (98%) rename {cmds => cmd}/core/server/handlers/private/accounts.go (98%) rename {cmds => cmd}/core/server/handlers/private/snapshots.go (89%) rename {cmds => cmd}/core/server/handlers/private/tasks.go (73%) rename {cmds => cmd}/core/server/server.go (100%) rename {cmds => cmd}/core/tasks/cleanup.go (98%) rename {cmds => cmd}/core/tasks/handler.go (90%) rename {cmds => cmd}/core/tasks/sessions.go (98%) rename {cmds => cmd}/core/tasks/split.go (100%) rename {cmds => cmd}/discord/commands/builder/command.go (96%) rename {cmds => cmd}/discord/commands/builder/common.go (100%) rename {cmds => cmd}/discord/commands/builder/option.go (98%) rename {cmds => cmd}/discord/commands/builder/option_choice.go (95%) rename {cmds => cmd}/discord/commands/builder/params.go (100%) rename {cmds => cmd}/discord/commands/common.go (92%) rename {cmds => cmd}/discord/commands/interactions.go (97%) rename {cmds => cmd}/discord/commands/link.go (95%) rename {cmds => cmd}/discord/commands/load.go (84%) rename {cmds => cmd}/discord/commands/manage.go (97%) rename {cmds => cmd}/discord/commands/options.go (96%) rename {cmds => cmd}/discord/commands/session.go (97%) rename {cmds => cmd}/discord/commands/stats.go (96%) rename {cmds => cmd}/discord/common/context.go (98%) rename {cmds => cmd}/discord/common/locale.go (100%) rename {cmds => cmd}/discord/common/pointer.go (100%) rename {cmds => cmd}/discord/common/reply.go (100%) rename {cmds => cmd}/discord/discord.go (73%) rename {cmds => cmd}/discord/middleware/middleware.go (91%) rename {cmds => cmd}/discord/rest/client.go (100%) rename {cmds => cmd}/discord/rest/commands.go (100%) rename {cmds => cmd}/discord/rest/interactions.go (100%) rename {cmds => cmd}/discord/router/handler.go (98%) rename {cmds => cmd}/discord/router/router.go (95%) diff --git a/.gitignore b/.gitignore index 5957f913..58d86976 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ tmp .vscode **/.DS_Store -.env +**/.env diff --git a/cmd/assets/README.md b/cmd/assets/README.md new file mode 100644 index 00000000..c76b5e01 --- /dev/null +++ b/cmd/assets/README.md @@ -0,0 +1,2 @@ +# Aftermath Assets +This application is designed to cache some assets required for Aftermath \ No newline at end of file diff --git a/cmd/assets/main.go b/cmd/assets/main.go new file mode 100644 index 00000000..cb01149c --- /dev/null +++ b/cmd/assets/main.go @@ -0,0 +1,39 @@ +// go:build ignore + +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/cufee/aftermath/internal/database" +) + +func main() { + // db, err := newDatabaseClientFromEnv() + // if err != nil { + // panic(err) + // } + // _ = db + + err := ScrapeNewsImages() + if err != nil { + panic(err) + } +} + +func newDatabaseClientFromEnv() (database.Client, error) { + err := os.MkdirAll(os.Getenv("DATABASE_PATH"), os.ModePerm) + if err != nil { + return nil, fmt.Errorf("os#MkdirAll failed %w", err) + } + + client, err := database.NewSQLiteClient(filepath.Join(os.Getenv("DATABASE_PATH"), os.Getenv("DATABASE_NAME"))) + if err != nil { + + return nil, fmt.Errorf("database#NewClient failed %w", err) + } + + return client, nil +} diff --git a/cmd/assets/news.go b/cmd/assets/news.go new file mode 100644 index 00000000..4b9d65b7 --- /dev/null +++ b/cmd/assets/news.go @@ -0,0 +1,148 @@ +package main + +import ( + "bytes" + "encoding/json" + "image" + "io" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/disintegration/imaging" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + _ "github.com/joho/godotenv/autoload" +) + +var httpClient = http.Client{ + Timeout: time.Second * 15, +} + +type Image struct { + image.Image + PublishDate time.Time + Name string + URL string +} + +type NewsItem struct { + Name string `json:"name"` + Images struct { + Medium string `json:"medium"` + } `json:"images"` + PublishDate string `json:"publication_start_at"` + PublishDateParsed time.Time `json:"-"` +} + +func ScrapeNewsImages() error { + endpoint := os.Getenv("NEWS_ENDPOINT") + if endpoint == "" { + return errors.New("NEWS_ENDPOINT not set") + } + + link, err := url.Parse(endpoint) + if err != nil { + return errors.Wrap(err, "invalid endpoint url") + } + + images, err := scrapeSinceDate(link, time.Now().Add(time.Hour*24*-1), 0) + if err != nil { + return err + } + + var wg sync.WaitGroup + validImagesCh := make(chan Image, len(images)) + for _, img := range images { + wg.Add(1) + go func(img NewsItem) { + defer wg.Done() + + res, err := httpClient.Get(img.Images.Medium) + if err != nil { + log.Err(err).Msg("failed to get image") + return + } + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + log.Err(err).Msg("failed to read image response body") + return + } + + decoded, err := imaging.Decode(bytes.NewReader(data)) + if err != nil { + log.Err(err).Msg("failed to decode image") + return + } + validImagesCh <- Image{decoded, img.PublishDateParsed, img.Name, img.Images.Medium} + + }(img) + } + wg.Wait() + close(validImagesCh) + + for img := range validImagesCh { + println("image", img.Name) + } + + return nil +} + +func scrapeSinceDate(endpoint *url.URL, sinceDate time.Time, page int) ([]NewsItem, error) { + query := url.Values{} + query.Set("page", strconv.Itoa(page)) + query.Set("page_size", "10") + endpoint.RawQuery = query.Encode() + + var data struct { + Count int `json:"count"` + Results []NewsItem `json:"results"` + } + + res, err := httpClient.Get(endpoint.String()) + if err != nil { + return nil, err + } + defer res.Body.Close() + err = json.NewDecoder(res.Body).Decode(&data) + if err != nil { + return nil, err + } + + var validImages []NewsItem + var oldestDate = time.Now() + for _, img := range data.Results { + img.PublishDateParsed, err = time.Parse("2006-01-02T15:04:05", img.PublishDate) + if err != nil { + log.Err(err).Msg("failed to parse date") + continue + } + + if img.PublishDateParsed.Before(sinceDate) { + oldestDate = img.PublishDateParsed + break + } + if strings.Contains(strings.ToLower(img.Name), "video:") { + continue + } + + validImages = append(validImages, img) + if img.PublishDateParsed.Before(oldestDate) { + oldestDate = img.PublishDateParsed + } + } + + if oldestDate.Before(sinceDate) { + return validImages, nil + } + + nextPage, err := scrapeSinceDate(endpoint, sinceDate, page+1) + return append(validImages, nextPage...), err +} diff --git a/cmds/core/client.go b/cmd/core/client.go similarity index 100% rename from cmds/core/client.go rename to cmd/core/client.go diff --git a/cmds/core/queue/errors.go b/cmd/core/queue/errors.go similarity index 100% rename from cmds/core/queue/errors.go rename to cmd/core/queue/errors.go diff --git a/cmds/core/queue/queue.go b/cmd/core/queue/queue.go similarity index 97% rename from cmds/core/queue/queue.go rename to cmd/core/queue/queue.go index e2f13203..f32e7f41 100644 --- a/cmds/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -8,9 +8,9 @@ import ( "sync" "time" - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/scheduler" - "github.com/cufee/aftermath/cmds/core/tasks" + "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/core/scheduler" + "github.com/cufee/aftermath/cmd/core/tasks" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/rs/zerolog/log" diff --git a/cmds/core/scheduler/scheduler.go b/cmd/core/scheduler/scheduler.go similarity index 97% rename from cmds/core/scheduler/scheduler.go rename to cmd/core/scheduler/scheduler.go index ae1ac46d..bcadb57e 100644 --- a/cmds/core/scheduler/scheduler.go +++ b/cmd/core/scheduler/scheduler.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmd/core" "github.com/go-co-op/gocron" ) diff --git a/cmds/core/scheduler/workers.go b/cmd/core/scheduler/workers.go similarity index 98% rename from cmds/core/scheduler/workers.go rename to cmd/core/scheduler/workers.go index bbd8b2eb..b6697880 100644 --- a/cmds/core/scheduler/workers.go +++ b/cmd/core/scheduler/workers.go @@ -4,8 +4,8 @@ import ( "context" "time" - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/tasks" + "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/core/tasks" "golang.org/x/text/language" "github.com/cufee/aftermath/internal/database" diff --git a/cmds/core/server/handlers/private/accounts.go b/cmd/core/server/handlers/private/accounts.go similarity index 98% rename from cmds/core/server/handlers/private/accounts.go rename to cmd/core/server/handlers/private/accounts.go index 0b8e3945..06734a2b 100644 --- a/cmds/core/server/handlers/private/accounts.go +++ b/cmd/core/server/handlers/private/accounts.go @@ -9,7 +9,7 @@ import ( "strconv" "sync" - "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmd/core" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/am-wg-proxy-next/v2/types" diff --git a/cmds/core/server/handlers/private/snapshots.go b/cmd/core/server/handlers/private/snapshots.go similarity index 89% rename from cmds/core/server/handlers/private/snapshots.go rename to cmd/core/server/handlers/private/snapshots.go index c56b563d..c01ea9c5 100644 --- a/cmds/core/server/handlers/private/snapshots.go +++ b/cmd/core/server/handlers/private/snapshots.go @@ -5,8 +5,8 @@ import ( "slices" "strings" - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/tasks" + "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/core/tasks" ) var validRealms = []string{"na", "eu", "as"} diff --git a/cmds/core/server/handlers/private/tasks.go b/cmd/core/server/handlers/private/tasks.go similarity index 73% rename from cmds/core/server/handlers/private/tasks.go rename to cmd/core/server/handlers/private/tasks.go index 1a520aa1..de1961fb 100644 --- a/cmds/core/server/handlers/private/tasks.go +++ b/cmd/core/server/handlers/private/tasks.go @@ -3,8 +3,8 @@ package private import ( "net/http" - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/scheduler" + "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/core/scheduler" ) func RestartStaleTasks(client core.Client) http.HandlerFunc { diff --git a/cmds/core/server/server.go b/cmd/core/server/server.go similarity index 100% rename from cmds/core/server/server.go rename to cmd/core/server/server.go diff --git a/cmds/core/tasks/cleanup.go b/cmd/core/tasks/cleanup.go similarity index 98% rename from cmds/core/tasks/cleanup.go rename to cmd/core/tasks/cleanup.go index efdcf6d9..e931faf4 100644 --- a/cmds/core/tasks/cleanup.go +++ b/cmd/core/tasks/cleanup.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" - "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmd/core" "github.com/cufee/aftermath/internal/database/models" ) diff --git a/cmds/core/tasks/handler.go b/cmd/core/tasks/handler.go similarity index 90% rename from cmds/core/tasks/handler.go rename to cmd/core/tasks/handler.go index 11e49bb1..f54c06c5 100644 --- a/cmds/core/tasks/handler.go +++ b/cmd/core/tasks/handler.go @@ -1,7 +1,7 @@ package tasks import ( - "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmd/core" "github.com/cufee/aftermath/internal/database/models" ) diff --git a/cmds/core/tasks/sessions.go b/cmd/core/tasks/sessions.go similarity index 98% rename from cmds/core/tasks/sessions.go rename to cmd/core/tasks/sessions.go index 0125d1ea..e3a4cfd1 100644 --- a/cmds/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" - "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmd/core" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/logic" "github.com/rs/zerolog/log" diff --git a/cmds/core/tasks/split.go b/cmd/core/tasks/split.go similarity index 100% rename from cmds/core/tasks/split.go rename to cmd/core/tasks/split.go diff --git a/cmds/discord/commands/builder/command.go b/cmd/discord/commands/builder/command.go similarity index 96% rename from cmds/discord/commands/builder/command.go rename to cmd/discord/commands/builder/command.go index 3eb8d470..5d654538 100644 --- a/cmds/discord/commands/builder/command.go +++ b/cmd/discord/commands/builder/command.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/discord/common" - "github.com/cufee/aftermath/cmds/discord/middleware" + "github.com/cufee/aftermath/cmd/discord/common" + "github.com/cufee/aftermath/cmd/discord/middleware" ) type commandType byte diff --git a/cmds/discord/commands/builder/common.go b/cmd/discord/commands/builder/common.go similarity index 100% rename from cmds/discord/commands/builder/common.go rename to cmd/discord/commands/builder/common.go diff --git a/cmds/discord/commands/builder/option.go b/cmd/discord/commands/builder/option.go similarity index 98% rename from cmds/discord/commands/builder/option.go rename to cmd/discord/commands/builder/option.go index e3133179..17f7b142 100644 --- a/cmds/discord/commands/builder/option.go +++ b/cmd/discord/commands/builder/option.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/common" ) type Option struct { diff --git a/cmds/discord/commands/builder/option_choice.go b/cmd/discord/commands/builder/option_choice.go similarity index 95% rename from cmds/discord/commands/builder/option_choice.go rename to cmd/discord/commands/builder/option_choice.go index 74db9549..96ac2868 100644 --- a/cmds/discord/commands/builder/option_choice.go +++ b/cmd/discord/commands/builder/option_choice.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/common" ) type OptionChoice struct { diff --git a/cmds/discord/commands/builder/params.go b/cmd/discord/commands/builder/params.go similarity index 100% rename from cmds/discord/commands/builder/params.go rename to cmd/discord/commands/builder/params.go diff --git a/cmds/discord/commands/common.go b/cmd/discord/commands/common.go similarity index 92% rename from cmds/discord/commands/common.go rename to cmd/discord/commands/common.go index 56438bb7..0d591818 100644 --- a/cmds/discord/commands/common.go +++ b/cmd/discord/commands/common.go @@ -2,7 +2,7 @@ package commands import ( "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/common" "github.com/cufee/aftermath/internal/database/models" ) diff --git a/cmds/discord/commands/interactions.go b/cmd/discord/commands/interactions.go similarity index 97% rename from cmds/discord/commands/interactions.go rename to cmd/discord/commands/interactions.go index 07957f63..901c252c 100644 --- a/cmds/discord/commands/interactions.go +++ b/cmd/discord/commands/interactions.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/common" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" "github.com/cufee/aftermath/internal/stats/fetch/v1" diff --git a/cmds/discord/commands/link.go b/cmd/discord/commands/link.go similarity index 95% rename from cmds/discord/commands/link.go rename to cmd/discord/commands/link.go index 7a6b0f7e..87705e5a 100644 --- a/cmds/discord/commands/link.go +++ b/cmd/discord/commands/link.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/common" "github.com/cufee/aftermath/internal/database/models" ) diff --git a/cmds/discord/commands/load.go b/cmd/discord/commands/load.go similarity index 84% rename from cmds/discord/commands/load.go rename to cmd/discord/commands/load.go index 0940e41e..6829651d 100644 --- a/cmds/discord/commands/load.go +++ b/cmd/discord/commands/load.go @@ -1,6 +1,6 @@ package commands -import "github.com/cufee/aftermath/cmds/discord/commands/builder" +import "github.com/cufee/aftermath/cmd/discord/commands/builder" type commandInit []builder.Builder diff --git a/cmds/discord/commands/manage.go b/cmd/discord/commands/manage.go similarity index 97% rename from cmds/discord/commands/manage.go rename to cmd/discord/commands/manage.go index 4b567039..eaaaad67 100644 --- a/cmds/discord/commands/manage.go +++ b/cmd/discord/commands/manage.go @@ -7,9 +7,9 @@ import ( "time" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/common" - "github.com/cufee/aftermath/cmds/discord/middleware" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/common" + "github.com/cufee/aftermath/cmd/discord/middleware" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" diff --git a/cmds/discord/commands/options.go b/cmd/discord/commands/options.go similarity index 96% rename from cmds/discord/commands/options.go rename to cmd/discord/commands/options.go index e8528249..c5f3db1e 100644 --- a/cmds/discord/commands/options.go +++ b/cmd/discord/commands/options.go @@ -5,8 +5,8 @@ import ( "time" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/common" ) var validNameRegex = regexp.MustCompile(`[^\w\_]`) diff --git a/cmds/discord/commands/session.go b/cmd/discord/commands/session.go similarity index 97% rename from cmds/discord/commands/session.go rename to cmd/discord/commands/session.go index e97b2ffb..bd217a0d 100644 --- a/cmds/discord/commands/session.go +++ b/cmd/discord/commands/session.go @@ -6,8 +6,8 @@ import ( "fmt" "strings" - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/common" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" diff --git a/cmds/discord/commands/stats.go b/cmd/discord/commands/stats.go similarity index 96% rename from cmds/discord/commands/stats.go rename to cmd/discord/commands/stats.go index 0dfdc547..c64fc7ce 100644 --- a/cmds/discord/commands/stats.go +++ b/cmd/discord/commands/stats.go @@ -6,8 +6,8 @@ import ( "fmt" "strings" - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/common" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" diff --git a/cmds/discord/common/context.go b/cmd/discord/common/context.go similarity index 98% rename from cmds/discord/common/context.go rename to cmd/discord/common/context.go index 004b05ac..ea64e3a4 100644 --- a/cmds/discord/common/context.go +++ b/cmd/discord/common/context.go @@ -6,8 +6,8 @@ import ( "github.com/pkg/errors" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/discord/rest" + "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/discord/rest" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" diff --git a/cmds/discord/common/locale.go b/cmd/discord/common/locale.go similarity index 100% rename from cmds/discord/common/locale.go rename to cmd/discord/common/locale.go diff --git a/cmds/discord/common/pointer.go b/cmd/discord/common/pointer.go similarity index 100% rename from cmds/discord/common/pointer.go rename to cmd/discord/common/pointer.go diff --git a/cmds/discord/common/reply.go b/cmd/discord/common/reply.go similarity index 100% rename from cmds/discord/common/reply.go rename to cmd/discord/common/reply.go diff --git a/cmds/discord/discord.go b/cmd/discord/discord.go similarity index 73% rename from cmds/discord/discord.go rename to cmd/discord/discord.go index 1c841493..5fdcd3f4 100644 --- a/cmds/discord/discord.go +++ b/cmd/discord/discord.go @@ -3,10 +3,10 @@ package discord import ( "net/http" - "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmd/core" - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/router" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/router" ) func NewRouterHandler(coreClient core.Client, token string, publicKey string, commands ...builder.Command) (http.HandlerFunc, error) { diff --git a/cmds/discord/middleware/middleware.go b/cmd/discord/middleware/middleware.go similarity index 91% rename from cmds/discord/middleware/middleware.go rename to cmd/discord/middleware/middleware.go index 26db204e..ce1d1aad 100644 --- a/cmds/discord/middleware/middleware.go +++ b/cmd/discord/middleware/middleware.go @@ -1,7 +1,7 @@ package middleware import ( - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/common" "github.com/cufee/aftermath/internal/permissions" ) diff --git a/cmds/discord/rest/client.go b/cmd/discord/rest/client.go similarity index 100% rename from cmds/discord/rest/client.go rename to cmd/discord/rest/client.go diff --git a/cmds/discord/rest/commands.go b/cmd/discord/rest/commands.go similarity index 100% rename from cmds/discord/rest/commands.go rename to cmd/discord/rest/commands.go diff --git a/cmds/discord/rest/interactions.go b/cmd/discord/rest/interactions.go similarity index 100% rename from cmds/discord/rest/interactions.go rename to cmd/discord/rest/interactions.go diff --git a/cmds/discord/router/handler.go b/cmd/discord/router/handler.go similarity index 98% rename from cmds/discord/router/handler.go rename to cmd/discord/router/handler.go index 721ea022..3f913d94 100644 --- a/cmds/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -15,8 +15,8 @@ import ( "github.com/pkg/errors" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/common" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/common" "github.com/rs/zerolog/log" ) diff --git a/cmds/discord/router/router.go b/cmd/discord/router/router.go similarity index 95% rename from cmds/discord/router/router.go rename to cmd/discord/router/router.go index ef30be48..459717fa 100644 --- a/cmds/discord/router/router.go +++ b/cmd/discord/router/router.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/discord/commands/builder" - "github.com/cufee/aftermath/cmds/discord/middleware" - "github.com/cufee/aftermath/cmds/discord/rest" + "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/middleware" + "github.com/cufee/aftermath/cmd/discord/rest" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/logic" "github.com/rs/zerolog/log" diff --git a/go.mod b/go.mod index c2c64c4a..c764876f 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/deluan/lookup v0.0.7 // indirect github.com/go-openapi/inflect v0.21.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -38,6 +39,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/otiai10/gosseract/v2 v2.4.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/zclconf/go-cty v1.14.4 // indirect diff --git a/go.sum b/go.sum index ee4f6f7a..c1f12c43 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/cufee/am-wg-proxy-next/v2 v2.1.5/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H 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/deluan/lookup v0.0.7 h1:O86eel067BpK+g4TmgsQPQbc8aU9m0OhlNRHxKVf9ec= +github.com/deluan/lookup v0.0.7/go.mod h1:mnYfdZ+Pz5it/x8IHBOSdDVzxFmcDf7wxYK8rtVV/tU= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= @@ -35,6 +37,7 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -42,6 +45,7 @@ github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 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/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -68,6 +72,8 @@ github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQ github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/otiai10/gosseract/v2 v2.4.1 h1:G8AyBpXEeSlcq8TI85LH/pM5SXk8Djy2GEXisgyblRw= +github.com/otiai10/gosseract/v2 v2.4.1/go.mod h1:1gNWP4Hgr2o7yqWfs6r5bZxAatjOIdqWxJLWsTsembk= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -83,6 +89,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -113,6 +121,7 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= @@ -123,10 +132,12 @@ golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= @@ -139,6 +150,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index 6ef9d117..fa85a4a9 100644 --- a/main.go +++ b/main.go @@ -13,15 +13,15 @@ import ( "os" "time" - "github.com/cufee/aftermath/cmds/core" - "github.com/cufee/aftermath/cmds/core/queue" - "github.com/cufee/aftermath/cmds/core/scheduler" - "github.com/cufee/aftermath/cmds/core/tasks" - "github.com/cufee/aftermath/cmds/discord/commands" - - "github.com/cufee/aftermath/cmds/core/server" - "github.com/cufee/aftermath/cmds/core/server/handlers/private" - "github.com/cufee/aftermath/cmds/discord" + "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/core/queue" + "github.com/cufee/aftermath/cmd/core/scheduler" + "github.com/cufee/aftermath/cmd/core/tasks" + "github.com/cufee/aftermath/cmd/discord/commands" + + "github.com/cufee/aftermath/cmd/core/server" + "github.com/cufee/aftermath/cmd/core/server/handlers/private" + "github.com/cufee/aftermath/cmd/discord" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" From e1d24eb67bd443ca110f18acb6d951b073beb671 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 20:58:34 -0400 Subject: [PATCH 189/341] added env example --- cmd/assets/.env.example | 1 + cmd/assets/news.go | 10 +--------- 2 files changed, 2 insertions(+), 9 deletions(-) create mode 100644 cmd/assets/.env.example diff --git a/cmd/assets/.env.example b/cmd/assets/.env.example new file mode 100644 index 00000000..198c6b24 --- /dev/null +++ b/cmd/assets/.env.example @@ -0,0 +1 @@ +NEWS_ENDPOINT="https://na.wotblitz.com/en/api/cms/news/" \ No newline at end of file diff --git a/cmd/assets/news.go b/cmd/assets/news.go index 4b9d65b7..2801174e 100644 --- a/cmd/assets/news.go +++ b/cmd/assets/news.go @@ -1,10 +1,8 @@ package main import ( - "bytes" "encoding/json" "image" - "io" "net/http" "net/url" "os" @@ -70,13 +68,7 @@ func ScrapeNewsImages() error { } defer res.Body.Close() - data, err := io.ReadAll(res.Body) - if err != nil { - log.Err(err).Msg("failed to read image response body") - return - } - - decoded, err := imaging.Decode(bytes.NewReader(data)) + decoded, err := imaging.Decode(res.Body) if err != nil { log.Err(err).Msg("failed to decode image") return From 48f600f308478e07c4c06fa14d7ba59097fcc899 Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 26 Jun 2024 20:58:59 -0400 Subject: [PATCH 190/341] tidy --- go.mod | 2 -- go.sum | 22 ---------------------- 2 files changed, 24 deletions(-) diff --git a/go.mod b/go.mod index c764876f..c2c64c4a 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,6 @@ require ( github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deluan/lookup v0.0.7 // indirect github.com/go-openapi/inflect v0.21.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -39,7 +38,6 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/otiai10/gosseract/v2 v2.4.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/zclconf/go-cty v1.14.4 // indirect diff --git a/go.sum b/go.sum index c1f12c43..b4bd05ca 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,6 @@ github.com/cufee/am-wg-proxy-next/v2 v2.1.5/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H 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/deluan/lookup v0.0.7 h1:O86eel067BpK+g4TmgsQPQbc8aU9m0OhlNRHxKVf9ec= -github.com/deluan/lookup v0.0.7/go.mod h1:mnYfdZ+Pz5it/x8IHBOSdDVzxFmcDf7wxYK8rtVV/tU= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= @@ -37,7 +35,6 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -45,7 +42,6 @@ github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 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/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -62,25 +58,17 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/otiai10/gosseract/v2 v2.4.1 h1:G8AyBpXEeSlcq8TI85LH/pM5SXk8Djy2GEXisgyblRw= -github.com/otiai10/gosseract/v2 v2.4.1/go.mod h1:1gNWP4Hgr2o7yqWfs6r5bZxAatjOIdqWxJLWsTsembk= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -89,12 +77,6 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -121,7 +103,6 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= @@ -132,12 +113,10 @@ golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= @@ -150,7 +129,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 3ecc059eee8d3145624aa17bba46fffa8a686aa2 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 13:25:41 -0400 Subject: [PATCH 191/341] fixed vehicle snapshot queries --- .gitignore | 1 + Dockerfile.migrate | 2 +- Taskfile.yaml | 7 +- go.sum | 10 + internal/commbus/bus.go | 1 - internal/database/accounts.go | 11 +- internal/database/cleanup.go | 10 +- internal/database/client.go | 26 +- internal/database/discord_interactions.go | 3 +- internal/database/ent/db/account.go | 37 +- internal/database/ent/db/account/account.go | 8 +- internal/database/ent/db/account/where.go | 74 +- internal/database/ent/db/account_create.go | 37 +- internal/database/ent/db/account_query.go | 26 +- internal/database/ent/db/account_update.go | 147 +- internal/database/ent/db/accountsnapshot.go | 29 +- .../ent/db/accountsnapshot/accountsnapshot.go | 7 +- .../database/ent/db/accountsnapshot/where.go | 56 +- .../database/ent/db/accountsnapshot_create.go | 31 +- .../database/ent/db/accountsnapshot_query.go | 26 +- .../database/ent/db/accountsnapshot_update.go | 103 +- .../database/ent/db/achievementssnapshot.go | 29 +- .../achievementssnapshot.go | 7 +- .../ent/db/achievementssnapshot/where.go | 56 +- .../ent/db/achievementssnapshot_create.go | 31 +- .../ent/db/achievementssnapshot_query.go | 26 +- .../ent/db/achievementssnapshot_update.go | 103 +- internal/database/ent/db/appconfiguration.go | 21 +- .../db/appconfiguration/appconfiguration.go | 8 +- .../database/ent/db/appconfiguration/where.go | 38 +- .../ent/db/appconfiguration_create.go | 25 +- .../database/ent/db/appconfiguration_query.go | 26 +- .../ent/db/appconfiguration_update.go | 59 +- .../database/ent/db/applicationcommand.go | 21 +- .../applicationcommand/applicationcommand.go | 8 +- .../ent/db/applicationcommand/where.go | 38 +- .../ent/db/applicationcommand_create.go | 25 +- .../ent/db/applicationcommand_query.go | 26 +- .../ent/db/applicationcommand_update.go | 59 +- internal/database/ent/db/clan.go | 21 +- internal/database/ent/db/clan/clan.go | 8 +- internal/database/ent/db/clan/where.go | 38 +- internal/database/ent/db/clan_create.go | 25 +- internal/database/ent/db/clan_query.go | 26 +- internal/database/ent/db/clan_update.go | 59 +- internal/database/ent/db/crontask.go | 37 +- internal/database/ent/db/crontask/crontask.go | 7 +- internal/database/ent/db/crontask/where.go | 74 +- internal/database/ent/db/crontask_create.go | 37 +- internal/database/ent/db/crontask_query.go | 26 +- internal/database/ent/db/crontask_update.go | 147 +- .../database/ent/db/discordinteraction.go | 21 +- .../discordinteraction/discordinteraction.go | 7 +- .../ent/db/discordinteraction/where.go | 38 +- .../ent/db/discordinteraction_create.go | 25 +- .../ent/db/discordinteraction_query.go | 26 +- .../ent/db/discordinteraction_update.go | 59 +- internal/database/ent/db/expose.go | 200 ++ internal/database/ent/db/migrate/schema.go | 76 +- internal/database/ent/db/mutation.go | 1894 +++-------------- internal/database/ent/db/runtime.go | 92 +- internal/database/ent/db/user.go | 21 +- internal/database/ent/db/user/user.go | 8 +- internal/database/ent/db/user/where.go | 38 +- internal/database/ent/db/user_create.go | 25 +- internal/database/ent/db/user_query.go | 26 +- internal/database/ent/db/user_update.go | 59 +- internal/database/ent/db/userconnection.go | 21 +- .../ent/db/userconnection/userconnection.go | 7 +- .../database/ent/db/userconnection/where.go | 38 +- .../database/ent/db/userconnection_create.go | 25 +- .../database/ent/db/userconnection_query.go | 26 +- .../database/ent/db/userconnection_update.go | 59 +- internal/database/ent/db/usercontent.go | 21 +- .../ent/db/usercontent/usercontent.go | 7 +- internal/database/ent/db/usercontent/where.go | 38 +- .../database/ent/db/usercontent_create.go | 25 +- internal/database/ent/db/usercontent_query.go | 26 +- .../database/ent/db/usercontent_update.go | 59 +- internal/database/ent/db/usersubscription.go | 29 +- .../db/usersubscription/usersubscription.go | 7 +- .../database/ent/db/usersubscription/where.go | 56 +- .../ent/db/usersubscription_create.go | 31 +- .../database/ent/db/usersubscription_query.go | 26 +- .../ent/db/usersubscription_update.go | 103 +- internal/database/ent/db/vehicle.go | 21 +- internal/database/ent/db/vehicle/vehicle.go | 8 +- internal/database/ent/db/vehicle/where.go | 38 +- internal/database/ent/db/vehicle_create.go | 25 +- internal/database/ent/db/vehicle_query.go | 26 +- internal/database/ent/db/vehicle_update.go | 59 +- internal/database/ent/db/vehicleaverage.go | 21 +- .../ent/db/vehicleaverage/vehicleaverage.go | 8 +- .../database/ent/db/vehicleaverage/where.go | 38 +- .../database/ent/db/vehicleaverage_create.go | 25 +- .../database/ent/db/vehicleaverage_query.go | 26 +- .../database/ent/db/vehicleaverage_update.go | 59 +- internal/database/ent/db/vehiclesnapshot.go | 29 +- .../ent/db/vehiclesnapshot/vehiclesnapshot.go | 7 +- .../database/ent/db/vehiclesnapshot/where.go | 56 +- .../database/ent/db/vehiclesnapshot_create.go | 31 +- .../database/ent/db/vehiclesnapshot_query.go | 26 +- .../database/ent/db/vehiclesnapshot_update.go | 103 +- internal/database/ent/generate.go | 2 +- internal/database/ent/migrate/main.go | 39 - .../migrations/20240622203812_init.sql | 102 - .../20240623143618_added_indexes_on_id.sql | 30 - .../ent/migrate/migrations/20240626213554.sql | 12 - .../ent/migrate/migrations/20240626225519.sql | 22 - .../ent/migrate/migrations/20240626233300.sql | 2 - .../database/ent/migrate/migrations/atlas.sum | 6 - .../ent/migrations/20240627171532.sql | 142 ++ internal/database/ent/migrations/atlas.sum | 2 + internal/database/ent/schema/account.go | 12 +- .../database/ent/schema/account_snapshot.go | 2 +- .../ent/schema/achievements_snapshot.go | 2 +- internal/database/ent/schema/clan.go | 8 +- internal/database/ent/schema/cron_task.go | 4 +- internal/database/ent/schema/defaults.go | 10 +- internal/database/ent/schema/user.go | 8 +- .../database/ent/schema/user_subscription.go | 2 +- internal/database/ent/schema/vehicle.go | 8 +- .../database/ent/schema/vehicle_average.go | 8 +- .../database/ent/schema/vehicle_snapshot.go | 2 +- internal/database/ent/templates/expose.tmpl | 24 + internal/database/snapshots.go | 103 +- internal/database/snapshots_test.go | 238 ++- internal/database/tasks.go | 22 +- internal/database/users.go | 7 +- internal/logic/{sessions.go => snapshots.go} | 9 +- 130 files changed, 2796 insertions(+), 3620 deletions(-) delete mode 100644 internal/commbus/bus.go create mode 100644 internal/database/ent/db/expose.go delete mode 100644 internal/database/ent/migrate/main.go delete mode 100644 internal/database/ent/migrate/migrations/20240622203812_init.sql delete mode 100644 internal/database/ent/migrate/migrations/20240623143618_added_indexes_on_id.sql delete mode 100644 internal/database/ent/migrate/migrations/20240626213554.sql delete mode 100644 internal/database/ent/migrate/migrations/20240626225519.sql delete mode 100644 internal/database/ent/migrate/migrations/20240626233300.sql delete mode 100644 internal/database/ent/migrate/migrations/atlas.sum create mode 100644 internal/database/ent/migrations/20240627171532.sql create mode 100644 internal/database/ent/migrations/atlas.sum create mode 100644 internal/database/ent/templates/expose.tmpl rename internal/logic/{sessions.go => snapshots.go} (97%) diff --git a/.gitignore b/.gitignore index 58d86976..ee8990c0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ tmp **/.DS_Store **/.env +**/.env.test diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 5c67306d..83289fbb 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -1,3 +1,3 @@ FROM arigaio/atlas:latest -COPY ./internal/database/ent/migrate/migrations /migrations +COPY ./internal/database/ent/migrations /migrations diff --git a/Taskfile.yaml b/Taskfile.yaml index c407bc9e..ade58543 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -21,10 +21,13 @@ tasks: db-migrate: desc: generate migrations - cmd: atlas migrate hash --dir "file://internal/database/ent/migrate/migrations" && atlas migrate diff {{.CLI_ARGS}} --dir "file://internal/database/ent/migrate/migrations" --to "ent://internal/database/ent/schema" --dev-url "sqlite://file?mode=memory&_fk=1" + cmd: atlas migrate hash --dir "file://internal/database/ent/migrations" && atlas migrate diff {{.CLI_ARGS}} --dir "file://internal/database/ent/migrations" --to "ent://internal/database/ent/schema" --dev-url "sqlite://file?mode=memory&_fk=1" db-migrate-apply: desc: apply migrations using atlas - cmd: atlas migrate apply --allow-dirty --dir "file://internal/database/ent/migrate/migrations" --tx-mode all --url "sqlite://${DATABASE_PATH}/${DATABASE_NAME}?_fk=1" + cmd: atlas migrate apply --allow-dirty --dir "file://internal/database/ent/migrations" --tx-mode all --url "sqlite://${DATABASE_PATH}/${DATABASE_NAME}?_fk=1" + db-migrate-apply-test: + desc: apply migrations to the tests database using atlas + cmd: atlas migrate apply --allow-dirty --dir "file://internal/database/ent/migrations" --tx-mode all --url "sqlite://${DATABASE_PATH}/tests.db?_fk=1" dev: desc: Start a local dev server diff --git a/go.sum b/go.sum index b4bd05ca..ee4f6f7a 100644 --- a/go.sum +++ b/go.sum @@ -58,17 +58,23 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -77,6 +83,10 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/internal/commbus/bus.go b/internal/commbus/bus.go deleted file mode 100644 index 8d8bf574..00000000 --- a/internal/commbus/bus.go +++ /dev/null @@ -1 +0,0 @@ -package commbus diff --git a/internal/database/accounts.go b/internal/database/accounts.go index 55c9ecf8..6c73671f 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -3,7 +3,6 @@ package database import ( "context" "strings" - "time" "github.com/cufee/aftermath/internal/database/ent/db" "github.com/cufee/aftermath/internal/database/ent/db/account" @@ -16,8 +15,8 @@ func toAccount(model *db.Account) models.Account { Realm: model.Realm, Nickname: model.Nickname, Private: model.Private, - CreatedAt: time.Unix(int64(model.AccountCreatedAt), 0), - LastBattleTime: time.Unix(int64(model.LastBattleTime), 0), + CreatedAt: model.AccountCreatedAt, + LastBattleTime: model.LastBattleTime, } if model.Edges.Clan != nil { account.ClanID = model.Edges.Clan.ID @@ -90,7 +89,7 @@ func (c *client) UpsertAccounts(ctx context.Context, accounts []models.Account) SetRealm(strings.ToUpper(update.Realm)). SetNickname(update.Nickname). SetPrivate(update.Private). - SetLastBattleTime(update.LastBattleTime.Unix()). + SetLastBattleTime(update.LastBattleTime). Exec(ctx) if err != nil { errors[r.ID] = err @@ -106,8 +105,8 @@ func (c *client) UpsertAccounts(ctx context.Context, accounts []models.Account) SetRealm(strings.ToUpper(a.Realm)). SetNickname(a.Nickname). SetPrivate(a.Private). - SetAccountCreatedAt(a.CreatedAt.Unix()). - SetLastBattleTime(a.LastBattleTime.Unix()), + SetAccountCreatedAt(a.CreatedAt). + SetLastBattleTime(a.LastBattleTime), ) } diff --git a/internal/database/cleanup.go b/internal/database/cleanup.go index c6706d6f..ddc680b1 100644 --- a/internal/database/cleanup.go +++ b/internal/database/cleanup.go @@ -14,7 +14,7 @@ import ( func (c *client) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { err := c.withTx(ctx, func(tx *db.Tx) error { - _, err := tx.CronTask.Delete().Where(crontask.CreatedAtLT(expiration.Unix())).Exec(ctx) + _, err := tx.CronTask.Delete().Where(crontask.CreatedAtLT(expiration)).Exec(ctx) return err }) return err @@ -22,24 +22,24 @@ func (c *client) DeleteExpiredTasks(ctx context.Context, expiration time.Time) e func (c *client) DeleteExpiredInteractions(ctx context.Context, expiration time.Time) error { err := c.withTx(ctx, func(tx *db.Tx) error { - _, err := tx.DiscordInteraction.Delete().Where(discordinteraction.CreatedAtLT(expiration.Unix())).Exec(ctx) + _, err := tx.DiscordInteraction.Delete().Where(discordinteraction.CreatedAtLT(expiration)).Exec(ctx) return err }) return err } func (c *client) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { - _, err := c.db.AccountSnapshot.Delete().Where(accountsnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) + _, err := c.db.AccountSnapshot.Delete().Where(accountsnapshot.CreatedAtLT(expiration)).Exec(ctx) if err != nil { return err } - _, err = c.db.VehicleSnapshot.Delete().Where(vehiclesnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) + _, err = c.db.VehicleSnapshot.Delete().Where(vehiclesnapshot.CreatedAtLT(expiration)).Exec(ctx) if err != nil { return err } - _, err = c.db.AchievementsSnapshot.Delete().Where(achievementssnapshot.CreatedAtLT(expiration.Unix())).Exec(ctx) + _, err = c.db.AchievementsSnapshot.Delete().Where(achievementssnapshot.CreatedAtLT(expiration)).Exec(ctx) if err != nil { return err } diff --git a/internal/database/client.go b/internal/database/client.go index 0c5f7770..7a99931a 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -130,14 +130,36 @@ func (c *client) Disconnect() error { return c.db.Close() } -func NewSQLiteClient(filePath string) (*client, error) { +type clientOptions struct { + debug bool +} + +type ClientOption func(*clientOptions) + +func WithDebug() func(*clientOptions) { + return func(opts *clientOptions) { + opts.debug = true + } +} + +func NewSQLiteClient(filePath string, options ...ClientOption) (*client, error) { defer func() { if r := recover(); r != nil { log.Fatal().Interface("error", r).Str("stack", string(debug.Stack())).Msg("NewSQLiteClient panic") } }() - c, err := db.Open("sqlite3", fmt.Sprintf("file://%s?_fk=1", filePath)) + opts := clientOptions{} + for _, apply := range options { + apply(&opts) + } + + var dbOptions []db.Option + if opts.debug { + dbOptions = append(dbOptions, db.Debug()) + } + + c, err := db.Open("sqlite3", fmt.Sprintf("file://%s?_fk=1", filePath), dbOptions...) if err != nil { return nil, err } diff --git a/internal/database/discord_interactions.go b/internal/database/discord_interactions.go index ce81fe5f..3a0eab13 100644 --- a/internal/database/discord_interactions.go +++ b/internal/database/discord_interactions.go @@ -2,7 +2,6 @@ package database import ( "context" - "time" "github.com/cufee/aftermath/internal/database/ent/db" "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" @@ -35,7 +34,7 @@ func toDiscordInteraction(record *db.DiscordInteraction) models.DiscordInteracti } return models.DiscordInteraction{ ID: record.ID, - CreatedAt: time.Unix(record.CreatedAt, 0), + CreatedAt: record.CreatedAt, UserID: record.UserID, Command: record.Command, diff --git a/internal/database/ent/db/account.go b/internal/database/ent/db/account.go index 40cc0fa5..ff1fed1f 100644 --- a/internal/database/ent/db/account.go +++ b/internal/database/ent/db/account.go @@ -5,6 +5,7 @@ package db import ( "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -18,13 +19,13 @@ type Account struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // LastBattleTime holds the value of the "last_battle_time" field. - LastBattleTime int64 `json:"last_battle_time,omitempty"` + LastBattleTime time.Time `json:"last_battle_time,omitempty"` // AccountCreatedAt holds the value of the "account_created_at" field. - AccountCreatedAt int64 `json:"account_created_at,omitempty"` + AccountCreatedAt time.Time `json:"account_created_at,omitempty"` // Realm holds the value of the "realm" field. Realm string `json:"realm,omitempty"` // Nickname holds the value of the "nickname" field. @@ -99,10 +100,10 @@ func (*Account) scanValues(columns []string) ([]any, error) { switch columns[i] { case account.FieldPrivate: values[i] = new(sql.NullBool) - case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldLastBattleTime, account.FieldAccountCreatedAt: - values[i] = new(sql.NullInt64) case account.FieldID, account.FieldRealm, account.FieldNickname, account.FieldClanID: values[i] = new(sql.NullString) + case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldLastBattleTime, account.FieldAccountCreatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -125,28 +126,28 @@ func (a *Account) assignValues(columns []string, values []any) error { a.ID = value.String } case account.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - a.CreatedAt = value.Int64 + a.CreatedAt = value.Time } case account.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - a.UpdatedAt = value.Int64 + a.UpdatedAt = value.Time } case account.FieldLastBattleTime: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) } else if value.Valid { - a.LastBattleTime = value.Int64 + a.LastBattleTime = value.Time } case account.FieldAccountCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field account_created_at", values[i]) } else if value.Valid { - a.AccountCreatedAt = value.Int64 + a.AccountCreatedAt = value.Time } case account.FieldRealm: if value, ok := values[i].(*sql.NullString); !ok { @@ -229,16 +230,16 @@ func (a *Account) String() string { builder.WriteString("Account(") builder.WriteString(fmt.Sprintf("id=%v, ", a.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", a.CreatedAt)) + builder.WriteString(a.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", a.UpdatedAt)) + builder.WriteString(a.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("last_battle_time=") - builder.WriteString(fmt.Sprintf("%v", a.LastBattleTime)) + builder.WriteString(a.LastBattleTime.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("account_created_at=") - builder.WriteString(fmt.Sprintf("%v", a.AccountCreatedAt)) + builder.WriteString(a.AccountCreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("realm=") builder.WriteString(a.Realm) diff --git a/internal/database/ent/db/account/account.go b/internal/database/ent/db/account/account.go index 3434e510..14226186 100644 --- a/internal/database/ent/db/account/account.go +++ b/internal/database/ent/db/account/account.go @@ -3,6 +3,8 @@ package account import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" ) @@ -93,11 +95,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // RealmValidator is a validator for the "realm" field. It is called by the builders before save. RealmValidator func(string) error // NicknameValidator is a validator for the "nickname" field. It is called by the builders before save. diff --git a/internal/database/ent/db/account/where.go b/internal/database/ent/db/account/where.go index 8f872414..fe718ba5 100644 --- a/internal/database/ent/db/account/where.go +++ b/internal/database/ent/db/account/where.go @@ -3,6 +3,8 @@ package account import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -64,22 +66,22 @@ func IDContainsFold(id string) predicate.Account { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.Account { +func CreatedAt(v time.Time) predicate.Account { return predicate.Account(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.Account { +func UpdatedAt(v time.Time) predicate.Account { return predicate.Account(sql.FieldEQ(FieldUpdatedAt, v)) } // LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. -func LastBattleTime(v int64) predicate.Account { +func LastBattleTime(v time.Time) predicate.Account { return predicate.Account(sql.FieldEQ(FieldLastBattleTime, v)) } // AccountCreatedAt applies equality check predicate on the "account_created_at" field. It's identical to AccountCreatedAtEQ. -func AccountCreatedAt(v int64) predicate.Account { +func AccountCreatedAt(v time.Time) predicate.Account { return predicate.Account(sql.FieldEQ(FieldAccountCreatedAt, v)) } @@ -104,162 +106,162 @@ func ClanID(v string) predicate.Account { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.Account { +func CreatedAtEQ(v time.Time) predicate.Account { return predicate.Account(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.Account { +func CreatedAtNEQ(v time.Time) predicate.Account { return predicate.Account(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.Account { +func CreatedAtIn(vs ...time.Time) predicate.Account { return predicate.Account(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.Account { +func CreatedAtNotIn(vs ...time.Time) predicate.Account { return predicate.Account(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.Account { +func CreatedAtGT(v time.Time) predicate.Account { return predicate.Account(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.Account { +func CreatedAtGTE(v time.Time) predicate.Account { return predicate.Account(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.Account { +func CreatedAtLT(v time.Time) predicate.Account { return predicate.Account(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.Account { +func CreatedAtLTE(v time.Time) predicate.Account { return predicate.Account(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.Account { +func UpdatedAtEQ(v time.Time) predicate.Account { return predicate.Account(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.Account { +func UpdatedAtNEQ(v time.Time) predicate.Account { return predicate.Account(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.Account { +func UpdatedAtIn(vs ...time.Time) predicate.Account { return predicate.Account(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.Account { +func UpdatedAtNotIn(vs ...time.Time) predicate.Account { return predicate.Account(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.Account { +func UpdatedAtGT(v time.Time) predicate.Account { return predicate.Account(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.Account { +func UpdatedAtGTE(v time.Time) predicate.Account { return predicate.Account(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.Account { +func UpdatedAtLT(v time.Time) predicate.Account { return predicate.Account(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.Account { +func UpdatedAtLTE(v time.Time) predicate.Account { return predicate.Account(sql.FieldLTE(FieldUpdatedAt, v)) } // LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. -func LastBattleTimeEQ(v int64) predicate.Account { +func LastBattleTimeEQ(v time.Time) predicate.Account { return predicate.Account(sql.FieldEQ(FieldLastBattleTime, v)) } // LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. -func LastBattleTimeNEQ(v int64) predicate.Account { +func LastBattleTimeNEQ(v time.Time) predicate.Account { return predicate.Account(sql.FieldNEQ(FieldLastBattleTime, v)) } // LastBattleTimeIn applies the In predicate on the "last_battle_time" field. -func LastBattleTimeIn(vs ...int64) predicate.Account { +func LastBattleTimeIn(vs ...time.Time) predicate.Account { return predicate.Account(sql.FieldIn(FieldLastBattleTime, vs...)) } // LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. -func LastBattleTimeNotIn(vs ...int64) predicate.Account { +func LastBattleTimeNotIn(vs ...time.Time) predicate.Account { return predicate.Account(sql.FieldNotIn(FieldLastBattleTime, vs...)) } // LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. -func LastBattleTimeGT(v int64) predicate.Account { +func LastBattleTimeGT(v time.Time) predicate.Account { return predicate.Account(sql.FieldGT(FieldLastBattleTime, v)) } // LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. -func LastBattleTimeGTE(v int64) predicate.Account { +func LastBattleTimeGTE(v time.Time) predicate.Account { return predicate.Account(sql.FieldGTE(FieldLastBattleTime, v)) } // LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. -func LastBattleTimeLT(v int64) predicate.Account { +func LastBattleTimeLT(v time.Time) predicate.Account { return predicate.Account(sql.FieldLT(FieldLastBattleTime, v)) } // LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. -func LastBattleTimeLTE(v int64) predicate.Account { +func LastBattleTimeLTE(v time.Time) predicate.Account { return predicate.Account(sql.FieldLTE(FieldLastBattleTime, v)) } // AccountCreatedAtEQ applies the EQ predicate on the "account_created_at" field. -func AccountCreatedAtEQ(v int64) predicate.Account { +func AccountCreatedAtEQ(v time.Time) predicate.Account { return predicate.Account(sql.FieldEQ(FieldAccountCreatedAt, v)) } // AccountCreatedAtNEQ applies the NEQ predicate on the "account_created_at" field. -func AccountCreatedAtNEQ(v int64) predicate.Account { +func AccountCreatedAtNEQ(v time.Time) predicate.Account { return predicate.Account(sql.FieldNEQ(FieldAccountCreatedAt, v)) } // AccountCreatedAtIn applies the In predicate on the "account_created_at" field. -func AccountCreatedAtIn(vs ...int64) predicate.Account { +func AccountCreatedAtIn(vs ...time.Time) predicate.Account { return predicate.Account(sql.FieldIn(FieldAccountCreatedAt, vs...)) } // AccountCreatedAtNotIn applies the NotIn predicate on the "account_created_at" field. -func AccountCreatedAtNotIn(vs ...int64) predicate.Account { +func AccountCreatedAtNotIn(vs ...time.Time) predicate.Account { return predicate.Account(sql.FieldNotIn(FieldAccountCreatedAt, vs...)) } // AccountCreatedAtGT applies the GT predicate on the "account_created_at" field. -func AccountCreatedAtGT(v int64) predicate.Account { +func AccountCreatedAtGT(v time.Time) predicate.Account { return predicate.Account(sql.FieldGT(FieldAccountCreatedAt, v)) } // AccountCreatedAtGTE applies the GTE predicate on the "account_created_at" field. -func AccountCreatedAtGTE(v int64) predicate.Account { +func AccountCreatedAtGTE(v time.Time) predicate.Account { return predicate.Account(sql.FieldGTE(FieldAccountCreatedAt, v)) } // AccountCreatedAtLT applies the LT predicate on the "account_created_at" field. -func AccountCreatedAtLT(v int64) predicate.Account { +func AccountCreatedAtLT(v time.Time) predicate.Account { return predicate.Account(sql.FieldLT(FieldAccountCreatedAt, v)) } // AccountCreatedAtLTE applies the LTE predicate on the "account_created_at" field. -func AccountCreatedAtLTE(v int64) predicate.Account { +func AccountCreatedAtLTE(v time.Time) predicate.Account { return predicate.Account(sql.FieldLTE(FieldAccountCreatedAt, v)) } diff --git a/internal/database/ent/db/account_create.go b/internal/database/ent/db/account_create.go index 7afba00e..88cc48f2 100644 --- a/internal/database/ent/db/account_create.go +++ b/internal/database/ent/db/account_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -24,42 +25,42 @@ type AccountCreate struct { } // SetCreatedAt sets the "created_at" field. -func (ac *AccountCreate) SetCreatedAt(i int64) *AccountCreate { - ac.mutation.SetCreatedAt(i) +func (ac *AccountCreate) SetCreatedAt(t time.Time) *AccountCreate { + ac.mutation.SetCreatedAt(t) return ac } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (ac *AccountCreate) SetNillableCreatedAt(i *int64) *AccountCreate { - if i != nil { - ac.SetCreatedAt(*i) +func (ac *AccountCreate) SetNillableCreatedAt(t *time.Time) *AccountCreate { + if t != nil { + ac.SetCreatedAt(*t) } return ac } // SetUpdatedAt sets the "updated_at" field. -func (ac *AccountCreate) SetUpdatedAt(i int64) *AccountCreate { - ac.mutation.SetUpdatedAt(i) +func (ac *AccountCreate) SetUpdatedAt(t time.Time) *AccountCreate { + ac.mutation.SetUpdatedAt(t) return ac } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (ac *AccountCreate) SetNillableUpdatedAt(i *int64) *AccountCreate { - if i != nil { - ac.SetUpdatedAt(*i) +func (ac *AccountCreate) SetNillableUpdatedAt(t *time.Time) *AccountCreate { + if t != nil { + ac.SetUpdatedAt(*t) } return ac } // SetLastBattleTime sets the "last_battle_time" field. -func (ac *AccountCreate) SetLastBattleTime(i int64) *AccountCreate { - ac.mutation.SetLastBattleTime(i) +func (ac *AccountCreate) SetLastBattleTime(t time.Time) *AccountCreate { + ac.mutation.SetLastBattleTime(t) return ac } // SetAccountCreatedAt sets the "account_created_at" field. -func (ac *AccountCreate) SetAccountCreatedAt(i int64) *AccountCreate { - ac.mutation.SetAccountCreatedAt(i) +func (ac *AccountCreate) SetAccountCreatedAt(t time.Time) *AccountCreate { + ac.mutation.SetAccountCreatedAt(t) return ac } @@ -277,19 +278,19 @@ func (ac *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := ac.mutation.CreatedAt(); ok { - _spec.SetField(account.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(account.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := ac.mutation.UpdatedAt(); ok { - _spec.SetField(account.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(account.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := ac.mutation.LastBattleTime(); ok { - _spec.SetField(account.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(account.FieldLastBattleTime, field.TypeTime, value) _node.LastBattleTime = value } if value, ok := ac.mutation.AccountCreatedAt(); ok { - _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt64, value) + _spec.SetField(account.FieldAccountCreatedAt, field.TypeTime, value) _node.AccountCreatedAt = value } if value, ok := ac.mutation.Realm(); ok { diff --git a/internal/database/ent/db/account_query.go b/internal/database/ent/db/account_query.go index bd81a273..ed808e7b 100644 --- a/internal/database/ent/db/account_query.go +++ b/internal/database/ent/db/account_query.go @@ -30,6 +30,7 @@ type AccountQuery struct { withSnapshots *AccountSnapshotQuery withVehicleSnapshots *VehicleSnapshotQuery withAchievementSnapshots *AchievementsSnapshotQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -406,7 +407,7 @@ func (aq *AccountQuery) WithAchievementSnapshots(opts ...func(*AchievementsSnaps // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -429,7 +430,7 @@ func (aq *AccountQuery) GroupBy(field string, fields ...string) *AccountGroupBy // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.Account.Query(). @@ -494,6 +495,9 @@ func (aq *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(aq.modifiers) > 0 { + _spec.Modifiers = aq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -657,6 +661,9 @@ func (aq *AccountQuery) loadAchievementSnapshots(ctx context.Context, query *Ach func (aq *AccountQuery) sqlCount(ctx context.Context) (int, error) { _spec := aq.querySpec() + if len(aq.modifiers) > 0 { + _spec.Modifiers = aq.modifiers + } _spec.Node.Columns = aq.ctx.Fields if len(aq.ctx.Fields) > 0 { _spec.Unique = aq.ctx.Unique != nil && *aq.ctx.Unique @@ -722,6 +729,9 @@ func (aq *AccountQuery) sqlQuery(ctx context.Context) *sql.Selector { if aq.ctx.Unique != nil && *aq.ctx.Unique { selector.Distinct() } + for _, m := range aq.modifiers { + m(selector) + } for _, p := range aq.predicates { p(selector) } @@ -739,6 +749,12 @@ func (aq *AccountQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (aq *AccountQuery) Modify(modifiers ...func(s *sql.Selector)) *AccountSelect { + aq.modifiers = append(aq.modifiers, modifiers...) + return aq.Select() +} + // AccountGroupBy is the group-by builder for Account entities. type AccountGroupBy struct { selector @@ -828,3 +844,9 @@ func (as *AccountSelect) sqlScan(ctx context.Context, root *AccountQuery, v any) defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (as *AccountSelect) Modify(modifiers ...func(s *sql.Selector)) *AccountSelect { + as.modifiers = append(as.modifiers, modifiers...) + return as +} diff --git a/internal/database/ent/db/account_update.go b/internal/database/ent/db/account_update.go index 7a80ee46..af0b100e 100644 --- a/internal/database/ent/db/account_update.go +++ b/internal/database/ent/db/account_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -21,8 +22,9 @@ import ( // AccountUpdate is the builder for updating Account entities. type AccountUpdate struct { config - hooks []Hook - mutation *AccountMutation + hooks []Hook + mutation *AccountMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the AccountUpdate builder. @@ -32,60 +34,39 @@ func (au *AccountUpdate) Where(ps ...predicate.Account) *AccountUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (au *AccountUpdate) SetUpdatedAt(i int64) *AccountUpdate { - au.mutation.ResetUpdatedAt() - au.mutation.SetUpdatedAt(i) - return au -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (au *AccountUpdate) AddUpdatedAt(i int64) *AccountUpdate { - au.mutation.AddUpdatedAt(i) +func (au *AccountUpdate) SetUpdatedAt(t time.Time) *AccountUpdate { + au.mutation.SetUpdatedAt(t) return au } // SetLastBattleTime sets the "last_battle_time" field. -func (au *AccountUpdate) SetLastBattleTime(i int64) *AccountUpdate { - au.mutation.ResetLastBattleTime() - au.mutation.SetLastBattleTime(i) +func (au *AccountUpdate) SetLastBattleTime(t time.Time) *AccountUpdate { + au.mutation.SetLastBattleTime(t) return au } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (au *AccountUpdate) SetNillableLastBattleTime(i *int64) *AccountUpdate { - if i != nil { - au.SetLastBattleTime(*i) +func (au *AccountUpdate) SetNillableLastBattleTime(t *time.Time) *AccountUpdate { + if t != nil { + au.SetLastBattleTime(*t) } return au } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (au *AccountUpdate) AddLastBattleTime(i int64) *AccountUpdate { - au.mutation.AddLastBattleTime(i) - return au -} - // SetAccountCreatedAt sets the "account_created_at" field. -func (au *AccountUpdate) SetAccountCreatedAt(i int64) *AccountUpdate { - au.mutation.ResetAccountCreatedAt() - au.mutation.SetAccountCreatedAt(i) +func (au *AccountUpdate) SetAccountCreatedAt(t time.Time) *AccountUpdate { + au.mutation.SetAccountCreatedAt(t) return au } // SetNillableAccountCreatedAt sets the "account_created_at" field if the given value is not nil. -func (au *AccountUpdate) SetNillableAccountCreatedAt(i *int64) *AccountUpdate { - if i != nil { - au.SetAccountCreatedAt(*i) +func (au *AccountUpdate) SetNillableAccountCreatedAt(t *time.Time) *AccountUpdate { + if t != nil { + au.SetAccountCreatedAt(*t) } return au } -// AddAccountCreatedAt adds i to the "account_created_at" field. -func (au *AccountUpdate) AddAccountCreatedAt(i int64) *AccountUpdate { - au.mutation.AddAccountCreatedAt(i) - return au -} - // SetRealm sets the "realm" field. func (au *AccountUpdate) SetRealm(s string) *AccountUpdate { au.mutation.SetRealm(s) @@ -323,6 +304,12 @@ func (au *AccountUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (au *AccountUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AccountUpdate { + au.modifiers = append(au.modifiers, modifiers...) + return au +} + func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := au.check(); err != nil { return n, err @@ -336,22 +323,13 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := au.mutation.UpdatedAt(); ok { - _spec.SetField(account.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := au.mutation.AddedUpdatedAt(); ok { - _spec.AddField(account.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(account.FieldUpdatedAt, field.TypeTime, value) } if value, ok := au.mutation.LastBattleTime(); ok { - _spec.SetField(account.FieldLastBattleTime, field.TypeInt64, value) - } - if value, ok := au.mutation.AddedLastBattleTime(); ok { - _spec.AddField(account.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(account.FieldLastBattleTime, field.TypeTime, value) } if value, ok := au.mutation.AccountCreatedAt(); ok { - _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt64, value) - } - if value, ok := au.mutation.AddedAccountCreatedAt(); ok { - _spec.AddField(account.FieldAccountCreatedAt, field.TypeInt64, value) + _spec.SetField(account.FieldAccountCreatedAt, field.TypeTime, value) } if value, ok := au.mutation.Realm(); ok { _spec.SetField(account.FieldRealm, field.TypeString, value) @@ -526,6 +504,7 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + _spec.AddModifiers(au.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, au.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{account.Label} @@ -541,66 +520,46 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { // AccountUpdateOne is the builder for updating a single Account entity. type AccountUpdateOne struct { config - fields []string - hooks []Hook - mutation *AccountMutation + fields []string + hooks []Hook + mutation *AccountMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (auo *AccountUpdateOne) SetUpdatedAt(i int64) *AccountUpdateOne { - auo.mutation.ResetUpdatedAt() - auo.mutation.SetUpdatedAt(i) - return auo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (auo *AccountUpdateOne) AddUpdatedAt(i int64) *AccountUpdateOne { - auo.mutation.AddUpdatedAt(i) +func (auo *AccountUpdateOne) SetUpdatedAt(t time.Time) *AccountUpdateOne { + auo.mutation.SetUpdatedAt(t) return auo } // SetLastBattleTime sets the "last_battle_time" field. -func (auo *AccountUpdateOne) SetLastBattleTime(i int64) *AccountUpdateOne { - auo.mutation.ResetLastBattleTime() - auo.mutation.SetLastBattleTime(i) +func (auo *AccountUpdateOne) SetLastBattleTime(t time.Time) *AccountUpdateOne { + auo.mutation.SetLastBattleTime(t) return auo } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (auo *AccountUpdateOne) SetNillableLastBattleTime(i *int64) *AccountUpdateOne { - if i != nil { - auo.SetLastBattleTime(*i) +func (auo *AccountUpdateOne) SetNillableLastBattleTime(t *time.Time) *AccountUpdateOne { + if t != nil { + auo.SetLastBattleTime(*t) } return auo } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (auo *AccountUpdateOne) AddLastBattleTime(i int64) *AccountUpdateOne { - auo.mutation.AddLastBattleTime(i) - return auo -} - // SetAccountCreatedAt sets the "account_created_at" field. -func (auo *AccountUpdateOne) SetAccountCreatedAt(i int64) *AccountUpdateOne { - auo.mutation.ResetAccountCreatedAt() - auo.mutation.SetAccountCreatedAt(i) +func (auo *AccountUpdateOne) SetAccountCreatedAt(t time.Time) *AccountUpdateOne { + auo.mutation.SetAccountCreatedAt(t) return auo } // SetNillableAccountCreatedAt sets the "account_created_at" field if the given value is not nil. -func (auo *AccountUpdateOne) SetNillableAccountCreatedAt(i *int64) *AccountUpdateOne { - if i != nil { - auo.SetAccountCreatedAt(*i) +func (auo *AccountUpdateOne) SetNillableAccountCreatedAt(t *time.Time) *AccountUpdateOne { + if t != nil { + auo.SetAccountCreatedAt(*t) } return auo } -// AddAccountCreatedAt adds i to the "account_created_at" field. -func (auo *AccountUpdateOne) AddAccountCreatedAt(i int64) *AccountUpdateOne { - auo.mutation.AddAccountCreatedAt(i) - return auo -} - // SetRealm sets the "realm" field. func (auo *AccountUpdateOne) SetRealm(s string) *AccountUpdateOne { auo.mutation.SetRealm(s) @@ -851,6 +810,12 @@ func (auo *AccountUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (auo *AccountUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AccountUpdateOne { + auo.modifiers = append(auo.modifiers, modifiers...) + return auo +} + func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err error) { if err := auo.check(); err != nil { return _node, err @@ -881,22 +846,13 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } } if value, ok := auo.mutation.UpdatedAt(); ok { - _spec.SetField(account.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := auo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(account.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(account.FieldUpdatedAt, field.TypeTime, value) } if value, ok := auo.mutation.LastBattleTime(); ok { - _spec.SetField(account.FieldLastBattleTime, field.TypeInt64, value) - } - if value, ok := auo.mutation.AddedLastBattleTime(); ok { - _spec.AddField(account.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(account.FieldLastBattleTime, field.TypeTime, value) } if value, ok := auo.mutation.AccountCreatedAt(); ok { - _spec.SetField(account.FieldAccountCreatedAt, field.TypeInt64, value) - } - if value, ok := auo.mutation.AddedAccountCreatedAt(); ok { - _spec.AddField(account.FieldAccountCreatedAt, field.TypeInt64, value) + _spec.SetField(account.FieldAccountCreatedAt, field.TypeTime, value) } if value, ok := auo.mutation.Realm(); ok { _spec.SetField(account.FieldRealm, field.TypeString, value) @@ -1071,6 +1027,7 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + _spec.AddModifiers(auo.modifiers...) _node = &Account{config: auo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/accountsnapshot.go b/internal/database/ent/db/accountsnapshot.go index 18d8c5f7..ba9c412e 100644 --- a/internal/database/ent/db/accountsnapshot.go +++ b/internal/database/ent/db/accountsnapshot.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -21,13 +22,13 @@ type AccountSnapshot struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.SnapshotType `json:"type,omitempty"` // LastBattleTime holds the value of the "last_battle_time" field. - LastBattleTime int64 `json:"last_battle_time,omitempty"` + LastBattleTime time.Time `json:"last_battle_time,omitempty"` // AccountID holds the value of the "account_id" field. AccountID string `json:"account_id,omitempty"` // ReferenceID holds the value of the "reference_id" field. @@ -73,10 +74,12 @@ func (*AccountSnapshot) scanValues(columns []string) ([]any, error) { switch columns[i] { case accountsnapshot.FieldRatingFrame, accountsnapshot.FieldRegularFrame: values[i] = new([]byte) - case accountsnapshot.FieldCreatedAt, accountsnapshot.FieldUpdatedAt, accountsnapshot.FieldLastBattleTime, accountsnapshot.FieldRatingBattles, accountsnapshot.FieldRegularBattles: + case accountsnapshot.FieldRatingBattles, accountsnapshot.FieldRegularBattles: values[i] = new(sql.NullInt64) case accountsnapshot.FieldID, accountsnapshot.FieldType, accountsnapshot.FieldAccountID, accountsnapshot.FieldReferenceID: values[i] = new(sql.NullString) + case accountsnapshot.FieldCreatedAt, accountsnapshot.FieldUpdatedAt, accountsnapshot.FieldLastBattleTime: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -99,16 +102,16 @@ func (as *AccountSnapshot) assignValues(columns []string, values []any) error { as.ID = value.String } case accountsnapshot.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - as.CreatedAt = value.Int64 + as.CreatedAt = value.Time } case accountsnapshot.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - as.UpdatedAt = value.Int64 + as.UpdatedAt = value.Time } case accountsnapshot.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -117,10 +120,10 @@ func (as *AccountSnapshot) assignValues(columns []string, values []any) error { as.Type = models.SnapshotType(value.String) } case accountsnapshot.FieldLastBattleTime: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) } else if value.Valid { - as.LastBattleTime = value.Int64 + as.LastBattleTime = value.Time } case accountsnapshot.FieldAccountID: if value, ok := values[i].(*sql.NullString); !ok { @@ -204,16 +207,16 @@ func (as *AccountSnapshot) String() string { builder.WriteString("AccountSnapshot(") builder.WriteString(fmt.Sprintf("id=%v, ", as.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", as.CreatedAt)) + builder.WriteString(as.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", as.UpdatedAt)) + builder.WriteString(as.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("type=") builder.WriteString(fmt.Sprintf("%v", as.Type)) builder.WriteString(", ") builder.WriteString("last_battle_time=") - builder.WriteString(fmt.Sprintf("%v", as.LastBattleTime)) + builder.WriteString(as.LastBattleTime.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("account_id=") builder.WriteString(as.AccountID) diff --git a/internal/database/ent/db/accountsnapshot/accountsnapshot.go b/internal/database/ent/db/accountsnapshot/accountsnapshot.go index 1a60c4b6..76caf2d3 100644 --- a/internal/database/ent/db/accountsnapshot/accountsnapshot.go +++ b/internal/database/ent/db/accountsnapshot/accountsnapshot.go @@ -4,6 +4,7 @@ package accountsnapshot import ( "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -75,11 +76,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. AccountIDValidator func(string) error // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. diff --git a/internal/database/ent/db/accountsnapshot/where.go b/internal/database/ent/db/accountsnapshot/where.go index 6f49d0e8..1bd66d6c 100644 --- a/internal/database/ent/db/accountsnapshot/where.go +++ b/internal/database/ent/db/accountsnapshot/where.go @@ -3,6 +3,8 @@ package accountsnapshot import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -65,17 +67,17 @@ func IDContainsFold(id string) predicate.AccountSnapshot { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.AccountSnapshot { +func CreatedAt(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.AccountSnapshot { +func UpdatedAt(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } // LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. -func LastBattleTime(v int64) predicate.AccountSnapshot { +func LastBattleTime(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } @@ -100,82 +102,82 @@ func RegularBattles(v int) predicate.AccountSnapshot { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.AccountSnapshot { +func CreatedAtEQ(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.AccountSnapshot { +func CreatedAtNEQ(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.AccountSnapshot { +func CreatedAtIn(vs ...time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.AccountSnapshot { +func CreatedAtNotIn(vs ...time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.AccountSnapshot { +func CreatedAtGT(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.AccountSnapshot { +func CreatedAtGTE(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.AccountSnapshot { +func CreatedAtLT(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.AccountSnapshot { +func CreatedAtLTE(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.AccountSnapshot { +func UpdatedAtEQ(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.AccountSnapshot { +func UpdatedAtNEQ(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.AccountSnapshot { +func UpdatedAtIn(vs ...time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.AccountSnapshot { +func UpdatedAtNotIn(vs ...time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.AccountSnapshot { +func UpdatedAtGT(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.AccountSnapshot { +func UpdatedAtGTE(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.AccountSnapshot { +func UpdatedAtLT(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.AccountSnapshot { +func UpdatedAtLTE(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLTE(FieldUpdatedAt, v)) } @@ -210,42 +212,42 @@ func TypeNotIn(vs ...models.SnapshotType) predicate.AccountSnapshot { } // LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. -func LastBattleTimeEQ(v int64) predicate.AccountSnapshot { +func LastBattleTimeEQ(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. -func LastBattleTimeNEQ(v int64) predicate.AccountSnapshot { +func LastBattleTimeNEQ(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNEQ(FieldLastBattleTime, v)) } // LastBattleTimeIn applies the In predicate on the "last_battle_time" field. -func LastBattleTimeIn(vs ...int64) predicate.AccountSnapshot { +func LastBattleTimeIn(vs ...time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldIn(FieldLastBattleTime, vs...)) } // LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. -func LastBattleTimeNotIn(vs ...int64) predicate.AccountSnapshot { +func LastBattleTimeNotIn(vs ...time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldNotIn(FieldLastBattleTime, vs...)) } // LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. -func LastBattleTimeGT(v int64) predicate.AccountSnapshot { +func LastBattleTimeGT(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGT(FieldLastBattleTime, v)) } // LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. -func LastBattleTimeGTE(v int64) predicate.AccountSnapshot { +func LastBattleTimeGTE(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldGTE(FieldLastBattleTime, v)) } // LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. -func LastBattleTimeLT(v int64) predicate.AccountSnapshot { +func LastBattleTimeLT(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLT(FieldLastBattleTime, v)) } // LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. -func LastBattleTimeLTE(v int64) predicate.AccountSnapshot { +func LastBattleTimeLTE(v time.Time) predicate.AccountSnapshot { return predicate.AccountSnapshot(sql.FieldLTE(FieldLastBattleTime, v)) } diff --git a/internal/database/ent/db/accountsnapshot_create.go b/internal/database/ent/db/accountsnapshot_create.go index 9cc5742c..e29034fe 100644 --- a/internal/database/ent/db/accountsnapshot_create.go +++ b/internal/database/ent/db/accountsnapshot_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -23,29 +24,29 @@ type AccountSnapshotCreate struct { } // SetCreatedAt sets the "created_at" field. -func (asc *AccountSnapshotCreate) SetCreatedAt(i int64) *AccountSnapshotCreate { - asc.mutation.SetCreatedAt(i) +func (asc *AccountSnapshotCreate) SetCreatedAt(t time.Time) *AccountSnapshotCreate { + asc.mutation.SetCreatedAt(t) return asc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (asc *AccountSnapshotCreate) SetNillableCreatedAt(i *int64) *AccountSnapshotCreate { - if i != nil { - asc.SetCreatedAt(*i) +func (asc *AccountSnapshotCreate) SetNillableCreatedAt(t *time.Time) *AccountSnapshotCreate { + if t != nil { + asc.SetCreatedAt(*t) } return asc } // SetUpdatedAt sets the "updated_at" field. -func (asc *AccountSnapshotCreate) SetUpdatedAt(i int64) *AccountSnapshotCreate { - asc.mutation.SetUpdatedAt(i) +func (asc *AccountSnapshotCreate) SetUpdatedAt(t time.Time) *AccountSnapshotCreate { + asc.mutation.SetUpdatedAt(t) return asc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (asc *AccountSnapshotCreate) SetNillableUpdatedAt(i *int64) *AccountSnapshotCreate { - if i != nil { - asc.SetUpdatedAt(*i) +func (asc *AccountSnapshotCreate) SetNillableUpdatedAt(t *time.Time) *AccountSnapshotCreate { + if t != nil { + asc.SetUpdatedAt(*t) } return asc } @@ -57,8 +58,8 @@ func (asc *AccountSnapshotCreate) SetType(mt models.SnapshotType) *AccountSnapsh } // SetLastBattleTime sets the "last_battle_time" field. -func (asc *AccountSnapshotCreate) SetLastBattleTime(i int64) *AccountSnapshotCreate { - asc.mutation.SetLastBattleTime(i) +func (asc *AccountSnapshotCreate) SetLastBattleTime(t time.Time) *AccountSnapshotCreate { + asc.mutation.SetLastBattleTime(t) return asc } @@ -252,11 +253,11 @@ func (asc *AccountSnapshotCreate) createSpec() (*AccountSnapshot, *sqlgraph.Crea _spec.ID.Value = id } if value, ok := asc.mutation.CreatedAt(); ok { - _spec.SetField(accountsnapshot.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(accountsnapshot.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := asc.mutation.UpdatedAt(); ok { - _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := asc.mutation.GetType(); ok { @@ -264,7 +265,7 @@ func (asc *AccountSnapshotCreate) createSpec() (*AccountSnapshot, *sqlgraph.Crea _node.Type = value } if value, ok := asc.mutation.LastBattleTime(); ok { - _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeTime, value) _node.LastBattleTime = value } if value, ok := asc.mutation.ReferenceID(); ok { diff --git a/internal/database/ent/db/accountsnapshot_query.go b/internal/database/ent/db/accountsnapshot_query.go index 33acaaf3..8ad15e4a 100644 --- a/internal/database/ent/db/accountsnapshot_query.go +++ b/internal/database/ent/db/accountsnapshot_query.go @@ -23,6 +23,7 @@ type AccountSnapshotQuery struct { inters []Interceptor predicates []predicate.AccountSnapshot withAccount *AccountQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -297,7 +298,7 @@ func (asq *AccountSnapshotQuery) WithAccount(opts ...func(*AccountQuery)) *Accou // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +321,7 @@ func (asq *AccountSnapshotQuery) GroupBy(field string, fields ...string) *Accoun // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.AccountSnapshot.Query(). @@ -382,6 +383,9 @@ func (asq *AccountSnapshotQuery) sqlAll(ctx context.Context, hooks ...queryHook) node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(asq.modifiers) > 0 { + _spec.Modifiers = asq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -432,6 +436,9 @@ func (asq *AccountSnapshotQuery) loadAccount(ctx context.Context, query *Account func (asq *AccountSnapshotQuery) sqlCount(ctx context.Context) (int, error) { _spec := asq.querySpec() + if len(asq.modifiers) > 0 { + _spec.Modifiers = asq.modifiers + } _spec.Node.Columns = asq.ctx.Fields if len(asq.ctx.Fields) > 0 { _spec.Unique = asq.ctx.Unique != nil && *asq.ctx.Unique @@ -497,6 +504,9 @@ func (asq *AccountSnapshotQuery) sqlQuery(ctx context.Context) *sql.Selector { if asq.ctx.Unique != nil && *asq.ctx.Unique { selector.Distinct() } + for _, m := range asq.modifiers { + m(selector) + } for _, p := range asq.predicates { p(selector) } @@ -514,6 +524,12 @@ func (asq *AccountSnapshotQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (asq *AccountSnapshotQuery) Modify(modifiers ...func(s *sql.Selector)) *AccountSnapshotSelect { + asq.modifiers = append(asq.modifiers, modifiers...) + return asq.Select() +} + // AccountSnapshotGroupBy is the group-by builder for AccountSnapshot entities. type AccountSnapshotGroupBy struct { selector @@ -603,3 +619,9 @@ func (ass *AccountSnapshotSelect) sqlScan(ctx context.Context, root *AccountSnap defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (ass *AccountSnapshotSelect) Modify(modifiers ...func(s *sql.Selector)) *AccountSnapshotSelect { + ass.modifiers = append(ass.modifiers, modifiers...) + return ass +} diff --git a/internal/database/ent/db/accountsnapshot_update.go b/internal/database/ent/db/accountsnapshot_update.go index 407e16bf..0643c23a 100644 --- a/internal/database/ent/db/accountsnapshot_update.go +++ b/internal/database/ent/db/accountsnapshot_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -19,8 +20,9 @@ import ( // AccountSnapshotUpdate is the builder for updating AccountSnapshot entities. type AccountSnapshotUpdate struct { config - hooks []Hook - mutation *AccountSnapshotMutation + hooks []Hook + mutation *AccountSnapshotMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the AccountSnapshotUpdate builder. @@ -30,15 +32,8 @@ func (asu *AccountSnapshotUpdate) Where(ps ...predicate.AccountSnapshot) *Accoun } // SetUpdatedAt sets the "updated_at" field. -func (asu *AccountSnapshotUpdate) SetUpdatedAt(i int64) *AccountSnapshotUpdate { - asu.mutation.ResetUpdatedAt() - asu.mutation.SetUpdatedAt(i) - return asu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (asu *AccountSnapshotUpdate) AddUpdatedAt(i int64) *AccountSnapshotUpdate { - asu.mutation.AddUpdatedAt(i) +func (asu *AccountSnapshotUpdate) SetUpdatedAt(t time.Time) *AccountSnapshotUpdate { + asu.mutation.SetUpdatedAt(t) return asu } @@ -57,26 +52,19 @@ func (asu *AccountSnapshotUpdate) SetNillableType(mt *models.SnapshotType) *Acco } // SetLastBattleTime sets the "last_battle_time" field. -func (asu *AccountSnapshotUpdate) SetLastBattleTime(i int64) *AccountSnapshotUpdate { - asu.mutation.ResetLastBattleTime() - asu.mutation.SetLastBattleTime(i) +func (asu *AccountSnapshotUpdate) SetLastBattleTime(t time.Time) *AccountSnapshotUpdate { + asu.mutation.SetLastBattleTime(t) return asu } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (asu *AccountSnapshotUpdate) SetNillableLastBattleTime(i *int64) *AccountSnapshotUpdate { - if i != nil { - asu.SetLastBattleTime(*i) +func (asu *AccountSnapshotUpdate) SetNillableLastBattleTime(t *time.Time) *AccountSnapshotUpdate { + if t != nil { + asu.SetLastBattleTime(*t) } return asu } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (asu *AccountSnapshotUpdate) AddLastBattleTime(i int64) *AccountSnapshotUpdate { - asu.mutation.AddLastBattleTime(i) - return asu -} - // SetReferenceID sets the "reference_id" field. func (asu *AccountSnapshotUpdate) SetReferenceID(s string) *AccountSnapshotUpdate { asu.mutation.SetReferenceID(s) @@ -220,6 +208,12 @@ func (asu *AccountSnapshotUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (asu *AccountSnapshotUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AccountSnapshotUpdate { + asu.modifiers = append(asu.modifiers, modifiers...) + return asu +} + func (asu *AccountSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := asu.check(); err != nil { return n, err @@ -233,19 +227,13 @@ func (asu *AccountSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error } } if value, ok := asu.mutation.UpdatedAt(); ok { - _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := asu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeTime, value) } if value, ok := asu.mutation.GetType(); ok { _spec.SetField(accountsnapshot.FieldType, field.TypeEnum, value) } if value, ok := asu.mutation.LastBattleTime(); ok { - _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) - } - if value, ok := asu.mutation.AddedLastBattleTime(); ok { - _spec.AddField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeTime, value) } if value, ok := asu.mutation.ReferenceID(); ok { _spec.SetField(accountsnapshot.FieldReferenceID, field.TypeString, value) @@ -268,6 +256,7 @@ func (asu *AccountSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error if value, ok := asu.mutation.RegularFrame(); ok { _spec.SetField(accountsnapshot.FieldRegularFrame, field.TypeJSON, value) } + _spec.AddModifiers(asu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, asu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{accountsnapshot.Label} @@ -283,21 +272,15 @@ func (asu *AccountSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error // AccountSnapshotUpdateOne is the builder for updating a single AccountSnapshot entity. type AccountSnapshotUpdateOne struct { config - fields []string - hooks []Hook - mutation *AccountSnapshotMutation + fields []string + hooks []Hook + mutation *AccountSnapshotMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (asuo *AccountSnapshotUpdateOne) SetUpdatedAt(i int64) *AccountSnapshotUpdateOne { - asuo.mutation.ResetUpdatedAt() - asuo.mutation.SetUpdatedAt(i) - return asuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (asuo *AccountSnapshotUpdateOne) AddUpdatedAt(i int64) *AccountSnapshotUpdateOne { - asuo.mutation.AddUpdatedAt(i) +func (asuo *AccountSnapshotUpdateOne) SetUpdatedAt(t time.Time) *AccountSnapshotUpdateOne { + asuo.mutation.SetUpdatedAt(t) return asuo } @@ -316,26 +299,19 @@ func (asuo *AccountSnapshotUpdateOne) SetNillableType(mt *models.SnapshotType) * } // SetLastBattleTime sets the "last_battle_time" field. -func (asuo *AccountSnapshotUpdateOne) SetLastBattleTime(i int64) *AccountSnapshotUpdateOne { - asuo.mutation.ResetLastBattleTime() - asuo.mutation.SetLastBattleTime(i) +func (asuo *AccountSnapshotUpdateOne) SetLastBattleTime(t time.Time) *AccountSnapshotUpdateOne { + asuo.mutation.SetLastBattleTime(t) return asuo } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (asuo *AccountSnapshotUpdateOne) SetNillableLastBattleTime(i *int64) *AccountSnapshotUpdateOne { - if i != nil { - asuo.SetLastBattleTime(*i) +func (asuo *AccountSnapshotUpdateOne) SetNillableLastBattleTime(t *time.Time) *AccountSnapshotUpdateOne { + if t != nil { + asuo.SetLastBattleTime(*t) } return asuo } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (asuo *AccountSnapshotUpdateOne) AddLastBattleTime(i int64) *AccountSnapshotUpdateOne { - asuo.mutation.AddLastBattleTime(i) - return asuo -} - // SetReferenceID sets the "reference_id" field. func (asuo *AccountSnapshotUpdateOne) SetReferenceID(s string) *AccountSnapshotUpdateOne { asuo.mutation.SetReferenceID(s) @@ -492,6 +468,12 @@ func (asuo *AccountSnapshotUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (asuo *AccountSnapshotUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AccountSnapshotUpdateOne { + asuo.modifiers = append(asuo.modifiers, modifiers...) + return asuo +} + func (asuo *AccountSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *AccountSnapshot, err error) { if err := asuo.check(); err != nil { return _node, err @@ -522,19 +504,13 @@ func (asuo *AccountSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *Accou } } if value, ok := asuo.mutation.UpdatedAt(); ok { - _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := asuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(accountsnapshot.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(accountsnapshot.FieldUpdatedAt, field.TypeTime, value) } if value, ok := asuo.mutation.GetType(); ok { _spec.SetField(accountsnapshot.FieldType, field.TypeEnum, value) } if value, ok := asuo.mutation.LastBattleTime(); ok { - _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) - } - if value, ok := asuo.mutation.AddedLastBattleTime(); ok { - _spec.AddField(accountsnapshot.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(accountsnapshot.FieldLastBattleTime, field.TypeTime, value) } if value, ok := asuo.mutation.ReferenceID(); ok { _spec.SetField(accountsnapshot.FieldReferenceID, field.TypeString, value) @@ -557,6 +533,7 @@ func (asuo *AccountSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *Accou if value, ok := asuo.mutation.RegularFrame(); ok { _spec.SetField(accountsnapshot.FieldRegularFrame, field.TypeJSON, value) } + _spec.AddModifiers(asuo.modifiers...) _node = &AccountSnapshot{config: asuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/achievementssnapshot.go b/internal/database/ent/db/achievementssnapshot.go index 6a16a84d..00969546 100644 --- a/internal/database/ent/db/achievementssnapshot.go +++ b/internal/database/ent/db/achievementssnapshot.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -21,9 +22,9 @@ type AchievementsSnapshot struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.SnapshotType `json:"type,omitempty"` // AccountID holds the value of the "account_id" field. @@ -33,7 +34,7 @@ type AchievementsSnapshot struct { // Battles holds the value of the "battles" field. Battles int `json:"battles,omitempty"` // LastBattleTime holds the value of the "last_battle_time" field. - LastBattleTime int64 `json:"last_battle_time,omitempty"` + LastBattleTime time.Time `json:"last_battle_time,omitempty"` // Data holds the value of the "data" field. Data types.AchievementsFrame `json:"data,omitempty"` // Edges holds the relations/edges for other nodes in the graph. @@ -69,10 +70,12 @@ func (*AchievementsSnapshot) scanValues(columns []string) ([]any, error) { switch columns[i] { case achievementssnapshot.FieldData: values[i] = new([]byte) - case achievementssnapshot.FieldCreatedAt, achievementssnapshot.FieldUpdatedAt, achievementssnapshot.FieldBattles, achievementssnapshot.FieldLastBattleTime: + case achievementssnapshot.FieldBattles: values[i] = new(sql.NullInt64) case achievementssnapshot.FieldID, achievementssnapshot.FieldType, achievementssnapshot.FieldAccountID, achievementssnapshot.FieldReferenceID: values[i] = new(sql.NullString) + case achievementssnapshot.FieldCreatedAt, achievementssnapshot.FieldUpdatedAt, achievementssnapshot.FieldLastBattleTime: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -95,16 +98,16 @@ func (as *AchievementsSnapshot) assignValues(columns []string, values []any) err as.ID = value.String } case achievementssnapshot.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - as.CreatedAt = value.Int64 + as.CreatedAt = value.Time } case achievementssnapshot.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - as.UpdatedAt = value.Int64 + as.UpdatedAt = value.Time } case achievementssnapshot.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -131,10 +134,10 @@ func (as *AchievementsSnapshot) assignValues(columns []string, values []any) err as.Battles = int(value.Int64) } case achievementssnapshot.FieldLastBattleTime: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) } else if value.Valid { - as.LastBattleTime = value.Int64 + as.LastBattleTime = value.Time } case achievementssnapshot.FieldData: if value, ok := values[i].(*[]byte); !ok { @@ -186,10 +189,10 @@ func (as *AchievementsSnapshot) String() string { builder.WriteString("AchievementsSnapshot(") builder.WriteString(fmt.Sprintf("id=%v, ", as.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", as.CreatedAt)) + builder.WriteString(as.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", as.UpdatedAt)) + builder.WriteString(as.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("type=") builder.WriteString(fmt.Sprintf("%v", as.Type)) @@ -204,7 +207,7 @@ func (as *AchievementsSnapshot) String() string { builder.WriteString(fmt.Sprintf("%v", as.Battles)) builder.WriteString(", ") builder.WriteString("last_battle_time=") - builder.WriteString(fmt.Sprintf("%v", as.LastBattleTime)) + builder.WriteString(as.LastBattleTime.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("data=") builder.WriteString(fmt.Sprintf("%v", as.Data)) diff --git a/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go b/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go index 959b9e38..cad20938 100644 --- a/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go +++ b/internal/database/ent/db/achievementssnapshot/achievementssnapshot.go @@ -4,6 +4,7 @@ package achievementssnapshot import ( "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -69,11 +70,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. AccountIDValidator func(string) error // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. diff --git a/internal/database/ent/db/achievementssnapshot/where.go b/internal/database/ent/db/achievementssnapshot/where.go index 5d0bea81..3d3e0932 100644 --- a/internal/database/ent/db/achievementssnapshot/where.go +++ b/internal/database/ent/db/achievementssnapshot/where.go @@ -3,6 +3,8 @@ package achievementssnapshot import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -65,12 +67,12 @@ func IDContainsFold(id string) predicate.AchievementsSnapshot { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.AchievementsSnapshot { +func CreatedAt(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.AchievementsSnapshot { +func UpdatedAt(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -90,87 +92,87 @@ func Battles(v int) predicate.AchievementsSnapshot { } // LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. -func LastBattleTime(v int64) predicate.AchievementsSnapshot { +func LastBattleTime(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.AchievementsSnapshot { +func CreatedAtEQ(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.AchievementsSnapshot { +func CreatedAtNEQ(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.AchievementsSnapshot { +func CreatedAtIn(vs ...time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.AchievementsSnapshot { +func CreatedAtNotIn(vs ...time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.AchievementsSnapshot { +func CreatedAtGT(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.AchievementsSnapshot { +func CreatedAtGTE(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.AchievementsSnapshot { +func CreatedAtLT(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.AchievementsSnapshot { +func CreatedAtLTE(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.AchievementsSnapshot { +func UpdatedAtEQ(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.AchievementsSnapshot { +func UpdatedAtNEQ(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.AchievementsSnapshot { +func UpdatedAtIn(vs ...time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.AchievementsSnapshot { +func UpdatedAtNotIn(vs ...time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.AchievementsSnapshot { +func UpdatedAtGT(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.AchievementsSnapshot { +func UpdatedAtGTE(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.AchievementsSnapshot { +func UpdatedAtLT(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.AchievementsSnapshot { +func UpdatedAtLTE(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLTE(FieldUpdatedAt, v)) } @@ -375,42 +377,42 @@ func BattlesLTE(v int) predicate.AchievementsSnapshot { } // LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. -func LastBattleTimeEQ(v int64) predicate.AchievementsSnapshot { +func LastBattleTimeEQ(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. -func LastBattleTimeNEQ(v int64) predicate.AchievementsSnapshot { +func LastBattleTimeNEQ(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNEQ(FieldLastBattleTime, v)) } // LastBattleTimeIn applies the In predicate on the "last_battle_time" field. -func LastBattleTimeIn(vs ...int64) predicate.AchievementsSnapshot { +func LastBattleTimeIn(vs ...time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldIn(FieldLastBattleTime, vs...)) } // LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. -func LastBattleTimeNotIn(vs ...int64) predicate.AchievementsSnapshot { +func LastBattleTimeNotIn(vs ...time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldNotIn(FieldLastBattleTime, vs...)) } // LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. -func LastBattleTimeGT(v int64) predicate.AchievementsSnapshot { +func LastBattleTimeGT(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGT(FieldLastBattleTime, v)) } // LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. -func LastBattleTimeGTE(v int64) predicate.AchievementsSnapshot { +func LastBattleTimeGTE(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldGTE(FieldLastBattleTime, v)) } // LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. -func LastBattleTimeLT(v int64) predicate.AchievementsSnapshot { +func LastBattleTimeLT(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLT(FieldLastBattleTime, v)) } // LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. -func LastBattleTimeLTE(v int64) predicate.AchievementsSnapshot { +func LastBattleTimeLTE(v time.Time) predicate.AchievementsSnapshot { return predicate.AchievementsSnapshot(sql.FieldLTE(FieldLastBattleTime, v)) } diff --git a/internal/database/ent/db/achievementssnapshot_create.go b/internal/database/ent/db/achievementssnapshot_create.go index 7e18e271..86e3137d 100644 --- a/internal/database/ent/db/achievementssnapshot_create.go +++ b/internal/database/ent/db/achievementssnapshot_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -23,29 +24,29 @@ type AchievementsSnapshotCreate struct { } // SetCreatedAt sets the "created_at" field. -func (asc *AchievementsSnapshotCreate) SetCreatedAt(i int64) *AchievementsSnapshotCreate { - asc.mutation.SetCreatedAt(i) +func (asc *AchievementsSnapshotCreate) SetCreatedAt(t time.Time) *AchievementsSnapshotCreate { + asc.mutation.SetCreatedAt(t) return asc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (asc *AchievementsSnapshotCreate) SetNillableCreatedAt(i *int64) *AchievementsSnapshotCreate { - if i != nil { - asc.SetCreatedAt(*i) +func (asc *AchievementsSnapshotCreate) SetNillableCreatedAt(t *time.Time) *AchievementsSnapshotCreate { + if t != nil { + asc.SetCreatedAt(*t) } return asc } // SetUpdatedAt sets the "updated_at" field. -func (asc *AchievementsSnapshotCreate) SetUpdatedAt(i int64) *AchievementsSnapshotCreate { - asc.mutation.SetUpdatedAt(i) +func (asc *AchievementsSnapshotCreate) SetUpdatedAt(t time.Time) *AchievementsSnapshotCreate { + asc.mutation.SetUpdatedAt(t) return asc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (asc *AchievementsSnapshotCreate) SetNillableUpdatedAt(i *int64) *AchievementsSnapshotCreate { - if i != nil { - asc.SetUpdatedAt(*i) +func (asc *AchievementsSnapshotCreate) SetNillableUpdatedAt(t *time.Time) *AchievementsSnapshotCreate { + if t != nil { + asc.SetUpdatedAt(*t) } return asc } @@ -75,8 +76,8 @@ func (asc *AchievementsSnapshotCreate) SetBattles(i int) *AchievementsSnapshotCr } // SetLastBattleTime sets the "last_battle_time" field. -func (asc *AchievementsSnapshotCreate) SetLastBattleTime(i int64) *AchievementsSnapshotCreate { - asc.mutation.SetLastBattleTime(i) +func (asc *AchievementsSnapshotCreate) SetLastBattleTime(t time.Time) *AchievementsSnapshotCreate { + asc.mutation.SetLastBattleTime(t) return asc } @@ -234,11 +235,11 @@ func (asc *AchievementsSnapshotCreate) createSpec() (*AchievementsSnapshot, *sql _spec.ID.Value = id } if value, ok := asc.mutation.CreatedAt(); ok { - _spec.SetField(achievementssnapshot.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(achievementssnapshot.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := asc.mutation.UpdatedAt(); ok { - _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := asc.mutation.GetType(); ok { @@ -254,7 +255,7 @@ func (asc *AchievementsSnapshotCreate) createSpec() (*AchievementsSnapshot, *sql _node.Battles = value } if value, ok := asc.mutation.LastBattleTime(); ok { - _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeTime, value) _node.LastBattleTime = value } if value, ok := asc.mutation.Data(); ok { diff --git a/internal/database/ent/db/achievementssnapshot_query.go b/internal/database/ent/db/achievementssnapshot_query.go index fa60ab7f..74fe01be 100644 --- a/internal/database/ent/db/achievementssnapshot_query.go +++ b/internal/database/ent/db/achievementssnapshot_query.go @@ -23,6 +23,7 @@ type AchievementsSnapshotQuery struct { inters []Interceptor predicates []predicate.AchievementsSnapshot withAccount *AccountQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -297,7 +298,7 @@ func (asq *AchievementsSnapshotQuery) WithAccount(opts ...func(*AccountQuery)) * // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +321,7 @@ func (asq *AchievementsSnapshotQuery) GroupBy(field string, fields ...string) *A // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.AchievementsSnapshot.Query(). @@ -382,6 +383,9 @@ func (asq *AchievementsSnapshotQuery) sqlAll(ctx context.Context, hooks ...query node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(asq.modifiers) > 0 { + _spec.Modifiers = asq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -432,6 +436,9 @@ func (asq *AchievementsSnapshotQuery) loadAccount(ctx context.Context, query *Ac func (asq *AchievementsSnapshotQuery) sqlCount(ctx context.Context) (int, error) { _spec := asq.querySpec() + if len(asq.modifiers) > 0 { + _spec.Modifiers = asq.modifiers + } _spec.Node.Columns = asq.ctx.Fields if len(asq.ctx.Fields) > 0 { _spec.Unique = asq.ctx.Unique != nil && *asq.ctx.Unique @@ -497,6 +504,9 @@ func (asq *AchievementsSnapshotQuery) sqlQuery(ctx context.Context) *sql.Selecto if asq.ctx.Unique != nil && *asq.ctx.Unique { selector.Distinct() } + for _, m := range asq.modifiers { + m(selector) + } for _, p := range asq.predicates { p(selector) } @@ -514,6 +524,12 @@ func (asq *AchievementsSnapshotQuery) sqlQuery(ctx context.Context) *sql.Selecto return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (asq *AchievementsSnapshotQuery) Modify(modifiers ...func(s *sql.Selector)) *AchievementsSnapshotSelect { + asq.modifiers = append(asq.modifiers, modifiers...) + return asq.Select() +} + // AchievementsSnapshotGroupBy is the group-by builder for AchievementsSnapshot entities. type AchievementsSnapshotGroupBy struct { selector @@ -603,3 +619,9 @@ func (ass *AchievementsSnapshotSelect) sqlScan(ctx context.Context, root *Achiev defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (ass *AchievementsSnapshotSelect) Modify(modifiers ...func(s *sql.Selector)) *AchievementsSnapshotSelect { + ass.modifiers = append(ass.modifiers, modifiers...) + return ass +} diff --git a/internal/database/ent/db/achievementssnapshot_update.go b/internal/database/ent/db/achievementssnapshot_update.go index 834089c4..43b79916 100644 --- a/internal/database/ent/db/achievementssnapshot_update.go +++ b/internal/database/ent/db/achievementssnapshot_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -19,8 +20,9 @@ import ( // AchievementsSnapshotUpdate is the builder for updating AchievementsSnapshot entities. type AchievementsSnapshotUpdate struct { config - hooks []Hook - mutation *AchievementsSnapshotMutation + hooks []Hook + mutation *AchievementsSnapshotMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the AchievementsSnapshotUpdate builder. @@ -30,15 +32,8 @@ func (asu *AchievementsSnapshotUpdate) Where(ps ...predicate.AchievementsSnapsho } // SetUpdatedAt sets the "updated_at" field. -func (asu *AchievementsSnapshotUpdate) SetUpdatedAt(i int64) *AchievementsSnapshotUpdate { - asu.mutation.ResetUpdatedAt() - asu.mutation.SetUpdatedAt(i) - return asu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (asu *AchievementsSnapshotUpdate) AddUpdatedAt(i int64) *AchievementsSnapshotUpdate { - asu.mutation.AddUpdatedAt(i) +func (asu *AchievementsSnapshotUpdate) SetUpdatedAt(t time.Time) *AchievementsSnapshotUpdate { + asu.mutation.SetUpdatedAt(t) return asu } @@ -92,26 +87,19 @@ func (asu *AchievementsSnapshotUpdate) AddBattles(i int) *AchievementsSnapshotUp } // SetLastBattleTime sets the "last_battle_time" field. -func (asu *AchievementsSnapshotUpdate) SetLastBattleTime(i int64) *AchievementsSnapshotUpdate { - asu.mutation.ResetLastBattleTime() - asu.mutation.SetLastBattleTime(i) +func (asu *AchievementsSnapshotUpdate) SetLastBattleTime(t time.Time) *AchievementsSnapshotUpdate { + asu.mutation.SetLastBattleTime(t) return asu } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (asu *AchievementsSnapshotUpdate) SetNillableLastBattleTime(i *int64) *AchievementsSnapshotUpdate { - if i != nil { - asu.SetLastBattleTime(*i) +func (asu *AchievementsSnapshotUpdate) SetNillableLastBattleTime(t *time.Time) *AchievementsSnapshotUpdate { + if t != nil { + asu.SetLastBattleTime(*t) } return asu } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (asu *AchievementsSnapshotUpdate) AddLastBattleTime(i int64) *AchievementsSnapshotUpdate { - asu.mutation.AddLastBattleTime(i) - return asu -} - // SetData sets the "data" field. func (asu *AchievementsSnapshotUpdate) SetData(tf types.AchievementsFrame) *AchievementsSnapshotUpdate { asu.mutation.SetData(tf) @@ -185,6 +173,12 @@ func (asu *AchievementsSnapshotUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (asu *AchievementsSnapshotUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AchievementsSnapshotUpdate { + asu.modifiers = append(asu.modifiers, modifiers...) + return asu +} + func (asu *AchievementsSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := asu.check(); err != nil { return n, err @@ -198,10 +192,7 @@ func (asu *AchievementsSnapshotUpdate) sqlSave(ctx context.Context) (n int, err } } if value, ok := asu.mutation.UpdatedAt(); ok { - _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := asu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeTime, value) } if value, ok := asu.mutation.GetType(); ok { _spec.SetField(achievementssnapshot.FieldType, field.TypeEnum, value) @@ -216,14 +207,12 @@ func (asu *AchievementsSnapshotUpdate) sqlSave(ctx context.Context) (n int, err _spec.AddField(achievementssnapshot.FieldBattles, field.TypeInt, value) } if value, ok := asu.mutation.LastBattleTime(); ok { - _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) - } - if value, ok := asu.mutation.AddedLastBattleTime(); ok { - _spec.AddField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeTime, value) } if value, ok := asu.mutation.Data(); ok { _spec.SetField(achievementssnapshot.FieldData, field.TypeJSON, value) } + _spec.AddModifiers(asu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, asu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{achievementssnapshot.Label} @@ -239,21 +228,15 @@ func (asu *AchievementsSnapshotUpdate) sqlSave(ctx context.Context) (n int, err // AchievementsSnapshotUpdateOne is the builder for updating a single AchievementsSnapshot entity. type AchievementsSnapshotUpdateOne struct { config - fields []string - hooks []Hook - mutation *AchievementsSnapshotMutation + fields []string + hooks []Hook + mutation *AchievementsSnapshotMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (asuo *AchievementsSnapshotUpdateOne) SetUpdatedAt(i int64) *AchievementsSnapshotUpdateOne { - asuo.mutation.ResetUpdatedAt() - asuo.mutation.SetUpdatedAt(i) - return asuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (asuo *AchievementsSnapshotUpdateOne) AddUpdatedAt(i int64) *AchievementsSnapshotUpdateOne { - asuo.mutation.AddUpdatedAt(i) +func (asuo *AchievementsSnapshotUpdateOne) SetUpdatedAt(t time.Time) *AchievementsSnapshotUpdateOne { + asuo.mutation.SetUpdatedAt(t) return asuo } @@ -307,26 +290,19 @@ func (asuo *AchievementsSnapshotUpdateOne) AddBattles(i int) *AchievementsSnapsh } // SetLastBattleTime sets the "last_battle_time" field. -func (asuo *AchievementsSnapshotUpdateOne) SetLastBattleTime(i int64) *AchievementsSnapshotUpdateOne { - asuo.mutation.ResetLastBattleTime() - asuo.mutation.SetLastBattleTime(i) +func (asuo *AchievementsSnapshotUpdateOne) SetLastBattleTime(t time.Time) *AchievementsSnapshotUpdateOne { + asuo.mutation.SetLastBattleTime(t) return asuo } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (asuo *AchievementsSnapshotUpdateOne) SetNillableLastBattleTime(i *int64) *AchievementsSnapshotUpdateOne { - if i != nil { - asuo.SetLastBattleTime(*i) +func (asuo *AchievementsSnapshotUpdateOne) SetNillableLastBattleTime(t *time.Time) *AchievementsSnapshotUpdateOne { + if t != nil { + asuo.SetLastBattleTime(*t) } return asuo } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (asuo *AchievementsSnapshotUpdateOne) AddLastBattleTime(i int64) *AchievementsSnapshotUpdateOne { - asuo.mutation.AddLastBattleTime(i) - return asuo -} - // SetData sets the "data" field. func (asuo *AchievementsSnapshotUpdateOne) SetData(tf types.AchievementsFrame) *AchievementsSnapshotUpdateOne { asuo.mutation.SetData(tf) @@ -413,6 +389,12 @@ func (asuo *AchievementsSnapshotUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (asuo *AchievementsSnapshotUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AchievementsSnapshotUpdateOne { + asuo.modifiers = append(asuo.modifiers, modifiers...) + return asuo +} + func (asuo *AchievementsSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *AchievementsSnapshot, err error) { if err := asuo.check(); err != nil { return _node, err @@ -443,10 +425,7 @@ func (asuo *AchievementsSnapshotUpdateOne) sqlSave(ctx context.Context) (_node * } } if value, ok := asuo.mutation.UpdatedAt(); ok { - _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := asuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(achievementssnapshot.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(achievementssnapshot.FieldUpdatedAt, field.TypeTime, value) } if value, ok := asuo.mutation.GetType(); ok { _spec.SetField(achievementssnapshot.FieldType, field.TypeEnum, value) @@ -461,14 +440,12 @@ func (asuo *AchievementsSnapshotUpdateOne) sqlSave(ctx context.Context) (_node * _spec.AddField(achievementssnapshot.FieldBattles, field.TypeInt, value) } if value, ok := asuo.mutation.LastBattleTime(); ok { - _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) - } - if value, ok := asuo.mutation.AddedLastBattleTime(); ok { - _spec.AddField(achievementssnapshot.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(achievementssnapshot.FieldLastBattleTime, field.TypeTime, value) } if value, ok := asuo.mutation.Data(); ok { _spec.SetField(achievementssnapshot.FieldData, field.TypeJSON, value) } + _spec.AddModifiers(asuo.modifiers...) _node = &AchievementsSnapshot{config: asuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/appconfiguration.go b/internal/database/ent/db/appconfiguration.go index 5aaf0eb8..d88d93b1 100644 --- a/internal/database/ent/db/appconfiguration.go +++ b/internal/database/ent/db/appconfiguration.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -18,9 +19,9 @@ type AppConfiguration struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Key holds the value of the "key" field. Key string `json:"key,omitempty"` // Value holds the value of the "value" field. @@ -37,10 +38,10 @@ func (*AppConfiguration) scanValues(columns []string) ([]any, error) { switch columns[i] { case appconfiguration.FieldValue, appconfiguration.FieldMetadata: values[i] = new([]byte) - case appconfiguration.FieldCreatedAt, appconfiguration.FieldUpdatedAt: - values[i] = new(sql.NullInt64) case appconfiguration.FieldID, appconfiguration.FieldKey: values[i] = new(sql.NullString) + case appconfiguration.FieldCreatedAt, appconfiguration.FieldUpdatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -63,16 +64,16 @@ func (ac *AppConfiguration) assignValues(columns []string, values []any) error { ac.ID = value.String } case appconfiguration.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - ac.CreatedAt = value.Int64 + ac.CreatedAt = value.Time } case appconfiguration.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - ac.UpdatedAt = value.Int64 + ac.UpdatedAt = value.Time } case appconfiguration.FieldKey: if value, ok := values[i].(*sql.NullString); !ok { @@ -133,10 +134,10 @@ func (ac *AppConfiguration) String() string { builder.WriteString("AppConfiguration(") builder.WriteString(fmt.Sprintf("id=%v, ", ac.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", ac.CreatedAt)) + builder.WriteString(ac.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", ac.UpdatedAt)) + builder.WriteString(ac.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("key=") builder.WriteString(ac.Key) diff --git a/internal/database/ent/db/appconfiguration/appconfiguration.go b/internal/database/ent/db/appconfiguration/appconfiguration.go index facffdce..2f8076a5 100644 --- a/internal/database/ent/db/appconfiguration/appconfiguration.go +++ b/internal/database/ent/db/appconfiguration/appconfiguration.go @@ -3,6 +3,8 @@ package appconfiguration import ( + "time" + "entgo.io/ent/dialect/sql" ) @@ -47,11 +49,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // KeyValidator is a validator for the "key" field. It is called by the builders before save. KeyValidator func(string) error // DefaultID holds the default value on creation for the "id" field. diff --git a/internal/database/ent/db/appconfiguration/where.go b/internal/database/ent/db/appconfiguration/where.go index 6d63474c..11bf5637 100644 --- a/internal/database/ent/db/appconfiguration/where.go +++ b/internal/database/ent/db/appconfiguration/where.go @@ -3,6 +3,8 @@ package appconfiguration import ( + "time" + "entgo.io/ent/dialect/sql" "github.com/cufee/aftermath/internal/database/ent/db/predicate" ) @@ -63,12 +65,12 @@ func IDContainsFold(id string) predicate.AppConfiguration { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.AppConfiguration { +func CreatedAt(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.AppConfiguration { +func UpdatedAt(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -78,82 +80,82 @@ func Key(v string) predicate.AppConfiguration { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.AppConfiguration { +func CreatedAtEQ(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.AppConfiguration { +func CreatedAtNEQ(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.AppConfiguration { +func CreatedAtIn(vs ...time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.AppConfiguration { +func CreatedAtNotIn(vs ...time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.AppConfiguration { +func CreatedAtGT(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.AppConfiguration { +func CreatedAtGTE(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.AppConfiguration { +func CreatedAtLT(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.AppConfiguration { +func CreatedAtLTE(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.AppConfiguration { +func UpdatedAtEQ(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.AppConfiguration { +func UpdatedAtNEQ(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.AppConfiguration { +func UpdatedAtIn(vs ...time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.AppConfiguration { +func UpdatedAtNotIn(vs ...time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.AppConfiguration { +func UpdatedAtGT(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.AppConfiguration { +func UpdatedAtGTE(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.AppConfiguration { +func UpdatedAtLT(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.AppConfiguration { +func UpdatedAtLTE(v time.Time) predicate.AppConfiguration { return predicate.AppConfiguration(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/appconfiguration_create.go b/internal/database/ent/db/appconfiguration_create.go index c6a9c349..2a3c9e91 100644 --- a/internal/database/ent/db/appconfiguration_create.go +++ b/internal/database/ent/db/appconfiguration_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -20,29 +21,29 @@ type AppConfigurationCreate struct { } // SetCreatedAt sets the "created_at" field. -func (acc *AppConfigurationCreate) SetCreatedAt(i int64) *AppConfigurationCreate { - acc.mutation.SetCreatedAt(i) +func (acc *AppConfigurationCreate) SetCreatedAt(t time.Time) *AppConfigurationCreate { + acc.mutation.SetCreatedAt(t) return acc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (acc *AppConfigurationCreate) SetNillableCreatedAt(i *int64) *AppConfigurationCreate { - if i != nil { - acc.SetCreatedAt(*i) +func (acc *AppConfigurationCreate) SetNillableCreatedAt(t *time.Time) *AppConfigurationCreate { + if t != nil { + acc.SetCreatedAt(*t) } return acc } // SetUpdatedAt sets the "updated_at" field. -func (acc *AppConfigurationCreate) SetUpdatedAt(i int64) *AppConfigurationCreate { - acc.mutation.SetUpdatedAt(i) +func (acc *AppConfigurationCreate) SetUpdatedAt(t time.Time) *AppConfigurationCreate { + acc.mutation.SetUpdatedAt(t) return acc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (acc *AppConfigurationCreate) SetNillableUpdatedAt(i *int64) *AppConfigurationCreate { - if i != nil { - acc.SetUpdatedAt(*i) +func (acc *AppConfigurationCreate) SetNillableUpdatedAt(t *time.Time) *AppConfigurationCreate { + if t != nil { + acc.SetUpdatedAt(*t) } return acc } @@ -183,11 +184,11 @@ func (acc *AppConfigurationCreate) createSpec() (*AppConfiguration, *sqlgraph.Cr _spec.ID.Value = id } if value, ok := acc.mutation.CreatedAt(); ok { - _spec.SetField(appconfiguration.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(appconfiguration.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := acc.mutation.UpdatedAt(); ok { - _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := acc.mutation.Key(); ok { diff --git a/internal/database/ent/db/appconfiguration_query.go b/internal/database/ent/db/appconfiguration_query.go index 759954bb..530965f1 100644 --- a/internal/database/ent/db/appconfiguration_query.go +++ b/internal/database/ent/db/appconfiguration_query.go @@ -21,6 +21,7 @@ type AppConfigurationQuery struct { order []appconfiguration.OrderOption inters []Interceptor predicates []predicate.AppConfiguration + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -261,7 +262,7 @@ func (acq *AppConfigurationQuery) Clone() *AppConfigurationQuery { // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +285,7 @@ func (acq *AppConfigurationQuery) GroupBy(field string, fields ...string) *AppCo // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.AppConfiguration.Query(). @@ -342,6 +343,9 @@ func (acq *AppConfigurationQuery) sqlAll(ctx context.Context, hooks ...queryHook nodes = append(nodes, node) return node.assignValues(columns, values) } + if len(acq.modifiers) > 0 { + _spec.Modifiers = acq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -356,6 +360,9 @@ func (acq *AppConfigurationQuery) sqlAll(ctx context.Context, hooks ...queryHook func (acq *AppConfigurationQuery) sqlCount(ctx context.Context) (int, error) { _spec := acq.querySpec() + if len(acq.modifiers) > 0 { + _spec.Modifiers = acq.modifiers + } _spec.Node.Columns = acq.ctx.Fields if len(acq.ctx.Fields) > 0 { _spec.Unique = acq.ctx.Unique != nil && *acq.ctx.Unique @@ -418,6 +425,9 @@ func (acq *AppConfigurationQuery) sqlQuery(ctx context.Context) *sql.Selector { if acq.ctx.Unique != nil && *acq.ctx.Unique { selector.Distinct() } + for _, m := range acq.modifiers { + m(selector) + } for _, p := range acq.predicates { p(selector) } @@ -435,6 +445,12 @@ func (acq *AppConfigurationQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (acq *AppConfigurationQuery) Modify(modifiers ...func(s *sql.Selector)) *AppConfigurationSelect { + acq.modifiers = append(acq.modifiers, modifiers...) + return acq.Select() +} + // AppConfigurationGroupBy is the group-by builder for AppConfiguration entities. type AppConfigurationGroupBy struct { selector @@ -524,3 +540,9 @@ func (acs *AppConfigurationSelect) sqlScan(ctx context.Context, root *AppConfigu defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (acs *AppConfigurationSelect) Modify(modifiers ...func(s *sql.Selector)) *AppConfigurationSelect { + acs.modifiers = append(acs.modifiers, modifiers...) + return acs +} diff --git a/internal/database/ent/db/appconfiguration_update.go b/internal/database/ent/db/appconfiguration_update.go index 12994e32..2da9b3d1 100644 --- a/internal/database/ent/db/appconfiguration_update.go +++ b/internal/database/ent/db/appconfiguration_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -17,8 +18,9 @@ import ( // AppConfigurationUpdate is the builder for updating AppConfiguration entities. type AppConfigurationUpdate struct { config - hooks []Hook - mutation *AppConfigurationMutation + hooks []Hook + mutation *AppConfigurationMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the AppConfigurationUpdate builder. @@ -28,15 +30,8 @@ func (acu *AppConfigurationUpdate) Where(ps ...predicate.AppConfiguration) *AppC } // SetUpdatedAt sets the "updated_at" field. -func (acu *AppConfigurationUpdate) SetUpdatedAt(i int64) *AppConfigurationUpdate { - acu.mutation.ResetUpdatedAt() - acu.mutation.SetUpdatedAt(i) - return acu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (acu *AppConfigurationUpdate) AddUpdatedAt(i int64) *AppConfigurationUpdate { - acu.mutation.AddUpdatedAt(i) +func (acu *AppConfigurationUpdate) SetUpdatedAt(t time.Time) *AppConfigurationUpdate { + acu.mutation.SetUpdatedAt(t) return acu } @@ -123,6 +118,12 @@ func (acu *AppConfigurationUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (acu *AppConfigurationUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AppConfigurationUpdate { + acu.modifiers = append(acu.modifiers, modifiers...) + return acu +} + func (acu *AppConfigurationUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := acu.check(); err != nil { return n, err @@ -136,10 +137,7 @@ func (acu *AppConfigurationUpdate) sqlSave(ctx context.Context) (n int, err erro } } if value, ok := acu.mutation.UpdatedAt(); ok { - _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := acu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeTime, value) } if value, ok := acu.mutation.Key(); ok { _spec.SetField(appconfiguration.FieldKey, field.TypeString, value) @@ -153,6 +151,7 @@ func (acu *AppConfigurationUpdate) sqlSave(ctx context.Context) (n int, err erro if acu.mutation.MetadataCleared() { _spec.ClearField(appconfiguration.FieldMetadata, field.TypeJSON) } + _spec.AddModifiers(acu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, acu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{appconfiguration.Label} @@ -168,21 +167,15 @@ func (acu *AppConfigurationUpdate) sqlSave(ctx context.Context) (n int, err erro // AppConfigurationUpdateOne is the builder for updating a single AppConfiguration entity. type AppConfigurationUpdateOne struct { config - fields []string - hooks []Hook - mutation *AppConfigurationMutation + fields []string + hooks []Hook + mutation *AppConfigurationMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (acuo *AppConfigurationUpdateOne) SetUpdatedAt(i int64) *AppConfigurationUpdateOne { - acuo.mutation.ResetUpdatedAt() - acuo.mutation.SetUpdatedAt(i) - return acuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (acuo *AppConfigurationUpdateOne) AddUpdatedAt(i int64) *AppConfigurationUpdateOne { - acuo.mutation.AddUpdatedAt(i) +func (acuo *AppConfigurationUpdateOne) SetUpdatedAt(t time.Time) *AppConfigurationUpdateOne { + acuo.mutation.SetUpdatedAt(t) return acuo } @@ -282,6 +275,12 @@ func (acuo *AppConfigurationUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (acuo *AppConfigurationUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AppConfigurationUpdateOne { + acuo.modifiers = append(acuo.modifiers, modifiers...) + return acuo +} + func (acuo *AppConfigurationUpdateOne) sqlSave(ctx context.Context) (_node *AppConfiguration, err error) { if err := acuo.check(); err != nil { return _node, err @@ -312,10 +311,7 @@ func (acuo *AppConfigurationUpdateOne) sqlSave(ctx context.Context) (_node *AppC } } if value, ok := acuo.mutation.UpdatedAt(); ok { - _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := acuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(appconfiguration.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(appconfiguration.FieldUpdatedAt, field.TypeTime, value) } if value, ok := acuo.mutation.Key(); ok { _spec.SetField(appconfiguration.FieldKey, field.TypeString, value) @@ -329,6 +325,7 @@ func (acuo *AppConfigurationUpdateOne) sqlSave(ctx context.Context) (_node *AppC if acuo.mutation.MetadataCleared() { _spec.ClearField(appconfiguration.FieldMetadata, field.TypeJSON) } + _spec.AddModifiers(acuo.modifiers...) _node = &AppConfiguration{config: acuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/applicationcommand.go b/internal/database/ent/db/applicationcommand.go index 0ba69869..88016dc0 100644 --- a/internal/database/ent/db/applicationcommand.go +++ b/internal/database/ent/db/applicationcommand.go @@ -5,6 +5,7 @@ package db import ( "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -17,9 +18,9 @@ type ApplicationCommand struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Name holds the value of the "name" field. Name string `json:"name,omitempty"` // Version holds the value of the "version" field. @@ -34,10 +35,10 @@ func (*ApplicationCommand) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case applicationcommand.FieldCreatedAt, applicationcommand.FieldUpdatedAt: - values[i] = new(sql.NullInt64) case applicationcommand.FieldID, applicationcommand.FieldName, applicationcommand.FieldVersion, applicationcommand.FieldOptionsHash: values[i] = new(sql.NullString) + case applicationcommand.FieldCreatedAt, applicationcommand.FieldUpdatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -60,16 +61,16 @@ func (ac *ApplicationCommand) assignValues(columns []string, values []any) error ac.ID = value.String } case applicationcommand.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - ac.CreatedAt = value.Int64 + ac.CreatedAt = value.Time } case applicationcommand.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - ac.UpdatedAt = value.Int64 + ac.UpdatedAt = value.Time } case applicationcommand.FieldName: if value, ok := values[i].(*sql.NullString); !ok { @@ -126,10 +127,10 @@ func (ac *ApplicationCommand) String() string { builder.WriteString("ApplicationCommand(") builder.WriteString(fmt.Sprintf("id=%v, ", ac.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", ac.CreatedAt)) + builder.WriteString(ac.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", ac.UpdatedAt)) + builder.WriteString(ac.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("name=") builder.WriteString(ac.Name) diff --git a/internal/database/ent/db/applicationcommand/applicationcommand.go b/internal/database/ent/db/applicationcommand/applicationcommand.go index 4e67599d..d2885b61 100644 --- a/internal/database/ent/db/applicationcommand/applicationcommand.go +++ b/internal/database/ent/db/applicationcommand/applicationcommand.go @@ -3,6 +3,8 @@ package applicationcommand import ( + "time" + "entgo.io/ent/dialect/sql" ) @@ -47,11 +49,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // NameValidator is a validator for the "name" field. It is called by the builders before save. NameValidator func(string) error // VersionValidator is a validator for the "version" field. It is called by the builders before save. diff --git a/internal/database/ent/db/applicationcommand/where.go b/internal/database/ent/db/applicationcommand/where.go index 81486987..d9b36efd 100644 --- a/internal/database/ent/db/applicationcommand/where.go +++ b/internal/database/ent/db/applicationcommand/where.go @@ -3,6 +3,8 @@ package applicationcommand import ( + "time" + "entgo.io/ent/dialect/sql" "github.com/cufee/aftermath/internal/database/ent/db/predicate" ) @@ -63,12 +65,12 @@ func IDContainsFold(id string) predicate.ApplicationCommand { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.ApplicationCommand { +func CreatedAt(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.ApplicationCommand { +func UpdatedAt(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -88,82 +90,82 @@ func OptionsHash(v string) predicate.ApplicationCommand { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.ApplicationCommand { +func CreatedAtEQ(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.ApplicationCommand { +func CreatedAtNEQ(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.ApplicationCommand { +func CreatedAtIn(vs ...time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.ApplicationCommand { +func CreatedAtNotIn(vs ...time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.ApplicationCommand { +func CreatedAtGT(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.ApplicationCommand { +func CreatedAtGTE(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.ApplicationCommand { +func CreatedAtLT(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.ApplicationCommand { +func CreatedAtLTE(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.ApplicationCommand { +func UpdatedAtEQ(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.ApplicationCommand { +func UpdatedAtNEQ(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.ApplicationCommand { +func UpdatedAtIn(vs ...time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.ApplicationCommand { +func UpdatedAtNotIn(vs ...time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.ApplicationCommand { +func UpdatedAtGT(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.ApplicationCommand { +func UpdatedAtGTE(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.ApplicationCommand { +func UpdatedAtLT(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.ApplicationCommand { +func UpdatedAtLTE(v time.Time) predicate.ApplicationCommand { return predicate.ApplicationCommand(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/applicationcommand_create.go b/internal/database/ent/db/applicationcommand_create.go index 830a457d..903f6337 100644 --- a/internal/database/ent/db/applicationcommand_create.go +++ b/internal/database/ent/db/applicationcommand_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -20,29 +21,29 @@ type ApplicationCommandCreate struct { } // SetCreatedAt sets the "created_at" field. -func (acc *ApplicationCommandCreate) SetCreatedAt(i int64) *ApplicationCommandCreate { - acc.mutation.SetCreatedAt(i) +func (acc *ApplicationCommandCreate) SetCreatedAt(t time.Time) *ApplicationCommandCreate { + acc.mutation.SetCreatedAt(t) return acc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (acc *ApplicationCommandCreate) SetNillableCreatedAt(i *int64) *ApplicationCommandCreate { - if i != nil { - acc.SetCreatedAt(*i) +func (acc *ApplicationCommandCreate) SetNillableCreatedAt(t *time.Time) *ApplicationCommandCreate { + if t != nil { + acc.SetCreatedAt(*t) } return acc } // SetUpdatedAt sets the "updated_at" field. -func (acc *ApplicationCommandCreate) SetUpdatedAt(i int64) *ApplicationCommandCreate { - acc.mutation.SetUpdatedAt(i) +func (acc *ApplicationCommandCreate) SetUpdatedAt(t time.Time) *ApplicationCommandCreate { + acc.mutation.SetUpdatedAt(t) return acc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (acc *ApplicationCommandCreate) SetNillableUpdatedAt(i *int64) *ApplicationCommandCreate { - if i != nil { - acc.SetUpdatedAt(*i) +func (acc *ApplicationCommandCreate) SetNillableUpdatedAt(t *time.Time) *ApplicationCommandCreate { + if t != nil { + acc.SetUpdatedAt(*t) } return acc } @@ -196,11 +197,11 @@ func (acc *ApplicationCommandCreate) createSpec() (*ApplicationCommand, *sqlgrap _spec.ID.Value = id } if value, ok := acc.mutation.CreatedAt(); ok { - _spec.SetField(applicationcommand.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(applicationcommand.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := acc.mutation.UpdatedAt(); ok { - _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := acc.mutation.Name(); ok { diff --git a/internal/database/ent/db/applicationcommand_query.go b/internal/database/ent/db/applicationcommand_query.go index 5bd7757c..f20b8ac6 100644 --- a/internal/database/ent/db/applicationcommand_query.go +++ b/internal/database/ent/db/applicationcommand_query.go @@ -21,6 +21,7 @@ type ApplicationCommandQuery struct { order []applicationcommand.OrderOption inters []Interceptor predicates []predicate.ApplicationCommand + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -261,7 +262,7 @@ func (acq *ApplicationCommandQuery) Clone() *ApplicationCommandQuery { // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +285,7 @@ func (acq *ApplicationCommandQuery) GroupBy(field string, fields ...string) *App // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.ApplicationCommand.Query(). @@ -342,6 +343,9 @@ func (acq *ApplicationCommandQuery) sqlAll(ctx context.Context, hooks ...queryHo nodes = append(nodes, node) return node.assignValues(columns, values) } + if len(acq.modifiers) > 0 { + _spec.Modifiers = acq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -356,6 +360,9 @@ func (acq *ApplicationCommandQuery) sqlAll(ctx context.Context, hooks ...queryHo func (acq *ApplicationCommandQuery) sqlCount(ctx context.Context) (int, error) { _spec := acq.querySpec() + if len(acq.modifiers) > 0 { + _spec.Modifiers = acq.modifiers + } _spec.Node.Columns = acq.ctx.Fields if len(acq.ctx.Fields) > 0 { _spec.Unique = acq.ctx.Unique != nil && *acq.ctx.Unique @@ -418,6 +425,9 @@ func (acq *ApplicationCommandQuery) sqlQuery(ctx context.Context) *sql.Selector if acq.ctx.Unique != nil && *acq.ctx.Unique { selector.Distinct() } + for _, m := range acq.modifiers { + m(selector) + } for _, p := range acq.predicates { p(selector) } @@ -435,6 +445,12 @@ func (acq *ApplicationCommandQuery) sqlQuery(ctx context.Context) *sql.Selector return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (acq *ApplicationCommandQuery) Modify(modifiers ...func(s *sql.Selector)) *ApplicationCommandSelect { + acq.modifiers = append(acq.modifiers, modifiers...) + return acq.Select() +} + // ApplicationCommandGroupBy is the group-by builder for ApplicationCommand entities. type ApplicationCommandGroupBy struct { selector @@ -524,3 +540,9 @@ func (acs *ApplicationCommandSelect) sqlScan(ctx context.Context, root *Applicat defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (acs *ApplicationCommandSelect) Modify(modifiers ...func(s *sql.Selector)) *ApplicationCommandSelect { + acs.modifiers = append(acs.modifiers, modifiers...) + return acs +} diff --git a/internal/database/ent/db/applicationcommand_update.go b/internal/database/ent/db/applicationcommand_update.go index f681b841..58829e30 100644 --- a/internal/database/ent/db/applicationcommand_update.go +++ b/internal/database/ent/db/applicationcommand_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -17,8 +18,9 @@ import ( // ApplicationCommandUpdate is the builder for updating ApplicationCommand entities. type ApplicationCommandUpdate struct { config - hooks []Hook - mutation *ApplicationCommandMutation + hooks []Hook + mutation *ApplicationCommandMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the ApplicationCommandUpdate builder. @@ -28,15 +30,8 @@ func (acu *ApplicationCommandUpdate) Where(ps ...predicate.ApplicationCommand) * } // SetUpdatedAt sets the "updated_at" field. -func (acu *ApplicationCommandUpdate) SetUpdatedAt(i int64) *ApplicationCommandUpdate { - acu.mutation.ResetUpdatedAt() - acu.mutation.SetUpdatedAt(i) - return acu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (acu *ApplicationCommandUpdate) AddUpdatedAt(i int64) *ApplicationCommandUpdate { - acu.mutation.AddUpdatedAt(i) +func (acu *ApplicationCommandUpdate) SetUpdatedAt(t time.Time) *ApplicationCommandUpdate { + acu.mutation.SetUpdatedAt(t) return acu } @@ -143,6 +138,12 @@ func (acu *ApplicationCommandUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (acu *ApplicationCommandUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *ApplicationCommandUpdate { + acu.modifiers = append(acu.modifiers, modifiers...) + return acu +} + func (acu *ApplicationCommandUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := acu.check(); err != nil { return n, err @@ -156,10 +157,7 @@ func (acu *ApplicationCommandUpdate) sqlSave(ctx context.Context) (n int, err er } } if value, ok := acu.mutation.UpdatedAt(); ok { - _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := acu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeTime, value) } if value, ok := acu.mutation.Name(); ok { _spec.SetField(applicationcommand.FieldName, field.TypeString, value) @@ -170,6 +168,7 @@ func (acu *ApplicationCommandUpdate) sqlSave(ctx context.Context) (n int, err er if value, ok := acu.mutation.OptionsHash(); ok { _spec.SetField(applicationcommand.FieldOptionsHash, field.TypeString, value) } + _spec.AddModifiers(acu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, acu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{applicationcommand.Label} @@ -185,21 +184,15 @@ func (acu *ApplicationCommandUpdate) sqlSave(ctx context.Context) (n int, err er // ApplicationCommandUpdateOne is the builder for updating a single ApplicationCommand entity. type ApplicationCommandUpdateOne struct { config - fields []string - hooks []Hook - mutation *ApplicationCommandMutation + fields []string + hooks []Hook + mutation *ApplicationCommandMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (acuo *ApplicationCommandUpdateOne) SetUpdatedAt(i int64) *ApplicationCommandUpdateOne { - acuo.mutation.ResetUpdatedAt() - acuo.mutation.SetUpdatedAt(i) - return acuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (acuo *ApplicationCommandUpdateOne) AddUpdatedAt(i int64) *ApplicationCommandUpdateOne { - acuo.mutation.AddUpdatedAt(i) +func (acuo *ApplicationCommandUpdateOne) SetUpdatedAt(t time.Time) *ApplicationCommandUpdateOne { + acuo.mutation.SetUpdatedAt(t) return acuo } @@ -319,6 +312,12 @@ func (acuo *ApplicationCommandUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (acuo *ApplicationCommandUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *ApplicationCommandUpdateOne { + acuo.modifiers = append(acuo.modifiers, modifiers...) + return acuo +} + func (acuo *ApplicationCommandUpdateOne) sqlSave(ctx context.Context) (_node *ApplicationCommand, err error) { if err := acuo.check(); err != nil { return _node, err @@ -349,10 +348,7 @@ func (acuo *ApplicationCommandUpdateOne) sqlSave(ctx context.Context) (_node *Ap } } if value, ok := acuo.mutation.UpdatedAt(); ok { - _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := acuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(applicationcommand.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(applicationcommand.FieldUpdatedAt, field.TypeTime, value) } if value, ok := acuo.mutation.Name(); ok { _spec.SetField(applicationcommand.FieldName, field.TypeString, value) @@ -363,6 +359,7 @@ func (acuo *ApplicationCommandUpdateOne) sqlSave(ctx context.Context) (_node *Ap if value, ok := acuo.mutation.OptionsHash(); ok { _spec.SetField(applicationcommand.FieldOptionsHash, field.TypeString, value) } + _spec.AddModifiers(acuo.modifiers...) _node = &ApplicationCommand{config: acuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/clan.go b/internal/database/ent/db/clan.go index 1f883f27..22c9c9b5 100644 --- a/internal/database/ent/db/clan.go +++ b/internal/database/ent/db/clan.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -18,9 +19,9 @@ type Clan struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Tag holds the value of the "tag" field. Tag string `json:"tag,omitempty"` // Name holds the value of the "name" field. @@ -60,10 +61,10 @@ func (*Clan) scanValues(columns []string) ([]any, error) { switch columns[i] { case clan.FieldMembers: values[i] = new([]byte) - case clan.FieldCreatedAt, clan.FieldUpdatedAt: - values[i] = new(sql.NullInt64) case clan.FieldID, clan.FieldTag, clan.FieldName, clan.FieldEmblemID: values[i] = new(sql.NullString) + case clan.FieldCreatedAt, clan.FieldUpdatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -86,16 +87,16 @@ func (c *Clan) assignValues(columns []string, values []any) error { c.ID = value.String } case clan.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - c.CreatedAt = value.Int64 + c.CreatedAt = value.Time } case clan.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - c.UpdatedAt = value.Int64 + c.UpdatedAt = value.Time } case clan.FieldTag: if value, ok := values[i].(*sql.NullString); !ok { @@ -165,10 +166,10 @@ func (c *Clan) String() string { builder.WriteString("Clan(") builder.WriteString(fmt.Sprintf("id=%v, ", c.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", c.CreatedAt)) + builder.WriteString(c.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", c.UpdatedAt)) + builder.WriteString(c.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("tag=") builder.WriteString(c.Tag) diff --git a/internal/database/ent/db/clan/clan.go b/internal/database/ent/db/clan/clan.go index 59a2f6e0..95cdacd5 100644 --- a/internal/database/ent/db/clan/clan.go +++ b/internal/database/ent/db/clan/clan.go @@ -3,6 +3,8 @@ package clan import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" ) @@ -60,11 +62,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // TagValidator is a validator for the "tag" field. It is called by the builders before save. TagValidator func(string) error // NameValidator is a validator for the "name" field. It is called by the builders before save. diff --git a/internal/database/ent/db/clan/where.go b/internal/database/ent/db/clan/where.go index 6ab4d8fe..2810326d 100644 --- a/internal/database/ent/db/clan/where.go +++ b/internal/database/ent/db/clan/where.go @@ -3,6 +3,8 @@ package clan import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -64,12 +66,12 @@ func IDContainsFold(id string) predicate.Clan { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.Clan { +func CreatedAt(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.Clan { +func UpdatedAt(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -89,82 +91,82 @@ func EmblemID(v string) predicate.Clan { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.Clan { +func CreatedAtEQ(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.Clan { +func CreatedAtNEQ(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.Clan { +func CreatedAtIn(vs ...time.Time) predicate.Clan { return predicate.Clan(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.Clan { +func CreatedAtNotIn(vs ...time.Time) predicate.Clan { return predicate.Clan(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.Clan { +func CreatedAtGT(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.Clan { +func CreatedAtGTE(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.Clan { +func CreatedAtLT(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.Clan { +func CreatedAtLTE(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.Clan { +func UpdatedAtEQ(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.Clan { +func UpdatedAtNEQ(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.Clan { +func UpdatedAtIn(vs ...time.Time) predicate.Clan { return predicate.Clan(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.Clan { +func UpdatedAtNotIn(vs ...time.Time) predicate.Clan { return predicate.Clan(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.Clan { +func UpdatedAtGT(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.Clan { +func UpdatedAtGTE(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.Clan { +func UpdatedAtLT(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.Clan { +func UpdatedAtLTE(v time.Time) predicate.Clan { return predicate.Clan(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/clan_create.go b/internal/database/ent/db/clan_create.go index 438f1f4d..bc4a8e94 100644 --- a/internal/database/ent/db/clan_create.go +++ b/internal/database/ent/db/clan_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -21,29 +22,29 @@ type ClanCreate struct { } // SetCreatedAt sets the "created_at" field. -func (cc *ClanCreate) SetCreatedAt(i int64) *ClanCreate { - cc.mutation.SetCreatedAt(i) +func (cc *ClanCreate) SetCreatedAt(t time.Time) *ClanCreate { + cc.mutation.SetCreatedAt(t) return cc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (cc *ClanCreate) SetNillableCreatedAt(i *int64) *ClanCreate { - if i != nil { - cc.SetCreatedAt(*i) +func (cc *ClanCreate) SetNillableCreatedAt(t *time.Time) *ClanCreate { + if t != nil { + cc.SetCreatedAt(*t) } return cc } // SetUpdatedAt sets the "updated_at" field. -func (cc *ClanCreate) SetUpdatedAt(i int64) *ClanCreate { - cc.mutation.SetUpdatedAt(i) +func (cc *ClanCreate) SetUpdatedAt(t time.Time) *ClanCreate { + cc.mutation.SetUpdatedAt(t) return cc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (cc *ClanCreate) SetNillableUpdatedAt(i *int64) *ClanCreate { - if i != nil { - cc.SetUpdatedAt(*i) +func (cc *ClanCreate) SetNillableUpdatedAt(t *time.Time) *ClanCreate { + if t != nil { + cc.SetUpdatedAt(*t) } return cc } @@ -213,11 +214,11 @@ func (cc *ClanCreate) createSpec() (*Clan, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := cc.mutation.CreatedAt(); ok { - _spec.SetField(clan.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(clan.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := cc.mutation.UpdatedAt(); ok { - _spec.SetField(clan.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(clan.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := cc.mutation.Tag(); ok { diff --git a/internal/database/ent/db/clan_query.go b/internal/database/ent/db/clan_query.go index 76649a32..046e2ed9 100644 --- a/internal/database/ent/db/clan_query.go +++ b/internal/database/ent/db/clan_query.go @@ -24,6 +24,7 @@ type ClanQuery struct { inters []Interceptor predicates []predicate.Clan withAccounts *AccountQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -298,7 +299,7 @@ func (cq *ClanQuery) WithAccounts(opts ...func(*AccountQuery)) *ClanQuery { // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -321,7 +322,7 @@ func (cq *ClanQuery) GroupBy(field string, fields ...string) *ClanGroupBy { // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.Clan.Query(). @@ -383,6 +384,9 @@ func (cq *ClanQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Clan, e node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(cq.modifiers) > 0 { + _spec.Modifiers = cq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -435,6 +439,9 @@ func (cq *ClanQuery) loadAccounts(ctx context.Context, query *AccountQuery, node func (cq *ClanQuery) sqlCount(ctx context.Context) (int, error) { _spec := cq.querySpec() + if len(cq.modifiers) > 0 { + _spec.Modifiers = cq.modifiers + } _spec.Node.Columns = cq.ctx.Fields if len(cq.ctx.Fields) > 0 { _spec.Unique = cq.ctx.Unique != nil && *cq.ctx.Unique @@ -497,6 +504,9 @@ func (cq *ClanQuery) sqlQuery(ctx context.Context) *sql.Selector { if cq.ctx.Unique != nil && *cq.ctx.Unique { selector.Distinct() } + for _, m := range cq.modifiers { + m(selector) + } for _, p := range cq.predicates { p(selector) } @@ -514,6 +524,12 @@ func (cq *ClanQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (cq *ClanQuery) Modify(modifiers ...func(s *sql.Selector)) *ClanSelect { + cq.modifiers = append(cq.modifiers, modifiers...) + return cq.Select() +} + // ClanGroupBy is the group-by builder for Clan entities. type ClanGroupBy struct { selector @@ -603,3 +619,9 @@ func (cs *ClanSelect) sqlScan(ctx context.Context, root *ClanQuery, v any) error defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (cs *ClanSelect) Modify(modifiers ...func(s *sql.Selector)) *ClanSelect { + cs.modifiers = append(cs.modifiers, modifiers...) + return cs +} diff --git a/internal/database/ent/db/clan_update.go b/internal/database/ent/db/clan_update.go index 8a5ae595..f291ac89 100644 --- a/internal/database/ent/db/clan_update.go +++ b/internal/database/ent/db/clan_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -19,8 +20,9 @@ import ( // ClanUpdate is the builder for updating Clan entities. type ClanUpdate struct { config - hooks []Hook - mutation *ClanMutation + hooks []Hook + mutation *ClanMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the ClanUpdate builder. @@ -30,15 +32,8 @@ func (cu *ClanUpdate) Where(ps ...predicate.Clan) *ClanUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (cu *ClanUpdate) SetUpdatedAt(i int64) *ClanUpdate { - cu.mutation.ResetUpdatedAt() - cu.mutation.SetUpdatedAt(i) - return cu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (cu *ClanUpdate) AddUpdatedAt(i int64) *ClanUpdate { - cu.mutation.AddUpdatedAt(i) +func (cu *ClanUpdate) SetUpdatedAt(t time.Time) *ClanUpdate { + cu.mutation.SetUpdatedAt(t) return cu } @@ -194,6 +189,12 @@ func (cu *ClanUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (cu *ClanUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *ClanUpdate { + cu.modifiers = append(cu.modifiers, modifiers...) + return cu +} + func (cu *ClanUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := cu.check(); err != nil { return n, err @@ -207,10 +208,7 @@ func (cu *ClanUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := cu.mutation.UpdatedAt(); ok { - _spec.SetField(clan.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := cu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(clan.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(clan.FieldUpdatedAt, field.TypeTime, value) } if value, ok := cu.mutation.Tag(); ok { _spec.SetField(clan.FieldTag, field.TypeString, value) @@ -277,6 +275,7 @@ func (cu *ClanUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + _spec.AddModifiers(cu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, cu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{clan.Label} @@ -292,21 +291,15 @@ func (cu *ClanUpdate) sqlSave(ctx context.Context) (n int, err error) { // ClanUpdateOne is the builder for updating a single Clan entity. type ClanUpdateOne struct { config - fields []string - hooks []Hook - mutation *ClanMutation + fields []string + hooks []Hook + mutation *ClanMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (cuo *ClanUpdateOne) SetUpdatedAt(i int64) *ClanUpdateOne { - cuo.mutation.ResetUpdatedAt() - cuo.mutation.SetUpdatedAt(i) - return cuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (cuo *ClanUpdateOne) AddUpdatedAt(i int64) *ClanUpdateOne { - cuo.mutation.AddUpdatedAt(i) +func (cuo *ClanUpdateOne) SetUpdatedAt(t time.Time) *ClanUpdateOne { + cuo.mutation.SetUpdatedAt(t) return cuo } @@ -475,6 +468,12 @@ func (cuo *ClanUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (cuo *ClanUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *ClanUpdateOne { + cuo.modifiers = append(cuo.modifiers, modifiers...) + return cuo +} + func (cuo *ClanUpdateOne) sqlSave(ctx context.Context) (_node *Clan, err error) { if err := cuo.check(); err != nil { return _node, err @@ -505,10 +504,7 @@ func (cuo *ClanUpdateOne) sqlSave(ctx context.Context) (_node *Clan, err error) } } if value, ok := cuo.mutation.UpdatedAt(); ok { - _spec.SetField(clan.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := cuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(clan.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(clan.FieldUpdatedAt, field.TypeTime, value) } if value, ok := cuo.mutation.Tag(); ok { _spec.SetField(clan.FieldTag, field.TypeString, value) @@ -575,6 +571,7 @@ func (cuo *ClanUpdateOne) sqlSave(ctx context.Context) (_node *Clan, err error) } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + _spec.AddModifiers(cuo.modifiers...) _node = &Clan{config: cuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/crontask.go b/internal/database/ent/db/crontask.go index c4ba99dd..dcc845a5 100644 --- a/internal/database/ent/db/crontask.go +++ b/internal/database/ent/db/crontask.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -19,9 +20,9 @@ type CronTask struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.TaskType `json:"type,omitempty"` // ReferenceID holds the value of the "reference_id" field. @@ -31,9 +32,9 @@ type CronTask struct { // Status holds the value of the "status" field. Status models.TaskStatus `json:"status,omitempty"` // ScheduledAfter holds the value of the "scheduled_after" field. - ScheduledAfter int64 `json:"scheduled_after,omitempty"` + ScheduledAfter time.Time `json:"scheduled_after,omitempty"` // LastRun holds the value of the "last_run" field. - LastRun int64 `json:"last_run,omitempty"` + LastRun time.Time `json:"last_run,omitempty"` // Logs holds the value of the "logs" field. Logs []models.TaskLog `json:"logs,omitempty"` // Data holds the value of the "data" field. @@ -48,10 +49,10 @@ func (*CronTask) scanValues(columns []string) ([]any, error) { switch columns[i] { case crontask.FieldTargets, crontask.FieldLogs, crontask.FieldData: values[i] = new([]byte) - case crontask.FieldCreatedAt, crontask.FieldUpdatedAt, crontask.FieldScheduledAfter, crontask.FieldLastRun: - values[i] = new(sql.NullInt64) case crontask.FieldID, crontask.FieldType, crontask.FieldReferenceID, crontask.FieldStatus: values[i] = new(sql.NullString) + case crontask.FieldCreatedAt, crontask.FieldUpdatedAt, crontask.FieldScheduledAfter, crontask.FieldLastRun: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -74,16 +75,16 @@ func (ct *CronTask) assignValues(columns []string, values []any) error { ct.ID = value.String } case crontask.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - ct.CreatedAt = value.Int64 + ct.CreatedAt = value.Time } case crontask.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - ct.UpdatedAt = value.Int64 + ct.UpdatedAt = value.Time } case crontask.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -112,16 +113,16 @@ func (ct *CronTask) assignValues(columns []string, values []any) error { ct.Status = models.TaskStatus(value.String) } case crontask.FieldScheduledAfter: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field scheduled_after", values[i]) } else if value.Valid { - ct.ScheduledAfter = value.Int64 + ct.ScheduledAfter = value.Time } case crontask.FieldLastRun: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field last_run", values[i]) } else if value.Valid { - ct.LastRun = value.Int64 + ct.LastRun = value.Time } case crontask.FieldLogs: if value, ok := values[i].(*[]byte); !ok { @@ -176,10 +177,10 @@ func (ct *CronTask) String() string { builder.WriteString("CronTask(") builder.WriteString(fmt.Sprintf("id=%v, ", ct.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", ct.CreatedAt)) + builder.WriteString(ct.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", ct.UpdatedAt)) + builder.WriteString(ct.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("type=") builder.WriteString(fmt.Sprintf("%v", ct.Type)) @@ -194,10 +195,10 @@ func (ct *CronTask) String() string { builder.WriteString(fmt.Sprintf("%v", ct.Status)) builder.WriteString(", ") builder.WriteString("scheduled_after=") - builder.WriteString(fmt.Sprintf("%v", ct.ScheduledAfter)) + builder.WriteString(ct.ScheduledAfter.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("last_run=") - builder.WriteString(fmt.Sprintf("%v", ct.LastRun)) + builder.WriteString(ct.LastRun.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("logs=") builder.WriteString(fmt.Sprintf("%v", ct.Logs)) diff --git a/internal/database/ent/db/crontask/crontask.go b/internal/database/ent/db/crontask/crontask.go index c83a8067..1e2f95ea 100644 --- a/internal/database/ent/db/crontask/crontask.go +++ b/internal/database/ent/db/crontask/crontask.go @@ -4,6 +4,7 @@ package crontask import ( "fmt" + "time" "entgo.io/ent/dialect/sql" "github.com/cufee/aftermath/internal/database/models" @@ -65,11 +66,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. ReferenceIDValidator func(string) error // DefaultID holds the default value on creation for the "id" field. diff --git a/internal/database/ent/db/crontask/where.go b/internal/database/ent/db/crontask/where.go index 3eb15146..3f2568ff 100644 --- a/internal/database/ent/db/crontask/where.go +++ b/internal/database/ent/db/crontask/where.go @@ -3,6 +3,8 @@ package crontask import ( + "time" + "entgo.io/ent/dialect/sql" "github.com/cufee/aftermath/internal/database/ent/db/predicate" "github.com/cufee/aftermath/internal/database/models" @@ -64,12 +66,12 @@ func IDContainsFold(id string) predicate.CronTask { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.CronTask { +func CreatedAt(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.CronTask { +func UpdatedAt(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -79,92 +81,92 @@ func ReferenceID(v string) predicate.CronTask { } // ScheduledAfter applies equality check predicate on the "scheduled_after" field. It's identical to ScheduledAfterEQ. -func ScheduledAfter(v int64) predicate.CronTask { +func ScheduledAfter(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldScheduledAfter, v)) } // LastRun applies equality check predicate on the "last_run" field. It's identical to LastRunEQ. -func LastRun(v int64) predicate.CronTask { +func LastRun(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldLastRun, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.CronTask { +func CreatedAtEQ(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.CronTask { +func CreatedAtNEQ(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.CronTask { +func CreatedAtIn(vs ...time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.CronTask { +func CreatedAtNotIn(vs ...time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.CronTask { +func CreatedAtGT(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.CronTask { +func CreatedAtGTE(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.CronTask { +func CreatedAtLT(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.CronTask { +func CreatedAtLTE(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.CronTask { +func UpdatedAtEQ(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.CronTask { +func UpdatedAtNEQ(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.CronTask { +func UpdatedAtIn(vs ...time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.CronTask { +func UpdatedAtNotIn(vs ...time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.CronTask { +func UpdatedAtGT(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.CronTask { +func UpdatedAtGTE(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.CronTask { +func UpdatedAtLT(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.CronTask { +func UpdatedAtLTE(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldLTE(FieldUpdatedAt, v)) } @@ -294,82 +296,82 @@ func StatusNotIn(vs ...models.TaskStatus) predicate.CronTask { } // ScheduledAfterEQ applies the EQ predicate on the "scheduled_after" field. -func ScheduledAfterEQ(v int64) predicate.CronTask { +func ScheduledAfterEQ(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldScheduledAfter, v)) } // ScheduledAfterNEQ applies the NEQ predicate on the "scheduled_after" field. -func ScheduledAfterNEQ(v int64) predicate.CronTask { +func ScheduledAfterNEQ(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldNEQ(FieldScheduledAfter, v)) } // ScheduledAfterIn applies the In predicate on the "scheduled_after" field. -func ScheduledAfterIn(vs ...int64) predicate.CronTask { +func ScheduledAfterIn(vs ...time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldIn(FieldScheduledAfter, vs...)) } // ScheduledAfterNotIn applies the NotIn predicate on the "scheduled_after" field. -func ScheduledAfterNotIn(vs ...int64) predicate.CronTask { +func ScheduledAfterNotIn(vs ...time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldNotIn(FieldScheduledAfter, vs...)) } // ScheduledAfterGT applies the GT predicate on the "scheduled_after" field. -func ScheduledAfterGT(v int64) predicate.CronTask { +func ScheduledAfterGT(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldGT(FieldScheduledAfter, v)) } // ScheduledAfterGTE applies the GTE predicate on the "scheduled_after" field. -func ScheduledAfterGTE(v int64) predicate.CronTask { +func ScheduledAfterGTE(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldGTE(FieldScheduledAfter, v)) } // ScheduledAfterLT applies the LT predicate on the "scheduled_after" field. -func ScheduledAfterLT(v int64) predicate.CronTask { +func ScheduledAfterLT(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldLT(FieldScheduledAfter, v)) } // ScheduledAfterLTE applies the LTE predicate on the "scheduled_after" field. -func ScheduledAfterLTE(v int64) predicate.CronTask { +func ScheduledAfterLTE(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldLTE(FieldScheduledAfter, v)) } // LastRunEQ applies the EQ predicate on the "last_run" field. -func LastRunEQ(v int64) predicate.CronTask { +func LastRunEQ(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldLastRun, v)) } // LastRunNEQ applies the NEQ predicate on the "last_run" field. -func LastRunNEQ(v int64) predicate.CronTask { +func LastRunNEQ(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldNEQ(FieldLastRun, v)) } // LastRunIn applies the In predicate on the "last_run" field. -func LastRunIn(vs ...int64) predicate.CronTask { +func LastRunIn(vs ...time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldIn(FieldLastRun, vs...)) } // LastRunNotIn applies the NotIn predicate on the "last_run" field. -func LastRunNotIn(vs ...int64) predicate.CronTask { +func LastRunNotIn(vs ...time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldNotIn(FieldLastRun, vs...)) } // LastRunGT applies the GT predicate on the "last_run" field. -func LastRunGT(v int64) predicate.CronTask { +func LastRunGT(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldGT(FieldLastRun, v)) } // LastRunGTE applies the GTE predicate on the "last_run" field. -func LastRunGTE(v int64) predicate.CronTask { +func LastRunGTE(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldGTE(FieldLastRun, v)) } // LastRunLT applies the LT predicate on the "last_run" field. -func LastRunLT(v int64) predicate.CronTask { +func LastRunLT(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldLT(FieldLastRun, v)) } // LastRunLTE applies the LTE predicate on the "last_run" field. -func LastRunLTE(v int64) predicate.CronTask { +func LastRunLTE(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldLTE(FieldLastRun, v)) } diff --git a/internal/database/ent/db/crontask_create.go b/internal/database/ent/db/crontask_create.go index 78feb06e..70343d77 100644 --- a/internal/database/ent/db/crontask_create.go +++ b/internal/database/ent/db/crontask_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -21,29 +22,29 @@ type CronTaskCreate struct { } // SetCreatedAt sets the "created_at" field. -func (ctc *CronTaskCreate) SetCreatedAt(i int64) *CronTaskCreate { - ctc.mutation.SetCreatedAt(i) +func (ctc *CronTaskCreate) SetCreatedAt(t time.Time) *CronTaskCreate { + ctc.mutation.SetCreatedAt(t) return ctc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (ctc *CronTaskCreate) SetNillableCreatedAt(i *int64) *CronTaskCreate { - if i != nil { - ctc.SetCreatedAt(*i) +func (ctc *CronTaskCreate) SetNillableCreatedAt(t *time.Time) *CronTaskCreate { + if t != nil { + ctc.SetCreatedAt(*t) } return ctc } // SetUpdatedAt sets the "updated_at" field. -func (ctc *CronTaskCreate) SetUpdatedAt(i int64) *CronTaskCreate { - ctc.mutation.SetUpdatedAt(i) +func (ctc *CronTaskCreate) SetUpdatedAt(t time.Time) *CronTaskCreate { + ctc.mutation.SetUpdatedAt(t) return ctc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (ctc *CronTaskCreate) SetNillableUpdatedAt(i *int64) *CronTaskCreate { - if i != nil { - ctc.SetUpdatedAt(*i) +func (ctc *CronTaskCreate) SetNillableUpdatedAt(t *time.Time) *CronTaskCreate { + if t != nil { + ctc.SetUpdatedAt(*t) } return ctc } @@ -73,14 +74,14 @@ func (ctc *CronTaskCreate) SetStatus(ms models.TaskStatus) *CronTaskCreate { } // SetScheduledAfter sets the "scheduled_after" field. -func (ctc *CronTaskCreate) SetScheduledAfter(i int64) *CronTaskCreate { - ctc.mutation.SetScheduledAfter(i) +func (ctc *CronTaskCreate) SetScheduledAfter(t time.Time) *CronTaskCreate { + ctc.mutation.SetScheduledAfter(t) return ctc } // SetLastRun sets the "last_run" field. -func (ctc *CronTaskCreate) SetLastRun(i int64) *CronTaskCreate { - ctc.mutation.SetLastRun(i) +func (ctc *CronTaskCreate) SetLastRun(t time.Time) *CronTaskCreate { + ctc.mutation.SetLastRun(t) return ctc } @@ -242,11 +243,11 @@ func (ctc *CronTaskCreate) createSpec() (*CronTask, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := ctc.mutation.CreatedAt(); ok { - _spec.SetField(crontask.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(crontask.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := ctc.mutation.UpdatedAt(); ok { - _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(crontask.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := ctc.mutation.GetType(); ok { @@ -266,11 +267,11 @@ func (ctc *CronTaskCreate) createSpec() (*CronTask, *sqlgraph.CreateSpec) { _node.Status = value } if value, ok := ctc.mutation.ScheduledAfter(); ok { - _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt64, value) + _spec.SetField(crontask.FieldScheduledAfter, field.TypeTime, value) _node.ScheduledAfter = value } if value, ok := ctc.mutation.LastRun(); ok { - _spec.SetField(crontask.FieldLastRun, field.TypeInt64, value) + _spec.SetField(crontask.FieldLastRun, field.TypeTime, value) _node.LastRun = value } if value, ok := ctc.mutation.Logs(); ok { diff --git a/internal/database/ent/db/crontask_query.go b/internal/database/ent/db/crontask_query.go index 27b8ca39..a8700af9 100644 --- a/internal/database/ent/db/crontask_query.go +++ b/internal/database/ent/db/crontask_query.go @@ -21,6 +21,7 @@ type CronTaskQuery struct { order []crontask.OrderOption inters []Interceptor predicates []predicate.CronTask + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -261,7 +262,7 @@ func (ctq *CronTaskQuery) Clone() *CronTaskQuery { // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +285,7 @@ func (ctq *CronTaskQuery) GroupBy(field string, fields ...string) *CronTaskGroup // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.CronTask.Query(). @@ -342,6 +343,9 @@ func (ctq *CronTaskQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Cr nodes = append(nodes, node) return node.assignValues(columns, values) } + if len(ctq.modifiers) > 0 { + _spec.Modifiers = ctq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -356,6 +360,9 @@ func (ctq *CronTaskQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Cr func (ctq *CronTaskQuery) sqlCount(ctx context.Context) (int, error) { _spec := ctq.querySpec() + if len(ctq.modifiers) > 0 { + _spec.Modifiers = ctq.modifiers + } _spec.Node.Columns = ctq.ctx.Fields if len(ctq.ctx.Fields) > 0 { _spec.Unique = ctq.ctx.Unique != nil && *ctq.ctx.Unique @@ -418,6 +425,9 @@ func (ctq *CronTaskQuery) sqlQuery(ctx context.Context) *sql.Selector { if ctq.ctx.Unique != nil && *ctq.ctx.Unique { selector.Distinct() } + for _, m := range ctq.modifiers { + m(selector) + } for _, p := range ctq.predicates { p(selector) } @@ -435,6 +445,12 @@ func (ctq *CronTaskQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (ctq *CronTaskQuery) Modify(modifiers ...func(s *sql.Selector)) *CronTaskSelect { + ctq.modifiers = append(ctq.modifiers, modifiers...) + return ctq.Select() +} + // CronTaskGroupBy is the group-by builder for CronTask entities. type CronTaskGroupBy struct { selector @@ -524,3 +540,9 @@ func (cts *CronTaskSelect) sqlScan(ctx context.Context, root *CronTaskQuery, v a defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (cts *CronTaskSelect) Modify(modifiers ...func(s *sql.Selector)) *CronTaskSelect { + cts.modifiers = append(cts.modifiers, modifiers...) + return cts +} diff --git a/internal/database/ent/db/crontask_update.go b/internal/database/ent/db/crontask_update.go index ddbf9627..5565a00b 100644 --- a/internal/database/ent/db/crontask_update.go +++ b/internal/database/ent/db/crontask_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -19,8 +20,9 @@ import ( // CronTaskUpdate is the builder for updating CronTask entities. type CronTaskUpdate struct { config - hooks []Hook - mutation *CronTaskMutation + hooks []Hook + mutation *CronTaskMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the CronTaskUpdate builder. @@ -30,15 +32,8 @@ func (ctu *CronTaskUpdate) Where(ps ...predicate.CronTask) *CronTaskUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (ctu *CronTaskUpdate) SetUpdatedAt(i int64) *CronTaskUpdate { - ctu.mutation.ResetUpdatedAt() - ctu.mutation.SetUpdatedAt(i) - return ctu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (ctu *CronTaskUpdate) AddUpdatedAt(i int64) *CronTaskUpdate { - ctu.mutation.AddUpdatedAt(i) +func (ctu *CronTaskUpdate) SetUpdatedAt(t time.Time) *CronTaskUpdate { + ctu.mutation.SetUpdatedAt(t) return ctu } @@ -97,47 +92,33 @@ func (ctu *CronTaskUpdate) SetNillableStatus(ms *models.TaskStatus) *CronTaskUpd } // SetScheduledAfter sets the "scheduled_after" field. -func (ctu *CronTaskUpdate) SetScheduledAfter(i int64) *CronTaskUpdate { - ctu.mutation.ResetScheduledAfter() - ctu.mutation.SetScheduledAfter(i) +func (ctu *CronTaskUpdate) SetScheduledAfter(t time.Time) *CronTaskUpdate { + ctu.mutation.SetScheduledAfter(t) return ctu } // SetNillableScheduledAfter sets the "scheduled_after" field if the given value is not nil. -func (ctu *CronTaskUpdate) SetNillableScheduledAfter(i *int64) *CronTaskUpdate { - if i != nil { - ctu.SetScheduledAfter(*i) +func (ctu *CronTaskUpdate) SetNillableScheduledAfter(t *time.Time) *CronTaskUpdate { + if t != nil { + ctu.SetScheduledAfter(*t) } return ctu } -// AddScheduledAfter adds i to the "scheduled_after" field. -func (ctu *CronTaskUpdate) AddScheduledAfter(i int64) *CronTaskUpdate { - ctu.mutation.AddScheduledAfter(i) - return ctu -} - // SetLastRun sets the "last_run" field. -func (ctu *CronTaskUpdate) SetLastRun(i int64) *CronTaskUpdate { - ctu.mutation.ResetLastRun() - ctu.mutation.SetLastRun(i) +func (ctu *CronTaskUpdate) SetLastRun(t time.Time) *CronTaskUpdate { + ctu.mutation.SetLastRun(t) return ctu } // SetNillableLastRun sets the "last_run" field if the given value is not nil. -func (ctu *CronTaskUpdate) SetNillableLastRun(i *int64) *CronTaskUpdate { - if i != nil { - ctu.SetLastRun(*i) +func (ctu *CronTaskUpdate) SetNillableLastRun(t *time.Time) *CronTaskUpdate { + if t != nil { + ctu.SetLastRun(*t) } return ctu } -// AddLastRun adds i to the "last_run" field. -func (ctu *CronTaskUpdate) AddLastRun(i int64) *CronTaskUpdate { - ctu.mutation.AddLastRun(i) - return ctu -} - // SetLogs sets the "logs" field. func (ctu *CronTaskUpdate) SetLogs(ml []models.TaskLog) *CronTaskUpdate { ctu.mutation.SetLogs(ml) @@ -217,6 +198,12 @@ func (ctu *CronTaskUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (ctu *CronTaskUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *CronTaskUpdate { + ctu.modifiers = append(ctu.modifiers, modifiers...) + return ctu +} + func (ctu *CronTaskUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := ctu.check(); err != nil { return n, err @@ -230,10 +217,7 @@ func (ctu *CronTaskUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := ctu.mutation.UpdatedAt(); ok { - _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := ctu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(crontask.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(crontask.FieldUpdatedAt, field.TypeTime, value) } if value, ok := ctu.mutation.GetType(); ok { _spec.SetField(crontask.FieldType, field.TypeEnum, value) @@ -253,16 +237,10 @@ func (ctu *CronTaskUpdate) sqlSave(ctx context.Context) (n int, err error) { _spec.SetField(crontask.FieldStatus, field.TypeEnum, value) } if value, ok := ctu.mutation.ScheduledAfter(); ok { - _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt64, value) - } - if value, ok := ctu.mutation.AddedScheduledAfter(); ok { - _spec.AddField(crontask.FieldScheduledAfter, field.TypeInt64, value) + _spec.SetField(crontask.FieldScheduledAfter, field.TypeTime, value) } if value, ok := ctu.mutation.LastRun(); ok { - _spec.SetField(crontask.FieldLastRun, field.TypeInt64, value) - } - if value, ok := ctu.mutation.AddedLastRun(); ok { - _spec.AddField(crontask.FieldLastRun, field.TypeInt64, value) + _spec.SetField(crontask.FieldLastRun, field.TypeTime, value) } if value, ok := ctu.mutation.Logs(); ok { _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) @@ -275,6 +253,7 @@ func (ctu *CronTaskUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := ctu.mutation.Data(); ok { _spec.SetField(crontask.FieldData, field.TypeJSON, value) } + _spec.AddModifiers(ctu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, ctu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{crontask.Label} @@ -290,21 +269,15 @@ func (ctu *CronTaskUpdate) sqlSave(ctx context.Context) (n int, err error) { // CronTaskUpdateOne is the builder for updating a single CronTask entity. type CronTaskUpdateOne struct { config - fields []string - hooks []Hook - mutation *CronTaskMutation + fields []string + hooks []Hook + mutation *CronTaskMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (ctuo *CronTaskUpdateOne) SetUpdatedAt(i int64) *CronTaskUpdateOne { - ctuo.mutation.ResetUpdatedAt() - ctuo.mutation.SetUpdatedAt(i) - return ctuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (ctuo *CronTaskUpdateOne) AddUpdatedAt(i int64) *CronTaskUpdateOne { - ctuo.mutation.AddUpdatedAt(i) +func (ctuo *CronTaskUpdateOne) SetUpdatedAt(t time.Time) *CronTaskUpdateOne { + ctuo.mutation.SetUpdatedAt(t) return ctuo } @@ -363,47 +336,33 @@ func (ctuo *CronTaskUpdateOne) SetNillableStatus(ms *models.TaskStatus) *CronTas } // SetScheduledAfter sets the "scheduled_after" field. -func (ctuo *CronTaskUpdateOne) SetScheduledAfter(i int64) *CronTaskUpdateOne { - ctuo.mutation.ResetScheduledAfter() - ctuo.mutation.SetScheduledAfter(i) +func (ctuo *CronTaskUpdateOne) SetScheduledAfter(t time.Time) *CronTaskUpdateOne { + ctuo.mutation.SetScheduledAfter(t) return ctuo } // SetNillableScheduledAfter sets the "scheduled_after" field if the given value is not nil. -func (ctuo *CronTaskUpdateOne) SetNillableScheduledAfter(i *int64) *CronTaskUpdateOne { - if i != nil { - ctuo.SetScheduledAfter(*i) +func (ctuo *CronTaskUpdateOne) SetNillableScheduledAfter(t *time.Time) *CronTaskUpdateOne { + if t != nil { + ctuo.SetScheduledAfter(*t) } return ctuo } -// AddScheduledAfter adds i to the "scheduled_after" field. -func (ctuo *CronTaskUpdateOne) AddScheduledAfter(i int64) *CronTaskUpdateOne { - ctuo.mutation.AddScheduledAfter(i) - return ctuo -} - // SetLastRun sets the "last_run" field. -func (ctuo *CronTaskUpdateOne) SetLastRun(i int64) *CronTaskUpdateOne { - ctuo.mutation.ResetLastRun() - ctuo.mutation.SetLastRun(i) +func (ctuo *CronTaskUpdateOne) SetLastRun(t time.Time) *CronTaskUpdateOne { + ctuo.mutation.SetLastRun(t) return ctuo } // SetNillableLastRun sets the "last_run" field if the given value is not nil. -func (ctuo *CronTaskUpdateOne) SetNillableLastRun(i *int64) *CronTaskUpdateOne { - if i != nil { - ctuo.SetLastRun(*i) +func (ctuo *CronTaskUpdateOne) SetNillableLastRun(t *time.Time) *CronTaskUpdateOne { + if t != nil { + ctuo.SetLastRun(*t) } return ctuo } -// AddLastRun adds i to the "last_run" field. -func (ctuo *CronTaskUpdateOne) AddLastRun(i int64) *CronTaskUpdateOne { - ctuo.mutation.AddLastRun(i) - return ctuo -} - // SetLogs sets the "logs" field. func (ctuo *CronTaskUpdateOne) SetLogs(ml []models.TaskLog) *CronTaskUpdateOne { ctuo.mutation.SetLogs(ml) @@ -496,6 +455,12 @@ func (ctuo *CronTaskUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (ctuo *CronTaskUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *CronTaskUpdateOne { + ctuo.modifiers = append(ctuo.modifiers, modifiers...) + return ctuo +} + func (ctuo *CronTaskUpdateOne) sqlSave(ctx context.Context) (_node *CronTask, err error) { if err := ctuo.check(); err != nil { return _node, err @@ -526,10 +491,7 @@ func (ctuo *CronTaskUpdateOne) sqlSave(ctx context.Context) (_node *CronTask, er } } if value, ok := ctuo.mutation.UpdatedAt(); ok { - _spec.SetField(crontask.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := ctuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(crontask.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(crontask.FieldUpdatedAt, field.TypeTime, value) } if value, ok := ctuo.mutation.GetType(); ok { _spec.SetField(crontask.FieldType, field.TypeEnum, value) @@ -549,16 +511,10 @@ func (ctuo *CronTaskUpdateOne) sqlSave(ctx context.Context) (_node *CronTask, er _spec.SetField(crontask.FieldStatus, field.TypeEnum, value) } if value, ok := ctuo.mutation.ScheduledAfter(); ok { - _spec.SetField(crontask.FieldScheduledAfter, field.TypeInt64, value) - } - if value, ok := ctuo.mutation.AddedScheduledAfter(); ok { - _spec.AddField(crontask.FieldScheduledAfter, field.TypeInt64, value) + _spec.SetField(crontask.FieldScheduledAfter, field.TypeTime, value) } if value, ok := ctuo.mutation.LastRun(); ok { - _spec.SetField(crontask.FieldLastRun, field.TypeInt64, value) - } - if value, ok := ctuo.mutation.AddedLastRun(); ok { - _spec.AddField(crontask.FieldLastRun, field.TypeInt64, value) + _spec.SetField(crontask.FieldLastRun, field.TypeTime, value) } if value, ok := ctuo.mutation.Logs(); ok { _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) @@ -571,6 +527,7 @@ func (ctuo *CronTaskUpdateOne) sqlSave(ctx context.Context) (_node *CronTask, er if value, ok := ctuo.mutation.Data(); ok { _spec.SetField(crontask.FieldData, field.TypeJSON, value) } + _spec.AddModifiers(ctuo.modifiers...) _node = &CronTask{config: ctuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/discordinteraction.go b/internal/database/ent/db/discordinteraction.go index 9ac0f690..3e28cba8 100644 --- a/internal/database/ent/db/discordinteraction.go +++ b/internal/database/ent/db/discordinteraction.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -20,9 +21,9 @@ type DiscordInteraction struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Command holds the value of the "command" field. Command string `json:"command,omitempty"` // UserID holds the value of the "user_id" field. @@ -68,10 +69,10 @@ func (*DiscordInteraction) scanValues(columns []string) ([]any, error) { switch columns[i] { case discordinteraction.FieldOptions: values[i] = new([]byte) - case discordinteraction.FieldCreatedAt, discordinteraction.FieldUpdatedAt: - values[i] = new(sql.NullInt64) case discordinteraction.FieldID, discordinteraction.FieldCommand, discordinteraction.FieldUserID, discordinteraction.FieldReferenceID, discordinteraction.FieldType, discordinteraction.FieldLocale: values[i] = new(sql.NullString) + case discordinteraction.FieldCreatedAt, discordinteraction.FieldUpdatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -94,16 +95,16 @@ func (di *DiscordInteraction) assignValues(columns []string, values []any) error di.ID = value.String } case discordinteraction.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - di.CreatedAt = value.Int64 + di.CreatedAt = value.Time } case discordinteraction.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - di.UpdatedAt = value.Int64 + di.UpdatedAt = value.Time } case discordinteraction.FieldCommand: if value, ok := values[i].(*sql.NullString); !ok { @@ -185,10 +186,10 @@ func (di *DiscordInteraction) String() string { builder.WriteString("DiscordInteraction(") builder.WriteString(fmt.Sprintf("id=%v, ", di.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", di.CreatedAt)) + builder.WriteString(di.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", di.UpdatedAt)) + builder.WriteString(di.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("command=") builder.WriteString(di.Command) diff --git a/internal/database/ent/db/discordinteraction/discordinteraction.go b/internal/database/ent/db/discordinteraction/discordinteraction.go index 3bad3cbe..7891a96c 100644 --- a/internal/database/ent/db/discordinteraction/discordinteraction.go +++ b/internal/database/ent/db/discordinteraction/discordinteraction.go @@ -4,6 +4,7 @@ package discordinteraction import ( "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -69,11 +70,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // CommandValidator is a validator for the "command" field. It is called by the builders before save. CommandValidator func(string) error // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. diff --git a/internal/database/ent/db/discordinteraction/where.go b/internal/database/ent/db/discordinteraction/where.go index 1b38d92e..bd219db5 100644 --- a/internal/database/ent/db/discordinteraction/where.go +++ b/internal/database/ent/db/discordinteraction/where.go @@ -3,6 +3,8 @@ package discordinteraction import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -65,12 +67,12 @@ func IDContainsFold(id string) predicate.DiscordInteraction { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.DiscordInteraction { +func CreatedAt(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.DiscordInteraction { +func UpdatedAt(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -95,82 +97,82 @@ func Locale(v string) predicate.DiscordInteraction { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.DiscordInteraction { +func CreatedAtEQ(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.DiscordInteraction { +func CreatedAtNEQ(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.DiscordInteraction { +func CreatedAtIn(vs ...time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.DiscordInteraction { +func CreatedAtNotIn(vs ...time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.DiscordInteraction { +func CreatedAtGT(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.DiscordInteraction { +func CreatedAtGTE(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.DiscordInteraction { +func CreatedAtLT(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.DiscordInteraction { +func CreatedAtLTE(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.DiscordInteraction { +func UpdatedAtEQ(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.DiscordInteraction { +func UpdatedAtNEQ(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.DiscordInteraction { +func UpdatedAtIn(vs ...time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.DiscordInteraction { +func UpdatedAtNotIn(vs ...time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.DiscordInteraction { +func UpdatedAtGT(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.DiscordInteraction { +func UpdatedAtGTE(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.DiscordInteraction { +func UpdatedAtLT(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.DiscordInteraction { +func UpdatedAtLTE(v time.Time) predicate.DiscordInteraction { return predicate.DiscordInteraction(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/discordinteraction_create.go b/internal/database/ent/db/discordinteraction_create.go index 45583113..97f37791 100644 --- a/internal/database/ent/db/discordinteraction_create.go +++ b/internal/database/ent/db/discordinteraction_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -22,29 +23,29 @@ type DiscordInteractionCreate struct { } // SetCreatedAt sets the "created_at" field. -func (dic *DiscordInteractionCreate) SetCreatedAt(i int64) *DiscordInteractionCreate { - dic.mutation.SetCreatedAt(i) +func (dic *DiscordInteractionCreate) SetCreatedAt(t time.Time) *DiscordInteractionCreate { + dic.mutation.SetCreatedAt(t) return dic } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (dic *DiscordInteractionCreate) SetNillableCreatedAt(i *int64) *DiscordInteractionCreate { - if i != nil { - dic.SetCreatedAt(*i) +func (dic *DiscordInteractionCreate) SetNillableCreatedAt(t *time.Time) *DiscordInteractionCreate { + if t != nil { + dic.SetCreatedAt(*t) } return dic } // SetUpdatedAt sets the "updated_at" field. -func (dic *DiscordInteractionCreate) SetUpdatedAt(i int64) *DiscordInteractionCreate { - dic.mutation.SetUpdatedAt(i) +func (dic *DiscordInteractionCreate) SetUpdatedAt(t time.Time) *DiscordInteractionCreate { + dic.mutation.SetUpdatedAt(t) return dic } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (dic *DiscordInteractionCreate) SetNillableUpdatedAt(i *int64) *DiscordInteractionCreate { - if i != nil { - dic.SetUpdatedAt(*i) +func (dic *DiscordInteractionCreate) SetNillableUpdatedAt(t *time.Time) *DiscordInteractionCreate { + if t != nil { + dic.SetUpdatedAt(*t) } return dic } @@ -238,11 +239,11 @@ func (dic *DiscordInteractionCreate) createSpec() (*DiscordInteraction, *sqlgrap _spec.ID.Value = id } if value, ok := dic.mutation.CreatedAt(); ok { - _spec.SetField(discordinteraction.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(discordinteraction.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := dic.mutation.UpdatedAt(); ok { - _spec.SetField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(discordinteraction.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := dic.mutation.Command(); ok { diff --git a/internal/database/ent/db/discordinteraction_query.go b/internal/database/ent/db/discordinteraction_query.go index bd085984..54ded610 100644 --- a/internal/database/ent/db/discordinteraction_query.go +++ b/internal/database/ent/db/discordinteraction_query.go @@ -23,6 +23,7 @@ type DiscordInteractionQuery struct { inters []Interceptor predicates []predicate.DiscordInteraction withUser *UserQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -297,7 +298,7 @@ func (diq *DiscordInteractionQuery) WithUser(opts ...func(*UserQuery)) *DiscordI // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +321,7 @@ func (diq *DiscordInteractionQuery) GroupBy(field string, fields ...string) *Dis // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.DiscordInteraction.Query(). @@ -382,6 +383,9 @@ func (diq *DiscordInteractionQuery) sqlAll(ctx context.Context, hooks ...queryHo node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(diq.modifiers) > 0 { + _spec.Modifiers = diq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -432,6 +436,9 @@ func (diq *DiscordInteractionQuery) loadUser(ctx context.Context, query *UserQue func (diq *DiscordInteractionQuery) sqlCount(ctx context.Context) (int, error) { _spec := diq.querySpec() + if len(diq.modifiers) > 0 { + _spec.Modifiers = diq.modifiers + } _spec.Node.Columns = diq.ctx.Fields if len(diq.ctx.Fields) > 0 { _spec.Unique = diq.ctx.Unique != nil && *diq.ctx.Unique @@ -497,6 +504,9 @@ func (diq *DiscordInteractionQuery) sqlQuery(ctx context.Context) *sql.Selector if diq.ctx.Unique != nil && *diq.ctx.Unique { selector.Distinct() } + for _, m := range diq.modifiers { + m(selector) + } for _, p := range diq.predicates { p(selector) } @@ -514,6 +524,12 @@ func (diq *DiscordInteractionQuery) sqlQuery(ctx context.Context) *sql.Selector return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (diq *DiscordInteractionQuery) Modify(modifiers ...func(s *sql.Selector)) *DiscordInteractionSelect { + diq.modifiers = append(diq.modifiers, modifiers...) + return diq.Select() +} + // DiscordInteractionGroupBy is the group-by builder for DiscordInteraction entities. type DiscordInteractionGroupBy struct { selector @@ -603,3 +619,9 @@ func (dis *DiscordInteractionSelect) sqlScan(ctx context.Context, root *DiscordI defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (dis *DiscordInteractionSelect) Modify(modifiers ...func(s *sql.Selector)) *DiscordInteractionSelect { + dis.modifiers = append(dis.modifiers, modifiers...) + return dis +} diff --git a/internal/database/ent/db/discordinteraction_update.go b/internal/database/ent/db/discordinteraction_update.go index 3b039729..304c4c49 100644 --- a/internal/database/ent/db/discordinteraction_update.go +++ b/internal/database/ent/db/discordinteraction_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -18,8 +19,9 @@ import ( // DiscordInteractionUpdate is the builder for updating DiscordInteraction entities. type DiscordInteractionUpdate struct { config - hooks []Hook - mutation *DiscordInteractionMutation + hooks []Hook + mutation *DiscordInteractionMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the DiscordInteractionUpdate builder. @@ -29,15 +31,8 @@ func (diu *DiscordInteractionUpdate) Where(ps ...predicate.DiscordInteraction) * } // SetUpdatedAt sets the "updated_at" field. -func (diu *DiscordInteractionUpdate) SetUpdatedAt(i int64) *DiscordInteractionUpdate { - diu.mutation.ResetUpdatedAt() - diu.mutation.SetUpdatedAt(i) - return diu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (diu *DiscordInteractionUpdate) AddUpdatedAt(i int64) *DiscordInteractionUpdate { - diu.mutation.AddUpdatedAt(i) +func (diu *DiscordInteractionUpdate) SetUpdatedAt(t time.Time) *DiscordInteractionUpdate { + diu.mutation.SetUpdatedAt(t) return diu } @@ -175,6 +170,12 @@ func (diu *DiscordInteractionUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (diu *DiscordInteractionUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *DiscordInteractionUpdate { + diu.modifiers = append(diu.modifiers, modifiers...) + return diu +} + func (diu *DiscordInteractionUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := diu.check(); err != nil { return n, err @@ -188,10 +189,7 @@ func (diu *DiscordInteractionUpdate) sqlSave(ctx context.Context) (n int, err er } } if value, ok := diu.mutation.UpdatedAt(); ok { - _spec.SetField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := diu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(discordinteraction.FieldUpdatedAt, field.TypeTime, value) } if value, ok := diu.mutation.Command(); ok { _spec.SetField(discordinteraction.FieldCommand, field.TypeString, value) @@ -208,6 +206,7 @@ func (diu *DiscordInteractionUpdate) sqlSave(ctx context.Context) (n int, err er if value, ok := diu.mutation.Options(); ok { _spec.SetField(discordinteraction.FieldOptions, field.TypeJSON, value) } + _spec.AddModifiers(diu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, diu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{discordinteraction.Label} @@ -223,21 +222,15 @@ func (diu *DiscordInteractionUpdate) sqlSave(ctx context.Context) (n int, err er // DiscordInteractionUpdateOne is the builder for updating a single DiscordInteraction entity. type DiscordInteractionUpdateOne struct { config - fields []string - hooks []Hook - mutation *DiscordInteractionMutation + fields []string + hooks []Hook + mutation *DiscordInteractionMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (diuo *DiscordInteractionUpdateOne) SetUpdatedAt(i int64) *DiscordInteractionUpdateOne { - diuo.mutation.ResetUpdatedAt() - diuo.mutation.SetUpdatedAt(i) - return diuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (diuo *DiscordInteractionUpdateOne) AddUpdatedAt(i int64) *DiscordInteractionUpdateOne { - diuo.mutation.AddUpdatedAt(i) +func (diuo *DiscordInteractionUpdateOne) SetUpdatedAt(t time.Time) *DiscordInteractionUpdateOne { + diuo.mutation.SetUpdatedAt(t) return diuo } @@ -388,6 +381,12 @@ func (diuo *DiscordInteractionUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (diuo *DiscordInteractionUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *DiscordInteractionUpdateOne { + diuo.modifiers = append(diuo.modifiers, modifiers...) + return diuo +} + func (diuo *DiscordInteractionUpdateOne) sqlSave(ctx context.Context) (_node *DiscordInteraction, err error) { if err := diuo.check(); err != nil { return _node, err @@ -418,10 +417,7 @@ func (diuo *DiscordInteractionUpdateOne) sqlSave(ctx context.Context) (_node *Di } } if value, ok := diuo.mutation.UpdatedAt(); ok { - _spec.SetField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := diuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(discordinteraction.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(discordinteraction.FieldUpdatedAt, field.TypeTime, value) } if value, ok := diuo.mutation.Command(); ok { _spec.SetField(discordinteraction.FieldCommand, field.TypeString, value) @@ -438,6 +434,7 @@ func (diuo *DiscordInteractionUpdateOne) sqlSave(ctx context.Context) (_node *Di if value, ok := diuo.mutation.Options(); ok { _spec.SetField(discordinteraction.FieldOptions, field.TypeJSON, value) } + _spec.AddModifiers(diuo.modifiers...) _node = &DiscordInteraction{config: diuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/expose.go b/internal/database/ent/db/expose.go new file mode 100644 index 00000000..4cc0d120 --- /dev/null +++ b/internal/database/ent/db/expose.go @@ -0,0 +1,200 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import "fmt" + +func (a *Account) AssignValues(columns []string, values []any) error { + if a == nil { + return fmt.Errorf("Account(nil)") + } + return a.assignValues(columns, values) +} +func (a *Account) ScanValues(columns []string) ([]any, error) { + if a == nil { + return nil, fmt.Errorf("Account(nil)") + } + return a.scanValues(columns) +} + +func (as *AccountSnapshot) AssignValues(columns []string, values []any) error { + if as == nil { + return fmt.Errorf("AccountSnapshot(nil)") + } + return as.assignValues(columns, values) +} +func (as *AccountSnapshot) ScanValues(columns []string) ([]any, error) { + if as == nil { + return nil, fmt.Errorf("AccountSnapshot(nil)") + } + return as.scanValues(columns) +} + +func (as *AchievementsSnapshot) AssignValues(columns []string, values []any) error { + if as == nil { + return fmt.Errorf("AchievementsSnapshot(nil)") + } + return as.assignValues(columns, values) +} +func (as *AchievementsSnapshot) ScanValues(columns []string) ([]any, error) { + if as == nil { + return nil, fmt.Errorf("AchievementsSnapshot(nil)") + } + return as.scanValues(columns) +} + +func (ac *AppConfiguration) AssignValues(columns []string, values []any) error { + if ac == nil { + return fmt.Errorf("AppConfiguration(nil)") + } + return ac.assignValues(columns, values) +} +func (ac *AppConfiguration) ScanValues(columns []string) ([]any, error) { + if ac == nil { + return nil, fmt.Errorf("AppConfiguration(nil)") + } + return ac.scanValues(columns) +} + +func (ac *ApplicationCommand) AssignValues(columns []string, values []any) error { + if ac == nil { + return fmt.Errorf("ApplicationCommand(nil)") + } + return ac.assignValues(columns, values) +} +func (ac *ApplicationCommand) ScanValues(columns []string) ([]any, error) { + if ac == nil { + return nil, fmt.Errorf("ApplicationCommand(nil)") + } + return ac.scanValues(columns) +} + +func (c *Clan) AssignValues(columns []string, values []any) error { + if c == nil { + return fmt.Errorf("Clan(nil)") + } + return c.assignValues(columns, values) +} +func (c *Clan) ScanValues(columns []string) ([]any, error) { + if c == nil { + return nil, fmt.Errorf("Clan(nil)") + } + return c.scanValues(columns) +} + +func (ct *CronTask) AssignValues(columns []string, values []any) error { + if ct == nil { + return fmt.Errorf("CronTask(nil)") + } + return ct.assignValues(columns, values) +} +func (ct *CronTask) ScanValues(columns []string) ([]any, error) { + if ct == nil { + return nil, fmt.Errorf("CronTask(nil)") + } + return ct.scanValues(columns) +} + +func (di *DiscordInteraction) AssignValues(columns []string, values []any) error { + if di == nil { + return fmt.Errorf("DiscordInteraction(nil)") + } + return di.assignValues(columns, values) +} +func (di *DiscordInteraction) ScanValues(columns []string) ([]any, error) { + if di == nil { + return nil, fmt.Errorf("DiscordInteraction(nil)") + } + return di.scanValues(columns) +} + +func (u *User) AssignValues(columns []string, values []any) error { + if u == nil { + return fmt.Errorf("User(nil)") + } + return u.assignValues(columns, values) +} +func (u *User) ScanValues(columns []string) ([]any, error) { + if u == nil { + return nil, fmt.Errorf("User(nil)") + } + return u.scanValues(columns) +} + +func (uc *UserConnection) AssignValues(columns []string, values []any) error { + if uc == nil { + return fmt.Errorf("UserConnection(nil)") + } + return uc.assignValues(columns, values) +} +func (uc *UserConnection) ScanValues(columns []string) ([]any, error) { + if uc == nil { + return nil, fmt.Errorf("UserConnection(nil)") + } + return uc.scanValues(columns) +} + +func (uc *UserContent) AssignValues(columns []string, values []any) error { + if uc == nil { + return fmt.Errorf("UserContent(nil)") + } + return uc.assignValues(columns, values) +} +func (uc *UserContent) ScanValues(columns []string) ([]any, error) { + if uc == nil { + return nil, fmt.Errorf("UserContent(nil)") + } + return uc.scanValues(columns) +} + +func (us *UserSubscription) AssignValues(columns []string, values []any) error { + if us == nil { + return fmt.Errorf("UserSubscription(nil)") + } + return us.assignValues(columns, values) +} +func (us *UserSubscription) ScanValues(columns []string) ([]any, error) { + if us == nil { + return nil, fmt.Errorf("UserSubscription(nil)") + } + return us.scanValues(columns) +} + +func (v *Vehicle) AssignValues(columns []string, values []any) error { + if v == nil { + return fmt.Errorf("Vehicle(nil)") + } + return v.assignValues(columns, values) +} +func (v *Vehicle) ScanValues(columns []string) ([]any, error) { + if v == nil { + return nil, fmt.Errorf("Vehicle(nil)") + } + return v.scanValues(columns) +} + +func (va *VehicleAverage) AssignValues(columns []string, values []any) error { + if va == nil { + return fmt.Errorf("VehicleAverage(nil)") + } + return va.assignValues(columns, values) +} +func (va *VehicleAverage) ScanValues(columns []string) ([]any, error) { + if va == nil { + return nil, fmt.Errorf("VehicleAverage(nil)") + } + return va.scanValues(columns) +} + +func (vs *VehicleSnapshot) AssignValues(columns []string, values []any) error { + if vs == nil { + return fmt.Errorf("VehicleSnapshot(nil)") + } + return vs.assignValues(columns, values) +} +func (vs *VehicleSnapshot) ScanValues(columns []string) ([]any, error) { + if vs == nil { + return nil, fmt.Errorf("VehicleSnapshot(nil)") + } + return vs.scanValues(columns) +} diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index 716a4a3c..15f56785 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -11,10 +11,10 @@ var ( // AccountsColumns holds the columns for the "accounts" table. AccountsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, - {Name: "last_battle_time", Type: field.TypeInt64}, - {Name: "account_created_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "last_battle_time", Type: field.TypeTime}, + {Name: "account_created_at", Type: field.TypeTime}, {Name: "realm", Type: field.TypeString, Size: 5}, {Name: "nickname", Type: field.TypeString}, {Name: "private", Type: field.TypeBool, Default: false}, @@ -64,10 +64,10 @@ var ( // AccountSnapshotsColumns holds the columns for the "account_snapshots" table. AccountSnapshotsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "type", Type: field.TypeEnum, Enums: []string{"live", "daily"}}, - {Name: "last_battle_time", Type: field.TypeInt64}, + {Name: "last_battle_time", Type: field.TypeTime}, {Name: "reference_id", Type: field.TypeString}, {Name: "rating_battles", Type: field.TypeInt}, {Name: "rating_frame", Type: field.TypeJSON}, @@ -119,12 +119,12 @@ var ( // AchievementsSnapshotsColumns holds the columns for the "achievements_snapshots" table. AchievementsSnapshotsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "type", Type: field.TypeEnum, Enums: []string{"live", "daily"}}, {Name: "reference_id", Type: field.TypeString}, {Name: "battles", Type: field.TypeInt}, - {Name: "last_battle_time", Type: field.TypeInt64}, + {Name: "last_battle_time", Type: field.TypeTime}, {Name: "data", Type: field.TypeJSON}, {Name: "account_id", Type: field.TypeString}, } @@ -167,8 +167,8 @@ var ( // AppConfigurationsColumns holds the columns for the "app_configurations" table. AppConfigurationsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "key", Type: field.TypeString, Unique: true}, {Name: "value", Type: field.TypeJSON}, {Name: "metadata", Type: field.TypeJSON, Nullable: true}, @@ -194,8 +194,8 @@ var ( // ApplicationCommandsColumns holds the columns for the "application_commands" table. ApplicationCommandsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "name", Type: field.TypeString}, {Name: "version", Type: field.TypeString}, {Name: "options_hash", Type: field.TypeString}, @@ -221,8 +221,8 @@ var ( // ClansColumns holds the columns for the "clans" table. ClansColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "tag", Type: field.TypeString}, {Name: "name", Type: field.TypeString}, {Name: "emblem_id", Type: field.TypeString, Nullable: true, Default: ""}, @@ -254,14 +254,14 @@ var ( // CronTasksColumns holds the columns for the "cron_tasks" table. CronTasksColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "type", Type: field.TypeEnum, Enums: []string{"UPDATE_CLANS", "RECORD_SNAPSHOTS", "CLEANUP_DATABASE"}}, {Name: "reference_id", Type: field.TypeString}, {Name: "targets", Type: field.TypeJSON}, {Name: "status", Type: field.TypeEnum, Enums: []string{"TASK_SCHEDULED", "TASK_IN_PROGRESS", "TASK_COMPLETE", "TASK_FAILED"}}, - {Name: "scheduled_after", Type: field.TypeInt64}, - {Name: "last_run", Type: field.TypeInt64}, + {Name: "scheduled_after", Type: field.TypeTime}, + {Name: "last_run", Type: field.TypeTime}, {Name: "logs", Type: field.TypeJSON}, {Name: "data", Type: field.TypeJSON}, } @@ -301,8 +301,8 @@ var ( // DiscordInteractionsColumns holds the columns for the "discord_interactions" table. DiscordInteractionsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "command", Type: field.TypeString}, {Name: "reference_id", Type: field.TypeString}, {Name: "type", Type: field.TypeEnum, Enums: []string{"stats"}}, @@ -354,8 +354,8 @@ var ( // UsersColumns holds the columns for the "users" table. UsersColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "permissions", Type: field.TypeString, Default: ""}, {Name: "feature_flags", Type: field.TypeJSON, Nullable: true}, } @@ -375,8 +375,8 @@ var ( // UserConnectionsColumns holds the columns for the "user_connections" table. UserConnectionsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "type", Type: field.TypeEnum, Enums: []string{"wargaming"}}, {Name: "reference_id", Type: field.TypeString}, {Name: "permissions", Type: field.TypeString, Nullable: true, Default: ""}, @@ -432,8 +432,8 @@ var ( // UserContentsColumns holds the columns for the "user_contents" table. UserContentsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "type", Type: field.TypeEnum, Enums: []string{"clan-background-image", "personal-background-image"}}, {Name: "reference_id", Type: field.TypeString}, {Name: "value", Type: field.TypeString}, @@ -484,10 +484,10 @@ var ( // UserSubscriptionsColumns holds the columns for the "user_subscriptions" table. UserSubscriptionsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "type", Type: field.TypeEnum, Enums: []string{"aftermath-pro", "aftermath-pro-clan", "aftermath-plus", "supporter", "verified-clan", "server-moderator", "content-moderator", "developer", "server-booster", "content-translator"}}, - {Name: "expires_at", Type: field.TypeInt64}, + {Name: "expires_at", Type: field.TypeTime}, {Name: "permissions", Type: field.TypeString}, {Name: "reference_id", Type: field.TypeString}, {Name: "user_id", Type: field.TypeString}, @@ -536,8 +536,8 @@ var ( // VehiclesColumns holds the columns for the "vehicles" table. VehiclesColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "tier", Type: field.TypeInt}, {Name: "localized_names", Type: field.TypeJSON}, } @@ -557,8 +557,8 @@ var ( // VehicleAveragesColumns holds the columns for the "vehicle_averages" table. VehicleAveragesColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "data", Type: field.TypeJSON}, } // VehicleAveragesTable holds the schema information for the "vehicle_averages" table. @@ -577,13 +577,13 @@ var ( // VehicleSnapshotsColumns holds the columns for the "vehicle_snapshots" table. VehicleSnapshotsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, - {Name: "created_at", Type: field.TypeInt64}, - {Name: "updated_at", Type: field.TypeInt64}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, {Name: "type", Type: field.TypeEnum, Enums: []string{"live", "daily"}}, {Name: "vehicle_id", Type: field.TypeString}, {Name: "reference_id", Type: field.TypeString}, {Name: "battles", Type: field.TypeInt}, - {Name: "last_battle_time", Type: field.TypeInt64}, + {Name: "last_battle_time", Type: field.TypeTime}, {Name: "frame", Type: field.TypeJSON}, {Name: "account_id", Type: field.TypeString}, } diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go index 1dcee88e..c4078845 100644 --- a/internal/database/ent/db/mutation.go +++ b/internal/database/ent/db/mutation.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "sync" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -63,14 +64,10 @@ type AccountMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 - last_battle_time *int64 - addlast_battle_time *int64 - account_created_at *int64 - addaccount_created_at *int64 + created_at *time.Time + updated_at *time.Time + last_battle_time *time.Time + account_created_at *time.Time realm *string nickname *string private *bool @@ -196,13 +193,12 @@ func (m *AccountMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *AccountMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *AccountMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *AccountMutation) CreatedAt() (r int64, exists bool) { +func (m *AccountMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -213,7 +209,7 @@ func (m *AccountMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the Account entity. // If the Account object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *AccountMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -227,38 +223,18 @@ func (m *AccountMutation) OldCreatedAt(ctx context.Context) (v int64, err error) return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *AccountMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *AccountMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *AccountMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *AccountMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *AccountMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *AccountMutation) UpdatedAt() (r int64, exists bool) { +func (m *AccountMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -269,7 +245,7 @@ func (m *AccountMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the Account entity. // If the Account object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *AccountMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -283,38 +259,18 @@ func (m *AccountMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *AccountMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *AccountMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *AccountMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetLastBattleTime sets the "last_battle_time" field. -func (m *AccountMutation) SetLastBattleTime(i int64) { - m.last_battle_time = &i - m.addlast_battle_time = nil +func (m *AccountMutation) SetLastBattleTime(t time.Time) { + m.last_battle_time = &t } // LastBattleTime returns the value of the "last_battle_time" field in the mutation. -func (m *AccountMutation) LastBattleTime() (r int64, exists bool) { +func (m *AccountMutation) LastBattleTime() (r time.Time, exists bool) { v := m.last_battle_time if v == nil { return @@ -325,7 +281,7 @@ func (m *AccountMutation) LastBattleTime() (r int64, exists bool) { // OldLastBattleTime returns the old "last_battle_time" field's value of the Account entity. // If the Account object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountMutation) OldLastBattleTime(ctx context.Context) (v int64, err error) { +func (m *AccountMutation) OldLastBattleTime(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") } @@ -339,38 +295,18 @@ func (m *AccountMutation) OldLastBattleTime(ctx context.Context) (v int64, err e return oldValue.LastBattleTime, nil } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (m *AccountMutation) AddLastBattleTime(i int64) { - if m.addlast_battle_time != nil { - *m.addlast_battle_time += i - } else { - m.addlast_battle_time = &i - } -} - -// AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. -func (m *AccountMutation) AddedLastBattleTime() (r int64, exists bool) { - v := m.addlast_battle_time - if v == nil { - return - } - return *v, true -} - // ResetLastBattleTime resets all changes to the "last_battle_time" field. func (m *AccountMutation) ResetLastBattleTime() { m.last_battle_time = nil - m.addlast_battle_time = nil } // SetAccountCreatedAt sets the "account_created_at" field. -func (m *AccountMutation) SetAccountCreatedAt(i int64) { - m.account_created_at = &i - m.addaccount_created_at = nil +func (m *AccountMutation) SetAccountCreatedAt(t time.Time) { + m.account_created_at = &t } // AccountCreatedAt returns the value of the "account_created_at" field in the mutation. -func (m *AccountMutation) AccountCreatedAt() (r int64, exists bool) { +func (m *AccountMutation) AccountCreatedAt() (r time.Time, exists bool) { v := m.account_created_at if v == nil { return @@ -381,7 +317,7 @@ func (m *AccountMutation) AccountCreatedAt() (r int64, exists bool) { // OldAccountCreatedAt returns the old "account_created_at" field's value of the Account entity. // If the Account object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountMutation) OldAccountCreatedAt(ctx context.Context) (v int64, err error) { +func (m *AccountMutation) OldAccountCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldAccountCreatedAt is only allowed on UpdateOne operations") } @@ -395,28 +331,9 @@ func (m *AccountMutation) OldAccountCreatedAt(ctx context.Context) (v int64, err return oldValue.AccountCreatedAt, nil } -// AddAccountCreatedAt adds i to the "account_created_at" field. -func (m *AccountMutation) AddAccountCreatedAt(i int64) { - if m.addaccount_created_at != nil { - *m.addaccount_created_at += i - } else { - m.addaccount_created_at = &i - } -} - -// AddedAccountCreatedAt returns the value that was added to the "account_created_at" field in this mutation. -func (m *AccountMutation) AddedAccountCreatedAt() (r int64, exists bool) { - v := m.addaccount_created_at - if v == nil { - return - } - return *v, true -} - // ResetAccountCreatedAt resets all changes to the "account_created_at" field. func (m *AccountMutation) ResetAccountCreatedAt() { m.account_created_at = nil - m.addaccount_created_at = nil } // SetRealm sets the "realm" field. @@ -883,28 +800,28 @@ func (m *AccountMutation) OldField(ctx context.Context, name string) (ent.Value, func (m *AccountMutation) SetField(name string, value ent.Value) error { switch name { case account.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case account.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUpdatedAt(v) return nil case account.FieldLastBattleTime: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetLastBattleTime(v) return nil case account.FieldAccountCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -945,36 +862,13 @@ func (m *AccountMutation) SetField(name string, value ent.Value) error { // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *AccountMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, account.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, account.FieldUpdatedAt) - } - if m.addlast_battle_time != nil { - fields = append(fields, account.FieldLastBattleTime) - } - if m.addaccount_created_at != nil { - fields = append(fields, account.FieldAccountCreatedAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *AccountMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case account.FieldCreatedAt: - return m.AddedCreatedAt() - case account.FieldUpdatedAt: - return m.AddedUpdatedAt() - case account.FieldLastBattleTime: - return m.AddedLastBattleTime() - case account.FieldAccountCreatedAt: - return m.AddedAccountCreatedAt() - } return nil, false } @@ -983,34 +877,6 @@ func (m *AccountMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *AccountMutation) AddField(name string, value ent.Value) error { switch name { - case account.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case account.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil - case account.FieldLastBattleTime: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddLastBattleTime(v) - return nil - case account.FieldAccountCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddAccountCreatedAt(v) - return nil } return fmt.Errorf("unknown Account numeric field %s", name) } @@ -1232,29 +1098,26 @@ func (m *AccountMutation) ResetEdge(name string) error { // AccountSnapshotMutation represents an operation that mutates the AccountSnapshot nodes in the graph. type AccountSnapshotMutation struct { config - op Op - typ string - id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 - _type *models.SnapshotType - last_battle_time *int64 - addlast_battle_time *int64 - reference_id *string - rating_battles *int - addrating_battles *int - rating_frame *frame.StatsFrame - regular_battles *int - addregular_battles *int - regular_frame *frame.StatsFrame - clearedFields map[string]struct{} - account *string - clearedaccount bool - done bool - oldValue func(context.Context) (*AccountSnapshot, error) - predicates []predicate.AccountSnapshot + op Op + typ string + id *string + created_at *time.Time + updated_at *time.Time + _type *models.SnapshotType + last_battle_time *time.Time + reference_id *string + rating_battles *int + addrating_battles *int + rating_frame *frame.StatsFrame + regular_battles *int + addregular_battles *int + regular_frame *frame.StatsFrame + clearedFields map[string]struct{} + account *string + clearedaccount bool + done bool + oldValue func(context.Context) (*AccountSnapshot, error) + predicates []predicate.AccountSnapshot } var _ ent.Mutation = (*AccountSnapshotMutation)(nil) @@ -1362,13 +1225,12 @@ func (m *AccountSnapshotMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *AccountSnapshotMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *AccountSnapshotMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *AccountSnapshotMutation) CreatedAt() (r int64, exists bool) { +func (m *AccountSnapshotMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -1379,7 +1241,7 @@ func (m *AccountSnapshotMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the AccountSnapshot entity. // If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountSnapshotMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *AccountSnapshotMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -1393,38 +1255,18 @@ func (m *AccountSnapshotMutation) OldCreatedAt(ctx context.Context) (v int64, er return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *AccountSnapshotMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *AccountSnapshotMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *AccountSnapshotMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *AccountSnapshotMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *AccountSnapshotMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *AccountSnapshotMutation) UpdatedAt() (r int64, exists bool) { +func (m *AccountSnapshotMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -1435,7 +1277,7 @@ func (m *AccountSnapshotMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the AccountSnapshot entity. // If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *AccountSnapshotMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -1449,28 +1291,9 @@ func (m *AccountSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int64, er return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *AccountSnapshotMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *AccountSnapshotMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *AccountSnapshotMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetType sets the "type" field. @@ -1510,13 +1333,12 @@ func (m *AccountSnapshotMutation) ResetType() { } // SetLastBattleTime sets the "last_battle_time" field. -func (m *AccountSnapshotMutation) SetLastBattleTime(i int64) { - m.last_battle_time = &i - m.addlast_battle_time = nil +func (m *AccountSnapshotMutation) SetLastBattleTime(t time.Time) { + m.last_battle_time = &t } // LastBattleTime returns the value of the "last_battle_time" field in the mutation. -func (m *AccountSnapshotMutation) LastBattleTime() (r int64, exists bool) { +func (m *AccountSnapshotMutation) LastBattleTime() (r time.Time, exists bool) { v := m.last_battle_time if v == nil { return @@ -1527,7 +1349,7 @@ func (m *AccountSnapshotMutation) LastBattleTime() (r int64, exists bool) { // OldLastBattleTime returns the old "last_battle_time" field's value of the AccountSnapshot entity. // If the AccountSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AccountSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int64, err error) { +func (m *AccountSnapshotMutation) OldLastBattleTime(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") } @@ -1541,28 +1363,9 @@ func (m *AccountSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int6 return oldValue.LastBattleTime, nil } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (m *AccountSnapshotMutation) AddLastBattleTime(i int64) { - if m.addlast_battle_time != nil { - *m.addlast_battle_time += i - } else { - m.addlast_battle_time = &i - } -} - -// AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. -func (m *AccountSnapshotMutation) AddedLastBattleTime() (r int64, exists bool) { - v := m.addlast_battle_time - if v == nil { - return - } - return *v, true -} - // ResetLastBattleTime resets all changes to the "last_battle_time" field. func (m *AccountSnapshotMutation) ResetLastBattleTime() { m.last_battle_time = nil - m.addlast_battle_time = nil } // SetAccountID sets the "account_id" field. @@ -1980,14 +1783,14 @@ func (m *AccountSnapshotMutation) OldField(ctx context.Context, name string) (en func (m *AccountSnapshotMutation) SetField(name string, value ent.Value) error { switch name { case accountsnapshot.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case accountsnapshot.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -2001,7 +1804,7 @@ func (m *AccountSnapshotMutation) SetField(name string, value ent.Value) error { m.SetType(v) return nil case accountsnapshot.FieldLastBattleTime: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -2057,15 +1860,6 @@ func (m *AccountSnapshotMutation) SetField(name string, value ent.Value) error { // this mutation. func (m *AccountSnapshotMutation) AddedFields() []string { var fields []string - if m.addcreated_at != nil { - fields = append(fields, accountsnapshot.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, accountsnapshot.FieldUpdatedAt) - } - if m.addlast_battle_time != nil { - fields = append(fields, accountsnapshot.FieldLastBattleTime) - } if m.addrating_battles != nil { fields = append(fields, accountsnapshot.FieldRatingBattles) } @@ -2080,12 +1874,6 @@ func (m *AccountSnapshotMutation) AddedFields() []string { // was not set, or was not defined in the schema. func (m *AccountSnapshotMutation) AddedField(name string) (ent.Value, bool) { switch name { - case accountsnapshot.FieldCreatedAt: - return m.AddedCreatedAt() - case accountsnapshot.FieldUpdatedAt: - return m.AddedUpdatedAt() - case accountsnapshot.FieldLastBattleTime: - return m.AddedLastBattleTime() case accountsnapshot.FieldRatingBattles: return m.AddedRatingBattles() case accountsnapshot.FieldRegularBattles: @@ -2099,27 +1887,6 @@ func (m *AccountSnapshotMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *AccountSnapshotMutation) AddField(name string, value ent.Value) error { switch name { - case accountsnapshot.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case accountsnapshot.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil - case accountsnapshot.FieldLastBattleTime: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddLastBattleTime(v) - return nil case accountsnapshot.FieldRatingBattles: v, ok := value.(int) if !ok { @@ -2272,26 +2039,23 @@ func (m *AccountSnapshotMutation) ResetEdge(name string) error { // AchievementsSnapshotMutation represents an operation that mutates the AchievementsSnapshot nodes in the graph. type AchievementsSnapshotMutation struct { config - op Op - typ string - id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 - _type *models.SnapshotType - reference_id *string - battles *int - addbattles *int - last_battle_time *int64 - addlast_battle_time *int64 - data *types.AchievementsFrame - clearedFields map[string]struct{} - account *string - clearedaccount bool - done bool - oldValue func(context.Context) (*AchievementsSnapshot, error) - predicates []predicate.AchievementsSnapshot + op Op + typ string + id *string + created_at *time.Time + updated_at *time.Time + _type *models.SnapshotType + reference_id *string + battles *int + addbattles *int + last_battle_time *time.Time + data *types.AchievementsFrame + clearedFields map[string]struct{} + account *string + clearedaccount bool + done bool + oldValue func(context.Context) (*AchievementsSnapshot, error) + predicates []predicate.AchievementsSnapshot } var _ ent.Mutation = (*AchievementsSnapshotMutation)(nil) @@ -2399,13 +2163,12 @@ func (m *AchievementsSnapshotMutation) IDs(ctx context.Context) ([]string, error } // SetCreatedAt sets the "created_at" field. -func (m *AchievementsSnapshotMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *AchievementsSnapshotMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *AchievementsSnapshotMutation) CreatedAt() (r int64, exists bool) { +func (m *AchievementsSnapshotMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -2416,7 +2179,7 @@ func (m *AchievementsSnapshotMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the AchievementsSnapshot entity. // If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AchievementsSnapshotMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *AchievementsSnapshotMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -2430,38 +2193,18 @@ func (m *AchievementsSnapshotMutation) OldCreatedAt(ctx context.Context) (v int6 return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *AchievementsSnapshotMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *AchievementsSnapshotMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *AchievementsSnapshotMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *AchievementsSnapshotMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *AchievementsSnapshotMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *AchievementsSnapshotMutation) UpdatedAt() (r int64, exists bool) { +func (m *AchievementsSnapshotMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -2472,7 +2215,7 @@ func (m *AchievementsSnapshotMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the AchievementsSnapshot entity. // If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AchievementsSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *AchievementsSnapshotMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -2486,28 +2229,9 @@ func (m *AchievementsSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int6 return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *AchievementsSnapshotMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *AchievementsSnapshotMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *AchievementsSnapshotMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetType sets the "type" field. @@ -2675,13 +2399,12 @@ func (m *AchievementsSnapshotMutation) ResetBattles() { } // SetLastBattleTime sets the "last_battle_time" field. -func (m *AchievementsSnapshotMutation) SetLastBattleTime(i int64) { - m.last_battle_time = &i - m.addlast_battle_time = nil +func (m *AchievementsSnapshotMutation) SetLastBattleTime(t time.Time) { + m.last_battle_time = &t } // LastBattleTime returns the value of the "last_battle_time" field in the mutation. -func (m *AchievementsSnapshotMutation) LastBattleTime() (r int64, exists bool) { +func (m *AchievementsSnapshotMutation) LastBattleTime() (r time.Time, exists bool) { v := m.last_battle_time if v == nil { return @@ -2692,7 +2415,7 @@ func (m *AchievementsSnapshotMutation) LastBattleTime() (r int64, exists bool) { // OldLastBattleTime returns the old "last_battle_time" field's value of the AchievementsSnapshot entity. // If the AchievementsSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AchievementsSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int64, err error) { +func (m *AchievementsSnapshotMutation) OldLastBattleTime(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") } @@ -2706,28 +2429,9 @@ func (m *AchievementsSnapshotMutation) OldLastBattleTime(ctx context.Context) (v return oldValue.LastBattleTime, nil } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (m *AchievementsSnapshotMutation) AddLastBattleTime(i int64) { - if m.addlast_battle_time != nil { - *m.addlast_battle_time += i - } else { - m.addlast_battle_time = &i - } -} - -// AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. -func (m *AchievementsSnapshotMutation) AddedLastBattleTime() (r int64, exists bool) { - v := m.addlast_battle_time - if v == nil { - return - } - return *v, true -} - // ResetLastBattleTime resets all changes to the "last_battle_time" field. func (m *AchievementsSnapshotMutation) ResetLastBattleTime() { m.last_battle_time = nil - m.addlast_battle_time = nil } // SetData sets the "data" field. @@ -2911,14 +2615,14 @@ func (m *AchievementsSnapshotMutation) OldField(ctx context.Context, name string func (m *AchievementsSnapshotMutation) SetField(name string, value ent.Value) error { switch name { case achievementssnapshot.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case achievementssnapshot.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -2953,7 +2657,7 @@ func (m *AchievementsSnapshotMutation) SetField(name string, value ent.Value) er m.SetBattles(v) return nil case achievementssnapshot.FieldLastBattleTime: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -2974,18 +2678,9 @@ func (m *AchievementsSnapshotMutation) SetField(name string, value ent.Value) er // this mutation. func (m *AchievementsSnapshotMutation) AddedFields() []string { var fields []string - if m.addcreated_at != nil { - fields = append(fields, achievementssnapshot.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, achievementssnapshot.FieldUpdatedAt) - } if m.addbattles != nil { fields = append(fields, achievementssnapshot.FieldBattles) } - if m.addlast_battle_time != nil { - fields = append(fields, achievementssnapshot.FieldLastBattleTime) - } return fields } @@ -2994,14 +2689,8 @@ func (m *AchievementsSnapshotMutation) AddedFields() []string { // was not set, or was not defined in the schema. func (m *AchievementsSnapshotMutation) AddedField(name string) (ent.Value, bool) { switch name { - case achievementssnapshot.FieldCreatedAt: - return m.AddedCreatedAt() - case achievementssnapshot.FieldUpdatedAt: - return m.AddedUpdatedAt() case achievementssnapshot.FieldBattles: return m.AddedBattles() - case achievementssnapshot.FieldLastBattleTime: - return m.AddedLastBattleTime() } return nil, false } @@ -3011,20 +2700,6 @@ func (m *AchievementsSnapshotMutation) AddedField(name string) (ent.Value, bool) // type. func (m *AchievementsSnapshotMutation) AddField(name string, value ent.Value) error { switch name { - case achievementssnapshot.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case achievementssnapshot.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil case achievementssnapshot.FieldBattles: v, ok := value.(int) if !ok { @@ -3032,13 +2707,6 @@ func (m *AchievementsSnapshotMutation) AddField(name string, value ent.Value) er } m.AddBattles(v) return nil - case achievementssnapshot.FieldLastBattleTime: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddLastBattleTime(v) - return nil } return fmt.Errorf("unknown AchievementsSnapshot numeric field %s", name) } @@ -3174,10 +2842,8 @@ type AppConfigurationMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time key *string value *any metadata *map[string]interface{} @@ -3292,13 +2958,12 @@ func (m *AppConfigurationMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *AppConfigurationMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *AppConfigurationMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *AppConfigurationMutation) CreatedAt() (r int64, exists bool) { +func (m *AppConfigurationMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -3309,7 +2974,7 @@ func (m *AppConfigurationMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the AppConfiguration entity. // If the AppConfiguration object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AppConfigurationMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *AppConfigurationMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -3323,38 +2988,18 @@ func (m *AppConfigurationMutation) OldCreatedAt(ctx context.Context) (v int64, e return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *AppConfigurationMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *AppConfigurationMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *AppConfigurationMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *AppConfigurationMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *AppConfigurationMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *AppConfigurationMutation) UpdatedAt() (r int64, exists bool) { +func (m *AppConfigurationMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -3365,7 +3010,7 @@ func (m *AppConfigurationMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the AppConfiguration entity. // If the AppConfiguration object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *AppConfigurationMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *AppConfigurationMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -3379,28 +3024,9 @@ func (m *AppConfigurationMutation) OldUpdatedAt(ctx context.Context) (v int64, e return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *AppConfigurationMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *AppConfigurationMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *AppConfigurationMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetKey sets the "key" field. @@ -3621,14 +3247,14 @@ func (m *AppConfigurationMutation) OldField(ctx context.Context, name string) (e func (m *AppConfigurationMutation) SetField(name string, value ent.Value) error { switch name { case appconfiguration.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case appconfiguration.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -3662,26 +3288,13 @@ func (m *AppConfigurationMutation) SetField(name string, value ent.Value) error // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *AppConfigurationMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, appconfiguration.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, appconfiguration.FieldUpdatedAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *AppConfigurationMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case appconfiguration.FieldCreatedAt: - return m.AddedCreatedAt() - case appconfiguration.FieldUpdatedAt: - return m.AddedUpdatedAt() - } return nil, false } @@ -3690,20 +3303,6 @@ func (m *AppConfigurationMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *AppConfigurationMutation) AddField(name string, value ent.Value) error { switch name { - case appconfiguration.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case appconfiguration.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil } return fmt.Errorf("unknown AppConfiguration numeric field %s", name) } @@ -3813,10 +3412,8 @@ type ApplicationCommandMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time name *string version *string options_hash *string @@ -3931,13 +3528,12 @@ func (m *ApplicationCommandMutation) IDs(ctx context.Context) ([]string, error) } // SetCreatedAt sets the "created_at" field. -func (m *ApplicationCommandMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *ApplicationCommandMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *ApplicationCommandMutation) CreatedAt() (r int64, exists bool) { +func (m *ApplicationCommandMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -3948,7 +3544,7 @@ func (m *ApplicationCommandMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the ApplicationCommand entity. // If the ApplicationCommand object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ApplicationCommandMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *ApplicationCommandMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -3962,38 +3558,18 @@ func (m *ApplicationCommandMutation) OldCreatedAt(ctx context.Context) (v int64, return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *ApplicationCommandMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *ApplicationCommandMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *ApplicationCommandMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *ApplicationCommandMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *ApplicationCommandMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *ApplicationCommandMutation) UpdatedAt() (r int64, exists bool) { +func (m *ApplicationCommandMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -4004,7 +3580,7 @@ func (m *ApplicationCommandMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the ApplicationCommand entity. // If the ApplicationCommand object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ApplicationCommandMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *ApplicationCommandMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -4018,28 +3594,9 @@ func (m *ApplicationCommandMutation) OldUpdatedAt(ctx context.Context) (v int64, return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *ApplicationCommandMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *ApplicationCommandMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *ApplicationCommandMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetName sets the "name" field. @@ -4247,14 +3804,14 @@ func (m *ApplicationCommandMutation) OldField(ctx context.Context, name string) func (m *ApplicationCommandMutation) SetField(name string, value ent.Value) error { switch name { case applicationcommand.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case applicationcommand.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -4288,26 +3845,13 @@ func (m *ApplicationCommandMutation) SetField(name string, value ent.Value) erro // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *ApplicationCommandMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, applicationcommand.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, applicationcommand.FieldUpdatedAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *ApplicationCommandMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case applicationcommand.FieldCreatedAt: - return m.AddedCreatedAt() - case applicationcommand.FieldUpdatedAt: - return m.AddedUpdatedAt() - } return nil, false } @@ -4316,20 +3860,6 @@ func (m *ApplicationCommandMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *ApplicationCommandMutation) AddField(name string, value ent.Value) error { switch name { - case applicationcommand.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case applicationcommand.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil } return fmt.Errorf("unknown ApplicationCommand numeric field %s", name) } @@ -4430,10 +3960,8 @@ type ClanMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time tag *string name *string emblem_id *string @@ -4553,13 +4081,12 @@ func (m *ClanMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *ClanMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *ClanMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *ClanMutation) CreatedAt() (r int64, exists bool) { +func (m *ClanMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -4570,7 +4097,7 @@ func (m *ClanMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the Clan entity. // If the Clan object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -4584,38 +4111,18 @@ func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *ClanMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *ClanMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *ClanMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *ClanMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *ClanMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *ClanMutation) UpdatedAt() (r int64, exists bool) { +func (m *ClanMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -4626,7 +4133,7 @@ func (m *ClanMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the Clan entity. // If the Clan object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -4640,28 +4147,9 @@ func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *ClanMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *ClanMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *ClanMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetTag sets the "tag" field. @@ -4994,14 +4482,14 @@ func (m *ClanMutation) OldField(ctx context.Context, name string) (ent.Value, er func (m *ClanMutation) SetField(name string, value ent.Value) error { switch name { case clan.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case clan.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -5042,26 +4530,13 @@ func (m *ClanMutation) SetField(name string, value ent.Value) error { // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *ClanMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, clan.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, clan.FieldUpdatedAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *ClanMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case clan.FieldCreatedAt: - return m.AddedCreatedAt() - case clan.FieldUpdatedAt: - return m.AddedUpdatedAt() - } return nil, false } @@ -5070,20 +4545,6 @@ func (m *ClanMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *ClanMutation) AddField(name string, value ent.Value) error { switch name { - case clan.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case clan.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil } return fmt.Errorf("unknown Clan numeric field %s", name) } @@ -5229,29 +4690,25 @@ func (m *ClanMutation) ResetEdge(name string) error { // CronTaskMutation represents an operation that mutates the CronTask nodes in the graph. type CronTaskMutation struct { config - op Op - typ string - id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 - _type *models.TaskType - reference_id *string - targets *[]string - appendtargets []string - status *models.TaskStatus - scheduled_after *int64 - addscheduled_after *int64 - last_run *int64 - addlast_run *int64 - logs *[]models.TaskLog - appendlogs []models.TaskLog - data *map[string]interface{} - clearedFields map[string]struct{} - done bool - oldValue func(context.Context) (*CronTask, error) - predicates []predicate.CronTask + op Op + typ string + id *string + created_at *time.Time + updated_at *time.Time + _type *models.TaskType + reference_id *string + targets *[]string + appendtargets []string + status *models.TaskStatus + scheduled_after *time.Time + last_run *time.Time + logs *[]models.TaskLog + appendlogs []models.TaskLog + data *map[string]interface{} + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*CronTask, error) + predicates []predicate.CronTask } var _ ent.Mutation = (*CronTaskMutation)(nil) @@ -5359,13 +4816,12 @@ func (m *CronTaskMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *CronTaskMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *CronTaskMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *CronTaskMutation) CreatedAt() (r int64, exists bool) { +func (m *CronTaskMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -5376,7 +4832,7 @@ func (m *CronTaskMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -5390,38 +4846,18 @@ func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v int64, err error return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *CronTaskMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *CronTaskMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *CronTaskMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *CronTaskMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *CronTaskMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *CronTaskMutation) UpdatedAt() (r int64, exists bool) { +func (m *CronTaskMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -5432,7 +4868,7 @@ func (m *CronTaskMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -5446,28 +4882,9 @@ func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v int64, err error return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *CronTaskMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *CronTaskMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *CronTaskMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetType sets the "type" field. @@ -5630,13 +5047,12 @@ func (m *CronTaskMutation) ResetStatus() { } // SetScheduledAfter sets the "scheduled_after" field. -func (m *CronTaskMutation) SetScheduledAfter(i int64) { - m.scheduled_after = &i - m.addscheduled_after = nil +func (m *CronTaskMutation) SetScheduledAfter(t time.Time) { + m.scheduled_after = &t } // ScheduledAfter returns the value of the "scheduled_after" field in the mutation. -func (m *CronTaskMutation) ScheduledAfter() (r int64, exists bool) { +func (m *CronTaskMutation) ScheduledAfter() (r time.Time, exists bool) { v := m.scheduled_after if v == nil { return @@ -5647,7 +5063,7 @@ func (m *CronTaskMutation) ScheduledAfter() (r int64, exists bool) { // OldScheduledAfter returns the old "scheduled_after" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldScheduledAfter(ctx context.Context) (v int64, err error) { +func (m *CronTaskMutation) OldScheduledAfter(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldScheduledAfter is only allowed on UpdateOne operations") } @@ -5661,38 +5077,18 @@ func (m *CronTaskMutation) OldScheduledAfter(ctx context.Context) (v int64, err return oldValue.ScheduledAfter, nil } -// AddScheduledAfter adds i to the "scheduled_after" field. -func (m *CronTaskMutation) AddScheduledAfter(i int64) { - if m.addscheduled_after != nil { - *m.addscheduled_after += i - } else { - m.addscheduled_after = &i - } -} - -// AddedScheduledAfter returns the value that was added to the "scheduled_after" field in this mutation. -func (m *CronTaskMutation) AddedScheduledAfter() (r int64, exists bool) { - v := m.addscheduled_after - if v == nil { - return - } - return *v, true -} - // ResetScheduledAfter resets all changes to the "scheduled_after" field. func (m *CronTaskMutation) ResetScheduledAfter() { m.scheduled_after = nil - m.addscheduled_after = nil } // SetLastRun sets the "last_run" field. -func (m *CronTaskMutation) SetLastRun(i int64) { - m.last_run = &i - m.addlast_run = nil +func (m *CronTaskMutation) SetLastRun(t time.Time) { + m.last_run = &t } // LastRun returns the value of the "last_run" field in the mutation. -func (m *CronTaskMutation) LastRun() (r int64, exists bool) { +func (m *CronTaskMutation) LastRun() (r time.Time, exists bool) { v := m.last_run if v == nil { return @@ -5703,7 +5099,7 @@ func (m *CronTaskMutation) LastRun() (r int64, exists bool) { // OldLastRun returns the old "last_run" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldLastRun(ctx context.Context) (v int64, err error) { +func (m *CronTaskMutation) OldLastRun(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastRun is only allowed on UpdateOne operations") } @@ -5717,28 +5113,9 @@ func (m *CronTaskMutation) OldLastRun(ctx context.Context) (v int64, err error) return oldValue.LastRun, nil } -// AddLastRun adds i to the "last_run" field. -func (m *CronTaskMutation) AddLastRun(i int64) { - if m.addlast_run != nil { - *m.addlast_run += i - } else { - m.addlast_run = &i - } -} - -// AddedLastRun returns the value that was added to the "last_run" field in this mutation. -func (m *CronTaskMutation) AddedLastRun() (r int64, exists bool) { - v := m.addlast_run - if v == nil { - return - } - return *v, true -} - // ResetLastRun resets all changes to the "last_run" field. func (m *CronTaskMutation) ResetLastRun() { m.last_run = nil - m.addlast_run = nil } // SetLogs sets the "logs" field. @@ -5960,14 +5337,14 @@ func (m *CronTaskMutation) OldField(ctx context.Context, name string) (ent.Value func (m *CronTaskMutation) SetField(name string, value ent.Value) error { switch name { case crontask.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case crontask.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -6002,14 +5379,14 @@ func (m *CronTaskMutation) SetField(name string, value ent.Value) error { m.SetStatus(v) return nil case crontask.FieldScheduledAfter: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetScheduledAfter(v) return nil case crontask.FieldLastRun: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -6036,36 +5413,13 @@ func (m *CronTaskMutation) SetField(name string, value ent.Value) error { // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *CronTaskMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, crontask.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, crontask.FieldUpdatedAt) - } - if m.addscheduled_after != nil { - fields = append(fields, crontask.FieldScheduledAfter) - } - if m.addlast_run != nil { - fields = append(fields, crontask.FieldLastRun) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *CronTaskMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case crontask.FieldCreatedAt: - return m.AddedCreatedAt() - case crontask.FieldUpdatedAt: - return m.AddedUpdatedAt() - case crontask.FieldScheduledAfter: - return m.AddedScheduledAfter() - case crontask.FieldLastRun: - return m.AddedLastRun() - } return nil, false } @@ -6074,34 +5428,6 @@ func (m *CronTaskMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *CronTaskMutation) AddField(name string, value ent.Value) error { switch name { - case crontask.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case crontask.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil - case crontask.FieldScheduledAfter: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddScheduledAfter(v) - return nil - case crontask.FieldLastRun: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddLastRun(v) - return nil } return fmt.Errorf("unknown CronTask numeric field %s", name) } @@ -6217,10 +5543,8 @@ type DiscordInteractionMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time command *string reference_id *string _type *models.DiscordInteractionType @@ -6339,13 +5663,12 @@ func (m *DiscordInteractionMutation) IDs(ctx context.Context) ([]string, error) } // SetCreatedAt sets the "created_at" field. -func (m *DiscordInteractionMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *DiscordInteractionMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *DiscordInteractionMutation) CreatedAt() (r int64, exists bool) { +func (m *DiscordInteractionMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -6356,7 +5679,7 @@ func (m *DiscordInteractionMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the DiscordInteraction entity. // If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *DiscordInteractionMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -6370,38 +5693,18 @@ func (m *DiscordInteractionMutation) OldCreatedAt(ctx context.Context) (v int64, return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *DiscordInteractionMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *DiscordInteractionMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *DiscordInteractionMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *DiscordInteractionMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *DiscordInteractionMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *DiscordInteractionMutation) UpdatedAt() (r int64, exists bool) { +func (m *DiscordInteractionMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -6412,7 +5715,7 @@ func (m *DiscordInteractionMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the DiscordInteraction entity. // If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *DiscordInteractionMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -6426,28 +5729,9 @@ func (m *DiscordInteractionMutation) OldUpdatedAt(ctx context.Context) (v int64, return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *DiscordInteractionMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *DiscordInteractionMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *DiscordInteractionMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetCommand sets the "command" field. @@ -6811,14 +6095,14 @@ func (m *DiscordInteractionMutation) OldField(ctx context.Context, name string) func (m *DiscordInteractionMutation) SetField(name string, value ent.Value) error { switch name { case discordinteraction.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case discordinteraction.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -6873,26 +6157,13 @@ func (m *DiscordInteractionMutation) SetField(name string, value ent.Value) erro // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *DiscordInteractionMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, discordinteraction.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, discordinteraction.FieldUpdatedAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *DiscordInteractionMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case discordinteraction.FieldCreatedAt: - return m.AddedCreatedAt() - case discordinteraction.FieldUpdatedAt: - return m.AddedUpdatedAt() - } return nil, false } @@ -6901,20 +6172,6 @@ func (m *DiscordInteractionMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *DiscordInteractionMutation) AddField(name string, value ent.Value) error { switch name { - case discordinteraction.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case discordinteraction.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil } return fmt.Errorf("unknown DiscordInteraction numeric field %s", name) } @@ -7050,10 +6307,8 @@ type UserMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time permissions *string feature_flags *[]string appendfeature_flags []string @@ -7180,13 +6435,12 @@ func (m *UserMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *UserMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *UserMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *UserMutation) CreatedAt() (r int64, exists bool) { +func (m *UserMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -7197,7 +6451,7 @@ func (m *UserMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the User entity. // If the User object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *UserMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -7211,38 +6465,18 @@ func (m *UserMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *UserMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *UserMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *UserMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *UserMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *UserMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *UserMutation) UpdatedAt() (r int64, exists bool) { +func (m *UserMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -7253,7 +6487,7 @@ func (m *UserMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the User entity. // If the User object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *UserMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -7267,28 +6501,9 @@ func (m *UserMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *UserMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *UserMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *UserMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetPermissions sets the "permissions" field. @@ -7698,14 +6913,14 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er func (m *UserMutation) SetField(name string, value ent.Value) error { switch name { case user.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case user.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -7732,26 +6947,13 @@ func (m *UserMutation) SetField(name string, value ent.Value) error { // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *UserMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, user.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, user.FieldUpdatedAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *UserMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case user.FieldCreatedAt: - return m.AddedCreatedAt() - case user.FieldUpdatedAt: - return m.AddedUpdatedAt() - } return nil, false } @@ -7760,20 +6962,6 @@ func (m *UserMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *UserMutation) AddField(name string, value ent.Value) error { switch name { - case user.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case user.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil } return fmt.Errorf("unknown User numeric field %s", name) } @@ -7994,10 +7182,8 @@ type UserConnectionMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time _type *models.ConnectionType reference_id *string permissions *string @@ -8115,13 +7301,12 @@ func (m *UserConnectionMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *UserConnectionMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *UserConnectionMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *UserConnectionMutation) CreatedAt() (r int64, exists bool) { +func (m *UserConnectionMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -8132,7 +7317,7 @@ func (m *UserConnectionMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the UserConnection entity. // If the UserConnection object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserConnectionMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *UserConnectionMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -8146,38 +7331,18 @@ func (m *UserConnectionMutation) OldCreatedAt(ctx context.Context) (v int64, err return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *UserConnectionMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *UserConnectionMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *UserConnectionMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *UserConnectionMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *UserConnectionMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *UserConnectionMutation) UpdatedAt() (r int64, exists bool) { +func (m *UserConnectionMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -8188,7 +7353,7 @@ func (m *UserConnectionMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the UserConnection entity. // If the UserConnection object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserConnectionMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *UserConnectionMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -8196,34 +7361,15 @@ func (m *UserConnectionMutation) OldUpdatedAt(ctx context.Context) (v int64, err return v, errors.New("OldUpdatedAt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) - } - return oldValue.UpdatedAt, nil -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (m *UserConnectionMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *UserConnectionMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) } - return *v, true + return oldValue.UpdatedAt, nil } // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *UserConnectionMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetType sets the "type" field. @@ -8570,14 +7716,14 @@ func (m *UserConnectionMutation) OldField(ctx context.Context, name string) (ent func (m *UserConnectionMutation) SetField(name string, value ent.Value) error { switch name { case userconnection.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case userconnection.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -8625,26 +7771,13 @@ func (m *UserConnectionMutation) SetField(name string, value ent.Value) error { // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *UserConnectionMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, userconnection.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, userconnection.FieldUpdatedAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *UserConnectionMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case userconnection.FieldCreatedAt: - return m.AddedCreatedAt() - case userconnection.FieldUpdatedAt: - return m.AddedUpdatedAt() - } return nil, false } @@ -8653,20 +7786,6 @@ func (m *UserConnectionMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *UserConnectionMutation) AddField(name string, value ent.Value) error { switch name { - case userconnection.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case userconnection.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil } return fmt.Errorf("unknown UserConnection numeric field %s", name) } @@ -8814,10 +7933,8 @@ type UserContentMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time _type *models.UserContentType reference_id *string value *string @@ -8935,13 +8052,12 @@ func (m *UserContentMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *UserContentMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *UserContentMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *UserContentMutation) CreatedAt() (r int64, exists bool) { +func (m *UserContentMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -8952,7 +8068,7 @@ func (m *UserContentMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the UserContent entity. // If the UserContent object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserContentMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *UserContentMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -8966,38 +8082,18 @@ func (m *UserContentMutation) OldCreatedAt(ctx context.Context) (v int64, err er return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *UserContentMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *UserContentMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *UserContentMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *UserContentMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *UserContentMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *UserContentMutation) UpdatedAt() (r int64, exists bool) { +func (m *UserContentMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -9008,7 +8104,7 @@ func (m *UserContentMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the UserContent entity. // If the UserContent object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserContentMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *UserContentMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -9022,28 +8118,9 @@ func (m *UserContentMutation) OldUpdatedAt(ctx context.Context) (v int64, err er return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *UserContentMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *UserContentMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *UserContentMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetType sets the "type" field. @@ -9364,14 +8441,14 @@ func (m *UserContentMutation) OldField(ctx context.Context, name string) (ent.Va func (m *UserContentMutation) SetField(name string, value ent.Value) error { switch name { case usercontent.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case usercontent.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -9419,26 +8496,13 @@ func (m *UserContentMutation) SetField(name string, value ent.Value) error { // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *UserContentMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, usercontent.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, usercontent.FieldUpdatedAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *UserContentMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case usercontent.FieldCreatedAt: - return m.AddedCreatedAt() - case usercontent.FieldUpdatedAt: - return m.AddedUpdatedAt() - } return nil, false } @@ -9447,20 +8511,6 @@ func (m *UserContentMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *UserContentMutation) AddField(name string, value ent.Value) error { switch name { - case usercontent.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case usercontent.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil } return fmt.Errorf("unknown UserContent numeric field %s", name) } @@ -9593,13 +8643,10 @@ type UserSubscriptionMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time _type *models.SubscriptionType - expires_at *int64 - addexpires_at *int64 + expires_at *time.Time permissions *string reference_id *string clearedFields map[string]struct{} @@ -9715,13 +8762,12 @@ func (m *UserSubscriptionMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *UserSubscriptionMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *UserSubscriptionMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *UserSubscriptionMutation) CreatedAt() (r int64, exists bool) { +func (m *UserSubscriptionMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -9732,7 +8778,7 @@ func (m *UserSubscriptionMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the UserSubscription entity. // If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserSubscriptionMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *UserSubscriptionMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -9746,38 +8792,18 @@ func (m *UserSubscriptionMutation) OldCreatedAt(ctx context.Context) (v int64, e return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *UserSubscriptionMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *UserSubscriptionMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *UserSubscriptionMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *UserSubscriptionMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *UserSubscriptionMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *UserSubscriptionMutation) UpdatedAt() (r int64, exists bool) { +func (m *UserSubscriptionMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -9788,7 +8814,7 @@ func (m *UserSubscriptionMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the UserSubscription entity. // If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserSubscriptionMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *UserSubscriptionMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -9802,28 +8828,9 @@ func (m *UserSubscriptionMutation) OldUpdatedAt(ctx context.Context) (v int64, e return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *UserSubscriptionMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *UserSubscriptionMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *UserSubscriptionMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetType sets the "type" field. @@ -9863,13 +8870,12 @@ func (m *UserSubscriptionMutation) ResetType() { } // SetExpiresAt sets the "expires_at" field. -func (m *UserSubscriptionMutation) SetExpiresAt(i int64) { - m.expires_at = &i - m.addexpires_at = nil +func (m *UserSubscriptionMutation) SetExpiresAt(t time.Time) { + m.expires_at = &t } // ExpiresAt returns the value of the "expires_at" field in the mutation. -func (m *UserSubscriptionMutation) ExpiresAt() (r int64, exists bool) { +func (m *UserSubscriptionMutation) ExpiresAt() (r time.Time, exists bool) { v := m.expires_at if v == nil { return @@ -9880,7 +8886,7 @@ func (m *UserSubscriptionMutation) ExpiresAt() (r int64, exists bool) { // OldExpiresAt returns the old "expires_at" field's value of the UserSubscription entity. // If the UserSubscription object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserSubscriptionMutation) OldExpiresAt(ctx context.Context) (v int64, err error) { +func (m *UserSubscriptionMutation) OldExpiresAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations") } @@ -9894,28 +8900,9 @@ func (m *UserSubscriptionMutation) OldExpiresAt(ctx context.Context) (v int64, e return oldValue.ExpiresAt, nil } -// AddExpiresAt adds i to the "expires_at" field. -func (m *UserSubscriptionMutation) AddExpiresAt(i int64) { - if m.addexpires_at != nil { - *m.addexpires_at += i - } else { - m.addexpires_at = &i - } -} - -// AddedExpiresAt returns the value that was added to the "expires_at" field in this mutation. -func (m *UserSubscriptionMutation) AddedExpiresAt() (r int64, exists bool) { - v := m.addexpires_at - if v == nil { - return - } - return *v, true -} - // ResetExpiresAt resets all changes to the "expires_at" field. func (m *UserSubscriptionMutation) ResetExpiresAt() { m.expires_at = nil - m.addexpires_at = nil } // SetUserID sets the "user_id" field. @@ -10164,14 +9151,14 @@ func (m *UserSubscriptionMutation) OldField(ctx context.Context, name string) (e func (m *UserSubscriptionMutation) SetField(name string, value ent.Value) error { switch name { case usersubscription.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case usersubscription.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -10185,7 +9172,7 @@ func (m *UserSubscriptionMutation) SetField(name string, value ent.Value) error m.SetType(v) return nil case usersubscription.FieldExpiresAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -10219,31 +9206,13 @@ func (m *UserSubscriptionMutation) SetField(name string, value ent.Value) error // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *UserSubscriptionMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, usersubscription.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, usersubscription.FieldUpdatedAt) - } - if m.addexpires_at != nil { - fields = append(fields, usersubscription.FieldExpiresAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *UserSubscriptionMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case usersubscription.FieldCreatedAt: - return m.AddedCreatedAt() - case usersubscription.FieldUpdatedAt: - return m.AddedUpdatedAt() - case usersubscription.FieldExpiresAt: - return m.AddedExpiresAt() - } return nil, false } @@ -10252,27 +9221,6 @@ func (m *UserSubscriptionMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *UserSubscriptionMutation) AddField(name string, value ent.Value) error { switch name { - case usersubscription.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case usersubscription.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil - case usersubscription.FieldExpiresAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddExpiresAt(v) - return nil } return fmt.Errorf("unknown UserSubscription numeric field %s", name) } @@ -10405,10 +9353,8 @@ type VehicleMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time tier *int addtier *int localized_names *map[string]string @@ -10523,13 +9469,12 @@ func (m *VehicleMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *VehicleMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *VehicleMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *VehicleMutation) CreatedAt() (r int64, exists bool) { +func (m *VehicleMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -10540,7 +9485,7 @@ func (m *VehicleMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the Vehicle entity. // If the Vehicle object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *VehicleMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -10554,38 +9499,18 @@ func (m *VehicleMutation) OldCreatedAt(ctx context.Context) (v int64, err error) return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *VehicleMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *VehicleMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *VehicleMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *VehicleMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *VehicleMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *VehicleMutation) UpdatedAt() (r int64, exists bool) { +func (m *VehicleMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -10596,7 +9521,7 @@ func (m *VehicleMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the Vehicle entity. // If the Vehicle object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *VehicleMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -10610,28 +9535,9 @@ func (m *VehicleMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *VehicleMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *VehicleMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *VehicleMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetTier sets the "tier" field. @@ -10816,14 +9722,14 @@ func (m *VehicleMutation) OldField(ctx context.Context, name string) (ent.Value, func (m *VehicleMutation) SetField(name string, value ent.Value) error { switch name { case vehicle.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case vehicle.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -10851,12 +9757,6 @@ func (m *VehicleMutation) SetField(name string, value ent.Value) error { // this mutation. func (m *VehicleMutation) AddedFields() []string { var fields []string - if m.addcreated_at != nil { - fields = append(fields, vehicle.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, vehicle.FieldUpdatedAt) - } if m.addtier != nil { fields = append(fields, vehicle.FieldTier) } @@ -10868,10 +9768,6 @@ func (m *VehicleMutation) AddedFields() []string { // was not set, or was not defined in the schema. func (m *VehicleMutation) AddedField(name string) (ent.Value, bool) { switch name { - case vehicle.FieldCreatedAt: - return m.AddedCreatedAt() - case vehicle.FieldUpdatedAt: - return m.AddedUpdatedAt() case vehicle.FieldTier: return m.AddedTier() } @@ -10883,20 +9779,6 @@ func (m *VehicleMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *VehicleMutation) AddField(name string, value ent.Value) error { switch name { - case vehicle.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case vehicle.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil case vehicle.FieldTier: v, ok := value.(int) if !ok { @@ -11001,10 +9883,8 @@ type VehicleAverageMutation struct { op Op typ string id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 + created_at *time.Time + updated_at *time.Time data *frame.StatsFrame clearedFields map[string]struct{} done bool @@ -11117,13 +9997,12 @@ func (m *VehicleAverageMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *VehicleAverageMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *VehicleAverageMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *VehicleAverageMutation) CreatedAt() (r int64, exists bool) { +func (m *VehicleAverageMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -11134,7 +10013,7 @@ func (m *VehicleAverageMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the VehicleAverage entity. // If the VehicleAverage object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleAverageMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *VehicleAverageMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -11148,38 +10027,18 @@ func (m *VehicleAverageMutation) OldCreatedAt(ctx context.Context) (v int64, err return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *VehicleAverageMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *VehicleAverageMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *VehicleAverageMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *VehicleAverageMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *VehicleAverageMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *VehicleAverageMutation) UpdatedAt() (r int64, exists bool) { +func (m *VehicleAverageMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -11190,7 +10049,7 @@ func (m *VehicleAverageMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the VehicleAverage entity. // If the VehicleAverage object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleAverageMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *VehicleAverageMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -11204,28 +10063,9 @@ func (m *VehicleAverageMutation) OldUpdatedAt(ctx context.Context) (v int64, err return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *VehicleAverageMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *VehicleAverageMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *VehicleAverageMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetData sets the "data" field. @@ -11347,14 +10187,14 @@ func (m *VehicleAverageMutation) OldField(ctx context.Context, name string) (ent func (m *VehicleAverageMutation) SetField(name string, value ent.Value) error { switch name { case vehicleaverage.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case vehicleaverage.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -11374,26 +10214,13 @@ func (m *VehicleAverageMutation) SetField(name string, value ent.Value) error { // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *VehicleAverageMutation) AddedFields() []string { - var fields []string - if m.addcreated_at != nil { - fields = append(fields, vehicleaverage.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, vehicleaverage.FieldUpdatedAt) - } - return fields + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *VehicleAverageMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case vehicleaverage.FieldCreatedAt: - return m.AddedCreatedAt() - case vehicleaverage.FieldUpdatedAt: - return m.AddedUpdatedAt() - } return nil, false } @@ -11402,20 +10229,6 @@ func (m *VehicleAverageMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *VehicleAverageMutation) AddField(name string, value ent.Value) error { switch name { - case vehicleaverage.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case vehicleaverage.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil } return fmt.Errorf("unknown VehicleAverage numeric field %s", name) } @@ -11507,27 +10320,24 @@ func (m *VehicleAverageMutation) ResetEdge(name string) error { // VehicleSnapshotMutation represents an operation that mutates the VehicleSnapshot nodes in the graph. type VehicleSnapshotMutation struct { config - op Op - typ string - id *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 - _type *models.SnapshotType - vehicle_id *string - reference_id *string - battles *int - addbattles *int - last_battle_time *int64 - addlast_battle_time *int64 - frame *frame.StatsFrame - clearedFields map[string]struct{} - account *string - clearedaccount bool - done bool - oldValue func(context.Context) (*VehicleSnapshot, error) - predicates []predicate.VehicleSnapshot + op Op + typ string + id *string + created_at *time.Time + updated_at *time.Time + _type *models.SnapshotType + vehicle_id *string + reference_id *string + battles *int + addbattles *int + last_battle_time *time.Time + frame *frame.StatsFrame + clearedFields map[string]struct{} + account *string + clearedaccount bool + done bool + oldValue func(context.Context) (*VehicleSnapshot, error) + predicates []predicate.VehicleSnapshot } var _ ent.Mutation = (*VehicleSnapshotMutation)(nil) @@ -11635,13 +10445,12 @@ func (m *VehicleSnapshotMutation) IDs(ctx context.Context) ([]string, error) { } // SetCreatedAt sets the "created_at" field. -func (m *VehicleSnapshotMutation) SetCreatedAt(i int64) { - m.created_at = &i - m.addcreated_at = nil +func (m *VehicleSnapshotMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *VehicleSnapshotMutation) CreatedAt() (r int64, exists bool) { +func (m *VehicleSnapshotMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -11652,7 +10461,7 @@ func (m *VehicleSnapshotMutation) CreatedAt() (r int64, exists bool) { // OldCreatedAt returns the old "created_at" field's value of the VehicleSnapshot entity. // If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleSnapshotMutation) OldCreatedAt(ctx context.Context) (v int64, err error) { +func (m *VehicleSnapshotMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -11666,38 +10475,18 @@ func (m *VehicleSnapshotMutation) OldCreatedAt(ctx context.Context) (v int64, er return oldValue.CreatedAt, nil } -// AddCreatedAt adds i to the "created_at" field. -func (m *VehicleSnapshotMutation) AddCreatedAt(i int64) { - if m.addcreated_at != nil { - *m.addcreated_at += i - } else { - m.addcreated_at = &i - } -} - -// AddedCreatedAt returns the value that was added to the "created_at" field in this mutation. -func (m *VehicleSnapshotMutation) AddedCreatedAt() (r int64, exists bool) { - v := m.addcreated_at - if v == nil { - return - } - return *v, true -} - // ResetCreatedAt resets all changes to the "created_at" field. func (m *VehicleSnapshotMutation) ResetCreatedAt() { m.created_at = nil - m.addcreated_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *VehicleSnapshotMutation) SetUpdatedAt(i int64) { - m.updated_at = &i - m.addupdated_at = nil +func (m *VehicleSnapshotMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *VehicleSnapshotMutation) UpdatedAt() (r int64, exists bool) { +func (m *VehicleSnapshotMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -11708,7 +10497,7 @@ func (m *VehicleSnapshotMutation) UpdatedAt() (r int64, exists bool) { // OldUpdatedAt returns the old "updated_at" field's value of the VehicleSnapshot entity. // If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int64, err error) { +func (m *VehicleSnapshotMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -11722,28 +10511,9 @@ func (m *VehicleSnapshotMutation) OldUpdatedAt(ctx context.Context) (v int64, er return oldValue.UpdatedAt, nil } -// AddUpdatedAt adds i to the "updated_at" field. -func (m *VehicleSnapshotMutation) AddUpdatedAt(i int64) { - if m.addupdated_at != nil { - *m.addupdated_at += i - } else { - m.addupdated_at = &i - } -} - -// AddedUpdatedAt returns the value that was added to the "updated_at" field in this mutation. -func (m *VehicleSnapshotMutation) AddedUpdatedAt() (r int64, exists bool) { - v := m.addupdated_at - if v == nil { - return - } - return *v, true -} - // ResetUpdatedAt resets all changes to the "updated_at" field. func (m *VehicleSnapshotMutation) ResetUpdatedAt() { m.updated_at = nil - m.addupdated_at = nil } // SetType sets the "type" field. @@ -11947,13 +10717,12 @@ func (m *VehicleSnapshotMutation) ResetBattles() { } // SetLastBattleTime sets the "last_battle_time" field. -func (m *VehicleSnapshotMutation) SetLastBattleTime(i int64) { - m.last_battle_time = &i - m.addlast_battle_time = nil +func (m *VehicleSnapshotMutation) SetLastBattleTime(t time.Time) { + m.last_battle_time = &t } // LastBattleTime returns the value of the "last_battle_time" field in the mutation. -func (m *VehicleSnapshotMutation) LastBattleTime() (r int64, exists bool) { +func (m *VehicleSnapshotMutation) LastBattleTime() (r time.Time, exists bool) { v := m.last_battle_time if v == nil { return @@ -11964,7 +10733,7 @@ func (m *VehicleSnapshotMutation) LastBattleTime() (r int64, exists bool) { // OldLastBattleTime returns the old "last_battle_time" field's value of the VehicleSnapshot entity. // If the VehicleSnapshot object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *VehicleSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int64, err error) { +func (m *VehicleSnapshotMutation) OldLastBattleTime(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastBattleTime is only allowed on UpdateOne operations") } @@ -11978,28 +10747,9 @@ func (m *VehicleSnapshotMutation) OldLastBattleTime(ctx context.Context) (v int6 return oldValue.LastBattleTime, nil } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (m *VehicleSnapshotMutation) AddLastBattleTime(i int64) { - if m.addlast_battle_time != nil { - *m.addlast_battle_time += i - } else { - m.addlast_battle_time = &i - } -} - -// AddedLastBattleTime returns the value that was added to the "last_battle_time" field in this mutation. -func (m *VehicleSnapshotMutation) AddedLastBattleTime() (r int64, exists bool) { - v := m.addlast_battle_time - if v == nil { - return - } - return *v, true -} - // ResetLastBattleTime resets all changes to the "last_battle_time" field. func (m *VehicleSnapshotMutation) ResetLastBattleTime() { m.last_battle_time = nil - m.addlast_battle_time = nil } // SetFrame sets the "frame" field. @@ -12190,14 +10940,14 @@ func (m *VehicleSnapshotMutation) OldField(ctx context.Context, name string) (en func (m *VehicleSnapshotMutation) SetField(name string, value ent.Value) error { switch name { case vehiclesnapshot.FieldCreatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case vehiclesnapshot.FieldUpdatedAt: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -12239,7 +10989,7 @@ func (m *VehicleSnapshotMutation) SetField(name string, value ent.Value) error { m.SetBattles(v) return nil case vehiclesnapshot.FieldLastBattleTime: - v, ok := value.(int64) + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -12260,18 +11010,9 @@ func (m *VehicleSnapshotMutation) SetField(name string, value ent.Value) error { // this mutation. func (m *VehicleSnapshotMutation) AddedFields() []string { var fields []string - if m.addcreated_at != nil { - fields = append(fields, vehiclesnapshot.FieldCreatedAt) - } - if m.addupdated_at != nil { - fields = append(fields, vehiclesnapshot.FieldUpdatedAt) - } if m.addbattles != nil { fields = append(fields, vehiclesnapshot.FieldBattles) } - if m.addlast_battle_time != nil { - fields = append(fields, vehiclesnapshot.FieldLastBattleTime) - } return fields } @@ -12280,14 +11021,8 @@ func (m *VehicleSnapshotMutation) AddedFields() []string { // was not set, or was not defined in the schema. func (m *VehicleSnapshotMutation) AddedField(name string) (ent.Value, bool) { switch name { - case vehiclesnapshot.FieldCreatedAt: - return m.AddedCreatedAt() - case vehiclesnapshot.FieldUpdatedAt: - return m.AddedUpdatedAt() case vehiclesnapshot.FieldBattles: return m.AddedBattles() - case vehiclesnapshot.FieldLastBattleTime: - return m.AddedLastBattleTime() } return nil, false } @@ -12297,20 +11032,6 @@ func (m *VehicleSnapshotMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *VehicleSnapshotMutation) AddField(name string, value ent.Value) error { switch name { - case vehiclesnapshot.FieldCreatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCreatedAt(v) - return nil - case vehiclesnapshot.FieldUpdatedAt: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddUpdatedAt(v) - return nil case vehiclesnapshot.FieldBattles: v, ok := value.(int) if !ok { @@ -12318,13 +11039,6 @@ func (m *VehicleSnapshotMutation) AddField(name string, value ent.Value) error { } m.AddBattles(v) return nil - case vehiclesnapshot.FieldLastBattleTime: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddLastBattleTime(v) - return nil } return fmt.Errorf("unknown VehicleSnapshot numeric field %s", name) } diff --git a/internal/database/ent/db/runtime.go b/internal/database/ent/db/runtime.go index c760c687..61052e6b 100644 --- a/internal/database/ent/db/runtime.go +++ b/internal/database/ent/db/runtime.go @@ -3,6 +3,8 @@ package db import ( + "time" + "github.com/cufee/aftermath/internal/database/ent/db/account" "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" @@ -30,13 +32,13 @@ func init() { // accountDescCreatedAt is the schema descriptor for created_at field. accountDescCreatedAt := accountFields[1].Descriptor() // account.DefaultCreatedAt holds the default value on creation for the created_at field. - account.DefaultCreatedAt = accountDescCreatedAt.Default.(func() int64) + account.DefaultCreatedAt = accountDescCreatedAt.Default.(func() time.Time) // accountDescUpdatedAt is the schema descriptor for updated_at field. accountDescUpdatedAt := accountFields[2].Descriptor() // account.DefaultUpdatedAt holds the default value on creation for the updated_at field. - account.DefaultUpdatedAt = accountDescUpdatedAt.Default.(func() int64) + account.DefaultUpdatedAt = accountDescUpdatedAt.Default.(func() time.Time) // account.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - account.UpdateDefaultUpdatedAt = accountDescUpdatedAt.UpdateDefault.(func() int64) + account.UpdateDefaultUpdatedAt = accountDescUpdatedAt.UpdateDefault.(func() time.Time) // accountDescRealm is the schema descriptor for realm field. accountDescRealm := accountFields[5].Descriptor() // account.RealmValidator is a validator for the "realm" field. It is called by the builders before save. @@ -68,13 +70,13 @@ func init() { // accountsnapshotDescCreatedAt is the schema descriptor for created_at field. accountsnapshotDescCreatedAt := accountsnapshotFields[1].Descriptor() // accountsnapshot.DefaultCreatedAt holds the default value on creation for the created_at field. - accountsnapshot.DefaultCreatedAt = accountsnapshotDescCreatedAt.Default.(func() int64) + accountsnapshot.DefaultCreatedAt = accountsnapshotDescCreatedAt.Default.(func() time.Time) // accountsnapshotDescUpdatedAt is the schema descriptor for updated_at field. accountsnapshotDescUpdatedAt := accountsnapshotFields[2].Descriptor() // accountsnapshot.DefaultUpdatedAt holds the default value on creation for the updated_at field. - accountsnapshot.DefaultUpdatedAt = accountsnapshotDescUpdatedAt.Default.(func() int64) + accountsnapshot.DefaultUpdatedAt = accountsnapshotDescUpdatedAt.Default.(func() time.Time) // accountsnapshot.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - accountsnapshot.UpdateDefaultUpdatedAt = accountsnapshotDescUpdatedAt.UpdateDefault.(func() int64) + accountsnapshot.UpdateDefaultUpdatedAt = accountsnapshotDescUpdatedAt.UpdateDefault.(func() time.Time) // accountsnapshotDescAccountID is the schema descriptor for account_id field. accountsnapshotDescAccountID := accountsnapshotFields[5].Descriptor() // accountsnapshot.AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. @@ -92,13 +94,13 @@ func init() { // achievementssnapshotDescCreatedAt is the schema descriptor for created_at field. achievementssnapshotDescCreatedAt := achievementssnapshotFields[1].Descriptor() // achievementssnapshot.DefaultCreatedAt holds the default value on creation for the created_at field. - achievementssnapshot.DefaultCreatedAt = achievementssnapshotDescCreatedAt.Default.(func() int64) + achievementssnapshot.DefaultCreatedAt = achievementssnapshotDescCreatedAt.Default.(func() time.Time) // achievementssnapshotDescUpdatedAt is the schema descriptor for updated_at field. achievementssnapshotDescUpdatedAt := achievementssnapshotFields[2].Descriptor() // achievementssnapshot.DefaultUpdatedAt holds the default value on creation for the updated_at field. - achievementssnapshot.DefaultUpdatedAt = achievementssnapshotDescUpdatedAt.Default.(func() int64) + achievementssnapshot.DefaultUpdatedAt = achievementssnapshotDescUpdatedAt.Default.(func() time.Time) // achievementssnapshot.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - achievementssnapshot.UpdateDefaultUpdatedAt = achievementssnapshotDescUpdatedAt.UpdateDefault.(func() int64) + achievementssnapshot.UpdateDefaultUpdatedAt = achievementssnapshotDescUpdatedAt.UpdateDefault.(func() time.Time) // achievementssnapshotDescAccountID is the schema descriptor for account_id field. achievementssnapshotDescAccountID := achievementssnapshotFields[4].Descriptor() // achievementssnapshot.AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. @@ -116,13 +118,13 @@ func init() { // appconfigurationDescCreatedAt is the schema descriptor for created_at field. appconfigurationDescCreatedAt := appconfigurationFields[1].Descriptor() // appconfiguration.DefaultCreatedAt holds the default value on creation for the created_at field. - appconfiguration.DefaultCreatedAt = appconfigurationDescCreatedAt.Default.(func() int64) + appconfiguration.DefaultCreatedAt = appconfigurationDescCreatedAt.Default.(func() time.Time) // appconfigurationDescUpdatedAt is the schema descriptor for updated_at field. appconfigurationDescUpdatedAt := appconfigurationFields[2].Descriptor() // appconfiguration.DefaultUpdatedAt holds the default value on creation for the updated_at field. - appconfiguration.DefaultUpdatedAt = appconfigurationDescUpdatedAt.Default.(func() int64) + appconfiguration.DefaultUpdatedAt = appconfigurationDescUpdatedAt.Default.(func() time.Time) // appconfiguration.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - appconfiguration.UpdateDefaultUpdatedAt = appconfigurationDescUpdatedAt.UpdateDefault.(func() int64) + appconfiguration.UpdateDefaultUpdatedAt = appconfigurationDescUpdatedAt.UpdateDefault.(func() time.Time) // appconfigurationDescKey is the schema descriptor for key field. appconfigurationDescKey := appconfigurationFields[3].Descriptor() // appconfiguration.KeyValidator is a validator for the "key" field. It is called by the builders before save. @@ -136,13 +138,13 @@ func init() { // applicationcommandDescCreatedAt is the schema descriptor for created_at field. applicationcommandDescCreatedAt := applicationcommandFields[1].Descriptor() // applicationcommand.DefaultCreatedAt holds the default value on creation for the created_at field. - applicationcommand.DefaultCreatedAt = applicationcommandDescCreatedAt.Default.(func() int64) + applicationcommand.DefaultCreatedAt = applicationcommandDescCreatedAt.Default.(func() time.Time) // applicationcommandDescUpdatedAt is the schema descriptor for updated_at field. applicationcommandDescUpdatedAt := applicationcommandFields[2].Descriptor() // applicationcommand.DefaultUpdatedAt holds the default value on creation for the updated_at field. - applicationcommand.DefaultUpdatedAt = applicationcommandDescUpdatedAt.Default.(func() int64) + applicationcommand.DefaultUpdatedAt = applicationcommandDescUpdatedAt.Default.(func() time.Time) // applicationcommand.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - applicationcommand.UpdateDefaultUpdatedAt = applicationcommandDescUpdatedAt.UpdateDefault.(func() int64) + applicationcommand.UpdateDefaultUpdatedAt = applicationcommandDescUpdatedAt.UpdateDefault.(func() time.Time) // applicationcommandDescName is the schema descriptor for name field. applicationcommandDescName := applicationcommandFields[3].Descriptor() // applicationcommand.NameValidator is a validator for the "name" field. It is called by the builders before save. @@ -164,13 +166,13 @@ func init() { // clanDescCreatedAt is the schema descriptor for created_at field. clanDescCreatedAt := clanFields[1].Descriptor() // clan.DefaultCreatedAt holds the default value on creation for the created_at field. - clan.DefaultCreatedAt = clanDescCreatedAt.Default.(func() int64) + clan.DefaultCreatedAt = clanDescCreatedAt.Default.(func() time.Time) // clanDescUpdatedAt is the schema descriptor for updated_at field. clanDescUpdatedAt := clanFields[2].Descriptor() // clan.DefaultUpdatedAt holds the default value on creation for the updated_at field. - clan.DefaultUpdatedAt = clanDescUpdatedAt.Default.(func() int64) + clan.DefaultUpdatedAt = clanDescUpdatedAt.Default.(func() time.Time) // clan.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - clan.UpdateDefaultUpdatedAt = clanDescUpdatedAt.UpdateDefault.(func() int64) + clan.UpdateDefaultUpdatedAt = clanDescUpdatedAt.UpdateDefault.(func() time.Time) // clanDescTag is the schema descriptor for tag field. clanDescTag := clanFields[3].Descriptor() // clan.TagValidator is a validator for the "tag" field. It is called by the builders before save. @@ -188,13 +190,13 @@ func init() { // crontaskDescCreatedAt is the schema descriptor for created_at field. crontaskDescCreatedAt := crontaskFields[1].Descriptor() // crontask.DefaultCreatedAt holds the default value on creation for the created_at field. - crontask.DefaultCreatedAt = crontaskDescCreatedAt.Default.(func() int64) + crontask.DefaultCreatedAt = crontaskDescCreatedAt.Default.(func() time.Time) // crontaskDescUpdatedAt is the schema descriptor for updated_at field. crontaskDescUpdatedAt := crontaskFields[2].Descriptor() // crontask.DefaultUpdatedAt holds the default value on creation for the updated_at field. - crontask.DefaultUpdatedAt = crontaskDescUpdatedAt.Default.(func() int64) + crontask.DefaultUpdatedAt = crontaskDescUpdatedAt.Default.(func() time.Time) // crontask.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - crontask.UpdateDefaultUpdatedAt = crontaskDescUpdatedAt.UpdateDefault.(func() int64) + crontask.UpdateDefaultUpdatedAt = crontaskDescUpdatedAt.UpdateDefault.(func() time.Time) // crontaskDescReferenceID is the schema descriptor for reference_id field. crontaskDescReferenceID := crontaskFields[4].Descriptor() // crontask.ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. @@ -208,13 +210,13 @@ func init() { // discordinteractionDescCreatedAt is the schema descriptor for created_at field. discordinteractionDescCreatedAt := discordinteractionFields[1].Descriptor() // discordinteraction.DefaultCreatedAt holds the default value on creation for the created_at field. - discordinteraction.DefaultCreatedAt = discordinteractionDescCreatedAt.Default.(func() int64) + discordinteraction.DefaultCreatedAt = discordinteractionDescCreatedAt.Default.(func() time.Time) // discordinteractionDescUpdatedAt is the schema descriptor for updated_at field. discordinteractionDescUpdatedAt := discordinteractionFields[2].Descriptor() // discordinteraction.DefaultUpdatedAt holds the default value on creation for the updated_at field. - discordinteraction.DefaultUpdatedAt = discordinteractionDescUpdatedAt.Default.(func() int64) + discordinteraction.DefaultUpdatedAt = discordinteractionDescUpdatedAt.Default.(func() time.Time) // discordinteraction.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - discordinteraction.UpdateDefaultUpdatedAt = discordinteractionDescUpdatedAt.UpdateDefault.(func() int64) + discordinteraction.UpdateDefaultUpdatedAt = discordinteractionDescUpdatedAt.UpdateDefault.(func() time.Time) // discordinteractionDescCommand is the schema descriptor for command field. discordinteractionDescCommand := discordinteractionFields[3].Descriptor() // discordinteraction.CommandValidator is a validator for the "command" field. It is called by the builders before save. @@ -236,13 +238,13 @@ func init() { // userDescCreatedAt is the schema descriptor for created_at field. userDescCreatedAt := userFields[1].Descriptor() // user.DefaultCreatedAt holds the default value on creation for the created_at field. - user.DefaultCreatedAt = userDescCreatedAt.Default.(func() int64) + user.DefaultCreatedAt = userDescCreatedAt.Default.(func() time.Time) // userDescUpdatedAt is the schema descriptor for updated_at field. userDescUpdatedAt := userFields[2].Descriptor() // user.DefaultUpdatedAt holds the default value on creation for the updated_at field. - user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() int64) + user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time) // user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() int64) + user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() time.Time) // userDescPermissions is the schema descriptor for permissions field. userDescPermissions := userFields[3].Descriptor() // user.DefaultPermissions holds the default value on creation for the permissions field. @@ -252,13 +254,13 @@ func init() { // userconnectionDescCreatedAt is the schema descriptor for created_at field. userconnectionDescCreatedAt := userconnectionFields[1].Descriptor() // userconnection.DefaultCreatedAt holds the default value on creation for the created_at field. - userconnection.DefaultCreatedAt = userconnectionDescCreatedAt.Default.(func() int64) + userconnection.DefaultCreatedAt = userconnectionDescCreatedAt.Default.(func() time.Time) // userconnectionDescUpdatedAt is the schema descriptor for updated_at field. userconnectionDescUpdatedAt := userconnectionFields[2].Descriptor() // userconnection.DefaultUpdatedAt holds the default value on creation for the updated_at field. - userconnection.DefaultUpdatedAt = userconnectionDescUpdatedAt.Default.(func() int64) + userconnection.DefaultUpdatedAt = userconnectionDescUpdatedAt.Default.(func() time.Time) // userconnection.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - userconnection.UpdateDefaultUpdatedAt = userconnectionDescUpdatedAt.UpdateDefault.(func() int64) + userconnection.UpdateDefaultUpdatedAt = userconnectionDescUpdatedAt.UpdateDefault.(func() time.Time) // userconnectionDescPermissions is the schema descriptor for permissions field. userconnectionDescPermissions := userconnectionFields[6].Descriptor() // userconnection.DefaultPermissions holds the default value on creation for the permissions field. @@ -272,13 +274,13 @@ func init() { // usercontentDescCreatedAt is the schema descriptor for created_at field. usercontentDescCreatedAt := usercontentFields[1].Descriptor() // usercontent.DefaultCreatedAt holds the default value on creation for the created_at field. - usercontent.DefaultCreatedAt = usercontentDescCreatedAt.Default.(func() int64) + usercontent.DefaultCreatedAt = usercontentDescCreatedAt.Default.(func() time.Time) // usercontentDescUpdatedAt is the schema descriptor for updated_at field. usercontentDescUpdatedAt := usercontentFields[2].Descriptor() // usercontent.DefaultUpdatedAt holds the default value on creation for the updated_at field. - usercontent.DefaultUpdatedAt = usercontentDescUpdatedAt.Default.(func() int64) + usercontent.DefaultUpdatedAt = usercontentDescUpdatedAt.Default.(func() time.Time) // usercontent.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - usercontent.UpdateDefaultUpdatedAt = usercontentDescUpdatedAt.UpdateDefault.(func() int64) + usercontent.UpdateDefaultUpdatedAt = usercontentDescUpdatedAt.UpdateDefault.(func() time.Time) // usercontentDescID is the schema descriptor for id field. usercontentDescID := usercontentFields[0].Descriptor() // usercontent.DefaultID holds the default value on creation for the id field. @@ -288,13 +290,13 @@ func init() { // usersubscriptionDescCreatedAt is the schema descriptor for created_at field. usersubscriptionDescCreatedAt := usersubscriptionFields[1].Descriptor() // usersubscription.DefaultCreatedAt holds the default value on creation for the created_at field. - usersubscription.DefaultCreatedAt = usersubscriptionDescCreatedAt.Default.(func() int64) + usersubscription.DefaultCreatedAt = usersubscriptionDescCreatedAt.Default.(func() time.Time) // usersubscriptionDescUpdatedAt is the schema descriptor for updated_at field. usersubscriptionDescUpdatedAt := usersubscriptionFields[2].Descriptor() // usersubscription.DefaultUpdatedAt holds the default value on creation for the updated_at field. - usersubscription.DefaultUpdatedAt = usersubscriptionDescUpdatedAt.Default.(func() int64) + usersubscription.DefaultUpdatedAt = usersubscriptionDescUpdatedAt.Default.(func() time.Time) // usersubscription.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - usersubscription.UpdateDefaultUpdatedAt = usersubscriptionDescUpdatedAt.UpdateDefault.(func() int64) + usersubscription.UpdateDefaultUpdatedAt = usersubscriptionDescUpdatedAt.UpdateDefault.(func() time.Time) // usersubscriptionDescUserID is the schema descriptor for user_id field. usersubscriptionDescUserID := usersubscriptionFields[5].Descriptor() // usersubscription.UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. @@ -316,13 +318,13 @@ func init() { // vehicleDescCreatedAt is the schema descriptor for created_at field. vehicleDescCreatedAt := vehicleFields[1].Descriptor() // vehicle.DefaultCreatedAt holds the default value on creation for the created_at field. - vehicle.DefaultCreatedAt = vehicleDescCreatedAt.Default.(func() int64) + vehicle.DefaultCreatedAt = vehicleDescCreatedAt.Default.(func() time.Time) // vehicleDescUpdatedAt is the schema descriptor for updated_at field. vehicleDescUpdatedAt := vehicleFields[2].Descriptor() // vehicle.DefaultUpdatedAt holds the default value on creation for the updated_at field. - vehicle.DefaultUpdatedAt = vehicleDescUpdatedAt.Default.(func() int64) + vehicle.DefaultUpdatedAt = vehicleDescUpdatedAt.Default.(func() time.Time) // vehicle.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - vehicle.UpdateDefaultUpdatedAt = vehicleDescUpdatedAt.UpdateDefault.(func() int64) + vehicle.UpdateDefaultUpdatedAt = vehicleDescUpdatedAt.UpdateDefault.(func() time.Time) // vehicleDescTier is the schema descriptor for tier field. vehicleDescTier := vehicleFields[3].Descriptor() // vehicle.TierValidator is a validator for the "tier" field. It is called by the builders before save. @@ -346,25 +348,25 @@ func init() { // vehicleaverageDescCreatedAt is the schema descriptor for created_at field. vehicleaverageDescCreatedAt := vehicleaverageFields[1].Descriptor() // vehicleaverage.DefaultCreatedAt holds the default value on creation for the created_at field. - vehicleaverage.DefaultCreatedAt = vehicleaverageDescCreatedAt.Default.(func() int64) + vehicleaverage.DefaultCreatedAt = vehicleaverageDescCreatedAt.Default.(func() time.Time) // vehicleaverageDescUpdatedAt is the schema descriptor for updated_at field. vehicleaverageDescUpdatedAt := vehicleaverageFields[2].Descriptor() // vehicleaverage.DefaultUpdatedAt holds the default value on creation for the updated_at field. - vehicleaverage.DefaultUpdatedAt = vehicleaverageDescUpdatedAt.Default.(func() int64) + vehicleaverage.DefaultUpdatedAt = vehicleaverageDescUpdatedAt.Default.(func() time.Time) // vehicleaverage.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - vehicleaverage.UpdateDefaultUpdatedAt = vehicleaverageDescUpdatedAt.UpdateDefault.(func() int64) + vehicleaverage.UpdateDefaultUpdatedAt = vehicleaverageDescUpdatedAt.UpdateDefault.(func() time.Time) vehiclesnapshotFields := schema.VehicleSnapshot{}.Fields() _ = vehiclesnapshotFields // vehiclesnapshotDescCreatedAt is the schema descriptor for created_at field. vehiclesnapshotDescCreatedAt := vehiclesnapshotFields[1].Descriptor() // vehiclesnapshot.DefaultCreatedAt holds the default value on creation for the created_at field. - vehiclesnapshot.DefaultCreatedAt = vehiclesnapshotDescCreatedAt.Default.(func() int64) + vehiclesnapshot.DefaultCreatedAt = vehiclesnapshotDescCreatedAt.Default.(func() time.Time) // vehiclesnapshotDescUpdatedAt is the schema descriptor for updated_at field. vehiclesnapshotDescUpdatedAt := vehiclesnapshotFields[2].Descriptor() // vehiclesnapshot.DefaultUpdatedAt holds the default value on creation for the updated_at field. - vehiclesnapshot.DefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.Default.(func() int64) + vehiclesnapshot.DefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.Default.(func() time.Time) // vehiclesnapshot.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. - vehiclesnapshot.UpdateDefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.UpdateDefault.(func() int64) + vehiclesnapshot.UpdateDefaultUpdatedAt = vehiclesnapshotDescUpdatedAt.UpdateDefault.(func() time.Time) // vehiclesnapshotDescAccountID is the schema descriptor for account_id field. vehiclesnapshotDescAccountID := vehiclesnapshotFields[4].Descriptor() // vehiclesnapshot.AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. diff --git a/internal/database/ent/db/user.go b/internal/database/ent/db/user.go index 0c537425..f94bd4f6 100644 --- a/internal/database/ent/db/user.go +++ b/internal/database/ent/db/user.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -18,9 +19,9 @@ type User struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Permissions holds the value of the "permissions" field. Permissions string `json:"permissions,omitempty"` // FeatureFlags holds the value of the "feature_flags" field. @@ -89,10 +90,10 @@ func (*User) scanValues(columns []string) ([]any, error) { switch columns[i] { case user.FieldFeatureFlags: values[i] = new([]byte) - case user.FieldCreatedAt, user.FieldUpdatedAt: - values[i] = new(sql.NullInt64) case user.FieldID, user.FieldPermissions: values[i] = new(sql.NullString) + case user.FieldCreatedAt, user.FieldUpdatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -115,16 +116,16 @@ func (u *User) assignValues(columns []string, values []any) error { u.ID = value.String } case user.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - u.CreatedAt = value.Int64 + u.CreatedAt = value.Time } case user.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - u.UpdatedAt = value.Int64 + u.UpdatedAt = value.Time } case user.FieldPermissions: if value, ok := values[i].(*sql.NullString); !ok { @@ -197,10 +198,10 @@ func (u *User) String() string { builder.WriteString("User(") builder.WriteString(fmt.Sprintf("id=%v, ", u.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", u.CreatedAt)) + builder.WriteString(u.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", u.UpdatedAt)) + builder.WriteString(u.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("permissions=") builder.WriteString(u.Permissions) diff --git a/internal/database/ent/db/user/user.go b/internal/database/ent/db/user/user.go index 524bdf47..e4787254 100644 --- a/internal/database/ent/db/user/user.go +++ b/internal/database/ent/db/user/user.go @@ -3,6 +3,8 @@ package user import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" ) @@ -81,11 +83,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // DefaultPermissions holds the default value on creation for the "permissions" field. DefaultPermissions string ) diff --git a/internal/database/ent/db/user/where.go b/internal/database/ent/db/user/where.go index c3a5e922..989d46ba 100644 --- a/internal/database/ent/db/user/where.go +++ b/internal/database/ent/db/user/where.go @@ -3,6 +3,8 @@ package user import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -64,12 +66,12 @@ func IDContainsFold(id string) predicate.User { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.User { +func CreatedAt(v time.Time) predicate.User { return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.User { +func UpdatedAt(v time.Time) predicate.User { return predicate.User(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -79,82 +81,82 @@ func Permissions(v string) predicate.User { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.User { +func CreatedAtEQ(v time.Time) predicate.User { return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.User { +func CreatedAtNEQ(v time.Time) predicate.User { return predicate.User(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.User { +func CreatedAtIn(vs ...time.Time) predicate.User { return predicate.User(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.User { +func CreatedAtNotIn(vs ...time.Time) predicate.User { return predicate.User(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.User { +func CreatedAtGT(v time.Time) predicate.User { return predicate.User(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.User { +func CreatedAtGTE(v time.Time) predicate.User { return predicate.User(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.User { +func CreatedAtLT(v time.Time) predicate.User { return predicate.User(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.User { +func CreatedAtLTE(v time.Time) predicate.User { return predicate.User(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.User { +func UpdatedAtEQ(v time.Time) predicate.User { return predicate.User(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.User { +func UpdatedAtNEQ(v time.Time) predicate.User { return predicate.User(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.User { +func UpdatedAtIn(vs ...time.Time) predicate.User { return predicate.User(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.User { +func UpdatedAtNotIn(vs ...time.Time) predicate.User { return predicate.User(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.User { +func UpdatedAtGT(v time.Time) predicate.User { return predicate.User(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.User { +func UpdatedAtGTE(v time.Time) predicate.User { return predicate.User(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.User { +func UpdatedAtLT(v time.Time) predicate.User { return predicate.User(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.User { +func UpdatedAtLTE(v time.Time) predicate.User { return predicate.User(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/user_create.go b/internal/database/ent/db/user_create.go index 1aa4e5e8..8cf24699 100644 --- a/internal/database/ent/db/user_create.go +++ b/internal/database/ent/db/user_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -24,29 +25,29 @@ type UserCreate struct { } // SetCreatedAt sets the "created_at" field. -func (uc *UserCreate) SetCreatedAt(i int64) *UserCreate { - uc.mutation.SetCreatedAt(i) +func (uc *UserCreate) SetCreatedAt(t time.Time) *UserCreate { + uc.mutation.SetCreatedAt(t) return uc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (uc *UserCreate) SetNillableCreatedAt(i *int64) *UserCreate { - if i != nil { - uc.SetCreatedAt(*i) +func (uc *UserCreate) SetNillableCreatedAt(t *time.Time) *UserCreate { + if t != nil { + uc.SetCreatedAt(*t) } return uc } // SetUpdatedAt sets the "updated_at" field. -func (uc *UserCreate) SetUpdatedAt(i int64) *UserCreate { - uc.mutation.SetUpdatedAt(i) +func (uc *UserCreate) SetUpdatedAt(t time.Time) *UserCreate { + uc.mutation.SetUpdatedAt(t) return uc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (uc *UserCreate) SetNillableUpdatedAt(i *int64) *UserCreate { - if i != nil { - uc.SetUpdatedAt(*i) +func (uc *UserCreate) SetNillableUpdatedAt(t *time.Time) *UserCreate { + if t != nil { + uc.SetUpdatedAt(*t) } return uc } @@ -233,11 +234,11 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := uc.mutation.CreatedAt(); ok { - _spec.SetField(user.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(user.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := uc.mutation.UpdatedAt(); ok { - _spec.SetField(user.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := uc.mutation.Permissions(); ok { diff --git a/internal/database/ent/db/user_query.go b/internal/database/ent/db/user_query.go index 1416b0f7..2e37a52c 100644 --- a/internal/database/ent/db/user_query.go +++ b/internal/database/ent/db/user_query.go @@ -30,6 +30,7 @@ type UserQuery struct { withSubscriptions *UserSubscriptionQuery withConnections *UserConnectionQuery withContent *UserContentQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -406,7 +407,7 @@ func (uq *UserQuery) WithContent(opts ...func(*UserContentQuery)) *UserQuery { // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -429,7 +430,7 @@ func (uq *UserQuery) GroupBy(field string, fields ...string) *UserGroupBy { // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.User.Query(). @@ -494,6 +495,9 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(uq.modifiers) > 0 { + _spec.Modifiers = uq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -659,6 +663,9 @@ func (uq *UserQuery) loadContent(ctx context.Context, query *UserContentQuery, n func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) { _spec := uq.querySpec() + if len(uq.modifiers) > 0 { + _spec.Modifiers = uq.modifiers + } _spec.Node.Columns = uq.ctx.Fields if len(uq.ctx.Fields) > 0 { _spec.Unique = uq.ctx.Unique != nil && *uq.ctx.Unique @@ -721,6 +728,9 @@ func (uq *UserQuery) sqlQuery(ctx context.Context) *sql.Selector { if uq.ctx.Unique != nil && *uq.ctx.Unique { selector.Distinct() } + for _, m := range uq.modifiers { + m(selector) + } for _, p := range uq.predicates { p(selector) } @@ -738,6 +748,12 @@ func (uq *UserQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (uq *UserQuery) Modify(modifiers ...func(s *sql.Selector)) *UserSelect { + uq.modifiers = append(uq.modifiers, modifiers...) + return uq.Select() +} + // UserGroupBy is the group-by builder for User entities. type UserGroupBy struct { selector @@ -827,3 +843,9 @@ func (us *UserSelect) sqlScan(ctx context.Context, root *UserQuery, v any) error defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (us *UserSelect) Modify(modifiers ...func(s *sql.Selector)) *UserSelect { + us.modifiers = append(us.modifiers, modifiers...) + return us +} diff --git a/internal/database/ent/db/user_update.go b/internal/database/ent/db/user_update.go index 4a19da4a..57c92a14 100644 --- a/internal/database/ent/db/user_update.go +++ b/internal/database/ent/db/user_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -22,8 +23,9 @@ import ( // UserUpdate is the builder for updating User entities. type UserUpdate struct { config - hooks []Hook - mutation *UserMutation + hooks []Hook + mutation *UserMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the UserUpdate builder. @@ -33,15 +35,8 @@ func (uu *UserUpdate) Where(ps ...predicate.User) *UserUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (uu *UserUpdate) SetUpdatedAt(i int64) *UserUpdate { - uu.mutation.ResetUpdatedAt() - uu.mutation.SetUpdatedAt(i) - return uu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (uu *UserUpdate) AddUpdatedAt(i int64) *UserUpdate { - uu.mutation.AddUpdatedAt(i) +func (uu *UserUpdate) SetUpdatedAt(t time.Time) *UserUpdate { + uu.mutation.SetUpdatedAt(t) return uu } @@ -262,6 +257,12 @@ func (uu *UserUpdate) defaults() { } } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (uu *UserUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserUpdate { + uu.modifiers = append(uu.modifiers, modifiers...) + return uu +} + func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { _spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeString)) if ps := uu.mutation.predicates; len(ps) > 0 { @@ -272,10 +273,7 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := uu.mutation.UpdatedAt(); ok { - _spec.SetField(user.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := uu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(user.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) } if value, ok := uu.mutation.Permissions(); ok { _spec.SetField(user.FieldPermissions, field.TypeString, value) @@ -471,6 +469,7 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + _spec.AddModifiers(uu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{user.Label} @@ -486,21 +485,15 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { // UserUpdateOne is the builder for updating a single User entity. type UserUpdateOne struct { config - fields []string - hooks []Hook - mutation *UserMutation + fields []string + hooks []Hook + mutation *UserMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (uuo *UserUpdateOne) SetUpdatedAt(i int64) *UserUpdateOne { - uuo.mutation.ResetUpdatedAt() - uuo.mutation.SetUpdatedAt(i) - return uuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (uuo *UserUpdateOne) AddUpdatedAt(i int64) *UserUpdateOne { - uuo.mutation.AddUpdatedAt(i) +func (uuo *UserUpdateOne) SetUpdatedAt(t time.Time) *UserUpdateOne { + uuo.mutation.SetUpdatedAt(t) return uuo } @@ -734,6 +727,12 @@ func (uuo *UserUpdateOne) defaults() { } } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (uuo *UserUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserUpdateOne { + uuo.modifiers = append(uuo.modifiers, modifiers...) + return uuo +} + func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { _spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeString)) id, ok := uuo.mutation.ID() @@ -761,10 +760,7 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) } } if value, ok := uuo.mutation.UpdatedAt(); ok { - _spec.SetField(user.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := uuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(user.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) } if value, ok := uuo.mutation.Permissions(); ok { _spec.SetField(user.FieldPermissions, field.TypeString, value) @@ -960,6 +956,7 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + _spec.AddModifiers(uuo.modifiers...) _node = &User{config: uuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/userconnection.go b/internal/database/ent/db/userconnection.go index 100553b6..c595e122 100644 --- a/internal/database/ent/db/userconnection.go +++ b/internal/database/ent/db/userconnection.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -20,9 +21,9 @@ type UserConnection struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.ConnectionType `json:"type,omitempty"` // UserID holds the value of the "user_id" field. @@ -66,10 +67,10 @@ func (*UserConnection) scanValues(columns []string) ([]any, error) { switch columns[i] { case userconnection.FieldMetadata: values[i] = new([]byte) - case userconnection.FieldCreatedAt, userconnection.FieldUpdatedAt: - values[i] = new(sql.NullInt64) case userconnection.FieldID, userconnection.FieldType, userconnection.FieldUserID, userconnection.FieldReferenceID, userconnection.FieldPermissions: values[i] = new(sql.NullString) + case userconnection.FieldCreatedAt, userconnection.FieldUpdatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -92,16 +93,16 @@ func (uc *UserConnection) assignValues(columns []string, values []any) error { uc.ID = value.String } case userconnection.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - uc.CreatedAt = value.Int64 + uc.CreatedAt = value.Time } case userconnection.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - uc.UpdatedAt = value.Int64 + uc.UpdatedAt = value.Time } case userconnection.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -177,10 +178,10 @@ func (uc *UserConnection) String() string { builder.WriteString("UserConnection(") builder.WriteString(fmt.Sprintf("id=%v, ", uc.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", uc.CreatedAt)) + builder.WriteString(uc.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", uc.UpdatedAt)) + builder.WriteString(uc.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("type=") builder.WriteString(fmt.Sprintf("%v", uc.Type)) diff --git a/internal/database/ent/db/userconnection/userconnection.go b/internal/database/ent/db/userconnection/userconnection.go index 0b1c4114..67c3b49d 100644 --- a/internal/database/ent/db/userconnection/userconnection.go +++ b/internal/database/ent/db/userconnection/userconnection.go @@ -4,6 +4,7 @@ package userconnection import ( "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -66,11 +67,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // DefaultPermissions holds the default value on creation for the "permissions" field. DefaultPermissions string // DefaultID holds the default value on creation for the "id" field. diff --git a/internal/database/ent/db/userconnection/where.go b/internal/database/ent/db/userconnection/where.go index 9726b22e..65f6ba25 100644 --- a/internal/database/ent/db/userconnection/where.go +++ b/internal/database/ent/db/userconnection/where.go @@ -3,6 +3,8 @@ package userconnection import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -65,12 +67,12 @@ func IDContainsFold(id string) predicate.UserConnection { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.UserConnection { +func CreatedAt(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.UserConnection { +func UpdatedAt(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -90,82 +92,82 @@ func Permissions(v string) predicate.UserConnection { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.UserConnection { +func CreatedAtEQ(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.UserConnection { +func CreatedAtNEQ(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.UserConnection { +func CreatedAtIn(vs ...time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.UserConnection { +func CreatedAtNotIn(vs ...time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.UserConnection { +func CreatedAtGT(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.UserConnection { +func CreatedAtGTE(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.UserConnection { +func CreatedAtLT(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.UserConnection { +func CreatedAtLTE(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.UserConnection { +func UpdatedAtEQ(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.UserConnection { +func UpdatedAtNEQ(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.UserConnection { +func UpdatedAtIn(vs ...time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.UserConnection { +func UpdatedAtNotIn(vs ...time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.UserConnection { +func UpdatedAtGT(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.UserConnection { +func UpdatedAtGTE(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.UserConnection { +func UpdatedAtLT(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.UserConnection { +func UpdatedAtLTE(v time.Time) predicate.UserConnection { return predicate.UserConnection(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/userconnection_create.go b/internal/database/ent/db/userconnection_create.go index 8478c906..22c040fd 100644 --- a/internal/database/ent/db/userconnection_create.go +++ b/internal/database/ent/db/userconnection_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -22,29 +23,29 @@ type UserConnectionCreate struct { } // SetCreatedAt sets the "created_at" field. -func (ucc *UserConnectionCreate) SetCreatedAt(i int64) *UserConnectionCreate { - ucc.mutation.SetCreatedAt(i) +func (ucc *UserConnectionCreate) SetCreatedAt(t time.Time) *UserConnectionCreate { + ucc.mutation.SetCreatedAt(t) return ucc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (ucc *UserConnectionCreate) SetNillableCreatedAt(i *int64) *UserConnectionCreate { - if i != nil { - ucc.SetCreatedAt(*i) +func (ucc *UserConnectionCreate) SetNillableCreatedAt(t *time.Time) *UserConnectionCreate { + if t != nil { + ucc.SetCreatedAt(*t) } return ucc } // SetUpdatedAt sets the "updated_at" field. -func (ucc *UserConnectionCreate) SetUpdatedAt(i int64) *UserConnectionCreate { - ucc.mutation.SetUpdatedAt(i) +func (ucc *UserConnectionCreate) SetUpdatedAt(t time.Time) *UserConnectionCreate { + ucc.mutation.SetUpdatedAt(t) return ucc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (ucc *UserConnectionCreate) SetNillableUpdatedAt(i *int64) *UserConnectionCreate { - if i != nil { - ucc.SetUpdatedAt(*i) +func (ucc *UserConnectionCreate) SetNillableUpdatedAt(t *time.Time) *UserConnectionCreate { + if t != nil { + ucc.SetUpdatedAt(*t) } return ucc } @@ -220,11 +221,11 @@ func (ucc *UserConnectionCreate) createSpec() (*UserConnection, *sqlgraph.Create _spec.ID.Value = id } if value, ok := ucc.mutation.CreatedAt(); ok { - _spec.SetField(userconnection.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(userconnection.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := ucc.mutation.UpdatedAt(); ok { - _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(userconnection.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := ucc.mutation.GetType(); ok { diff --git a/internal/database/ent/db/userconnection_query.go b/internal/database/ent/db/userconnection_query.go index 164e8e87..17f6603d 100644 --- a/internal/database/ent/db/userconnection_query.go +++ b/internal/database/ent/db/userconnection_query.go @@ -23,6 +23,7 @@ type UserConnectionQuery struct { inters []Interceptor predicates []predicate.UserConnection withUser *UserQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -297,7 +298,7 @@ func (ucq *UserConnectionQuery) WithUser(opts ...func(*UserQuery)) *UserConnecti // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +321,7 @@ func (ucq *UserConnectionQuery) GroupBy(field string, fields ...string) *UserCon // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.UserConnection.Query(). @@ -382,6 +383,9 @@ func (ucq *UserConnectionQuery) sqlAll(ctx context.Context, hooks ...queryHook) node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(ucq.modifiers) > 0 { + _spec.Modifiers = ucq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -432,6 +436,9 @@ func (ucq *UserConnectionQuery) loadUser(ctx context.Context, query *UserQuery, func (ucq *UserConnectionQuery) sqlCount(ctx context.Context) (int, error) { _spec := ucq.querySpec() + if len(ucq.modifiers) > 0 { + _spec.Modifiers = ucq.modifiers + } _spec.Node.Columns = ucq.ctx.Fields if len(ucq.ctx.Fields) > 0 { _spec.Unique = ucq.ctx.Unique != nil && *ucq.ctx.Unique @@ -497,6 +504,9 @@ func (ucq *UserConnectionQuery) sqlQuery(ctx context.Context) *sql.Selector { if ucq.ctx.Unique != nil && *ucq.ctx.Unique { selector.Distinct() } + for _, m := range ucq.modifiers { + m(selector) + } for _, p := range ucq.predicates { p(selector) } @@ -514,6 +524,12 @@ func (ucq *UserConnectionQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (ucq *UserConnectionQuery) Modify(modifiers ...func(s *sql.Selector)) *UserConnectionSelect { + ucq.modifiers = append(ucq.modifiers, modifiers...) + return ucq.Select() +} + // UserConnectionGroupBy is the group-by builder for UserConnection entities. type UserConnectionGroupBy struct { selector @@ -603,3 +619,9 @@ func (ucs *UserConnectionSelect) sqlScan(ctx context.Context, root *UserConnecti defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (ucs *UserConnectionSelect) Modify(modifiers ...func(s *sql.Selector)) *UserConnectionSelect { + ucs.modifiers = append(ucs.modifiers, modifiers...) + return ucs +} diff --git a/internal/database/ent/db/userconnection_update.go b/internal/database/ent/db/userconnection_update.go index 8744515f..e41384cd 100644 --- a/internal/database/ent/db/userconnection_update.go +++ b/internal/database/ent/db/userconnection_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -18,8 +19,9 @@ import ( // UserConnectionUpdate is the builder for updating UserConnection entities. type UserConnectionUpdate struct { config - hooks []Hook - mutation *UserConnectionMutation + hooks []Hook + mutation *UserConnectionMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the UserConnectionUpdate builder. @@ -29,15 +31,8 @@ func (ucu *UserConnectionUpdate) Where(ps ...predicate.UserConnection) *UserConn } // SetUpdatedAt sets the "updated_at" field. -func (ucu *UserConnectionUpdate) SetUpdatedAt(i int64) *UserConnectionUpdate { - ucu.mutation.ResetUpdatedAt() - ucu.mutation.SetUpdatedAt(i) - return ucu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (ucu *UserConnectionUpdate) AddUpdatedAt(i int64) *UserConnectionUpdate { - ucu.mutation.AddUpdatedAt(i) +func (ucu *UserConnectionUpdate) SetUpdatedAt(t time.Time) *UserConnectionUpdate { + ucu.mutation.SetUpdatedAt(t) return ucu } @@ -155,6 +150,12 @@ func (ucu *UserConnectionUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (ucu *UserConnectionUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserConnectionUpdate { + ucu.modifiers = append(ucu.modifiers, modifiers...) + return ucu +} + func (ucu *UserConnectionUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := ucu.check(); err != nil { return n, err @@ -168,10 +169,7 @@ func (ucu *UserConnectionUpdate) sqlSave(ctx context.Context) (n int, err error) } } if value, ok := ucu.mutation.UpdatedAt(); ok { - _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := ucu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(userconnection.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(userconnection.FieldUpdatedAt, field.TypeTime, value) } if value, ok := ucu.mutation.GetType(); ok { _spec.SetField(userconnection.FieldType, field.TypeEnum, value) @@ -191,6 +189,7 @@ func (ucu *UserConnectionUpdate) sqlSave(ctx context.Context) (n int, err error) if ucu.mutation.MetadataCleared() { _spec.ClearField(userconnection.FieldMetadata, field.TypeJSON) } + _spec.AddModifiers(ucu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, ucu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{userconnection.Label} @@ -206,21 +205,15 @@ func (ucu *UserConnectionUpdate) sqlSave(ctx context.Context) (n int, err error) // UserConnectionUpdateOne is the builder for updating a single UserConnection entity. type UserConnectionUpdateOne struct { config - fields []string - hooks []Hook - mutation *UserConnectionMutation + fields []string + hooks []Hook + mutation *UserConnectionMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (ucuo *UserConnectionUpdateOne) SetUpdatedAt(i int64) *UserConnectionUpdateOne { - ucuo.mutation.ResetUpdatedAt() - ucuo.mutation.SetUpdatedAt(i) - return ucuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (ucuo *UserConnectionUpdateOne) AddUpdatedAt(i int64) *UserConnectionUpdateOne { - ucuo.mutation.AddUpdatedAt(i) +func (ucuo *UserConnectionUpdateOne) SetUpdatedAt(t time.Time) *UserConnectionUpdateOne { + ucuo.mutation.SetUpdatedAt(t) return ucuo } @@ -351,6 +344,12 @@ func (ucuo *UserConnectionUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (ucuo *UserConnectionUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserConnectionUpdateOne { + ucuo.modifiers = append(ucuo.modifiers, modifiers...) + return ucuo +} + func (ucuo *UserConnectionUpdateOne) sqlSave(ctx context.Context) (_node *UserConnection, err error) { if err := ucuo.check(); err != nil { return _node, err @@ -381,10 +380,7 @@ func (ucuo *UserConnectionUpdateOne) sqlSave(ctx context.Context) (_node *UserCo } } if value, ok := ucuo.mutation.UpdatedAt(); ok { - _spec.SetField(userconnection.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := ucuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(userconnection.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(userconnection.FieldUpdatedAt, field.TypeTime, value) } if value, ok := ucuo.mutation.GetType(); ok { _spec.SetField(userconnection.FieldType, field.TypeEnum, value) @@ -404,6 +400,7 @@ func (ucuo *UserConnectionUpdateOne) sqlSave(ctx context.Context) (_node *UserCo if ucuo.mutation.MetadataCleared() { _spec.ClearField(userconnection.FieldMetadata, field.TypeJSON) } + _spec.AddModifiers(ucuo.modifiers...) _node = &UserConnection{config: ucuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/usercontent.go b/internal/database/ent/db/usercontent.go index f60b64d1..7144a59f 100644 --- a/internal/database/ent/db/usercontent.go +++ b/internal/database/ent/db/usercontent.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -20,9 +21,9 @@ type UserContent struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.UserContentType `json:"type,omitempty"` // UserID holds the value of the "user_id" field. @@ -66,10 +67,10 @@ func (*UserContent) scanValues(columns []string) ([]any, error) { switch columns[i] { case usercontent.FieldMetadata: values[i] = new([]byte) - case usercontent.FieldCreatedAt, usercontent.FieldUpdatedAt: - values[i] = new(sql.NullInt64) case usercontent.FieldID, usercontent.FieldType, usercontent.FieldUserID, usercontent.FieldReferenceID, usercontent.FieldValue: values[i] = new(sql.NullString) + case usercontent.FieldCreatedAt, usercontent.FieldUpdatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -92,16 +93,16 @@ func (uc *UserContent) assignValues(columns []string, values []any) error { uc.ID = value.String } case usercontent.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - uc.CreatedAt = value.Int64 + uc.CreatedAt = value.Time } case usercontent.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - uc.UpdatedAt = value.Int64 + uc.UpdatedAt = value.Time } case usercontent.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -177,10 +178,10 @@ func (uc *UserContent) String() string { builder.WriteString("UserContent(") builder.WriteString(fmt.Sprintf("id=%v, ", uc.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", uc.CreatedAt)) + builder.WriteString(uc.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", uc.UpdatedAt)) + builder.WriteString(uc.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("type=") builder.WriteString(fmt.Sprintf("%v", uc.Type)) diff --git a/internal/database/ent/db/usercontent/usercontent.go b/internal/database/ent/db/usercontent/usercontent.go index a05b1dcb..ab3dc61b 100644 --- a/internal/database/ent/db/usercontent/usercontent.go +++ b/internal/database/ent/db/usercontent/usercontent.go @@ -4,6 +4,7 @@ package usercontent import ( "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -66,11 +67,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // DefaultID holds the default value on creation for the "id" field. DefaultID func() string ) diff --git a/internal/database/ent/db/usercontent/where.go b/internal/database/ent/db/usercontent/where.go index db3d4033..40dfbac4 100644 --- a/internal/database/ent/db/usercontent/where.go +++ b/internal/database/ent/db/usercontent/where.go @@ -3,6 +3,8 @@ package usercontent import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -65,12 +67,12 @@ func IDContainsFold(id string) predicate.UserContent { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.UserContent { +func CreatedAt(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.UserContent { +func UpdatedAt(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -90,82 +92,82 @@ func Value(v string) predicate.UserContent { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.UserContent { +func CreatedAtEQ(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.UserContent { +func CreatedAtNEQ(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.UserContent { +func CreatedAtIn(vs ...time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.UserContent { +func CreatedAtNotIn(vs ...time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.UserContent { +func CreatedAtGT(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.UserContent { +func CreatedAtGTE(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.UserContent { +func CreatedAtLT(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.UserContent { +func CreatedAtLTE(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.UserContent { +func UpdatedAtEQ(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.UserContent { +func UpdatedAtNEQ(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.UserContent { +func UpdatedAtIn(vs ...time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.UserContent { +func UpdatedAtNotIn(vs ...time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.UserContent { +func UpdatedAtGT(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.UserContent { +func UpdatedAtGTE(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.UserContent { +func UpdatedAtLT(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.UserContent { +func UpdatedAtLTE(v time.Time) predicate.UserContent { return predicate.UserContent(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/usercontent_create.go b/internal/database/ent/db/usercontent_create.go index d127d2a3..ba70058f 100644 --- a/internal/database/ent/db/usercontent_create.go +++ b/internal/database/ent/db/usercontent_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -22,29 +23,29 @@ type UserContentCreate struct { } // SetCreatedAt sets the "created_at" field. -func (ucc *UserContentCreate) SetCreatedAt(i int64) *UserContentCreate { - ucc.mutation.SetCreatedAt(i) +func (ucc *UserContentCreate) SetCreatedAt(t time.Time) *UserContentCreate { + ucc.mutation.SetCreatedAt(t) return ucc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (ucc *UserContentCreate) SetNillableCreatedAt(i *int64) *UserContentCreate { - if i != nil { - ucc.SetCreatedAt(*i) +func (ucc *UserContentCreate) SetNillableCreatedAt(t *time.Time) *UserContentCreate { + if t != nil { + ucc.SetCreatedAt(*t) } return ucc } // SetUpdatedAt sets the "updated_at" field. -func (ucc *UserContentCreate) SetUpdatedAt(i int64) *UserContentCreate { - ucc.mutation.SetUpdatedAt(i) +func (ucc *UserContentCreate) SetUpdatedAt(t time.Time) *UserContentCreate { + ucc.mutation.SetUpdatedAt(t) return ucc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (ucc *UserContentCreate) SetNillableUpdatedAt(i *int64) *UserContentCreate { - if i != nil { - ucc.SetUpdatedAt(*i) +func (ucc *UserContentCreate) SetNillableUpdatedAt(t *time.Time) *UserContentCreate { + if t != nil { + ucc.SetUpdatedAt(*t) } return ucc } @@ -214,11 +215,11 @@ func (ucc *UserContentCreate) createSpec() (*UserContent, *sqlgraph.CreateSpec) _spec.ID.Value = id } if value, ok := ucc.mutation.CreatedAt(); ok { - _spec.SetField(usercontent.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(usercontent.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := ucc.mutation.UpdatedAt(); ok { - _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(usercontent.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := ucc.mutation.GetType(); ok { diff --git a/internal/database/ent/db/usercontent_query.go b/internal/database/ent/db/usercontent_query.go index 8c189429..7552a40f 100644 --- a/internal/database/ent/db/usercontent_query.go +++ b/internal/database/ent/db/usercontent_query.go @@ -23,6 +23,7 @@ type UserContentQuery struct { inters []Interceptor predicates []predicate.UserContent withUser *UserQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -297,7 +298,7 @@ func (ucq *UserContentQuery) WithUser(opts ...func(*UserQuery)) *UserContentQuer // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +321,7 @@ func (ucq *UserContentQuery) GroupBy(field string, fields ...string) *UserConten // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.UserContent.Query(). @@ -382,6 +383,9 @@ func (ucq *UserContentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([] node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(ucq.modifiers) > 0 { + _spec.Modifiers = ucq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -432,6 +436,9 @@ func (ucq *UserContentQuery) loadUser(ctx context.Context, query *UserQuery, nod func (ucq *UserContentQuery) sqlCount(ctx context.Context) (int, error) { _spec := ucq.querySpec() + if len(ucq.modifiers) > 0 { + _spec.Modifiers = ucq.modifiers + } _spec.Node.Columns = ucq.ctx.Fields if len(ucq.ctx.Fields) > 0 { _spec.Unique = ucq.ctx.Unique != nil && *ucq.ctx.Unique @@ -497,6 +504,9 @@ func (ucq *UserContentQuery) sqlQuery(ctx context.Context) *sql.Selector { if ucq.ctx.Unique != nil && *ucq.ctx.Unique { selector.Distinct() } + for _, m := range ucq.modifiers { + m(selector) + } for _, p := range ucq.predicates { p(selector) } @@ -514,6 +524,12 @@ func (ucq *UserContentQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (ucq *UserContentQuery) Modify(modifiers ...func(s *sql.Selector)) *UserContentSelect { + ucq.modifiers = append(ucq.modifiers, modifiers...) + return ucq.Select() +} + // UserContentGroupBy is the group-by builder for UserContent entities. type UserContentGroupBy struct { selector @@ -603,3 +619,9 @@ func (ucs *UserContentSelect) sqlScan(ctx context.Context, root *UserContentQuer defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (ucs *UserContentSelect) Modify(modifiers ...func(s *sql.Selector)) *UserContentSelect { + ucs.modifiers = append(ucs.modifiers, modifiers...) + return ucs +} diff --git a/internal/database/ent/db/usercontent_update.go b/internal/database/ent/db/usercontent_update.go index 93054653..b6595543 100644 --- a/internal/database/ent/db/usercontent_update.go +++ b/internal/database/ent/db/usercontent_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -18,8 +19,9 @@ import ( // UserContentUpdate is the builder for updating UserContent entities. type UserContentUpdate struct { config - hooks []Hook - mutation *UserContentMutation + hooks []Hook + mutation *UserContentMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the UserContentUpdate builder. @@ -29,15 +31,8 @@ func (ucu *UserContentUpdate) Where(ps ...predicate.UserContent) *UserContentUpd } // SetUpdatedAt sets the "updated_at" field. -func (ucu *UserContentUpdate) SetUpdatedAt(i int64) *UserContentUpdate { - ucu.mutation.ResetUpdatedAt() - ucu.mutation.SetUpdatedAt(i) - return ucu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (ucu *UserContentUpdate) AddUpdatedAt(i int64) *UserContentUpdate { - ucu.mutation.AddUpdatedAt(i) +func (ucu *UserContentUpdate) SetUpdatedAt(t time.Time) *UserContentUpdate { + ucu.mutation.SetUpdatedAt(t) return ucu } @@ -143,6 +138,12 @@ func (ucu *UserContentUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (ucu *UserContentUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserContentUpdate { + ucu.modifiers = append(ucu.modifiers, modifiers...) + return ucu +} + func (ucu *UserContentUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := ucu.check(); err != nil { return n, err @@ -156,10 +157,7 @@ func (ucu *UserContentUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := ucu.mutation.UpdatedAt(); ok { - _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := ucu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(usercontent.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(usercontent.FieldUpdatedAt, field.TypeTime, value) } if value, ok := ucu.mutation.GetType(); ok { _spec.SetField(usercontent.FieldType, field.TypeEnum, value) @@ -173,6 +171,7 @@ func (ucu *UserContentUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := ucu.mutation.Metadata(); ok { _spec.SetField(usercontent.FieldMetadata, field.TypeJSON, value) } + _spec.AddModifiers(ucu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, ucu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{usercontent.Label} @@ -188,21 +187,15 @@ func (ucu *UserContentUpdate) sqlSave(ctx context.Context) (n int, err error) { // UserContentUpdateOne is the builder for updating a single UserContent entity. type UserContentUpdateOne struct { config - fields []string - hooks []Hook - mutation *UserContentMutation + fields []string + hooks []Hook + mutation *UserContentMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (ucuo *UserContentUpdateOne) SetUpdatedAt(i int64) *UserContentUpdateOne { - ucuo.mutation.ResetUpdatedAt() - ucuo.mutation.SetUpdatedAt(i) - return ucuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (ucuo *UserContentUpdateOne) AddUpdatedAt(i int64) *UserContentUpdateOne { - ucuo.mutation.AddUpdatedAt(i) +func (ucuo *UserContentUpdateOne) SetUpdatedAt(t time.Time) *UserContentUpdateOne { + ucuo.mutation.SetUpdatedAt(t) return ucuo } @@ -321,6 +314,12 @@ func (ucuo *UserContentUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (ucuo *UserContentUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserContentUpdateOne { + ucuo.modifiers = append(ucuo.modifiers, modifiers...) + return ucuo +} + func (ucuo *UserContentUpdateOne) sqlSave(ctx context.Context) (_node *UserContent, err error) { if err := ucuo.check(); err != nil { return _node, err @@ -351,10 +350,7 @@ func (ucuo *UserContentUpdateOne) sqlSave(ctx context.Context) (_node *UserConte } } if value, ok := ucuo.mutation.UpdatedAt(); ok { - _spec.SetField(usercontent.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := ucuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(usercontent.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(usercontent.FieldUpdatedAt, field.TypeTime, value) } if value, ok := ucuo.mutation.GetType(); ok { _spec.SetField(usercontent.FieldType, field.TypeEnum, value) @@ -368,6 +364,7 @@ func (ucuo *UserContentUpdateOne) sqlSave(ctx context.Context) (_node *UserConte if value, ok := ucuo.mutation.Metadata(); ok { _spec.SetField(usercontent.FieldMetadata, field.TypeJSON, value) } + _spec.AddModifiers(ucuo.modifiers...) _node = &UserContent{config: ucuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/usersubscription.go b/internal/database/ent/db/usersubscription.go index 11cdb283..945701a8 100644 --- a/internal/database/ent/db/usersubscription.go +++ b/internal/database/ent/db/usersubscription.go @@ -5,6 +5,7 @@ package db import ( "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -19,13 +20,13 @@ type UserSubscription struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.SubscriptionType `json:"type,omitempty"` // ExpiresAt holds the value of the "expires_at" field. - ExpiresAt int64 `json:"expires_at,omitempty"` + ExpiresAt time.Time `json:"expires_at,omitempty"` // UserID holds the value of the "user_id" field. UserID string `json:"user_id,omitempty"` // Permissions holds the value of the "permissions" field. @@ -63,10 +64,10 @@ func (*UserSubscription) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case usersubscription.FieldCreatedAt, usersubscription.FieldUpdatedAt, usersubscription.FieldExpiresAt: - values[i] = new(sql.NullInt64) case usersubscription.FieldID, usersubscription.FieldType, usersubscription.FieldUserID, usersubscription.FieldPermissions, usersubscription.FieldReferenceID: values[i] = new(sql.NullString) + case usersubscription.FieldCreatedAt, usersubscription.FieldUpdatedAt, usersubscription.FieldExpiresAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -89,16 +90,16 @@ func (us *UserSubscription) assignValues(columns []string, values []any) error { us.ID = value.String } case usersubscription.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - us.CreatedAt = value.Int64 + us.CreatedAt = value.Time } case usersubscription.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - us.UpdatedAt = value.Int64 + us.UpdatedAt = value.Time } case usersubscription.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -107,10 +108,10 @@ func (us *UserSubscription) assignValues(columns []string, values []any) error { us.Type = models.SubscriptionType(value.String) } case usersubscription.FieldExpiresAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field expires_at", values[i]) } else if value.Valid { - us.ExpiresAt = value.Int64 + us.ExpiresAt = value.Time } case usersubscription.FieldUserID: if value, ok := values[i].(*sql.NullString); !ok { @@ -172,16 +173,16 @@ func (us *UserSubscription) String() string { builder.WriteString("UserSubscription(") builder.WriteString(fmt.Sprintf("id=%v, ", us.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", us.CreatedAt)) + builder.WriteString(us.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", us.UpdatedAt)) + builder.WriteString(us.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("type=") builder.WriteString(fmt.Sprintf("%v", us.Type)) builder.WriteString(", ") builder.WriteString("expires_at=") - builder.WriteString(fmt.Sprintf("%v", us.ExpiresAt)) + builder.WriteString(us.ExpiresAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("user_id=") builder.WriteString(us.UserID) diff --git a/internal/database/ent/db/usersubscription/usersubscription.go b/internal/database/ent/db/usersubscription/usersubscription.go index 5a34f32b..7bdea462 100644 --- a/internal/database/ent/db/usersubscription/usersubscription.go +++ b/internal/database/ent/db/usersubscription/usersubscription.go @@ -4,6 +4,7 @@ package usersubscription import ( "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -66,11 +67,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. UserIDValidator func(string) error // PermissionsValidator is a validator for the "permissions" field. It is called by the builders before save. diff --git a/internal/database/ent/db/usersubscription/where.go b/internal/database/ent/db/usersubscription/where.go index 9111e4f8..7dbc9b0b 100644 --- a/internal/database/ent/db/usersubscription/where.go +++ b/internal/database/ent/db/usersubscription/where.go @@ -3,6 +3,8 @@ package usersubscription import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -65,17 +67,17 @@ func IDContainsFold(id string) predicate.UserSubscription { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.UserSubscription { +func CreatedAt(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.UserSubscription { +func UpdatedAt(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldUpdatedAt, v)) } // ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ. -func ExpiresAt(v int64) predicate.UserSubscription { +func ExpiresAt(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldExpiresAt, v)) } @@ -95,82 +97,82 @@ func ReferenceID(v string) predicate.UserSubscription { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.UserSubscription { +func CreatedAtEQ(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.UserSubscription { +func CreatedAtNEQ(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.UserSubscription { +func CreatedAtIn(vs ...time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.UserSubscription { +func CreatedAtNotIn(vs ...time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.UserSubscription { +func CreatedAtGT(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.UserSubscription { +func CreatedAtGTE(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.UserSubscription { +func CreatedAtLT(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.UserSubscription { +func CreatedAtLTE(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.UserSubscription { +func UpdatedAtEQ(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.UserSubscription { +func UpdatedAtNEQ(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.UserSubscription { +func UpdatedAtIn(vs ...time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.UserSubscription { +func UpdatedAtNotIn(vs ...time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.UserSubscription { +func UpdatedAtGT(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.UserSubscription { +func UpdatedAtGTE(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.UserSubscription { +func UpdatedAtLT(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.UserSubscription { +func UpdatedAtLTE(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLTE(FieldUpdatedAt, v)) } @@ -205,42 +207,42 @@ func TypeNotIn(vs ...models.SubscriptionType) predicate.UserSubscription { } // ExpiresAtEQ applies the EQ predicate on the "expires_at" field. -func ExpiresAtEQ(v int64) predicate.UserSubscription { +func ExpiresAtEQ(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldEQ(FieldExpiresAt, v)) } // ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field. -func ExpiresAtNEQ(v int64) predicate.UserSubscription { +func ExpiresAtNEQ(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNEQ(FieldExpiresAt, v)) } // ExpiresAtIn applies the In predicate on the "expires_at" field. -func ExpiresAtIn(vs ...int64) predicate.UserSubscription { +func ExpiresAtIn(vs ...time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldIn(FieldExpiresAt, vs...)) } // ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field. -func ExpiresAtNotIn(vs ...int64) predicate.UserSubscription { +func ExpiresAtNotIn(vs ...time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldNotIn(FieldExpiresAt, vs...)) } // ExpiresAtGT applies the GT predicate on the "expires_at" field. -func ExpiresAtGT(v int64) predicate.UserSubscription { +func ExpiresAtGT(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGT(FieldExpiresAt, v)) } // ExpiresAtGTE applies the GTE predicate on the "expires_at" field. -func ExpiresAtGTE(v int64) predicate.UserSubscription { +func ExpiresAtGTE(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldGTE(FieldExpiresAt, v)) } // ExpiresAtLT applies the LT predicate on the "expires_at" field. -func ExpiresAtLT(v int64) predicate.UserSubscription { +func ExpiresAtLT(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLT(FieldExpiresAt, v)) } // ExpiresAtLTE applies the LTE predicate on the "expires_at" field. -func ExpiresAtLTE(v int64) predicate.UserSubscription { +func ExpiresAtLTE(v time.Time) predicate.UserSubscription { return predicate.UserSubscription(sql.FieldLTE(FieldExpiresAt, v)) } diff --git a/internal/database/ent/db/usersubscription_create.go b/internal/database/ent/db/usersubscription_create.go index 64971258..28c8bebf 100644 --- a/internal/database/ent/db/usersubscription_create.go +++ b/internal/database/ent/db/usersubscription_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -22,29 +23,29 @@ type UserSubscriptionCreate struct { } // SetCreatedAt sets the "created_at" field. -func (usc *UserSubscriptionCreate) SetCreatedAt(i int64) *UserSubscriptionCreate { - usc.mutation.SetCreatedAt(i) +func (usc *UserSubscriptionCreate) SetCreatedAt(t time.Time) *UserSubscriptionCreate { + usc.mutation.SetCreatedAt(t) return usc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (usc *UserSubscriptionCreate) SetNillableCreatedAt(i *int64) *UserSubscriptionCreate { - if i != nil { - usc.SetCreatedAt(*i) +func (usc *UserSubscriptionCreate) SetNillableCreatedAt(t *time.Time) *UserSubscriptionCreate { + if t != nil { + usc.SetCreatedAt(*t) } return usc } // SetUpdatedAt sets the "updated_at" field. -func (usc *UserSubscriptionCreate) SetUpdatedAt(i int64) *UserSubscriptionCreate { - usc.mutation.SetUpdatedAt(i) +func (usc *UserSubscriptionCreate) SetUpdatedAt(t time.Time) *UserSubscriptionCreate { + usc.mutation.SetUpdatedAt(t) return usc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (usc *UserSubscriptionCreate) SetNillableUpdatedAt(i *int64) *UserSubscriptionCreate { - if i != nil { - usc.SetUpdatedAt(*i) +func (usc *UserSubscriptionCreate) SetNillableUpdatedAt(t *time.Time) *UserSubscriptionCreate { + if t != nil { + usc.SetUpdatedAt(*t) } return usc } @@ -56,8 +57,8 @@ func (usc *UserSubscriptionCreate) SetType(mt models.SubscriptionType) *UserSubs } // SetExpiresAt sets the "expires_at" field. -func (usc *UserSubscriptionCreate) SetExpiresAt(i int64) *UserSubscriptionCreate { - usc.mutation.SetExpiresAt(i) +func (usc *UserSubscriptionCreate) SetExpiresAt(t time.Time) *UserSubscriptionCreate { + usc.mutation.SetExpiresAt(t) return usc } @@ -229,11 +230,11 @@ func (usc *UserSubscriptionCreate) createSpec() (*UserSubscription, *sqlgraph.Cr _spec.ID.Value = id } if value, ok := usc.mutation.CreatedAt(); ok { - _spec.SetField(usersubscription.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(usersubscription.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := usc.mutation.UpdatedAt(); ok { - _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := usc.mutation.GetType(); ok { @@ -241,7 +242,7 @@ func (usc *UserSubscriptionCreate) createSpec() (*UserSubscription, *sqlgraph.Cr _node.Type = value } if value, ok := usc.mutation.ExpiresAt(); ok { - _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt64, value) + _spec.SetField(usersubscription.FieldExpiresAt, field.TypeTime, value) _node.ExpiresAt = value } if value, ok := usc.mutation.Permissions(); ok { diff --git a/internal/database/ent/db/usersubscription_query.go b/internal/database/ent/db/usersubscription_query.go index 25027d45..8e30a79f 100644 --- a/internal/database/ent/db/usersubscription_query.go +++ b/internal/database/ent/db/usersubscription_query.go @@ -23,6 +23,7 @@ type UserSubscriptionQuery struct { inters []Interceptor predicates []predicate.UserSubscription withUser *UserQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -297,7 +298,7 @@ func (usq *UserSubscriptionQuery) WithUser(opts ...func(*UserQuery)) *UserSubscr // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +321,7 @@ func (usq *UserSubscriptionQuery) GroupBy(field string, fields ...string) *UserS // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.UserSubscription.Query(). @@ -382,6 +383,9 @@ func (usq *UserSubscriptionQuery) sqlAll(ctx context.Context, hooks ...queryHook node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(usq.modifiers) > 0 { + _spec.Modifiers = usq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -432,6 +436,9 @@ func (usq *UserSubscriptionQuery) loadUser(ctx context.Context, query *UserQuery func (usq *UserSubscriptionQuery) sqlCount(ctx context.Context) (int, error) { _spec := usq.querySpec() + if len(usq.modifiers) > 0 { + _spec.Modifiers = usq.modifiers + } _spec.Node.Columns = usq.ctx.Fields if len(usq.ctx.Fields) > 0 { _spec.Unique = usq.ctx.Unique != nil && *usq.ctx.Unique @@ -497,6 +504,9 @@ func (usq *UserSubscriptionQuery) sqlQuery(ctx context.Context) *sql.Selector { if usq.ctx.Unique != nil && *usq.ctx.Unique { selector.Distinct() } + for _, m := range usq.modifiers { + m(selector) + } for _, p := range usq.predicates { p(selector) } @@ -514,6 +524,12 @@ func (usq *UserSubscriptionQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (usq *UserSubscriptionQuery) Modify(modifiers ...func(s *sql.Selector)) *UserSubscriptionSelect { + usq.modifiers = append(usq.modifiers, modifiers...) + return usq.Select() +} + // UserSubscriptionGroupBy is the group-by builder for UserSubscription entities. type UserSubscriptionGroupBy struct { selector @@ -603,3 +619,9 @@ func (uss *UserSubscriptionSelect) sqlScan(ctx context.Context, root *UserSubscr defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (uss *UserSubscriptionSelect) Modify(modifiers ...func(s *sql.Selector)) *UserSubscriptionSelect { + uss.modifiers = append(uss.modifiers, modifiers...) + return uss +} diff --git a/internal/database/ent/db/usersubscription_update.go b/internal/database/ent/db/usersubscription_update.go index b2806f54..256a1fe3 100644 --- a/internal/database/ent/db/usersubscription_update.go +++ b/internal/database/ent/db/usersubscription_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -18,8 +19,9 @@ import ( // UserSubscriptionUpdate is the builder for updating UserSubscription entities. type UserSubscriptionUpdate struct { config - hooks []Hook - mutation *UserSubscriptionMutation + hooks []Hook + mutation *UserSubscriptionMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the UserSubscriptionUpdate builder. @@ -29,15 +31,8 @@ func (usu *UserSubscriptionUpdate) Where(ps ...predicate.UserSubscription) *User } // SetUpdatedAt sets the "updated_at" field. -func (usu *UserSubscriptionUpdate) SetUpdatedAt(i int64) *UserSubscriptionUpdate { - usu.mutation.ResetUpdatedAt() - usu.mutation.SetUpdatedAt(i) - return usu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (usu *UserSubscriptionUpdate) AddUpdatedAt(i int64) *UserSubscriptionUpdate { - usu.mutation.AddUpdatedAt(i) +func (usu *UserSubscriptionUpdate) SetUpdatedAt(t time.Time) *UserSubscriptionUpdate { + usu.mutation.SetUpdatedAt(t) return usu } @@ -56,26 +51,19 @@ func (usu *UserSubscriptionUpdate) SetNillableType(mt *models.SubscriptionType) } // SetExpiresAt sets the "expires_at" field. -func (usu *UserSubscriptionUpdate) SetExpiresAt(i int64) *UserSubscriptionUpdate { - usu.mutation.ResetExpiresAt() - usu.mutation.SetExpiresAt(i) +func (usu *UserSubscriptionUpdate) SetExpiresAt(t time.Time) *UserSubscriptionUpdate { + usu.mutation.SetExpiresAt(t) return usu } // SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. -func (usu *UserSubscriptionUpdate) SetNillableExpiresAt(i *int64) *UserSubscriptionUpdate { - if i != nil { - usu.SetExpiresAt(*i) +func (usu *UserSubscriptionUpdate) SetNillableExpiresAt(t *time.Time) *UserSubscriptionUpdate { + if t != nil { + usu.SetExpiresAt(*t) } return usu } -// AddExpiresAt adds i to the "expires_at" field. -func (usu *UserSubscriptionUpdate) AddExpiresAt(i int64) *UserSubscriptionUpdate { - usu.mutation.AddExpiresAt(i) - return usu -} - // SetPermissions sets the "permissions" field. func (usu *UserSubscriptionUpdate) SetPermissions(s string) *UserSubscriptionUpdate { usu.mutation.SetPermissions(s) @@ -168,6 +156,12 @@ func (usu *UserSubscriptionUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (usu *UserSubscriptionUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserSubscriptionUpdate { + usu.modifiers = append(usu.modifiers, modifiers...) + return usu +} + func (usu *UserSubscriptionUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := usu.check(); err != nil { return n, err @@ -181,19 +175,13 @@ func (usu *UserSubscriptionUpdate) sqlSave(ctx context.Context) (n int, err erro } } if value, ok := usu.mutation.UpdatedAt(); ok { - _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := usu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeTime, value) } if value, ok := usu.mutation.GetType(); ok { _spec.SetField(usersubscription.FieldType, field.TypeEnum, value) } if value, ok := usu.mutation.ExpiresAt(); ok { - _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt64, value) - } - if value, ok := usu.mutation.AddedExpiresAt(); ok { - _spec.AddField(usersubscription.FieldExpiresAt, field.TypeInt64, value) + _spec.SetField(usersubscription.FieldExpiresAt, field.TypeTime, value) } if value, ok := usu.mutation.Permissions(); ok { _spec.SetField(usersubscription.FieldPermissions, field.TypeString, value) @@ -201,6 +189,7 @@ func (usu *UserSubscriptionUpdate) sqlSave(ctx context.Context) (n int, err erro if value, ok := usu.mutation.ReferenceID(); ok { _spec.SetField(usersubscription.FieldReferenceID, field.TypeString, value) } + _spec.AddModifiers(usu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, usu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{usersubscription.Label} @@ -216,21 +205,15 @@ func (usu *UserSubscriptionUpdate) sqlSave(ctx context.Context) (n int, err erro // UserSubscriptionUpdateOne is the builder for updating a single UserSubscription entity. type UserSubscriptionUpdateOne struct { config - fields []string - hooks []Hook - mutation *UserSubscriptionMutation + fields []string + hooks []Hook + mutation *UserSubscriptionMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (usuo *UserSubscriptionUpdateOne) SetUpdatedAt(i int64) *UserSubscriptionUpdateOne { - usuo.mutation.ResetUpdatedAt() - usuo.mutation.SetUpdatedAt(i) - return usuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (usuo *UserSubscriptionUpdateOne) AddUpdatedAt(i int64) *UserSubscriptionUpdateOne { - usuo.mutation.AddUpdatedAt(i) +func (usuo *UserSubscriptionUpdateOne) SetUpdatedAt(t time.Time) *UserSubscriptionUpdateOne { + usuo.mutation.SetUpdatedAt(t) return usuo } @@ -249,26 +232,19 @@ func (usuo *UserSubscriptionUpdateOne) SetNillableType(mt *models.SubscriptionTy } // SetExpiresAt sets the "expires_at" field. -func (usuo *UserSubscriptionUpdateOne) SetExpiresAt(i int64) *UserSubscriptionUpdateOne { - usuo.mutation.ResetExpiresAt() - usuo.mutation.SetExpiresAt(i) +func (usuo *UserSubscriptionUpdateOne) SetExpiresAt(t time.Time) *UserSubscriptionUpdateOne { + usuo.mutation.SetExpiresAt(t) return usuo } // SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. -func (usuo *UserSubscriptionUpdateOne) SetNillableExpiresAt(i *int64) *UserSubscriptionUpdateOne { - if i != nil { - usuo.SetExpiresAt(*i) +func (usuo *UserSubscriptionUpdateOne) SetNillableExpiresAt(t *time.Time) *UserSubscriptionUpdateOne { + if t != nil { + usuo.SetExpiresAt(*t) } return usuo } -// AddExpiresAt adds i to the "expires_at" field. -func (usuo *UserSubscriptionUpdateOne) AddExpiresAt(i int64) *UserSubscriptionUpdateOne { - usuo.mutation.AddExpiresAt(i) - return usuo -} - // SetPermissions sets the "permissions" field. func (usuo *UserSubscriptionUpdateOne) SetPermissions(s string) *UserSubscriptionUpdateOne { usuo.mutation.SetPermissions(s) @@ -374,6 +350,12 @@ func (usuo *UserSubscriptionUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (usuo *UserSubscriptionUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserSubscriptionUpdateOne { + usuo.modifiers = append(usuo.modifiers, modifiers...) + return usuo +} + func (usuo *UserSubscriptionUpdateOne) sqlSave(ctx context.Context) (_node *UserSubscription, err error) { if err := usuo.check(); err != nil { return _node, err @@ -404,19 +386,13 @@ func (usuo *UserSubscriptionUpdateOne) sqlSave(ctx context.Context) (_node *User } } if value, ok := usuo.mutation.UpdatedAt(); ok { - _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := usuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(usersubscription.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(usersubscription.FieldUpdatedAt, field.TypeTime, value) } if value, ok := usuo.mutation.GetType(); ok { _spec.SetField(usersubscription.FieldType, field.TypeEnum, value) } if value, ok := usuo.mutation.ExpiresAt(); ok { - _spec.SetField(usersubscription.FieldExpiresAt, field.TypeInt64, value) - } - if value, ok := usuo.mutation.AddedExpiresAt(); ok { - _spec.AddField(usersubscription.FieldExpiresAt, field.TypeInt64, value) + _spec.SetField(usersubscription.FieldExpiresAt, field.TypeTime, value) } if value, ok := usuo.mutation.Permissions(); ok { _spec.SetField(usersubscription.FieldPermissions, field.TypeString, value) @@ -424,6 +400,7 @@ func (usuo *UserSubscriptionUpdateOne) sqlSave(ctx context.Context) (_node *User if value, ok := usuo.mutation.ReferenceID(); ok { _spec.SetField(usersubscription.FieldReferenceID, field.TypeString, value) } + _spec.AddModifiers(usuo.modifiers...) _node = &UserSubscription{config: usuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/vehicle.go b/internal/database/ent/db/vehicle.go index 639c60e9..78a302ba 100644 --- a/internal/database/ent/db/vehicle.go +++ b/internal/database/ent/db/vehicle.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -18,9 +19,9 @@ type Vehicle struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Tier holds the value of the "tier" field. Tier int `json:"tier,omitempty"` // LocalizedNames holds the value of the "localized_names" field. @@ -35,10 +36,12 @@ func (*Vehicle) scanValues(columns []string) ([]any, error) { switch columns[i] { case vehicle.FieldLocalizedNames: values[i] = new([]byte) - case vehicle.FieldCreatedAt, vehicle.FieldUpdatedAt, vehicle.FieldTier: + case vehicle.FieldTier: values[i] = new(sql.NullInt64) case vehicle.FieldID: values[i] = new(sql.NullString) + case vehicle.FieldCreatedAt, vehicle.FieldUpdatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -61,16 +64,16 @@ func (v *Vehicle) assignValues(columns []string, values []any) error { v.ID = value.String } case vehicle.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - v.CreatedAt = value.Int64 + v.CreatedAt = value.Time } case vehicle.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - v.UpdatedAt = value.Int64 + v.UpdatedAt = value.Time } case vehicle.FieldTier: if value, ok := values[i].(*sql.NullInt64); !ok { @@ -123,10 +126,10 @@ func (v *Vehicle) String() string { builder.WriteString("Vehicle(") builder.WriteString(fmt.Sprintf("id=%v, ", v.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", v.CreatedAt)) + builder.WriteString(v.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", v.UpdatedAt)) + builder.WriteString(v.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("tier=") builder.WriteString(fmt.Sprintf("%v", v.Tier)) diff --git a/internal/database/ent/db/vehicle/vehicle.go b/internal/database/ent/db/vehicle/vehicle.go index 5e417ea2..f29f3a64 100644 --- a/internal/database/ent/db/vehicle/vehicle.go +++ b/internal/database/ent/db/vehicle/vehicle.go @@ -3,6 +3,8 @@ package vehicle import ( + "time" + "entgo.io/ent/dialect/sql" ) @@ -44,11 +46,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // TierValidator is a validator for the "tier" field. It is called by the builders before save. TierValidator func(int) error ) diff --git a/internal/database/ent/db/vehicle/where.go b/internal/database/ent/db/vehicle/where.go index 5bf6a6dc..b0f84e66 100644 --- a/internal/database/ent/db/vehicle/where.go +++ b/internal/database/ent/db/vehicle/where.go @@ -3,6 +3,8 @@ package vehicle import ( + "time" + "entgo.io/ent/dialect/sql" "github.com/cufee/aftermath/internal/database/ent/db/predicate" ) @@ -63,12 +65,12 @@ func IDContainsFold(id string) predicate.Vehicle { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.Vehicle { +func CreatedAt(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.Vehicle { +func UpdatedAt(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -78,82 +80,82 @@ func Tier(v int) predicate.Vehicle { } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.Vehicle { +func CreatedAtEQ(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.Vehicle { +func CreatedAtNEQ(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.Vehicle { +func CreatedAtIn(vs ...time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.Vehicle { +func CreatedAtNotIn(vs ...time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.Vehicle { +func CreatedAtGT(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.Vehicle { +func CreatedAtGTE(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.Vehicle { +func CreatedAtLT(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.Vehicle { +func CreatedAtLTE(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.Vehicle { +func UpdatedAtEQ(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.Vehicle { +func UpdatedAtNEQ(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.Vehicle { +func UpdatedAtIn(vs ...time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.Vehicle { +func UpdatedAtNotIn(vs ...time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.Vehicle { +func UpdatedAtGT(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.Vehicle { +func UpdatedAtGTE(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.Vehicle { +func UpdatedAtLT(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.Vehicle { +func UpdatedAtLTE(v time.Time) predicate.Vehicle { return predicate.Vehicle(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/vehicle_create.go b/internal/database/ent/db/vehicle_create.go index b94dc90c..a11e8725 100644 --- a/internal/database/ent/db/vehicle_create.go +++ b/internal/database/ent/db/vehicle_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -20,29 +21,29 @@ type VehicleCreate struct { } // SetCreatedAt sets the "created_at" field. -func (vc *VehicleCreate) SetCreatedAt(i int64) *VehicleCreate { - vc.mutation.SetCreatedAt(i) +func (vc *VehicleCreate) SetCreatedAt(t time.Time) *VehicleCreate { + vc.mutation.SetCreatedAt(t) return vc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (vc *VehicleCreate) SetNillableCreatedAt(i *int64) *VehicleCreate { - if i != nil { - vc.SetCreatedAt(*i) +func (vc *VehicleCreate) SetNillableCreatedAt(t *time.Time) *VehicleCreate { + if t != nil { + vc.SetCreatedAt(*t) } return vc } // SetUpdatedAt sets the "updated_at" field. -func (vc *VehicleCreate) SetUpdatedAt(i int64) *VehicleCreate { - vc.mutation.SetUpdatedAt(i) +func (vc *VehicleCreate) SetUpdatedAt(t time.Time) *VehicleCreate { + vc.mutation.SetUpdatedAt(t) return vc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (vc *VehicleCreate) SetNillableUpdatedAt(i *int64) *VehicleCreate { - if i != nil { - vc.SetUpdatedAt(*i) +func (vc *VehicleCreate) SetNillableUpdatedAt(t *time.Time) *VehicleCreate { + if t != nil { + vc.SetUpdatedAt(*t) } return vc } @@ -165,11 +166,11 @@ func (vc *VehicleCreate) createSpec() (*Vehicle, *sqlgraph.CreateSpec) { _spec.ID.Value = id } if value, ok := vc.mutation.CreatedAt(); ok { - _spec.SetField(vehicle.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(vehicle.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := vc.mutation.UpdatedAt(); ok { - _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(vehicle.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := vc.mutation.Tier(); ok { diff --git a/internal/database/ent/db/vehicle_query.go b/internal/database/ent/db/vehicle_query.go index 6b09ce3b..3cc19a05 100644 --- a/internal/database/ent/db/vehicle_query.go +++ b/internal/database/ent/db/vehicle_query.go @@ -21,6 +21,7 @@ type VehicleQuery struct { order []vehicle.OrderOption inters []Interceptor predicates []predicate.Vehicle + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -261,7 +262,7 @@ func (vq *VehicleQuery) Clone() *VehicleQuery { // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +285,7 @@ func (vq *VehicleQuery) GroupBy(field string, fields ...string) *VehicleGroupBy // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.Vehicle.Query(). @@ -342,6 +343,9 @@ func (vq *VehicleQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Vehi nodes = append(nodes, node) return node.assignValues(columns, values) } + if len(vq.modifiers) > 0 { + _spec.Modifiers = vq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -356,6 +360,9 @@ func (vq *VehicleQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Vehi func (vq *VehicleQuery) sqlCount(ctx context.Context) (int, error) { _spec := vq.querySpec() + if len(vq.modifiers) > 0 { + _spec.Modifiers = vq.modifiers + } _spec.Node.Columns = vq.ctx.Fields if len(vq.ctx.Fields) > 0 { _spec.Unique = vq.ctx.Unique != nil && *vq.ctx.Unique @@ -418,6 +425,9 @@ func (vq *VehicleQuery) sqlQuery(ctx context.Context) *sql.Selector { if vq.ctx.Unique != nil && *vq.ctx.Unique { selector.Distinct() } + for _, m := range vq.modifiers { + m(selector) + } for _, p := range vq.predicates { p(selector) } @@ -435,6 +445,12 @@ func (vq *VehicleQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (vq *VehicleQuery) Modify(modifiers ...func(s *sql.Selector)) *VehicleSelect { + vq.modifiers = append(vq.modifiers, modifiers...) + return vq.Select() +} + // VehicleGroupBy is the group-by builder for Vehicle entities. type VehicleGroupBy struct { selector @@ -524,3 +540,9 @@ func (vs *VehicleSelect) sqlScan(ctx context.Context, root *VehicleQuery, v any) defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (vs *VehicleSelect) Modify(modifiers ...func(s *sql.Selector)) *VehicleSelect { + vs.modifiers = append(vs.modifiers, modifiers...) + return vs +} diff --git a/internal/database/ent/db/vehicle_update.go b/internal/database/ent/db/vehicle_update.go index f5cec932..6ef632ed 100644 --- a/internal/database/ent/db/vehicle_update.go +++ b/internal/database/ent/db/vehicle_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -17,8 +18,9 @@ import ( // VehicleUpdate is the builder for updating Vehicle entities. type VehicleUpdate struct { config - hooks []Hook - mutation *VehicleMutation + hooks []Hook + mutation *VehicleMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the VehicleUpdate builder. @@ -28,15 +30,8 @@ func (vu *VehicleUpdate) Where(ps ...predicate.Vehicle) *VehicleUpdate { } // SetUpdatedAt sets the "updated_at" field. -func (vu *VehicleUpdate) SetUpdatedAt(i int64) *VehicleUpdate { - vu.mutation.ResetUpdatedAt() - vu.mutation.SetUpdatedAt(i) - return vu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (vu *VehicleUpdate) AddUpdatedAt(i int64) *VehicleUpdate { - vu.mutation.AddUpdatedAt(i) +func (vu *VehicleUpdate) SetUpdatedAt(t time.Time) *VehicleUpdate { + vu.mutation.SetUpdatedAt(t) return vu } @@ -118,6 +113,12 @@ func (vu *VehicleUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (vu *VehicleUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *VehicleUpdate { + vu.modifiers = append(vu.modifiers, modifiers...) + return vu +} + func (vu *VehicleUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := vu.check(); err != nil { return n, err @@ -131,10 +132,7 @@ func (vu *VehicleUpdate) sqlSave(ctx context.Context) (n int, err error) { } } if value, ok := vu.mutation.UpdatedAt(); ok { - _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := vu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehicle.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(vehicle.FieldUpdatedAt, field.TypeTime, value) } if value, ok := vu.mutation.Tier(); ok { _spec.SetField(vehicle.FieldTier, field.TypeInt, value) @@ -145,6 +143,7 @@ func (vu *VehicleUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := vu.mutation.LocalizedNames(); ok { _spec.SetField(vehicle.FieldLocalizedNames, field.TypeJSON, value) } + _spec.AddModifiers(vu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, vu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{vehicle.Label} @@ -160,21 +159,15 @@ func (vu *VehicleUpdate) sqlSave(ctx context.Context) (n int, err error) { // VehicleUpdateOne is the builder for updating a single Vehicle entity. type VehicleUpdateOne struct { config - fields []string - hooks []Hook - mutation *VehicleMutation + fields []string + hooks []Hook + mutation *VehicleMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (vuo *VehicleUpdateOne) SetUpdatedAt(i int64) *VehicleUpdateOne { - vuo.mutation.ResetUpdatedAt() - vuo.mutation.SetUpdatedAt(i) - return vuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (vuo *VehicleUpdateOne) AddUpdatedAt(i int64) *VehicleUpdateOne { - vuo.mutation.AddUpdatedAt(i) +func (vuo *VehicleUpdateOne) SetUpdatedAt(t time.Time) *VehicleUpdateOne { + vuo.mutation.SetUpdatedAt(t) return vuo } @@ -269,6 +262,12 @@ func (vuo *VehicleUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (vuo *VehicleUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *VehicleUpdateOne { + vuo.modifiers = append(vuo.modifiers, modifiers...) + return vuo +} + func (vuo *VehicleUpdateOne) sqlSave(ctx context.Context) (_node *Vehicle, err error) { if err := vuo.check(); err != nil { return _node, err @@ -299,10 +298,7 @@ func (vuo *VehicleUpdateOne) sqlSave(ctx context.Context) (_node *Vehicle, err e } } if value, ok := vuo.mutation.UpdatedAt(); ok { - _spec.SetField(vehicle.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := vuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehicle.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(vehicle.FieldUpdatedAt, field.TypeTime, value) } if value, ok := vuo.mutation.Tier(); ok { _spec.SetField(vehicle.FieldTier, field.TypeInt, value) @@ -313,6 +309,7 @@ func (vuo *VehicleUpdateOne) sqlSave(ctx context.Context) (_node *Vehicle, err e if value, ok := vuo.mutation.LocalizedNames(); ok { _spec.SetField(vehicle.FieldLocalizedNames, field.TypeJSON, value) } + _spec.AddModifiers(vuo.modifiers...) _node = &Vehicle{config: vuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/vehicleaverage.go b/internal/database/ent/db/vehicleaverage.go index f24062ab..84c88a04 100644 --- a/internal/database/ent/db/vehicleaverage.go +++ b/internal/database/ent/db/vehicleaverage.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -19,9 +20,9 @@ type VehicleAverage struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Data holds the value of the "data" field. Data frame.StatsFrame `json:"data,omitempty"` selectValues sql.SelectValues @@ -34,10 +35,10 @@ func (*VehicleAverage) scanValues(columns []string) ([]any, error) { switch columns[i] { case vehicleaverage.FieldData: values[i] = new([]byte) - case vehicleaverage.FieldCreatedAt, vehicleaverage.FieldUpdatedAt: - values[i] = new(sql.NullInt64) case vehicleaverage.FieldID: values[i] = new(sql.NullString) + case vehicleaverage.FieldCreatedAt, vehicleaverage.FieldUpdatedAt: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -60,16 +61,16 @@ func (va *VehicleAverage) assignValues(columns []string, values []any) error { va.ID = value.String } case vehicleaverage.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - va.CreatedAt = value.Int64 + va.CreatedAt = value.Time } case vehicleaverage.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - va.UpdatedAt = value.Int64 + va.UpdatedAt = value.Time } case vehicleaverage.FieldData: if value, ok := values[i].(*[]byte); !ok { @@ -116,10 +117,10 @@ func (va *VehicleAverage) String() string { builder.WriteString("VehicleAverage(") builder.WriteString(fmt.Sprintf("id=%v, ", va.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", va.CreatedAt)) + builder.WriteString(va.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", va.UpdatedAt)) + builder.WriteString(va.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("data=") builder.WriteString(fmt.Sprintf("%v", va.Data)) diff --git a/internal/database/ent/db/vehicleaverage/vehicleaverage.go b/internal/database/ent/db/vehicleaverage/vehicleaverage.go index ab45c826..1c658ae4 100644 --- a/internal/database/ent/db/vehicleaverage/vehicleaverage.go +++ b/internal/database/ent/db/vehicleaverage/vehicleaverage.go @@ -3,6 +3,8 @@ package vehicleaverage import ( + "time" + "entgo.io/ent/dialect/sql" ) @@ -41,11 +43,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time ) // OrderOption defines the ordering options for the VehicleAverage queries. diff --git a/internal/database/ent/db/vehicleaverage/where.go b/internal/database/ent/db/vehicleaverage/where.go index e9f20546..5053b2dd 100644 --- a/internal/database/ent/db/vehicleaverage/where.go +++ b/internal/database/ent/db/vehicleaverage/where.go @@ -3,6 +3,8 @@ package vehicleaverage import ( + "time" + "entgo.io/ent/dialect/sql" "github.com/cufee/aftermath/internal/database/ent/db/predicate" ) @@ -63,92 +65,92 @@ func IDContainsFold(id string) predicate.VehicleAverage { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.VehicleAverage { +func CreatedAt(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.VehicleAverage { +func UpdatedAt(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldEQ(FieldUpdatedAt, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.VehicleAverage { +func CreatedAtEQ(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.VehicleAverage { +func CreatedAtNEQ(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.VehicleAverage { +func CreatedAtIn(vs ...time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.VehicleAverage { +func CreatedAtNotIn(vs ...time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.VehicleAverage { +func CreatedAtGT(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.VehicleAverage { +func CreatedAtGTE(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.VehicleAverage { +func CreatedAtLT(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.VehicleAverage { +func CreatedAtLTE(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.VehicleAverage { +func UpdatedAtEQ(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.VehicleAverage { +func UpdatedAtNEQ(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.VehicleAverage { +func UpdatedAtIn(vs ...time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.VehicleAverage { +func UpdatedAtNotIn(vs ...time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.VehicleAverage { +func UpdatedAtGT(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.VehicleAverage { +func UpdatedAtGTE(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.VehicleAverage { +func UpdatedAtLT(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.VehicleAverage { +func UpdatedAtLTE(v time.Time) predicate.VehicleAverage { return predicate.VehicleAverage(sql.FieldLTE(FieldUpdatedAt, v)) } diff --git a/internal/database/ent/db/vehicleaverage_create.go b/internal/database/ent/db/vehicleaverage_create.go index 1ed5f96c..73d73a36 100644 --- a/internal/database/ent/db/vehicleaverage_create.go +++ b/internal/database/ent/db/vehicleaverage_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -21,29 +22,29 @@ type VehicleAverageCreate struct { } // SetCreatedAt sets the "created_at" field. -func (vac *VehicleAverageCreate) SetCreatedAt(i int64) *VehicleAverageCreate { - vac.mutation.SetCreatedAt(i) +func (vac *VehicleAverageCreate) SetCreatedAt(t time.Time) *VehicleAverageCreate { + vac.mutation.SetCreatedAt(t) return vac } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (vac *VehicleAverageCreate) SetNillableCreatedAt(i *int64) *VehicleAverageCreate { - if i != nil { - vac.SetCreatedAt(*i) +func (vac *VehicleAverageCreate) SetNillableCreatedAt(t *time.Time) *VehicleAverageCreate { + if t != nil { + vac.SetCreatedAt(*t) } return vac } // SetUpdatedAt sets the "updated_at" field. -func (vac *VehicleAverageCreate) SetUpdatedAt(i int64) *VehicleAverageCreate { - vac.mutation.SetUpdatedAt(i) +func (vac *VehicleAverageCreate) SetUpdatedAt(t time.Time) *VehicleAverageCreate { + vac.mutation.SetUpdatedAt(t) return vac } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (vac *VehicleAverageCreate) SetNillableUpdatedAt(i *int64) *VehicleAverageCreate { - if i != nil { - vac.SetUpdatedAt(*i) +func (vac *VehicleAverageCreate) SetNillableUpdatedAt(t *time.Time) *VehicleAverageCreate { + if t != nil { + vac.SetUpdatedAt(*t) } return vac } @@ -152,11 +153,11 @@ func (vac *VehicleAverageCreate) createSpec() (*VehicleAverage, *sqlgraph.Create _spec.ID.Value = id } if value, ok := vac.mutation.CreatedAt(); ok { - _spec.SetField(vehicleaverage.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(vehicleaverage.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := vac.mutation.UpdatedAt(); ok { - _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := vac.mutation.Data(); ok { diff --git a/internal/database/ent/db/vehicleaverage_query.go b/internal/database/ent/db/vehicleaverage_query.go index b5c7891d..cbddd69c 100644 --- a/internal/database/ent/db/vehicleaverage_query.go +++ b/internal/database/ent/db/vehicleaverage_query.go @@ -21,6 +21,7 @@ type VehicleAverageQuery struct { order []vehicleaverage.OrderOption inters []Interceptor predicates []predicate.VehicleAverage + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -261,7 +262,7 @@ func (vaq *VehicleAverageQuery) Clone() *VehicleAverageQuery { // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -284,7 +285,7 @@ func (vaq *VehicleAverageQuery) GroupBy(field string, fields ...string) *Vehicle // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.VehicleAverage.Query(). @@ -342,6 +343,9 @@ func (vaq *VehicleAverageQuery) sqlAll(ctx context.Context, hooks ...queryHook) nodes = append(nodes, node) return node.assignValues(columns, values) } + if len(vaq.modifiers) > 0 { + _spec.Modifiers = vaq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -356,6 +360,9 @@ func (vaq *VehicleAverageQuery) sqlAll(ctx context.Context, hooks ...queryHook) func (vaq *VehicleAverageQuery) sqlCount(ctx context.Context) (int, error) { _spec := vaq.querySpec() + if len(vaq.modifiers) > 0 { + _spec.Modifiers = vaq.modifiers + } _spec.Node.Columns = vaq.ctx.Fields if len(vaq.ctx.Fields) > 0 { _spec.Unique = vaq.ctx.Unique != nil && *vaq.ctx.Unique @@ -418,6 +425,9 @@ func (vaq *VehicleAverageQuery) sqlQuery(ctx context.Context) *sql.Selector { if vaq.ctx.Unique != nil && *vaq.ctx.Unique { selector.Distinct() } + for _, m := range vaq.modifiers { + m(selector) + } for _, p := range vaq.predicates { p(selector) } @@ -435,6 +445,12 @@ func (vaq *VehicleAverageQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (vaq *VehicleAverageQuery) Modify(modifiers ...func(s *sql.Selector)) *VehicleAverageSelect { + vaq.modifiers = append(vaq.modifiers, modifiers...) + return vaq.Select() +} + // VehicleAverageGroupBy is the group-by builder for VehicleAverage entities. type VehicleAverageGroupBy struct { selector @@ -524,3 +540,9 @@ func (vas *VehicleAverageSelect) sqlScan(ctx context.Context, root *VehicleAvera defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (vas *VehicleAverageSelect) Modify(modifiers ...func(s *sql.Selector)) *VehicleAverageSelect { + vas.modifiers = append(vas.modifiers, modifiers...) + return vas +} diff --git a/internal/database/ent/db/vehicleaverage_update.go b/internal/database/ent/db/vehicleaverage_update.go index 199f96c2..1152f2d8 100644 --- a/internal/database/ent/db/vehicleaverage_update.go +++ b/internal/database/ent/db/vehicleaverage_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -18,8 +19,9 @@ import ( // VehicleAverageUpdate is the builder for updating VehicleAverage entities. type VehicleAverageUpdate struct { config - hooks []Hook - mutation *VehicleAverageMutation + hooks []Hook + mutation *VehicleAverageMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the VehicleAverageUpdate builder. @@ -29,15 +31,8 @@ func (vau *VehicleAverageUpdate) Where(ps ...predicate.VehicleAverage) *VehicleA } // SetUpdatedAt sets the "updated_at" field. -func (vau *VehicleAverageUpdate) SetUpdatedAt(i int64) *VehicleAverageUpdate { - vau.mutation.ResetUpdatedAt() - vau.mutation.SetUpdatedAt(i) - return vau -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (vau *VehicleAverageUpdate) AddUpdatedAt(i int64) *VehicleAverageUpdate { - vau.mutation.AddUpdatedAt(i) +func (vau *VehicleAverageUpdate) SetUpdatedAt(t time.Time) *VehicleAverageUpdate { + vau.mutation.SetUpdatedAt(t) return vau } @@ -96,6 +91,12 @@ func (vau *VehicleAverageUpdate) defaults() { } } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (vau *VehicleAverageUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *VehicleAverageUpdate { + vau.modifiers = append(vau.modifiers, modifiers...) + return vau +} + func (vau *VehicleAverageUpdate) sqlSave(ctx context.Context) (n int, err error) { _spec := sqlgraph.NewUpdateSpec(vehicleaverage.Table, vehicleaverage.Columns, sqlgraph.NewFieldSpec(vehicleaverage.FieldID, field.TypeString)) if ps := vau.mutation.predicates; len(ps) > 0 { @@ -106,14 +107,12 @@ func (vau *VehicleAverageUpdate) sqlSave(ctx context.Context) (n int, err error) } } if value, ok := vau.mutation.UpdatedAt(); ok { - _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := vau.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeTime, value) } if value, ok := vau.mutation.Data(); ok { _spec.SetField(vehicleaverage.FieldData, field.TypeJSON, value) } + _spec.AddModifiers(vau.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, vau.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{vehicleaverage.Label} @@ -129,21 +128,15 @@ func (vau *VehicleAverageUpdate) sqlSave(ctx context.Context) (n int, err error) // VehicleAverageUpdateOne is the builder for updating a single VehicleAverage entity. type VehicleAverageUpdateOne struct { config - fields []string - hooks []Hook - mutation *VehicleAverageMutation + fields []string + hooks []Hook + mutation *VehicleAverageMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (vauo *VehicleAverageUpdateOne) SetUpdatedAt(i int64) *VehicleAverageUpdateOne { - vauo.mutation.ResetUpdatedAt() - vauo.mutation.SetUpdatedAt(i) - return vauo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (vauo *VehicleAverageUpdateOne) AddUpdatedAt(i int64) *VehicleAverageUpdateOne { - vauo.mutation.AddUpdatedAt(i) +func (vauo *VehicleAverageUpdateOne) SetUpdatedAt(t time.Time) *VehicleAverageUpdateOne { + vauo.mutation.SetUpdatedAt(t) return vauo } @@ -215,6 +208,12 @@ func (vauo *VehicleAverageUpdateOne) defaults() { } } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (vauo *VehicleAverageUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *VehicleAverageUpdateOne { + vauo.modifiers = append(vauo.modifiers, modifiers...) + return vauo +} + func (vauo *VehicleAverageUpdateOne) sqlSave(ctx context.Context) (_node *VehicleAverage, err error) { _spec := sqlgraph.NewUpdateSpec(vehicleaverage.Table, vehicleaverage.Columns, sqlgraph.NewFieldSpec(vehicleaverage.FieldID, field.TypeString)) id, ok := vauo.mutation.ID() @@ -242,14 +241,12 @@ func (vauo *VehicleAverageUpdateOne) sqlSave(ctx context.Context) (_node *Vehicl } } if value, ok := vauo.mutation.UpdatedAt(); ok { - _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := vauo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehicleaverage.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(vehicleaverage.FieldUpdatedAt, field.TypeTime, value) } if value, ok := vauo.mutation.Data(); ok { _spec.SetField(vehicleaverage.FieldData, field.TypeJSON, value) } + _spec.AddModifiers(vauo.modifiers...) _node = &VehicleAverage{config: vauo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/db/vehiclesnapshot.go b/internal/database/ent/db/vehiclesnapshot.go index 03565cf0..126b4036 100644 --- a/internal/database/ent/db/vehiclesnapshot.go +++ b/internal/database/ent/db/vehiclesnapshot.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -21,9 +22,9 @@ type VehicleSnapshot struct { // ID of the ent. ID string `json:"id,omitempty"` // CreatedAt holds the value of the "created_at" field. - CreatedAt int64 `json:"created_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. - UpdatedAt int64 `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type models.SnapshotType `json:"type,omitempty"` // AccountID holds the value of the "account_id" field. @@ -35,7 +36,7 @@ type VehicleSnapshot struct { // Battles holds the value of the "battles" field. Battles int `json:"battles,omitempty"` // LastBattleTime holds the value of the "last_battle_time" field. - LastBattleTime int64 `json:"last_battle_time,omitempty"` + LastBattleTime time.Time `json:"last_battle_time,omitempty"` // Frame holds the value of the "frame" field. Frame frame.StatsFrame `json:"frame,omitempty"` // Edges holds the relations/edges for other nodes in the graph. @@ -71,10 +72,12 @@ func (*VehicleSnapshot) scanValues(columns []string) ([]any, error) { switch columns[i] { case vehiclesnapshot.FieldFrame: values[i] = new([]byte) - case vehiclesnapshot.FieldCreatedAt, vehiclesnapshot.FieldUpdatedAt, vehiclesnapshot.FieldBattles, vehiclesnapshot.FieldLastBattleTime: + case vehiclesnapshot.FieldBattles: values[i] = new(sql.NullInt64) case vehiclesnapshot.FieldID, vehiclesnapshot.FieldType, vehiclesnapshot.FieldAccountID, vehiclesnapshot.FieldVehicleID, vehiclesnapshot.FieldReferenceID: values[i] = new(sql.NullString) + case vehiclesnapshot.FieldCreatedAt, vehiclesnapshot.FieldUpdatedAt, vehiclesnapshot.FieldLastBattleTime: + values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } @@ -97,16 +100,16 @@ func (vs *VehicleSnapshot) assignValues(columns []string, values []any) error { vs.ID = value.String } case vehiclesnapshot.FieldCreatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { - vs.CreatedAt = value.Int64 + vs.CreatedAt = value.Time } case vehiclesnapshot.FieldUpdatedAt: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) } else if value.Valid { - vs.UpdatedAt = value.Int64 + vs.UpdatedAt = value.Time } case vehiclesnapshot.FieldType: if value, ok := values[i].(*sql.NullString); !ok { @@ -139,10 +142,10 @@ func (vs *VehicleSnapshot) assignValues(columns []string, values []any) error { vs.Battles = int(value.Int64) } case vehiclesnapshot.FieldLastBattleTime: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field last_battle_time", values[i]) } else if value.Valid { - vs.LastBattleTime = value.Int64 + vs.LastBattleTime = value.Time } case vehiclesnapshot.FieldFrame: if value, ok := values[i].(*[]byte); !ok { @@ -194,10 +197,10 @@ func (vs *VehicleSnapshot) String() string { builder.WriteString("VehicleSnapshot(") builder.WriteString(fmt.Sprintf("id=%v, ", vs.ID)) builder.WriteString("created_at=") - builder.WriteString(fmt.Sprintf("%v", vs.CreatedAt)) + builder.WriteString(vs.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("updated_at=") - builder.WriteString(fmt.Sprintf("%v", vs.UpdatedAt)) + builder.WriteString(vs.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("type=") builder.WriteString(fmt.Sprintf("%v", vs.Type)) @@ -215,7 +218,7 @@ func (vs *VehicleSnapshot) String() string { builder.WriteString(fmt.Sprintf("%v", vs.Battles)) builder.WriteString(", ") builder.WriteString("last_battle_time=") - builder.WriteString(fmt.Sprintf("%v", vs.LastBattleTime)) + builder.WriteString(vs.LastBattleTime.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("frame=") builder.WriteString(fmt.Sprintf("%v", vs.Frame)) diff --git a/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go b/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go index 0d13bf1a..e0a3d4dc 100644 --- a/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go +++ b/internal/database/ent/db/vehiclesnapshot/vehiclesnapshot.go @@ -4,6 +4,7 @@ package vehiclesnapshot import ( "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -72,11 +73,11 @@ func ValidColumn(column string) bool { var ( // DefaultCreatedAt holds the default value on creation for the "created_at" field. - DefaultCreatedAt func() int64 + DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. - DefaultUpdatedAt func() int64 + DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. - UpdateDefaultUpdatedAt func() int64 + UpdateDefaultUpdatedAt func() time.Time // AccountIDValidator is a validator for the "account_id" field. It is called by the builders before save. AccountIDValidator func(string) error // VehicleIDValidator is a validator for the "vehicle_id" field. It is called by the builders before save. diff --git a/internal/database/ent/db/vehiclesnapshot/where.go b/internal/database/ent/db/vehiclesnapshot/where.go index d0489259..18ff9c52 100644 --- a/internal/database/ent/db/vehiclesnapshot/where.go +++ b/internal/database/ent/db/vehiclesnapshot/where.go @@ -3,6 +3,8 @@ package vehiclesnapshot import ( + "time" + "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/cufee/aftermath/internal/database/ent/db/predicate" @@ -65,12 +67,12 @@ func IDContainsFold(id string) predicate.VehicleSnapshot { } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. -func CreatedAt(v int64) predicate.VehicleSnapshot { +func CreatedAt(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. -func UpdatedAt(v int64) predicate.VehicleSnapshot { +func UpdatedAt(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } @@ -95,87 +97,87 @@ func Battles(v int) predicate.VehicleSnapshot { } // LastBattleTime applies equality check predicate on the "last_battle_time" field. It's identical to LastBattleTimeEQ. -func LastBattleTime(v int64) predicate.VehicleSnapshot { +func LastBattleTime(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. -func CreatedAtEQ(v int64) predicate.VehicleSnapshot { +func CreatedAtEQ(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. -func CreatedAtNEQ(v int64) predicate.VehicleSnapshot { +func CreatedAtNEQ(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. -func CreatedAtIn(vs ...int64) predicate.VehicleSnapshot { +func CreatedAtIn(vs ...time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. -func CreatedAtNotIn(vs ...int64) predicate.VehicleSnapshot { +func CreatedAtNotIn(vs ...time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. -func CreatedAtGT(v int64) predicate.VehicleSnapshot { +func CreatedAtGT(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. -func CreatedAtGTE(v int64) predicate.VehicleSnapshot { +func CreatedAtGTE(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. -func CreatedAtLT(v int64) predicate.VehicleSnapshot { +func CreatedAtLT(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. -func CreatedAtLTE(v int64) predicate.VehicleSnapshot { +func CreatedAtLTE(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLTE(FieldCreatedAt, v)) } // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. -func UpdatedAtEQ(v int64) predicate.VehicleSnapshot { +func UpdatedAtEQ(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldUpdatedAt, v)) } // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. -func UpdatedAtNEQ(v int64) predicate.VehicleSnapshot { +func UpdatedAtNEQ(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNEQ(FieldUpdatedAt, v)) } // UpdatedAtIn applies the In predicate on the "updated_at" field. -func UpdatedAtIn(vs ...int64) predicate.VehicleSnapshot { +func UpdatedAtIn(vs ...time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldIn(FieldUpdatedAt, vs...)) } // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. -func UpdatedAtNotIn(vs ...int64) predicate.VehicleSnapshot { +func UpdatedAtNotIn(vs ...time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNotIn(FieldUpdatedAt, vs...)) } // UpdatedAtGT applies the GT predicate on the "updated_at" field. -func UpdatedAtGT(v int64) predicate.VehicleSnapshot { +func UpdatedAtGT(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGT(FieldUpdatedAt, v)) } // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. -func UpdatedAtGTE(v int64) predicate.VehicleSnapshot { +func UpdatedAtGTE(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGTE(FieldUpdatedAt, v)) } // UpdatedAtLT applies the LT predicate on the "updated_at" field. -func UpdatedAtLT(v int64) predicate.VehicleSnapshot { +func UpdatedAtLT(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLT(FieldUpdatedAt, v)) } // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. -func UpdatedAtLTE(v int64) predicate.VehicleSnapshot { +func UpdatedAtLTE(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLTE(FieldUpdatedAt, v)) } @@ -445,42 +447,42 @@ func BattlesLTE(v int) predicate.VehicleSnapshot { } // LastBattleTimeEQ applies the EQ predicate on the "last_battle_time" field. -func LastBattleTimeEQ(v int64) predicate.VehicleSnapshot { +func LastBattleTimeEQ(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldEQ(FieldLastBattleTime, v)) } // LastBattleTimeNEQ applies the NEQ predicate on the "last_battle_time" field. -func LastBattleTimeNEQ(v int64) predicate.VehicleSnapshot { +func LastBattleTimeNEQ(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNEQ(FieldLastBattleTime, v)) } // LastBattleTimeIn applies the In predicate on the "last_battle_time" field. -func LastBattleTimeIn(vs ...int64) predicate.VehicleSnapshot { +func LastBattleTimeIn(vs ...time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldIn(FieldLastBattleTime, vs...)) } // LastBattleTimeNotIn applies the NotIn predicate on the "last_battle_time" field. -func LastBattleTimeNotIn(vs ...int64) predicate.VehicleSnapshot { +func LastBattleTimeNotIn(vs ...time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldNotIn(FieldLastBattleTime, vs...)) } // LastBattleTimeGT applies the GT predicate on the "last_battle_time" field. -func LastBattleTimeGT(v int64) predicate.VehicleSnapshot { +func LastBattleTimeGT(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGT(FieldLastBattleTime, v)) } // LastBattleTimeGTE applies the GTE predicate on the "last_battle_time" field. -func LastBattleTimeGTE(v int64) predicate.VehicleSnapshot { +func LastBattleTimeGTE(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldGTE(FieldLastBattleTime, v)) } // LastBattleTimeLT applies the LT predicate on the "last_battle_time" field. -func LastBattleTimeLT(v int64) predicate.VehicleSnapshot { +func LastBattleTimeLT(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLT(FieldLastBattleTime, v)) } // LastBattleTimeLTE applies the LTE predicate on the "last_battle_time" field. -func LastBattleTimeLTE(v int64) predicate.VehicleSnapshot { +func LastBattleTimeLTE(v time.Time) predicate.VehicleSnapshot { return predicate.VehicleSnapshot(sql.FieldLTE(FieldLastBattleTime, v)) } diff --git a/internal/database/ent/db/vehiclesnapshot_create.go b/internal/database/ent/db/vehiclesnapshot_create.go index 32c64243..c0801288 100644 --- a/internal/database/ent/db/vehiclesnapshot_create.go +++ b/internal/database/ent/db/vehiclesnapshot_create.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" @@ -23,29 +24,29 @@ type VehicleSnapshotCreate struct { } // SetCreatedAt sets the "created_at" field. -func (vsc *VehicleSnapshotCreate) SetCreatedAt(i int64) *VehicleSnapshotCreate { - vsc.mutation.SetCreatedAt(i) +func (vsc *VehicleSnapshotCreate) SetCreatedAt(t time.Time) *VehicleSnapshotCreate { + vsc.mutation.SetCreatedAt(t) return vsc } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. -func (vsc *VehicleSnapshotCreate) SetNillableCreatedAt(i *int64) *VehicleSnapshotCreate { - if i != nil { - vsc.SetCreatedAt(*i) +func (vsc *VehicleSnapshotCreate) SetNillableCreatedAt(t *time.Time) *VehicleSnapshotCreate { + if t != nil { + vsc.SetCreatedAt(*t) } return vsc } // SetUpdatedAt sets the "updated_at" field. -func (vsc *VehicleSnapshotCreate) SetUpdatedAt(i int64) *VehicleSnapshotCreate { - vsc.mutation.SetUpdatedAt(i) +func (vsc *VehicleSnapshotCreate) SetUpdatedAt(t time.Time) *VehicleSnapshotCreate { + vsc.mutation.SetUpdatedAt(t) return vsc } // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. -func (vsc *VehicleSnapshotCreate) SetNillableUpdatedAt(i *int64) *VehicleSnapshotCreate { - if i != nil { - vsc.SetUpdatedAt(*i) +func (vsc *VehicleSnapshotCreate) SetNillableUpdatedAt(t *time.Time) *VehicleSnapshotCreate { + if t != nil { + vsc.SetUpdatedAt(*t) } return vsc } @@ -81,8 +82,8 @@ func (vsc *VehicleSnapshotCreate) SetBattles(i int) *VehicleSnapshotCreate { } // SetLastBattleTime sets the "last_battle_time" field. -func (vsc *VehicleSnapshotCreate) SetLastBattleTime(i int64) *VehicleSnapshotCreate { - vsc.mutation.SetLastBattleTime(i) +func (vsc *VehicleSnapshotCreate) SetLastBattleTime(t time.Time) *VehicleSnapshotCreate { + vsc.mutation.SetLastBattleTime(t) return vsc } @@ -248,11 +249,11 @@ func (vsc *VehicleSnapshotCreate) createSpec() (*VehicleSnapshot, *sqlgraph.Crea _spec.ID.Value = id } if value, ok := vsc.mutation.CreatedAt(); ok { - _spec.SetField(vehiclesnapshot.FieldCreatedAt, field.TypeInt64, value) + _spec.SetField(vehiclesnapshot.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := vsc.mutation.UpdatedAt(); ok { - _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } if value, ok := vsc.mutation.GetType(); ok { @@ -272,7 +273,7 @@ func (vsc *VehicleSnapshotCreate) createSpec() (*VehicleSnapshot, *sqlgraph.Crea _node.Battles = value } if value, ok := vsc.mutation.LastBattleTime(); ok { - _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeTime, value) _node.LastBattleTime = value } if value, ok := vsc.mutation.Frame(); ok { diff --git a/internal/database/ent/db/vehiclesnapshot_query.go b/internal/database/ent/db/vehiclesnapshot_query.go index c6c9730e..4796b165 100644 --- a/internal/database/ent/db/vehiclesnapshot_query.go +++ b/internal/database/ent/db/vehiclesnapshot_query.go @@ -23,6 +23,7 @@ type VehicleSnapshotQuery struct { inters []Interceptor predicates []predicate.VehicleSnapshot withAccount *AccountQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -297,7 +298,7 @@ func (vsq *VehicleSnapshotQuery) WithAccount(opts ...func(*AccountQuery)) *Vehic // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // Count int `json:"count,omitempty"` // } // @@ -320,7 +321,7 @@ func (vsq *VehicleSnapshotQuery) GroupBy(field string, fields ...string) *Vehicl // Example: // // var v []struct { -// CreatedAt int64 `json:"created_at,omitempty"` +// CreatedAt time.Time `json:"created_at,omitempty"` // } // // client.VehicleSnapshot.Query(). @@ -382,6 +383,9 @@ func (vsq *VehicleSnapshotQuery) sqlAll(ctx context.Context, hooks ...queryHook) node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } + if len(vsq.modifiers) > 0 { + _spec.Modifiers = vsq.modifiers + } for i := range hooks { hooks[i](ctx, _spec) } @@ -432,6 +436,9 @@ func (vsq *VehicleSnapshotQuery) loadAccount(ctx context.Context, query *Account func (vsq *VehicleSnapshotQuery) sqlCount(ctx context.Context) (int, error) { _spec := vsq.querySpec() + if len(vsq.modifiers) > 0 { + _spec.Modifiers = vsq.modifiers + } _spec.Node.Columns = vsq.ctx.Fields if len(vsq.ctx.Fields) > 0 { _spec.Unique = vsq.ctx.Unique != nil && *vsq.ctx.Unique @@ -497,6 +504,9 @@ func (vsq *VehicleSnapshotQuery) sqlQuery(ctx context.Context) *sql.Selector { if vsq.ctx.Unique != nil && *vsq.ctx.Unique { selector.Distinct() } + for _, m := range vsq.modifiers { + m(selector) + } for _, p := range vsq.predicates { p(selector) } @@ -514,6 +524,12 @@ func (vsq *VehicleSnapshotQuery) sqlQuery(ctx context.Context) *sql.Selector { return selector } +// Modify adds a query modifier for attaching custom logic to queries. +func (vsq *VehicleSnapshotQuery) Modify(modifiers ...func(s *sql.Selector)) *VehicleSnapshotSelect { + vsq.modifiers = append(vsq.modifiers, modifiers...) + return vsq.Select() +} + // VehicleSnapshotGroupBy is the group-by builder for VehicleSnapshot entities. type VehicleSnapshotGroupBy struct { selector @@ -603,3 +619,9 @@ func (vss *VehicleSnapshotSelect) sqlScan(ctx context.Context, root *VehicleSnap defer rows.Close() return sql.ScanSlice(rows, v) } + +// Modify adds a query modifier for attaching custom logic to queries. +func (vss *VehicleSnapshotSelect) Modify(modifiers ...func(s *sql.Selector)) *VehicleSnapshotSelect { + vss.modifiers = append(vss.modifiers, modifiers...) + return vss +} diff --git a/internal/database/ent/db/vehiclesnapshot_update.go b/internal/database/ent/db/vehiclesnapshot_update.go index ec24d4d9..e8d7b5a6 100644 --- a/internal/database/ent/db/vehiclesnapshot_update.go +++ b/internal/database/ent/db/vehiclesnapshot_update.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -19,8 +20,9 @@ import ( // VehicleSnapshotUpdate is the builder for updating VehicleSnapshot entities. type VehicleSnapshotUpdate struct { config - hooks []Hook - mutation *VehicleSnapshotMutation + hooks []Hook + mutation *VehicleSnapshotMutation + modifiers []func(*sql.UpdateBuilder) } // Where appends a list predicates to the VehicleSnapshotUpdate builder. @@ -30,15 +32,8 @@ func (vsu *VehicleSnapshotUpdate) Where(ps ...predicate.VehicleSnapshot) *Vehicl } // SetUpdatedAt sets the "updated_at" field. -func (vsu *VehicleSnapshotUpdate) SetUpdatedAt(i int64) *VehicleSnapshotUpdate { - vsu.mutation.ResetUpdatedAt() - vsu.mutation.SetUpdatedAt(i) - return vsu -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (vsu *VehicleSnapshotUpdate) AddUpdatedAt(i int64) *VehicleSnapshotUpdate { - vsu.mutation.AddUpdatedAt(i) +func (vsu *VehicleSnapshotUpdate) SetUpdatedAt(t time.Time) *VehicleSnapshotUpdate { + vsu.mutation.SetUpdatedAt(t) return vsu } @@ -92,26 +87,19 @@ func (vsu *VehicleSnapshotUpdate) AddBattles(i int) *VehicleSnapshotUpdate { } // SetLastBattleTime sets the "last_battle_time" field. -func (vsu *VehicleSnapshotUpdate) SetLastBattleTime(i int64) *VehicleSnapshotUpdate { - vsu.mutation.ResetLastBattleTime() - vsu.mutation.SetLastBattleTime(i) +func (vsu *VehicleSnapshotUpdate) SetLastBattleTime(t time.Time) *VehicleSnapshotUpdate { + vsu.mutation.SetLastBattleTime(t) return vsu } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (vsu *VehicleSnapshotUpdate) SetNillableLastBattleTime(i *int64) *VehicleSnapshotUpdate { - if i != nil { - vsu.SetLastBattleTime(*i) +func (vsu *VehicleSnapshotUpdate) SetNillableLastBattleTime(t *time.Time) *VehicleSnapshotUpdate { + if t != nil { + vsu.SetLastBattleTime(*t) } return vsu } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (vsu *VehicleSnapshotUpdate) AddLastBattleTime(i int64) *VehicleSnapshotUpdate { - vsu.mutation.AddLastBattleTime(i) - return vsu -} - // SetFrame sets the "frame" field. func (vsu *VehicleSnapshotUpdate) SetFrame(ff frame.StatsFrame) *VehicleSnapshotUpdate { vsu.mutation.SetFrame(ff) @@ -185,6 +173,12 @@ func (vsu *VehicleSnapshotUpdate) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (vsu *VehicleSnapshotUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *VehicleSnapshotUpdate { + vsu.modifiers = append(vsu.modifiers, modifiers...) + return vsu +} + func (vsu *VehicleSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error) { if err := vsu.check(); err != nil { return n, err @@ -198,10 +192,7 @@ func (vsu *VehicleSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error } } if value, ok := vsu.mutation.UpdatedAt(); ok { - _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := vsu.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeTime, value) } if value, ok := vsu.mutation.GetType(); ok { _spec.SetField(vehiclesnapshot.FieldType, field.TypeEnum, value) @@ -216,14 +207,12 @@ func (vsu *VehicleSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error _spec.AddField(vehiclesnapshot.FieldBattles, field.TypeInt, value) } if value, ok := vsu.mutation.LastBattleTime(); ok { - _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) - } - if value, ok := vsu.mutation.AddedLastBattleTime(); ok { - _spec.AddField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeTime, value) } if value, ok := vsu.mutation.Frame(); ok { _spec.SetField(vehiclesnapshot.FieldFrame, field.TypeJSON, value) } + _spec.AddModifiers(vsu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, vsu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{vehiclesnapshot.Label} @@ -239,21 +228,15 @@ func (vsu *VehicleSnapshotUpdate) sqlSave(ctx context.Context) (n int, err error // VehicleSnapshotUpdateOne is the builder for updating a single VehicleSnapshot entity. type VehicleSnapshotUpdateOne struct { config - fields []string - hooks []Hook - mutation *VehicleSnapshotMutation + fields []string + hooks []Hook + mutation *VehicleSnapshotMutation + modifiers []func(*sql.UpdateBuilder) } // SetUpdatedAt sets the "updated_at" field. -func (vsuo *VehicleSnapshotUpdateOne) SetUpdatedAt(i int64) *VehicleSnapshotUpdateOne { - vsuo.mutation.ResetUpdatedAt() - vsuo.mutation.SetUpdatedAt(i) - return vsuo -} - -// AddUpdatedAt adds i to the "updated_at" field. -func (vsuo *VehicleSnapshotUpdateOne) AddUpdatedAt(i int64) *VehicleSnapshotUpdateOne { - vsuo.mutation.AddUpdatedAt(i) +func (vsuo *VehicleSnapshotUpdateOne) SetUpdatedAt(t time.Time) *VehicleSnapshotUpdateOne { + vsuo.mutation.SetUpdatedAt(t) return vsuo } @@ -307,26 +290,19 @@ func (vsuo *VehicleSnapshotUpdateOne) AddBattles(i int) *VehicleSnapshotUpdateOn } // SetLastBattleTime sets the "last_battle_time" field. -func (vsuo *VehicleSnapshotUpdateOne) SetLastBattleTime(i int64) *VehicleSnapshotUpdateOne { - vsuo.mutation.ResetLastBattleTime() - vsuo.mutation.SetLastBattleTime(i) +func (vsuo *VehicleSnapshotUpdateOne) SetLastBattleTime(t time.Time) *VehicleSnapshotUpdateOne { + vsuo.mutation.SetLastBattleTime(t) return vsuo } // SetNillableLastBattleTime sets the "last_battle_time" field if the given value is not nil. -func (vsuo *VehicleSnapshotUpdateOne) SetNillableLastBattleTime(i *int64) *VehicleSnapshotUpdateOne { - if i != nil { - vsuo.SetLastBattleTime(*i) +func (vsuo *VehicleSnapshotUpdateOne) SetNillableLastBattleTime(t *time.Time) *VehicleSnapshotUpdateOne { + if t != nil { + vsuo.SetLastBattleTime(*t) } return vsuo } -// AddLastBattleTime adds i to the "last_battle_time" field. -func (vsuo *VehicleSnapshotUpdateOne) AddLastBattleTime(i int64) *VehicleSnapshotUpdateOne { - vsuo.mutation.AddLastBattleTime(i) - return vsuo -} - // SetFrame sets the "frame" field. func (vsuo *VehicleSnapshotUpdateOne) SetFrame(ff frame.StatsFrame) *VehicleSnapshotUpdateOne { vsuo.mutation.SetFrame(ff) @@ -413,6 +389,12 @@ func (vsuo *VehicleSnapshotUpdateOne) check() error { return nil } +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (vsuo *VehicleSnapshotUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *VehicleSnapshotUpdateOne { + vsuo.modifiers = append(vsuo.modifiers, modifiers...) + return vsuo +} + func (vsuo *VehicleSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *VehicleSnapshot, err error) { if err := vsuo.check(); err != nil { return _node, err @@ -443,10 +425,7 @@ func (vsuo *VehicleSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *Vehic } } if value, ok := vsuo.mutation.UpdatedAt(); ok { - _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) - } - if value, ok := vsuo.mutation.AddedUpdatedAt(); ok { - _spec.AddField(vehiclesnapshot.FieldUpdatedAt, field.TypeInt64, value) + _spec.SetField(vehiclesnapshot.FieldUpdatedAt, field.TypeTime, value) } if value, ok := vsuo.mutation.GetType(); ok { _spec.SetField(vehiclesnapshot.FieldType, field.TypeEnum, value) @@ -461,14 +440,12 @@ func (vsuo *VehicleSnapshotUpdateOne) sqlSave(ctx context.Context) (_node *Vehic _spec.AddField(vehiclesnapshot.FieldBattles, field.TypeInt, value) } if value, ok := vsuo.mutation.LastBattleTime(); ok { - _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) - } - if value, ok := vsuo.mutation.AddedLastBattleTime(); ok { - _spec.AddField(vehiclesnapshot.FieldLastBattleTime, field.TypeInt64, value) + _spec.SetField(vehiclesnapshot.FieldLastBattleTime, field.TypeTime, value) } if value, ok := vsuo.mutation.Frame(); ok { _spec.SetField(vehiclesnapshot.FieldFrame, field.TypeJSON, value) } + _spec.AddModifiers(vsuo.modifiers...) _node = &VehicleSnapshot{config: vsuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/database/ent/generate.go b/internal/database/ent/generate.go index f92ce718..ef051213 100644 --- a/internal/database/ent/generate.go +++ b/internal/database/ent/generate.go @@ -1,3 +1,3 @@ package ent -//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration,sql/execquery --target ./db ./schema +//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration,sql/execquery,sql/modifier --template ./templates/expose.tmpl --target ./db ./schema diff --git a/internal/database/ent/migrate/main.go b/internal/database/ent/migrate/main.go deleted file mode 100644 index 5932cafc..00000000 --- a/internal/database/ent/migrate/main.go +++ /dev/null @@ -1,39 +0,0 @@ -// go:build ignore - -package main - -import ( - "context" - "log" - "os" - - atlas "ariga.io/atlas/sql/migrate" - "entgo.io/ent/dialect" - "entgo.io/ent/dialect/sql/schema" - "github.com/cufee/aftermath/internal/database/ent/db/migrate" -) - -func main() { - ctx := context.Background() - // Create a local migration directory able to understand Atlas migration file format for replay. - dir, err := atlas.NewLocalDir("internal/database/ent/migrate/migrations") - if err != nil { - log.Fatalf("failed creating atlas migration directory: %v", err) - } - // Migrate diff options. - opts := []schema.MigrateOption{ - schema.WithForeignKeys(false), - schema.WithDir(dir), // provide migration directory - schema.WithMigrationMode(schema.ModeReplay), // provide migration mode - schema.WithDialect(dialect.SQLite), // Ent dialect to use - schema.WithFormatter(atlas.DefaultFormatter), - } - if len(os.Args) != 2 { - log.Fatalln("migration name is required. Use: 'go run -mod=mod ent/migrate/main.go '") - } - // Generate migrations using Atlas support for MySQL (note the Ent dialect option passed above). - err = migrate.NamedDiff(ctx, os.Getenv("DATABASE_URL"), os.Args[1], opts...) - if err != nil { - log.Fatalf("failed generating migration file: %v", err) - } -} diff --git a/internal/database/ent/migrate/migrations/20240622203812_init.sql b/internal/database/ent/migrate/migrations/20240622203812_init.sql deleted file mode 100644 index 55d46318..00000000 --- a/internal/database/ent/migrate/migrations/20240622203812_init.sql +++ /dev/null @@ -1,102 +0,0 @@ --- Create "accounts" table -CREATE TABLE `accounts` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `last_battle_time` integer NOT NULL, `account_created_at` integer NOT NULL, `realm` text NOT NULL, `nickname` text NOT NULL, `private` bool NOT NULL DEFAULT (false), `clan_id` text NULL, PRIMARY KEY (`id`), CONSTRAINT `accounts_clans_accounts` FOREIGN KEY (`clan_id`) REFERENCES `clans` (`id`) ON DELETE SET NULL); --- Create index "account_id_last_battle_time" to table: "accounts" -CREATE INDEX `account_id_last_battle_time` ON `accounts` (`id`, `last_battle_time`); --- Create index "account_realm" to table: "accounts" -CREATE INDEX `account_realm` ON `accounts` (`realm`); --- Create index "account_realm_last_battle_time" to table: "accounts" -CREATE INDEX `account_realm_last_battle_time` ON `accounts` (`realm`, `last_battle_time`); --- Create index "account_clan_id" to table: "accounts" -CREATE INDEX `account_clan_id` ON `accounts` (`clan_id`); --- Create "account_snapshots" table -CREATE TABLE `account_snapshots` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `last_battle_time` integer NOT NULL, `reference_id` text NOT NULL, `rating_battles` integer NOT NULL, `rating_frame` json NOT NULL, `regular_battles` integer NOT NULL, `regular_frame` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `account_snapshots_accounts_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION); --- Create index "accountsnapshot_created_at" to table: "account_snapshots" -CREATE INDEX `accountsnapshot_created_at` ON `account_snapshots` (`created_at`); --- Create index "accountsnapshot_type_account_id_created_at" to table: "account_snapshots" -CREATE INDEX `accountsnapshot_type_account_id_created_at` ON `account_snapshots` (`type`, `account_id`, `created_at`); --- Create index "accountsnapshot_type_account_id_reference_id" to table: "account_snapshots" -CREATE INDEX `accountsnapshot_type_account_id_reference_id` ON `account_snapshots` (`type`, `account_id`, `reference_id`); --- Create index "accountsnapshot_type_account_id_reference_id_created_at" to table: "account_snapshots" -CREATE INDEX `accountsnapshot_type_account_id_reference_id_created_at` ON `account_snapshots` (`type`, `account_id`, `reference_id`, `created_at`); --- Create "achievements_snapshots" table -CREATE TABLE `achievements_snapshots` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `battles` integer NOT NULL, `last_battle_time` integer NOT NULL, `data` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `achievements_snapshots_accounts_achievement_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION); --- Create index "achievementssnapshot_created_at" to table: "achievements_snapshots" -CREATE INDEX `achievementssnapshot_created_at` ON `achievements_snapshots` (`created_at`); --- Create index "achievementssnapshot_account_id_reference_id" to table: "achievements_snapshots" -CREATE INDEX `achievementssnapshot_account_id_reference_id` ON `achievements_snapshots` (`account_id`, `reference_id`); --- Create index "achievementssnapshot_account_id_reference_id_created_at" to table: "achievements_snapshots" -CREATE INDEX `achievementssnapshot_account_id_reference_id_created_at` ON `achievements_snapshots` (`account_id`, `reference_id`, `created_at`); --- Create "app_configurations" table -CREATE TABLE `app_configurations` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `key` text NOT NULL, `value` json NOT NULL, `metadata` json NULL, PRIMARY KEY (`id`)); --- Create index "app_configurations_key_key" to table: "app_configurations" -CREATE UNIQUE INDEX `app_configurations_key_key` ON `app_configurations` (`key`); --- Create index "appconfiguration_key" to table: "app_configurations" -CREATE INDEX `appconfiguration_key` ON `app_configurations` (`key`); --- Create "application_commands" table -CREATE TABLE `application_commands` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `name` text NOT NULL, `version` text NOT NULL, `options_hash` text NOT NULL, PRIMARY KEY (`id`)); --- Create index "application_commands_name_key" to table: "application_commands" -CREATE UNIQUE INDEX `application_commands_name_key` ON `application_commands` (`name`); --- Create index "applicationcommand_options_hash" to table: "application_commands" -CREATE INDEX `applicationcommand_options_hash` ON `application_commands` (`options_hash`); --- Create "clans" table -CREATE TABLE `clans` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `tag` text NOT NULL, `name` text NOT NULL, `emblem_id` text NULL DEFAULT (''), `members` json NOT NULL, PRIMARY KEY (`id`)); --- Create index "clan_tag" to table: "clans" -CREATE INDEX `clan_tag` ON `clans` (`tag`); --- Create index "clan_name" to table: "clans" -CREATE INDEX `clan_name` ON `clans` (`name`); --- Create "cron_tasks" table -CREATE TABLE `cron_tasks` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `targets` json NOT NULL, `status` text NOT NULL, `scheduled_after` integer NOT NULL, `last_run` integer NOT NULL, `logs` json NOT NULL, `data` json NOT NULL, PRIMARY KEY (`id`)); --- Create index "crontask_reference_id" to table: "cron_tasks" -CREATE INDEX `crontask_reference_id` ON `cron_tasks` (`reference_id`); --- Create index "crontask_status_last_run" to table: "cron_tasks" -CREATE INDEX `crontask_status_last_run` ON `cron_tasks` (`status`, `last_run`); --- Create index "crontask_status_created_at" to table: "cron_tasks" -CREATE INDEX `crontask_status_created_at` ON `cron_tasks` (`status`, `created_at`); --- Create index "crontask_status_scheduled_after" to table: "cron_tasks" -CREATE INDEX `crontask_status_scheduled_after` ON `cron_tasks` (`status`, `scheduled_after`); --- Create "users" table -CREATE TABLE `users` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `permissions` text NOT NULL DEFAULT (''), `feature_flags` json NULL, PRIMARY KEY (`id`)); --- Create "user_connections" table -CREATE TABLE `user_connections` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `permissions` text NULL DEFAULT (''), `metadata` json NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_connections_users_connections` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); --- Create index "userconnection_user_id" to table: "user_connections" -CREATE INDEX `userconnection_user_id` ON `user_connections` (`user_id`); --- Create index "userconnection_type_user_id" to table: "user_connections" -CREATE INDEX `userconnection_type_user_id` ON `user_connections` (`type`, `user_id`); --- Create index "userconnection_reference_id" to table: "user_connections" -CREATE INDEX `userconnection_reference_id` ON `user_connections` (`reference_id`); --- Create index "userconnection_type_reference_id" to table: "user_connections" -CREATE INDEX `userconnection_type_reference_id` ON `user_connections` (`type`, `reference_id`); --- Create "user_contents" table -CREATE TABLE `user_contents` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `value` json NOT NULL, `metadata` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_contents_users_content` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); --- Create index "usercontent_user_id" to table: "user_contents" -CREATE INDEX `usercontent_user_id` ON `user_contents` (`user_id`); --- Create index "usercontent_type_user_id" to table: "user_contents" -CREATE INDEX `usercontent_type_user_id` ON `user_contents` (`type`, `user_id`); --- Create index "usercontent_reference_id" to table: "user_contents" -CREATE INDEX `usercontent_reference_id` ON `user_contents` (`reference_id`); --- Create index "usercontent_type_reference_id" to table: "user_contents" -CREATE INDEX `usercontent_type_reference_id` ON `user_contents` (`type`, `reference_id`); --- Create "user_subscriptions" table -CREATE TABLE `user_subscriptions` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `expires_at` integer NOT NULL, `permissions` text NOT NULL, `reference_id` text NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_subscriptions_users_subscriptions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); --- Create index "usersubscription_user_id" to table: "user_subscriptions" -CREATE INDEX `usersubscription_user_id` ON `user_subscriptions` (`user_id`); --- Create index "usersubscription_type_user_id" to table: "user_subscriptions" -CREATE INDEX `usersubscription_type_user_id` ON `user_subscriptions` (`type`, `user_id`); --- Create index "usersubscription_expires_at" to table: "user_subscriptions" -CREATE INDEX `usersubscription_expires_at` ON `user_subscriptions` (`expires_at`); --- Create index "usersubscription_expires_at_user_id" to table: "user_subscriptions" -CREATE INDEX `usersubscription_expires_at_user_id` ON `user_subscriptions` (`expires_at`, `user_id`); --- Create "vehicles" table -CREATE TABLE `vehicles` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `tier` integer NOT NULL, `localized_names` json NOT NULL, PRIMARY KEY (`id`)); --- Create "vehicle_averages" table -CREATE TABLE `vehicle_averages` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `data` json NOT NULL, PRIMARY KEY (`id`)); --- Create "vehicle_snapshots" table -CREATE TABLE `vehicle_snapshots` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `vehicle_id` text NOT NULL, `reference_id` text NOT NULL, `battles` integer NOT NULL, `last_battle_time` integer NOT NULL, `frame` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `vehicle_snapshots_accounts_vehicle_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION); --- Create index "vehiclesnapshot_created_at" to table: "vehicle_snapshots" -CREATE INDEX `vehiclesnapshot_created_at` ON `vehicle_snapshots` (`created_at`); --- Create index "vehiclesnapshot_vehicle_id_created_at" to table: "vehicle_snapshots" -CREATE INDEX `vehiclesnapshot_vehicle_id_created_at` ON `vehicle_snapshots` (`vehicle_id`, `created_at`); --- Create index "vehiclesnapshot_account_id_created_at" to table: "vehicle_snapshots" -CREATE INDEX `vehiclesnapshot_account_id_created_at` ON `vehicle_snapshots` (`account_id`, `created_at`); --- Create index "vehiclesnapshot_account_id_type_created_at" to table: "vehicle_snapshots" -CREATE INDEX `vehiclesnapshot_account_id_type_created_at` ON `vehicle_snapshots` (`account_id`, `type`, `created_at`); diff --git a/internal/database/ent/migrate/migrations/20240623143618_added_indexes_on_id.sql b/internal/database/ent/migrate/migrations/20240623143618_added_indexes_on_id.sql deleted file mode 100644 index 7314180c..00000000 --- a/internal/database/ent/migrate/migrations/20240623143618_added_indexes_on_id.sql +++ /dev/null @@ -1,30 +0,0 @@ --- Create index "account_id" to table: "accounts" -CREATE INDEX `account_id` ON `accounts` (`id`); --- Create index "accountsnapshot_id" to table: "account_snapshots" -CREATE INDEX `accountsnapshot_id` ON `account_snapshots` (`id`); --- Create index "achievementssnapshot_id" to table: "achievements_snapshots" -CREATE INDEX `achievementssnapshot_id` ON `achievements_snapshots` (`id`); --- Create index "appconfiguration_id" to table: "app_configurations" -CREATE INDEX `appconfiguration_id` ON `app_configurations` (`id`); --- Create index "applicationcommand_id" to table: "application_commands" -CREATE INDEX `applicationcommand_id` ON `application_commands` (`id`); --- Create index "clan_id" to table: "clans" -CREATE INDEX `clan_id` ON `clans` (`id`); --- Create index "crontask_id" to table: "cron_tasks" -CREATE INDEX `crontask_id` ON `cron_tasks` (`id`); --- Create index "user_id" to table: "users" -CREATE INDEX `user_id` ON `users` (`id`); --- Create index "userconnection_id" to table: "user_connections" -CREATE INDEX `userconnection_id` ON `user_connections` (`id`); --- Create index "userconnection_reference_id_user_id_type" to table: "user_connections" -CREATE UNIQUE INDEX `userconnection_reference_id_user_id_type` ON `user_connections` (`reference_id`, `user_id`, `type`); --- Create index "usercontent_id" to table: "user_contents" -CREATE INDEX `usercontent_id` ON `user_contents` (`id`); --- Create index "usersubscription_id" to table: "user_subscriptions" -CREATE INDEX `usersubscription_id` ON `user_subscriptions` (`id`); --- Create index "vehicle_id" to table: "vehicles" -CREATE INDEX `vehicle_id` ON `vehicles` (`id`); --- Create index "vehicleaverage_id" to table: "vehicle_averages" -CREATE INDEX `vehicleaverage_id` ON `vehicle_averages` (`id`); --- Create index "vehiclesnapshot_id" to table: "vehicle_snapshots" -CREATE INDEX `vehiclesnapshot_id` ON `vehicle_snapshots` (`id`); diff --git a/internal/database/ent/migrate/migrations/20240626213554.sql b/internal/database/ent/migrate/migrations/20240626213554.sql deleted file mode 100644 index 378ea14d..00000000 --- a/internal/database/ent/migrate/migrations/20240626213554.sql +++ /dev/null @@ -1,12 +0,0 @@ --- Create "discord_interactions" table -CREATE TABLE `discord_interactions` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `command` text NOT NULL, `reference_id` text NOT NULL, `type` text NOT NULL, `locale` text NOT NULL, `options` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `discord_interactions_users_discord_interactions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); --- Create index "discordinteraction_id" to table: "discord_interactions" -CREATE INDEX `discordinteraction_id` ON `discord_interactions` (`id`); --- Create index "discordinteraction_command" to table: "discord_interactions" -CREATE INDEX `discordinteraction_command` ON `discord_interactions` (`command`); --- Create index "discordinteraction_user_id" to table: "discord_interactions" -CREATE INDEX `discordinteraction_user_id` ON `discord_interactions` (`user_id`); --- Create index "discordinteraction_user_id_type" to table: "discord_interactions" -CREATE INDEX `discordinteraction_user_id_type` ON `discord_interactions` (`user_id`, `type`); --- Create index "discordinteraction_reference_id" to table: "discord_interactions" -CREATE INDEX `discordinteraction_reference_id` ON `discord_interactions` (`reference_id`); diff --git a/internal/database/ent/migrate/migrations/20240626225519.sql b/internal/database/ent/migrate/migrations/20240626225519.sql deleted file mode 100644 index f59ccc1c..00000000 --- a/internal/database/ent/migrate/migrations/20240626225519.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Disable the enforcement of foreign-keys constraints -PRAGMA foreign_keys = off; --- Create "new_user_contents" table -CREATE TABLE `new_user_contents` (`id` text NOT NULL, `created_at` integer NOT NULL, `updated_at` integer NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `value` text NOT NULL, `metadata` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_contents_users_content` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); --- Copy rows from old table "user_contents" to new temporary table "new_user_contents" -INSERT INTO `new_user_contents` (`id`, `created_at`, `updated_at`, `type`, `reference_id`, `value`, `metadata`, `user_id`) SELECT `id`, `created_at`, `updated_at`, `type`, `reference_id`, `value`, `metadata`, `user_id` FROM `user_contents`; --- Drop "user_contents" table after copying rows -DROP TABLE `user_contents`; --- Rename temporary table "new_user_contents" to "user_contents" -ALTER TABLE `new_user_contents` RENAME TO `user_contents`; --- Create index "usercontent_id" to table: "user_contents" -CREATE INDEX `usercontent_id` ON `user_contents` (`id`); --- Create index "usercontent_user_id" to table: "user_contents" -CREATE INDEX `usercontent_user_id` ON `user_contents` (`user_id`); --- Create index "usercontent_type_user_id" to table: "user_contents" -CREATE INDEX `usercontent_type_user_id` ON `user_contents` (`type`, `user_id`); --- Create index "usercontent_reference_id" to table: "user_contents" -CREATE INDEX `usercontent_reference_id` ON `user_contents` (`reference_id`); --- Create index "usercontent_type_reference_id" to table: "user_contents" -CREATE INDEX `usercontent_type_reference_id` ON `user_contents` (`type`, `reference_id`); --- Enable back the enforcement of foreign-keys constraints -PRAGMA foreign_keys = on; diff --git a/internal/database/ent/migrate/migrations/20240626233300.sql b/internal/database/ent/migrate/migrations/20240626233300.sql deleted file mode 100644 index 235896cb..00000000 --- a/internal/database/ent/migrate/migrations/20240626233300.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Drop index "application_commands_name_key" from table: "application_commands" -DROP INDEX `application_commands_name_key`; diff --git a/internal/database/ent/migrate/migrations/atlas.sum b/internal/database/ent/migrate/migrations/atlas.sum deleted file mode 100644 index 54f7f426..00000000 --- a/internal/database/ent/migrate/migrations/atlas.sum +++ /dev/null @@ -1,6 +0,0 @@ -h1:CT0057YWfzRHEDECr8CPEZlaBJOat31j3qFdme4zb/8= -20240622203812_init.sql h1:Rt6NGvXbPBEpjg1pUFqn97FV8X3X2d1x7J2zOAAxolc= -20240623143618_added_indexes_on_id.sql h1:o57d1Hc6+wGscJp3VIWnCNrmQkBSeSKTEVKRSWmsc6k= -20240626213554.sql h1:sKGMP6giStLCW5lUK8VyaLxmiPRQTxT+y6xRkax1juM= -20240626225519.sql h1:/ARpBIJ80fqBpTK0WH4d5+3Z2kOh86SWlOGSu1PTOFc= -20240626233300.sql h1:Klr1WjPuZR6NtWPeWwIUHOokVyEEWXbo80DVxNaCzjo= diff --git a/internal/database/ent/migrations/20240627171532.sql b/internal/database/ent/migrations/20240627171532.sql new file mode 100644 index 00000000..16b6cc27 --- /dev/null +++ b/internal/database/ent/migrations/20240627171532.sql @@ -0,0 +1,142 @@ +-- Create "accounts" table +CREATE TABLE `accounts` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `last_battle_time` datetime NOT NULL, `account_created_at` datetime NOT NULL, `realm` text NOT NULL, `nickname` text NOT NULL, `private` bool NOT NULL DEFAULT (false), `clan_id` text NULL, PRIMARY KEY (`id`), CONSTRAINT `accounts_clans_accounts` FOREIGN KEY (`clan_id`) REFERENCES `clans` (`id`) ON DELETE SET NULL); +-- Create index "account_id" to table: "accounts" +CREATE INDEX `account_id` ON `accounts` (`id`); +-- Create index "account_id_last_battle_time" to table: "accounts" +CREATE INDEX `account_id_last_battle_time` ON `accounts` (`id`, `last_battle_time`); +-- Create index "account_realm" to table: "accounts" +CREATE INDEX `account_realm` ON `accounts` (`realm`); +-- Create index "account_realm_last_battle_time" to table: "accounts" +CREATE INDEX `account_realm_last_battle_time` ON `accounts` (`realm`, `last_battle_time`); +-- Create index "account_clan_id" to table: "accounts" +CREATE INDEX `account_clan_id` ON `accounts` (`clan_id`); +-- Create "account_snapshots" table +CREATE TABLE `account_snapshots` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `last_battle_time` datetime NOT NULL, `reference_id` text NOT NULL, `rating_battles` integer NOT NULL, `rating_frame` json NOT NULL, `regular_battles` integer NOT NULL, `regular_frame` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `account_snapshots_accounts_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION); +-- Create index "accountsnapshot_id" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_id` ON `account_snapshots` (`id`); +-- Create index "accountsnapshot_created_at" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_created_at` ON `account_snapshots` (`created_at`); +-- Create index "accountsnapshot_type_account_id_created_at" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_type_account_id_created_at` ON `account_snapshots` (`type`, `account_id`, `created_at`); +-- Create index "accountsnapshot_type_account_id_reference_id" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_type_account_id_reference_id` ON `account_snapshots` (`type`, `account_id`, `reference_id`); +-- Create index "accountsnapshot_type_account_id_reference_id_created_at" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_type_account_id_reference_id_created_at` ON `account_snapshots` (`type`, `account_id`, `reference_id`, `created_at`); +-- Create "achievements_snapshots" table +CREATE TABLE `achievements_snapshots` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `battles` integer NOT NULL, `last_battle_time` datetime NOT NULL, `data` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `achievements_snapshots_accounts_achievement_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION); +-- Create index "achievementssnapshot_id" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_id` ON `achievements_snapshots` (`id`); +-- Create index "achievementssnapshot_created_at" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_created_at` ON `achievements_snapshots` (`created_at`); +-- Create index "achievementssnapshot_account_id_reference_id" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_account_id_reference_id` ON `achievements_snapshots` (`account_id`, `reference_id`); +-- Create index "achievementssnapshot_account_id_reference_id_created_at" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_account_id_reference_id_created_at` ON `achievements_snapshots` (`account_id`, `reference_id`, `created_at`); +-- Create "app_configurations" table +CREATE TABLE `app_configurations` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `key` text NOT NULL, `value` json NOT NULL, `metadata` json NULL, PRIMARY KEY (`id`)); +-- Create index "app_configurations_key_key" to table: "app_configurations" +CREATE UNIQUE INDEX `app_configurations_key_key` ON `app_configurations` (`key`); +-- Create index "appconfiguration_id" to table: "app_configurations" +CREATE INDEX `appconfiguration_id` ON `app_configurations` (`id`); +-- Create index "appconfiguration_key" to table: "app_configurations" +CREATE INDEX `appconfiguration_key` ON `app_configurations` (`key`); +-- Create "application_commands" table +CREATE TABLE `application_commands` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `name` text NOT NULL, `version` text NOT NULL, `options_hash` text NOT NULL, PRIMARY KEY (`id`)); +-- Create index "applicationcommand_id" to table: "application_commands" +CREATE INDEX `applicationcommand_id` ON `application_commands` (`id`); +-- Create index "applicationcommand_options_hash" to table: "application_commands" +CREATE INDEX `applicationcommand_options_hash` ON `application_commands` (`options_hash`); +-- Create "clans" table +CREATE TABLE `clans` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `tag` text NOT NULL, `name` text NOT NULL, `emblem_id` text NULL DEFAULT (''), `members` json NOT NULL, PRIMARY KEY (`id`)); +-- Create index "clan_id" to table: "clans" +CREATE INDEX `clan_id` ON `clans` (`id`); +-- Create index "clan_tag" to table: "clans" +CREATE INDEX `clan_tag` ON `clans` (`tag`); +-- Create index "clan_name" to table: "clans" +CREATE INDEX `clan_name` ON `clans` (`name`); +-- Create "cron_tasks" table +CREATE TABLE `cron_tasks` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `targets` json NOT NULL, `status` text NOT NULL, `scheduled_after` datetime NOT NULL, `last_run` datetime NOT NULL, `logs` json NOT NULL, `data` json NOT NULL, PRIMARY KEY (`id`)); +-- Create index "crontask_id" to table: "cron_tasks" +CREATE INDEX `crontask_id` ON `cron_tasks` (`id`); +-- Create index "crontask_reference_id" to table: "cron_tasks" +CREATE INDEX `crontask_reference_id` ON `cron_tasks` (`reference_id`); +-- Create index "crontask_status_last_run" to table: "cron_tasks" +CREATE INDEX `crontask_status_last_run` ON `cron_tasks` (`status`, `last_run`); +-- Create index "crontask_status_created_at" to table: "cron_tasks" +CREATE INDEX `crontask_status_created_at` ON `cron_tasks` (`status`, `created_at`); +-- Create index "crontask_status_scheduled_after" to table: "cron_tasks" +CREATE INDEX `crontask_status_scheduled_after` ON `cron_tasks` (`status`, `scheduled_after`); +-- Create "discord_interactions" table +CREATE TABLE `discord_interactions` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `command` text NOT NULL, `reference_id` text NOT NULL, `type` text NOT NULL, `locale` text NOT NULL, `options` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `discord_interactions_users_discord_interactions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Create index "discordinteraction_id" to table: "discord_interactions" +CREATE INDEX `discordinteraction_id` ON `discord_interactions` (`id`); +-- Create index "discordinteraction_command" to table: "discord_interactions" +CREATE INDEX `discordinteraction_command` ON `discord_interactions` (`command`); +-- Create index "discordinteraction_user_id" to table: "discord_interactions" +CREATE INDEX `discordinteraction_user_id` ON `discord_interactions` (`user_id`); +-- Create index "discordinteraction_user_id_type" to table: "discord_interactions" +CREATE INDEX `discordinteraction_user_id_type` ON `discord_interactions` (`user_id`, `type`); +-- Create index "discordinteraction_reference_id" to table: "discord_interactions" +CREATE INDEX `discordinteraction_reference_id` ON `discord_interactions` (`reference_id`); +-- Create "users" table +CREATE TABLE `users` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `permissions` text NOT NULL DEFAULT (''), `feature_flags` json NULL, PRIMARY KEY (`id`)); +-- Create index "user_id" to table: "users" +CREATE INDEX `user_id` ON `users` (`id`); +-- Create "user_connections" table +CREATE TABLE `user_connections` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `permissions` text NULL DEFAULT (''), `metadata` json NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_connections_users_connections` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Create index "userconnection_id" to table: "user_connections" +CREATE INDEX `userconnection_id` ON `user_connections` (`id`); +-- Create index "userconnection_user_id" to table: "user_connections" +CREATE INDEX `userconnection_user_id` ON `user_connections` (`user_id`); +-- Create index "userconnection_type_user_id" to table: "user_connections" +CREATE INDEX `userconnection_type_user_id` ON `user_connections` (`type`, `user_id`); +-- Create index "userconnection_reference_id" to table: "user_connections" +CREATE INDEX `userconnection_reference_id` ON `user_connections` (`reference_id`); +-- Create index "userconnection_type_reference_id" to table: "user_connections" +CREATE INDEX `userconnection_type_reference_id` ON `user_connections` (`type`, `reference_id`); +-- Create index "userconnection_reference_id_user_id_type" to table: "user_connections" +CREATE UNIQUE INDEX `userconnection_reference_id_user_id_type` ON `user_connections` (`reference_id`, `user_id`, `type`); +-- Create "user_contents" table +CREATE TABLE `user_contents` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `value` text NOT NULL, `metadata` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_contents_users_content` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Create index "usercontent_id" to table: "user_contents" +CREATE INDEX `usercontent_id` ON `user_contents` (`id`); +-- Create index "usercontent_user_id" to table: "user_contents" +CREATE INDEX `usercontent_user_id` ON `user_contents` (`user_id`); +-- Create index "usercontent_type_user_id" to table: "user_contents" +CREATE INDEX `usercontent_type_user_id` ON `user_contents` (`type`, `user_id`); +-- Create index "usercontent_reference_id" to table: "user_contents" +CREATE INDEX `usercontent_reference_id` ON `user_contents` (`reference_id`); +-- Create index "usercontent_type_reference_id" to table: "user_contents" +CREATE INDEX `usercontent_type_reference_id` ON `user_contents` (`type`, `reference_id`); +-- Create "user_subscriptions" table +CREATE TABLE `user_subscriptions` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `expires_at` datetime NOT NULL, `permissions` text NOT NULL, `reference_id` text NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_subscriptions_users_subscriptions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Create index "usersubscription_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_id` ON `user_subscriptions` (`id`); +-- Create index "usersubscription_user_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_user_id` ON `user_subscriptions` (`user_id`); +-- Create index "usersubscription_type_user_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_type_user_id` ON `user_subscriptions` (`type`, `user_id`); +-- Create index "usersubscription_expires_at" to table: "user_subscriptions" +CREATE INDEX `usersubscription_expires_at` ON `user_subscriptions` (`expires_at`); +-- Create index "usersubscription_expires_at_user_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_expires_at_user_id` ON `user_subscriptions` (`expires_at`, `user_id`); +-- Create "vehicles" table +CREATE TABLE `vehicles` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `tier` integer NOT NULL, `localized_names` json NOT NULL, PRIMARY KEY (`id`)); +-- Create index "vehicle_id" to table: "vehicles" +CREATE INDEX `vehicle_id` ON `vehicles` (`id`); +-- Create "vehicle_averages" table +CREATE TABLE `vehicle_averages` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `data` json NOT NULL, PRIMARY KEY (`id`)); +-- Create index "vehicleaverage_id" to table: "vehicle_averages" +CREATE INDEX `vehicleaverage_id` ON `vehicle_averages` (`id`); +-- Create "vehicle_snapshots" table +CREATE TABLE `vehicle_snapshots` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `vehicle_id` text NOT NULL, `reference_id` text NOT NULL, `battles` integer NOT NULL, `last_battle_time` datetime NOT NULL, `frame` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `vehicle_snapshots_accounts_vehicle_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION); +-- Create index "vehiclesnapshot_id" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_id` ON `vehicle_snapshots` (`id`); +-- Create index "vehiclesnapshot_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_created_at` ON `vehicle_snapshots` (`created_at`); +-- Create index "vehiclesnapshot_vehicle_id_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_vehicle_id_created_at` ON `vehicle_snapshots` (`vehicle_id`, `created_at`); +-- Create index "vehiclesnapshot_account_id_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_account_id_created_at` ON `vehicle_snapshots` (`account_id`, `created_at`); +-- Create index "vehiclesnapshot_account_id_type_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_account_id_type_created_at` ON `vehicle_snapshots` (`account_id`, `type`, `created_at`); diff --git a/internal/database/ent/migrations/atlas.sum b/internal/database/ent/migrations/atlas.sum new file mode 100644 index 00000000..078d2ca9 --- /dev/null +++ b/internal/database/ent/migrations/atlas.sum @@ -0,0 +1,2 @@ +h1:xMoifuEeyeG1Wp9DhNcR2c+9QM7POMSSmax2AhvV6Z0= +20240627171532.sql h1:480JhjRCpyyix192z/L92yEI7LSsgJhuiK0ljat55TI= diff --git a/internal/database/ent/schema/account.go b/internal/database/ent/schema/account.go index ea686293..b9518e57 100644 --- a/internal/database/ent/schema/account.go +++ b/internal/database/ent/schema/account.go @@ -17,14 +17,14 @@ func (Account) Fields() []ent.Field { return []ent.Field{field.String("id"). Unique(). Immutable(), - field.Int64("created_at"). + field.Time("created_at"). Immutable(). - DefaultFunc(timeNow), - field.Int64("updated_at"). - DefaultFunc(timeNow). + Default(timeNow), + field.Time("updated_at"). + Default(timeNow). UpdateDefault(timeNow), - field.Int64("last_battle_time"), - field.Int64("account_created_at"), + field.Time("last_battle_time"), + field.Time("account_created_at"), // field.String("realm"). MinLen(2). diff --git a/internal/database/ent/schema/account_snapshot.go b/internal/database/ent/schema/account_snapshot.go index 67473711..b7bff591 100644 --- a/internal/database/ent/schema/account_snapshot.go +++ b/internal/database/ent/schema/account_snapshot.go @@ -19,7 +19,7 @@ func (AccountSnapshot) Fields() []ent.Field { return append(defaultFields, field.Enum("type"). GoType(models.SnapshotType("")), - field.Int64("last_battle_time"), + field.Time("last_battle_time"), // field.String("account_id").NotEmpty().Immutable(), field.String("reference_id").NotEmpty(), diff --git a/internal/database/ent/schema/achievements_snapshot.go b/internal/database/ent/schema/achievements_snapshot.go index 70d56852..b5f24775 100644 --- a/internal/database/ent/schema/achievements_snapshot.go +++ b/internal/database/ent/schema/achievements_snapshot.go @@ -23,7 +23,7 @@ func (AchievementsSnapshot) Fields() []ent.Field { field.String("reference_id").NotEmpty(), // field.Int("battles"), - field.Int64("last_battle_time"), + field.Time("last_battle_time"), field.JSON("data", types.AchievementsFrame{}), ) } diff --git a/internal/database/ent/schema/clan.go b/internal/database/ent/schema/clan.go index 0aa605e3..f5afceba 100644 --- a/internal/database/ent/schema/clan.go +++ b/internal/database/ent/schema/clan.go @@ -18,11 +18,11 @@ func (Clan) Fields() []ent.Field { field.String("id"). Unique(). Immutable(), - field.Int64("created_at"). + field.Time("created_at"). Immutable(). - DefaultFunc(timeNow), - field.Int64("updated_at"). - DefaultFunc(timeNow). + Default(timeNow), + field.Time("updated_at"). + Default(timeNow). UpdateDefault(timeNow), // field.String("tag").NotEmpty(), diff --git a/internal/database/ent/schema/cron_task.go b/internal/database/ent/schema/cron_task.go index 34a6640e..e20544a2 100644 --- a/internal/database/ent/schema/cron_task.go +++ b/internal/database/ent/schema/cron_task.go @@ -22,8 +22,8 @@ func (CronTask) Fields() []ent.Field { // field.Enum("status"). GoType(models.TaskStatus("")), - field.Int64("scheduled_after"), - field.Int64("last_run"), + field.Time("scheduled_after"), + field.Time("last_run"), // field.JSON("logs", []models.TaskLog{}), field.JSON("data", map[string]any{}), diff --git a/internal/database/ent/schema/defaults.go b/internal/database/ent/schema/defaults.go index 97d60c1d..04bfaf9e 100644 --- a/internal/database/ent/schema/defaults.go +++ b/internal/database/ent/schema/defaults.go @@ -13,12 +13,12 @@ var defaultFields = []ent.Field{ Unique(). Immutable(). DefaultFunc(cuid.New), - field.Int64("created_at"). + field.Time("created_at"). Immutable(). - DefaultFunc(timeNow), - field.Int64("updated_at"). - DefaultFunc(timeNow). + Default(timeNow), + field.Time("updated_at"). + Default(timeNow). UpdateDefault(timeNow), } -func timeNow() int64 { return time.Now().Unix() } +func timeNow() time.Time { return time.Now() } diff --git a/internal/database/ent/schema/user.go b/internal/database/ent/schema/user.go index 6ae361e9..aec9bc0f 100644 --- a/internal/database/ent/schema/user.go +++ b/internal/database/ent/schema/user.go @@ -18,11 +18,11 @@ func (User) Fields() []ent.Field { field.String("id"). Unique(). Immutable(), - field.Int64("created_at"). + field.Time("created_at"). Immutable(). - DefaultFunc(timeNow), - field.Int64("updated_at"). - DefaultFunc(timeNow). + Default(timeNow), + field.Time("updated_at"). + Default(timeNow). UpdateDefault(timeNow), // field.String("permissions").Default(""), diff --git a/internal/database/ent/schema/user_subscription.go b/internal/database/ent/schema/user_subscription.go index ad0b5439..5b87f395 100644 --- a/internal/database/ent/schema/user_subscription.go +++ b/internal/database/ent/schema/user_subscription.go @@ -18,7 +18,7 @@ func (UserSubscription) Fields() []ent.Field { return append(defaultFields, field.Enum("type"). GoType(models.SubscriptionType("")), - field.Int64("expires_at"), + field.Time("expires_at"), // field.String("user_id").NotEmpty().Immutable(), field.String("permissions").NotEmpty(), diff --git a/internal/database/ent/schema/vehicle.go b/internal/database/ent/schema/vehicle.go index 167259db..0ee5a3d3 100644 --- a/internal/database/ent/schema/vehicle.go +++ b/internal/database/ent/schema/vehicle.go @@ -17,11 +17,11 @@ func (Vehicle) Fields() []ent.Field { field.String("id"). Unique(). Immutable(), - field.Int64("created_at"). + field.Time("created_at"). Immutable(). - DefaultFunc(timeNow), - field.Int64("updated_at"). - DefaultFunc(timeNow). + Default(timeNow), + field.Time("updated_at"). + Default(timeNow). UpdateDefault(timeNow), // field.Int("tier"). diff --git a/internal/database/ent/schema/vehicle_average.go b/internal/database/ent/schema/vehicle_average.go index 57d1a05a..fde82571 100644 --- a/internal/database/ent/schema/vehicle_average.go +++ b/internal/database/ent/schema/vehicle_average.go @@ -18,11 +18,11 @@ func (VehicleAverage) Fields() []ent.Field { field.String("id"). Unique(). Immutable(), - field.Int64("created_at"). + field.Time("created_at"). Immutable(). - DefaultFunc(timeNow), - field.Int64("updated_at"). - DefaultFunc(timeNow). + Default(timeNow), + field.Time("updated_at"). + Default(timeNow). UpdateDefault(timeNow), // field.JSON("data", frame.StatsFrame{}), diff --git a/internal/database/ent/schema/vehicle_snapshot.go b/internal/database/ent/schema/vehicle_snapshot.go index 13fc124f..b2f69e9d 100644 --- a/internal/database/ent/schema/vehicle_snapshot.go +++ b/internal/database/ent/schema/vehicle_snapshot.go @@ -25,7 +25,7 @@ func (VehicleSnapshot) Fields() []ent.Field { field.String("reference_id").NotEmpty(), // field.Int("battles"), - field.Int64("last_battle_time"), + field.Time("last_battle_time"), field.JSON("frame", frame.StatsFrame{}), ) } diff --git a/internal/database/ent/templates/expose.tmpl b/internal/database/ent/templates/expose.tmpl new file mode 100644 index 00000000..cb503a91 --- /dev/null +++ b/internal/database/ent/templates/expose.tmpl @@ -0,0 +1,24 @@ +{{ define "expose" }} + +{{/* Add the base header for the generated file */}} +{{ $pkg := base $.Config.Package }} +{{ template "header" $ }} + +{{/* Loop over all nodes and implement the "GoStringer" interface */}} +{{ range $n := $.Nodes }} + {{ $receiver := $n.Receiver }} + func ({{ $receiver }} *{{ $n.Name }}) AssignValues(columns []string, values []any) error { + if {{ $receiver }} == nil { + return fmt.Errorf("{{ $n.Name }}(nil)") + } + return {{ $receiver }}.assignValues(columns, values) + } + func ({{ $receiver }} *{{ $n.Name }}) ScanValues(columns []string) ([]any, error) { + if {{ $receiver }} == nil { + return nil, fmt.Errorf("{{ $n.Name }}(nil)") + } + return {{ $receiver }}.scanValues(columns) + } +{{ end }} + +{{ end }} \ No newline at end of file diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 34613a5c..a0809469 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -41,8 +41,8 @@ func toVehicleSnapshot(record *db.VehicleSnapshot) models.VehicleSnapshot { return models.VehicleSnapshot{ ID: record.ID, Type: record.Type, - CreatedAt: time.Unix(record.CreatedAt, 0), - LastBattleTime: time.Unix(record.LastBattleTime, 0), + CreatedAt: record.CreatedAt, + LastBattleTime: record.LastBattleTime, ReferenceID: record.ReferenceID, AccountID: record.AccountID, VehicleID: record.VehicleID, @@ -56,15 +56,16 @@ func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID st } var errors = make(map[string]error) - return errors, c.withTx(ctx, func(tx *db.Tx) error { + err := c.withTx(ctx, func(tx *db.Tx) error { for _, data := range snapshots { err := c.db.VehicleSnapshot.Create(). SetType(data.Type). SetFrame(data.Stats). SetVehicleID(data.VehicleID). SetReferenceID(data.ReferenceID). + SetCreatedAt(data.CreatedAt). SetBattles(int(data.Stats.Battles.Float())). - SetLastBattleTime(data.LastBattleTime.Unix()). + SetLastBattleTime(data.LastBattleTime). SetAccount(c.db.Account.GetX(ctx, accountID)). Exec(ctx) if err != nil { @@ -73,6 +74,13 @@ func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID st } return nil }) + if err != nil { + return nil, err + } + if len(errors) > 0 { + return errors, nil + } + return nil, nil } func (c *client) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) { @@ -81,31 +89,65 @@ func (c *client) GetVehicleSnapshots(ctx context.Context, accountID, referenceID apply(&query) } - var where []predicate.VehicleSnapshot - where = append(where, vehiclesnapshot.AccountID(accountID)) - where = append(where, vehiclesnapshot.ReferenceID(referenceID)) - where = append(where, vehiclesnapshot.TypeEQ(kind)) + // this query is impossible to build using the db.VehicleSnapshots methods, so we do it manually + var innerWhere []*sql.Predicate + innerWhere = append(innerWhere, + sql.EQ(vehiclesnapshot.FieldType, kind), + sql.EQ(vehiclesnapshot.FieldAccountID, accountID), + sql.EQ(vehiclesnapshot.FieldReferenceID, referenceID), + ) + innerOrder := sql.Desc(vehiclesnapshot.FieldCreatedAt) - order := vehiclesnapshot.ByCreatedAt(sql.OrderDesc()) if query.createdAfter != nil { - order = vehiclesnapshot.ByCreatedAt(sql.OrderAsc()) - where = append(where, vehiclesnapshot.CreatedAtGT(query.createdAfter.Unix())) + innerWhere = append(innerWhere, sql.GT(vehiclesnapshot.FieldCreatedAt, *query.createdAfter)) + innerOrder = sql.Asc(vehiclesnapshot.FieldCreatedAt) } if query.createdBefore != nil { - where = append(where, vehiclesnapshot.CreatedAtLT(query.createdBefore.Unix())) + innerWhere = append(innerWhere, sql.LT(vehiclesnapshot.FieldCreatedAt, *query.createdBefore)) + innerOrder = sql.Desc(vehiclesnapshot.FieldCreatedAt) } - if query.vehicleIDs != nil { - where = append(where, vehiclesnapshot.VehicleIDIn(query.vehicleIDs...)) + if len(query.vehicleIDs) > 0 { + var ids []any + for _, id := range query.vehicleIDs { + ids = append(ids, id) + } + innerWhere = append(innerWhere, sql.In(vehiclesnapshot.FieldVehicleID, ids...)) } - var records db.VehicleSnapshots - err := c.db.VehicleSnapshot.Query().Where(where...).Order(order).GroupBy(vehiclesnapshot.FieldVehicleID).Aggregate(func(s *sql.Selector) string { return s.Select("*").String() }).Scan(ctx, &records) + innerQuery := sql.Select(vehiclesnapshot.Columns...).From(sql.Table(vehiclesnapshot.Table)) + innerQuery = innerQuery.Where(sql.And(innerWhere...)) + innerQuery = innerQuery.OrderBy(innerOrder) + + innerQueryString, innerQueryArgs := innerQuery.Query() + + // wrap the inner query in a GROUP BY + wrapper := &sql.Builder{} + wrapped := wrapper.Wrap(func(b *sql.Builder) { b.WriteString(innerQueryString) }) + queryString, _ := sql.Select("*").FromExpr(wrapped).GroupBy(vehiclesnapshot.FieldVehicleID).Query() + + rows, err := c.db.VehicleSnapshot.QueryContext(ctx, queryString, innerQueryArgs...) if err != nil { return nil, err } + defer rows.Close() + var snapshots []models.VehicleSnapshot - for _, r := range records { - snapshots = append(snapshots, toVehicleSnapshot(r)) + for rows.Next() { + var record db.VehicleSnapshot + + values, err := record.ScanValues(vehiclesnapshot.Columns) + if err != nil { + return nil, err + } + if err := rows.Scan(values...); err != nil { + return nil, err + } + + err = record.AssignValues(vehiclesnapshot.Columns, values) + if err != nil { + return nil, err + } + snapshots = append(snapshots, toVehicleSnapshot(&record)) } return snapshots, nil @@ -119,8 +161,8 @@ func toAccountSnapshot(record *db.AccountSnapshot) models.AccountSnapshot { ReferenceID: record.ReferenceID, RatingBattles: record.RatingFrame, RegularBattles: record.RegularFrame, - CreatedAt: time.Unix(record.CreatedAt, 0), - LastBattleTime: time.Unix(record.LastBattleTime, 0), + CreatedAt: record.CreatedAt, + LastBattleTime: record.LastBattleTime, } } @@ -130,12 +172,12 @@ func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...models } var errors = make(map[string]error) - return errors, c.withTx(ctx, func(tx *db.Tx) error { + err := c.withTx(ctx, func(tx *db.Tx) error { for _, s := range snapshots { err := c.db.AccountSnapshot.Create(). SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here - SetCreatedAt(s.CreatedAt.Unix()). - SetLastBattleTime(s.LastBattleTime.Unix()). + SetCreatedAt(s.CreatedAt). + SetLastBattleTime(s.LastBattleTime). SetRatingBattles(int(s.RatingBattles.Battles.Float())). SetRatingFrame(s.RatingBattles). SetReferenceID(s.ReferenceID). @@ -148,6 +190,13 @@ func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...models } return nil }) + if err != nil { + return nil, err + } + if len(errors) > 0 { + return errors, nil + } + return nil, nil } func (c *client) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) { @@ -175,10 +224,10 @@ func (c *client) GetAccountSnapshot(ctx context.Context, accountID, referenceID where = append(where, accountsnapshot.AccountID(accountID), accountsnapshot.ReferenceID(referenceID), accountsnapshot.TypeEQ(kind)) if query.createdAfter != nil { order = accountsnapshot.ByCreatedAt(sql.OrderAsc()) - where = append(where, accountsnapshot.CreatedAtGT(query.createdAfter.Unix())) + where = append(where, accountsnapshot.CreatedAtGT(*query.createdAfter)) } if query.createdBefore != nil { - where = append(where, accountsnapshot.CreatedAtLT(query.createdBefore.Unix())) + where = append(where, accountsnapshot.CreatedAtLT(*query.createdBefore)) } record, err := c.db.AccountSnapshot.Query().Where(where...).Order(order).First(ctx) @@ -198,10 +247,10 @@ func (c *client) GetManyAccountSnapshots(ctx context.Context, accountIDs []strin var where []predicate.AccountSnapshot where = append(where, accountsnapshot.AccountIDIn(accountIDs...), accountsnapshot.TypeEQ(kind)) if query.createdAfter != nil { - where = append(where, accountsnapshot.CreatedAtGT(query.createdAfter.Unix())) + where = append(where, accountsnapshot.CreatedAtGT(*query.createdAfter)) } if query.createdBefore != nil { - where = append(where, accountsnapshot.CreatedAtLT(query.createdAfter.Unix())) + where = append(where, accountsnapshot.CreatedAtLT(*query.createdAfter)) } records, err := c.db.AccountSnapshot.Query().Where(where...).All(ctx) diff --git a/internal/database/snapshots_test.go b/internal/database/snapshots_test.go index 06653cf5..0726554d 100644 --- a/internal/database/snapshots_test.go +++ b/internal/database/snapshots_test.go @@ -1,113 +1,129 @@ package database -// /* -// DATABASE_URL needs to be set and migrations need to be applied -// */ -// func TestGetVehicleSnapshots(t *testing.T) { -// client, err := NewClient() -// assert.NoError(t, err, "new client should not error") -// defer client.prisma.Disconnect() - -// ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) -// defer cancel() - -// client.prisma.VehicleSnapshot.FindMany().Delete().Exec(ctx) -// defer client.prisma.VehicleSnapshot.FindMany().Delete().Exec(ctx) - -// createdAtVehicle1 := time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC) -// createdAtVehicle2 := time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC) -// createdAtVehicle3 := time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC) - -// createdAtVehicle4 := time.Date(2023, 9, 1, 0, 0, 0, 0, time.UTC) -// createdAtVehicle5 := time.Date(2023, 9, 2, 0, 0, 0, 0, time.UTC) - -// vehicle1 := VehicleSnapshot{ -// VehicleID: "v1", -// AccountID: "a1", -// ReferenceID: "r1", - -// Type: SnapshotTypeDaily, -// CreatedAt: createdAtVehicle1, -// LastBattleTime: createdAtVehicle1, -// } -// vehicle2 := VehicleSnapshot{ -// VehicleID: "v1", -// AccountID: "a1", -// ReferenceID: "r1", - -// Type: SnapshotTypeDaily, -// CreatedAt: createdAtVehicle2, -// LastBattleTime: createdAtVehicle2, -// } -// vehicle3 := VehicleSnapshot{ -// VehicleID: "v1", -// AccountID: "a1", -// ReferenceID: "r1", - -// Type: SnapshotTypeDaily, -// CreatedAt: createdAtVehicle3, -// LastBattleTime: createdAtVehicle3, -// } -// vehicle4 := VehicleSnapshot{ -// VehicleID: "v4", -// AccountID: "a1", -// ReferenceID: "r2", - -// Type: SnapshotTypeDaily, -// CreatedAt: createdAtVehicle4, -// LastBattleTime: createdAtVehicle4, -// } -// vehicle5 := VehicleSnapshot{ -// VehicleID: "v5", -// AccountID: "a1", -// ReferenceID: "r2", - -// Type: SnapshotTypeDaily, -// CreatedAt: createdAtVehicle5, -// LastBattleTime: createdAtVehicle5, -// } -// vehicle6 := VehicleSnapshot{ -// VehicleID: "v5", -// AccountID: "a1", -// ReferenceID: "r2", - -// Type: SnapshotTypeDaily, -// CreatedAt: createdAtVehicle5, -// LastBattleTime: createdAtVehicle5, -// } - -// { // create snapshots -// snaphots := []VehicleSnapshot{vehicle1, vehicle2, vehicle3, vehicle4, vehicle5, vehicle6} -// err = client.CreateVehicleSnapshots(ctx, snaphots...) -// assert.NoError(t, err, "create vehicle snapshot should not error") -// } -// { // when we check created after, vehicles need to be ordered by createdAt ASC, so we expect to get vehicle2 back -// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedAfter(createdAtVehicle1)) -// assert.NoError(t, err, "get vehicle snapshot error") -// assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") -// assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned", vehicles) -// } -// { // when we check created before, vehicles need to be ordered by createdAt DESC, so we expect to get vehicle2 back -// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle3)) -// assert.NoError(t, err, "get vehicle snapshot error") -// assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") -// assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned", vehicles) -// } -// { // make sure only 1 vehicle is returned per ID -// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle5.Add(time.Hour))) -// assert.NoError(t, err, "get vehicle snapshot error") -// assert.Len(t, vehicles, 2, "should return exactly 2 snapshots") -// assert.NotEqual(t, vehicles[0].ID, vehicles[1].ID, "each vehicle id should only be returned once", vehicles) -// } -// { // get a cehicle with a specific id -// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", SnapshotTypeDaily, WithVehicleIDs([]string{"v5"})) -// assert.NoError(t, err, "get vehicle snapshot error") -// assert.Len(t, vehicles, 1, "should return exactly 1 snapshots") -// assert.NotEqual(t, vehicles[0].ID, "v5", "incorrect vehicle returned", vehicles) -// } -// { // this should return no result -// vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle1)) -// assert.NoError(t, err, "no results from a raw query does not trigger an error") -// assert.Len(t, vehicles, 0, "return should have no results", vehicles) -// } -// } +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + "github.com/cufee/aftermath/internal/database/models" + _ "github.com/joho/godotenv/autoload" + "github.com/stretchr/testify/assert" +) + +/* +DATABASE_PATH, DATABASE_NAME need to be set, and migrations need to be applied +*/ +func TestGetVehicleSnapshots(t *testing.T) { + client, err := NewSQLiteClient(filepath.Join(os.Getenv("DATABASE_PATH"), os.Getenv("DATABASE_NAME")), WithDebug()) + assert.NoError(t, err, "new client should not error") + defer client.Disconnect() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + client.db.VehicleSnapshot.Delete().Where().Exec(ctx) + defer client.db.VehicleSnapshot.Delete().Exec(ctx) + + _, err = client.UpsertAccounts(ctx, []models.Account{{ID: "a1", Realm: "test", Nickname: "test_account"}}) + assert.NoError(t, err, "failed to upsert an account") + + createdAtVehicle1 := time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC) + createdAtVehicle2 := time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC) + createdAtVehicle3 := time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC) + + createdAtVehicle4 := time.Date(2023, 9, 1, 0, 0, 0, 0, time.UTC) + createdAtVehicle5 := time.Date(2023, 9, 2, 0, 0, 0, 0, time.UTC) + + vehicle1 := models.VehicleSnapshot{ + VehicleID: "v1", + AccountID: "a1", + ReferenceID: "r1", + + Type: models.SnapshotTypeDaily, + CreatedAt: createdAtVehicle1, + LastBattleTime: createdAtVehicle1, + } + vehicle2 := models.VehicleSnapshot{ + VehicleID: "v1", + AccountID: "a1", + ReferenceID: "r1", + + Type: models.SnapshotTypeDaily, + CreatedAt: createdAtVehicle2, + LastBattleTime: createdAtVehicle2, + } + vehicle3 := models.VehicleSnapshot{ + VehicleID: "v1", + AccountID: "a1", + ReferenceID: "r1", + + Type: models.SnapshotTypeDaily, + CreatedAt: createdAtVehicle3, + LastBattleTime: createdAtVehicle3, + } + vehicle4 := models.VehicleSnapshot{ + VehicleID: "v4", + AccountID: "a1", + ReferenceID: "r2", + + Type: models.SnapshotTypeDaily, + CreatedAt: createdAtVehicle4, + LastBattleTime: createdAtVehicle4, + } + vehicle5 := models.VehicleSnapshot{ + VehicleID: "v5", + AccountID: "a1", + ReferenceID: "r2", + + Type: models.SnapshotTypeDaily, + CreatedAt: createdAtVehicle5, + LastBattleTime: createdAtVehicle5, + } + vehicle6 := models.VehicleSnapshot{ + VehicleID: "v5", + AccountID: "a1", + ReferenceID: "r2", + + Type: models.SnapshotTypeDaily, + CreatedAt: createdAtVehicle5, + LastBattleTime: createdAtVehicle5, + } + + { // create snapshots + snapshots := []models.VehicleSnapshot{vehicle1, vehicle2, vehicle3, vehicle4, vehicle5, vehicle6} + aErr, err := client.CreateAccountVehicleSnapshots(ctx, "a1", snapshots...) + assert.NoError(t, err, "create vehicle snapshot should not error") + assert.Nil(t, aErr, "insert returned some errors") + } + { // when we check created after, vehicles need to be ordered by createdAt ASC, so we expect to get vehicle2 back + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", models.SnapshotTypeDaily, WithCreatedAfter(createdAtVehicle1)) + assert.NoError(t, err, "get vehicle snapshot error") + assert.Len(t, vehicles, 1, "should return exactly 1 snapshot") + assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned\nvehicles:%#v\nexpected:%#v", vehicles, createdAtVehicle2) + } + { // when we check created before, vehicles need to be ordered by createdAt DESC, so we expect to get vehicle2 back + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", models.SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle3)) + assert.NoError(t, err, "get vehicle snapshot error") + assert.Len(t, vehicles, 1, "should return exactly 1 snapshot") + assert.True(t, vehicles[0].CreatedAt.Equal(createdAtVehicle2), "wrong vehicle snapshot returned\nvehicles:%#v\nexpected:%#v", vehicles, createdAtVehicle2) + } + { // make sure only 1 vehicle is returned per ID + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", models.SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle5.Add(time.Hour))) + assert.NoError(t, err, "get vehicle snapshot error") + assert.Len(t, vehicles, 2, "should return exactly 2 snapshot") + assert.NotEqual(t, vehicles[0].ID, vehicles[1].ID, "each vehicle id should only be returned once\nvehicles:%#v", vehicles) + } + { // get a vehicle with a specific id + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r2", models.SnapshotTypeDaily, WithVehicleIDs([]string{"v5"})) + assert.NoError(t, err, "get vehicle snapshot error") + assert.Len(t, vehicles, 1, "should return exactly 1 snapshot") + assert.NotEqual(t, vehicles[0].ID, "v5", "incorrect vehicle returned\nvehicles:%#v", vehicles) + } + { // this should return no result + vehicles, err := client.GetVehicleSnapshots(ctx, "a1", "r1", models.SnapshotTypeDaily, WithCreatedBefore(createdAtVehicle1)) + assert.NoError(t, err, "no results from a raw query does not trigger an error") + assert.Len(t, vehicles, 0, "return should have no results\nvehicles:%#v", vehicles) + } +} diff --git a/internal/database/tasks.go b/internal/database/tasks.go index e6ca2bf2..063334c7 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -15,14 +15,14 @@ func toCronTask(record *db.CronTask) models.Task { return models.Task{ ID: record.ID, Type: record.Type, - CreatedAt: time.Unix(record.CreatedAt, 0), - UpdatedAt: time.Unix(record.UpdatedAt, 0), + CreatedAt: record.CreatedAt, + UpdatedAt: record.UpdatedAt, ReferenceID: record.ReferenceID, Targets: record.Targets, Logs: record.Logs, Status: record.Status, - ScheduledAfter: time.Unix(record.ScheduledAfter, 0), - LastRun: time.Unix(record.LastRun, 0), + ScheduledAfter: record.ScheduledAfter, + LastRun: record.LastRun, Data: record.Data, } } @@ -34,7 +34,7 @@ func (c *client) GetStaleTasks(ctx context.Context, limit int) ([]models.Task, e records, err := c.db.CronTask.Query(). Where( crontask.StatusEQ(models.TaskStatusInProgress), - crontask.LastRunLT(time.Now().Add(time.Hour*-1).Unix()), + crontask.LastRunLT(time.Now().Add(time.Hour*-1)), ). Order(crontask.ByScheduledAfter(sql.OrderAsc())). Limit(limit). @@ -56,7 +56,7 @@ Returns all tasks that were created after createdAfter, sorted by ScheduledAfter */ func (c *client) GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) { var where []predicate.CronTask - where = append(where, crontask.CreatedAtGT(createdAfter.Unix())) + where = append(where, crontask.CreatedAtGT(createdAfter)) if len(status) > 0 { where = append(where, crontask.StatusIn(status...)) } @@ -84,7 +84,7 @@ func (c *client) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task var tasks []models.Task return tasks, c.withTx(ctx, func(tx *db.Tx) error { - records, err := tx.CronTask.Query().Where(crontask.StatusEQ(models.TaskStatusScheduled), crontask.ScheduledAfterLT(time.Now().Unix())).Order(crontask.ByScheduledAfter(sql.OrderAsc())).Limit(limit).All(ctx) + records, err := tx.CronTask.Query().Where(crontask.StatusEQ(models.TaskStatusScheduled), crontask.ScheduledAfterLT(time.Now())).Order(crontask.ByScheduledAfter(sql.OrderAsc())).Limit(limit).All(ctx) if err != nil { return err } @@ -156,9 +156,9 @@ func (c *client) CreateTasks(ctx context.Context, tasks ...models.Task) error { SetLogs(t.Logs). SetStatus(t.Status). SetTargets(t.Targets). - SetLastRun(t.LastRun.Unix()). + SetLastRun(t.LastRun). SetReferenceID(t.ReferenceID). - SetScheduledAfter(t.ScheduledAfter.Unix()), + SetScheduledAfter(t.ScheduledAfter), ) } return c.db.CronTask.CreateBulk(inserts...).Exec(ctx) @@ -202,8 +202,8 @@ func (c *client) UpdateTasks(ctx context.Context, tasks ...models.Task) error { SetLogs(t.Logs). SetStatus(t.Status). SetTargets(t.Targets). - SetLastRun(t.LastRun.Unix()). - SetScheduledAfter(t.ScheduledAfter.Unix()). + SetLastRun(t.LastRun). + SetScheduledAfter(t.ScheduledAfter). Exec(ctx) if err != nil { return err diff --git a/internal/database/users.go b/internal/database/users.go index 839ff321..c31d423a 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -2,7 +2,6 @@ package database import ( "context" - "time" "github.com/cufee/aftermath/internal/database/ent/db" "github.com/cufee/aftermath/internal/database/ent/db/user" @@ -44,7 +43,7 @@ func toUserSubscription(record *db.UserSubscription) models.UserSubscription { Type: record.Type, UserID: record.UserID, ReferenceID: record.ReferenceID, - ExpiresAt: time.Unix(record.ExpiresAt, 0), + ExpiresAt: record.ExpiresAt, Permissions: permissions.Parse(record.Permissions, permissions.Blank), } } @@ -59,8 +58,8 @@ func toUserContent(record *db.UserContent) models.UserContent { Value: record.Value, Meta: record.Metadata, - CreatedAt: time.Unix(record.CreatedAt, 0), - UpdatedAt: time.Unix(record.UpdatedAt, 0), + CreatedAt: record.CreatedAt, + UpdatedAt: record.UpdatedAt, } } diff --git a/internal/logic/sessions.go b/internal/logic/snapshots.go similarity index 97% rename from internal/logic/sessions.go rename to internal/logic/snapshots.go index 13c2ec95..c161b072 100644 --- a/internal/logic/sessions.go +++ b/internal/logic/snapshots.go @@ -56,9 +56,6 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl }(id) continue } - if data.LastBattleTime == 0 { - continue - } if s, ok := existingSnapshotsMap[id]; !force && ok && data.LastBattleTime == int(s.LastBattleTime.Unix()) { // last snapshot is the same, we can skip it continue @@ -70,7 +67,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl return nil, nil } - // clans are options-ish + // clans are optional-ish clans, err := wgClient.BatchAccountClan(ctx, realm, validAccounts) if err != nil { log.Err(err).Msg("failed to get batch account clans") @@ -128,10 +125,6 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } vehicleStats := fetch.WargamingVehiclesToFrame(vehicles) for id, vehicle := range vehicleStats { - if vehicle.LastBattleTime.Unix() < 1 { - // vehicle was never played - continue - } if s, ok := existingSnapshotsMap[id]; !force && ok && s.Stats.Battles == vehicle.Battles { // last snapshot is the same, we can skip it continue From ac476f85ed6f99e0a00d3cc9a0347792c93ea2b1 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 14:35:07 -0400 Subject: [PATCH 192/341] tidy --- go.mod | 2 +- go.sum | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/go.mod b/go.mod index c2c64c4a..38628307 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/cufee/aftermath go 1.22.3 require ( - ariga.io/atlas v0.24.0 entgo.io/ent v0.13.1 github.com/bwmarrin/discordgo v0.28.1 github.com/cufee/am-wg-proxy-next/v2 v2.1.5 @@ -27,6 +26,7 @@ require ( ) require ( + ariga.io/atlas v0.24.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index ee4f6f7a..b4bd05ca 100644 --- a/go.sum +++ b/go.sum @@ -58,23 +58,17 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -83,10 +77,6 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= From 9886b822c6e64eb8df6c1a6bfec1c90b71c1b543 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 15:09:55 -0400 Subject: [PATCH 193/341] improved queue and snapshots logic --- cmd/core/queue/errors.go | 1 + cmd/core/queue/queue.go | 14 ++++++- internal/database/snapshots.go | 53 ++++++++++++----------- internal/logic/snapshots.go | 58 ++++++++++++++------------ internal/stats/fetch/v1/multisource.go | 9 ++-- 5 files changed, 77 insertions(+), 58 deletions(-) diff --git a/cmd/core/queue/errors.go b/cmd/core/queue/errors.go index 49a1e479..4be3c6be 100644 --- a/cmd/core/queue/errors.go +++ b/cmd/core/queue/errors.go @@ -3,3 +3,4 @@ package queue import "github.com/pkg/errors" var ErrQueueLocked = errors.New("queue is locked") +var ErrQueueFull = errors.New("queue is full") diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index f32e7f41..d3667864 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -83,7 +83,7 @@ func (q *queue) Start(ctx context.Context) (func(), error) { } go q.startWorkers(qctx, func(_ string) { - if len(q.queued) < q.workerLimit { + if len(q.queued) < q.workerLimit/2 { err := q.pullAndEnqueueTasks(qctx, coreClint.Database()) if err != nil && !errors.Is(ErrQueueLocked, err) { log.Err(err).Msg("failed to pull tasks") @@ -125,7 +125,17 @@ func (q *queue) pullAndEnqueueTasks(ctx context.Context, db database.Client) err log.Debug().Msg("pulling available tasks into queue") - tasks, err := db.GetAndStartTasks(ctx, q.workerLimit) + var tasksToEnqueue int + currentQueue := len(q.queued) + if q.workerLimit > currentQueue { + tasksToEnqueue = q.workerLimit - currentQueue + } + + if tasksToEnqueue < 1 { + return ErrQueueFull + } + + tasks, err := db.GetAndStartTasks(ctx, tasksToEnqueue) if err != nil && !database.IsNotFound(err) { return err } diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index a0809469..442b6544 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -56,8 +56,9 @@ func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID st } var errors = make(map[string]error) - err := c.withTx(ctx, func(tx *db.Tx) error { - for _, data := range snapshots { + for _, data := range snapshots { + // make a transaction per write to avoid locking for too long + err := c.withTx(ctx, func(tx *db.Tx) error { err := c.db.VehicleSnapshot.Create(). SetType(data.Type). SetFrame(data.Stats). @@ -71,12 +72,13 @@ func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID st if err != nil { errors[data.VehicleID] = err } + return nil + }) + if err != nil { + errors[data.VehicleID] = err } - return nil - }) - if err != nil { - return nil, err } + if len(errors) > 0 { return errors, nil } @@ -172,26 +174,29 @@ func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...models } var errors = make(map[string]error) - err := c.withTx(ctx, func(tx *db.Tx) error { - for _, s := range snapshots { - err := c.db.AccountSnapshot.Create(). - SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here - SetCreatedAt(s.CreatedAt). - SetLastBattleTime(s.LastBattleTime). - SetRatingBattles(int(s.RatingBattles.Battles.Float())). - SetRatingFrame(s.RatingBattles). - SetReferenceID(s.ReferenceID). - SetRegularBattles(int(s.RegularBattles.Battles)). - SetRegularFrame(s.RegularBattles). - SetType(s.Type).Exec(ctx) - if err != nil { - errors[s.AccountID] = err + for _, snapshot := range snapshots { + // make a transaction per write to avoid locking for too long + err := c.withTx(ctx, func(tx *db.Tx) error { + for _, s := range snapshots { + err := c.db.AccountSnapshot.Create(). + SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here + SetCreatedAt(s.CreatedAt). + SetLastBattleTime(s.LastBattleTime). + SetRatingBattles(int(s.RatingBattles.Battles.Float())). + SetRatingFrame(s.RatingBattles). + SetReferenceID(s.ReferenceID). + SetRegularBattles(int(s.RegularBattles.Battles)). + SetRegularFrame(s.RegularBattles). + SetType(s.Type).Exec(ctx) + if err != nil { + errors[s.AccountID] = err + } } + return nil + }) + if err != nil { + errors[snapshot.AccountID] = err } - return nil - }) - if err != nil { - return nil, err } if len(errors) > 0 { return errors, nil diff --git a/internal/logic/snapshots.go b/internal/logic/snapshots.go index c161b072..f3a827b3 100644 --- a/internal/logic/snapshots.go +++ b/internal/logic/snapshots.go @@ -88,8 +88,9 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl close(vehicleCh) var accountErrors = make(map[string]error) - var accountUpdates []models.Account - var snapshots []models.AccountSnapshot + var accountUpdates = make(map[string]models.Account) + + var accountSnapshots []models.AccountSnapshot var vehicleSnapshots = make(map[string][]models.VehicleSnapshot) for result := range vehicleCh { // there is only 1 key in this map @@ -109,8 +110,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } stats := fetch.WargamingToStats(realm, accounts[id], clans[id], vehicles) - accountUpdates = append(accountUpdates, stats.Account) - snapshots = append(snapshots, models.AccountSnapshot{ + accountSnapshots = append(accountSnapshots, models.AccountSnapshot{ Type: models.SnapshotTypeDaily, CreatedAt: createdAt, AccountID: stats.Account.ID, @@ -119,6 +119,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl RatingBattles: stats.RatingBattles.StatsFrame, RegularBattles: stats.RegularBattles.StatsFrame, }) + accountUpdates[id] = stats.Account if len(vehicles) < 1 { continue @@ -143,37 +144,40 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } } - aErr, err := dbClient.CreateAccountSnapshots(ctx, snapshots...) - if err != nil { - return nil, errors.Wrap(err, "failed to save account snapshots to database") - } - for id, err := range aErr { - if err != nil { - accountErrors[id] = err + for _, accountSnapshot := range accountSnapshots { + vehicles, ok := vehicleSnapshots[accountSnapshot.AccountID] + if !ok { + accountErrors[accountSnapshot.AccountID] = errors.New("account missing vehicle snapshots") + continue } - } - for accountId, vehicles := range vehicleSnapshots { - vErr, err := dbClient.CreateAccountVehicleSnapshots(ctx, accountId, vehicles...) + // save all vehicle snapshots + vErr, err := dbClient.CreateAccountVehicleSnapshots(ctx, accountSnapshot.AccountID, vehicles...) if err != nil { - return nil, errors.Wrap(err, "failed to save vehicle snapshots to database") + accountErrors[accountSnapshot.AccountID] = err + continue } if len(vErr) > 0 { - accountErrors[accountId] = errors.Errorf("failed to insert %d vehicle snapshots", len(vErr)) + accountErrors[accountSnapshot.AccountID] = errors.Errorf("failed to insert %d vehicle snapshots", len(vErr)) + continue } - } - uErr, err := dbClient.UpsertAccounts(ctx, accountUpdates) - // these errors are not critical, we log and continue - if err != nil { - log.Err(err).Msg("failed to upsert accounts") - } - if len(uErr) > 0 { - var errors = make(map[string]string) - for id, err := range uErr { - errors[id] = err.Error() + // save account snapshot + aErr, err := dbClient.CreateAccountSnapshots(ctx, accountSnapshot) + if err != nil { + accountErrors[accountSnapshot.AccountID] = errors.Wrap(err, "failed to save account snapshots to database") + continue + } + if err := aErr[accountSnapshot.AccountID]; err != nil { + accountErrors[accountSnapshot.AccountID] = errors.Wrap(err, "failed to save account snapshots to database") + continue + } + + // update account cache, non critical and should not fail the flow + _, err = dbClient.UpsertAccounts(ctx, []models.Account{accountUpdates[accountSnapshot.AccountID]}) + if err != nil { + log.Err(err).Str("accountId", accountSnapshot.AccountID).Msg("failed to upsert account") } - log.Error().Any("errors", errors).Msg("failed to upsert some accounts") } return accountErrors, nil diff --git a/internal/stats/fetch/v1/multisource.go b/internal/stats/fetch/v1/multisource.go index 92c3b7a9..0db25424 100644 --- a/internal/stats/fetch/v1/multisource.go +++ b/internal/stats/fetch/v1/multisource.go @@ -313,12 +313,11 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session defer group.Done() s, err := c.database.GetAccountSnapshot(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) accountSnapshot = retry.DataWithErr[models.AccountSnapshot]{Data: s, Err: err} - }() + if err != nil { + return + } - group.Add(1) - go func() { - defer group.Done() - v, err := c.database.GetVehicleSnapshots(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) + v, err := c.database.GetVehicleSnapshots(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(s.LastBattleTime.Add(time.Second))) vehiclesSnapshots = retry.DataWithErr[[]models.VehicleSnapshot]{Data: v, Err: err} }() From 5ec559f19afc8885e0d1bfbda2fdf3fa5a629337 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 15:33:32 -0400 Subject: [PATCH 194/341] better encoding --- cmd/core/queue/queue.go | 24 ++++---- cmd/core/tasks/cleanup.go | 63 ++++++++++++--------- cmd/core/tasks/handler.go | 6 +- cmd/core/tasks/sessions.go | 31 +++++----- internal/database/ent/db/crontask.go | 2 +- internal/database/ent/db/crontask_create.go | 2 +- internal/database/ent/db/crontask_update.go | 4 +- internal/database/ent/db/mutation.go | 10 ++-- internal/database/ent/schema/cron_task.go | 2 +- internal/database/models/task.go | 2 +- 10 files changed, 78 insertions(+), 68 deletions(-) diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index d3667864..9a4e91aa 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -36,7 +36,7 @@ func New(workerLimit int, newCoreClient func() (core.Client, error)) *queue { handlers: make(map[models.TaskType]tasks.TaskHandler), workerLimit: workerLimit, - workerTimeout: time.Second * 30, + workerTimeout: time.Second * 60, // a single cron scheduler cycle workerLimiter: make(chan struct{}, workerLimit), activeTasksMx: &sync.Mutex{}, @@ -123,19 +123,14 @@ func (q *queue) pullAndEnqueueTasks(ctx context.Context, db database.Client) err } defer q.queueLock.Unlock() - log.Debug().Msg("pulling available tasks into queue") - - var tasksToEnqueue int currentQueue := len(q.queued) - if q.workerLimit > currentQueue { - tasksToEnqueue = q.workerLimit - currentQueue - } - - if tasksToEnqueue < 1 { + if q.workerLimit < currentQueue { + log.Debug().Msg("queue is full") return ErrQueueFull } - tasks, err := db.GetAndStartTasks(ctx, tasksToEnqueue) + log.Debug().Msg("pulling available tasks into queue") + tasks, err := db.GetAndStartTasks(ctx, q.workerLimit-currentQueue) if err != nil && !database.IsNotFound(err) { return err } @@ -223,7 +218,10 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { return } - msg, err := handler.Process(coreClient, task) + wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) + defer cancel() + + msg, err := handler.Process(wctx, coreClient, task) task.Status = models.TaskStatusComplete l := models.TaskLog{ Timestamp: time.Now(), @@ -239,10 +237,10 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { } task.LogAttempt(l) - wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) + uctx, cancel := context.WithTimeout(ctx, q.workerTimeout) defer cancel() - err = coreClient.Database().UpdateTasks(wctx, task) + err = coreClient.Database().UpdateTasks(uctx, task) if err != nil { log.Err(err).Msg("failed to update a task") } diff --git a/cmd/core/tasks/cleanup.go b/cmd/core/tasks/cleanup.go index e931faf4..6f3b3361 100644 --- a/cmd/core/tasks/cleanup.go +++ b/cmd/core/tasks/cleanup.go @@ -12,36 +12,42 @@ import ( func init() { defaultHandlers[models.TaskTypeDatabaseCleanup] = TaskHandler{ - Process: func(client core.Client, task models.Task) (string, error) { + Process: func(ctx context.Context, client core.Client, task models.Task) (string, error) { if task.Data == nil { return "no data provided", errors.New("no data provided") } - snapshotExpiration, ok := task.Data["expiration_snapshots"].(int64) - if !ok { - return "invalid expiration_snapshots", errors.New("failed to cast expiration_snapshots to time") + { + taskExpiration, err := time.Parse(time.RFC3339, task.Data["expiration_tasks"]) + if err != nil { + task.Data["triesLeft"] = "0" // do not retry + return "invalid expiration_tasks", errors.Wrap(err, "failed to parse expiration_tasks to time") + } + err = client.Database().DeleteExpiredTasks(ctx, taskExpiration) + if err != nil { + return "failed to delete expired tasks", err + } } - taskExpiration, ok := task.Data["expiration_tasks"].(int64) - if !ok { - task.Data["triesLeft"] = int(0) // do not retry - return "invalid expiration_tasks", errors.New("failed to cast expiration_tasks to time") + { + interactionExpiration, err := time.Parse(time.RFC3339, task.Data["expiration_interactions"]) + if err != nil { + task.Data["triesLeft"] = "0" // do not retry + return "invalid expiration_snapshots", errors.Wrap(err, "failed to parse interactionExpiration to time") + } + err = client.Database().DeleteExpiredInteractions(ctx, interactionExpiration) + if err != nil { + return "failed to delete expired interactions", err + } } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - - err := client.Database().DeleteExpiredTasks(ctx, time.Unix(taskExpiration, 0)) - if err != nil { - return "failed to delete expired tasks", err - } - - err = client.Database().DeleteExpiredInteractions(ctx, time.Unix(taskExpiration, 0)) - if err != nil { - return "failed to delete expired interactions", err - } - - err = client.Database().DeleteExpiredSnapshots(ctx, time.Unix(snapshotExpiration, 0)) - if err != nil { - return "failed to delete expired snapshots", err + { + snapshotExpiration, err := time.Parse(time.RFC3339, task.Data["expiration_snapshots"]) + if err != nil { + task.Data["triesLeft"] = "0" // do not retry + return "invalid expiration_snapshots", errors.Wrap(err, "failed to parse expiration_snapshots to time") + } + err = client.Database().DeleteExpiredSnapshots(ctx, snapshotExpiration) + if err != nil { + return "failed to delete expired snapshots", err + } } return "cleanup complete", nil @@ -59,9 +65,10 @@ func CreateCleanupTasks(client core.Client) error { Type: models.TaskTypeDatabaseCleanup, ReferenceID: "database_cleanup", ScheduledAfter: now, - Data: map[string]any{ - "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90).Unix(), // 90 days - "expiration_tasks": now.Add(-1 * time.Hour * 24 * 7).Unix(), // 7 days + Data: map[string]string{ + "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90).Format(time.RFC3339), // 90 days + "expiration_interactions": now.Add(-1 * time.Hour * 24 * 7).Format(time.RFC3339), // 7 days + "expiration_tasks": now.Add(-1 * time.Hour * 24 * 7).Format(time.RFC3339), // 7 days }, } diff --git a/cmd/core/tasks/handler.go b/cmd/core/tasks/handler.go index f54c06c5..dd421b10 100644 --- a/cmd/core/tasks/handler.go +++ b/cmd/core/tasks/handler.go @@ -1,13 +1,15 @@ package tasks import ( + "context" + "github.com/cufee/aftermath/cmd/core" "github.com/cufee/aftermath/internal/database/models" ) type TaskHandler struct { - Process func(client core.Client, task models.Task) (string, error) - ShouldRetry func(task *models.Task) bool + Process func(context.Context, core.Client, models.Task) (string, error) + ShouldRetry func(*models.Task) bool } var defaultHandlers = make(map[models.TaskType]TaskHandler) diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index e3a4cfd1..cd3873cb 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -2,6 +2,7 @@ package tasks import ( "context" + "strconv" "strings" "time" @@ -15,30 +16,27 @@ import ( func init() { defaultHandlers[models.TaskTypeRecordSnapshots] = TaskHandler{ - Process: func(client core.Client, task models.Task) (string, error) { + Process: func(ctx context.Context, client core.Client, task models.Task) (string, error) { if task.Data == nil { return "no data provided", errors.New("no data provided") } - realm, ok := task.Data["realm"].(string) + realm, ok := task.Data["realm"] if !ok { - task.Data["triesLeft"] = int64(0) // do not retry + task.Data["triesLeft"] = "0" // do not retry return "invalid realm", errors.New("invalid realm") } if len(task.Targets) > 100 { - task.Data["triesLeft"] = int64(0) // do not retry + task.Data["triesLeft"] = "0" // do not retry return "cannot process 100+ accounts at a time", errors.New("invalid targets length") } if len(task.Targets) < 1 { - task.Data["triesLeft"] = int64(0) // do not retry + task.Data["triesLeft"] = "0" // do not retry return "target ids cannot be left blank", errors.New("invalid targets length") } - forceUpdate, _ := task.Data["force"].(bool) + forceUpdate := task.Data["force"] == "true" log.Debug().Str("taskId", task.ID).Any("targets", task.Targets).Msg("started working on a session refresh task") - ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) - defer cancel() - accountErrors, err := logic.RecordAccountSnapshots(ctx, client.Wargaming(), client.Database(), realm, forceUpdate, task.Targets...) if err != nil { return "failed to record sessions", err @@ -53,11 +51,16 @@ func init() { for id := range accountErrors { task.Targets = append(task.Targets, id) } + task.LogAttempt(models.TaskLog{ + Timestamp: time.Now(), + }) + return "retrying failed accounts", errors.New("some accounts failed") }, ShouldRetry: func(task *models.Task) bool { - triesLeft, ok := task.Data["triesLeft"].(int64) - if !ok { + triesLeft, err := strconv.Atoi(task.Data["triesLeft"]) + if err != nil { + log.Err(err).Str("taskId", task.ID).Msg("failed to parse task triesLeft") return false } if triesLeft <= 0 { @@ -65,7 +68,7 @@ func init() { } triesLeft -= 1 - task.Data["triesLeft"] = triesLeft + task.Data["triesLeft"] = strconv.Itoa(triesLeft) task.ScheduledAfter = time.Now().Add(5 * time.Minute) // Backoff for 5 minutes to avoid spamming return true }, @@ -78,9 +81,9 @@ func CreateRecordSnapshotsTasks(client core.Client, realm string) error { Type: models.TaskTypeRecordSnapshots, ReferenceID: "realm_" + realm, ScheduledAfter: time.Now(), - Data: map[string]any{ + Data: map[string]string{ "realm": realm, - "triesLeft": int64(3), + "triesLeft": "3", }, } diff --git a/internal/database/ent/db/crontask.go b/internal/database/ent/db/crontask.go index dcc845a5..4b56d14c 100644 --- a/internal/database/ent/db/crontask.go +++ b/internal/database/ent/db/crontask.go @@ -38,7 +38,7 @@ type CronTask struct { // Logs holds the value of the "logs" field. Logs []models.TaskLog `json:"logs,omitempty"` // Data holds the value of the "data" field. - Data map[string]interface{} `json:"data,omitempty"` + Data map[string]string `json:"data,omitempty"` selectValues sql.SelectValues } diff --git a/internal/database/ent/db/crontask_create.go b/internal/database/ent/db/crontask_create.go index 70343d77..f9a3b4eb 100644 --- a/internal/database/ent/db/crontask_create.go +++ b/internal/database/ent/db/crontask_create.go @@ -92,7 +92,7 @@ func (ctc *CronTaskCreate) SetLogs(ml []models.TaskLog) *CronTaskCreate { } // SetData sets the "data" field. -func (ctc *CronTaskCreate) SetData(m map[string]interface{}) *CronTaskCreate { +func (ctc *CronTaskCreate) SetData(m map[string]string) *CronTaskCreate { ctc.mutation.SetData(m) return ctc } diff --git a/internal/database/ent/db/crontask_update.go b/internal/database/ent/db/crontask_update.go index 5565a00b..df58b722 100644 --- a/internal/database/ent/db/crontask_update.go +++ b/internal/database/ent/db/crontask_update.go @@ -132,7 +132,7 @@ func (ctu *CronTaskUpdate) AppendLogs(ml []models.TaskLog) *CronTaskUpdate { } // SetData sets the "data" field. -func (ctu *CronTaskUpdate) SetData(m map[string]interface{}) *CronTaskUpdate { +func (ctu *CronTaskUpdate) SetData(m map[string]string) *CronTaskUpdate { ctu.mutation.SetData(m) return ctu } @@ -376,7 +376,7 @@ func (ctuo *CronTaskUpdateOne) AppendLogs(ml []models.TaskLog) *CronTaskUpdateOn } // SetData sets the "data" field. -func (ctuo *CronTaskUpdateOne) SetData(m map[string]interface{}) *CronTaskUpdateOne { +func (ctuo *CronTaskUpdateOne) SetData(m map[string]string) *CronTaskUpdateOne { ctuo.mutation.SetData(m) return ctuo } diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go index c4078845..586c843d 100644 --- a/internal/database/ent/db/mutation.go +++ b/internal/database/ent/db/mutation.go @@ -4704,7 +4704,7 @@ type CronTaskMutation struct { last_run *time.Time logs *[]models.TaskLog appendlogs []models.TaskLog - data *map[string]interface{} + data *map[string]string clearedFields map[string]struct{} done bool oldValue func(context.Context) (*CronTask, error) @@ -5170,12 +5170,12 @@ func (m *CronTaskMutation) ResetLogs() { } // SetData sets the "data" field. -func (m *CronTaskMutation) SetData(value map[string]interface{}) { +func (m *CronTaskMutation) SetData(value map[string]string) { m.data = &value } // Data returns the value of the "data" field in the mutation. -func (m *CronTaskMutation) Data() (r map[string]interface{}, exists bool) { +func (m *CronTaskMutation) Data() (r map[string]string, exists bool) { v := m.data if v == nil { return @@ -5186,7 +5186,7 @@ func (m *CronTaskMutation) Data() (r map[string]interface{}, exists bool) { // OldData returns the old "data" field's value of the CronTask entity. // If the CronTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldData(ctx context.Context) (v map[string]interface{}, err error) { +func (m *CronTaskMutation) OldData(ctx context.Context) (v map[string]string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldData is only allowed on UpdateOne operations") } @@ -5400,7 +5400,7 @@ func (m *CronTaskMutation) SetField(name string, value ent.Value) error { m.SetLogs(v) return nil case crontask.FieldData: - v, ok := value.(map[string]interface{}) + v, ok := value.(map[string]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } diff --git a/internal/database/ent/schema/cron_task.go b/internal/database/ent/schema/cron_task.go index e20544a2..395749d9 100644 --- a/internal/database/ent/schema/cron_task.go +++ b/internal/database/ent/schema/cron_task.go @@ -26,7 +26,7 @@ func (CronTask) Fields() []ent.Field { field.Time("last_run"), // field.JSON("logs", []models.TaskLog{}), - field.JSON("data", map[string]any{}), + field.JSON("data", map[string]string{}), ) } diff --git a/internal/database/models/task.go b/internal/database/models/task.go index d2d569bb..e81ef137 100644 --- a/internal/database/models/task.go +++ b/internal/database/models/task.go @@ -70,7 +70,7 @@ type Task struct { ScheduledAfter time.Time `json:"scheduledAfter"` LastRun time.Time `json:"lastRun"` - Data map[string]any `json:"data"` + Data map[string]string `json:"data"` } func (t *Task) LogAttempt(log TaskLog) { From 88358d3ce52ead969ad360e339906a0e275561ba Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 15:38:06 -0400 Subject: [PATCH 195/341] better logs --- cmd/core/tasks/sessions.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index cd3873cb..03ae0101 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -47,13 +47,19 @@ func init() { } // Retry failed accounts - task.Targets = make([]string, len(accountErrors)) - for id := range accountErrors { + newTargets := make([]string, 0, len(accountErrors)) + for id, err := range accountErrors { + if err == nil { + continue + } task.Targets = append(task.Targets, id) + task.LogAttempt(models.TaskLog{ + Timestamp: time.Now(), + Error: err.Error(), + Comment: id, + }) } - task.LogAttempt(models.TaskLog{ - Timestamp: time.Now(), - }) + task.Targets = newTargets return "retrying failed accounts", errors.New("some accounts failed") }, From f7d17c382edf396265c006f794ae43f9055d0987 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 15:47:09 -0400 Subject: [PATCH 196/341] fixed task targets --- cmd/core/tasks/sessions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index 03ae0101..4f97e9c1 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -52,7 +52,7 @@ func init() { if err == nil { continue } - task.Targets = append(task.Targets, id) + newTargets = append(newTargets, id) task.LogAttempt(models.TaskLog{ Timestamp: time.Now(), Error: err.Error(), From 2c42e7b22fea715deea0bfc815205eccac31ba29 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 15:50:59 -0400 Subject: [PATCH 197/341] fixed bad loops --- internal/database/snapshots.go | 36 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 442b6544..8d9e4fb7 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -59,7 +59,7 @@ func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID st for _, data := range snapshots { // make a transaction per write to avoid locking for too long err := c.withTx(ctx, func(tx *db.Tx) error { - err := c.db.VehicleSnapshot.Create(). + return c.db.VehicleSnapshot.Create(). SetType(data.Type). SetFrame(data.Stats). SetVehicleID(data.VehicleID). @@ -69,10 +69,6 @@ func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID st SetLastBattleTime(data.LastBattleTime). SetAccount(c.db.Account.GetX(ctx, accountID)). Exec(ctx) - if err != nil { - errors[data.VehicleID] = err - } - return nil }) if err != nil { errors[data.VehicleID] = err @@ -174,28 +170,22 @@ func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...models } var errors = make(map[string]error) - for _, snapshot := range snapshots { + for _, s := range snapshots { // make a transaction per write to avoid locking for too long err := c.withTx(ctx, func(tx *db.Tx) error { - for _, s := range snapshots { - err := c.db.AccountSnapshot.Create(). - SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here - SetCreatedAt(s.CreatedAt). - SetLastBattleTime(s.LastBattleTime). - SetRatingBattles(int(s.RatingBattles.Battles.Float())). - SetRatingFrame(s.RatingBattles). - SetReferenceID(s.ReferenceID). - SetRegularBattles(int(s.RegularBattles.Battles)). - SetRegularFrame(s.RegularBattles). - SetType(s.Type).Exec(ctx) - if err != nil { - errors[s.AccountID] = err - } - } - return nil + return c.db.AccountSnapshot.Create(). + SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here + SetCreatedAt(s.CreatedAt). + SetLastBattleTime(s.LastBattleTime). + SetRatingBattles(int(s.RatingBattles.Battles.Float())). + SetRatingFrame(s.RatingBattles). + SetReferenceID(s.ReferenceID). + SetRegularBattles(int(s.RegularBattles.Battles)). + SetRegularFrame(s.RegularBattles). + SetType(s.Type).Exec(ctx) }) if err != nil { - errors[snapshot.AccountID] = err + errors[s.AccountID] = err } } if len(errors) > 0 { From 4ac37841ebe96532cb1eb15540e0b16743c82a52 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 15:56:45 -0400 Subject: [PATCH 198/341] cleaner task handlers --- cmd/core/queue/queue.go | 4 ++-- cmd/core/tasks/cleanup.go | 26 ++++++++++++++------------ cmd/core/tasks/handler.go | 2 +- cmd/core/tasks/sessions.go | 18 +++++++----------- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index 9a4e91aa..7a960eb4 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -221,11 +221,11 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) defer cancel() - msg, err := handler.Process(wctx, coreClient, task) + err = handler.Process(wctx, coreClient, &task) task.Status = models.TaskStatusComplete l := models.TaskLog{ Timestamp: time.Now(), - Comment: msg, + Comment: "task handler finished", } if err != nil { l.Error = err.Error() diff --git a/cmd/core/tasks/cleanup.go b/cmd/core/tasks/cleanup.go index 6f3b3361..f15ceb03 100644 --- a/cmd/core/tasks/cleanup.go +++ b/cmd/core/tasks/cleanup.go @@ -7,50 +7,52 @@ import ( "github.com/pkg/errors" "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" ) func init() { defaultHandlers[models.TaskTypeDatabaseCleanup] = TaskHandler{ - Process: func(ctx context.Context, client core.Client, task models.Task) (string, error) { + Process: func(ctx context.Context, client core.Client, task *models.Task) error { if task.Data == nil { - return "no data provided", errors.New("no data provided") + task.Data["triesLeft"] = "0" // do not retry + return errors.New("no data provided") } { taskExpiration, err := time.Parse(time.RFC3339, task.Data["expiration_tasks"]) if err != nil { task.Data["triesLeft"] = "0" // do not retry - return "invalid expiration_tasks", errors.Wrap(err, "failed to parse expiration_tasks to time") + return errors.Wrap(err, "failed to parse expiration_tasks to time") } err = client.Database().DeleteExpiredTasks(ctx, taskExpiration) - if err != nil { - return "failed to delete expired tasks", err + if err != nil && !database.IsNotFound(err) { + return errors.Wrap(err, "failed to delete expired tasks") } } { interactionExpiration, err := time.Parse(time.RFC3339, task.Data["expiration_interactions"]) if err != nil { task.Data["triesLeft"] = "0" // do not retry - return "invalid expiration_snapshots", errors.Wrap(err, "failed to parse interactionExpiration to time") + return errors.Wrap(err, "failed to parse interactionExpiration to time") } err = client.Database().DeleteExpiredInteractions(ctx, interactionExpiration) - if err != nil { - return "failed to delete expired interactions", err + if err != nil && !database.IsNotFound(err) { + return errors.Wrap(err, "failed to delete expired interactions") } } { snapshotExpiration, err := time.Parse(time.RFC3339, task.Data["expiration_snapshots"]) if err != nil { task.Data["triesLeft"] = "0" // do not retry - return "invalid expiration_snapshots", errors.Wrap(err, "failed to parse expiration_snapshots to time") + return errors.Wrap(err, "failed to parse expiration_snapshots to time") } err = client.Database().DeleteExpiredSnapshots(ctx, snapshotExpiration) - if err != nil { - return "failed to delete expired snapshots", err + if err != nil && !database.IsNotFound(err) { + return errors.Wrap(err, "failed to delete expired snapshots") } } - return "cleanup complete", nil + return nil }, ShouldRetry: func(task *models.Task) bool { return false diff --git a/cmd/core/tasks/handler.go b/cmd/core/tasks/handler.go index dd421b10..8b8343df 100644 --- a/cmd/core/tasks/handler.go +++ b/cmd/core/tasks/handler.go @@ -8,7 +8,7 @@ import ( ) type TaskHandler struct { - Process func(context.Context, core.Client, models.Task) (string, error) + Process func(context.Context, core.Client, *models.Task) error ShouldRetry func(*models.Task) bool } diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index 4f97e9c1..d2c6b5fa 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -16,22 +16,22 @@ import ( func init() { defaultHandlers[models.TaskTypeRecordSnapshots] = TaskHandler{ - Process: func(ctx context.Context, client core.Client, task models.Task) (string, error) { + Process: func(ctx context.Context, client core.Client, task *models.Task) error { if task.Data == nil { - return "no data provided", errors.New("no data provided") + return errors.New("no data provided") } realm, ok := task.Data["realm"] if !ok { task.Data["triesLeft"] = "0" // do not retry - return "invalid realm", errors.New("invalid realm") + return errors.New("invalid realm") } if len(task.Targets) > 100 { task.Data["triesLeft"] = "0" // do not retry - return "cannot process 100+ accounts at a time", errors.New("invalid targets length") + return errors.New("invalid targets length") } if len(task.Targets) < 1 { task.Data["triesLeft"] = "0" // do not retry - return "target ids cannot be left blank", errors.New("invalid targets length") + return errors.New("invalid targets length") } forceUpdate := task.Data["force"] == "true" @@ -39,11 +39,7 @@ func init() { accountErrors, err := logic.RecordAccountSnapshots(ctx, client.Wargaming(), client.Database(), realm, forceUpdate, task.Targets...) if err != nil { - return "failed to record sessions", err - } - - if len(accountErrors) == 0 { - return "finished session update on all accounts", nil + return err } // Retry failed accounts @@ -61,7 +57,7 @@ func init() { } task.Targets = newTargets - return "retrying failed accounts", errors.New("some accounts failed") + return nil }, ShouldRetry: func(task *models.Task) bool { triesLeft, err := strconv.Atoi(task.Data["triesLeft"]) From b1fa558f27d09709064861125d710519de2b21d4 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 16:06:59 -0400 Subject: [PATCH 199/341] moved retry counter login out of handlers --- cmd/core/queue/queue.go | 4 +- cmd/core/tasks/cleanup.go | 3 - cmd/core/tasks/handler.go | 3 +- cmd/core/tasks/sessions.go | 24 ++--- go.sum | 10 ++ internal/database/ent/db/crontask.go | 13 +++ internal/database/ent/db/crontask/crontask.go | 8 ++ internal/database/ent/db/crontask/where.go | 45 +++++++++ internal/database/ent/db/crontask_create.go | 13 +++ internal/database/ent/db/crontask_update.go | 54 +++++++++++ internal/database/ent/db/migrate/schema.go | 1 + internal/database/ent/db/mutation.go | 94 ++++++++++++++++++- .../ent/migrations/20240627200608.sql | 2 + internal/database/ent/migrations/atlas.sum | 3 +- internal/database/ent/schema/cron_task.go | 1 + internal/database/models/task.go | 1 + internal/database/tasks.go | 7 +- 17 files changed, 261 insertions(+), 25 deletions(-) create mode 100644 internal/database/ent/migrations/20240627200608.sql diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index 7a960eb4..09f8c6a2 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -199,6 +199,7 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { return } + task.TriesLeft -= 1 handler, ok := q.handlers[task.Type] if !ok { task.Status = models.TaskStatusFailed @@ -229,8 +230,9 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { } if err != nil { l.Error = err.Error() - if handler.ShouldRetry(&task) { + if task.TriesLeft > 0 { task.Status = models.TaskStatusScheduled + task.ScheduledAfter = time.Now().Add(time.Minute * 5) } else { task.Status = models.TaskStatusFailed } diff --git a/cmd/core/tasks/cleanup.go b/cmd/core/tasks/cleanup.go index f15ceb03..b3836c56 100644 --- a/cmd/core/tasks/cleanup.go +++ b/cmd/core/tasks/cleanup.go @@ -54,9 +54,6 @@ func init() { return nil }, - ShouldRetry: func(task *models.Task) bool { - return false - }, } } diff --git a/cmd/core/tasks/handler.go b/cmd/core/tasks/handler.go index 8b8343df..9c182750 100644 --- a/cmd/core/tasks/handler.go +++ b/cmd/core/tasks/handler.go @@ -8,8 +8,7 @@ import ( ) type TaskHandler struct { - Process func(context.Context, core.Client, *models.Task) error - ShouldRetry func(*models.Task) bool + Process func(context.Context, core.Client, *models.Task) error } var defaultHandlers = make(map[models.TaskType]TaskHandler) diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index d2c6b5fa..21db9fc0 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -20,11 +20,19 @@ func init() { if task.Data == nil { return errors.New("no data provided") } + + triesLeft, err := strconv.Atoi(task.Data["triesLeft"]) + if err != nil { + return errors.Wrap(err, "failed to parse tries left") + } + task.Data["triesLeft"] = strconv.Itoa(triesLeft - 1) + realm, ok := task.Data["realm"] if !ok { task.Data["triesLeft"] = "0" // do not retry return errors.New("invalid realm") } + if len(task.Targets) > 100 { task.Data["triesLeft"] = "0" // do not retry return errors.New("invalid targets length") @@ -33,6 +41,7 @@ func init() { task.Data["triesLeft"] = "0" // do not retry return errors.New("invalid targets length") } + forceUpdate := task.Data["force"] == "true" log.Debug().Str("taskId", task.ID).Any("targets", task.Targets).Msg("started working on a session refresh task") @@ -59,21 +68,6 @@ func init() { return nil }, - ShouldRetry: func(task *models.Task) bool { - triesLeft, err := strconv.Atoi(task.Data["triesLeft"]) - if err != nil { - log.Err(err).Str("taskId", task.ID).Msg("failed to parse task triesLeft") - return false - } - if triesLeft <= 0 { - return false - } - - triesLeft -= 1 - task.Data["triesLeft"] = strconv.Itoa(triesLeft) - task.ScheduledAfter = time.Now().Add(5 * time.Minute) // Backoff for 5 minutes to avoid spamming - return true - }, } } diff --git a/go.sum b/go.sum index b4bd05ca..ee4f6f7a 100644 --- a/go.sum +++ b/go.sum @@ -58,17 +58,23 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -77,6 +83,10 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/internal/database/ent/db/crontask.go b/internal/database/ent/db/crontask.go index 4b56d14c..38595bf1 100644 --- a/internal/database/ent/db/crontask.go +++ b/internal/database/ent/db/crontask.go @@ -35,6 +35,8 @@ type CronTask struct { ScheduledAfter time.Time `json:"scheduled_after,omitempty"` // LastRun holds the value of the "last_run" field. LastRun time.Time `json:"last_run,omitempty"` + // TriesLeft holds the value of the "tries_left" field. + TriesLeft int `json:"tries_left,omitempty"` // Logs holds the value of the "logs" field. Logs []models.TaskLog `json:"logs,omitempty"` // Data holds the value of the "data" field. @@ -49,6 +51,8 @@ func (*CronTask) scanValues(columns []string) ([]any, error) { switch columns[i] { case crontask.FieldTargets, crontask.FieldLogs, crontask.FieldData: values[i] = new([]byte) + case crontask.FieldTriesLeft: + values[i] = new(sql.NullInt64) case crontask.FieldID, crontask.FieldType, crontask.FieldReferenceID, crontask.FieldStatus: values[i] = new(sql.NullString) case crontask.FieldCreatedAt, crontask.FieldUpdatedAt, crontask.FieldScheduledAfter, crontask.FieldLastRun: @@ -124,6 +128,12 @@ func (ct *CronTask) assignValues(columns []string, values []any) error { } else if value.Valid { ct.LastRun = value.Time } + case crontask.FieldTriesLeft: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field tries_left", values[i]) + } else if value.Valid { + ct.TriesLeft = int(value.Int64) + } case crontask.FieldLogs: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field logs", values[i]) @@ -200,6 +210,9 @@ func (ct *CronTask) String() string { builder.WriteString("last_run=") builder.WriteString(ct.LastRun.Format(time.ANSIC)) builder.WriteString(", ") + builder.WriteString("tries_left=") + builder.WriteString(fmt.Sprintf("%v", ct.TriesLeft)) + builder.WriteString(", ") builder.WriteString("logs=") builder.WriteString(fmt.Sprintf("%v", ct.Logs)) builder.WriteString(", ") diff --git a/internal/database/ent/db/crontask/crontask.go b/internal/database/ent/db/crontask/crontask.go index 1e2f95ea..34eaf930 100644 --- a/internal/database/ent/db/crontask/crontask.go +++ b/internal/database/ent/db/crontask/crontask.go @@ -31,6 +31,8 @@ const ( FieldScheduledAfter = "scheduled_after" // FieldLastRun holds the string denoting the last_run field in the database. FieldLastRun = "last_run" + // FieldTriesLeft holds the string denoting the tries_left field in the database. + FieldTriesLeft = "tries_left" // FieldLogs holds the string denoting the logs field in the database. FieldLogs = "logs" // FieldData holds the string denoting the data field in the database. @@ -50,6 +52,7 @@ var Columns = []string{ FieldStatus, FieldScheduledAfter, FieldLastRun, + FieldTriesLeft, FieldLogs, FieldData, } @@ -139,3 +142,8 @@ func ByScheduledAfter(opts ...sql.OrderTermOption) OrderOption { func ByLastRun(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldLastRun, opts...).ToFunc() } + +// ByTriesLeft orders the results by the tries_left field. +func ByTriesLeft(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTriesLeft, opts...).ToFunc() +} diff --git a/internal/database/ent/db/crontask/where.go b/internal/database/ent/db/crontask/where.go index 3f2568ff..82c5119d 100644 --- a/internal/database/ent/db/crontask/where.go +++ b/internal/database/ent/db/crontask/where.go @@ -90,6 +90,11 @@ func LastRun(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldLastRun, v)) } +// TriesLeft applies equality check predicate on the "tries_left" field. It's identical to TriesLeftEQ. +func TriesLeft(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldTriesLeft, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldEQ(FieldCreatedAt, v)) @@ -375,6 +380,46 @@ func LastRunLTE(v time.Time) predicate.CronTask { return predicate.CronTask(sql.FieldLTE(FieldLastRun, v)) } +// TriesLeftEQ applies the EQ predicate on the "tries_left" field. +func TriesLeftEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldEQ(FieldTriesLeft, v)) +} + +// TriesLeftNEQ applies the NEQ predicate on the "tries_left" field. +func TriesLeftNEQ(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldNEQ(FieldTriesLeft, v)) +} + +// TriesLeftIn applies the In predicate on the "tries_left" field. +func TriesLeftIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldIn(FieldTriesLeft, vs...)) +} + +// TriesLeftNotIn applies the NotIn predicate on the "tries_left" field. +func TriesLeftNotIn(vs ...int) predicate.CronTask { + return predicate.CronTask(sql.FieldNotIn(FieldTriesLeft, vs...)) +} + +// TriesLeftGT applies the GT predicate on the "tries_left" field. +func TriesLeftGT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGT(FieldTriesLeft, v)) +} + +// TriesLeftGTE applies the GTE predicate on the "tries_left" field. +func TriesLeftGTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldGTE(FieldTriesLeft, v)) +} + +// TriesLeftLT applies the LT predicate on the "tries_left" field. +func TriesLeftLT(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLT(FieldTriesLeft, v)) +} + +// TriesLeftLTE applies the LTE predicate on the "tries_left" field. +func TriesLeftLTE(v int) predicate.CronTask { + return predicate.CronTask(sql.FieldLTE(FieldTriesLeft, v)) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.CronTask) predicate.CronTask { return predicate.CronTask(sql.AndPredicates(predicates...)) diff --git a/internal/database/ent/db/crontask_create.go b/internal/database/ent/db/crontask_create.go index f9a3b4eb..085e2871 100644 --- a/internal/database/ent/db/crontask_create.go +++ b/internal/database/ent/db/crontask_create.go @@ -85,6 +85,12 @@ func (ctc *CronTaskCreate) SetLastRun(t time.Time) *CronTaskCreate { return ctc } +// SetTriesLeft sets the "tries_left" field. +func (ctc *CronTaskCreate) SetTriesLeft(i int) *CronTaskCreate { + ctc.mutation.SetTriesLeft(i) + return ctc +} + // SetLogs sets the "logs" field. func (ctc *CronTaskCreate) SetLogs(ml []models.TaskLog) *CronTaskCreate { ctc.mutation.SetLogs(ml) @@ -201,6 +207,9 @@ func (ctc *CronTaskCreate) check() error { if _, ok := ctc.mutation.LastRun(); !ok { return &ValidationError{Name: "last_run", err: errors.New(`db: missing required field "CronTask.last_run"`)} } + if _, ok := ctc.mutation.TriesLeft(); !ok { + return &ValidationError{Name: "tries_left", err: errors.New(`db: missing required field "CronTask.tries_left"`)} + } if _, ok := ctc.mutation.Logs(); !ok { return &ValidationError{Name: "logs", err: errors.New(`db: missing required field "CronTask.logs"`)} } @@ -274,6 +283,10 @@ func (ctc *CronTaskCreate) createSpec() (*CronTask, *sqlgraph.CreateSpec) { _spec.SetField(crontask.FieldLastRun, field.TypeTime, value) _node.LastRun = value } + if value, ok := ctc.mutation.TriesLeft(); ok { + _spec.SetField(crontask.FieldTriesLeft, field.TypeInt, value) + _node.TriesLeft = value + } if value, ok := ctc.mutation.Logs(); ok { _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) _node.Logs = value diff --git a/internal/database/ent/db/crontask_update.go b/internal/database/ent/db/crontask_update.go index df58b722..e448816b 100644 --- a/internal/database/ent/db/crontask_update.go +++ b/internal/database/ent/db/crontask_update.go @@ -119,6 +119,27 @@ func (ctu *CronTaskUpdate) SetNillableLastRun(t *time.Time) *CronTaskUpdate { return ctu } +// SetTriesLeft sets the "tries_left" field. +func (ctu *CronTaskUpdate) SetTriesLeft(i int) *CronTaskUpdate { + ctu.mutation.ResetTriesLeft() + ctu.mutation.SetTriesLeft(i) + return ctu +} + +// SetNillableTriesLeft sets the "tries_left" field if the given value is not nil. +func (ctu *CronTaskUpdate) SetNillableTriesLeft(i *int) *CronTaskUpdate { + if i != nil { + ctu.SetTriesLeft(*i) + } + return ctu +} + +// AddTriesLeft adds i to the "tries_left" field. +func (ctu *CronTaskUpdate) AddTriesLeft(i int) *CronTaskUpdate { + ctu.mutation.AddTriesLeft(i) + return ctu +} + // SetLogs sets the "logs" field. func (ctu *CronTaskUpdate) SetLogs(ml []models.TaskLog) *CronTaskUpdate { ctu.mutation.SetLogs(ml) @@ -242,6 +263,12 @@ func (ctu *CronTaskUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := ctu.mutation.LastRun(); ok { _spec.SetField(crontask.FieldLastRun, field.TypeTime, value) } + if value, ok := ctu.mutation.TriesLeft(); ok { + _spec.SetField(crontask.FieldTriesLeft, field.TypeInt, value) + } + if value, ok := ctu.mutation.AddedTriesLeft(); ok { + _spec.AddField(crontask.FieldTriesLeft, field.TypeInt, value) + } if value, ok := ctu.mutation.Logs(); ok { _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) } @@ -363,6 +390,27 @@ func (ctuo *CronTaskUpdateOne) SetNillableLastRun(t *time.Time) *CronTaskUpdateO return ctuo } +// SetTriesLeft sets the "tries_left" field. +func (ctuo *CronTaskUpdateOne) SetTriesLeft(i int) *CronTaskUpdateOne { + ctuo.mutation.ResetTriesLeft() + ctuo.mutation.SetTriesLeft(i) + return ctuo +} + +// SetNillableTriesLeft sets the "tries_left" field if the given value is not nil. +func (ctuo *CronTaskUpdateOne) SetNillableTriesLeft(i *int) *CronTaskUpdateOne { + if i != nil { + ctuo.SetTriesLeft(*i) + } + return ctuo +} + +// AddTriesLeft adds i to the "tries_left" field. +func (ctuo *CronTaskUpdateOne) AddTriesLeft(i int) *CronTaskUpdateOne { + ctuo.mutation.AddTriesLeft(i) + return ctuo +} + // SetLogs sets the "logs" field. func (ctuo *CronTaskUpdateOne) SetLogs(ml []models.TaskLog) *CronTaskUpdateOne { ctuo.mutation.SetLogs(ml) @@ -516,6 +564,12 @@ func (ctuo *CronTaskUpdateOne) sqlSave(ctx context.Context) (_node *CronTask, er if value, ok := ctuo.mutation.LastRun(); ok { _spec.SetField(crontask.FieldLastRun, field.TypeTime, value) } + if value, ok := ctuo.mutation.TriesLeft(); ok { + _spec.SetField(crontask.FieldTriesLeft, field.TypeInt, value) + } + if value, ok := ctuo.mutation.AddedTriesLeft(); ok { + _spec.AddField(crontask.FieldTriesLeft, field.TypeInt, value) + } if value, ok := ctuo.mutation.Logs(); ok { _spec.SetField(crontask.FieldLogs, field.TypeJSON, value) } diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index 15f56785..82bd0479 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -262,6 +262,7 @@ var ( {Name: "status", Type: field.TypeEnum, Enums: []string{"TASK_SCHEDULED", "TASK_IN_PROGRESS", "TASK_COMPLETE", "TASK_FAILED"}}, {Name: "scheduled_after", Type: field.TypeTime}, {Name: "last_run", Type: field.TypeTime}, + {Name: "tries_left", Type: field.TypeInt}, {Name: "logs", Type: field.TypeJSON}, {Name: "data", Type: field.TypeJSON}, } diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go index 586c843d..024970b4 100644 --- a/internal/database/ent/db/mutation.go +++ b/internal/database/ent/db/mutation.go @@ -4702,6 +4702,8 @@ type CronTaskMutation struct { status *models.TaskStatus scheduled_after *time.Time last_run *time.Time + tries_left *int + addtries_left *int logs *[]models.TaskLog appendlogs []models.TaskLog data *map[string]string @@ -5118,6 +5120,62 @@ func (m *CronTaskMutation) ResetLastRun() { m.last_run = nil } +// SetTriesLeft sets the "tries_left" field. +func (m *CronTaskMutation) SetTriesLeft(i int) { + m.tries_left = &i + m.addtries_left = nil +} + +// TriesLeft returns the value of the "tries_left" field in the mutation. +func (m *CronTaskMutation) TriesLeft() (r int, exists bool) { + v := m.tries_left + if v == nil { + return + } + return *v, true +} + +// OldTriesLeft returns the old "tries_left" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldTriesLeft(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTriesLeft is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTriesLeft requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTriesLeft: %w", err) + } + return oldValue.TriesLeft, nil +} + +// AddTriesLeft adds i to the "tries_left" field. +func (m *CronTaskMutation) AddTriesLeft(i int) { + if m.addtries_left != nil { + *m.addtries_left += i + } else { + m.addtries_left = &i + } +} + +// AddedTriesLeft returns the value that was added to the "tries_left" field in this mutation. +func (m *CronTaskMutation) AddedTriesLeft() (r int, exists bool) { + v := m.addtries_left + if v == nil { + return + } + return *v, true +} + +// ResetTriesLeft resets all changes to the "tries_left" field. +func (m *CronTaskMutation) ResetTriesLeft() { + m.tries_left = nil + m.addtries_left = nil +} + // SetLogs sets the "logs" field. func (m *CronTaskMutation) SetLogs(ml []models.TaskLog) { m.logs = &ml @@ -5239,7 +5297,7 @@ func (m *CronTaskMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *CronTaskMutation) Fields() []string { - fields := make([]string, 0, 10) + fields := make([]string, 0, 11) if m.created_at != nil { fields = append(fields, crontask.FieldCreatedAt) } @@ -5264,6 +5322,9 @@ func (m *CronTaskMutation) Fields() []string { if m.last_run != nil { fields = append(fields, crontask.FieldLastRun) } + if m.tries_left != nil { + fields = append(fields, crontask.FieldTriesLeft) + } if m.logs != nil { fields = append(fields, crontask.FieldLogs) } @@ -5294,6 +5355,8 @@ func (m *CronTaskMutation) Field(name string) (ent.Value, bool) { return m.ScheduledAfter() case crontask.FieldLastRun: return m.LastRun() + case crontask.FieldTriesLeft: + return m.TriesLeft() case crontask.FieldLogs: return m.Logs() case crontask.FieldData: @@ -5323,6 +5386,8 @@ func (m *CronTaskMutation) OldField(ctx context.Context, name string) (ent.Value return m.OldScheduledAfter(ctx) case crontask.FieldLastRun: return m.OldLastRun(ctx) + case crontask.FieldTriesLeft: + return m.OldTriesLeft(ctx) case crontask.FieldLogs: return m.OldLogs(ctx) case crontask.FieldData: @@ -5392,6 +5457,13 @@ func (m *CronTaskMutation) SetField(name string, value ent.Value) error { } m.SetLastRun(v) return nil + case crontask.FieldTriesLeft: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTriesLeft(v) + return nil case crontask.FieldLogs: v, ok := value.([]models.TaskLog) if !ok { @@ -5413,13 +5485,21 @@ func (m *CronTaskMutation) SetField(name string, value ent.Value) error { // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *CronTaskMutation) AddedFields() []string { - return nil + var fields []string + if m.addtries_left != nil { + fields = append(fields, crontask.FieldTriesLeft) + } + return fields } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *CronTaskMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case crontask.FieldTriesLeft: + return m.AddedTriesLeft() + } return nil, false } @@ -5428,6 +5508,13 @@ func (m *CronTaskMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *CronTaskMutation) AddField(name string, value ent.Value) error { switch name { + case crontask.FieldTriesLeft: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddTriesLeft(v) + return nil } return fmt.Errorf("unknown CronTask numeric field %s", name) } @@ -5479,6 +5566,9 @@ func (m *CronTaskMutation) ResetField(name string) error { case crontask.FieldLastRun: m.ResetLastRun() return nil + case crontask.FieldTriesLeft: + m.ResetTriesLeft() + return nil case crontask.FieldLogs: m.ResetLogs() return nil diff --git a/internal/database/ent/migrations/20240627200608.sql b/internal/database/ent/migrations/20240627200608.sql new file mode 100644 index 00000000..e61ae19b --- /dev/null +++ b/internal/database/ent/migrations/20240627200608.sql @@ -0,0 +1,2 @@ +-- Add column "tries_left" to table: "cron_tasks" +ALTER TABLE `cron_tasks` ADD COLUMN `tries_left` integer NOT NULL; diff --git a/internal/database/ent/migrations/atlas.sum b/internal/database/ent/migrations/atlas.sum index 078d2ca9..c8ae9d2d 100644 --- a/internal/database/ent/migrations/atlas.sum +++ b/internal/database/ent/migrations/atlas.sum @@ -1,2 +1,3 @@ -h1:xMoifuEeyeG1Wp9DhNcR2c+9QM7POMSSmax2AhvV6Z0= +h1:D1oLv8vS98PlDAuqVaSups+M9YQhXif84Hs7QmNKe1A= 20240627171532.sql h1:480JhjRCpyyix192z/L92yEI7LSsgJhuiK0ljat55TI= +20240627200608.sql h1:S0XjBX2P92O6vWPa/g5FRSp06Oo73opQttFzN3eoDQA= diff --git a/internal/database/ent/schema/cron_task.go b/internal/database/ent/schema/cron_task.go index 395749d9..dafa5b90 100644 --- a/internal/database/ent/schema/cron_task.go +++ b/internal/database/ent/schema/cron_task.go @@ -24,6 +24,7 @@ func (CronTask) Fields() []ent.Field { GoType(models.TaskStatus("")), field.Time("scheduled_after"), field.Time("last_run"), + field.Int("tries_left"), // field.JSON("logs", []models.TaskLog{}), field.JSON("data", map[string]string{}), diff --git a/internal/database/models/task.go b/internal/database/models/task.go index e81ef137..9d3faca3 100644 --- a/internal/database/models/task.go +++ b/internal/database/models/task.go @@ -68,6 +68,7 @@ type Task struct { Status TaskStatus `json:"status"` ScheduledAfter time.Time `json:"scheduledAfter"` + TriesLeft int `json:triesLeft` LastRun time.Time `json:"lastRun"` Data map[string]string `json:"data"` diff --git a/internal/database/tasks.go b/internal/database/tasks.go index 063334c7..9698dd8e 100644 --- a/internal/database/tasks.go +++ b/internal/database/tasks.go @@ -84,7 +84,11 @@ func (c *client) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task var tasks []models.Task return tasks, c.withTx(ctx, func(tx *db.Tx) error { - records, err := tx.CronTask.Query().Where(crontask.StatusEQ(models.TaskStatusScheduled), crontask.ScheduledAfterLT(time.Now())).Order(crontask.ByScheduledAfter(sql.OrderAsc())).Limit(limit).All(ctx) + records, err := tx.CronTask.Query().Where( + crontask.TriesLeftGT(0), + crontask.ScheduledAfterLT(time.Now()), + crontask.StatusEQ(models.TaskStatusScheduled), + ).Order(crontask.ByScheduledAfter(sql.OrderAsc())).Limit(limit).All(ctx) if err != nil { return err } @@ -157,6 +161,7 @@ func (c *client) CreateTasks(ctx context.Context, tasks ...models.Task) error { SetStatus(t.Status). SetTargets(t.Targets). SetLastRun(t.LastRun). + SetTriesLeft(t.TriesLeft). SetReferenceID(t.ReferenceID). SetScheduledAfter(t.ScheduledAfter), ) From 3c981a74654c9d6a5a197a216b294dd8ff1698b0 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 16:10:26 -0400 Subject: [PATCH 200/341] added default value --- .../ent/migrations/20240627200608.sql | 2 -- .../ent/migrations/20240627200953.sql | 22 +++++++++++++++++++ internal/database/ent/migrations/atlas.sum | 4 ++-- internal/database/ent/schema/cron_task.go | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) delete mode 100644 internal/database/ent/migrations/20240627200608.sql create mode 100644 internal/database/ent/migrations/20240627200953.sql diff --git a/internal/database/ent/migrations/20240627200608.sql b/internal/database/ent/migrations/20240627200608.sql deleted file mode 100644 index e61ae19b..00000000 --- a/internal/database/ent/migrations/20240627200608.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Add column "tries_left" to table: "cron_tasks" -ALTER TABLE `cron_tasks` ADD COLUMN `tries_left` integer NOT NULL; diff --git a/internal/database/ent/migrations/20240627200953.sql b/internal/database/ent/migrations/20240627200953.sql new file mode 100644 index 00000000..be677edd --- /dev/null +++ b/internal/database/ent/migrations/20240627200953.sql @@ -0,0 +1,22 @@ +-- Disable the enforcement of foreign-keys constraints +PRAGMA foreign_keys = off; +-- Create "new_cron_tasks" table +CREATE TABLE `new_cron_tasks` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `targets` json NOT NULL, `status` text NOT NULL, `scheduled_after` datetime NOT NULL, `last_run` datetime NOT NULL, `tries_left` integer NOT NULL DEFAULT (0), `logs` json NOT NULL, `data` json NOT NULL, PRIMARY KEY (`id`)); +-- Copy rows from old table "cron_tasks" to new temporary table "new_cron_tasks" +INSERT INTO `new_cron_tasks` (`id`, `created_at`, `updated_at`, `type`, `reference_id`, `targets`, `status`, `scheduled_after`, `last_run`, `logs`, `data`) SELECT `id`, `created_at`, `updated_at`, `type`, `reference_id`, `targets`, `status`, `scheduled_after`, `last_run`, `logs`, `data` FROM `cron_tasks`; +-- Drop "cron_tasks" table after copying rows +DROP TABLE `cron_tasks`; +-- Rename temporary table "new_cron_tasks" to "cron_tasks" +ALTER TABLE `new_cron_tasks` RENAME TO `cron_tasks`; +-- Create index "crontask_id" to table: "cron_tasks" +CREATE INDEX `crontask_id` ON `cron_tasks` (`id`); +-- Create index "crontask_reference_id" to table: "cron_tasks" +CREATE INDEX `crontask_reference_id` ON `cron_tasks` (`reference_id`); +-- Create index "crontask_status_last_run" to table: "cron_tasks" +CREATE INDEX `crontask_status_last_run` ON `cron_tasks` (`status`, `last_run`); +-- Create index "crontask_status_created_at" to table: "cron_tasks" +CREATE INDEX `crontask_status_created_at` ON `cron_tasks` (`status`, `created_at`); +-- Create index "crontask_status_scheduled_after" to table: "cron_tasks" +CREATE INDEX `crontask_status_scheduled_after` ON `cron_tasks` (`status`, `scheduled_after`); +-- Enable back the enforcement of foreign-keys constraints +PRAGMA foreign_keys = on; diff --git a/internal/database/ent/migrations/atlas.sum b/internal/database/ent/migrations/atlas.sum index c8ae9d2d..2251e9e6 100644 --- a/internal/database/ent/migrations/atlas.sum +++ b/internal/database/ent/migrations/atlas.sum @@ -1,3 +1,3 @@ -h1:D1oLv8vS98PlDAuqVaSups+M9YQhXif84Hs7QmNKe1A= +h1:ZYxvqqhpfA/QIZSU/etxpGhuHYFfgwF3/i9HL04YT0E= 20240627171532.sql h1:480JhjRCpyyix192z/L92yEI7LSsgJhuiK0ljat55TI= -20240627200608.sql h1:S0XjBX2P92O6vWPa/g5FRSp06Oo73opQttFzN3eoDQA= +20240627200953.sql h1:VUQZd1HQrxITMUtNO9HRtnDE8wgtWgRyzduACb3D3u4= diff --git a/internal/database/ent/schema/cron_task.go b/internal/database/ent/schema/cron_task.go index dafa5b90..d5209573 100644 --- a/internal/database/ent/schema/cron_task.go +++ b/internal/database/ent/schema/cron_task.go @@ -24,7 +24,7 @@ func (CronTask) Fields() []ent.Field { GoType(models.TaskStatus("")), field.Time("scheduled_after"), field.Time("last_run"), - field.Int("tries_left"), + field.Int("tries_left").Default(0), // field.JSON("logs", []models.TaskLog{}), field.JSON("data", map[string]string{}), From 37af5202fa1b36d38ffa7f7ca638d1df281d4f56 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 16:17:14 -0400 Subject: [PATCH 201/341] typo --- internal/database/models/task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/database/models/task.go b/internal/database/models/task.go index 9d3faca3..9ded8cd4 100644 --- a/internal/database/models/task.go +++ b/internal/database/models/task.go @@ -68,7 +68,7 @@ type Task struct { Status TaskStatus `json:"status"` ScheduledAfter time.Time `json:"scheduledAfter"` - TriesLeft int `json:triesLeft` + TriesLeft int `json:"triesLeft"` LastRun time.Time `json:"lastRun"` Data map[string]string `json:"data"` From df5f55bcb87e4dabfa7e785a8eef50d1f35fbf4b Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 16:19:45 -0400 Subject: [PATCH 202/341] added tries for new tasks --- cmd/core/tasks/cleanup.go | 1 + cmd/core/tasks/sessions.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/core/tasks/cleanup.go b/cmd/core/tasks/cleanup.go index b3836c56..9a2d8871 100644 --- a/cmd/core/tasks/cleanup.go +++ b/cmd/core/tasks/cleanup.go @@ -61,6 +61,7 @@ func CreateCleanupTasks(client core.Client) error { now := time.Now() task := models.Task{ + TriesLeft: 1, Type: models.TaskTypeDatabaseCleanup, ReferenceID: "database_cleanup", ScheduledAfter: now, diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index 21db9fc0..a009f0d8 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -74,12 +74,12 @@ func init() { func CreateRecordSnapshotsTasks(client core.Client, realm string) error { realm = strings.ToUpper(realm) task := models.Task{ + TriesLeft: 3, Type: models.TaskTypeRecordSnapshots, ReferenceID: "realm_" + realm, ScheduledAfter: time.Now(), Data: map[string]string{ - "realm": realm, - "triesLeft": "3", + "realm": realm, }, } From 0ec84885aa7f5e79914eb47c078a625ada684dcd Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 16:26:33 -0400 Subject: [PATCH 203/341] removed old checks --- cmd/core/tasks/cleanup.go | 7 ------- cmd/core/tasks/sessions.go | 19 +------------------ 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/cmd/core/tasks/cleanup.go b/cmd/core/tasks/cleanup.go index 9a2d8871..3d1524dc 100644 --- a/cmd/core/tasks/cleanup.go +++ b/cmd/core/tasks/cleanup.go @@ -14,14 +14,9 @@ import ( func init() { defaultHandlers[models.TaskTypeDatabaseCleanup] = TaskHandler{ Process: func(ctx context.Context, client core.Client, task *models.Task) error { - if task.Data == nil { - task.Data["triesLeft"] = "0" // do not retry - return errors.New("no data provided") - } { taskExpiration, err := time.Parse(time.RFC3339, task.Data["expiration_tasks"]) if err != nil { - task.Data["triesLeft"] = "0" // do not retry return errors.Wrap(err, "failed to parse expiration_tasks to time") } err = client.Database().DeleteExpiredTasks(ctx, taskExpiration) @@ -32,7 +27,6 @@ func init() { { interactionExpiration, err := time.Parse(time.RFC3339, task.Data["expiration_interactions"]) if err != nil { - task.Data["triesLeft"] = "0" // do not retry return errors.Wrap(err, "failed to parse interactionExpiration to time") } err = client.Database().DeleteExpiredInteractions(ctx, interactionExpiration) @@ -43,7 +37,6 @@ func init() { { snapshotExpiration, err := time.Parse(time.RFC3339, task.Data["expiration_snapshots"]) if err != nil { - task.Data["triesLeft"] = "0" // do not retry return errors.Wrap(err, "failed to parse expiration_snapshots to time") } err = client.Database().DeleteExpiredSnapshots(ctx, snapshotExpiration) diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index a009f0d8..7e459ff2 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -2,7 +2,6 @@ package tasks import ( "context" - "strconv" "strings" "time" @@ -17,35 +16,19 @@ import ( func init() { defaultHandlers[models.TaskTypeRecordSnapshots] = TaskHandler{ Process: func(ctx context.Context, client core.Client, task *models.Task) error { - if task.Data == nil { - return errors.New("no data provided") - } - - triesLeft, err := strconv.Atoi(task.Data["triesLeft"]) - if err != nil { - return errors.Wrap(err, "failed to parse tries left") - } - task.Data["triesLeft"] = strconv.Itoa(triesLeft - 1) - + forceUpdate := task.Data["force"] == "true" realm, ok := task.Data["realm"] if !ok { - task.Data["triesLeft"] = "0" // do not retry return errors.New("invalid realm") } - if len(task.Targets) > 100 { - task.Data["triesLeft"] = "0" // do not retry return errors.New("invalid targets length") } if len(task.Targets) < 1 { - task.Data["triesLeft"] = "0" // do not retry return errors.New("invalid targets length") } - forceUpdate := task.Data["force"] == "true" - log.Debug().Str("taskId", task.ID).Any("targets", task.Targets).Msg("started working on a session refresh task") - accountErrors, err := logic.RecordAccountSnapshots(ctx, client.Wargaming(), client.Database(), realm, forceUpdate, task.Targets...) if err != nil { return err From d5a66b33f0be2671a70c53bb807d4c78faca21f6 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 16:27:16 -0400 Subject: [PATCH 204/341] pretty --- cmd/core/tasks/cleanup.go | 4 ++-- cmd/core/tasks/sessions.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/core/tasks/cleanup.go b/cmd/core/tasks/cleanup.go index 3d1524dc..5f2c5fc7 100644 --- a/cmd/core/tasks/cleanup.go +++ b/cmd/core/tasks/cleanup.go @@ -55,9 +55,9 @@ func CreateCleanupTasks(client core.Client) error { task := models.Task{ TriesLeft: 1, - Type: models.TaskTypeDatabaseCleanup, - ReferenceID: "database_cleanup", ScheduledAfter: now, + ReferenceID: "database_cleanup", + Type: models.TaskTypeDatabaseCleanup, Data: map[string]string{ "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90).Format(time.RFC3339), // 90 days "expiration_interactions": now.Add(-1 * time.Hour * 24 * 7).Format(time.RFC3339), // 7 days diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index 7e459ff2..fd324e69 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -57,13 +57,13 @@ func init() { func CreateRecordSnapshotsTasks(client core.Client, realm string) error { realm = strings.ToUpper(realm) task := models.Task{ - TriesLeft: 3, Type: models.TaskTypeRecordSnapshots, ReferenceID: "realm_" + realm, ScheduledAfter: time.Now(), Data: map[string]string{ "realm": realm, }, + TriesLeft: 3, } ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) From e6ba90c15a37c648fff8f93b35851a9a8a769b46 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 16:36:30 -0400 Subject: [PATCH 205/341] retrying on some accounts failed --- cmd/core/tasks/sessions.go | 5 ++++- internal/logic/snapshots.go | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index fd324e69..b0b7c844 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -47,8 +47,11 @@ func init() { Comment: id, }) } - task.Targets = newTargets + if len(newTargets) > 0 { + task.Targets = newTargets + return errors.New("some accounts failed") + } return nil }, } diff --git a/internal/logic/snapshots.go b/internal/logic/snapshots.go index f3a827b3..0349fe75 100644 --- a/internal/logic/snapshots.go +++ b/internal/logic/snapshots.go @@ -56,7 +56,10 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl }(id) continue } - if s, ok := existingSnapshotsMap[id]; !force && ok && data.LastBattleTime == int(s.LastBattleTime.Unix()) { + if data.LastBattleTime < 1 { + continue + } + if s, ok := existingSnapshotsMap[id]; !force && (ok && data.LastBattleTime == int(s.LastBattleTime.Unix())) { // last snapshot is the same, we can skip it continue } @@ -132,13 +135,13 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } vehicleSnapshots[stats.Account.ID] = append(vehicleSnapshots[stats.Account.ID], models.VehicleSnapshot{ - CreatedAt: createdAt, Type: models.SnapshotTypeDaily, LastBattleTime: vehicle.LastBattleTime, - AccountID: stats.Account.ID, + Stats: *vehicle.StatsFrame, VehicleID: vehicle.VehicleID, + AccountID: stats.Account.ID, ReferenceID: stats.Account.ID, - Stats: *vehicle.StatsFrame, + CreatedAt: createdAt, }) } } From 04ee807473b2d574516d635686f4b79da7c6c401 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 16:49:14 -0400 Subject: [PATCH 206/341] saving snapshots without vehicles (rating battles) --- cmd/core/tasks/sessions.go | 2 +- internal/logic/snapshots.go | 27 ++++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/sessions.go index b0b7c844..7a02dea7 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/sessions.go @@ -34,7 +34,7 @@ func init() { return err } - // Retry failed accounts + // retry failed accounts newTargets := make([]string, 0, len(accountErrors)) for id, err := range accountErrors { if err == nil { diff --git a/internal/logic/snapshots.go b/internal/logic/snapshots.go index 0349fe75..314a4224 100644 --- a/internal/logic/snapshots.go +++ b/internal/logic/snapshots.go @@ -148,21 +148,18 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } for _, accountSnapshot := range accountSnapshots { - vehicles, ok := vehicleSnapshots[accountSnapshot.AccountID] - if !ok { - accountErrors[accountSnapshot.AccountID] = errors.New("account missing vehicle snapshots") - continue - } - - // save all vehicle snapshots - vErr, err := dbClient.CreateAccountVehicleSnapshots(ctx, accountSnapshot.AccountID, vehicles...) - if err != nil { - accountErrors[accountSnapshot.AccountID] = err - continue - } - if len(vErr) > 0 { - accountErrors[accountSnapshot.AccountID] = errors.Errorf("failed to insert %d vehicle snapshots", len(vErr)) - continue + vehicles := vehicleSnapshots[accountSnapshot.AccountID] + if len(vehicles) > 0 { + // save all vehicle snapshots) + vErr, err := dbClient.CreateAccountVehicleSnapshots(ctx, accountSnapshot.AccountID, vehicles...) + if err != nil { + accountErrors[accountSnapshot.AccountID] = err + continue + } + if len(vErr) > 0 { + accountErrors[accountSnapshot.AccountID] = errors.Errorf("failed to insert %d vehicle snapshots", len(vErr)) + continue + } } // save account snapshot From c06a7b83f44a02fbcd32e8583c9b725e78f81334 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 16:50:53 -0400 Subject: [PATCH 207/341] better snapshot compare --- internal/logic/snapshots.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/logic/snapshots.go b/internal/logic/snapshots.go index 314a4224..73ed394b 100644 --- a/internal/logic/snapshots.go +++ b/internal/logic/snapshots.go @@ -129,7 +129,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } vehicleStats := fetch.WargamingVehiclesToFrame(vehicles) for id, vehicle := range vehicleStats { - if s, ok := existingSnapshotsMap[id]; !force && ok && s.Stats.Battles == vehicle.Battles { + if s, ok := existingSnapshotsMap[id]; !force && ok && s.LastBattleTime.Equal(vehicle.LastBattleTime) { // last snapshot is the same, we can skip it continue } From 8bcdfb48a890dcbb31f61f6f075ac72d23b3c660 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 17:03:56 -0400 Subject: [PATCH 208/341] minor UI tweaks --- internal/database/models/vehicle.go | 8 ++++++-- internal/stats/render/session/v1/cards.go | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/internal/database/models/vehicle.go b/internal/database/models/vehicle.go index 53787d30..4db8346e 100644 --- a/internal/database/models/vehicle.go +++ b/internal/database/models/vehicle.go @@ -1,6 +1,10 @@ package models -import "golang.org/x/text/language" +import ( + "fmt" + + "golang.org/x/text/language" +) type Vehicle struct { ID string @@ -15,5 +19,5 @@ func (v Vehicle) Name(locale language.Tag) string { if n := v.LocalizedNames[language.English.String()]; n != "" { return n } - return "Secret Tank" + return fmt.Sprintf("Secret Tank %s", v.ID) } diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index 72afa921..e089214f 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -17,14 +17,14 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card renderUnratedVehiclesCount = 3 // minimum number of vehicle cards // primary cards // when there are some unrated battles or no battles at all - shouldRenderUnratedOverview = session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 + shouldRenderUnratedOverview = session.RegularBattles.Battles > 0 || session.RatingBattles.Battles < 1 // when there are 3 vehicle cards and no rating overview cards or there are 6 vehicle cards and some rating battles shouldRenderUnratedHighlights = (session.RegularBattles.Battles > 0 && session.RatingBattles.Battles < 1 && len(cards.Unrated.Vehicles) > renderUnratedVehiclesCount) || - (session.RegularBattles.Battles > 0 && len(cards.Unrated.Vehicles) > 6) + (session.RegularBattles.Battles > 0 && len(cards.Unrated.Vehicles) > 3) shouldRenderRatingOverview = session.RatingBattles.Battles > 0 - shouldRenderRatingVehicles = len(cards.Unrated.Vehicles) == 0 + shouldRenderRatingVehicles = session.RatingBattles.Battles > 0 && len(cards.Rating.Vehicles) > 0 && len(cards.Unrated.Vehicles) < 1 // secondary cards - shouldRenderUnratedVehicles = len(cards.Unrated.Vehicles) > 0 + shouldRenderUnratedVehicles = session.RegularBattles.Battles > 0 && len(cards.Unrated.Vehicles) > 0 ) // try to make the columns height roughly similar to primary column From 6143f01c07c03000b9b217db5695174425ac976d Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 20:46:45 -0400 Subject: [PATCH 209/341] added testing client for db and fetch --- internal/database/client.go | 4 +- internal/database/users.go | 12 +- internal/stats/fetch/v1/client.go | 10 +- internal/stats/fetch/v1/multisource.go | 6 +- internal/stats/render/session/v1/cards.go | 20 +-- render_test.go | 11 +- tests/static_data.go | 109 +++++++++++++++ tests/static_database.go | 159 ++++++++++++++++++++++ tests/static_fetch.go | 93 +++++++++++++ 9 files changed, 392 insertions(+), 32 deletions(-) create mode 100644 tests/static_data.go create mode 100644 tests/static_database.go create mode 100644 tests/static_fetch.go diff --git a/internal/database/client.go b/internal/database/client.go index 7a99931a..d192ca4b 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -35,8 +35,8 @@ type GlossaryClient interface { } type UsersClient interface { - GetUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) - GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) + GetUserByID(ctx context.Context, id string, opts ...UserGetOption) (models.User, error) + GetOrCreateUserByID(ctx context.Context, id string, opts ...UserGetOption) (models.User, error) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) diff --git a/internal/database/users.go b/internal/database/users.go index c31d423a..e172ae0b 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -69,19 +69,19 @@ type userGetOpts struct { subscriptions bool } -type userGetOption func(*userGetOpts) +type UserGetOption func(*userGetOpts) -func WithConnections() userGetOption { +func WithConnections() UserGetOption { return func(ugo *userGetOpts) { ugo.connections = true } } -func WithSubscriptions() userGetOption { +func WithSubscriptions() UserGetOption { return func(ugo *userGetOpts) { ugo.subscriptions = true } } -func WithContent() userGetOption { +func WithContent() UserGetOption { return func(ugo *userGetOpts) { ugo.content = true } @@ -91,7 +91,7 @@ func WithContent() userGetOption { Gets or creates a user with specified ID - assumes the ID is valid */ -func (c *client) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { +func (c *client) GetOrCreateUserByID(ctx context.Context, id string, opts ...UserGetOption) (models.User, error) { user, err := c.GetUserByID(ctx, id, opts...) if err != nil && !IsNotFound(err) { return models.User{}, err @@ -115,7 +115,7 @@ func (c *client) GetOrCreateUserByID(ctx context.Context, id string, opts ...use Gets a user with specified ID - assumes the ID is valid */ -func (c *client) GetUserByID(ctx context.Context, id string, opts ...userGetOption) (models.User, error) { +func (c *client) GetUserByID(ctx context.Context, id string, opts ...UserGetOption) (models.User, error) { var options userGetOpts for _, apply := range opts { apply(&options) diff --git a/internal/stats/fetch/v1/client.go b/internal/stats/fetch/v1/client.go index f15e157b..8bcef83b 100644 --- a/internal/stats/fetch/v1/client.go +++ b/internal/stats/fetch/v1/client.go @@ -49,10 +49,10 @@ type StatsWithVehicles struct { type Client interface { Account(ctx context.Context, id string) (models.Account, error) Search(ctx context.Context, nickname, realm string) (types.Account, error) - CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) + CurrentStats(ctx context.Context, id string, opts ...StatsOption) (AccountStatsOverPeriod, error) - PeriodStats(ctx context.Context, id string, from time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) - SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, AccountStatsOverPeriod, error) + PeriodStats(ctx context.Context, id string, from time.Time, opts ...StatsOption) (AccountStatsOverPeriod, error) + SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...StatsOption) (AccountStatsOverPeriod, AccountStatsOverPeriod, error) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) } @@ -61,9 +61,9 @@ type statsOptions struct { withWN8 bool } -type statsOption func(*statsOptions) +type StatsOption func(*statsOptions) -func WithWN8() statsOption { +func WithWN8() StatsOption { return func(so *statsOptions) { so.withWN8 = true } diff --git a/internal/stats/fetch/v1/multisource.go b/internal/stats/fetch/v1/multisource.go index 0db25424..558b07a3 100644 --- a/internal/stats/fetch/v1/multisource.go +++ b/internal/stats/fetch/v1/multisource.go @@ -106,7 +106,7 @@ Get live account stats from wargaming - Each request will be retried c.retriesPerRequest times - This function will assume the player ID is valid and optimistically run all request concurrently, returning the first error once all request finish */ -func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) { +func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts ...StatsOption) (AccountStatsOverPeriod, error) { var options statsOptions for _, apply := range opts { apply(&options) @@ -187,7 +187,7 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. return stats, nil } -func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) { +func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodStart time.Time, opts ...StatsOption) (AccountStatsOverPeriod, error) { var options statsOptions for _, apply := range opts { apply(&options) @@ -271,7 +271,7 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt return stats, nil } -func (c *multiSourceClient) SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, AccountStatsOverPeriod, error) { +func (c *multiSourceClient) SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...StatsOption) (AccountStatsOverPeriod, AccountStatsOverPeriod, error) { var options statsOptions for _, apply := range opts { apply(&options) diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index e089214f..2ce92711 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -48,6 +48,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card highlightCardBlockSizes := make(map[string]float64) var primaryCardWidth float64 = minPrimaryCardWidth var secondaryCardWidth, totalFrameWidth float64 + var overviewColumnMaxWidth float64 // rating and unrated battles > 0 unrated battles > 0 rating battles > 0 // [title card ] | [vehicle] [title card ] | [vehicle] [title card ] // [overview unrated] | [vehicle] OR [overview unrated] | [vehicle] OR [overview rating ] @@ -73,6 +74,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) + overviewColumnMaxWidth = common.Max(overviewColumnMaxWidth, contentWidth) totalContentWidth += contentWidth for key, width := range presetBlockWidth { primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) @@ -88,6 +90,7 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) + overviewColumnMaxWidth = common.Max(overviewColumnMaxWidth, contentWidth) totalContentWidth += contentWidth for key, width := range presetBlockWidth { primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) @@ -196,10 +199,10 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // overview cards if shouldRenderUnratedOverview { - primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, overviewCardStyle(primaryCardWidth))) + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, overviewCardStyle(primaryCardWidth), overviewColumnMaxWidth)) } if shouldRenderRatingOverview { - primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, overviewRatingCardStyle(primaryCardWidth))) + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, overviewRatingCardStyle(primaryCardWidth), overviewColumnMaxWidth)) } // highlights @@ -387,31 +390,32 @@ func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string] return common.NewBlocksContent(containerStyle, common.NewBlocksContent(vehicleBlocksRowStyle(0), content...)) } -func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, style common.Style) common.Block { +func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, style common.Style, columnWidth float64) common.Block { // made all columns the same width for things to be centered - columnWidth := (style.Width - style.Gap*float64(len(card.Blocks)-1) - style.PaddingX*2) / float64(len(card.Blocks)) var content []common.Block // add a blank block to balance the offset added from icons blockStyle := vehicleBlockStyle() for _, column := range card.Blocks { var columnContent []common.Block for _, block := range column { var col common.Block + blockWidth := columnWidth // fit the block to column width to make things look even if block.Tag == prepare.TagWN8 || block.Tag == prepare.TagRankedRating { - col = makeSpecialRatingColumn(block, blockSizes[block.Tag.String()]) + blockWidth = blockSizes[block.Tag.String()] + col = makeSpecialRatingColumn(block, blockWidth) } else if blockShouldHaveCompareIcon(block) { - col = common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), + col = common.NewBlocksContent(statsBlockStyle(blockWidth), blockWithDoubleVehicleIcon(common.NewTextContent(blockStyle.session, block.Data.Session.String()), block.Data.Session, block.Data.Career), common.NewTextContent(blockStyle.label, block.Label), ) } else { - col = common.NewBlocksContent(statsBlockStyle(blockSizes[block.Tag.String()]), + col = common.NewBlocksContent(statsBlockStyle(blockWidth), common.NewTextContent(blockStyle.session, block.Data.Session.String()), common.NewTextContent(blockStyle.label, block.Label), ) } columnContent = append(columnContent, col) } - content = append(content, common.NewBlocksContent(overviewColumnStyle(columnWidth), columnContent...)) + content = append(content, common.NewBlocksContent(overviewColumnStyle(0), columnContent...)) } return common.NewBlocksContent(style, content...) } diff --git a/render_test.go b/render_test.go index 77756364..855dccf0 100644 --- a/render_test.go +++ b/render_test.go @@ -8,6 +8,7 @@ import ( "github.com/cufee/aftermath/internal/stats/render/common/v1" stats "github.com/cufee/aftermath/internal/stats/renderer/v1" + "github.com/cufee/aftermath/tests" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "golang.org/x/text/language" @@ -22,14 +23,8 @@ func TestRenderSession(t *testing.T) { loadStaticAssets(static) - db, err := newDatabaseClientFromEnv() - assert.NoError(t, err) - - coreClient, _ := coreClientsFromEnv(db) - defer coreClient.Database().Disconnect() - - renderer := stats.NewRenderer(coreClient.Fetch(), coreClient.Database(), coreClient.Wargaming(), language.English) - image, _, err := renderer.Session(context.Background(), "1013072123", time.Now(), common.WithBackground("")) + renderer := stats.NewRenderer(tests.StaticTestingFetch(), tests.StaticTestingDatabase(), nil, language.English) + image, _, err := renderer.Session(context.Background(), tests.DefaultAccountNA, time.Now(), common.WithBackground("")) assert.NoError(t, err, "failed to render a session image") assert.NotNil(t, image, "image is nil") diff --git a/tests/static_data.go b/tests/static_data.go new file mode 100644 index 00000000..36f37df5 --- /dev/null +++ b/tests/static_data.go @@ -0,0 +1,109 @@ +package tests + +import ( + "time" + + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/frame" +) + +const DefaultAccountNA = "account1" +const DefaultAccountEU = "account2" +const DefaultAccountAS = "account3" + +var staticAccounts = map[string]models.Account{ + DefaultAccountNA: {ID: DefaultAccountNA, Realm: "NA", Nickname: "@test_account_na_1", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan1", ClanTag: "TEST1"}, + DefaultAccountEU: {ID: DefaultAccountEU, Realm: "EU", Nickname: "@test_account_eu_1", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan2", ClanTag: "TEST2"}, + DefaultAccountAS: {ID: DefaultAccountAS, Realm: "AS", Nickname: "@test_account_as_1", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan3", ClanTag: "TEST3"}, +} + +var ( + DefaultStatsFrameBig1 = frame.StatsFrame{ + Battles: 100, + BattlesWon: 80, + BattlesSurvived: 70, + DamageDealt: 234567, + DamageReceived: 123567, + ShotsHit: 690, + ShotsFired: 1000, + Frags: 420, + MaxFrags: 50, + EnemiesSpotted: 240, + CapturePoints: 0, + DroppedCapturePoints: 0, + Rating: 100, + } + DefaultStatsFrameBig2 = frame.StatsFrame{ + Battles: 45, + BattlesWon: 34, + BattlesSurvived: 60, + DamageDealt: 234567 / 2, + DamageReceived: 123567 / 2, + ShotsHit: 690 / 2, + ShotsFired: 1000 / 2, + Frags: 420 / 2, + MaxFrags: 50 / 2, + EnemiesSpotted: 240 / 2, + CapturePoints: 0, + DroppedCapturePoints: 0, + Rating: 100, + } + DefaultStatsFrameSmall1 = frame.StatsFrame{ + Battles: 20, + BattlesWon: 16, + BattlesSurvived: 14, + DamageDealt: 23456 * 2, + DamageReceived: 12356 * 2, + ShotsHit: 69 * 2, + ShotsFired: 100 * 2, + Frags: 42 * 2, + MaxFrags: 5 * 2, + EnemiesSpotted: 24 * 2, + CapturePoints: 0, + DroppedCapturePoints: 0, + Rating: 100, + } + DefaultStatsFrameSmall2 = frame.StatsFrame{ + Battles: 10, + BattlesWon: 8, + BattlesSurvived: 7, + DamageDealt: 23456, + DamageReceived: 12356, + ShotsHit: 69, + ShotsFired: 100, + Frags: 42, + MaxFrags: 5, + EnemiesSpotted: 24, + CapturePoints: 0, + DroppedCapturePoints: 0, + Rating: 100, + } +) + +func DefaultVehicleStatsFrameBig1(id string) frame.VehicleStatsFrame { + return frame.VehicleStatsFrame{ + VehicleID: id, + StatsFrame: &DefaultStatsFrameBig1, + } +} + +func DefaultVehicleStatsFrameBig2(id string) frame.VehicleStatsFrame { + return frame.VehicleStatsFrame{ + VehicleID: id, + StatsFrame: &DefaultStatsFrameBig2, + } +} + +func DefaultVehicleStatsFrameSmall1(id string) frame.VehicleStatsFrame { + return frame.VehicleStatsFrame{ + VehicleID: id, + StatsFrame: &DefaultStatsFrameSmall1, + } +} + +func DefaultVehicleStatsFrameSmall2(id string) frame.VehicleStatsFrame { + return frame.VehicleStatsFrame{ + VehicleID: id, + StatsFrame: &DefaultStatsFrameSmall2, + } +} diff --git a/tests/static_database.go b/tests/static_database.go new file mode 100644 index 00000000..29fdfab6 --- /dev/null +++ b/tests/static_database.go @@ -0,0 +1,159 @@ +package tests + +import ( + "context" + "errors" + "time" + + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/permissions" + "github.com/cufee/aftermath/internal/stats/frame" +) + +var ErrNotFound = &db.NotFoundError{} + +var _ database.Client = &staticTestingDatabase{} + +type staticTestingDatabase struct{} + +func StaticTestingDatabase() *staticTestingDatabase { + return &staticTestingDatabase{} +} + +func (c *staticTestingDatabase) Disconnect() error { + return nil +} + +func (c *staticTestingDatabase) GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) { + var accounts []models.Account + for _, id := range ids { + if a, ok := staticAccounts[id]; ok { + accounts = append(accounts, a) + } + } + return accounts, nil +} +func (c *staticTestingDatabase) GetAccountByID(ctx context.Context, id string) (models.Account, error) { + if account, ok := staticAccounts[id]; ok { + return account, nil + } + return models.Account{}, ErrNotFound +} +func (c *staticTestingDatabase) GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) { + return nil, errors.New("GetRealmAccountIDs not implemented") +} +func (c *staticTestingDatabase) AccountSetPrivate(ctx context.Context, id string, value bool) error { + return errors.New("AccountSetPrivate not implemented") +} +func (c *staticTestingDatabase) UpsertAccounts(ctx context.Context, accounts []models.Account) (map[string]error, error) { + for _, acc := range accounts { + if account, ok := staticAccounts[acc.ID]; ok { + staticAccounts[acc.ID] = account + } + } + return nil, nil +} + +func (c *staticTestingDatabase) GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) { + vehicles := make(map[string]models.Vehicle) + for _, id := range ids { + vehicles[id] = models.Vehicle{ID: id, Tier: 10, LocalizedNames: map[string]string{"en": "Test Vehicle " + id}} + } + return vehicles, nil +} +func (c *staticTestingDatabase) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { + // TODO: get some kind of data in + return map[string]frame.StatsFrame{}, nil +} +func (c *staticTestingDatabase) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) (map[string]error, error) { + return nil, errors.New("UpsertVehicles not implemented") +} +func (c *staticTestingDatabase) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) (map[string]error, error) { + return nil, errors.New("UpsertVehicleAverages not implemented") +} + +func (c *staticTestingDatabase) GetUserByID(ctx context.Context, id string, opts ...database.UserGetOption) (models.User, error) { + return models.User{}, errors.New("GetUserByID not implemented") +} +func (c *staticTestingDatabase) GetOrCreateUserByID(ctx context.Context, id string, opts ...database.UserGetOption) (models.User, error) { + return models.User{}, errors.New("GetOrCreateUserByID not implemented") +} +func (c *staticTestingDatabase) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) { + return models.User{}, errors.New("UpsertUserWithPermissions not implemented") +} +func (c *staticTestingDatabase) UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { + return models.UserConnection{}, errors.New("UpdateConnection not implemented") +} +func (c *staticTestingDatabase) UpsertConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { + return models.UserConnection{}, errors.New("UpsertConnection not implemented") +} + +func (c *staticTestingDatabase) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...database.SnapshotQuery) (models.AccountSnapshot, error) { + return models.AccountSnapshot{}, errors.New("GetAccountSnapshot not implemented") +} +func (c *staticTestingDatabase) CreateAccountSnapshots(ctx context.Context, snapshots ...models.AccountSnapshot) (map[string]error, error) { + return nil, errors.New("CreateAccountSnapshots not implemented") +} +func (c *staticTestingDatabase) GetLastAccountSnapshots(ctx context.Context, accountID string, limit int) ([]models.AccountSnapshot, error) { + return nil, errors.New("GetLastAccountSnapshots not implemented") +} +func (c *staticTestingDatabase) GetManyAccountSnapshots(ctx context.Context, accountIDs []string, kind models.SnapshotType, options ...database.SnapshotQuery) ([]models.AccountSnapshot, error) { + return nil, errors.New("GetManyAccountSnapshots not implemented") +} +func (c *staticTestingDatabase) GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...database.SnapshotQuery) ([]models.VehicleSnapshot, error) { + return nil, errors.New("GetVehicleSnapshots not implemented") +} +func (c *staticTestingDatabase) CreateAccountVehicleSnapshots(ctx context.Context, accountID string, snapshots ...models.VehicleSnapshot) (map[string]error, error) { + return nil, errors.New("CreateAccountVehicleSnapshots not implemented") +} +func (c *staticTestingDatabase) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { + return errors.New("DeleteExpiredSnapshots not implemented") +} + +func (c *staticTestingDatabase) CreateTasks(ctx context.Context, tasks ...models.Task) error { + return errors.New("not implemented") +} +func (c *staticTestingDatabase) GetTasks(ctx context.Context, ids ...string) ([]models.Task, error) { + return nil, errors.New("CreateTasks not implemented") +} +func (c *staticTestingDatabase) UpdateTasks(ctx context.Context, tasks ...models.Task) error { + return errors.New("UpdateTasks not implemented") +} +func (c *staticTestingDatabase) DeleteTasks(ctx context.Context, ids ...string) error { + return errors.New("DeleteTasks not implemented") +} +func (c *staticTestingDatabase) AbandonTasks(ctx context.Context, ids ...string) error { + return errors.New("AbandonTasks not implemented") +} + +func (c *staticTestingDatabase) GetStaleTasks(ctx context.Context, limit int) ([]models.Task, error) { + return nil, errors.New("GetStaleTasks not implemented") +} +func (c *staticTestingDatabase) GetRecentTasks(ctx context.Context, createdAfter time.Time, status ...models.TaskStatus) ([]models.Task, error) { + return nil, errors.New("GetRecentTasks not implemented") +} +func (c *staticTestingDatabase) GetAndStartTasks(ctx context.Context, limit int) ([]models.Task, error) { + return nil, errors.New("GetAndStartTasks not implemented") +} +func (c *staticTestingDatabase) DeleteExpiredTasks(ctx context.Context, expiration time.Time) error { + return errors.New("DeleteExpiredTasks not implemented") +} + +func (c *staticTestingDatabase) UpsertCommands(ctx context.Context, commands ...models.ApplicationCommand) error { + return errors.New("UpsertCommands not implemented") +} +func (c *staticTestingDatabase) GetCommandsByID(ctx context.Context, commandIDs ...string) ([]models.ApplicationCommand, error) { + return nil, errors.New("GetCommandsByID not implemented") +} + +func (c *staticTestingDatabase) CreateDiscordInteraction(ctx context.Context, data models.DiscordInteraction) error { + return errors.New("CreateDiscordInteraction not implemented") +} +func (c *staticTestingDatabase) GetDiscordInteraction(ctx context.Context, referenceID string) (models.DiscordInteraction, error) { + return models.DiscordInteraction{}, errors.New("GetDiscordInteraction not implemented") +} +func (c *staticTestingDatabase) DeleteExpiredInteractions(ctx context.Context, expiration time.Time) error { + return errors.New("DeleteExpiredInteractions not implemented") +} diff --git a/tests/static_fetch.go b/tests/static_fetch.go new file mode 100644 index 00000000..41dfc92f --- /dev/null +++ b/tests/static_fetch.go @@ -0,0 +1,93 @@ +package tests + +import ( + "context" + "fmt" + "time" + + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + "github.com/cufee/aftermath/internal/stats/frame" + "github.com/cufee/am-wg-proxy-next/v2/types" + "github.com/pkg/errors" +) + +var _ fetch.Client = &staticTestingFetch{} + +type staticTestingFetch struct{} + +func StaticTestingFetch() *staticTestingFetch { + return &staticTestingFetch{} +} + +func (c *staticTestingFetch) Account(ctx context.Context, id string) (models.Account, error) { + if account, ok := staticAccounts[id]; ok { + return account, nil + } + return models.Account{}, errors.New("account not found") +} +func (c *staticTestingFetch) Search(ctx context.Context, nickname, realm string) (types.Account, error) { + return types.Account{}, nil +} +func (c *staticTestingFetch) CurrentStats(ctx context.Context, id string, opts ...fetch.StatsOption) (fetch.AccountStatsOverPeriod, error) { + account, err := c.Account(ctx, id) + if err != nil { + return fetch.AccountStatsOverPeriod{}, err + } + + var vehicles = make(map[string]frame.VehicleStatsFrame) + for id := range 10 { + vehicles[fmt.Sprint(id)] = DefaultVehicleStatsFrameBig1(fmt.Sprint(id)) + } + + return fetch.AccountStatsOverPeriod{ + Account: account, + Realm: account.Realm, + + PeriodEnd: time.Now(), + PeriodStart: time.Now().Add(time.Hour * 25 * 1), + LastBattleTime: time.Now(), + + RegularBattles: fetch.StatsWithVehicles{ + Vehicles: vehicles, + StatsFrame: DefaultStatsFrameBig1, + }, + RatingBattles: fetch.StatsWithVehicles{ + StatsFrame: DefaultStatsFrameBig2, + }, + }, nil +} + +func (c *staticTestingFetch) PeriodStats(ctx context.Context, id string, from time.Time, opts ...fetch.StatsOption) (fetch.AccountStatsOverPeriod, error) { + current, err := c.CurrentStats(ctx, id, opts...) + if err != nil { + return fetch.AccountStatsOverPeriod{}, err + } + + current.PeriodStart = from + current.RegularBattles.StatsFrame.Subtract(DefaultStatsFrameSmall1) + current.RatingBattles.StatsFrame.Subtract(DefaultStatsFrameSmall2) + + for id, stats := range current.RegularBattles.Vehicles { + stats.StatsFrame.Subtract(DefaultStatsFrameSmall1) + current.RegularBattles.Vehicles[id] = stats + } + return current, nil +} +func (c *staticTestingFetch) SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...fetch.StatsOption) (fetch.AccountStatsOverPeriod, fetch.AccountStatsOverPeriod, error) { + session, err := c.PeriodStats(ctx, id, sessionStart, opts...) + if err != nil { + return fetch.AccountStatsOverPeriod{}, fetch.AccountStatsOverPeriod{}, err + } + career, err := c.CurrentStats(ctx, id, opts...) + if err != nil { + return fetch.AccountStatsOverPeriod{}, fetch.AccountStatsOverPeriod{}, err + } + + return session, career, nil +} + +func (c *staticTestingFetch) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) { + // TODO: add some data + return map[string]frame.StatsFrame{}, nil +} From 7ef12ea50ee7f6611c7f9a03919134a512635108 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 20:49:17 -0400 Subject: [PATCH 210/341] fixed vehicle frames --- tests/static_data.go | 12 ++++++++---- tests/static_fetch.go | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/static_data.go b/tests/static_data.go index 36f37df5..2344548c 100644 --- a/tests/static_data.go +++ b/tests/static_data.go @@ -81,29 +81,33 @@ var ( ) func DefaultVehicleStatsFrameBig1(id string) frame.VehicleStatsFrame { + f := DefaultStatsFrameBig1 return frame.VehicleStatsFrame{ VehicleID: id, - StatsFrame: &DefaultStatsFrameBig1, + StatsFrame: &f, } } func DefaultVehicleStatsFrameBig2(id string) frame.VehicleStatsFrame { + f := DefaultStatsFrameBig2 return frame.VehicleStatsFrame{ VehicleID: id, - StatsFrame: &DefaultStatsFrameBig2, + StatsFrame: &f, } } func DefaultVehicleStatsFrameSmall1(id string) frame.VehicleStatsFrame { + f := DefaultStatsFrameSmall1 return frame.VehicleStatsFrame{ VehicleID: id, - StatsFrame: &DefaultStatsFrameSmall1, + StatsFrame: &f, } } func DefaultVehicleStatsFrameSmall2(id string) frame.VehicleStatsFrame { + f := DefaultStatsFrameSmall2 return frame.VehicleStatsFrame{ VehicleID: id, - StatsFrame: &DefaultStatsFrameSmall2, + StatsFrame: &f, } } diff --git a/tests/static_fetch.go b/tests/static_fetch.go index 41dfc92f..530cff6f 100644 --- a/tests/static_fetch.go +++ b/tests/static_fetch.go @@ -71,6 +71,7 @@ func (c *staticTestingFetch) PeriodStats(ctx context.Context, id string, from ti for id, stats := range current.RegularBattles.Vehicles { stats.StatsFrame.Subtract(DefaultStatsFrameSmall1) current.RegularBattles.Vehicles[id] = stats + fmt.Printf("%#v\n", stats.StatsFrame) } return current, nil } From a1c18aa52bcc0c9d53b60e31fa90628988825ade Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 20:52:46 -0400 Subject: [PATCH 211/341] added short nickname option --- render_test.go | 2 +- tests/static_data.go | 8 +++++--- tests/static_fetch.go | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/render_test.go b/render_test.go index 855dccf0..66b12f79 100644 --- a/render_test.go +++ b/render_test.go @@ -24,7 +24,7 @@ func TestRenderSession(t *testing.T) { loadStaticAssets(static) renderer := stats.NewRenderer(tests.StaticTestingFetch(), tests.StaticTestingDatabase(), nil, language.English) - image, _, err := renderer.Session(context.Background(), tests.DefaultAccountNA, time.Now(), common.WithBackground("")) + image, _, err := renderer.Session(context.Background(), tests.DefaultAccountNAShort, time.Now(), common.WithBackground("")) assert.NoError(t, err, "failed to render a session image") assert.NotNil(t, image, "image is nil") diff --git a/tests/static_data.go b/tests/static_data.go index 2344548c..93dcc80c 100644 --- a/tests/static_data.go +++ b/tests/static_data.go @@ -8,13 +8,15 @@ import ( ) const DefaultAccountNA = "account1" +const DefaultAccountNAShort = "account11" const DefaultAccountEU = "account2" const DefaultAccountAS = "account3" var staticAccounts = map[string]models.Account{ - DefaultAccountNA: {ID: DefaultAccountNA, Realm: "NA", Nickname: "@test_account_na_1", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan1", ClanTag: "TEST1"}, - DefaultAccountEU: {ID: DefaultAccountEU, Realm: "EU", Nickname: "@test_account_eu_1", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan2", ClanTag: "TEST2"}, - DefaultAccountAS: {ID: DefaultAccountAS, Realm: "AS", Nickname: "@test_account_as_1", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan3", ClanTag: "TEST3"}, + DefaultAccountNA: {ID: DefaultAccountNA, Realm: "NA", Nickname: "@test_account_na_1", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan1", ClanTag: "TEST1"}, + DefaultAccountNAShort: {ID: DefaultAccountNA, Realm: "NA", Nickname: "@test_acc", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan1", ClanTag: "TEST1"}, + DefaultAccountEU: {ID: DefaultAccountEU, Realm: "EU", Nickname: "@test_account_eu_1", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan2", ClanTag: "TEST2"}, + DefaultAccountAS: {ID: DefaultAccountAS, Realm: "AS", Nickname: "@test_account_as_1", CreatedAt: time.Now(), LastBattleTime: time.Now(), ClanID: "clan3", ClanTag: "TEST3"}, } var ( diff --git a/tests/static_fetch.go b/tests/static_fetch.go index 530cff6f..41dfc92f 100644 --- a/tests/static_fetch.go +++ b/tests/static_fetch.go @@ -71,7 +71,6 @@ func (c *staticTestingFetch) PeriodStats(ctx context.Context, id string, from ti for id, stats := range current.RegularBattles.Vehicles { stats.StatsFrame.Subtract(DefaultStatsFrameSmall1) current.RegularBattles.Vehicles[id] = stats - fmt.Printf("%#v\n", stats.StatsFrame) } return current, nil } From a67474e7be8e24751bf70cf5324d6d647ad58de5 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 20:57:20 -0400 Subject: [PATCH 212/341] minot ui tweaks --- internal/stats/render/session/v1/constants.go | 3 +- render_test.go | 33 ++++++++++++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/internal/stats/render/session/v1/constants.go b/internal/stats/render/session/v1/constants.go index bdbfd3e1..fa46eb4f 100644 --- a/internal/stats/render/session/v1/constants.go +++ b/internal/stats/render/session/v1/constants.go @@ -63,7 +63,8 @@ func overviewColumnStyle(width float64) common.Style { func overviewCardStyle(width float64) common.Style { style := defaultCardStyle(width) - style.JustifyContent = common.JustifyContentSpaceBetween + // style.JustifyContent = common.JustifyContentSpaceBetween + style.JustifyContent = common.JustifyContentSpaceAround style.Direction = common.DirectionHorizontal style.AlignItems = common.AlignItemsEnd style.PaddingY = 20 diff --git a/render_test.go b/render_test.go index 66b12f79..e995f5f6 100644 --- a/render_test.go +++ b/render_test.go @@ -24,14 +24,29 @@ func TestRenderSession(t *testing.T) { loadStaticAssets(static) renderer := stats.NewRenderer(tests.StaticTestingFetch(), tests.StaticTestingDatabase(), nil, language.English) - image, _, err := renderer.Session(context.Background(), tests.DefaultAccountNAShort, time.Now(), common.WithBackground("")) - assert.NoError(t, err, "failed to render a session image") - assert.NotNil(t, image, "image is nil") - f, err := os.Create("tmp/render_test_session.png") - assert.NoError(t, err, "failed to create a file") - defer f.Close() - - err = image.PNG(f) - assert.NoError(t, err, "failed to encode a png image") + { + image, _, err := renderer.Session(context.Background(), tests.DefaultAccountNAShort, time.Now(), common.WithBackground("")) + assert.NoError(t, err, "failed to render a session image") + assert.NotNil(t, image, "image is nil") + + f, err := os.Create("tmp/render_test_session_full_small.png") + assert.NoError(t, err, "failed to create a file") + defer f.Close() + + err = image.PNG(f) + assert.NoError(t, err, "failed to encode a png image") + } + { + image, _, err := renderer.Session(context.Background(), tests.DefaultAccountNA, time.Now(), common.WithBackground("")) + assert.NoError(t, err, "failed to render a session image") + assert.NotNil(t, image, "image is nil") + + f, err := os.Create("tmp/render_test_session_full_large.png") + assert.NoError(t, err, "failed to create a file") + defer f.Close() + + err = image.PNG(f) + assert.NoError(t, err, "failed to encode a png image") + } } From 38d46b9c89ed6fdb6ed5cb1ce3dec5e4690888ed Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 21:22:50 -0400 Subject: [PATCH 213/341] fixed localization on fmt strings --- cmd/discord/common/reply.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/discord/common/reply.go b/cmd/discord/common/reply.go index 457a253e..70fe2080 100644 --- a/cmd/discord/common/reply.go +++ b/cmd/discord/common/reply.go @@ -23,7 +23,7 @@ func (r reply) Text(message ...string) reply { } func (r reply) Format(format string, args ...any) reply { - r.text = append(r.text, fmt.Sprintf(format, args...)) + r.text = append(r.text, fmt.Sprintf(r.ctx.Localize(format), args...)) return r } @@ -63,7 +63,7 @@ func (r reply) Send(content ...string) error { func (r reply) data(localePrinter func(string) string) discordgo.InteractionResponseData { var content []string for _, t := range r.text { - content = append(content, localePrinter(t)) + content = append(content, r.ctx.Localize(t)) } return discordgo.InteractionResponseData{ Content: strings.Join(content, "\n"), From b0dec9db2d06cbbc68710ca85a481b285dbb3f40 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 27 Jun 2024 22:39:39 -0400 Subject: [PATCH 214/341] fixed special col width calculations --- internal/stats/fetch/v1/multisource.go | 9 ++-- internal/stats/prepare/period/v1/card.go | 4 +- internal/stats/prepare/period/v1/constants.go | 18 ++++++- internal/stats/prepare/session/v1/card.go | 15 +++--- .../stats/prepare/session/v1/constants.go | 33 +++++++++++-- internal/stats/render/period/v1/cards.go | 4 +- internal/stats/render/session/v1/cards.go | 49 ++++++++++++------- tests/static_fetch.go | 12 +++-- 8 files changed, 104 insertions(+), 40 deletions(-) diff --git a/internal/stats/fetch/v1/multisource.go b/internal/stats/fetch/v1/multisource.go index 558b07a3..060398e7 100644 --- a/internal/stats/fetch/v1/multisource.go +++ b/internal/stats/fetch/v1/multisource.go @@ -317,7 +317,7 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session return } - v, err := c.database.GetVehicleSnapshots(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(s.LastBattleTime.Add(time.Second))) + v, err := c.database.GetVehicleSnapshots(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) vehiclesSnapshots = retry.DataWithErr[[]models.VehicleSnapshot]{Data: v, Err: err} }() @@ -378,8 +378,11 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session continue } - current.StatsFrame.Subtract(snapshot.Stats) - session.RegularBattles.Vehicles[id] = current + sessionFrame := current + stats := *current.StatsFrame + stats.Subtract(snapshot.Stats) + sessionFrame.StatsFrame = &stats + session.RegularBattles.Vehicles[id] = sessionFrame } if options.withWN8 { diff --git a/internal/stats/prepare/period/v1/card.go b/internal/stats/prepare/period/v1/card.go index ce1e0407..a6ffca86 100644 --- a/internal/stats/prepare/period/v1/card.go +++ b/internal/stats/prepare/period/v1/card.go @@ -23,7 +23,7 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]models.Veh // Overview Card for _, column := range overviewBlocks { var columnBlocks []common.StatsBlock[BlockData] - for _, preset := range column { + for _, preset := range column.blocks { var block common.StatsBlock[BlockData] b, err := presetToBlock(preset, stats.RegularBattles.StatsFrame, stats.RegularBattles.Vehicles, glossary) if err != nil { @@ -36,7 +36,7 @@ func NewCards(stats fetch.AccountStatsOverPeriod, glossary map[string]models.Veh } cards.Overview.Type = common.CardTypeOverview - cards.Overview.Blocks = append(cards.Overview.Blocks, columnBlocks) + cards.Overview.Blocks = append(cards.Overview.Blocks, OverviewColumn{columnBlocks, column.flavor}) } if len(stats.RegularBattles.Vehicles) < 1 || len(highlights) < 1 { diff --git a/internal/stats/prepare/period/v1/constants.go b/internal/stats/prepare/period/v1/constants.go index eebf62fc..2b3b9bb4 100644 --- a/internal/stats/prepare/period/v1/constants.go +++ b/internal/stats/prepare/period/v1/constants.go @@ -6,7 +6,16 @@ import ( const TagAvgTier common.Tag = "avg_tier" -var overviewBlocks = [][]common.Tag{{common.TagDamageRatio, common.TagAvgDamage, common.TagAccuracy}, {common.TagWN8, common.TagBattles}, {TagAvgTier, common.TagWinrate, common.TagSurvivalPercent}} +type overviewColumnBlocks struct { + blocks []common.Tag + flavor blockFlavor +} + +var overviewBlocks = []overviewColumnBlocks{ + {[]common.Tag{common.TagDamageRatio, common.TagAvgDamage, common.TagAccuracy}, BlockFlavorDefault}, + {[]common.Tag{common.TagWN8, common.TagBattles}, BlockFlavorSpecial}, + {[]common.Tag{TagAvgTier, common.TagWinrate, common.TagSurvivalPercent}, BlockFlavorDefault}, +} var highlights = []common.Highlight{common.HighlightBattles, common.HighlightWN8, common.HighlightAvgDamage} type Cards struct { @@ -14,7 +23,12 @@ type Cards struct { Highlights []VehicleCard `json:"highlights"` } -type OverviewCard common.StatsCard[[]common.StatsBlock[BlockData], string] +type OverviewColumn struct { + Blocks []common.StatsBlock[BlockData] + Flavor blockFlavor +} + +type OverviewCard common.StatsCard[OverviewColumn, string] type VehicleCard common.StatsCard[common.StatsBlock[BlockData], string] type BlockData struct { diff --git a/internal/stats/prepare/session/v1/card.go b/internal/stats/prepare/session/v1/card.go index b74511bc..d9a1a6bb 100644 --- a/internal/stats/prepare/session/v1/card.go +++ b/internal/stats/prepare/session/v1/card.go @@ -67,7 +67,7 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] return int(b.LastBattleTime.Unix() - a.LastBattleTime.Unix()) }) for _, data := range ratingVehicles { - if len(cards.Rating.Vehicles) >= 5 { + if len(cards.Rating.Vehicles) >= 10 { break } @@ -221,11 +221,11 @@ func makeHighlightCard(highlight common.Highlight, session, career frame.Vehicle }, nil } -func makeOverviewCard(columns [][]common.Tag, session, career frame.StatsFrame, label string, printer func(string) string, replace func(common.Tag) common.Tag) (OverviewCard, error) { - var blocks [][]common.StatsBlock[BlockData] - for _, presets := range columns { +func makeOverviewCard(columns []overviewColumnBlocks, session, career frame.StatsFrame, label string, printer func(string) string, replace func(common.Tag) common.Tag) (OverviewCard, error) { + var blocks []OverviewColumn + for _, columnBlocks := range columns { var column []common.StatsBlock[BlockData] - for _, p := range presets { + for _, p := range columnBlocks.blocks { preset := p if replace != nil { preset = replace(p) @@ -237,7 +237,10 @@ func makeOverviewCard(columns [][]common.Tag, session, career frame.StatsFrame, block.Localize(printer) column = append(column, block) } - blocks = append(blocks, column) + blocks = append(blocks, OverviewColumn{ + Flavor: columnBlocks.flavor, + Blocks: column, + }) } return OverviewCard{ Type: common.CardTypeOverview, diff --git a/internal/stats/prepare/session/v1/constants.go b/internal/stats/prepare/session/v1/constants.go index b663a651..6996f9bf 100644 --- a/internal/stats/prepare/session/v1/constants.go +++ b/internal/stats/prepare/session/v1/constants.go @@ -5,8 +5,23 @@ import ( "github.com/cufee/aftermath/internal/stats/prepare/common/v1" ) -var unratedOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagWinrate}, {common.TagWN8}, {common.TagAvgDamage, common.TagDamageRatio}} -var ratingOverviewBlocks = [][]common.Tag{{common.TagBattles, common.TagWinrate}, {common.TagRankedRating}, {common.TagAvgDamage, common.TagDamageRatio}} +type overviewColumnBlocks struct { + blocks []common.Tag + flavor blockFlavor +} + +var unratedOverviewBlocks = []overviewColumnBlocks{ + {[]common.Tag{common.TagBattles, common.TagWinrate}, BlockFlavorDefault}, + {[]common.Tag{common.TagWN8}, BlockFlavorRating}, + {[]common.Tag{common.TagAvgDamage, common.TagDamageRatio}, BlockFlavorDefault}, +} + +var ratingOverviewBlocks = []overviewColumnBlocks{ + {[]common.Tag{common.TagBattles, common.TagWinrate}, BlockFlavorDefault}, + {[]common.Tag{common.TagRankedRating}, BlockFlavorRating}, + {[]common.Tag{common.TagAvgDamage, common.TagDamageRatio}, BlockFlavorDefault}, +} + var vehicleBlocks = []common.Tag{common.TagBattles, common.TagWinrate, common.TagAvgDamage, common.TagWN8} var highlights = []common.Highlight{common.HighlightBattles, common.HighlightWN8, common.HighlightAvgDamage} @@ -26,10 +41,22 @@ type RatingCards struct { Vehicles []VehicleCard } -type OverviewCard common.StatsCard[[]common.StatsBlock[BlockData], string] +type OverviewColumn struct { + Blocks []common.StatsBlock[BlockData] + Flavor blockFlavor +} + +type OverviewCard common.StatsCard[OverviewColumn, string] type VehicleCard common.StatsCard[common.StatsBlock[BlockData], string] type BlockData struct { Session frame.Value Career frame.Value } + +type blockFlavor string + +const ( + BlockFlavorDefault blockFlavor = "default" + BlockFlavorRating blockFlavor = "rating" +) diff --git a/internal/stats/render/period/v1/cards.go b/internal/stats/render/period/v1/cards.go index e3f4a350..87551ddc 100644 --- a/internal/stats/render/period/v1/cards.go +++ b/internal/stats/render/period/v1/cards.go @@ -35,7 +35,7 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs { rowStyle := getOverviewStyle(cardWidth) for _, column := range cards.Overview.Blocks { - for _, block := range column { + for _, block := range column.Blocks { valueStyle, labelStyle := rowStyle.block(block) label := block.Label @@ -120,7 +120,7 @@ func generateCards(stats fetch.AccountStatsOverPeriod, cards period.Cards, subs { var overviewCardBlocks []common.Block for _, column := range cards.Overview.Blocks { - columnBlock, err := statsBlocksToColumnBlock(getOverviewStyle(overviewColumnWidth), column) + columnBlock, err := statsBlocksToColumnBlock(getOverviewStyle(overviewColumnWidth), column.Blocks) if err != nil { return segments, err } diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index 2ce92711..ae0cda4c 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -43,12 +43,12 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card var secondaryColumn []common.Block // Calculate minimal card width to fit all the content + overviewColumnSizes := make(map[string]float64) primaryCardBlockSizes := make(map[string]float64) secondaryCardBlockSizes := make(map[string]float64) highlightCardBlockSizes := make(map[string]float64) var primaryCardWidth float64 = minPrimaryCardWidth var secondaryCardWidth, totalFrameWidth float64 - var overviewColumnMaxWidth float64 // rating and unrated battles > 0 unrated battles > 0 rating battles > 0 // [title card ] | [vehicle] [title card ] | [vehicle] [title card ] // [overview unrated] | [vehicle] OR [overview unrated] | [vehicle] OR [overview rating ] @@ -68,36 +68,45 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } if shouldRenderUnratedOverview { - var totalContentWidth float64 = overviewCardStyle(0).Gap * float64(len(cards.Unrated.Overview.Blocks)-1) for _, column := range cards.Unrated.Overview.Blocks { styleWithIconOffset := overviewStatsBlockStyle() styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize - presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) - overviewColumnMaxWidth = common.Max(overviewColumnMaxWidth, contentWidth) - totalContentWidth += contentWidth + presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column.Blocks, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) + overviewColumnSizes[string(column.Flavor)] = common.Max(overviewColumnSizes[string(column.Flavor)], contentWidth) for key, width := range presetBlockWidth { primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) } } - primaryCardWidth = common.Max(primaryCardWidth, totalContentWidth) } - if shouldRenderRatingOverview { - var totalContentWidth float64 = overviewCardStyle(0).Gap * float64(len(cards.Rating.Overview.Blocks)-1) - for _, column := range cards.Unrated.Overview.Blocks { + for _, column := range cards.Rating.Overview.Blocks { styleWithIconOffset := overviewStatsBlockStyle() styleWithIconOffset.session.PaddingX += vehicleComparisonIconSize - presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) - overviewColumnMaxWidth = common.Max(overviewColumnMaxWidth, contentWidth) - totalContentWidth += contentWidth + presetBlockWidth, contentWidth := overviewColumnBlocksWidth(column.Blocks, styleWithIconOffset.session, styleWithIconOffset.career, styleWithIconOffset.label, overviewColumnStyle(0)) + overviewColumnSizes[string(column.Flavor)] = common.Max(overviewColumnSizes[string(column.Flavor)], contentWidth) for key, width := range presetBlockWidth { primaryCardBlockSizes[key] = common.Max(primaryCardBlockSizes[key], width) } } + } + // we now have column width for both unrated and rating overviews + if shouldRenderUnratedOverview { + var totalContentWidth float64 = overviewCardStyle(0).Gap*float64(len(cards.Unrated.Overview.Blocks)-1) + overviewCardStyle(0).PaddingX*2 + for _, column := range cards.Unrated.Overview.Blocks { + totalContentWidth += overviewColumnSizes[string(column.Flavor)] + } + primaryCardWidth = common.Max(primaryCardWidth, totalContentWidth) + } + if shouldRenderRatingOverview { + var totalContentWidth float64 = overviewCardStyle(0).Gap*float64(len(cards.Rating.Overview.Blocks)-1) + overviewCardStyle(0).PaddingX*2 + for _, column := range cards.Rating.Overview.Blocks { + totalContentWidth += overviewColumnSizes[string(column.Flavor)] + } primaryCardWidth = common.Max(primaryCardWidth, totalContentWidth) } + // rating vehicle cards go on the primary block - only show if there are no unrated battles/vehicles if shouldRenderRatingVehicles { for _, card := range cards.Rating.Vehicles { @@ -199,10 +208,10 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // overview cards if shouldRenderUnratedOverview { - primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, primaryCardBlockSizes, overviewCardStyle(primaryCardWidth), overviewColumnMaxWidth)) + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Unrated.Overview, overviewColumnSizes, overviewCardStyle(primaryCardWidth))) } if shouldRenderRatingOverview { - primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, primaryCardBlockSizes, overviewRatingCardStyle(primaryCardWidth), overviewColumnMaxWidth)) + primaryColumn = append(primaryColumn, makeOverviewCard(cards.Rating.Overview, overviewColumnSizes, overviewRatingCardStyle(primaryCardWidth))) } // highlights @@ -308,9 +317,12 @@ func overviewColumnBlocksWidth(blocks []prepare.StatsBlock[session.BlockData], s tierNameSize := common.MeasureString(common.GetWN8TierName(block.Value.Float()), overviewSpecialRatingLabelStyle(nil).Font) tierNameWithPadding := tierNameSize.TotalWidth + overviewSpecialRatingPillStyle(nil).PaddingX*2 presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], specialRatingIconSize, tierNameWithPadding) + contentWidth = common.Max(contentWidth, tierNameWithPadding) } if block.Tag == prepare.TagRankedRating { - presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], specialRatingIconSize) + valueSize := common.MeasureString(block.Value.String(), overviewSpecialRatingLabelStyle(nil).Font) + presetBlockWidth[block.Tag.String()] = common.Max(presetBlockWidth[block.Tag.String()], specialRatingIconSize, valueSize.TotalWidth) + contentWidth = common.Max(contentWidth, valueSize.TotalWidth) } } return presetBlockWidth, contentWidth @@ -390,17 +402,16 @@ func makeVehicleLegendCard(reference session.VehicleCard, blockSizes map[string] return common.NewBlocksContent(containerStyle, common.NewBlocksContent(vehicleBlocksRowStyle(0), content...)) } -func makeOverviewCard(card session.OverviewCard, blockSizes map[string]float64, style common.Style, columnWidth float64) common.Block { +func makeOverviewCard(card session.OverviewCard, columnSizes map[string]float64, style common.Style) common.Block { // made all columns the same width for things to be centered var content []common.Block // add a blank block to balance the offset added from icons blockStyle := vehicleBlockStyle() for _, column := range card.Blocks { var columnContent []common.Block - for _, block := range column { + for _, block := range column.Blocks { var col common.Block - blockWidth := columnWidth // fit the block to column width to make things look even + blockWidth := columnSizes[string(column.Flavor)] // fit the block to column width to make things look even if block.Tag == prepare.TagWN8 || block.Tag == prepare.TagRankedRating { - blockWidth = blockSizes[block.Tag.String()] col = makeSpecialRatingColumn(block, blockWidth) } else if blockShouldHaveCompareIcon(block) { col = common.NewBlocksContent(statsBlockStyle(blockWidth), diff --git a/tests/static_fetch.go b/tests/static_fetch.go index 41dfc92f..9977dfff 100644 --- a/tests/static_fetch.go +++ b/tests/static_fetch.go @@ -37,10 +37,12 @@ func (c *staticTestingFetch) CurrentStats(ctx context.Context, id string, opts . var vehicles = make(map[string]frame.VehicleStatsFrame) for id := range 10 { - vehicles[fmt.Sprint(id)] = DefaultVehicleStatsFrameBig1(fmt.Sprint(id)) + f := DefaultVehicleStatsFrameBig1(fmt.Sprint(id)) + f.SetWN8(9999) + vehicles[fmt.Sprint(id)] = f } - return fetch.AccountStatsOverPeriod{ + stats := fetch.AccountStatsOverPeriod{ Account: account, Realm: account.Realm, @@ -55,7 +57,9 @@ func (c *staticTestingFetch) CurrentStats(ctx context.Context, id string, opts . RatingBattles: fetch.StatsWithVehicles{ StatsFrame: DefaultStatsFrameBig2, }, - }, nil + } + stats.RegularBattles.SetWN8(9999) + return stats, nil } func (c *staticTestingFetch) PeriodStats(ctx context.Context, id string, from time.Time, opts ...fetch.StatsOption) (fetch.AccountStatsOverPeriod, error) { @@ -65,10 +69,12 @@ func (c *staticTestingFetch) PeriodStats(ctx context.Context, id string, from ti } current.PeriodStart = from + current.RegularBattles.SetWN8(9999) current.RegularBattles.StatsFrame.Subtract(DefaultStatsFrameSmall1) current.RatingBattles.StatsFrame.Subtract(DefaultStatsFrameSmall2) for id, stats := range current.RegularBattles.Vehicles { + stats.SetWN8(9999) stats.StatsFrame.Subtract(DefaultStatsFrameSmall1) current.RegularBattles.Vehicles[id] = stats } From 9adeef1fa960781b85ec6617dc3d7f124fa4f6ce Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 28 Jun 2024 13:53:21 -0400 Subject: [PATCH 215/341] work on asset downloader --- cmd/assets/.env.example | 9 +- cmd/assets/Dockerfile | 33 +++ cmd/assets/main.go | 2 + cmd/assets/strings.go | 255 ++++++++++++++++++++++ go.mod | 1 + go.sum | 12 +- internal/stats/render/session/v1/cards.go | 11 - 7 files changed, 301 insertions(+), 22 deletions(-) create mode 100644 cmd/assets/Dockerfile create mode 100644 cmd/assets/strings.go diff --git a/cmd/assets/.env.example b/cmd/assets/.env.example index 198c6b24..8be29a1b 100644 --- a/cmd/assets/.env.example +++ b/cmd/assets/.env.example @@ -1 +1,8 @@ -NEWS_ENDPOINT="https://na.wotblitz.com/en/api/cms/news/" \ No newline at end of file +NEWS_ENDPOINT="https://na.wotblitz.com/en/api/cms/news/" + +DOWNLOADER_STEAM_USERNAME="" +DOWNLOADER_STEAM_PASSWORD="" +DOWNLOADER_STEAM_AUTH_CODE="" +DOWNLOADER_CMD_PATH="./DepotDownloader" +DOWNLOADER_DEPOT_ID="444202" +DOWNLOADER_APP_ID="444200" \ No newline at end of file diff --git a/cmd/assets/Dockerfile b/cmd/assets/Dockerfile new file mode 100644 index 00000000..6f8450ab --- /dev/null +++ b/cmd/assets/Dockerfile @@ -0,0 +1,33 @@ +# Download required assets/binaries +FROM debian:stable-slim as downloader + +WORKDIR /tmp + +RUN apt-get update +RUN apt-get install unzip wget -y + +RUN wget https://github.com/SteamRE/DepotDownloader/releases/download/DepotDownloader_2.6.0/DepotDownloader-linux-x64.zip -O downloader.zip +RUN unzip downloader.zip -d /downloader + +# Build the application +FROM golang:1.22.3-bookworm as builder + +WORKDIR /workspace + +COPY go.mod go.sum ./ +RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download + +COPY ./ ./ + +# build a fully standalone binary with zero dependencies +RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=0 GOOS=linux go build -o /bin/app cmd/assets/*.go + +# Run +FROM scratch + +COPY --from=downloader /downloader /downloader +COPY --from=builder /bin/app /usr/bin/app + +ENV DOWNLOADER_CMD_PATH=/downloader/DepotDownloader + +ENTRYPOINT [ "app" ] \ No newline at end of file diff --git a/cmd/assets/main.go b/cmd/assets/main.go index cb01149c..158ebc96 100644 --- a/cmd/assets/main.go +++ b/cmd/assets/main.go @@ -10,6 +10,8 @@ import ( "github.com/cufee/aftermath/internal/database" ) +// os.Getenv("DOWNLOADER_CMD_PATH") + func main() { // db, err := newDatabaseClientFromEnv() // if err != nil { diff --git a/cmd/assets/strings.go b/cmd/assets/strings.go new file mode 100644 index 00000000..bd1dac3e --- /dev/null +++ b/cmd/assets/strings.go @@ -0,0 +1,255 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "io/fs" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + + "golang.org/x/text/language" + "gopkg.in/yaml.v3" + + "encoding/binary" + "errors" + "hash/crc32" + + _ "github.com/joho/godotenv/autoload" + "github.com/pierrec/lz4/v4" +) + +type steamDownloader struct { + cmdPath string + outDir string + + steamUsername string + steamPassword string + steamAuthCode string +} + +func NewDownloader(cmdPath string) *steamDownloader { + return &steamDownloader{cmdPath: cmdPath, outDir: "/tmp"} +} + +func (d *steamDownloader) DownloadDepot(appID, depotID string, fileListPath ...string) (string, error) { + var args []string + + if len(fileListPath) > 0 { + args = append(args, "-filelist") + args = append(args, fileListPath[0]) + } + + args = append(args, "-username") + args = append(args, d.steamUsername) + args = append(args, "-password") + args = append(args, d.steamPassword) + if d.steamAuthCode != "" { + // args = append(args, "-filelist") + // args = append(args, "filelist.txt") + } + + args = append(args, "-app") + args = append(args, appID) + args = append(args, "-depot") + args = append(args, depotID) + args = append(args, "-dir") + args = append(args, d.outDir) + + cmd := exec.Command(d.cmdPath, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + return "", err + } + + if cmd.ProcessState.ExitCode() != 0 { + return "", fmt.Errorf("bad exit code from downloader: %d", cmd.ProcessState.ExitCode()) + } + return d.outDir, nil + +} + +type localizationString struct { + Key string `yaml:"key"` + Value string `yaml:"value"` + Notes string `yaml:"notes"` +} + +type parsedData struct { + data []localizationString + existingKeys map[string]struct{} +} + +func (d *parsedData) Encode(w io.Writer) error { + return yaml.NewEncoder(w).Encode(d.data) +} + +type parsedKey string + +func (d *parsedData) Add(category, key, value string) { + if d.existingKeys == nil { + d.existingKeys = make(map[string]struct{}) + } + if _, ok := d.existingKeys[key]; ok { + return + } + + d.data = append(d.data, localizationString{Key: category + "_" + key, Value: value, Notes: "Generated from game files"}) +} + +func (k parsedKey) IsMap() bool { + return strings.HasPrefix(string(k), "#maps:") && strings.HasSuffix(string(k), ".sc2") +} + +func (k parsedKey) MapName() string { + split := strings.Split(string(k), ":") + if len(split) < 2 { + return "" + } + return split[1] +} + +func (k parsedKey) IsBattleType() bool { + return strings.HasPrefix(string(k), "battleType/") && len(strings.Split(string(k), "/")) == 2 +} + +func (k parsedKey) BattleTypeName() string { + return strings.Split(string(k), "/")[1] +} + +func ParseGameStrings(dl *steamDownloader, fileList string) error { + path, err := dl.DownloadDepot(os.Getenv("DOWNLOADER_APP_ID"), os.Getenv("DOWNLOADER_DEPOT_ID"), fileList) + if err != nil { + return err + } + + dirPath := filepath.Join(path, "/Data/Strings") + dir, err := os.ReadDir(dirPath) + if err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, len(dir)) + + for _, entry := range dir { + wg.Add(1) + go func(entry fs.DirEntry) { + defer wg.Done() + if entry.IsDir() || !strings.Contains(entry.Name(), ".yaml") { + return + } + log.Println("decrypting and parsing", entry.Name()) + + raw, err := os.ReadFile(filepath.Join(dirPath, entry.Name())) + if err != nil { + errCh <- err + return + } + + lang := strings.Split(entry.Name(), ".")[0] + locale, err := language.Parse(lang) + if err != nil { + errCh <- err + return + } + + path := filepath.Join("./assets/", locale.String(), "game_strings.yaml") + err = parseAndSaveFile(raw, path) + if err != nil { + errCh <- err + return + } + + log.Println("saved assets to", path) + }(entry) + } + + wg.Wait() + close(errCh) + if len(errCh) > 0 { + return <-errCh + } + return nil +} + +func parseAndSaveFile(encrypted []byte, outPath string) error { + raw, err := decryptDVPL(encrypted) + if err != nil { + return err + } + + data, err := decodeYAML(bytes.NewBuffer(raw)) + if err != nil { + return err + } + + var parsed parsedData + + for key, value := range data { + if key.IsMap() { + parsed.Add("maps", key.MapName(), value) + } + if key.IsBattleType() { + parsed.Add("battle_types", key.BattleTypeName(), value) + } + } + + err = os.MkdirAll(filepath.Dir(outPath), os.ModePerm) + if err != nil { + return err + } + + f, err := os.Create(outPath) + if err != nil { + return err + } + defer f.Close() + + err = parsed.Encode(f) + if err != nil { + return err + } + return nil +} + +func decryptDVPL(inputBuf []byte) ([]byte, error) { + dataBuf := inputBuf[:len(inputBuf)-20] + footerBuf := inputBuf[len(inputBuf)-20:] + + originalSize := binary.LittleEndian.Uint32(footerBuf[:4]) + compressedSize := binary.LittleEndian.Uint32(footerBuf[4:8]) + if int(compressedSize) != len(dataBuf) { + return nil, errors.New("invalid compressed data length") + } + + crc32DataSum := binary.LittleEndian.Uint32(footerBuf[8:12]) + if crc32DataSum != crc32.ChecksumIEEE(dataBuf) { + return nil, errors.New("invalid crc32 sum") + } + + compressType := binary.LittleEndian.Uint32(footerBuf[12:16]) + outputBuf := make([]byte, originalSize) + if compressType == 0 { + outputBuf = dataBuf + } else { + actualOutputSize, err := lz4.UncompressBlock(dataBuf, outputBuf) + if err != nil { + return nil, errors.New("failed to uncompressed lz4") + } + outputBuf = outputBuf[:actualOutputSize] + } + return outputBuf, nil +} + +func decodeYAML(r io.Reader) (map[parsedKey]string, error) { + decoded := make(map[parsedKey]string) + return decoded, yaml.NewDecoder(r).Decode(&decoded) +} diff --git a/go.mod b/go.mod index 38628307..c1b268b1 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/lucsky/cuid v1.2.1 github.com/mattn/go-sqlite3 v1.14.22 github.com/nlpodyssey/gopickle v0.3.0 + github.com/pierrec/lz4/v4 v4.1.21 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index ee4f6f7a..59719986 100644 --- a/go.sum +++ b/go.sum @@ -58,23 +58,19 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -83,10 +79,6 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/internal/stats/render/session/v1/cards.go b/internal/stats/render/session/v1/cards.go index ae0cda4c..9ba757eb 100644 --- a/internal/stats/render/session/v1/cards.go +++ b/internal/stats/render/session/v1/cards.go @@ -49,10 +49,6 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card highlightCardBlockSizes := make(map[string]float64) var primaryCardWidth float64 = minPrimaryCardWidth var secondaryCardWidth, totalFrameWidth float64 - // rating and unrated battles > 0 unrated battles > 0 rating battles > 0 - // [title card ] | [vehicle] [title card ] | [vehicle] [title card ] - // [overview unrated] | [vehicle] OR [overview unrated] | [vehicle] OR [overview rating ] - // [overview rating ] | [... ] [highlight ] | [... ] [vehicle ] { titleStyle := common.DefaultPlayerTitleStyle(playerNameCardStyle(0)) @@ -110,7 +106,6 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // rating vehicle cards go on the primary block - only show if there are no unrated battles/vehicles if shouldRenderRatingVehicles { for _, card := range cards.Rating.Vehicles { - // [title] [session] style := ratingVehicleBlockStyle() titleSize := common.MeasureString(card.Title, ratingVehicleCardTitleStyle().Font) presetBlockWidth, contentWidth := vehicleBlocksWidth(card.Blocks, style.session, style.career, style.label, ratingVehicleBlocksRowStyle(0)) @@ -126,8 +121,6 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card // highlighted vehicles go on the primary block if shouldRenderUnratedHighlights { for _, card := range cards.Unrated.Highlights { - // [card label] [session] - // [title] [label] labelSize := common.MeasureString(card.Meta, highlightCardTitleTextStyle().Font) titleSize := common.MeasureString(card.Title, highlightVehicleNameTextStyle().Font) @@ -144,10 +137,6 @@ func cardsToSegments(session, _ fetch.AccountStatsOverPeriod, cards session.Card } if shouldRenderUnratedVehicles { // unrated vehicles go on the secondary block for _, card := range cards.Unrated.Vehicles { - // [ label ] - // [session] - // [career ] - styleWithIconOffset := vehicleBlockStyle() // icon is only on one side, so we divide by 2 styleWithIconOffset.label.PaddingX += vehicleComparisonIconSize / 2 From 4a40d4ce7e322e92de68148307ac05f79b05974a Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 28 Jun 2024 20:05:00 -0400 Subject: [PATCH 216/341] added achievement snapshots --- cmd/core/tasks/{sessions.go => snapshots.go} | 7 +- go.mod | 2 +- go.sum | 4 +- internal/database/client.go | 3 + internal/database/ent/db/account.go | 18 +- internal/database/ent/db/account/account.go | 34 +-- internal/database/ent/db/account/where.go | 12 +- internal/database/ent/db/account_create.go | 18 +- internal/database/ent/db/account_query.go | 32 +-- internal/database/ent/db/account_update.go | 96 ++++---- internal/database/ent/db/client.go | 6 +- internal/database/ent/db/crontask/crontask.go | 2 + internal/database/ent/db/crontask_create.go | 12 + internal/database/ent/db/migrate/schema.go | 4 +- internal/database/ent/db/mutation.go | 94 ++++---- internal/database/ent/db/runtime.go | 4 + internal/database/ent/schema/account.go | 4 +- .../database/ent/schema/account_snapshot.go | 2 +- .../ent/schema/achievements_snapshot.go | 3 - .../database/models/achievements_snapshot.go | 19 ++ internal/database/models/task.go | 5 - internal/database/snapshots.go | 112 +++++++++ internal/logic/snapshots.go | 225 ++++++++++++++---- tests/static_database.go | 6 + 24 files changed, 500 insertions(+), 224 deletions(-) rename cmd/core/tasks/{sessions.go => snapshots.go} (90%) create mode 100644 internal/database/models/achievements_snapshot.go diff --git a/cmd/core/tasks/sessions.go b/cmd/core/tasks/snapshots.go similarity index 90% rename from cmd/core/tasks/sessions.go rename to cmd/core/tasks/snapshots.go index 7a02dea7..e6deda26 100644 --- a/cmd/core/tasks/sessions.go +++ b/cmd/core/tasks/snapshots.go @@ -81,7 +81,12 @@ func CreateRecordSnapshotsTasks(client core.Client, realm string) error { } task.Targets = append(task.Targets, accounts...) - // This update requires (2 + n) requests per n players + // This update requires (3 + 2n) requests per n players + // 1 - get all account stats + // 1 - get all clan profiles + // 1 - get all account achievements + // n - get vehicle stats for each account + // n - get vehicle achievements for each account tasks := splitTaskByTargets(task, 50) err = client.Database().CreateTasks(ctx, tasks...) if err != nil { diff --git a/go.mod b/go.mod index c1b268b1..d39d25ff 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.3 require ( entgo.io/ent v0.13.1 github.com/bwmarrin/discordgo v0.28.1 - github.com/cufee/am-wg-proxy-next/v2 v2.1.5 + github.com/cufee/am-wg-proxy-next/v2 v2.1.6 github.com/disintegration/imaging v1.6.2 github.com/fogleman/gg v1.3.0 github.com/go-co-op/gocron v1.37.0 diff --git a/go.sum b/go.sum index 59719986..b64339d0 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cufee/am-wg-proxy-next/v2 v2.1.5 h1:F16XkZ8l+vZcRtgSYfxIphldsGBAc4+kWcIly3F3x+U= -github.com/cufee/am-wg-proxy-next/v2 v2.1.5/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= +github.com/cufee/am-wg-proxy-next/v2 v2.1.6 h1:LNGXS9n1FANtblhEUxMAZehfcTIH6FsGfF0snhf2hHM= +github.com/cufee/am-wg-proxy-next/v2 v2.1.6/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= 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= diff --git a/internal/database/client.go b/internal/database/client.go index d192ca4b..8d682d4a 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -52,6 +52,9 @@ type SnapshotsClient interface { GetVehicleSnapshots(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.VehicleSnapshot, error) CreateAccountVehicleSnapshots(ctx context.Context, accountID string, snapshots ...models.VehicleSnapshot) (map[string]error, error) + CreateAccountAchievementSnapshots(ctx context.Context, accountID string, snapshots ...models.AchievementsSnapshot) (map[string]error, error) + GetAchievementsSnapshots(ctx context.Context, accountID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.AchievementsSnapshot, error) + DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error } diff --git a/internal/database/ent/db/account.go b/internal/database/ent/db/account.go index ff1fed1f..c2ac1943 100644 --- a/internal/database/ent/db/account.go +++ b/internal/database/ent/db/account.go @@ -44,8 +44,8 @@ type Account struct { type AccountEdges struct { // Clan holds the value of the clan edge. Clan *Clan `json:"clan,omitempty"` - // Snapshots holds the value of the snapshots edge. - Snapshots []*AccountSnapshot `json:"snapshots,omitempty"` + // AccountSnapshots holds the value of the account_snapshots edge. + AccountSnapshots []*AccountSnapshot `json:"account_snapshots,omitempty"` // VehicleSnapshots holds the value of the vehicle_snapshots edge. VehicleSnapshots []*VehicleSnapshot `json:"vehicle_snapshots,omitempty"` // AchievementSnapshots holds the value of the achievement_snapshots edge. @@ -66,13 +66,13 @@ func (e AccountEdges) ClanOrErr() (*Clan, error) { return nil, &NotLoadedError{edge: "clan"} } -// SnapshotsOrErr returns the Snapshots value or an error if the edge +// AccountSnapshotsOrErr returns the AccountSnapshots value or an error if the edge // was not loaded in eager-loading. -func (e AccountEdges) SnapshotsOrErr() ([]*AccountSnapshot, error) { +func (e AccountEdges) AccountSnapshotsOrErr() ([]*AccountSnapshot, error) { if e.loadedTypes[1] { - return e.Snapshots, nil + return e.AccountSnapshots, nil } - return nil, &NotLoadedError{edge: "snapshots"} + return nil, &NotLoadedError{edge: "account_snapshots"} } // VehicleSnapshotsOrErr returns the VehicleSnapshots value or an error if the edge @@ -191,9 +191,9 @@ func (a *Account) QueryClan() *ClanQuery { return NewAccountClient(a.config).QueryClan(a) } -// QuerySnapshots queries the "snapshots" edge of the Account entity. -func (a *Account) QuerySnapshots() *AccountSnapshotQuery { - return NewAccountClient(a.config).QuerySnapshots(a) +// QueryAccountSnapshots queries the "account_snapshots" edge of the Account entity. +func (a *Account) QueryAccountSnapshots() *AccountSnapshotQuery { + return NewAccountClient(a.config).QueryAccountSnapshots(a) } // QueryVehicleSnapshots queries the "vehicle_snapshots" edge of the Account entity. diff --git a/internal/database/ent/db/account/account.go b/internal/database/ent/db/account/account.go index 14226186..adb50e4e 100644 --- a/internal/database/ent/db/account/account.go +++ b/internal/database/ent/db/account/account.go @@ -32,8 +32,8 @@ const ( FieldClanID = "clan_id" // EdgeClan holds the string denoting the clan edge name in mutations. EdgeClan = "clan" - // EdgeSnapshots holds the string denoting the snapshots edge name in mutations. - EdgeSnapshots = "snapshots" + // EdgeAccountSnapshots holds the string denoting the account_snapshots edge name in mutations. + EdgeAccountSnapshots = "account_snapshots" // EdgeVehicleSnapshots holds the string denoting the vehicle_snapshots edge name in mutations. EdgeVehicleSnapshots = "vehicle_snapshots" // EdgeAchievementSnapshots holds the string denoting the achievement_snapshots edge name in mutations. @@ -47,13 +47,13 @@ const ( ClanInverseTable = "clans" // ClanColumn is the table column denoting the clan relation/edge. ClanColumn = "clan_id" - // SnapshotsTable is the table that holds the snapshots relation/edge. - SnapshotsTable = "account_snapshots" - // SnapshotsInverseTable is the table name for the AccountSnapshot entity. + // AccountSnapshotsTable is the table that holds the account_snapshots relation/edge. + AccountSnapshotsTable = "account_snapshots" + // AccountSnapshotsInverseTable is the table name for the AccountSnapshot entity. // It exists in this package in order to avoid circular dependency with the "accountsnapshot" package. - SnapshotsInverseTable = "account_snapshots" - // SnapshotsColumn is the table column denoting the snapshots relation/edge. - SnapshotsColumn = "account_id" + AccountSnapshotsInverseTable = "account_snapshots" + // AccountSnapshotsColumn is the table column denoting the account_snapshots relation/edge. + AccountSnapshotsColumn = "account_id" // VehicleSnapshotsTable is the table that holds the vehicle_snapshots relation/edge. VehicleSnapshotsTable = "vehicle_snapshots" // VehicleSnapshotsInverseTable is the table name for the VehicleSnapshot entity. @@ -163,17 +163,17 @@ func ByClanField(field string, opts ...sql.OrderTermOption) OrderOption { } } -// BySnapshotsCount orders the results by snapshots count. -func BySnapshotsCount(opts ...sql.OrderTermOption) OrderOption { +// ByAccountSnapshotsCount orders the results by account_snapshots count. +func ByAccountSnapshotsCount(opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { - sqlgraph.OrderByNeighborsCount(s, newSnapshotsStep(), opts...) + sqlgraph.OrderByNeighborsCount(s, newAccountSnapshotsStep(), opts...) } } -// BySnapshots orders the results by snapshots terms. -func BySnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { +// ByAccountSnapshots orders the results by account_snapshots terms. +func ByAccountSnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { return func(s *sql.Selector) { - sqlgraph.OrderByNeighborTerms(s, newSnapshotsStep(), append([]sql.OrderTerm{term}, terms...)...) + sqlgraph.OrderByNeighborTerms(s, newAccountSnapshotsStep(), append([]sql.OrderTerm{term}, terms...)...) } } @@ -211,11 +211,11 @@ func newClanStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.M2O, true, ClanTable, ClanColumn), ) } -func newSnapshotsStep() *sqlgraph.Step { +func newAccountSnapshotsStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), - sqlgraph.To(SnapshotsInverseTable, FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, SnapshotsTable, SnapshotsColumn), + sqlgraph.To(AccountSnapshotsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AccountSnapshotsTable, AccountSnapshotsColumn), ) } func newVehicleSnapshotsStep() *sqlgraph.Step { diff --git a/internal/database/ent/db/account/where.go b/internal/database/ent/db/account/where.go index fe718ba5..2b170a4f 100644 --- a/internal/database/ent/db/account/where.go +++ b/internal/database/ent/db/account/where.go @@ -503,21 +503,21 @@ func HasClanWith(preds ...predicate.Clan) predicate.Account { }) } -// HasSnapshots applies the HasEdge predicate on the "snapshots" edge. -func HasSnapshots() predicate.Account { +// HasAccountSnapshots applies the HasEdge predicate on the "account_snapshots" edge. +func HasAccountSnapshots() predicate.Account { return predicate.Account(func(s *sql.Selector) { step := sqlgraph.NewStep( sqlgraph.From(Table, FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, SnapshotsTable, SnapshotsColumn), + sqlgraph.Edge(sqlgraph.O2M, false, AccountSnapshotsTable, AccountSnapshotsColumn), ) sqlgraph.HasNeighbors(s, step) }) } -// HasSnapshotsWith applies the HasEdge predicate on the "snapshots" edge with a given conditions (other predicates). -func HasSnapshotsWith(preds ...predicate.AccountSnapshot) predicate.Account { +// HasAccountSnapshotsWith applies the HasEdge predicate on the "account_snapshots" edge with a given conditions (other predicates). +func HasAccountSnapshotsWith(preds ...predicate.AccountSnapshot) predicate.Account { return predicate.Account(func(s *sql.Selector) { - step := newSnapshotsStep() + step := newAccountSnapshotsStep() sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { for _, p := range preds { p(s) diff --git a/internal/database/ent/db/account_create.go b/internal/database/ent/db/account_create.go index 88cc48f2..c54cc2b3 100644 --- a/internal/database/ent/db/account_create.go +++ b/internal/database/ent/db/account_create.go @@ -115,19 +115,19 @@ func (ac *AccountCreate) SetClan(c *Clan) *AccountCreate { return ac.SetClanID(c.ID) } -// AddSnapshotIDs adds the "snapshots" edge to the AccountSnapshot entity by IDs. -func (ac *AccountCreate) AddSnapshotIDs(ids ...string) *AccountCreate { - ac.mutation.AddSnapshotIDs(ids...) +// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by IDs. +func (ac *AccountCreate) AddAccountSnapshotIDs(ids ...string) *AccountCreate { + ac.mutation.AddAccountSnapshotIDs(ids...) return ac } -// AddSnapshots adds the "snapshots" edges to the AccountSnapshot entity. -func (ac *AccountCreate) AddSnapshots(a ...*AccountSnapshot) *AccountCreate { +// AddAccountSnapshots adds the "account_snapshots" edges to the AccountSnapshot entity. +func (ac *AccountCreate) AddAccountSnapshots(a ...*AccountSnapshot) *AccountCreate { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return ac.AddSnapshotIDs(ids...) + return ac.AddAccountSnapshotIDs(ids...) } // AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. @@ -322,12 +322,12 @@ func (ac *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) { _node.ClanID = nodes[0] _spec.Edges = append(_spec.Edges, edge) } - if nodes := ac.mutation.SnapshotsIDs(); len(nodes) > 0 { + if nodes := ac.mutation.AccountSnapshotsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.SnapshotsTable, - Columns: []string{account.SnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), diff --git a/internal/database/ent/db/account_query.go b/internal/database/ent/db/account_query.go index ed808e7b..d3dc8f75 100644 --- a/internal/database/ent/db/account_query.go +++ b/internal/database/ent/db/account_query.go @@ -27,7 +27,7 @@ type AccountQuery struct { inters []Interceptor predicates []predicate.Account withClan *ClanQuery - withSnapshots *AccountSnapshotQuery + withAccountSnapshots *AccountSnapshotQuery withVehicleSnapshots *VehicleSnapshotQuery withAchievementSnapshots *AchievementsSnapshotQuery modifiers []func(*sql.Selector) @@ -89,8 +89,8 @@ func (aq *AccountQuery) QueryClan() *ClanQuery { return query } -// QuerySnapshots chains the current query on the "snapshots" edge. -func (aq *AccountQuery) QuerySnapshots() *AccountSnapshotQuery { +// QueryAccountSnapshots chains the current query on the "account_snapshots" edge. +func (aq *AccountQuery) QueryAccountSnapshots() *AccountSnapshotQuery { query := (&AccountSnapshotClient{config: aq.config}).Query() query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { if err := aq.prepareQuery(ctx); err != nil { @@ -103,7 +103,7 @@ func (aq *AccountQuery) QuerySnapshots() *AccountSnapshotQuery { step := sqlgraph.NewStep( sqlgraph.From(account.Table, account.FieldID, selector), sqlgraph.To(accountsnapshot.Table, accountsnapshot.FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, account.SnapshotsTable, account.SnapshotsColumn), + sqlgraph.Edge(sqlgraph.O2M, false, account.AccountSnapshotsTable, account.AccountSnapshotsColumn), ) fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) return fromU, nil @@ -348,7 +348,7 @@ func (aq *AccountQuery) Clone() *AccountQuery { inters: append([]Interceptor{}, aq.inters...), predicates: append([]predicate.Account{}, aq.predicates...), withClan: aq.withClan.Clone(), - withSnapshots: aq.withSnapshots.Clone(), + withAccountSnapshots: aq.withAccountSnapshots.Clone(), withVehicleSnapshots: aq.withVehicleSnapshots.Clone(), withAchievementSnapshots: aq.withAchievementSnapshots.Clone(), // clone intermediate query. @@ -368,14 +368,14 @@ func (aq *AccountQuery) WithClan(opts ...func(*ClanQuery)) *AccountQuery { return aq } -// WithSnapshots tells the query-builder to eager-load the nodes that are connected to -// the "snapshots" edge. The optional arguments are used to configure the query builder of the edge. -func (aq *AccountQuery) WithSnapshots(opts ...func(*AccountSnapshotQuery)) *AccountQuery { +// WithAccountSnapshots tells the query-builder to eager-load the nodes that are connected to +// the "account_snapshots" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AccountQuery) WithAccountSnapshots(opts ...func(*AccountSnapshotQuery)) *AccountQuery { query := (&AccountSnapshotClient{config: aq.config}).Query() for _, opt := range opts { opt(query) } - aq.withSnapshots = query + aq.withAccountSnapshots = query return aq } @@ -481,7 +481,7 @@ func (aq *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco _spec = aq.querySpec() loadedTypes = [4]bool{ aq.withClan != nil, - aq.withSnapshots != nil, + aq.withAccountSnapshots != nil, aq.withVehicleSnapshots != nil, aq.withAchievementSnapshots != nil, } @@ -513,10 +513,10 @@ func (aq *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco return nil, err } } - if query := aq.withSnapshots; query != nil { - if err := aq.loadSnapshots(ctx, query, nodes, - func(n *Account) { n.Edges.Snapshots = []*AccountSnapshot{} }, - func(n *Account, e *AccountSnapshot) { n.Edges.Snapshots = append(n.Edges.Snapshots, e) }); err != nil { + if query := aq.withAccountSnapshots; query != nil { + if err := aq.loadAccountSnapshots(ctx, query, nodes, + func(n *Account) { n.Edges.AccountSnapshots = []*AccountSnapshot{} }, + func(n *Account, e *AccountSnapshot) { n.Edges.AccountSnapshots = append(n.Edges.AccountSnapshots, e) }); err != nil { return nil, err } } @@ -568,7 +568,7 @@ func (aq *AccountQuery) loadClan(ctx context.Context, query *ClanQuery, nodes [] } return nil } -func (aq *AccountQuery) loadSnapshots(ctx context.Context, query *AccountSnapshotQuery, nodes []*Account, init func(*Account), assign func(*Account, *AccountSnapshot)) error { +func (aq *AccountQuery) loadAccountSnapshots(ctx context.Context, query *AccountSnapshotQuery, nodes []*Account, init func(*Account), assign func(*Account, *AccountSnapshot)) error { fks := make([]driver.Value, 0, len(nodes)) nodeids := make(map[string]*Account) for i := range nodes { @@ -582,7 +582,7 @@ func (aq *AccountQuery) loadSnapshots(ctx context.Context, query *AccountSnapsho query.ctx.AppendFieldOnce(accountsnapshot.FieldAccountID) } query.Where(predicate.AccountSnapshot(func(s *sql.Selector) { - s.Where(sql.InValues(s.C(account.SnapshotsColumn), fks...)) + s.Where(sql.InValues(s.C(account.AccountSnapshotsColumn), fks...)) })) neighbors, err := query.All(ctx) if err != nil { diff --git a/internal/database/ent/db/account_update.go b/internal/database/ent/db/account_update.go index af0b100e..7aca76d5 100644 --- a/internal/database/ent/db/account_update.go +++ b/internal/database/ent/db/account_update.go @@ -134,19 +134,19 @@ func (au *AccountUpdate) SetClan(c *Clan) *AccountUpdate { return au.SetClanID(c.ID) } -// AddSnapshotIDs adds the "snapshots" edge to the AccountSnapshot entity by IDs. -func (au *AccountUpdate) AddSnapshotIDs(ids ...string) *AccountUpdate { - au.mutation.AddSnapshotIDs(ids...) +// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by IDs. +func (au *AccountUpdate) AddAccountSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.AddAccountSnapshotIDs(ids...) return au } -// AddSnapshots adds the "snapshots" edges to the AccountSnapshot entity. -func (au *AccountUpdate) AddSnapshots(a ...*AccountSnapshot) *AccountUpdate { +// AddAccountSnapshots adds the "account_snapshots" edges to the AccountSnapshot entity. +func (au *AccountUpdate) AddAccountSnapshots(a ...*AccountSnapshot) *AccountUpdate { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return au.AddSnapshotIDs(ids...) + return au.AddAccountSnapshotIDs(ids...) } // AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. @@ -190,25 +190,25 @@ func (au *AccountUpdate) ClearClan() *AccountUpdate { return au } -// ClearSnapshots clears all "snapshots" edges to the AccountSnapshot entity. -func (au *AccountUpdate) ClearSnapshots() *AccountUpdate { - au.mutation.ClearSnapshots() +// ClearAccountSnapshots clears all "account_snapshots" edges to the AccountSnapshot entity. +func (au *AccountUpdate) ClearAccountSnapshots() *AccountUpdate { + au.mutation.ClearAccountSnapshots() return au } -// RemoveSnapshotIDs removes the "snapshots" edge to AccountSnapshot entities by IDs. -func (au *AccountUpdate) RemoveSnapshotIDs(ids ...string) *AccountUpdate { - au.mutation.RemoveSnapshotIDs(ids...) +// RemoveAccountSnapshotIDs removes the "account_snapshots" edge to AccountSnapshot entities by IDs. +func (au *AccountUpdate) RemoveAccountSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.RemoveAccountSnapshotIDs(ids...) return au } -// RemoveSnapshots removes "snapshots" edges to AccountSnapshot entities. -func (au *AccountUpdate) RemoveSnapshots(a ...*AccountSnapshot) *AccountUpdate { +// RemoveAccountSnapshots removes "account_snapshots" edges to AccountSnapshot entities. +func (au *AccountUpdate) RemoveAccountSnapshots(a ...*AccountSnapshot) *AccountUpdate { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return au.RemoveSnapshotIDs(ids...) + return au.RemoveAccountSnapshotIDs(ids...) } // ClearVehicleSnapshots clears all "vehicle_snapshots" edges to the VehicleSnapshot entity. @@ -369,12 +369,12 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } - if au.mutation.SnapshotsCleared() { + if au.mutation.AccountSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.SnapshotsTable, - Columns: []string{account.SnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), @@ -382,12 +382,12 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := au.mutation.RemovedSnapshotsIDs(); len(nodes) > 0 && !au.mutation.SnapshotsCleared() { + if nodes := au.mutation.RemovedAccountSnapshotsIDs(); len(nodes) > 0 && !au.mutation.AccountSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.SnapshotsTable, - Columns: []string{account.SnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), @@ -398,12 +398,12 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := au.mutation.SnapshotsIDs(); len(nodes) > 0 { + if nodes := au.mutation.AccountSnapshotsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.SnapshotsTable, - Columns: []string{account.SnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), @@ -627,19 +627,19 @@ func (auo *AccountUpdateOne) SetClan(c *Clan) *AccountUpdateOne { return auo.SetClanID(c.ID) } -// AddSnapshotIDs adds the "snapshots" edge to the AccountSnapshot entity by IDs. -func (auo *AccountUpdateOne) AddSnapshotIDs(ids ...string) *AccountUpdateOne { - auo.mutation.AddSnapshotIDs(ids...) +// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by IDs. +func (auo *AccountUpdateOne) AddAccountSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.AddAccountSnapshotIDs(ids...) return auo } -// AddSnapshots adds the "snapshots" edges to the AccountSnapshot entity. -func (auo *AccountUpdateOne) AddSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { +// AddAccountSnapshots adds the "account_snapshots" edges to the AccountSnapshot entity. +func (auo *AccountUpdateOne) AddAccountSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return auo.AddSnapshotIDs(ids...) + return auo.AddAccountSnapshotIDs(ids...) } // AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. @@ -683,25 +683,25 @@ func (auo *AccountUpdateOne) ClearClan() *AccountUpdateOne { return auo } -// ClearSnapshots clears all "snapshots" edges to the AccountSnapshot entity. -func (auo *AccountUpdateOne) ClearSnapshots() *AccountUpdateOne { - auo.mutation.ClearSnapshots() +// ClearAccountSnapshots clears all "account_snapshots" edges to the AccountSnapshot entity. +func (auo *AccountUpdateOne) ClearAccountSnapshots() *AccountUpdateOne { + auo.mutation.ClearAccountSnapshots() return auo } -// RemoveSnapshotIDs removes the "snapshots" edge to AccountSnapshot entities by IDs. -func (auo *AccountUpdateOne) RemoveSnapshotIDs(ids ...string) *AccountUpdateOne { - auo.mutation.RemoveSnapshotIDs(ids...) +// RemoveAccountSnapshotIDs removes the "account_snapshots" edge to AccountSnapshot entities by IDs. +func (auo *AccountUpdateOne) RemoveAccountSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.RemoveAccountSnapshotIDs(ids...) return auo } -// RemoveSnapshots removes "snapshots" edges to AccountSnapshot entities. -func (auo *AccountUpdateOne) RemoveSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { +// RemoveAccountSnapshots removes "account_snapshots" edges to AccountSnapshot entities. +func (auo *AccountUpdateOne) RemoveAccountSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return auo.RemoveSnapshotIDs(ids...) + return auo.RemoveAccountSnapshotIDs(ids...) } // ClearVehicleSnapshots clears all "vehicle_snapshots" edges to the VehicleSnapshot entity. @@ -892,12 +892,12 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } _spec.Edges.Add = append(_spec.Edges.Add, edge) } - if auo.mutation.SnapshotsCleared() { + if auo.mutation.AccountSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.SnapshotsTable, - Columns: []string{account.SnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), @@ -905,12 +905,12 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := auo.mutation.RemovedSnapshotsIDs(); len(nodes) > 0 && !auo.mutation.SnapshotsCleared() { + if nodes := auo.mutation.RemovedAccountSnapshotsIDs(); len(nodes) > 0 && !auo.mutation.AccountSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.SnapshotsTable, - Columns: []string{account.SnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), @@ -921,12 +921,12 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := auo.mutation.SnapshotsIDs(); len(nodes) > 0 { + if nodes := auo.mutation.AccountSnapshotsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.SnapshotsTable, - Columns: []string{account.SnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), diff --git a/internal/database/ent/db/client.go b/internal/database/ent/db/client.go index 9bceec9e..e77ab304 100644 --- a/internal/database/ent/db/client.go +++ b/internal/database/ent/db/client.go @@ -449,15 +449,15 @@ func (c *AccountClient) QueryClan(a *Account) *ClanQuery { return query } -// QuerySnapshots queries the snapshots edge of a Account. -func (c *AccountClient) QuerySnapshots(a *Account) *AccountSnapshotQuery { +// QueryAccountSnapshots queries the account_snapshots edge of a Account. +func (c *AccountClient) QueryAccountSnapshots(a *Account) *AccountSnapshotQuery { query := (&AccountSnapshotClient{config: c.config}).Query() query.path = func(context.Context) (fromV *sql.Selector, _ error) { id := a.ID step := sqlgraph.NewStep( sqlgraph.From(account.Table, account.FieldID, id), sqlgraph.To(accountsnapshot.Table, accountsnapshot.FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, account.SnapshotsTable, account.SnapshotsColumn), + sqlgraph.Edge(sqlgraph.O2M, false, account.AccountSnapshotsTable, account.AccountSnapshotsColumn), ) fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) return fromV, nil diff --git a/internal/database/ent/db/crontask/crontask.go b/internal/database/ent/db/crontask/crontask.go index 34eaf930..7c6b7ab4 100644 --- a/internal/database/ent/db/crontask/crontask.go +++ b/internal/database/ent/db/crontask/crontask.go @@ -76,6 +76,8 @@ var ( UpdateDefaultUpdatedAt func() time.Time // ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. ReferenceIDValidator func(string) error + // DefaultTriesLeft holds the default value on creation for the "tries_left" field. + DefaultTriesLeft int // DefaultID holds the default value on creation for the "id" field. DefaultID func() string ) diff --git a/internal/database/ent/db/crontask_create.go b/internal/database/ent/db/crontask_create.go index 085e2871..2936ce48 100644 --- a/internal/database/ent/db/crontask_create.go +++ b/internal/database/ent/db/crontask_create.go @@ -91,6 +91,14 @@ func (ctc *CronTaskCreate) SetTriesLeft(i int) *CronTaskCreate { return ctc } +// SetNillableTriesLeft sets the "tries_left" field if the given value is not nil. +func (ctc *CronTaskCreate) SetNillableTriesLeft(i *int) *CronTaskCreate { + if i != nil { + ctc.SetTriesLeft(*i) + } + return ctc +} + // SetLogs sets the "logs" field. func (ctc *CronTaskCreate) SetLogs(ml []models.TaskLog) *CronTaskCreate { ctc.mutation.SetLogs(ml) @@ -160,6 +168,10 @@ func (ctc *CronTaskCreate) defaults() { v := crontask.DefaultUpdatedAt() ctc.mutation.SetUpdatedAt(v) } + if _, ok := ctc.mutation.TriesLeft(); !ok { + v := crontask.DefaultTriesLeft + ctc.mutation.SetTriesLeft(v) + } if _, ok := ctc.mutation.ID(); !ok { v := crontask.DefaultID() ctc.mutation.SetID(v) diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index 82bd0479..8017db8f 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -82,7 +82,7 @@ var ( PrimaryKey: []*schema.Column{AccountSnapshotsColumns[0]}, ForeignKeys: []*schema.ForeignKey{ { - Symbol: "account_snapshots_accounts_snapshots", + Symbol: "account_snapshots_accounts_account_snapshots", Columns: []*schema.Column{AccountSnapshotsColumns[10]}, RefColumns: []*schema.Column{AccountsColumns[0]}, OnDelete: schema.NoAction, @@ -262,7 +262,7 @@ var ( {Name: "status", Type: field.TypeEnum, Enums: []string{"TASK_SCHEDULED", "TASK_IN_PROGRESS", "TASK_COMPLETE", "TASK_FAILED"}}, {Name: "scheduled_after", Type: field.TypeTime}, {Name: "last_run", Type: field.TypeTime}, - {Name: "tries_left", Type: field.TypeInt}, + {Name: "tries_left", Type: field.TypeInt, Default: 0}, {Name: "logs", Type: field.TypeJSON}, {Name: "data", Type: field.TypeJSON}, } diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go index 024970b4..23952010 100644 --- a/internal/database/ent/db/mutation.go +++ b/internal/database/ent/db/mutation.go @@ -74,9 +74,9 @@ type AccountMutation struct { clearedFields map[string]struct{} clan *string clearedclan bool - snapshots map[string]struct{} - removedsnapshots map[string]struct{} - clearedsnapshots bool + account_snapshots map[string]struct{} + removedaccount_snapshots map[string]struct{} + clearedaccount_snapshots bool vehicle_snapshots map[string]struct{} removedvehicle_snapshots map[string]struct{} clearedvehicle_snapshots bool @@ -520,58 +520,58 @@ func (m *AccountMutation) ResetClan() { m.clearedclan = false } -// AddSnapshotIDs adds the "snapshots" edge to the AccountSnapshot entity by ids. -func (m *AccountMutation) AddSnapshotIDs(ids ...string) { - if m.snapshots == nil { - m.snapshots = make(map[string]struct{}) +// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by ids. +func (m *AccountMutation) AddAccountSnapshotIDs(ids ...string) { + if m.account_snapshots == nil { + m.account_snapshots = make(map[string]struct{}) } for i := range ids { - m.snapshots[ids[i]] = struct{}{} + m.account_snapshots[ids[i]] = struct{}{} } } -// ClearSnapshots clears the "snapshots" edge to the AccountSnapshot entity. -func (m *AccountMutation) ClearSnapshots() { - m.clearedsnapshots = true +// ClearAccountSnapshots clears the "account_snapshots" edge to the AccountSnapshot entity. +func (m *AccountMutation) ClearAccountSnapshots() { + m.clearedaccount_snapshots = true } -// SnapshotsCleared reports if the "snapshots" edge to the AccountSnapshot entity was cleared. -func (m *AccountMutation) SnapshotsCleared() bool { - return m.clearedsnapshots +// AccountSnapshotsCleared reports if the "account_snapshots" edge to the AccountSnapshot entity was cleared. +func (m *AccountMutation) AccountSnapshotsCleared() bool { + return m.clearedaccount_snapshots } -// RemoveSnapshotIDs removes the "snapshots" edge to the AccountSnapshot entity by IDs. -func (m *AccountMutation) RemoveSnapshotIDs(ids ...string) { - if m.removedsnapshots == nil { - m.removedsnapshots = make(map[string]struct{}) +// RemoveAccountSnapshotIDs removes the "account_snapshots" edge to the AccountSnapshot entity by IDs. +func (m *AccountMutation) RemoveAccountSnapshotIDs(ids ...string) { + if m.removedaccount_snapshots == nil { + m.removedaccount_snapshots = make(map[string]struct{}) } for i := range ids { - delete(m.snapshots, ids[i]) - m.removedsnapshots[ids[i]] = struct{}{} + delete(m.account_snapshots, ids[i]) + m.removedaccount_snapshots[ids[i]] = struct{}{} } } -// RemovedSnapshots returns the removed IDs of the "snapshots" edge to the AccountSnapshot entity. -func (m *AccountMutation) RemovedSnapshotsIDs() (ids []string) { - for id := range m.removedsnapshots { +// RemovedAccountSnapshots returns the removed IDs of the "account_snapshots" edge to the AccountSnapshot entity. +func (m *AccountMutation) RemovedAccountSnapshotsIDs() (ids []string) { + for id := range m.removedaccount_snapshots { ids = append(ids, id) } return } -// SnapshotsIDs returns the "snapshots" edge IDs in the mutation. -func (m *AccountMutation) SnapshotsIDs() (ids []string) { - for id := range m.snapshots { +// AccountSnapshotsIDs returns the "account_snapshots" edge IDs in the mutation. +func (m *AccountMutation) AccountSnapshotsIDs() (ids []string) { + for id := range m.account_snapshots { ids = append(ids, id) } return } -// ResetSnapshots resets all changes to the "snapshots" edge. -func (m *AccountMutation) ResetSnapshots() { - m.snapshots = nil - m.clearedsnapshots = false - m.removedsnapshots = nil +// ResetAccountSnapshots resets all changes to the "account_snapshots" edge. +func (m *AccountMutation) ResetAccountSnapshots() { + m.account_snapshots = nil + m.clearedaccount_snapshots = false + m.removedaccount_snapshots = nil } // AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by ids. @@ -947,8 +947,8 @@ func (m *AccountMutation) AddedEdges() []string { if m.clan != nil { edges = append(edges, account.EdgeClan) } - if m.snapshots != nil { - edges = append(edges, account.EdgeSnapshots) + if m.account_snapshots != nil { + edges = append(edges, account.EdgeAccountSnapshots) } if m.vehicle_snapshots != nil { edges = append(edges, account.EdgeVehicleSnapshots) @@ -967,9 +967,9 @@ func (m *AccountMutation) AddedIDs(name string) []ent.Value { if id := m.clan; id != nil { return []ent.Value{*id} } - case account.EdgeSnapshots: - ids := make([]ent.Value, 0, len(m.snapshots)) - for id := range m.snapshots { + case account.EdgeAccountSnapshots: + ids := make([]ent.Value, 0, len(m.account_snapshots)) + for id := range m.account_snapshots { ids = append(ids, id) } return ids @@ -992,8 +992,8 @@ func (m *AccountMutation) AddedIDs(name string) []ent.Value { // RemovedEdges returns all edge names that were removed in this mutation. func (m *AccountMutation) RemovedEdges() []string { edges := make([]string, 0, 4) - if m.removedsnapshots != nil { - edges = append(edges, account.EdgeSnapshots) + if m.removedaccount_snapshots != nil { + edges = append(edges, account.EdgeAccountSnapshots) } if m.removedvehicle_snapshots != nil { edges = append(edges, account.EdgeVehicleSnapshots) @@ -1008,9 +1008,9 @@ func (m *AccountMutation) RemovedEdges() []string { // the given name in this mutation. func (m *AccountMutation) RemovedIDs(name string) []ent.Value { switch name { - case account.EdgeSnapshots: - ids := make([]ent.Value, 0, len(m.removedsnapshots)) - for id := range m.removedsnapshots { + case account.EdgeAccountSnapshots: + ids := make([]ent.Value, 0, len(m.removedaccount_snapshots)) + for id := range m.removedaccount_snapshots { ids = append(ids, id) } return ids @@ -1036,8 +1036,8 @@ func (m *AccountMutation) ClearedEdges() []string { if m.clearedclan { edges = append(edges, account.EdgeClan) } - if m.clearedsnapshots { - edges = append(edges, account.EdgeSnapshots) + if m.clearedaccount_snapshots { + edges = append(edges, account.EdgeAccountSnapshots) } if m.clearedvehicle_snapshots { edges = append(edges, account.EdgeVehicleSnapshots) @@ -1054,8 +1054,8 @@ func (m *AccountMutation) EdgeCleared(name string) bool { switch name { case account.EdgeClan: return m.clearedclan - case account.EdgeSnapshots: - return m.clearedsnapshots + case account.EdgeAccountSnapshots: + return m.clearedaccount_snapshots case account.EdgeVehicleSnapshots: return m.clearedvehicle_snapshots case account.EdgeAchievementSnapshots: @@ -1082,8 +1082,8 @@ func (m *AccountMutation) ResetEdge(name string) error { case account.EdgeClan: m.ResetClan() return nil - case account.EdgeSnapshots: - m.ResetSnapshots() + case account.EdgeAccountSnapshots: + m.ResetAccountSnapshots() return nil case account.EdgeVehicleSnapshots: m.ResetVehicleSnapshots() diff --git a/internal/database/ent/db/runtime.go b/internal/database/ent/db/runtime.go index 61052e6b..7e0f56ad 100644 --- a/internal/database/ent/db/runtime.go +++ b/internal/database/ent/db/runtime.go @@ -201,6 +201,10 @@ func init() { crontaskDescReferenceID := crontaskFields[4].Descriptor() // crontask.ReferenceIDValidator is a validator for the "reference_id" field. It is called by the builders before save. crontask.ReferenceIDValidator = crontaskDescReferenceID.Validators[0].(func(string) error) + // crontaskDescTriesLeft is the schema descriptor for tries_left field. + crontaskDescTriesLeft := crontaskFields[9].Descriptor() + // crontask.DefaultTriesLeft holds the default value on creation for the tries_left field. + crontask.DefaultTriesLeft = crontaskDescTriesLeft.Default.(int) // crontaskDescID is the schema descriptor for id field. crontaskDescID := crontaskFields[0].Descriptor() // crontask.DefaultID holds the default value on creation for the id field. diff --git a/internal/database/ent/schema/account.go b/internal/database/ent/schema/account.go index b9518e57..c27e22df 100644 --- a/internal/database/ent/schema/account.go +++ b/internal/database/ent/schema/account.go @@ -41,9 +41,9 @@ func (Account) Fields() []ent.Field { func (Account) Edges() []ent.Edge { return []ent.Edge{ edge.From("clan", Clan.Type).Ref("accounts").Field("clan_id").Unique(), - edge.To("snapshots", AccountSnapshot.Type), - edge.To("vehicle_snapshots", VehicleSnapshot.Type), edge.To("achievement_snapshots", AchievementsSnapshot.Type), + edge.To("vehicle_snapshots", VehicleSnapshot.Type), + edge.To("account_snapshots", AccountSnapshot.Type), } } diff --git a/internal/database/ent/schema/account_snapshot.go b/internal/database/ent/schema/account_snapshot.go index b7bff591..738d8386 100644 --- a/internal/database/ent/schema/account_snapshot.go +++ b/internal/database/ent/schema/account_snapshot.go @@ -35,7 +35,7 @@ func (AccountSnapshot) Fields() []ent.Field { // Edges of the AccountSnapshot. func (AccountSnapshot) Edges() []ent.Edge { return []ent.Edge{ - edge.From("account", Account.Type).Ref("snapshots").Field("account_id").Required().Immutable().Unique(), + edge.From("account", Account.Type).Ref("account_snapshots").Field("account_id").Required().Immutable().Unique(), } } diff --git a/internal/database/ent/schema/achievements_snapshot.go b/internal/database/ent/schema/achievements_snapshot.go index b5f24775..9c9c1158 100644 --- a/internal/database/ent/schema/achievements_snapshot.go +++ b/internal/database/ent/schema/achievements_snapshot.go @@ -9,12 +9,10 @@ import ( "github.com/cufee/am-wg-proxy-next/v2/types" ) -// AchievementsSnapshot holds the schema definition for the AchievementsSnapshot entity. type AchievementsSnapshot struct { ent.Schema } -// Fields of the AchievementsSnapshot. func (AchievementsSnapshot) Fields() []ent.Field { return append(defaultFields, field.Enum("type"). @@ -28,7 +26,6 @@ func (AchievementsSnapshot) Fields() []ent.Field { ) } -// Edges of the AchievementsSnapshot. func (AchievementsSnapshot) Edges() []ent.Edge { return []ent.Edge{ edge.From("account", Account.Type).Ref("achievement_snapshots").Field("account_id").Required().Immutable().Unique(), diff --git a/internal/database/models/achievements_snapshot.go b/internal/database/models/achievements_snapshot.go new file mode 100644 index 00000000..11872587 --- /dev/null +++ b/internal/database/models/achievements_snapshot.go @@ -0,0 +1,19 @@ +package models + +import ( + "time" + + "github.com/cufee/am-wg-proxy-next/v2/types" +) + +type AchievementsSnapshot struct { + ID string + Type SnapshotType + CreatedAt time.Time + AccountID string + ReferenceID string // accountID or vehicleID + + Battles int + LastBattleTime time.Time + Data types.AchievementsFrame +} diff --git a/internal/database/models/task.go b/internal/database/models/task.go index 9ded8cd4..9ea7ed03 100644 --- a/internal/database/models/task.go +++ b/internal/database/models/task.go @@ -9,8 +9,6 @@ type TaskType string const ( TaskTypeUpdateClans TaskType = "UPDATE_CLANS" TaskTypeRecordSnapshots TaskType = "RECORD_SNAPSHOTS" - // TaskTypeUpdateAccountWN8 TaskType = "UPDATE_ACCOUNT_WN8" - // TaskTypeRecordPlayerAchievements TaskType = "UPDATE_ACCOUNT_ACHIEVEMENTS" TaskTypeDatabaseCleanup TaskType = "CLEANUP_DATABASE" ) @@ -21,9 +19,6 @@ func (TaskType) Values() []string { for _, s := range []TaskType{ TaskTypeUpdateClans, TaskTypeRecordSnapshots, - // TaskTypeUpdateAccountWN8, - // TaskTypeRecordPlayerAchievements, - // TaskTypeDatabaseCleanup, } { kinds = append(kinds, string(s)) diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 8d9e4fb7..25902362 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -7,6 +7,7 @@ import ( "entgo.io/ent/dialect/sql" "github.com/cufee/aftermath/internal/database/ent/db" "github.com/cufee/aftermath/internal/database/ent/db/accountsnapshot" + "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" "github.com/cufee/aftermath/internal/database/ent/db/predicate" "github.com/cufee/aftermath/internal/database/ent/db/vehiclesnapshot" "github.com/cufee/aftermath/internal/database/models" @@ -259,5 +260,116 @@ func (c *client) GetManyAccountSnapshots(ctx context.Context, accountIDs []strin } return snapshots, nil +} + +func toAchievementsSnapshot(record *db.AchievementsSnapshot) models.AchievementsSnapshot { + return models.AchievementsSnapshot{ + ID: record.ID, + Type: record.Type, + CreatedAt: record.CreatedAt, + LastBattleTime: record.LastBattleTime, + ReferenceID: record.ReferenceID, + AccountID: record.AccountID, + Battles: record.Battles, + Data: record.Data, + } +} + +func (c *client) CreateAccountAchievementSnapshots(ctx context.Context, accountID string, snapshots ...models.AchievementsSnapshot) (map[string]error, error) { + if len(snapshots) < 1 { + return nil, nil + } + + var errors = make(map[string]error) + for _, data := range snapshots { + // make a transaction per write to avoid locking for too long + err := c.withTx(ctx, func(tx *db.Tx) error { + return c.db.AchievementsSnapshot.Create(). + SetType(data.Type). + SetData(data.Data). + SetBattles(data.Battles). + SetCreatedAt(data.CreatedAt). + SetReferenceID(data.ReferenceID). + SetLastBattleTime(data.LastBattleTime). + SetAccount(c.db.Account.GetX(ctx, accountID)). + Exec(ctx) + }) + if err != nil { + errors[data.ReferenceID] = err + } + } + + if len(errors) > 0 { + return errors, nil + } + return nil, nil +} + +func (c *client) GetAchievementsSnapshots(ctx context.Context, accountID string, kind models.SnapshotType, options ...SnapshotQuery) ([]models.AchievementsSnapshot, error) { + var query getSnapshotQuery + for _, apply := range options { + apply(&query) + } + + // this query is impossible to build using the db.AchievementSnapshot methods, so we do it manually + var innerWhere []*sql.Predicate + innerWhere = append(innerWhere, + sql.EQ(achievementssnapshot.FieldType, kind), + sql.EQ(achievementssnapshot.FieldAccountID, accountID), + ) + innerOrder := sql.Desc(achievementssnapshot.FieldCreatedAt) + + if query.createdAfter != nil { + innerWhere = append(innerWhere, sql.GT(achievementssnapshot.FieldCreatedAt, *query.createdAfter)) + innerOrder = sql.Asc(achievementssnapshot.FieldCreatedAt) + } + if query.createdBefore != nil { + innerWhere = append(innerWhere, sql.LT(achievementssnapshot.FieldCreatedAt, *query.createdBefore)) + innerOrder = sql.Desc(achievementssnapshot.FieldCreatedAt) + } + if len(query.vehicleIDs) > 0 { + var ids []any + for _, id := range query.vehicleIDs { + ids = append(ids, id) + } + innerWhere = append(innerWhere, sql.In(achievementssnapshot.FieldReferenceID, append(ids, accountID)...)) + } + + innerQuery := sql.Select(achievementssnapshot.Columns...).From(sql.Table(achievementssnapshot.Table)) + innerQuery = innerQuery.Where(sql.And(innerWhere...)) + innerQuery = innerQuery.OrderBy(innerOrder) + + innerQueryString, innerQueryArgs := innerQuery.Query() + + // wrap the inner query in a GROUP BY + wrapper := &sql.Builder{} + wrapped := wrapper.Wrap(func(b *sql.Builder) { b.WriteString(innerQueryString) }) + queryString, _ := sql.Select("*").FromExpr(wrapped).GroupBy(achievementssnapshot.FieldReferenceID).Query() + + rows, err := c.db.AchievementsSnapshot.QueryContext(ctx, queryString, innerQueryArgs...) + if err != nil { + return nil, err + } + defer rows.Close() + var snapshots []models.AchievementsSnapshot + for rows.Next() { + var record db.AchievementsSnapshot + + values, err := record.ScanValues(achievementssnapshot.Columns) + if err != nil { + return nil, err + } + if err := rows.Scan(values...); err != nil { + return nil, err + } + + err = record.AssignValues(achievementssnapshot.Columns, values) + if err != nil { + return nil, err + } + snapshots = append(snapshots, toAchievementsSnapshot(&record)) + } + + return snapshots, nil } diff --git a/internal/logic/snapshots.go b/internal/logic/snapshots.go index 73ed394b..c71db3f1 100644 --- a/internal/logic/snapshots.go +++ b/internal/logic/snapshots.go @@ -16,23 +16,26 @@ import ( "github.com/rs/zerolog/log" ) -type vehicleResponseData map[string][]types.VehicleStatsFrame - -func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbClient database.Client, realm string, force bool, accountIDs ...string) (map[string]error, error) { - if len(accountIDs) < 1 { - return nil, nil - } +type vehicleBattleData struct { + LastBattle time.Time + Battles int +} - createdAt := time.Now() +/* +Gets all accounts from WH and their respective snapshots + - an account is considered valid is it has played a battle since the last snapshot, or has no snapshots + - as side effect, invalid accounts will be set as private +*/ +func getAndValidateAccounts(ctx context.Context, wgClient wargaming.Client, dbClient database.Client, force bool, realm string, accountIDs ...string) (map[string]types.ExtendedAccount, []string, map[string]*models.AccountSnapshot, error) { accounts, err := wgClient.BatchAccountByID(ctx, realm, accountIDs) if err != nil { - return nil, errors.Wrap(err, "failed to fetch accounts") + return nil, nil, nil, errors.Wrap(err, "failed to fetch accounts") } // existing snapshots for accounts existingSnapshots, err := dbClient.GetManyAccountSnapshots(ctx, accountIDs, models.SnapshotTypeDaily) if err != nil && !database.IsNotFound(err) { - return nil, errors.Wrap(err, "failed to get existing snapshots") + return nil, nil, nil, errors.Wrap(err, "failed to get existing snapshots") } existingSnapshotsMap := make(map[string]*models.AccountSnapshot) for _, snapshot := range existingSnapshots { @@ -40,7 +43,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } // make a new slice just in case some accounts were not returned/are private - var validAccounts []string + var needAnUpdate []string for _, id := range accountIDs { data, ok := accounts[id] if !ok { @@ -63,85 +66,182 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl // last snapshot is the same, we can skip it continue } - validAccounts = append(validAccounts, id) + needAnUpdate = append(needAnUpdate, id) } + return accounts, needAnUpdate, existingSnapshotsMap, nil +} - if len(validAccounts) < 1 { +func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbClient database.Client, realm string, force bool, accountIDs ...string) (map[string]error, error) { + if len(accountIDs) < 1 { return nil, nil } + createdAt := time.Now() - // clans are optional-ish - clans, err := wgClient.BatchAccountClan(ctx, realm, validAccounts) + accounts, accountsNeedAnUpdate, _, err := getAndValidateAccounts(ctx, wgClient, dbClient, force, realm, accountIDs...) if err != nil { - log.Err(err).Msg("failed to get batch account clans") - clans = make(map[string]types.ClanMember) + return nil, err + } + if len(accountsNeedAnUpdate) < 1 { + return nil, nil } - vehicleCh := make(chan retry.DataWithErr[vehicleResponseData], len(validAccounts)) var group sync.WaitGroup - group.Add(len(validAccounts)) - for _, id := range validAccounts { + var clans = make(map[string]types.ClanMember) + var accountAchievements retry.DataWithErr[map[string]types.AchievementsFrame] + + var accountVehiclesMx sync.Mutex + var accountVehicleAchievementsMx sync.Mutex + accountVehicles := make(map[string]retry.DataWithErr[[]types.VehicleStatsFrame], len(accountsNeedAnUpdate)) + accountVehicleAchievements := make(map[string]retry.DataWithErr[map[string]types.AchievementsFrame], len(accountsNeedAnUpdate)) + + for _, id := range accountsNeedAnUpdate { + group.Add(1) + // get account vehicle stats go func(id string) { defer group.Done() data, err := wgClient.AccountVehicles(ctx, realm, id) - vehicleCh <- retry.DataWithErr[vehicleResponseData]{Data: vehicleResponseData{id: data}, Err: err} + + accountVehiclesMx.Lock() + accountVehicles[id] = retry.DataWithErr[[]types.VehicleStatsFrame]{Data: data, Err: err} + accountVehiclesMx.Unlock() + }(id) + + group.Add(1) + // get account vehicle achievements + go func(id string) { + defer group.Done() + data, err := wgClient.AccountVehicleAchievements(ctx, realm, id) + + accountVehicleAchievementsMx.Lock() + accountVehicleAchievements[id] = retry.DataWithErr[map[string]types.AchievementsFrame]{Data: data, Err: err} + accountVehicleAchievementsMx.Unlock() }(id) } + + group.Add(1) + // get account clans, not critical + go func() { + defer group.Done() + // clans are optional-ish + data, err := wgClient.BatchAccountClan(ctx, realm, accountsNeedAnUpdate) + if err != nil { + log.Err(err).Msg("failed to get batch account clans") + } + clans = data + }() + + group.Add(1) + // get account achievements, not critical + go func() { + defer group.Done() + // clans are optional-ish + data, err := wgClient.BatchAccountAchievements(ctx, realm, accountsNeedAnUpdate) + if err != nil { + log.Err(err).Msg("failed to get batch account achievements") + } + accountAchievements.Data = data + accountAchievements.Err = err + }() + group.Wait() - close(vehicleCh) var accountErrors = make(map[string]error) var accountUpdates = make(map[string]models.Account) var accountSnapshots []models.AccountSnapshot var vehicleSnapshots = make(map[string][]models.VehicleSnapshot) - for result := range vehicleCh { - // there is only 1 key in this map - for id, vehicles := range result.Data { - if result.Err != nil { - accountErrors[id] = result.Err - continue - } - existingSnapshots, err := dbClient.GetVehicleSnapshots(ctx, id, id, models.SnapshotTypeDaily) - if err != nil && !database.IsNotFound(err) { - accountErrors[id] = err - continue - } - existingSnapshotsMap := make(map[string]*models.VehicleSnapshot) - for _, snapshot := range existingSnapshots { - existingSnapshotsMap[snapshot.VehicleID] = &snapshot - } + var achievementsSnapshots = make(map[string][]models.AchievementsSnapshot) - stats := fetch.WargamingToStats(realm, accounts[id], clans[id], vehicles) + for _, accountID := range accountsNeedAnUpdate { + vehicles := accountVehicles[accountID] + if vehicles.Err != nil { + accountErrors[accountID] = vehicles.Err + continue + } + + // get existing vehicle snapshots from db + existingSnapshots, err := dbClient.GetVehicleSnapshots(ctx, accountID, accountID, models.SnapshotTypeDaily) + if err != nil && !database.IsNotFound(err) { + accountErrors[accountID] = err + continue + } + existingSnapshotsMap := make(map[string]*models.VehicleSnapshot) + for _, snapshot := range existingSnapshots { + existingSnapshotsMap[snapshot.VehicleID] = &snapshot + } + + snapshotStats := fetch.WargamingToStats(realm, accounts[accountID], clans[accountID], vehicles.Data) + { // account snapshot accountSnapshots = append(accountSnapshots, models.AccountSnapshot{ Type: models.SnapshotTypeDaily, CreatedAt: createdAt, - AccountID: stats.Account.ID, - ReferenceID: stats.Account.ID, - LastBattleTime: stats.LastBattleTime, - RatingBattles: stats.RatingBattles.StatsFrame, - RegularBattles: stats.RegularBattles.StatsFrame, + AccountID: snapshotStats.Account.ID, + ReferenceID: snapshotStats.Account.ID, + LastBattleTime: snapshotStats.LastBattleTime, + RatingBattles: snapshotStats.RatingBattles.StatsFrame, + RegularBattles: snapshotStats.RegularBattles.StatsFrame, }) - accountUpdates[id] = stats.Account + accountUpdates[accountID] = snapshotStats.Account + } - if len(vehicles) < 1 { - continue - } - vehicleStats := fetch.WargamingVehiclesToFrame(vehicles) + // vehicle snapshots + var vehicleLastBattleTimes = make(map[string]vehicleBattleData) + vehicleStats := fetch.WargamingVehiclesToFrame(vehicles.Data) + if len(vehicles.Data) > 0 { for id, vehicle := range vehicleStats { if s, ok := existingSnapshotsMap[id]; !force && ok && s.LastBattleTime.Equal(vehicle.LastBattleTime) { // last snapshot is the same, we can skip it continue } - - vehicleSnapshots[stats.Account.ID] = append(vehicleSnapshots[stats.Account.ID], models.VehicleSnapshot{ + vehicleLastBattleTimes[id] = vehicleBattleData{vehicle.LastBattleTime, int(vehicle.Battles.Float())} + vehicleSnapshots[accountID] = append(vehicleSnapshots[accountID], models.VehicleSnapshot{ Type: models.SnapshotTypeDaily, LastBattleTime: vehicle.LastBattleTime, Stats: *vehicle.StatsFrame, VehicleID: vehicle.VehicleID, - AccountID: stats.Account.ID, - ReferenceID: stats.Account.ID, + AccountID: accountID, + ReferenceID: accountID, + CreatedAt: createdAt, + }) + } + } + + // account achievement snapshot + if accountAchievements.Err != nil { + accountErrors[accountID] = errors.Wrap(accountAchievements.Err, "failed to get account achievements") + } + if achievements, ok := accountAchievements.Data[accountID]; ok { + achievementsSnapshots[accountID] = append(achievementsSnapshots[accountID], models.AchievementsSnapshot{ + Data: achievements, + CreatedAt: createdAt, + AccountID: accountID, + ReferenceID: accountID, + Type: models.SnapshotTypeDaily, + LastBattleTime: snapshotStats.LastBattleTime, + Battles: int(snapshotStats.RegularBattles.Battles.Float()), + }) + } + + // account vehicle achievement snapshots + if achievements, ok := accountVehicleAchievements[accountID]; ok { + if achievements.Err != nil { + accountErrors[accountID] = errors.Wrap(achievements.Err, "failed to get vehicle achievements") + } + for vehicleID, a := range achievements.Data { + battleData, ok := vehicleLastBattleTimes[vehicleID] + if !ok { + // vehicle was not played, no need to save achievements + continue + } + + achievementsSnapshots[accountID] = append(achievementsSnapshots[accountID], models.AchievementsSnapshot{ + Data: a, CreatedAt: createdAt, + AccountID: accountID, + ReferenceID: vehicleID, + Battles: battleData.Battles, + LastBattleTime: battleData.LastBattle, + Type: models.SnapshotTypeDaily, }) } } @@ -162,6 +262,27 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } } + achievements := achievementsSnapshots[accountSnapshot.AccountID] + if len(achievements) > 0 { + achErr, err := dbClient.CreateAccountAchievementSnapshots(ctx, accountSnapshot.AccountID, achievements...) + if err != nil { + accountErrors[accountSnapshot.AccountID] = errors.Wrap(err, "failed to save account achievements to database") + continue + } + + for id, e := range achErr { + if e != nil { + err = errors.Wrapf(e, "failed to save some achievement snapshots, id %s", id) + break + } + } + if err != nil { + accountErrors[accountSnapshot.AccountID] = err + continue + } + + } + // save account snapshot aErr, err := dbClient.CreateAccountSnapshots(ctx, accountSnapshot) if err != nil { diff --git a/tests/static_database.go b/tests/static_database.go index 29fdfab6..8c18c841 100644 --- a/tests/static_database.go +++ b/tests/static_database.go @@ -111,6 +111,12 @@ func (c *staticTestingDatabase) CreateAccountVehicleSnapshots(ctx context.Contex func (c *staticTestingDatabase) DeleteExpiredSnapshots(ctx context.Context, expiration time.Time) error { return errors.New("DeleteExpiredSnapshots not implemented") } +func (c *staticTestingDatabase) CreateAccountAchievementSnapshots(ctx context.Context, accountID string, snapshots ...models.AchievementsSnapshot) (map[string]error, error) { + return nil, errors.New("CreateAccountAchievementSnapshots not implemented") +} +func (c *staticTestingDatabase) GetAchievementsSnapshots(ctx context.Context, accountID string, kind models.SnapshotType, options ...database.SnapshotQuery) ([]models.AchievementsSnapshot, error) { + return nil, errors.New("GetAchievementsSnapshots not implemented") +} func (c *staticTestingDatabase) CreateTasks(ctx context.Context, tasks ...models.Task) error { return errors.New("not implemented") From ccb20db5b67a7b4c3fdb0be5dbaaf6a44789cf4f Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 28 Jun 2024 21:23:03 -0400 Subject: [PATCH 217/341] fixing a memory leak --- cmd/core/queue/queue.go | 6 +++--- cmd/discord/common/reply.go | 4 ++-- cmd/discord/router/handler.go | 17 +++++++++-------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index 09f8c6a2..68561820 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -25,7 +25,7 @@ type queue struct { workerLimiter chan struct{} workerTimeout time.Duration - activeTasks map[string]struct{} + activeTasks map[string]*struct{} activeTasksMx *sync.Mutex newCoreClient func() (core.Client, error) } @@ -40,7 +40,7 @@ func New(workerLimit int, newCoreClient func() (core.Client, error)) *queue { workerLimiter: make(chan struct{}, workerLimit), activeTasksMx: &sync.Mutex{}, - activeTasks: make(map[string]struct{}), + activeTasks: make(map[string]*struct{}, workerLimit*2), queueLock: &sync.Mutex{}, queued: make(chan models.Task, workerLimit*2), @@ -152,7 +152,7 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { go func() { q.workerLimiter <- struct{}{} q.activeTasksMx.Lock() - q.activeTasks[task.ID] = struct{}{} + q.activeTasks[task.ID] = &struct{}{} q.activeTasksMx.Unlock() defer func() { <-q.workerLimiter diff --git a/cmd/discord/common/reply.go b/cmd/discord/common/reply.go index 70fe2080..4e608a65 100644 --- a/cmd/discord/common/reply.go +++ b/cmd/discord/common/reply.go @@ -57,10 +57,10 @@ func (r reply) Embed(embeds ...*discordgo.MessageEmbed) reply { func (r reply) Send(content ...string) error { r.text = append(r.text, content...) - return r.ctx.respond(r.data(r.ctx.Localize)) + return r.ctx.respond(r.data()) } -func (r reply) data(localePrinter func(string) string) discordgo.InteractionResponseData { +func (r reply) data() discordgo.InteractionResponseData { var content []string for _, t := range r.text { content = append(content, r.ctx.Localize(t)) diff --git a/cmd/discord/router/handler.go b/cmd/discord/router/handler.go index 3f913d94..419f55ac 100644 --- a/cmd/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "net/http" + "runtime/debug" "sync" "time" @@ -208,14 +209,14 @@ func (router *Router) startInteractionHandlerAsync(interaction discordgo.Interac responseCh := make(chan discordgo.InteractionResponseData) go func() { - // defer func() { - // if r := recover(); r != nil { - // log.Error().Str("stack", string(debug.Stack())).Msg("panic in interaction handler") - // state.mx.Lock() - // router.sendInteractionReply(interaction, state, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed. Please try again in a few seconds."}) - // state.mx.Unlock() - // } - // }() + defer func() { + if r := recover(); r != nil { + log.Error().Str("stack", string(debug.Stack())).Msg("panic in interaction handler") + state.mx.Lock() + router.sendInteractionReply(interaction, state, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed. Please try again in a few seconds."}) + state.mx.Unlock() + } + }() router.handleInteraction(workerCtx, cancelWorker, interaction, command, responseCh) }() From 5ab12859b36b90057ae413fb4bbfc26dfe18791d Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 29 Jun 2024 17:38:26 -0400 Subject: [PATCH 218/341] hopefully fixed a leak --- cmd/core/queue/queue.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index 68561820..037c2662 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -134,13 +134,12 @@ func (q *queue) pullAndEnqueueTasks(ctx context.Context, db database.Client) err if err != nil && !database.IsNotFound(err) { return err } - log.Debug().Msgf("pulled %d tasks into queue", len(tasks)) - go func(tasks []models.Task) { - for _, t := range tasks { - q.Enqueue(t) - } - }(tasks) + log.Debug().Msgf("adding %d tasks into queue", len(tasks)) + for _, t := range tasks { + q.Enqueue(t) + } + log.Debug().Msgf("added %d tasks into queue", len(tasks)) return nil } From 638f3aef5a375737747488edc1fce5470ae93497 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 11:27:32 -0400 Subject: [PATCH 219/341] enabled pprof --- internal/database/snapshots.go | 23 +++++++++++++++++++---- main.go | 10 ++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 25902362..d6e3fcb6 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -56,6 +56,11 @@ func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID st return nil, nil } + account, err := c.db.Account.Get(ctx, accountID) + if err != nil { + return nil, err + } + var errors = make(map[string]error) for _, data := range snapshots { // make a transaction per write to avoid locking for too long @@ -68,7 +73,7 @@ func (c *client) CreateAccountVehicleSnapshots(ctx context.Context, accountID st SetCreatedAt(data.CreatedAt). SetBattles(int(data.Stats.Battles.Float())). SetLastBattleTime(data.LastBattleTime). - SetAccount(c.db.Account.GetX(ctx, accountID)). + SetAccount(account). Exec(ctx) }) if err != nil { @@ -172,10 +177,15 @@ func (c *client) CreateAccountSnapshots(ctx context.Context, snapshots ...models var errors = make(map[string]error) for _, s := range snapshots { + account, err := c.db.Account.Get(ctx, s.AccountID) + if err != nil { + errors[s.AccountID] = err + continue + } // make a transaction per write to avoid locking for too long - err := c.withTx(ctx, func(tx *db.Tx) error { + err = c.withTx(ctx, func(tx *db.Tx) error { return c.db.AccountSnapshot.Create(). - SetAccount(c.db.Account.GetX(ctx, s.AccountID)). // we assume the account exists here + SetAccount(account). SetCreatedAt(s.CreatedAt). SetLastBattleTime(s.LastBattleTime). SetRatingBattles(int(s.RatingBattles.Battles.Float())). @@ -280,6 +290,11 @@ func (c *client) CreateAccountAchievementSnapshots(ctx context.Context, accountI return nil, nil } + account, err := c.db.Account.Get(ctx, accountID) + if err != nil { + return nil, err + } + var errors = make(map[string]error) for _, data := range snapshots { // make a transaction per write to avoid locking for too long @@ -291,7 +306,7 @@ func (c *client) CreateAccountAchievementSnapshots(ctx context.Context, accountI SetCreatedAt(data.CreatedAt). SetReferenceID(data.ReferenceID). SetLastBattleTime(data.LastBattleTime). - SetAccount(c.db.Account.GetX(ctx, accountID)). + SetAccount(account). Exec(ctx) }) if err != nil { diff --git a/main.go b/main.go index fa85a4a9..929ec2e9 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,10 @@ import ( "github.com/rs/zerolog/log" _ "github.com/joho/godotenv/autoload" + + "net/http" + "net/http/pprof" + // "github.com/pkg/profile" ) //go:generate go generate ./internal/database/ent @@ -75,6 +79,12 @@ func main() { if e := os.Getenv("PRIVATE_SERVER_ENABLED"); e == "true" { port := os.Getenv("PRIVATE_SERVER_PORT") servePrivate := server.NewServer(port, []server.Handler{ + { + Path: "GET /debug/profile/{name}", + Func: func(w http.ResponseWriter, r *http.Request) { + pprof.Handler(r.PathValue("name")).ServeHTTP(w, r) + }, + }, { Path: "POST /v1/tasks/restart", Func: private.RestartStaleTasks(cacheCoreClient), From 7bed4565aa344d49c831da97bec7d2716c2e00c2 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 11:45:21 -0400 Subject: [PATCH 220/341] checking queue length from map instead of chan --- cmd/core/queue/queue.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index 037c2662..499a4e34 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -123,7 +123,10 @@ func (q *queue) pullAndEnqueueTasks(ctx context.Context, db database.Client) err } defer q.queueLock.Unlock() - currentQueue := len(q.queued) + q.activeTasksMx.Lock() + currentQueue := len(q.activeTasks) + q.activeTasksMx.Unlock() + if q.workerLimit < currentQueue { log.Debug().Msg("queue is full") return ErrQueueFull From a3f1924bd7cf1f03480462992cf79288d7f3f792 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 12:21:54 -0400 Subject: [PATCH 221/341] simplified interaction handlers --- cmd/discord/common/context.go | 30 +++++- cmd/discord/discord.go | 7 +- cmd/discord/rest/client.go | 14 ++- cmd/discord/rest/commands.go | 26 +++--- cmd/discord/rest/interactions.go | 17 ++-- cmd/discord/router/handler.go | 154 ++++++++++--------------------- cmd/discord/router/router.go | 10 +- internal/retry/retry.go | 9 +- 8 files changed, 125 insertions(+), 142 deletions(-) diff --git a/cmd/discord/common/context.go b/cmd/discord/common/context.go index ea64e3a4..7354802b 100644 --- a/cmd/discord/common/context.go +++ b/cmd/discord/common/context.go @@ -2,6 +2,9 @@ package common import ( "context" + "os" + "sync" + "time" "github.com/pkg/errors" @@ -12,6 +15,7 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/localization" + "github.com/cufee/aftermath/internal/retry" "github.com/rs/zerolog/log" "golang.org/x/text/language" @@ -25,6 +29,11 @@ const ( ContextKeyInteraction ) +type interactionState struct { + mx *sync.Mutex + replied bool +} + type Context struct { context.Context User models.User @@ -37,11 +46,10 @@ type Context struct { rest *rest.Client interaction discordgo.Interaction - respondCh chan<- discordgo.InteractionResponseData } -func NewContext(ctx context.Context, interaction discordgo.Interaction, respondCh chan<- discordgo.InteractionResponseData, rest *rest.Client, client core.Client) (*Context, error) { - c := &Context{Context: ctx, Locale: language.English, Core: client, rest: rest, interaction: interaction, respondCh: respondCh} +func NewContext(ctx context.Context, interaction discordgo.Interaction, rest *rest.Client, client core.Client) (*Context, error) { + c := &Context{Context: ctx, Locale: language.English, Core: client, rest: rest, interaction: interaction} if interaction.User != nil { c.Member = *interaction.User @@ -80,9 +88,21 @@ func (c *Context) respond(data discordgo.InteractionResponseData) error { case <-c.Context.Done(): return c.Context.Err() default: - c.respondCh <- data + res := retry.Retry( + func() (struct{}, error) { + ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*500) + defer cancel() + return struct{}{}, c.rest.SendInteractionResponse(ctx, c.interaction.ID, c.interaction.Token, discordgo.InteractionResponse{Data: &data}) + }, + 3, // 3 tries + time.Millisecond*100, + func(err error) bool { + // stop trying if the error is not caused by a timeout + return c.Context.Err() != nil || (err != nil && !os.IsTimeout(err)) + }, + ) + return res.Err } - return nil } func (c *Context) InteractionID() string { diff --git a/cmd/discord/discord.go b/cmd/discord/discord.go index 5fdcd3f4..cb02b5e3 100644 --- a/cmd/discord/discord.go +++ b/cmd/discord/discord.go @@ -1,7 +1,9 @@ package discord import ( + "context" "net/http" + "time" "github.com/cufee/aftermath/cmd/core" @@ -17,7 +19,10 @@ func NewRouterHandler(coreClient core.Client, token string, publicKey string, co rt.LoadCommands(commands...) - err = rt.UpdateLoadedCommands() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + err = rt.UpdateLoadedCommands(ctx) if err != nil { return nil, err } diff --git a/cmd/discord/rest/client.go b/cmd/discord/rest/client.go index 74cdc95f..ffaecea3 100644 --- a/cmd/discord/rest/client.go +++ b/cmd/discord/rest/client.go @@ -2,6 +2,7 @@ package rest import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -28,7 +29,10 @@ func NewClient(token string) (*Client, error) { http: http.Client{Timeout: time.Millisecond * 5000}, // discord is very slow sometimes } - _, err := client.lookupApplicationID() + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) + defer cancel() + + _, err := client.lookupApplicationID(ctx) if err != nil { return nil, err } @@ -108,7 +112,9 @@ func partHeader(contentDisposition string, contentType string) textproto.MIMEHea } } -func (c *Client) do(req *http.Request, target any) error { +func (c *Client) do(ctx context.Context, req *http.Request, target any) error { + req = req.WithContext(ctx) + res, err := c.http.Do(req) if err != nil { return err @@ -140,14 +146,14 @@ func (c *Client) do(req *http.Request, target any) error { return nil } -func (c *Client) lookupApplicationID() (string, error) { +func (c *Client) lookupApplicationID(ctx context.Context) (string, error) { req, err := c.request("GET", discordgo.EndpointApplication("@me"), nil) if err != nil { return "", err } var data discordgo.Application - err = c.do(req, &data) + err = c.do(ctx, req, &data) if err != nil { return "", err } diff --git a/cmd/discord/rest/commands.go b/cmd/discord/rest/commands.go index 1a6599e2..c2cfce02 100644 --- a/cmd/discord/rest/commands.go +++ b/cmd/discord/rest/commands.go @@ -1,47 +1,51 @@ package rest -import "github.com/bwmarrin/discordgo" +import ( + "context" -func (c *Client) OverwriteGlobalApplicationCommands(data []discordgo.ApplicationCommand) error { + "github.com/bwmarrin/discordgo" +) + +func (c *Client) OverwriteGlobalApplicationCommands(ctx context.Context, data []discordgo.ApplicationCommand) error { req, err := c.request("PUT", discordgo.EndpointApplicationGlobalCommands(c.applicationID), data) if err != nil { return err } - return c.do(req, nil) + return c.do(ctx, req, nil) } -func (c *Client) CreateGlobalApplicationCommand(data discordgo.ApplicationCommand) (discordgo.ApplicationCommand, error) { +func (c *Client) CreateGlobalApplicationCommand(ctx context.Context, data discordgo.ApplicationCommand) (discordgo.ApplicationCommand, error) { req, err := c.request("POST", discordgo.EndpointApplicationGlobalCommands(c.applicationID), data) if err != nil { return discordgo.ApplicationCommand{}, err } var command discordgo.ApplicationCommand - return command, c.do(req, &command) + return command, c.do(ctx, req, &command) } -func (c *Client) GetGlobalApplicationCommands() ([]discordgo.ApplicationCommand, error) { +func (c *Client) GetGlobalApplicationCommands(ctx context.Context) ([]discordgo.ApplicationCommand, error) { req, err := c.request("GET", discordgo.EndpointApplicationGlobalCommands(c.applicationID), nil) if err != nil { return nil, err } var data []discordgo.ApplicationCommand - return data, c.do(req, &data) + return data, c.do(ctx, req, &data) } -func (c *Client) UpdateGlobalApplicationCommand(id string, data discordgo.ApplicationCommand) (discordgo.ApplicationCommand, error) { +func (c *Client) UpdateGlobalApplicationCommand(ctx context.Context, id string, data discordgo.ApplicationCommand) (discordgo.ApplicationCommand, error) { req, err := c.request("PATCH", discordgo.EndpointApplicationGlobalCommand(c.applicationID, id), data) if err != nil { return discordgo.ApplicationCommand{}, err } var command discordgo.ApplicationCommand - return command, c.do(req, &command) + return command, c.do(ctx, req, &command) } -func (c *Client) DeleteGlobalApplicationCommand(id string) error { +func (c *Client) DeleteGlobalApplicationCommand(ctx context.Context, id string) error { req, err := c.request("DELETE", discordgo.EndpointApplicationGlobalCommand(c.applicationID, id), nil) if err != nil { return err } - return c.do(req, nil) + return c.do(ctx, req, nil) } diff --git a/cmd/discord/rest/interactions.go b/cmd/discord/rest/interactions.go index 692b2b7d..1c47d71e 100644 --- a/cmd/discord/rest/interactions.go +++ b/cmd/discord/rest/interactions.go @@ -1,12 +1,13 @@ package rest import ( + "context" "net/http" "github.com/bwmarrin/discordgo" ) -func (c *Client) SendInteractionResponse(id, token string, data discordgo.InteractionResponse) error { +func (c *Client) SendInteractionResponse(ctx context.Context, id, token string, data discordgo.InteractionResponse) error { var files []*discordgo.File if data.Data != nil { files = data.Data.Files @@ -15,31 +16,31 @@ func (c *Client) SendInteractionResponse(id, token string, data discordgo.Intera if err != nil { return err } - return c.do(req, nil) + return c.do(ctx, req, nil) } -func (c *Client) UpdateInteractionResponse(id, token string, data discordgo.InteractionResponseData) error { +func (c *Client) UpdateInteractionResponse(ctx context.Context, id, token string, data discordgo.InteractionResponseData) error { req, err := c.interactionRequest("PATCH", discordgo.EndpointInteractionResponseActions(id, token), data, data.Files) if err != nil { return err } - return c.do(req, nil) + return c.do(ctx, req, nil) } -func (c *Client) SendInteractionFollowup(id, token string, data discordgo.InteractionResponse) error { +func (c *Client) SendInteractionFollowup(ctx context.Context, id, token string, data discordgo.InteractionResponse) error { req, err := c.interactionRequest("POST", discordgo.EndpointFollowupMessage(id, token), data, data.Data.Files) if err != nil { return err } - return c.do(req, nil) + return c.do(ctx, req, nil) } -func (c *Client) EditInteractionFollowup(id, token string, data discordgo.InteractionResponseData) error { +func (c *Client) EditInteractionFollowup(ctx context.Context, id, token string, data discordgo.InteractionResponseData) error { req, err := c.interactionRequest("PATCH", discordgo.EndpointFollowupMessage(id, token), data, data.Files) if err != nil { return err } - return c.do(req, nil) + return c.do(ctx, req, nil) } func (c *Client) interactionRequest(method string, url string, payload any, files []*discordgo.File) (*http.Request, error) { diff --git a/cmd/discord/router/handler.go b/cmd/discord/router/handler.go index 419f55ac..c905c375 100644 --- a/cmd/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -10,7 +10,6 @@ import ( "io" "net/http" "runtime/debug" - "sync" "time" "github.com/pkg/errors" @@ -18,15 +17,10 @@ import ( "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmd/discord/commands/builder" "github.com/cufee/aftermath/cmd/discord/common" + "github.com/cufee/aftermath/internal/retry" "github.com/rs/zerolog/log" ) -type interactionState struct { - mx *sync.Mutex - acked bool - replied bool -} - // https://github.com/bsdlp/discord-interactions-go/blob/main/interactions/verify.go func verifyPublicKey(r *http.Request, key ed25519.PublicKey) bool { var msg bytes.Buffer @@ -105,7 +99,7 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { return } - command, err := router.routeInteraction(data) + cmd, err := router.routeInteraction(data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Err(err).Str("id", data.ID).Msg("failed to route interaction") @@ -113,27 +107,31 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { } // ack the interaction proactively - err = router.restClient.SendInteractionResponse(data.ID, data.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseDeferredChannelMessageWithSource}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + res := retry.Retry(func() (struct{}, error) { + ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*250) + defer cancel() + return struct{}{}, router.restClient.SendInteractionResponse(ctx, data.ID, data.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseDeferredChannelMessageWithSource}) + }, 2, time.Millisecond*50) + if res.Err != nil { + http.Error(w, res.Err.Error(), http.StatusInternalServerError) log.Err(err).Str("id", data.ID).Msg("failed to ack an interaction") - return + // cross our fingers and hope discord registered one of those requests } + // write the ack into response - err = writeDeferredInteractionResponseAck(w, data.Type, command.Ephemeral) + err = writeDeferredInteractionResponseAck(w, data.Type, cmd.Ephemeral) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Err(err).Str("id", data.ID).Msg("failed to write an interaction ack response") return } - // route the interaction to a proper handler for a later reply - state := &interactionState{mx: &sync.Mutex{}, acked: true} - router.startInteractionHandlerAsync(data, state, command) + // run the interaction handler + go router.handleInteraction(data, cmd) }, nil } -func (r *Router) routeInteraction(interaction discordgo.Interaction) (*builder.Command, error) { +func (r *Router) routeInteraction(interaction discordgo.Interaction) (builder.Command, error) { var matchKey string switch interaction.Type { @@ -146,29 +144,27 @@ func (r *Router) routeInteraction(interaction discordgo.Interaction) (*builder.C } if matchKey == "" { - return nil, errors.New("match key not found") + return builder.Command{}, errors.New("match key not found") } for _, cmd := range r.commands { if cmd.Match(matchKey) { - return &cmd, nil + return cmd, nil } } - return nil, fmt.Errorf("failed to match %s to a command handler", matchKey) + return builder.Command{}, fmt.Errorf("failed to match %s to a command handler", matchKey) } -func (r *Router) handleInteraction(ctx context.Context, cancel context.CancelFunc, interaction discordgo.Interaction, command *builder.Command, reply chan<- discordgo.InteractionResponseData) { +func (r *Router) handleInteraction(interaction discordgo.Interaction, command builder.Command) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - cCtx, err := common.NewContext(ctx, interaction, reply, r.restClient, r.core) + cCtx, err := common.NewContext(ctx, interaction, r.restClient, r.core) if err != nil { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() log.Err(err).Msg("failed to create a common.Context for a handler") - select { - case <-ctx.Done(): - return - default: - reply <- discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")} - } + r.sendInteractionReply(ctx, interaction, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed. Please try again in a few seconds."}) return } @@ -177,17 +173,24 @@ func (r *Router) handleInteraction(ctx context.Context, cancel context.CancelFun handler = command.Middleware[i](cCtx, handler) } - err = handler(cCtx) - if err != nil { - log.Err(err).Msg("handler returned an error") - select { - case <-ctx.Done(): + func() { + defer func() { + if rec := recover(); rec != nil { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + log.Error().Str("stack", string(debug.Stack())).Msg("panic in interaction handler") + r.sendInteractionReply(ctx, interaction, discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")}) + } + }() + err = handler(cCtx) + if err != nil { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + log.Err(err).Msg("handler returned an error") + r.sendInteractionReply(ctx, interaction, discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")}) return - default: - reply <- discordgo.InteractionResponseData{Content: cCtx.Localize("common_error_unhandled_not_reported")} } - return - } + }() } func sendPingReply(w http.ResponseWriter) { @@ -198,89 +201,26 @@ func sendPingReply(w http.ResponseWriter) { } } -func (router *Router) startInteractionHandlerAsync(interaction discordgo.Interaction, state *interactionState, command *builder.Command) { - log.Info().Str("id", interaction.ID).Msg("started handling an interaction") - - // create a new context and cancel it on reply - // we need to respond within 15 minutes, but it's safe to assume something failed if we did not reply quickly - workerCtx, cancelWorker := context.WithTimeout(context.Background(), time.Second*10) - - // handle the interaction - responseCh := make(chan discordgo.InteractionResponseData) - - go func() { - defer func() { - if r := recover(); r != nil { - log.Error().Str("stack", string(debug.Stack())).Msg("panic in interaction handler") - state.mx.Lock() - router.sendInteractionReply(interaction, state, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed. Please try again in a few seconds."}) - state.mx.Unlock() - } - }() - router.handleInteraction(workerCtx, cancelWorker, interaction, command, responseCh) - }() - - go func() { - defer close(responseCh) - defer cancelWorker() - - for { - select { - case <-workerCtx.Done(): - state.mx.Lock() - defer state.mx.Unlock() - // we are done, there is nothing else we should do here - // lock in case responseCh is still busy sending some data over - log.Info().Str("id", interaction.ID).Msg("finished handling an interaction") - return - - case data := <-responseCh: - state.mx.Lock() - log.Debug().Str("id", interaction.ID).Msg("sending an interaction reply") - router.sendInteractionReply(interaction, state, data) - state.mx.Unlock() - } - } - }() -} - -func (r *Router) sendInteractionReply(interaction discordgo.Interaction, state *interactionState, data discordgo.InteractionResponseData) { +func (r *Router) sendInteractionReply(ctx context.Context, interaction discordgo.Interaction, data discordgo.InteractionResponseData) { var handler func() error switch interaction.Type { case discordgo.InteractionApplicationCommand, discordgo.InteractionMessageComponent: - if state.replied || state.acked { - // We already replied to this interaction - edit the message - handler = func() error { - return r.restClient.UpdateInteractionResponse(interaction.AppID, interaction.Token, data) - } - } else { - // We never replied to this message, create a new reply - handler = func() error { - payload := discordgo.InteractionResponse{Data: &data, Type: discordgo.InteractionResponseChannelMessageWithSource} - return r.restClient.SendInteractionResponse(interaction.ID, interaction.Token, payload) - } + handler = func() error { + return r.restClient.UpdateInteractionResponse(ctx, interaction.AppID, interaction.Token, data) } default: - log.Error().Stack().Any("state", state).Any("data", data).Str("id", interaction.ID).Msg("unknown interaction type received") - if state.replied || state.acked { - handler = func() error { - return r.restClient.UpdateInteractionResponse(interaction.AppID, interaction.Token, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed."}) - } - } else { - handler = func() error { - return r.restClient.SendInteractionResponse(interaction.ID, interaction.Token, discordgo.InteractionResponse{Data: &discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed."}, Type: discordgo.InteractionResponseChannelMessageWithSource}) - } + log.Error().Stack().Any("data", data).Str("id", interaction.ID).Msg("unknown interaction type received") + handler = func() error { + return r.restClient.UpdateInteractionResponse(ctx, interaction.AppID, interaction.Token, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed."}) } } err := handler() if err != nil { - log.Err(err).Stack().Any("state", state).Any("data", data).Str("id", interaction.ID).Msg("failed to send an interaction response") + log.Err(err).Stack().Any("data", data).Str("id", interaction.ID).Msg("failed to send an interaction response") return } - state.acked = true - state.replied = true } func writeDeferredInteractionResponseAck(w http.ResponseWriter, t discordgo.InteractionType, ephemeral bool) error { diff --git a/cmd/discord/router/router.go b/cmd/discord/router/router.go index 459717fa..bb829bb0 100644 --- a/cmd/discord/router/router.go +++ b/cmd/discord/router/router.go @@ -64,13 +64,13 @@ Updates all loaded commands using the Discord REST API - any commands that are loaded will be created/updated - any commands that were not loaded will be deleted */ -func (r *Router) UpdateLoadedCommands() error { +func (r *Router) UpdateLoadedCommands(ctx context.Context) error { var commandByName = make(map[string]command) for _, cmd := range r.commands { commandByName[cmd.Name] = command{requested: &cmd} } - current, err := r.restClient.GetGlobalApplicationCommands() + current, err := r.restClient.GetGlobalApplicationCommands(ctx) if err != nil { return err } @@ -103,7 +103,7 @@ func (r *Router) UpdateLoadedCommands() error { if err != nil { return err } - command, err := r.restClient.CreateGlobalApplicationCommand(data.requested.ApplicationCommand) + command, err := r.restClient.CreateGlobalApplicationCommand(ctx, data.requested.ApplicationCommand) if err != nil { return err } @@ -115,7 +115,7 @@ func (r *Router) UpdateLoadedCommands() error { case data.requested == nil && data.current != nil: log.Debug().Str("name", data.current.Name).Str("id", data.current.ID).Msg("deleting a global command") - err := r.restClient.DeleteGlobalApplicationCommand(data.current.ID) + err := r.restClient.DeleteGlobalApplicationCommand(ctx, data.current.ID) if err != nil { return err } @@ -132,7 +132,7 @@ func (r *Router) UpdateLoadedCommands() error { if err != nil { return err } - command, err := r.restClient.UpdateGlobalApplicationCommand(data.current.ID, data.requested.ApplicationCommand) + command, err := r.restClient.UpdateGlobalApplicationCommand(ctx, data.current.ID, data.requested.ApplicationCommand) if err != nil { return err } diff --git a/internal/retry/retry.go b/internal/retry/retry.go index 7585e5c6..ebe873ef 100644 --- a/internal/retry/retry.go +++ b/internal/retry/retry.go @@ -11,7 +11,7 @@ type DataWithErr[T any] struct { Err error } -func Retry[T any](fn func() (T, error), tries int, sleepOnFail time.Duration) DataWithErr[T] { +func Retry[T any](fn func() (T, error), tries int, sleepOnFail time.Duration, breakIf ...func(error) bool) DataWithErr[T] { if tries < 1 { return DataWithErr[T]{Err: errors.New("invalid number of tries provided")} } @@ -22,6 +22,13 @@ func Retry[T any](fn func() (T, error), tries int, sleepOnFail time.Duration) Da return DataWithErr[T]{Data: data, Err: err} } + for _, check := range breakIf { + shouldBreak := check(err) + if shouldBreak { + return DataWithErr[T]{Err: err} + } + } + time.Sleep(sleepOnFail) return Retry(fn, tries, sleepOnFail) } From f07ec88e8aab00e3de7847f5bd051d2fbfab95d5 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 12:35:59 -0400 Subject: [PATCH 222/341] added known errors to rest --- cmd/discord/rest/client.go | 6 ++++-- cmd/discord/rest/errors.go | 25 +++++++++++++++++++++++++ cmd/discord/router/handler.go | 18 ++++++++++++------ internal/retry/retry.go | 6 ++++++ 4 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 cmd/discord/rest/errors.go diff --git a/cmd/discord/rest/client.go b/cmd/discord/rest/client.go index ffaecea3..ba101f16 100644 --- a/cmd/discord/rest/client.go +++ b/cmd/discord/rest/client.go @@ -133,8 +133,10 @@ func (c *Client) do(ctx context.Context, req *http.Request, target any) error { if message == "" { message = res.Status + ", response was not valid json" } - - return errors.New("discord error: " + message) + if err := knownError(body.Code); err != nil { + return err + } + return errors.New("discord api: " + message) } if target != nil { diff --git a/cmd/discord/rest/errors.go b/cmd/discord/rest/errors.go new file mode 100644 index 00000000..dadd264e --- /dev/null +++ b/cmd/discord/rest/errors.go @@ -0,0 +1,25 @@ +package rest + +import ( + "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" +) + +var ( + ErrUnknownWebhook = errors.New("discord api: unknown webhook") + ErrUnknownInteraction = errors.New("discord api: unknown interaction") + ErrInteractionAlreadyAcked = errors.New("discord api: interaction already acked") +) + +func knownError(code int) error { + switch code { + default: + return nil + case discordgo.ErrCodeUnknownWebhook: + return ErrUnknownWebhook + case discordgo.ErrCodeUnknownInteraction: + return ErrUnknownInteraction + case discordgo.ErrCodeInteractionHasAlreadyBeenAcknowledged: + return ErrInteractionAlreadyAcked + } +} diff --git a/cmd/discord/router/handler.go b/cmd/discord/router/handler.go index c905c375..944f31f1 100644 --- a/cmd/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -17,6 +17,7 @@ import ( "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmd/discord/commands/builder" "github.com/cufee/aftermath/cmd/discord/common" + "github.com/cufee/aftermath/cmd/discord/rest" "github.com/cufee/aftermath/internal/retry" "github.com/rs/zerolog/log" ) @@ -107,12 +108,17 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { } // ack the interaction proactively - res := retry.Retry(func() (struct{}, error) { - ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*250) - defer cancel() - return struct{}{}, router.restClient.SendInteractionResponse(ctx, data.ID, data.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseDeferredChannelMessageWithSource}) - }, 2, time.Millisecond*50) - if res.Err != nil { + res := retry.Retry( + func() (struct{}, error) { + ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*250) + defer cancel() + return struct{}{}, router.restClient.SendInteractionResponse(ctx, data.ID, data.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseDeferredChannelMessageWithSource}) + }, + 3, + time.Millisecond*50, + // break if the error + func(err error) bool { return errors.Is(err, rest.ErrInteractionAlreadyAcked) }) + if res.Err != nil && !errors.Is(res.Err, rest.ErrInteractionAlreadyAcked) { http.Error(w, res.Err.Error(), http.StatusInternalServerError) log.Err(err).Str("id", data.ID).Msg("failed to ack an interaction") // cross our fingers and hope discord registered one of those requests diff --git a/internal/retry/retry.go b/internal/retry/retry.go index ebe873ef..9cbc25be 100644 --- a/internal/retry/retry.go +++ b/internal/retry/retry.go @@ -32,3 +32,9 @@ func Retry[T any](fn func() (T, error), tries int, sleepOnFail time.Duration, br time.Sleep(sleepOnFail) return Retry(fn, tries, sleepOnFail) } + +func IfErrNot(notErr error) func(error) bool { + return func(err error) bool { + return !errors.Is(err, notErr) + } +} From 60b805a75003c8814175085e39a08b34c74439bd Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 12:45:24 -0400 Subject: [PATCH 223/341] adjuster retries on response --- cmd/discord/common/context.go | 7 ++++--- cmd/discord/router/handler.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/discord/common/context.go b/cmd/discord/common/context.go index 7354802b..3497a1aa 100644 --- a/cmd/discord/common/context.go +++ b/cmd/discord/common/context.go @@ -92,13 +92,14 @@ func (c *Context) respond(data discordgo.InteractionResponseData) error { func() (struct{}, error) { ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*500) defer cancel() - return struct{}{}, c.rest.SendInteractionResponse(ctx, c.interaction.ID, c.interaction.Token, discordgo.InteractionResponse{Data: &data}) + return struct{}{}, c.rest.SendInteractionResponse(ctx, c.interaction.ID, c.interaction.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseDeferredMessageUpdate, Data: &data}) }, 3, // 3 tries time.Millisecond*100, + // stop trying if the parent context was cancelled, or the error is not caused by a timeout and is not indicating that interaction was not acked + // the rest.ErrUnknownWebhook error specifically is likely caused by discord being slow and not propagating the state of out initial ack -- all interaction we handle are always acked func(err error) bool { - // stop trying if the error is not caused by a timeout - return c.Context.Err() != nil || (err != nil && !os.IsTimeout(err)) + return c.Context.Err() != nil || (!os.IsTimeout(err) && !errors.Is(err, rest.ErrUnknownWebhook)) }, ) return res.Err diff --git a/cmd/discord/router/handler.go b/cmd/discord/router/handler.go index 944f31f1..e64b7c8b 100644 --- a/cmd/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -121,7 +121,7 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { if res.Err != nil && !errors.Is(res.Err, rest.ErrInteractionAlreadyAcked) { http.Error(w, res.Err.Error(), http.StatusInternalServerError) log.Err(err).Str("id", data.ID).Msg("failed to ack an interaction") - // cross our fingers and hope discord registered one of those requests + // cross our fingers and hope discord registered one of those requests, or will propagate the ack from the response body } // write the ack into response From 5f869e622ac12a94eaf543a3e7d1012709d15128 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 13:25:47 -0400 Subject: [PATCH 224/341] updated deps --- go.mod | 2 +- go.sum | 4 ++-- internal/external/wargaming/client.go | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index d39d25ff..68658f09 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.3 require ( entgo.io/ent v0.13.1 github.com/bwmarrin/discordgo v0.28.1 - github.com/cufee/am-wg-proxy-next/v2 v2.1.6 + github.com/cufee/am-wg-proxy-next/v2 v2.1.7 github.com/disintegration/imaging v1.6.2 github.com/fogleman/gg v1.3.0 github.com/go-co-op/gocron v1.37.0 diff --git a/go.sum b/go.sum index b64339d0..bf5c2b86 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cufee/am-wg-proxy-next/v2 v2.1.6 h1:LNGXS9n1FANtblhEUxMAZehfcTIH6FsGfF0snhf2hHM= -github.com/cufee/am-wg-proxy-next/v2 v2.1.6/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= +github.com/cufee/am-wg-proxy-next/v2 v2.1.7 h1:cNIQ6hLM/eqqKLuNH0K52AEjzQQqwo7qoi9VOXD6bSM= +github.com/cufee/am-wg-proxy-next/v2 v2.1.7/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= 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= diff --git a/internal/external/wargaming/client.go b/internal/external/wargaming/client.go index 130de06b..738c4cac 100644 --- a/internal/external/wargaming/client.go +++ b/internal/external/wargaming/client.go @@ -6,6 +6,7 @@ import ( "time" "github.com/cufee/am-wg-proxy-next/v2/client" + "github.com/rs/zerolog" ) type Client interface { @@ -25,5 +26,5 @@ func NewClientFromEnv(primaryAppId, primaryAppRps, requestTimeout, proxyHostList } timeout := time.Second * time.Duration(timeoutInt) - return client.NewEmbeddedClient(primaryAppId, primaryRps, proxyHostList, timeout) + return client.NewEmbeddedClient(primaryAppId, primaryRps, proxyHostList, timeout, client.WithLogLevel(zerolog.WarnLevel)) } From fb087fa3807136b86fcf4934924f4c3b9e9cc2a4 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 13:37:59 -0400 Subject: [PATCH 225/341] removed io.ReadAll calls --- cmd/core/server/handlers/private/accounts.go | 9 +-------- cmd/discord/rest/client.go | 7 +------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/cmd/core/server/handlers/private/accounts.go b/cmd/core/server/handlers/private/accounts.go index 06734a2b..d42b260b 100644 --- a/cmd/core/server/handlers/private/accounts.go +++ b/cmd/core/server/handlers/private/accounts.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" "strconv" "sync" @@ -22,15 +21,9 @@ This function is temporary and does not need to be good :D */ func LoadAccountsHandler(client core.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - payload, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } defer r.Body.Close() - var accounts []string - err = json.Unmarshal(payload, &accounts) + err := json.NewDecoder(r.Body).Decode(&accounts) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/cmd/discord/rest/client.go b/cmd/discord/rest/client.go index ba101f16..4adaad56 100644 --- a/cmd/discord/rest/client.go +++ b/cmd/discord/rest/client.go @@ -123,12 +123,7 @@ func (c *Client) do(ctx context.Context, req *http.Request, target any) error { if res.StatusCode > 299 { var body discordgo.APIErrorMessage - data, err := io.ReadAll(res.Body) - if err != nil { - return fmt.Errorf("failed to read body: %w", err) - } - - _ = json.Unmarshal(data, &body) + _ = json.NewDecoder(res.Body).Decode(&body) message := body.Message if message == "" { message = res.Status + ", response was not valid json" From f26515710ea86bd14d0a6314e482361c256d1840 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 13:47:39 -0400 Subject: [PATCH 226/341] typo --- cmd/discord/common/context.go | 2 +- docker-compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/discord/common/context.go b/cmd/discord/common/context.go index 3497a1aa..846cf7a6 100644 --- a/cmd/discord/common/context.go +++ b/cmd/discord/common/context.go @@ -92,7 +92,7 @@ func (c *Context) respond(data discordgo.InteractionResponseData) error { func() (struct{}, error) { ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*500) defer cancel() - return struct{}{}, c.rest.SendInteractionResponse(ctx, c.interaction.ID, c.interaction.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseDeferredMessageUpdate, Data: &data}) + return struct{}{}, c.rest.UpdateInteractionResponse(ctx, c.interaction.ID, c.interaction.Token, data) }, 3, // 3 tries time.Millisecond*100, diff --git a/docker-compose.yaml b/docker-compose.yaml index e808c713..ca895eef 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -28,7 +28,7 @@ services: deploy: resources: reservations: - memory: 512m + memory: 128m networks: - dokploy-network labels: From 5fd365e507eda20e4916c52a76579a5c1312dee2 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 13:52:30 -0400 Subject: [PATCH 227/341] proper error --- cmd/discord/router/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/discord/router/handler.go b/cmd/discord/router/handler.go index e64b7c8b..56f55245 100644 --- a/cmd/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -120,7 +120,7 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { func(err error) bool { return errors.Is(err, rest.ErrInteractionAlreadyAcked) }) if res.Err != nil && !errors.Is(res.Err, rest.ErrInteractionAlreadyAcked) { http.Error(w, res.Err.Error(), http.StatusInternalServerError) - log.Err(err).Str("id", data.ID).Msg("failed to ack an interaction") + log.Err(res.Err).Str("id", data.ID).Msg("failed to ack an interaction") // cross our fingers and hope discord registered one of those requests, or will propagate the ack from the response body } From 8e2fb4b1608e02ceda7c60381ef504c3a7cf10b6 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 14:06:34 -0400 Subject: [PATCH 228/341] simplified response code --- cmd/discord/common/context.go | 20 +++----------------- cmd/discord/router/handler.go | 35 +++++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/cmd/discord/common/context.go b/cmd/discord/common/context.go index 846cf7a6..52895dc0 100644 --- a/cmd/discord/common/context.go +++ b/cmd/discord/common/context.go @@ -2,7 +2,6 @@ package common import ( "context" - "os" "sync" "time" @@ -15,7 +14,6 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/localization" - "github.com/cufee/aftermath/internal/retry" "github.com/rs/zerolog/log" "golang.org/x/text/language" @@ -88,21 +86,9 @@ func (c *Context) respond(data discordgo.InteractionResponseData) error { case <-c.Context.Done(): return c.Context.Err() default: - res := retry.Retry( - func() (struct{}, error) { - ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*500) - defer cancel() - return struct{}{}, c.rest.UpdateInteractionResponse(ctx, c.interaction.ID, c.interaction.Token, data) - }, - 3, // 3 tries - time.Millisecond*100, - // stop trying if the parent context was cancelled, or the error is not caused by a timeout and is not indicating that interaction was not acked - // the rest.ErrUnknownWebhook error specifically is likely caused by discord being slow and not propagating the state of out initial ack -- all interaction we handle are always acked - func(err error) bool { - return c.Context.Err() != nil || (!os.IsTimeout(err) && !errors.Is(err, rest.ErrUnknownWebhook)) - }, - ) - return res.Err + ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*3000) + defer cancel() + return c.rest.UpdateInteractionResponse(ctx, c.interaction.AppID, c.interaction.Token, data) } } diff --git a/cmd/discord/router/handler.go b/cmd/discord/router/handler.go index 56f55245..76536aec 100644 --- a/cmd/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -108,27 +108,40 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { } // ack the interaction proactively + payload, err := deferredInteractionResponsePayload(data.Type, cmd.Ephemeral) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Err(err).Str("id", data.ID).Msg("failed to make an ack response payload") + return + } + res := retry.Retry( func() (struct{}, error) { - ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*250) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) defer cancel() - return struct{}{}, router.restClient.SendInteractionResponse(ctx, data.ID, data.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseDeferredChannelMessageWithSource}) + err := router.restClient.SendInteractionResponse(ctx, data.ID, data.Token, payload) + return struct{}{}, err }, 3, time.Millisecond*50, - // break if the error + // break if the error means we were able to ack on the last request func(err error) bool { return errors.Is(err, rest.ErrInteractionAlreadyAcked) }) if res.Err != nil && !errors.Is(res.Err, rest.ErrInteractionAlreadyAcked) { - http.Error(w, res.Err.Error(), http.StatusInternalServerError) log.Err(res.Err).Str("id", data.ID).Msg("failed to ack an interaction") // cross our fingers and hope discord registered one of those requests, or will propagate the ack from the response body } - // write the ack into response - err = writeDeferredInteractionResponseAck(w, data.Type, cmd.Ephemeral) + // write the ack into response, this typically does nothing/is flaky + body, err := json.Marshal(payload) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Err(err).Str("id", data.ID).Msg("failed to encode interaction ack payload") + return + } + _, err = w.Write(body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - log.Err(err).Str("id", data.ID).Msg("failed to write an interaction ack response") + log.Err(err).Str("id", data.ID).Msg("failed to write interaction ack payload") return } @@ -229,7 +242,7 @@ func (r *Router) sendInteractionReply(ctx context.Context, interaction discordgo } } -func writeDeferredInteractionResponseAck(w http.ResponseWriter, t discordgo.InteractionType, ephemeral bool) error { +func deferredInteractionResponsePayload(t discordgo.InteractionType, ephemeral bool) (discordgo.InteractionResponse, error) { var response discordgo.InteractionResponse if ephemeral { response.Data.Flags = discordgo.MessageFlagsEphemeral @@ -242,10 +255,8 @@ func writeDeferredInteractionResponseAck(w http.ResponseWriter, t discordgo.Inte response.Type = discordgo.InteractionResponseDeferredMessageUpdate default: - return fmt.Errorf("interaction type %s not supported", t.String()) + return response, fmt.Errorf("interaction type %s not supported", t.String()) } - body, _ := json.Marshal(response) - _, err := w.Write(body) - return err + return response, nil } From 33b9cc93133b0403f46934a91567fdc7dabf335e Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 14:11:37 -0400 Subject: [PATCH 229/341] fixed response type --- cmd/discord/router/handler.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/discord/router/handler.go b/cmd/discord/router/handler.go index 76536aec..71f35a6d 100644 --- a/cmd/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -249,10 +249,8 @@ func deferredInteractionResponsePayload(t discordgo.InteractionType, ephemeral b } switch t { - case discordgo.InteractionApplicationCommand: + case discordgo.InteractionApplicationCommand, discordgo.InteractionMessageComponent: response.Type = discordgo.InteractionResponseDeferredChannelMessageWithSource - case discordgo.InteractionMessageComponent: - response.Type = discordgo.InteractionResponseDeferredMessageUpdate default: return response, fmt.Errorf("interaction type %s not supported", t.String()) From 20adb25339c2a216cce0874a46a7915139742211 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 14:19:25 -0400 Subject: [PATCH 230/341] blaming discord --- cmd/discord/common/context.go | 17 ++++++++++------- static/localization/en/discord.yaml | 3 +++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cmd/discord/common/context.go b/cmd/discord/common/context.go index 52895dc0..64e755be 100644 --- a/cmd/discord/common/context.go +++ b/cmd/discord/common/context.go @@ -2,7 +2,7 @@ package common import ( "context" - "sync" + "os" "time" "github.com/pkg/errors" @@ -27,11 +27,6 @@ const ( ContextKeyInteraction ) -type interactionState struct { - mx *sync.Mutex - replied bool -} - type Context struct { context.Context User models.User @@ -88,7 +83,15 @@ func (c *Context) respond(data discordgo.InteractionResponseData) error { default: ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*3000) defer cancel() - return c.rest.UpdateInteractionResponse(ctx, c.interaction.AppID, c.interaction.Token, data) + err := c.rest.UpdateInteractionResponse(ctx, c.interaction.AppID, c.interaction.Token, data) + if os.IsTimeout(err) { + // request timed out, most likely a discord issue + log.Error().Str("id", c.ID()).Str("interactionId", c.interaction.ID).Msg("discord api: request timed out while responding to an interaction") + ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*1000) + defer cancel() + return c.rest.UpdateInteractionResponse(ctx, c.interaction.AppID, c.interaction.Token, discordgo.InteractionResponseData{Content: c.Localize("common_error_discord_outage")}) + } + return err } } diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml index 19885bba..0de71987 100755 --- a/static/localization/en/discord.yaml +++ b/static/localization/en/discord.yaml @@ -66,6 +66,9 @@ - key: common_error_service_outage value: "Aftermath is currently undergoing maintenance and is temporarily unavailable. Please try again in a few moments." context: Failed to reach some internal service +- key: common_error_discord_outage + value: "It looks like Discord is having some temporary issues. Please try again in a few seconds." + context: Discord request timed out for unknown reasons - key: common_error_unhandled_reported value: Something unexpected happened and your command failed.\n*This error was reported automatically, you can also reach out to our team on Aftermath Official*" From fe4ae9e120f90e900fa26227eab676566582275c Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 14:50:08 -0400 Subject: [PATCH 231/341] removed some unused files --- .air.toml | 1 + static/images/icons/chevron-down-double.png | Bin 306 -> 0 bytes static/images/icons/chevron-down-single.png | Bin 243 -> 0 bytes static/images/icons/chevron-up-double.png | Bin 285 -> 0 bytes static/images/icons/chevron-up-single.png | Bin 239 -> 0 bytes static/images/icons/logo-128.png | Bin 24744 -> 0 bytes static/images/icons/triangle-down-outline.png | Bin 739 -> 0 bytes static/images/icons/triangle-up-outline.png | Bin 710 -> 0 bytes 8 files changed, 1 insertion(+) delete mode 100644 static/images/icons/chevron-down-double.png delete mode 100644 static/images/icons/chevron-down-single.png delete mode 100644 static/images/icons/chevron-up-double.png delete mode 100644 static/images/icons/chevron-up-single.png delete mode 100644 static/images/icons/logo-128.png delete mode 100644 static/images/icons/triangle-down-outline.png delete mode 100644 static/images/icons/triangle-up-outline.png diff --git a/.air.toml b/.air.toml index 44d5d232..4a197927 100644 --- a/.air.toml +++ b/.air.toml @@ -1,2 +1,3 @@ [build] +# cmd = "go build -gcflags=-m=3 -o ./tmp/main ." stop_on_error = true diff --git a/static/images/icons/chevron-down-double.png b/static/images/icons/chevron-down-double.png deleted file mode 100644 index 6a9282929a2f726c0aec110be4d35f9d44f71c36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJpPnv`ArbD$DG~_>EDn4#?y*WR zVd>yxWKD4DWq;J_@TcK~3-@JX!4?0Q?QD-&Fr=)sxuW2%P}|s`%+v7ccVh&@s%sOX z_!wp~f8w=sSBT~d`Y+JoxUf-JK$uCvnV-SX{)$LUpTqV)$^mOuVc$9I645JXcs>LK-byJpvKDU0p85W*)>gFs3ARQ-p z8B^SI8j6@WmX^&>E15OJgrVzxje^{UNdh6CUNRdlbmFi~ar1Usv{}V4lObM_W%Ud< z??V?S6;w=b+2IwqszZ1-@5&@eGiNth11Tnk%h6$q+FVdQ&MBb@0N%`I AL;wH) diff --git a/static/images/icons/chevron-down-single.png b/static/images/icons/chevron-down-single.png deleted file mode 100644 index f10eb35fec042d58632a4b717c813c129981fd05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJU7jwEArbCxryS&MFyLWM3`$^1 zV3J@oYw%R83+d8|y7c8@zxLB+q1!vdzQjJhZ2s-NL<5Tl5FKzzn127OTB*m{WAWxy z=_@^7ojM!AWaGt@yzi<@rsY;ML#>XUwgB_ zh1uC#tXfXX98zTrQdzLDd8QeE^i;MVTnjbDXI678S;u_8`)oGT5tW!{TNw@H<1FPC qzOJ~}a9YFk2!o0PlgfoT2e^MU$SUqSKl=pG7Yv@RelF{r5}E)cWnKRO diff --git a/static/images/icons/chevron-up-double.png b/static/images/icons/chevron-up-double.png deleted file mode 100644 index 9495bf8a75ae666314dbaf22c52d73605b78cbd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJC!Q{jArbCxrv&mc849?(7inNV zz$oOv#5bWqMCAbkrvjr)19QQFoK3$kZz-|<`{KOWO_P^fXI|g=_RQr6hWyv&GbzZt zC`{;P+m|IQZ(u*8EsfFTPg~Tg7wn(sH6}0w74EiRy~s78t3I^$pw&gD3BvbRevmb0 zeaNcO8U6TVIoBcP0O9R&3lwY^^flImxc=LDBd)4Qvt`cRKh^Ve8B9OU5%$;N3Ms5$ zc(QhWfcTUBYge+MXDD~ hzbDM_@IV23UEZHC#WRBTx9zu;-g$h`E*3^o3JHBOFy9cA919q@7C3Bi@VDR8eeC7mg{~}o zO=bKu&a?Noge$aiUsru7sB!SF&{ihPm}_yFVdQ&MBb@0IQ%|zyJUM diff --git a/static/images/icons/logo-128.png b/static/images/icons/logo-128.png deleted file mode 100644 index 184987bdff489e37da61ec8ce58adbe5ee96c9b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24744 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 Vb}|38Y`_2j002ovPDHLkV1fzsQY-)f diff --git a/static/images/icons/triangle-up-outline.png b/static/images/icons/triangle-up-outline.png deleted file mode 100644 index 24fa2dac44f9e3e78900011a048ee379722df765..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 710 zcmV;%0y+JOP)DfC(rQ02`pclQ@w}ZP{{KdKca0 z?52+WFUfu-!@-K%aK-TtdIHOiU>|`#0!|O~DHL$v4*j>s|36dv>U4JN<6TAIj8?m9 ztawhpuS=8Yg1{kxyq^Q~i0(g>B+&(dcLG>3$VYAuxSdhE^`8mU1nzI|x7`2YD4=y)NdnlQr;O-Bd0#*x3Y+}nhFWZNx0NDL zvG)Z~+h%QUZtMk+a#<+?NR?wof-`?K2i27Bi#FU*f9yg}50@YSuulreKJ@{#%~Hmi zxRoFP@ZxccS9P>N6oj+QOCTq&D?*FYO3Wp0UIJRQQcf0qV$Fz2wzzo+U<1n8E}~^U z+o2TzF5;#&BPpG72{c73M_p?|Dyp3=Zn*^X;%%a3^&+Am;+ErttYZ|L@P)3nlEDgG z#_2Rf6m~ha%=6?#U_!HtXuVM%FD@R#i_SJrU^#~Wtd&Xt-q%N9NVKe^Ml?_hVN!{a zN?xxI7hMlpWa=0@%2c@5#Ap2#MZX*caH)QL_>l(@a{l zKx7u3J#7G@0N_dDW+k9YhOG12$fc%nB5rU{w8XFw(37T?3`X3)!V)L9k+pEP|5 z(Xx`!=?Zb%2t61hFrtU$jP{faR`4 Date: Sun, 30 Jun 2024 18:50:24 -0400 Subject: [PATCH 232/341] passing files as slices instead of readers, handling bad discord event propagation --- cmd/discord/commands/interactions.go | 2 +- cmd/discord/commands/manage.go | 5 ++- cmd/discord/commands/session.go | 2 +- cmd/discord/commands/stats.go | 2 +- cmd/discord/common/context.go | 6 ++-- cmd/discord/common/reply.go | 16 +++++----- cmd/discord/rest/interactions.go | 47 ++++++++++++++++++++-------- cmd/discord/router/handler.go | 6 ++-- 8 files changed, 53 insertions(+), 33 deletions(-) diff --git a/cmd/discord/commands/interactions.go b/cmd/discord/commands/interactions.go index 901c252c..5d529612 100644 --- a/cmd/discord/commands/interactions.go +++ b/cmd/discord/commands/interactions.go @@ -102,7 +102,7 @@ func init() { timings = append(timings, "```") } - return ctx.Reply().File(&buf, interaction.Command+"_command_by_aftermath.png").Component(button).Text(timings...).Send() + return ctx.Reply().File(buf.Bytes(), interaction.Command+"_command_by_aftermath.png").Component(button).Text(timings...).Send() }), ) } diff --git a/cmd/discord/commands/manage.go b/cmd/discord/commands/manage.go index eaaaad67..dff707d1 100644 --- a/cmd/discord/commands/manage.go +++ b/cmd/discord/commands/manage.go @@ -1,7 +1,6 @@ package commands import ( - "bytes" "encoding/json" "fmt" "time" @@ -145,7 +144,7 @@ func init() { } content += string(data) - return ctx.Reply().File(bytes.NewBufferString(content), "tasks.json").Send() + return ctx.Reply().File([]byte(content), "tasks.json").Send() case "tasks_details": if !ctx.User.HasPermission(permissions.ViewTaskLogs) { @@ -169,7 +168,7 @@ func init() { if err != nil { return ctx.Reply().Send("json.Marshal: " + err.Error()) } - return ctx.Reply().File(bytes.NewReader(data), "tasks.json").Send() + return ctx.Reply().File(data, "tasks.json").Send() default: return ctx.Reply().Send("invalid subcommand, thought this should never happen") diff --git a/cmd/discord/commands/session.go b/cmd/discord/commands/session.go index bd217a0d..c5386b4a 100644 --- a/cmd/discord/commands/session.go +++ b/cmd/discord/commands/session.go @@ -106,7 +106,7 @@ func init() { timings = append(timings, "```") } - return ctx.Reply().File(&buf, "session_command_by_aftermath.png").Component(button).Text(timings...).Send() + return ctx.Reply().File(buf.Bytes(), "session_command_by_aftermath.png").Component(button).Text(timings...).Send() }), ) } diff --git a/cmd/discord/commands/stats.go b/cmd/discord/commands/stats.go index c64fc7ce..61a1f0cb 100644 --- a/cmd/discord/commands/stats.go +++ b/cmd/discord/commands/stats.go @@ -91,7 +91,7 @@ func init() { timings = append(timings, "```") } - return ctx.Reply().File(&buf, "stats_command_by_aftermath.png").Component(button).Text(timings...).Send() + return ctx.Reply().File(buf.Bytes(), "stats_command_by_aftermath.png").Component(button).Text(timings...).Send() }), ) } diff --git a/cmd/discord/common/context.go b/cmd/discord/common/context.go index 64e755be..2edd93d3 100644 --- a/cmd/discord/common/context.go +++ b/cmd/discord/common/context.go @@ -76,20 +76,20 @@ func NewContext(ctx context.Context, interaction discordgo.Interaction, rest *re return c, nil } -func (c *Context) respond(data discordgo.InteractionResponseData) error { +func (c *Context) respond(data discordgo.InteractionResponseData, files []rest.File) error { select { case <-c.Context.Done(): return c.Context.Err() default: ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*3000) defer cancel() - err := c.rest.UpdateInteractionResponse(ctx, c.interaction.AppID, c.interaction.Token, data) + err := c.rest.UpdateOrSendInteractionResponse(ctx, c.interaction.AppID, c.interaction.ID, c.interaction.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &data}, files) if os.IsTimeout(err) { // request timed out, most likely a discord issue log.Error().Str("id", c.ID()).Str("interactionId", c.interaction.ID).Msg("discord api: request timed out while responding to an interaction") ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*1000) defer cancel() - return c.rest.UpdateInteractionResponse(ctx, c.interaction.AppID, c.interaction.Token, discordgo.InteractionResponseData{Content: c.Localize("common_error_discord_outage")}) + return c.rest.UpdateOrSendInteractionResponse(ctx, c.interaction.AppID, c.interaction.ID, c.interaction.Token, discordgo.InteractionResponse{Data: &discordgo.InteractionResponseData{Content: c.Localize("common_error_discord_outage")}, Type: discordgo.InteractionResponseChannelMessageWithSource}, nil) } return err } diff --git a/cmd/discord/common/reply.go b/cmd/discord/common/reply.go index 4e608a65..89662a07 100644 --- a/cmd/discord/common/reply.go +++ b/cmd/discord/common/reply.go @@ -2,17 +2,17 @@ package common import ( "fmt" - "io" "strings" "github.com/bwmarrin/discordgo" + "github.com/cufee/aftermath/cmd/discord/rest" ) type reply struct { ctx *Context text []string - files []*discordgo.File + files []rest.File components []discordgo.MessageComponent embeds []*discordgo.MessageEmbed } @@ -27,11 +27,11 @@ func (r reply) Format(format string, args ...any) reply { return r } -func (r reply) File(reader io.Reader, name string) reply { - if reader == nil { +func (r reply) File(data []byte, name string) reply { + if data == nil { return r } - r.files = append(r.files, &discordgo.File{Reader: reader, Name: name}) + r.files = append(r.files, rest.File{Data: data, Name: name}) return r } @@ -60,15 +60,15 @@ func (r reply) Send(content ...string) error { return r.ctx.respond(r.data()) } -func (r reply) data() discordgo.InteractionResponseData { +func (r reply) data() (discordgo.InteractionResponseData, []rest.File) { var content []string for _, t := range r.text { content = append(content, r.ctx.Localize(t)) } + return discordgo.InteractionResponseData{ Content: strings.Join(content, "\n"), Components: r.components, Embeds: r.embeds, - Files: r.files, - } + }, r.files } diff --git a/cmd/discord/rest/interactions.go b/cmd/discord/rest/interactions.go index 1c47d71e..382a76b8 100644 --- a/cmd/discord/rest/interactions.go +++ b/cmd/discord/rest/interactions.go @@ -1,51 +1,72 @@ package rest import ( + "bytes" "context" + "errors" "net/http" "github.com/bwmarrin/discordgo" ) -func (c *Client) SendInteractionResponse(ctx context.Context, id, token string, data discordgo.InteractionResponse) error { - var files []*discordgo.File - if data.Data != nil { - files = data.Data.Files +type File struct { + Data []byte + Name string +} + +/* +Optimistically send an interaction response update request with fallback to interaction response send request +*/ +func (c *Client) UpdateOrSendInteractionResponse(ctx context.Context, appID, interactionID, token string, data discordgo.InteractionResponse, files []File) error { + err := c.UpdateInteractionResponse(ctx, appID, token, *data.Data, files) + if err != nil { + if errors.Is(err, ErrUnknownWebhook) { + return c.SendInteractionResponse(ctx, interactionID, token, data, files) + } + return err } - req, err := c.interactionRequest("POST", discordgo.EndpointInteractionResponse(id, token), data, files) + return nil +} + +func (c *Client) SendInteractionResponse(ctx context.Context, interactionID, token string, data discordgo.InteractionResponse, files []File) error { + req, err := c.interactionRequest("POST", discordgo.EndpointInteractionResponse(interactionID, token), data, files) if err != nil { return err } return c.do(ctx, req, nil) } -func (c *Client) UpdateInteractionResponse(ctx context.Context, id, token string, data discordgo.InteractionResponseData) error { - req, err := c.interactionRequest("PATCH", discordgo.EndpointInteractionResponseActions(id, token), data, data.Files) +func (c *Client) UpdateInteractionResponse(ctx context.Context, appID, token string, data discordgo.InteractionResponseData, files []File) error { + req, err := c.interactionRequest("PATCH", discordgo.EndpointInteractionResponseActions(appID, token), data, files) if err != nil { return err } return c.do(ctx, req, nil) } -func (c *Client) SendInteractionFollowup(ctx context.Context, id, token string, data discordgo.InteractionResponse) error { - req, err := c.interactionRequest("POST", discordgo.EndpointFollowupMessage(id, token), data, data.Data.Files) +func (c *Client) SendInteractionFollowup(ctx context.Context, appID, token string, data discordgo.InteractionResponse, files []File) error { + req, err := c.interactionRequest("POST", discordgo.EndpointFollowupMessage(appID, token), data, files) if err != nil { return err } return c.do(ctx, req, nil) } -func (c *Client) EditInteractionFollowup(ctx context.Context, id, token string, data discordgo.InteractionResponseData) error { - req, err := c.interactionRequest("PATCH", discordgo.EndpointFollowupMessage(id, token), data, data.Files) +func (c *Client) EditInteractionFollowup(ctx context.Context, appID, token string, data discordgo.InteractionResponseData, files []File) error { + req, err := c.interactionRequest("PATCH", discordgo.EndpointFollowupMessage(appID, token), data, files) if err != nil { return err } return c.do(ctx, req, nil) } -func (c *Client) interactionRequest(method string, url string, payload any, files []*discordgo.File) (*http.Request, error) { +func (c *Client) interactionRequest(method string, url string, payload any, files []File) (*http.Request, error) { if len(files) > 0 { - return c.requestWithFiles(method, url, payload, files) + var df []*discordgo.File + for _, f := range files { + df = append(df, &discordgo.File{Name: f.Name, Reader: bytes.NewReader(f.Data)}) + } + return c.requestWithFiles(method, url, payload, df) } return c.request(method, url, payload) } diff --git a/cmd/discord/router/handler.go b/cmd/discord/router/handler.go index 71f35a6d..5b41fc03 100644 --- a/cmd/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -119,7 +119,7 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { func() (struct{}, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) defer cancel() - err := router.restClient.SendInteractionResponse(ctx, data.ID, data.Token, payload) + err := router.restClient.SendInteractionResponse(ctx, data.ID, data.Token, payload, nil) return struct{}{}, err }, 3, @@ -226,12 +226,12 @@ func (r *Router) sendInteractionReply(ctx context.Context, interaction discordgo switch interaction.Type { case discordgo.InteractionApplicationCommand, discordgo.InteractionMessageComponent: handler = func() error { - return r.restClient.UpdateInteractionResponse(ctx, interaction.AppID, interaction.Token, data) + return r.restClient.UpdateOrSendInteractionResponse(ctx, interaction.AppID, interaction.ID, interaction.Token, discordgo.InteractionResponse{Data: &data, Type: discordgo.InteractionResponseChannelMessageWithSource}, nil) } default: log.Error().Stack().Any("data", data).Str("id", interaction.ID).Msg("unknown interaction type received") handler = func() error { - return r.restClient.UpdateInteractionResponse(ctx, interaction.AppID, interaction.Token, discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed."}) + return r.restClient.UpdateOrSendInteractionResponse(ctx, interaction.AppID, interaction.ID, interaction.Token, discordgo.InteractionResponse{Data: &discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed."}, Type: discordgo.InteractionResponseChannelMessageWithSource}, nil) } } From bbded92cf6696757fb41474a2d742cc517cd92b7 Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 21:25:02 -0400 Subject: [PATCH 233/341] added accounts commands --- cmd/discord/commands/accounts.go | 176 +++++++++++++++++++++++++ cmd/discord/commands/builder/option.go | 8 ++ cmd/discord/commands/link.go | 1 + cmd/discord/common/context.go | 26 +++- cmd/discord/common/reply.go | 27 +++- cmd/discord/rest/interactions.go | 2 +- cmd/discord/router/handler.go | 30 +++-- cmd/discord/router/router.go | 3 + static/localization/en/discord.yaml | 25 ++++ 9 files changed, 279 insertions(+), 19 deletions(-) create mode 100644 cmd/discord/commands/accounts.go diff --git a/cmd/discord/commands/accounts.go b/cmd/discord/commands/accounts.go new file mode 100644 index 00000000..62e81a95 --- /dev/null +++ b/cmd/discord/commands/accounts.go @@ -0,0 +1,176 @@ +package commands + +import ( + "fmt" + "strings" + "unicode/utf8" + + "github.com/bwmarrin/discordgo" + "github.com/cufee/aftermath/cmd/discord/commands/builder" + "github.com/cufee/aftermath/cmd/discord/common" + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" + "github.com/lucsky/cuid" + "github.com/rs/zerolog/log" +) + +func init() { + LoadedPublic.add( + builder.NewCommand("accounts"). + Ephemeral(). + Options( + builder.NewOption("default", discordgo.ApplicationCommandOptionSubCommand). + Params(builder.SetNameKey("command_option_accounts_default_name"), builder.SetDescKey("command_option_accounts_default_description")). + Options( + builder.NewOption("selected", discordgo.ApplicationCommandOptionString). + Autocomplete(). + Required(), + ), + builder.NewOption("linked", discordgo.ApplicationCommandOptionSubCommand). + Params(builder.SetNameKey("command_option_accounts_linked_name"), builder.SetDescKey("command_option_accounts_linked_description")), + ). + Handler(func(ctx *common.Context) error { + // handle subcommand + subcommand, subOptions, _ := ctx.Options().Subcommand() + switch subcommand { + default: + return ctx.Error("received an unexpected subcommand: " + subcommand) + + case "default": + value, _ := subOptions.Value("selected").(string) + parts := strings.Split(value, "#") + if len(parts) != 4 || parts[0] != "valid" { + return ctx.Reply().Send("accounts_error_connection_not_found") + } + connectionID := parts[1] + nickname := parts[2] + realm := parts[3] + + if err := cuid.IsCuid(connectionID); err != nil { + return ctx.Reply().Send("accounts_error_connection_not_found") + } + + for _, conn := range ctx.User.Connections { + if conn.ID != connectionID { + continue + } + conn.Metadata["default"] = true + _, err := ctx.Core.Database().UpdateConnection(ctx.Context, conn) + if err != nil { + if database.IsNotFound(err) { + return ctx.Reply().Send("accounts_error_connection_not_found") + } + return ctx.Err(err) + } + + var content = []string{fmt.Sprintf(ctx.Localize("command_accounts_set_default_successfully_fmt"), nickname, realm)} + if verified, _ := conn.Metadata["verified"].(bool); !verified { + content = append(content, ctx.Localize("command_accounts_verify_cta")) + } + return ctx.Reply().Text(content...).Send() + } + return ctx.Reply().Send("accounts_error_connection_not_found") + + case "linked": + var currentDefault string + var linkedAccounts []string + for _, conn := range ctx.User.Connections { + if conn.Type != models.ConnectionTypeWargaming { + continue + } + linkedAccounts = append(linkedAccounts, conn.ReferenceID) + if def, _ := conn.Metadata["default"].(bool); def { + currentDefault = conn.ReferenceID + } + } + accounts, err := ctx.Core.Database().GetAccounts(ctx.Context, linkedAccounts) + if err != nil && !database.IsNotFound(err) { + return ctx.Err(err) + } + if len(accounts) == 0 { + return ctx.Reply().Send("stats_error_connection_not_found_personal") + } + + var longestName int + for _, a := range accounts { + if l := utf8.RuneCountInString(a.Nickname); l > longestName { + longestName = l + } + } + + var nicknames []string + for _, a := range accounts { + nicknames = append(nicknames, accountToRow(a, longestName, currentDefault == a.ID || len(linkedAccounts) == 1)) + } + return ctx.Reply().Send(strings.Join(nicknames, "\n")) + } + }), + ) + + LoadedPublic.add( + builder.NewCommand("autocomplete_accounts"). + ComponentType(func(s string) bool { + return s == "autocomplete_accounts" + }). + Handler(func(ctx *common.Context) error { + command, _, ok := ctx.Options().Subcommand() + if !ok { + log.Error().Str("command", "autocomplete_accounts").Msg("interaction is not a subcommand") + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("stats_error_connection_not_found_personal"), Value: "error#stats_error_connection_not_found_personal"}) + } + if command != "default" { + log.Error().Str("command", command).Msg("invalid subcommand received in autocomplete_accounts") + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("stats_error_connection_not_found_personal"), Value: "error#stats_error_connection_not_found_personal"}) + } + + var currentDefault string + var linkedAccounts []string + var accountToConnection = make(map[string]string) + for _, conn := range ctx.User.Connections { + if conn.Type != models.ConnectionTypeWargaming { + continue + } + linkedAccounts = append(linkedAccounts, conn.ReferenceID) + accountToConnection[conn.ReferenceID] = conn.ID + if def, _ := conn.Metadata["default"].(bool); def { + currentDefault = conn.ReferenceID + } + + } + if len(linkedAccounts) < 1 { + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("stats_error_connection_not_found_personal"), Value: "error#stats_error_connection_not_found_personal"}) + } + + accounts, err := ctx.Core.Database().GetAccounts(ctx.Context, linkedAccounts) + if err != nil { + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("stats_error_connection_not_found_personal"), Value: "error#stats_error_connection_not_found_personal"}) + } + + var longestName int + for _, a := range accounts { + if l := utf8.RuneCountInString(a.Nickname); l > longestName { + longestName = l + } + } + + var opts []*discordgo.ApplicationCommandOptionChoice + for _, a := range accounts { + opts = append(opts, &discordgo.ApplicationCommandOptionChoice{Name: accountToRow(a, longestName, currentDefault == a.ID || len(linkedAccounts) == 1), Value: fmt.Sprintf("valid#%s#%s#%s", accountToConnection[a.ID], a.Nickname, a.Realm)}) + } + return ctx.Reply().Choices(opts...) + }), + ) +} + +func accountToRow(account models.Account, padding int, isDefault bool) string { + var row string + row += account.Nickname + strings.Repeat(" ", padding-utf8.RuneCountInString(account.Nickname)) + row += " [" + account.Realm + "]" + if isDefault { + row += " ✅" + } + if account.Private { + row += " 🔒" + } + return row +} diff --git a/cmd/discord/commands/builder/option.go b/cmd/discord/commands/builder/option.go index 17f7b142..1b72c088 100644 --- a/cmd/discord/commands/builder/option.go +++ b/cmd/discord/commands/builder/option.go @@ -27,6 +27,8 @@ type Option struct { choices []OptionChoice options []Option + + autocomplete bool } func NewOption(name string, kind discordgo.ApplicationCommandOptionType) Option { @@ -81,6 +83,11 @@ func (o Option) Choices(choices ...OptionChoice) Option { return o } +func (o Option) Autocomplete() Option { + o.autocomplete = true + return o +} + func (o Option) Build(command string) discordgo.ApplicationCommandOption { if o.kind == 0 { panic("option type is not set") @@ -104,6 +111,7 @@ func (o Option) Build(command string) discordgo.ApplicationCommandOption { return discordgo.ApplicationCommandOption{ Name: strings.ToLower(stringOr(nameLocalized[discordgo.EnglishUS], o.name)), Description: stringOr(descLocalized[discordgo.EnglishUS], o.name), + Autocomplete: o.autocomplete, NameLocalizations: nameLocalized, DescriptionLocalizations: descLocalized, MinLength: o.minLength, diff --git a/cmd/discord/commands/link.go b/cmd/discord/commands/link.go index 87705e5a..6ce21e3c 100644 --- a/cmd/discord/commands/link.go +++ b/cmd/discord/commands/link.go @@ -56,6 +56,7 @@ func init() { currentConnection.Type = models.ConnectionTypeWargaming } + currentConnection.Metadata["default"] = true currentConnection.Metadata["verified"] = false currentConnection.ReferenceID = fmt.Sprint(account.ID) diff --git a/cmd/discord/common/context.go b/cmd/discord/common/context.go index 2edd93d3..2868c437 100644 --- a/cmd/discord/common/context.go +++ b/cmd/discord/common/context.go @@ -76,14 +76,14 @@ func NewContext(ctx context.Context, interaction discordgo.Interaction, rest *re return c, nil } -func (c *Context) respond(data discordgo.InteractionResponseData, files []rest.File) error { +func (c *Context) respond(data discordgo.InteractionResponse, files []rest.File) error { select { case <-c.Context.Done(): return c.Context.Err() default: ctx, cancel := context.WithTimeout(c.Context, time.Millisecond*3000) defer cancel() - err := c.rest.UpdateOrSendInteractionResponse(ctx, c.interaction.AppID, c.interaction.ID, c.interaction.Token, discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &data}, files) + err := c.rest.UpdateOrSendInteractionResponse(ctx, c.interaction.AppID, c.interaction.ID, c.interaction.Token, data, files) if os.IsTimeout(err) { // request timed out, most likely a discord issue log.Error().Str("id", c.ID()).Str("interactionId", c.interaction.ID).Msg("discord api: request timed out while responding to an interaction") @@ -105,12 +105,12 @@ func (c *Context) Reply() reply { func (c *Context) Err(err error) error { log.Err(err).Str("interactionId", c.interaction.ID).Msg("error while handling an interaction") - return c.Reply().Send("common_error_unhandled_not_reported") + return c.Reply().Send("common_error_unhandled_reported") } func (c *Context) Error(message string) error { log.Error().Str("message", message).Str("interactionId", c.interaction.ID).Msg("error while handling an interaction") - return c.Reply().Send("common_error_unhandled_not_reported") + return c.Reply().Send("common_error_unhandled_reported") } func (c *Context) isCommand() bool { @@ -121,6 +121,14 @@ func (c *Context) isComponentInteraction() bool { return c.interaction.Type == discordgo.InteractionMessageComponent } +func (c *Context) isAutocompleteInteraction() bool { + return c.interaction.Type == discordgo.InteractionApplicationCommandAutocomplete +} + +func (c *Context) isModalSubmit() bool { + return c.interaction.Type == discordgo.InteractionModalSubmit +} + func (c *Context) ID() string { if c.isCommand() { d, _ := c.CommandData() @@ -134,7 +142,7 @@ func (c *Context) ID() string { } func (c *Context) Options() options { - if data, ok := c.CommandData(); ok { + if data, ok := c.interaction.Data.(discordgo.ApplicationCommandInteractionData); ok { return data.Options } return options{} @@ -193,3 +201,11 @@ func (c *Context) ComponentData() (discordgo.MessageComponentInteractionData, bo data, ok := c.interaction.Data.(discordgo.MessageComponentInteractionData) return data, ok } + +func (c *Context) AutocompleteData() (discordgo.ApplicationCommandInteractionData, bool) { + if !c.isAutocompleteInteraction() { + return discordgo.ApplicationCommandInteractionData{}, false + } + data, ok := c.interaction.Data.(discordgo.ApplicationCommandInteractionData) + return data, ok +} diff --git a/cmd/discord/common/reply.go b/cmd/discord/common/reply.go index 89662a07..d107ceff 100644 --- a/cmd/discord/common/reply.go +++ b/cmd/discord/common/reply.go @@ -1,11 +1,14 @@ package common import ( + "context" "fmt" "strings" + "time" "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmd/discord/rest" + "github.com/rs/zerolog/log" ) type reply struct { @@ -17,6 +20,17 @@ type reply struct { embeds []*discordgo.MessageEmbed } +func (r reply) Choices(data ...*discordgo.ApplicationCommandOptionChoice) error { + ctx, cancel := context.WithTimeout(r.ctx.Context, time.Millisecond*3000) + defer cancel() + err := r.ctx.rest.UpdateOrSendInteractionResponse(ctx, r.ctx.interaction.AppID, r.ctx.interaction.ID, r.ctx.interaction.Token, discordgo.InteractionResponse{Type: discordgo.InteractionApplicationCommandAutocompleteResult, Data: &discordgo.InteractionResponseData{Choices: data}}, nil) + if err != nil { + log.Err(err).Str("interactionId", r.ctx.interaction.ID).Msg("failed to send an autocomplete response") + } + return nil + +} + func (r reply) Text(message ...string) reply { r.text = append(r.text, message...) return r @@ -60,15 +74,18 @@ func (r reply) Send(content ...string) error { return r.ctx.respond(r.data()) } -func (r reply) data() (discordgo.InteractionResponseData, []rest.File) { +func (r reply) data() (discordgo.InteractionResponse, []rest.File) { var content []string for _, t := range r.text { content = append(content, r.ctx.Localize(t)) } - return discordgo.InteractionResponseData{ - Content: strings.Join(content, "\n"), - Components: r.components, - Embeds: r.embeds, + return discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: strings.Join(content, "\n"), + Components: r.components, + Embeds: r.embeds, + }, }, r.files } diff --git a/cmd/discord/rest/interactions.go b/cmd/discord/rest/interactions.go index 382a76b8..bd280f67 100644 --- a/cmd/discord/rest/interactions.go +++ b/cmd/discord/rest/interactions.go @@ -20,7 +20,7 @@ Optimistically send an interaction response update request with fallback to inte func (c *Client) UpdateOrSendInteractionResponse(ctx context.Context, appID, interactionID, token string, data discordgo.InteractionResponse, files []File) error { err := c.UpdateInteractionResponse(ctx, appID, token, *data.Data, files) if err != nil { - if errors.Is(err, ErrUnknownWebhook) { + if errors.Is(err, ErrUnknownWebhook) || errors.Is(err, ErrUnknownInteraction) { return c.SendInteractionResponse(ctx, interactionID, token, data, files) } return err diff --git a/cmd/discord/router/handler.go b/cmd/discord/router/handler.go index 5b41fc03..226dfbfb 100644 --- a/cmd/discord/router/handler.go +++ b/cmd/discord/router/handler.go @@ -10,6 +10,7 @@ import ( "io" "net/http" "runtime/debug" + "slices" "time" "github.com/pkg/errors" @@ -22,6 +23,8 @@ import ( "github.com/rs/zerolog/log" ) +var supportedInteractionTypes = []discordgo.InteractionType{discordgo.InteractionApplicationCommand, discordgo.InteractionMessageComponent, discordgo.InteractionApplicationCommandAutocomplete, discordgo.InteractionModalSubmit} + // https://github.com/bsdlp/discord-interactions-go/blob/main/interactions/verify.go func verifyPublicKey(r *http.Request, key ed25519.PublicKey) bool { var msg bytes.Buffer @@ -108,12 +111,16 @@ func (router *Router) HTTPHandler() (http.HandlerFunc, error) { } // ack the interaction proactively - payload, err := deferredInteractionResponsePayload(data.Type, cmd.Ephemeral) + payload, needsAck, err := deferredInteractionResponsePayload(data.Type, cmd.Ephemeral) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Err(err).Str("id", data.ID).Msg("failed to make an ack response payload") return } + if !needsAck { + router.handleInteraction(data, cmd) // we wait for the handler to reply + return + } res := retry.Retry( func() (struct{}, error) { @@ -160,6 +167,9 @@ func (r *Router) routeInteraction(interaction discordgo.Interaction) (builder.Co case discordgo.InteractionMessageComponent: data, _ := interaction.Data.(discordgo.MessageComponentInteractionData) matchKey = data.CustomID + case discordgo.InteractionApplicationCommandAutocomplete: + data, _ := interaction.Data.(discordgo.ApplicationCommandInteractionData) + matchKey = "autocomplete_" + data.Name } if matchKey == "" { @@ -223,12 +233,11 @@ func sendPingReply(w http.ResponseWriter) { func (r *Router) sendInteractionReply(ctx context.Context, interaction discordgo.Interaction, data discordgo.InteractionResponseData) { var handler func() error - switch interaction.Type { - case discordgo.InteractionApplicationCommand, discordgo.InteractionMessageComponent: + if slices.Contains(supportedInteractionTypes, interaction.Type) { handler = func() error { return r.restClient.UpdateOrSendInteractionResponse(ctx, interaction.AppID, interaction.ID, interaction.Token, discordgo.InteractionResponse{Data: &data, Type: discordgo.InteractionResponseChannelMessageWithSource}, nil) } - default: + } else { log.Error().Stack().Any("data", data).Str("id", interaction.ID).Msg("unknown interaction type received") handler = func() error { return r.restClient.UpdateOrSendInteractionResponse(ctx, interaction.AppID, interaction.ID, interaction.Token, discordgo.InteractionResponse{Data: &discordgo.InteractionResponseData{Content: "Something unexpected happened and your command failed."}, Type: discordgo.InteractionResponseChannelMessageWithSource}, nil) @@ -242,19 +251,24 @@ func (r *Router) sendInteractionReply(ctx context.Context, interaction discordgo } } -func deferredInteractionResponsePayload(t discordgo.InteractionType, ephemeral bool) (discordgo.InteractionResponse, error) { +func deferredInteractionResponsePayload(t discordgo.InteractionType, ephemeral bool) (discordgo.InteractionResponse, bool, error) { var response discordgo.InteractionResponse if ephemeral { + if response.Data == nil { + response.Data = &discordgo.InteractionResponseData{} + } response.Data.Flags = discordgo.MessageFlagsEphemeral } switch t { - case discordgo.InteractionApplicationCommand, discordgo.InteractionMessageComponent: + case discordgo.InteractionApplicationCommand, discordgo.InteractionMessageComponent, discordgo.InteractionModalSubmit: response.Type = discordgo.InteractionResponseDeferredChannelMessageWithSource + case discordgo.InteractionApplicationCommandAutocomplete: + return response, false, nil default: - return response, fmt.Errorf("interaction type %s not supported", t.String()) + return response, false, fmt.Errorf("interaction type %s not supported", t.String()) } - return response, nil + return response, true, nil } diff --git a/cmd/discord/router/router.go b/cmd/discord/router/router.go index bb829bb0..71f8dcfd 100644 --- a/cmd/discord/router/router.go +++ b/cmd/discord/router/router.go @@ -67,6 +67,9 @@ Updates all loaded commands using the Discord REST API func (r *Router) UpdateLoadedCommands(ctx context.Context) error { var commandByName = make(map[string]command) for _, cmd := range r.commands { + if cmd.Type != builder.CommandTypeChat { + continue + } commandByName[cmd.Name] = command{requested: &cmd} } diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml index 0de71987..ae295ab9 100755 --- a/static/localization/en/discord.yaml +++ b/static/localization/en/discord.yaml @@ -20,6 +20,25 @@ value: Link your Blitz account to Aftermath - key: command_link_linked_successfully_fmt value: "Your account has been linked! Aftermath will now default to **%s** on **%s** when checking stats.\nYou can also verify your account with `/verify`" +# /accounts +- key: command_accounts_name + value: accounts +- key: command_accounts_description + value: View and manage your linked accounts +- key: command_option_accounts_default_name + value: default +- key: command_option_accounts_default_description + value: Set the default account Aftermath will check when using commands +- key: command_option_accounts_linked_name + value: linked +- key: command_option_accounts_linked_description + value: View your linked Blitz accounts + +- key: command_accounts_set_default_successfully_fmt + value: "Your default account has been updated! Aftermath will now default to **%s** on **%s** when checking stats." +- key: command_accounts_verify_cta + value: "You can also verify your account with `/verify`" + context: A new default account was set, but it is not verified # Common command options # days @@ -178,6 +197,12 @@ value: "Please select a server your account is registered on." context: Server not selected + # /accounts errors +- key: accounts_error_connection_not_found + value: "I was not able to find your linked account with this nickname. Try adding it through `/link` instead." + context: Nickname is invalid or failed to find a valid connection + + # Wargaming specific errors - key: wargaming_error_private_account value: "This account is marked private by Wargaming and no stats are available for it at this time." From 6afdadc69ddb1eb54edc28ad6f7abd63e4ebd51f Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 22:26:11 -0400 Subject: [PATCH 234/341] multiple accounts linking support --- cmd/discord/commands/accounts.go | 83 +++++++++++++++++++---------- cmd/discord/commands/link.go | 57 +++++++++++++++----- cmd/discord/commands/session.go | 4 +- cmd/discord/commands/stats.go | 4 +- internal/database/client.go | 1 + internal/database/models/user.go | 8 ++- internal/database/users.go | 4 ++ static/localization/en/discord.yaml | 12 +++++ 8 files changed, 128 insertions(+), 45 deletions(-) diff --git a/cmd/discord/commands/accounts.go b/cmd/discord/commands/accounts.go index 62e81a95..b2157651 100644 --- a/cmd/discord/commands/accounts.go +++ b/cmd/discord/commands/accounts.go @@ -10,7 +10,6 @@ import ( "github.com/cufee/aftermath/cmd/discord/common" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" - "github.com/lucsky/cuid" "github.com/rs/zerolog/log" ) @@ -26,6 +25,13 @@ func init() { Autocomplete(). Required(), ), + builder.NewOption("unlink", discordgo.ApplicationCommandOptionSubCommand). + Params(builder.SetNameKey("command_option_accounts_unlink_name"), builder.SetDescKey("command_option_accounts_unlink_description")). + Options( + builder.NewOption("selected", discordgo.ApplicationCommandOptionString). + Autocomplete(). + Required(), + ), builder.NewOption("linked", discordgo.ApplicationCommandOptionSubCommand). Params(builder.SetNameKey("command_option_accounts_linked_name"), builder.SetDescKey("command_option_accounts_linked_description")), ). @@ -42,19 +48,22 @@ func init() { if len(parts) != 4 || parts[0] != "valid" { return ctx.Reply().Send("accounts_error_connection_not_found") } - connectionID := parts[1] + accountID := parts[1] nickname := parts[2] realm := parts[3] - if err := cuid.IsCuid(connectionID); err != nil { - return ctx.Reply().Send("accounts_error_connection_not_found") - } - + var found bool + var newConnectionVerified bool for _, conn := range ctx.User.Connections { - if conn.ID != connectionID { + if conn.Type != models.ConnectionTypeWargaming { continue } - conn.Metadata["default"] = true + if conn.ReferenceID == accountID { + newConnectionVerified, _ = conn.Metadata["verified"].(bool) + found = true + } + conn.Metadata["default"] = conn.ReferenceID == accountID + _, err := ctx.Core.Database().UpdateConnection(ctx.Context, conn) if err != nil { if database.IsNotFound(err) { @@ -62,14 +71,38 @@ func init() { } return ctx.Err(err) } + } + if !found { + return ctx.Reply().Send("accounts_error_connection_not_found") + } - var content = []string{fmt.Sprintf(ctx.Localize("command_accounts_set_default_successfully_fmt"), nickname, realm)} - if verified, _ := conn.Metadata["verified"].(bool); !verified { - content = append(content, ctx.Localize("command_accounts_verify_cta")) + var content = []string{fmt.Sprintf(ctx.Localize("command_accounts_set_default_successfully_fmt"), nickname, realm)} + if !newConnectionVerified { + content = append(content, ctx.Localize("command_accounts_verify_cta")) + } + return ctx.Reply().Text(content...).Send() + + case "unlink": + value, _ := subOptions.Value("selected").(string) + parts := strings.Split(value, "#") + if len(parts) != 4 || parts[0] != "valid" { + return ctx.Reply().Send("accounts_error_connection_not_found_selected") + } + accountID := parts[1] + + for _, conn := range ctx.User.Connections { + if conn.Type != models.ConnectionTypeWargaming { + continue + } + if conn.ReferenceID == accountID { + err := ctx.Core.Database().DeleteConnection(ctx.Context, conn.ID) + if err != nil { + return ctx.Err(err) + } + return ctx.Reply().Send("command_accounts_unlinked_successfully") } - return ctx.Reply().Text(content...).Send() } - return ctx.Reply().Send("accounts_error_connection_not_found") + return ctx.Reply().Send("accounts_error_connection_not_found_selected") case "linked": var currentDefault string @@ -83,6 +116,7 @@ func init() { currentDefault = conn.ReferenceID } } + accounts, err := ctx.Core.Database().GetAccounts(ctx.Context, linkedAccounts) if err != nil && !database.IsNotFound(err) { return ctx.Err(err) @@ -100,7 +134,7 @@ func init() { var nicknames []string for _, a := range accounts { - nicknames = append(nicknames, accountToRow(a, longestName, currentDefault == a.ID || len(linkedAccounts) == 1)) + nicknames = append(nicknames, accountToRow(a, longestName, currentDefault == a.ID)) } return ctx.Reply().Send(strings.Join(nicknames, "\n")) } @@ -116,34 +150,32 @@ func init() { command, _, ok := ctx.Options().Subcommand() if !ok { log.Error().Str("command", "autocomplete_accounts").Msg("interaction is not a subcommand") - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("stats_error_connection_not_found_personal"), Value: "error#stats_error_connection_not_found_personal"}) + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("accounts_error_connection_not_found"), Value: "error#accounts_error_connection_not_found"}) } - if command != "default" { + if command != "default" && command != "unlink" { log.Error().Str("command", command).Msg("invalid subcommand received in autocomplete_accounts") - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("stats_error_connection_not_found_personal"), Value: "error#stats_error_connection_not_found_personal"}) + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("accounts_error_connection_not_found"), Value: "error#accounts_error_connection_not_found"}) } var currentDefault string var linkedAccounts []string - var accountToConnection = make(map[string]string) for _, conn := range ctx.User.Connections { if conn.Type != models.ConnectionTypeWargaming { continue } linkedAccounts = append(linkedAccounts, conn.ReferenceID) - accountToConnection[conn.ReferenceID] = conn.ID if def, _ := conn.Metadata["default"].(bool); def { currentDefault = conn.ReferenceID } } if len(linkedAccounts) < 1 { - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("stats_error_connection_not_found_personal"), Value: "error#stats_error_connection_not_found_personal"}) + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("accounts_error_connection_not_found"), Value: "error#accounts_error_connection_not_found"}) } accounts, err := ctx.Core.Database().GetAccounts(ctx.Context, linkedAccounts) if err != nil { - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("stats_error_connection_not_found_personal"), Value: "error#stats_error_connection_not_found_personal"}) + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("accounts_error_connection_not_found"), Value: "error#accounts_error_connection_not_found"}) } var longestName int @@ -155,7 +187,7 @@ func init() { var opts []*discordgo.ApplicationCommandOptionChoice for _, a := range accounts { - opts = append(opts, &discordgo.ApplicationCommandOptionChoice{Name: accountToRow(a, longestName, currentDefault == a.ID || len(linkedAccounts) == 1), Value: fmt.Sprintf("valid#%s#%s#%s", accountToConnection[a.ID], a.Nickname, a.Realm)}) + opts = append(opts, &discordgo.ApplicationCommandOptionChoice{Name: accountToRow(a, longestName, currentDefault == a.ID), Value: fmt.Sprintf("valid#%s#%s#%s", a.ID, a.Nickname, a.Realm)}) } return ctx.Reply().Choices(opts...) }), @@ -164,13 +196,10 @@ func init() { func accountToRow(account models.Account, padding int, isDefault bool) string { var row string + row += "[" + account.Realm + "] " row += account.Nickname + strings.Repeat(" ", padding-utf8.RuneCountInString(account.Nickname)) - row += " [" + account.Realm + "]" if isDefault { - row += " ✅" - } - if account.Private { - row += " 🔒" + row += " ⭐" } return row } diff --git a/cmd/discord/commands/link.go b/cmd/discord/commands/link.go index 6ce21e3c..dc2856c1 100644 --- a/cmd/discord/commands/link.go +++ b/cmd/discord/commands/link.go @@ -1,8 +1,10 @@ package commands import ( + "context" "fmt" "strings" + "time" "github.com/bwmarrin/discordgo" "github.com/cufee/aftermath/cmd/discord/commands/builder" @@ -35,6 +37,17 @@ func init() { ), ). Handler(func(ctx *common.Context) error { + var wgConnections []models.UserConnection + for _, conn := range ctx.User.Connections { + if conn.Type != models.ConnectionTypeWargaming { + continue + } + wgConnections = append(wgConnections, conn) + } + if len(wgConnections) >= 3 { + return ctx.Reply().Send("link_error_too_many_connections") + } + options := getDefaultStatsOptions(ctx) message, valid := options.Validate(ctx) if !valid { @@ -49,21 +62,39 @@ func init() { return ctx.Err(err) } - currentConnection, exists := ctx.User.Connection(models.ConnectionTypeWargaming) - if !exists { - currentConnection.UserID = ctx.User.ID - currentConnection.Metadata = make(map[string]any) - currentConnection.Type = models.ConnectionTypeWargaming - } - - currentConnection.Metadata["default"] = true - currentConnection.Metadata["verified"] = false - currentConnection.ReferenceID = fmt.Sprint(account.ID) + var found bool + for _, conn := range wgConnections { + if conn.ReferenceID == fmt.Sprint(account.ID) { + conn.Metadata["verified"] = false + found = true + } + conn.Metadata["default"] = conn.ReferenceID == fmt.Sprint(account.ID) - _, err = ctx.Core.Database().UpsertConnection(ctx.Context, currentConnection) - if err != nil { - return ctx.Err(err) + _, err = ctx.Core.Database().UpsertConnection(ctx.Context, conn) + if err != nil { + return ctx.Err(err) + } } + if !found { + meta := make(map[string]any) + meta["verified"] = false + meta["default"] = true + _, err = ctx.Core.Database().UpsertConnection(ctx.Context, models.UserConnection{ + Type: models.ConnectionTypeWargaming, + ReferenceID: fmt.Sprint(account.ID), + UserID: ctx.User.ID, + Metadata: meta, + }) + if err != nil { + return ctx.Err(err) + } + } + + go func(id string) { + c, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _, _ = ctx.Core.Fetch().Account(c, id) // Make sure the account is cached + }(fmt.Sprint(account.ID)) return ctx.Reply().Format("command_link_linked_successfully_fmt", account.Nickname, strings.ToUpper(options.Server)).Send() }), diff --git a/cmd/discord/commands/session.go b/cmd/discord/commands/session.go index c5386b4a..1fbd9564 100644 --- a/cmd/discord/commands/session.go +++ b/cmd/discord/commands/session.go @@ -36,7 +36,7 @@ func init() { case options.UserID != "": // mentioned another user, check if the user has an account linked mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithSubscriptions(), database.WithContent()) - defaultAccount, hasDefaultAccount := mentionedUser.Connection(models.ConnectionTypeWargaming) + defaultAccount, hasDefaultAccount := mentionedUser.Connection(models.ConnectionTypeWargaming, map[string]any{"default": true}) if !hasDefaultAccount { return ctx.Reply().Send("stats_error_connection_not_found_vague") } @@ -58,7 +58,7 @@ func init() { accountID = fmt.Sprint(account.ID) default: - defaultAccount, hasDefaultAccount := ctx.User.Connection(models.ConnectionTypeWargaming) + defaultAccount, hasDefaultAccount := ctx.User.Connection(models.ConnectionTypeWargaming, map[string]any{"default": true}) if !hasDefaultAccount { return ctx.Reply().Send("stats_error_nickname_or_server_missing") } diff --git a/cmd/discord/commands/stats.go b/cmd/discord/commands/stats.go index 61a1f0cb..e0251ef4 100644 --- a/cmd/discord/commands/stats.go +++ b/cmd/discord/commands/stats.go @@ -33,7 +33,7 @@ func init() { case options.UserID != "": // mentioned another user, check if the user has an account linked mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithSubscriptions(), database.WithContent()) - defaultAccount, hasDefaultAccount := mentionedUser.Connection(models.ConnectionTypeWargaming) + defaultAccount, hasDefaultAccount := mentionedUser.Connection(models.ConnectionTypeWargaming, map[string]any{"default": true}) if !hasDefaultAccount { return ctx.Reply().Send("stats_error_connection_not_found_vague") } @@ -52,7 +52,7 @@ func init() { accountID = fmt.Sprint(account.ID) default: - defaultAccount, hasDefaultAccount := ctx.User.Connection(models.ConnectionTypeWargaming) + defaultAccount, hasDefaultAccount := ctx.User.Connection(models.ConnectionTypeWargaming, map[string]any{"default": true}) if !hasDefaultAccount { return ctx.Reply().Send("stats_error_nickname_or_server_missing") } diff --git a/internal/database/client.go b/internal/database/client.go index 8d682d4a..cdcb8670 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -41,6 +41,7 @@ type UsersClient interface { UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) UpsertConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) + DeleteConnection(ctx context.Context, connectionID string) error } type SnapshotsClient interface { diff --git a/internal/database/models/user.go b/internal/database/models/user.go index efdfe56a..e0c8b1fc 100644 --- a/internal/database/models/user.go +++ b/internal/database/models/user.go @@ -34,9 +34,15 @@ func (u User) HasPermission(value permissions.Permissions) bool { return perms.Has(value) } -func (u User) Connection(kind ConnectionType) (UserConnection, bool) { +func (u User) Connection(kind ConnectionType, conditions map[string]any) (UserConnection, bool) { +outerLoop: for _, connection := range u.Connections { if connection.Type == kind { + for key, value := range conditions { + if connection.Metadata[key] != value { + continue outerLoop + } + } return connection, true } } diff --git a/internal/database/users.go b/internal/database/users.go index e172ae0b..27d8fed7 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -207,3 +207,7 @@ func (c *client) UpsertConnection(ctx context.Context, connection models.UserCon return connection, nil } + +func (c *client) DeleteConnection(ctx context.Context, connectionID string) error { + return c.db.UserConnection.DeleteOneID(connectionID).Exec(ctx) +} diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml index ae295ab9..aefa15a3 100755 --- a/static/localization/en/discord.yaml +++ b/static/localization/en/discord.yaml @@ -33,9 +33,15 @@ value: linked - key: command_option_accounts_linked_description value: View your linked Blitz accounts +- key: command_option_accounts_unlink_name + value: unlink +- key: command_option_accounts_unlink_description + value: Unlink your Blitz account - key: command_accounts_set_default_successfully_fmt value: "Your default account has been updated! Aftermath will now default to **%s** on **%s** when checking stats." +- key: command_accounts_unlinked_successfully + value: "Your linked account was removed." - key: command_accounts_verify_cta value: "You can also verify your account with `/verify`" context: A new default account was set, but it is not verified @@ -191,6 +197,9 @@ - key: link_error_missing_input value: "I need both the nickname and server to find your account." context: Nickname or server not provided +- key: link_error_too_many_connections + value: "It looks like you have reached the limit for the number of Blitz accounts linked.\nTry removing an existing account with `/accounts unlink` before adding a new one." + context: User reached their connection limit for Blitz accounts # /verify errors - key: verify_error_missing_server @@ -201,6 +210,9 @@ - key: accounts_error_connection_not_found value: "I was not able to find your linked account with this nickname. Try adding it through `/link` instead." context: Nickname is invalid or failed to find a valid connection +- key: accounts_error_connection_not_found_selected + value: "I was not able to the account you have selected." + context: Failed to find the selected account by ID # Wargaming specific errors From 61ca515672fa4112d2d16ca94524daede2d1c57d Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 22:48:48 -0400 Subject: [PATCH 235/341] unified commands --- cmd/discord/commands/link.go | 102 ------------- .../commands/{accounts.go => links.go} | 143 ++++++++++++++---- cmd/discord/commands/options.go | 10 +- cmd/discord/commands/session.go | 2 +- cmd/discord/commands/stats.go | 2 +- cmd/discord/common/context.go | 13 +- cmd/discord/discord.go | 2 +- static/localization/en/discord.yaml | 71 +++++---- tests/static_database.go | 3 + 9 files changed, 160 insertions(+), 188 deletions(-) delete mode 100644 cmd/discord/commands/link.go rename cmd/discord/commands/{accounts.go => links.go} (51%) diff --git a/cmd/discord/commands/link.go b/cmd/discord/commands/link.go deleted file mode 100644 index dc2856c1..00000000 --- a/cmd/discord/commands/link.go +++ /dev/null @@ -1,102 +0,0 @@ -package commands - -import ( - "context" - "fmt" - "strings" - "time" - - "github.com/bwmarrin/discordgo" - "github.com/cufee/aftermath/cmd/discord/commands/builder" - "github.com/cufee/aftermath/cmd/discord/common" - "github.com/cufee/aftermath/internal/database/models" -) - -func init() { - LoadedPublic.add( - builder.NewCommand("link"). - Options( - builder.NewOption("nickname", discordgo.ApplicationCommandOptionString). - Required(). - Min(5). - Max(30). - Params( - builder.SetNameKey("common_option_stats_nickname_name"), - builder.SetDescKey("common_option_stats_nickname_description"), - ), - builder.NewOption("server", discordgo.ApplicationCommandOptionString). - Required(). - Params( - builder.SetNameKey("common_option_stats_realm_name"), - builder.SetDescKey("common_option_stats_realm_description"), - ). - Choices( - builder.NewChoice("realm_na", "na").Params(builder.SetNameKey("common_label_realm_na")), - builder.NewChoice("realm_eu", "eu").Params(builder.SetNameKey("common_label_realm_eu")), - builder.NewChoice("realm_as", "as").Params(builder.SetNameKey("common_label_realm_as")), - ), - ). - Handler(func(ctx *common.Context) error { - var wgConnections []models.UserConnection - for _, conn := range ctx.User.Connections { - if conn.Type != models.ConnectionTypeWargaming { - continue - } - wgConnections = append(wgConnections, conn) - } - if len(wgConnections) >= 3 { - return ctx.Reply().Send("link_error_too_many_connections") - } - - options := getDefaultStatsOptions(ctx) - message, valid := options.Validate(ctx) - if !valid { - return ctx.Reply().Send(message) - } - - account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) - if err != nil { - if err.Error() == "no results found" { - return ctx.Reply().Format("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() - } - return ctx.Err(err) - } - - var found bool - for _, conn := range wgConnections { - if conn.ReferenceID == fmt.Sprint(account.ID) { - conn.Metadata["verified"] = false - found = true - } - conn.Metadata["default"] = conn.ReferenceID == fmt.Sprint(account.ID) - - _, err = ctx.Core.Database().UpsertConnection(ctx.Context, conn) - if err != nil { - return ctx.Err(err) - } - } - if !found { - meta := make(map[string]any) - meta["verified"] = false - meta["default"] = true - _, err = ctx.Core.Database().UpsertConnection(ctx.Context, models.UserConnection{ - Type: models.ConnectionTypeWargaming, - ReferenceID: fmt.Sprint(account.ID), - UserID: ctx.User.ID, - Metadata: meta, - }) - if err != nil { - return ctx.Err(err) - } - } - - go func(id string) { - c, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - _, _ = ctx.Core.Fetch().Account(c, id) // Make sure the account is cached - }(fmt.Sprint(account.ID)) - - return ctx.Reply().Format("command_link_linked_successfully_fmt", account.Nickname, strings.ToUpper(options.Server)).Send() - }), - ) -} diff --git a/cmd/discord/commands/accounts.go b/cmd/discord/commands/links.go similarity index 51% rename from cmd/discord/commands/accounts.go rename to cmd/discord/commands/links.go index b2157651..1ddd7b7c 100644 --- a/cmd/discord/commands/accounts.go +++ b/cmd/discord/commands/links.go @@ -1,8 +1,10 @@ package commands import ( + "context" "fmt" "strings" + "time" "unicode/utf8" "github.com/bwmarrin/discordgo" @@ -10,30 +12,53 @@ import ( "github.com/cufee/aftermath/cmd/discord/common" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" - "github.com/rs/zerolog/log" ) func init() { LoadedPublic.add( - builder.NewCommand("accounts"). + builder.NewCommand("links"). Ephemeral(). Options( - builder.NewOption("default", discordgo.ApplicationCommandOptionSubCommand). - Params(builder.SetNameKey("command_option_accounts_default_name"), builder.SetDescKey("command_option_accounts_default_description")). + builder.NewOption("add", discordgo.ApplicationCommandOptionSubCommand). + Params(builder.SetNameKey("command_option_links_add_name"), builder.SetDescKey("command_option_links_add_desc")). + Options( + builder.NewOption("nickname", discordgo.ApplicationCommandOptionString). + Required(). + Min(5). + Max(30). + Params( + builder.SetNameKey("common_option_stats_nickname_name"), + builder.SetDescKey("common_option_stats_nickname_description"), + ), + builder.NewOption("server", discordgo.ApplicationCommandOptionString). + Required(). + Params( + builder.SetNameKey("common_option_stats_realm_name"), + builder.SetDescKey("common_option_stats_realm_description"), + ). + Choices( + builder.NewChoice("realm_na", "NA").Params(builder.SetNameKey("common_label_realm_na")), + builder.NewChoice("realm_eu", "EU").Params(builder.SetNameKey("common_label_realm_eu")), + builder.NewChoice("realm_as", "AS").Params(builder.SetNameKey("common_label_realm_as")), + ), + ), + builder.NewOption("favorite", discordgo.ApplicationCommandOptionSubCommand). + Params(builder.SetNameKey("command_option_links_fav_name"), builder.SetDescKey("command_option_links_fav_desc")). Options( builder.NewOption("selected", discordgo.ApplicationCommandOptionString). Autocomplete(). Required(), ), - builder.NewOption("unlink", discordgo.ApplicationCommandOptionSubCommand). - Params(builder.SetNameKey("command_option_accounts_unlink_name"), builder.SetDescKey("command_option_accounts_unlink_description")). + + builder.NewOption("view", discordgo.ApplicationCommandOptionSubCommand). + Params(builder.SetNameKey("command_option_links_view_name"), builder.SetDescKey("command_option_links_view_desc")), + builder.NewOption("remove", discordgo.ApplicationCommandOptionSubCommand). + Params(builder.SetNameKey("command_option_links_remove_name"), builder.SetDescKey("command_option_links_remove_desc")). Options( builder.NewOption("selected", discordgo.ApplicationCommandOptionString). Autocomplete(). Required(), ), - builder.NewOption("linked", discordgo.ApplicationCommandOptionSubCommand). - Params(builder.SetNameKey("command_option_accounts_linked_name"), builder.SetDescKey("command_option_accounts_linked_description")), ). Handler(func(ctx *common.Context) error { // handle subcommand @@ -42,11 +67,11 @@ func init() { default: return ctx.Error("received an unexpected subcommand: " + subcommand) - case "default": + case "favorite": value, _ := subOptions.Value("selected").(string) parts := strings.Split(value, "#") if len(parts) != 4 || parts[0] != "valid" { - return ctx.Reply().Send("accounts_error_connection_not_found") + return ctx.Reply().Send("links_error_connection_not_found") } accountID := parts[1] nickname := parts[2] @@ -67,26 +92,26 @@ func init() { _, err := ctx.Core.Database().UpdateConnection(ctx.Context, conn) if err != nil { if database.IsNotFound(err) { - return ctx.Reply().Send("accounts_error_connection_not_found") + return ctx.Reply().Send("links_error_connection_not_found") } return ctx.Err(err) } } if !found { - return ctx.Reply().Send("accounts_error_connection_not_found") + return ctx.Reply().Send("links_error_connection_not_found") } - var content = []string{fmt.Sprintf(ctx.Localize("command_accounts_set_default_successfully_fmt"), nickname, realm)} + var content = []string{fmt.Sprintf(ctx.Localize("command_links_set_default_successfully_fmt"), nickname, realm)} if !newConnectionVerified { - content = append(content, ctx.Localize("command_accounts_verify_cta")) + content = append(content, ctx.Localize("command_links_verify_cta")) } return ctx.Reply().Text(content...).Send() - case "unlink": + case "remove": value, _ := subOptions.Value("selected").(string) parts := strings.Split(value, "#") if len(parts) != 4 || parts[0] != "valid" { - return ctx.Reply().Send("accounts_error_connection_not_found_selected") + return ctx.Reply().Send("links_error_connection_not_found_selected") } accountID := parts[1] @@ -99,12 +124,12 @@ func init() { if err != nil { return ctx.Err(err) } - return ctx.Reply().Send("command_accounts_unlinked_successfully") + return ctx.Reply().Send("command_links_unlinked_successfully") } } - return ctx.Reply().Send("accounts_error_connection_not_found_selected") + return ctx.Reply().Send("links_error_connection_not_found_selected") - case "linked": + case "view": var currentDefault string var linkedAccounts []string for _, conn := range ctx.User.Connections { @@ -137,26 +162,78 @@ func init() { nicknames = append(nicknames, accountToRow(a, longestName, currentDefault == a.ID)) } return ctx.Reply().Send(strings.Join(nicknames, "\n")) + + case "add": + var wgConnections []models.UserConnection + for _, conn := range ctx.User.Connections { + if conn.Type != models.ConnectionTypeWargaming { + continue + } + wgConnections = append(wgConnections, conn) + } + if len(wgConnections) >= 3 { + return ctx.Reply().Send("links_error_too_many_connections") + } + + options := getDefaultStatsOptions(subOptions) + message, valid := options.Validate(ctx) + if !valid { + return ctx.Reply().Send(message) + } + + account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) + if err != nil { + if err.Error() == "no results found" { + return ctx.Reply().Format("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)).Send() + } + return ctx.Err(err) + } + + var found bool + for _, conn := range wgConnections { + if conn.ReferenceID == fmt.Sprint(account.ID) { + conn.Metadata["verified"] = false + found = true + } + conn.Metadata["default"] = conn.ReferenceID == fmt.Sprint(account.ID) + + _, err = ctx.Core.Database().UpsertConnection(ctx.Context, conn) + if err != nil { + return ctx.Err(err) + } + } + if !found { + meta := make(map[string]any) + meta["verified"] = false + meta["default"] = true + _, err = ctx.Core.Database().UpsertConnection(ctx.Context, models.UserConnection{ + Type: models.ConnectionTypeWargaming, + ReferenceID: fmt.Sprint(account.ID), + UserID: ctx.User.ID, + Metadata: meta, + }) + if err != nil { + return ctx.Err(err) + } + } + + go func(id string) { + c, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _, _ = ctx.Core.Fetch().Account(c, id) // Make sure the account is cached + }(fmt.Sprint(account.ID)) + + return ctx.Reply().Format("command_links_linked_successfully_fmt", account.Nickname, strings.ToUpper(options.Server)).Send() } }), ) LoadedPublic.add( - builder.NewCommand("autocomplete_accounts"). + builder.NewCommand("autocomplete_links"). ComponentType(func(s string) bool { - return s == "autocomplete_accounts" + return s == "autocomplete_links" }). Handler(func(ctx *common.Context) error { - command, _, ok := ctx.Options().Subcommand() - if !ok { - log.Error().Str("command", "autocomplete_accounts").Msg("interaction is not a subcommand") - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("accounts_error_connection_not_found"), Value: "error#accounts_error_connection_not_found"}) - } - if command != "default" && command != "unlink" { - log.Error().Str("command", command).Msg("invalid subcommand received in autocomplete_accounts") - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("accounts_error_connection_not_found"), Value: "error#accounts_error_connection_not_found"}) - } - var currentDefault string var linkedAccounts []string for _, conn := range ctx.User.Connections { @@ -170,12 +247,12 @@ func init() { } if len(linkedAccounts) < 1 { - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("accounts_error_connection_not_found"), Value: "error#accounts_error_connection_not_found"}) + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("links_error_connection_not_found"), Value: "error#links_error_connection_not_found"}) } accounts, err := ctx.Core.Database().GetAccounts(ctx.Context, linkedAccounts) if err != nil { - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("accounts_error_connection_not_found"), Value: "error#accounts_error_connection_not_found"}) + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("links_error_connection_not_found"), Value: "error#links_error_connection_not_found"}) } var longestName int diff --git a/cmd/discord/commands/options.go b/cmd/discord/commands/options.go index c5f3db1e..701a13b9 100644 --- a/cmd/discord/commands/options.go +++ b/cmd/discord/commands/options.go @@ -79,14 +79,14 @@ func (o statsOptions) Validate(ctx *common.Context) (string, bool) { return "", true } -func getDefaultStatsOptions(ctx *common.Context) statsOptions { +func getDefaultStatsOptions(data []*discordgo.ApplicationCommandInteractionDataOption) statsOptions { var options statsOptions - options.Nickname, _ = common.GetOption[string](ctx, "nickname") - options.Server, _ = common.GetOption[string](ctx, "server") - options.UserID, _ = common.GetOption[string](ctx, "user") + options.Nickname, _ = common.GetOption[string](data, "nickname") + options.Server, _ = common.GetOption[string](data, "server") + options.UserID, _ = common.GetOption[string](data, "user") - if days, _ := common.GetOption[float64](ctx, "days"); days > 0 { + if days, _ := common.GetOption[float64](data, "days"); days > 0 { options.Days = int(days) options.PeriodStart = time.Now().Add(time.Hour * 24 * time.Duration(days) * -1) } diff --git a/cmd/discord/commands/session.go b/cmd/discord/commands/session.go index 1fbd9564..2fe129a9 100644 --- a/cmd/discord/commands/session.go +++ b/cmd/discord/commands/session.go @@ -23,7 +23,7 @@ func init() { builder.NewCommand("session"). Options(defaultStatsOptions...). Handler(func(ctx *common.Context) error { - options := getDefaultStatsOptions(ctx) + options := getDefaultStatsOptions(ctx.Options()) message, valid := options.Validate(ctx) if !valid { return ctx.Reply().Send(message) diff --git a/cmd/discord/commands/stats.go b/cmd/discord/commands/stats.go index e0251ef4..f2a7be15 100644 --- a/cmd/discord/commands/stats.go +++ b/cmd/discord/commands/stats.go @@ -20,7 +20,7 @@ func init() { builder.NewCommand("stats"). Options(defaultStatsOptions...). Handler(func(ctx *common.Context) error { - options := getDefaultStatsOptions(ctx) + options := getDefaultStatsOptions(ctx.Options()) message, valid := options.Validate(ctx) if !valid { return ctx.Reply().Send(message) diff --git a/cmd/discord/common/context.go b/cmd/discord/common/context.go index 2868c437..93780453 100644 --- a/cmd/discord/common/context.go +++ b/cmd/discord/common/context.go @@ -172,15 +172,12 @@ func (o options) Subcommand() (string, options, bool) { return "", options{}, false } -func GetOption[T any](c *Context, name string) (T, bool) { +func GetOption[T any](data []*discordgo.ApplicationCommandInteractionDataOption, name string) (T, bool) { var v T - if data, ok := c.CommandData(); ok { - for _, opt := range data.Options { - if opt.Name == name { - v, _ = opt.Value.(T) - - return v, true - } + for _, opt := range data { + if opt.Name == name { + v, _ = opt.Value.(T) + return v, true } } return v, false diff --git a/cmd/discord/discord.go b/cmd/discord/discord.go index cb02b5e3..58e1de5c 100644 --- a/cmd/discord/discord.go +++ b/cmd/discord/discord.go @@ -19,7 +19,7 @@ func NewRouterHandler(coreClient core.Client, token string, publicKey string, co rt.LoadCommands(commands...) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) defer cancel() err = rt.UpdateLoadedCommands(ctx) diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml index aefa15a3..3b53befb 100755 --- a/static/localization/en/discord.yaml +++ b/static/localization/en/discord.yaml @@ -13,36 +13,35 @@ value: session - key: command_session_description value: Get detailed stats for your recent session -# /link -- key: command_link_name - value: link -- key: command_link_description - value: Link your Blitz account to Aftermath -- key: command_link_linked_successfully_fmt - value: "Your account has been linked! Aftermath will now default to **%s** on **%s** when checking stats.\nYou can also verify your account with `/verify`" -# /accounts -- key: command_accounts_name - value: accounts -- key: command_accounts_description - value: View and manage your linked accounts -- key: command_option_accounts_default_name - value: default -- key: command_option_accounts_default_description - value: Set the default account Aftermath will check when using commands -- key: command_option_accounts_linked_name - value: linked -- key: command_option_accounts_linked_description +# /links +- key: command_links_name + value: links +- key: command_links_description + value: Manage your linked Blitz accounts +- key: command_option_links_add_name + value: add +- key: command_option_links_add_desc + value: Link a new Blitz account +- key: command_option_links_remove_name + value: remove +- key: command_option_links_remove_desc + value: Remove your linked Blitz account +- key: command_option_links_fav_name + value: favorite +- key: command_option_links_fav_desc + value: Mark your linked account as default for Aftermath to check +- key: command_option_links_view_name + value: view +- key: command_option_links_view_desc value: View your linked Blitz accounts -- key: command_option_accounts_unlink_name - value: unlink -- key: command_option_accounts_unlink_description - value: Unlink your Blitz account +- key: command_links_linked_successfully_fmt + value: "Your account has been linked! Aftermath will now default to **%s** on **%s** when checking stats.\nYou can also verify your account with `/verify`" -- key: command_accounts_set_default_successfully_fmt +- key: command_links_set_default_successfully_fmt value: "Your default account has been updated! Aftermath will now default to **%s** on **%s** when checking stats." -- key: command_accounts_unlinked_successfully +- key: command_links_unlinked_successfully value: "Your linked account was removed." -- key: command_accounts_verify_cta +- key: command_links_verify_cta value: "You can also verify your account with `/verify`" context: A new default account was set, but it is not verified @@ -193,27 +192,25 @@ value: "You need to provide a link or attach a WoT Blitz replay file." context: Invalid file attached - # /link errors -- key: link_error_missing_input + # /links errors +- key: links_error_missing_input value: "I need both the nickname and server to find your account." context: Nickname or server not provided -- key: link_error_too_many_connections +- key: links_error_too_many_connections value: "It looks like you have reached the limit for the number of Blitz accounts linked.\nTry removing an existing account with `/accounts unlink` before adding a new one." context: User reached their connection limit for Blitz accounts +- key: links_error_connection_not_found + value: "I was not able to find your linked account with this nickname. Try adding it through `/link` instead." + context: Nickname is invalid or failed to find a valid connection +- key: links_error_connection_not_found_selected + value: "I was not able to the account you have selected." + context: Failed to find the selected account by ID # /verify errors - key: verify_error_missing_server value: "Please select a server your account is registered on." context: Server not selected - # /accounts errors -- key: accounts_error_connection_not_found - value: "I was not able to find your linked account with this nickname. Try adding it through `/link` instead." - context: Nickname is invalid or failed to find a valid connection -- key: accounts_error_connection_not_found_selected - value: "I was not able to the account you have selected." - context: Failed to find the selected account by ID - # Wargaming specific errors - key: wargaming_error_private_account diff --git a/tests/static_database.go b/tests/static_database.go index 8c18c841..065c0c7a 100644 --- a/tests/static_database.go +++ b/tests/static_database.go @@ -89,6 +89,9 @@ func (c *staticTestingDatabase) UpdateConnection(ctx context.Context, connection func (c *staticTestingDatabase) UpsertConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { return models.UserConnection{}, errors.New("UpsertConnection not implemented") } +func (c *staticTestingDatabase) DeleteConnection(ctx context.Context, connectionID string) error { + return errors.New("DeleteConnection not implemented") +} func (c *staticTestingDatabase) GetAccountSnapshot(ctx context.Context, accountID, referenceID string, kind models.SnapshotType, options ...database.SnapshotQuery) (models.AccountSnapshot, error) { return models.AccountSnapshot{}, errors.New("GetAccountSnapshot not implemented") From f23502411d30596a67b207da05b38a8e3f26b2bc Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 22:53:51 -0400 Subject: [PATCH 236/341] new string --- cmd/discord/commands/links.go | 4 ++-- static/localization/en/discord.yaml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/discord/commands/links.go b/cmd/discord/commands/links.go index 1ddd7b7c..506d2a3d 100644 --- a/cmd/discord/commands/links.go +++ b/cmd/discord/commands/links.go @@ -247,12 +247,12 @@ func init() { } if len(linkedAccounts) < 1 { - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("links_error_connection_not_found"), Value: "error#links_error_connection_not_found"}) + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("links_error_no_accounts_linked"), Value: "error#links_error_no_accounts_linked"}) } accounts, err := ctx.Core.Database().GetAccounts(ctx.Context, linkedAccounts) if err != nil { - return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("links_error_connection_not_found"), Value: "error#links_error_connection_not_found"}) + return ctx.Reply().Choices(&discordgo.ApplicationCommandOptionChoice{Name: ctx.Localize("links_error_no_accounts_linked"), Value: "error#links_error_no_accounts_linked"}) } var longestName int diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml index 3b53befb..7c69c8df 100755 --- a/static/localization/en/discord.yaml +++ b/static/localization/en/discord.yaml @@ -205,6 +205,8 @@ - key: links_error_connection_not_found_selected value: "I was not able to the account you have selected." context: Failed to find the selected account by ID +- key: links_error_no_accounts_linked + value: "You don't have any linked accounts." # /verify errors - key: verify_error_missing_server From c133573a89250406c47bb3e3ca86fe1b0c420d5d Mon Sep 17 00:00:00 2001 From: Vovko Date: Sun, 30 Jun 2024 22:56:07 -0400 Subject: [PATCH 237/341] typos --- static/localization/en/discord.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml index 7c69c8df..0db84573 100755 --- a/static/localization/en/discord.yaml +++ b/static/localization/en/discord.yaml @@ -138,11 +138,11 @@ context: Mentioned self when checking stats, will not prevent the command from running - key: stats_error_connection_not_found_personal - value: "Looks like we haven't got your Blitz nickname yet. Give the `/link` command a shot." + value: "Looks like you don't have a Blitz account linked yet. Give the `/links` command a shot." context: No Blitz account linked - key: stats_error_nickname_or_server_missing - value: "I need both the name and server to find your account. You can also use the `/link` command to setup a default account." + value: "I need both the name and server to find your account. You can also use the `/links` command to setup a default account." context: User provided a nickname, but no server - key: stats_error_nickname_invalid @@ -200,7 +200,7 @@ value: "It looks like you have reached the limit for the number of Blitz accounts linked.\nTry removing an existing account with `/accounts unlink` before adding a new one." context: User reached their connection limit for Blitz accounts - key: links_error_connection_not_found - value: "I was not able to find your linked account with this nickname. Try adding it through `/link` instead." + value: "I was not able to find a linked account with this nickname. Try adding a new one with `/links add`." context: Nickname is invalid or failed to find a valid connection - key: links_error_connection_not_found_selected value: "I was not able to the account you have selected." From 719768905a78a219de5732a36fec267f0762661e Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 2 Jul 2024 20:43:15 -0400 Subject: [PATCH 238/341] added a basic frontend --- .air.toml | 10 +- .air.web.toml | 13 + .env.example | 4 + Taskfile.yaml | 4 + cmd/core/client.go | 8 +- cmd/discord/commands/interactions.go | 13 +- cmd/discord/commands/session.go | 5 +- cmd/discord/commands/stats.go | 4 +- cmd/frontend/assets/assets.go | 3 + cmd/frontend/assets/gen.go | 56 ++ cmd/frontend/assets/rating.go | 11 + cmd/frontend/assets/wn8.go | 24 + cmd/frontend/components/logo.templ | 7 + cmd/frontend/components/logo_templ.go | 50 ++ cmd/frontend/components/widget/default.templ | 141 +++++ .../components/widget/default_templ.go | 535 ++++++++++++++++++ cmd/frontend/components/widget/widget.templ | 124 ++++ .../components/widget/widget_templ.go | 196 +++++++ cmd/frontend/handler/context.go | 100 ++++ cmd/frontend/handlers.go | 75 +++ cmd/frontend/layouts/main.templ | 58 ++ cmd/frontend/layouts/main_templ.go | 69 +++ cmd/frontend/logic/scripts.go | 75 +++ cmd/frontend/public/favicon.ico | Bin 0 -> 3890 bytes cmd/frontend/public/favicon.png | Bin 0 -> 692 bytes cmd/frontend/public/icon-128.png | Bin 0 -> 2378 bytes cmd/frontend/public/icon-256.png | Bin 0 -> 4905 bytes cmd/frontend/public/icon-32.png | Bin 0 -> 692 bytes cmd/frontend/public/icon-512.png | Bin 0 -> 10321 bytes cmd/frontend/public/icon-64.png | Bin 0 -> 1235 bytes cmd/frontend/public/og-widget.jpg | Bin 0 -> 40334 bytes cmd/frontend/public/og.jpg | Bin 0 -> 40334 bytes cmd/frontend/public/og.png | Bin 0 -> 14493 bytes cmd/frontend/public/promo-invite.jpg | Bin 0 -> 279770 bytes cmd/frontend/public/promo-join.jpg | Bin 0 -> 61040 bytes cmd/frontend/public/rating/bronze.png | Bin 0 -> 52350 bytes cmd/frontend/public/rating/calibration.png | Bin 0 -> 2231 bytes cmd/frontend/public/rating/diamond.png | Bin 0 -> 58867 bytes cmd/frontend/public/rating/gold.png | Bin 0 -> 57541 bytes cmd/frontend/public/rating/platinum.png | Bin 0 -> 68497 bytes cmd/frontend/public/rating/silver.png | Bin 0 -> 56479 bytes cmd/frontend/public/wn8/.gitignore | 1 + cmd/frontend/routes/errors.templ | 33 ++ cmd/frontend/routes/errors_templ.go | 103 ++++ cmd/frontend/routes/index.templ | 37 ++ cmd/frontend/routes/index_templ.go | 51 ++ cmd/frontend/routes/widget/configure.templ | 72 +++ cmd/frontend/routes/widget/configure_templ.go | 93 +++ cmd/frontend/routes/widget/live.templ | 45 ++ cmd/frontend/routes/widget/live_templ.go | 76 +++ dev_web.go | 73 +++ go.mod | 3 + go.sum | 9 + internal/database/models/vehicle_snapshot.go | 5 +- internal/stats/client/v1/client.go | 35 ++ .../stats/{renderer => client}/v1/errors.go | 2 +- .../stats/{renderer => client}/v1/image.go | 2 +- .../stats/{renderer => client}/v1/metadata.go | 2 +- internal/stats/client/v1/options.go | 58 ++ .../stats/{renderer => client}/v1/period.go | 31 +- internal/stats/client/v1/replay.go | 1 + .../stats/{renderer => client}/v1/session.go | 56 +- internal/stats/fetch/v1/client.go | 14 +- internal/stats/fetch/v1/multisource.go | 6 +- .../stats/prepare/session/v1/constants.go | 3 +- internal/stats/render/common/v1/rating.go | 45 ++ internal/stats/render/session/v1/blocks.go | 2 +- internal/stats/render/session/v1/icons.go | 40 -- internal/stats/renderer/v1/renderer.go | 32 -- internal/stats/renderer/v1/replay.go | 1 - main.go | 36 +- render_test.go | 9 +- tests/static_database.go | 7 +- 73 files changed, 2430 insertions(+), 138 deletions(-) create mode 100644 .air.web.toml create mode 100644 cmd/frontend/assets/assets.go create mode 100644 cmd/frontend/assets/gen.go create mode 100644 cmd/frontend/assets/rating.go create mode 100644 cmd/frontend/assets/wn8.go create mode 100644 cmd/frontend/components/logo.templ create mode 100644 cmd/frontend/components/logo_templ.go create mode 100644 cmd/frontend/components/widget/default.templ create mode 100644 cmd/frontend/components/widget/default_templ.go create mode 100644 cmd/frontend/components/widget/widget.templ create mode 100644 cmd/frontend/components/widget/widget_templ.go create mode 100644 cmd/frontend/handler/context.go create mode 100644 cmd/frontend/handlers.go create mode 100644 cmd/frontend/layouts/main.templ create mode 100644 cmd/frontend/layouts/main_templ.go create mode 100644 cmd/frontend/logic/scripts.go create mode 100644 cmd/frontend/public/favicon.ico create mode 100644 cmd/frontend/public/favicon.png create mode 100644 cmd/frontend/public/icon-128.png create mode 100644 cmd/frontend/public/icon-256.png create mode 100644 cmd/frontend/public/icon-32.png create mode 100644 cmd/frontend/public/icon-512.png create mode 100644 cmd/frontend/public/icon-64.png create mode 100644 cmd/frontend/public/og-widget.jpg create mode 100644 cmd/frontend/public/og.jpg create mode 100644 cmd/frontend/public/og.png create mode 100644 cmd/frontend/public/promo-invite.jpg create mode 100644 cmd/frontend/public/promo-join.jpg create mode 100644 cmd/frontend/public/rating/bronze.png create mode 100644 cmd/frontend/public/rating/calibration.png create mode 100644 cmd/frontend/public/rating/diamond.png create mode 100644 cmd/frontend/public/rating/gold.png create mode 100644 cmd/frontend/public/rating/platinum.png create mode 100644 cmd/frontend/public/rating/silver.png create mode 100644 cmd/frontend/public/wn8/.gitignore create mode 100644 cmd/frontend/routes/errors.templ create mode 100644 cmd/frontend/routes/errors_templ.go create mode 100644 cmd/frontend/routes/index.templ create mode 100644 cmd/frontend/routes/index_templ.go create mode 100644 cmd/frontend/routes/widget/configure.templ create mode 100644 cmd/frontend/routes/widget/configure_templ.go create mode 100644 cmd/frontend/routes/widget/live.templ create mode 100644 cmd/frontend/routes/widget/live_templ.go create mode 100644 dev_web.go create mode 100644 internal/stats/client/v1/client.go rename internal/stats/{renderer => client}/v1/errors.go (85%) rename internal/stats/{renderer => client}/v1/image.go (97%) rename internal/stats/{renderer => client}/v1/metadata.go (97%) create mode 100644 internal/stats/client/v1/options.go rename internal/stats/{renderer => client}/v1/period.go (63%) create mode 100644 internal/stats/client/v1/replay.go rename internal/stats/{renderer => client}/v1/session.go (59%) create mode 100644 internal/stats/render/common/v1/rating.go delete mode 100644 internal/stats/render/session/v1/icons.go delete mode 100644 internal/stats/renderer/v1/renderer.go delete mode 100644 internal/stats/renderer/v1/replay.go diff --git a/.air.toml b/.air.toml index 4a197927..9de18a51 100644 --- a/.air.toml +++ b/.air.toml @@ -1,3 +1,11 @@ [build] -# cmd = "go build -gcflags=-m=3 -o ./tmp/main ." +include_ext = ["go", "tmpl", "templ", "html"] +exclude_regex = [".*_templ.go"] +pre_cmd = ["templ generate"] +send_interrupt = true stop_on_error = true + +[proxy] +enabled = true +proxy_port = 8080 +app_port = 9092 diff --git a/.air.web.toml b/.air.web.toml new file mode 100644 index 00000000..181cb47e --- /dev/null +++ b/.air.web.toml @@ -0,0 +1,13 @@ +[build] +include_ext = ["go", "templ", "html"] +exclude_regex = [".*_templ.go"] +pre_cmd = ["templ generate"] +send_interrupt = true +stop_on_error = true +cmd = "go build -o ./tmp/web dev_web.go" +bin = "tmp/web" + +[proxy] +enabled = true +proxy_port = 8081 +app_port = 9092 diff --git a/.env.example b/.env.example index fbe664d0..79768cef 100644 --- a/.env.example +++ b/.env.example @@ -33,6 +33,10 @@ SCHEDULER_CONCURRENT_WORKERS="5" PRIVATE_SERVER_ENABLED="true" # A private server can be used to access some managemenet endpoints PRIVATE_SERVER_PORT="9093" +# Discord Invites +PRIMARY_GUILD_INVITE_LINK="https://discord.gg/8PVq2qvafw" +BOT_INVITE_LINK="https://discord.com/application-directory/1196247108215386142" + # Misc configuration # This is not required for local deployment using compose. When deploying with Dokploy, this is the domain aftermath service will be available on. TRAEFIK_HOST="local.amth.one" diff --git a/Taskfile.yaml b/Taskfile.yaml index ade58543..394154d4 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -33,3 +33,7 @@ tasks: desc: Start a local dev server cmds: - air + dev-web: + desc: Start a local web server for frontend only + cmds: + - air -c .air.web.toml \ No newline at end of file diff --git a/cmd/core/client.go b/cmd/core/client.go index 3653c1d1..242ef1e0 100644 --- a/cmd/core/client.go +++ b/cmd/core/client.go @@ -3,15 +3,15 @@ package core import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/wargaming" + stats "github.com/cufee/aftermath/internal/stats/client/v1" "github.com/cufee/aftermath/internal/stats/fetch/v1" - stats "github.com/cufee/aftermath/internal/stats/renderer/v1" "golang.org/x/text/language" ) var _ Client = &client{} type Client interface { - Render(locale language.Tag) stats.Renderer + Stats(locale language.Tag) stats.Client Wargaming() wargaming.Client Database() database.Client @@ -36,8 +36,8 @@ func (c *client) Fetch() fetch.Client { return c.fetch } -func (c *client) Render(locale language.Tag) stats.Renderer { - return stats.NewRenderer(c.fetch, c.db, c.wargaming, locale) +func (c *client) Stats(locale language.Tag) stats.Client { + return stats.NewClient(c.fetch, c.db, c.wargaming, locale) } func NewClient(fetch fetch.Client, wargaming wargaming.Client, database database.Client) *client { diff --git a/cmd/discord/commands/interactions.go b/cmd/discord/commands/interactions.go index 5d529612..a06720ae 100644 --- a/cmd/discord/commands/interactions.go +++ b/cmd/discord/commands/interactions.go @@ -12,9 +12,8 @@ import ( "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" "github.com/cufee/aftermath/internal/stats/fetch/v1" - render "github.com/cufee/aftermath/internal/stats/render/common/v1" - "github.com/cufee/aftermath/internal/stats/renderer/v1" + stats "github.com/cufee/aftermath/internal/stats/client/v1" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -54,11 +53,11 @@ func init() { return ctx.Reply().Send("stats_refresh_interaction_error_expired") } - var image renderer.Image - var meta renderer.Metadata + var image stats.Image + var meta stats.Metadata switch interaction.Command { case "stats": - img, mt, err := ctx.Core.Render(ctx.Locale).Period(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, render.WithBackground(interaction.Options.BackgroundImageURL)) + img, mt, err := ctx.Core.Stats(ctx.Locale).PeriodImage(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, stats.WithBackgroundURL(interaction.Options.BackgroundImageURL)) if err != nil { return ctx.Err(err) } @@ -66,9 +65,9 @@ func init() { meta = mt case "session": - img, mt, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, render.WithBackground(interaction.Options.BackgroundImageURL)) + img, mt, err := ctx.Core.Stats(ctx.Locale).SessionImage(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, stats.WithBackgroundURL(interaction.Options.BackgroundImageURL)) if err != nil { - if errors.Is(err, fetch.ErrSessionNotFound) || errors.Is(err, renderer.ErrAccountNotTracked) { + if errors.Is(err, fetch.ErrSessionNotFound) || errors.Is(err, stats.ErrAccountNotTracked) { return ctx.Reply().Send("stats_refresh_interaction_error_expired") } return ctx.Err(err) diff --git a/cmd/discord/commands/session.go b/cmd/discord/commands/session.go index 2fe129a9..23a709db 100644 --- a/cmd/discord/commands/session.go +++ b/cmd/discord/commands/session.go @@ -11,9 +11,8 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" + stats "github.com/cufee/aftermath/internal/stats/client/v1" "github.com/cufee/aftermath/internal/stats/fetch/v1" - render "github.com/cufee/aftermath/internal/stats/render/common/v1" - stats "github.com/cufee/aftermath/internal/stats/renderer/v1" "github.com/pkg/errors" "github.com/rs/zerolog/log" ) @@ -70,7 +69,7 @@ func init() { } } - image, meta, err := ctx.Core.Render(ctx.Locale).Session(context.Background(), accountID, options.PeriodStart, render.WithBackground(backgroundURL)) + image, meta, err := ctx.Core.Stats(ctx.Locale).SessionImage(context.Background(), accountID, options.PeriodStart, stats.WithBackgroundURL(backgroundURL)) if err != nil { if errors.Is(err, stats.ErrAccountNotTracked) || (errors.Is(err, fetch.ErrSessionNotFound) && options.Days < 1) { return ctx.Reply().Send("session_error_account_was_not_tracked") diff --git a/cmd/discord/commands/stats.go b/cmd/discord/commands/stats.go index f2a7be15..3483ea2c 100644 --- a/cmd/discord/commands/stats.go +++ b/cmd/discord/commands/stats.go @@ -11,7 +11,7 @@ import ( "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/database/models" "github.com/cufee/aftermath/internal/permissions" - render "github.com/cufee/aftermath/internal/stats/render/common/v1" + stats "github.com/cufee/aftermath/internal/stats/client/v1" "github.com/rs/zerolog/log" ) @@ -61,7 +61,7 @@ func init() { // TODO: Get user background } - image, meta, err := ctx.Core.Render(ctx.Locale).Period(context.Background(), accountID, options.PeriodStart, render.WithBackground(backgroundURL)) + image, meta, err := ctx.Core.Stats(ctx.Locale).PeriodImage(context.Background(), accountID, options.PeriodStart, stats.WithBackgroundURL(backgroundURL)) if err != nil { return ctx.Err(err) } diff --git a/cmd/frontend/assets/assets.go b/cmd/frontend/assets/assets.go new file mode 100644 index 00000000..85f48bc9 --- /dev/null +++ b/cmd/frontend/assets/assets.go @@ -0,0 +1,3 @@ +package assets + +//go:generate go run gen.go diff --git a/cmd/frontend/assets/gen.go b/cmd/frontend/assets/gen.go new file mode 100644 index 00000000..5b233b97 --- /dev/null +++ b/cmd/frontend/assets/gen.go @@ -0,0 +1,56 @@ +//go:build ignore + +package main + +import ( + "image/png" + "os" + "path/filepath" + + "github.com/cufee/aftermath/cmd/frontend/assets" + "github.com/cufee/aftermath/internal/stats/render/common/v1" + "github.com/rs/zerolog/log" +) + +func main() { + generateWN8Icons() +} + +var wn8Tiers = []int{0, 1, 301, 451, 651, 901, 1201, 1601, 2001, 2451, 2901} + +func generateWN8Icons() { + log.Debug().Msg("generating wn8 image assets") + + for _, tier := range wn8Tiers { + color := common.GetWN8Colors(float32(tier)).Background + if tier < 1 { + color = common.TextAlt + } + { + filename := assets.WN8IconFilename(float32(tier)) + img := common.AftermathLogo(color, common.DefaultLogoOptions()) + f, err := os.Create(filepath.Join("../public", "wn8", filename)) + if err != nil { + panic(err) + } + err = png.Encode(f, img) + if err != nil { + panic(err) + } + f.Close() + } + { + filename := "small_" + assets.WN8IconFilename(float32(tier)) + img := common.AftermathLogo(color, common.SmallLogoOptions()) + f, err := os.Create(filepath.Join("../public", "wn8", filename)) + if err != nil { + panic(err) + } + err = png.Encode(f, img) + if err != nil { + panic(err) + } + f.Close() + } + } +} diff --git a/cmd/frontend/assets/rating.go b/cmd/frontend/assets/rating.go new file mode 100644 index 00000000..baeea494 --- /dev/null +++ b/cmd/frontend/assets/rating.go @@ -0,0 +1,11 @@ +package assets + +import ( + "path/filepath" + + "github.com/cufee/aftermath/internal/stats/render/common/v1" +) + +func RatingIconPath(rating float32) string { + return filepath.Join("/assets", "rating", common.GetRatingIconName(rating)+".png") +} diff --git a/cmd/frontend/assets/wn8.go b/cmd/frontend/assets/wn8.go new file mode 100644 index 00000000..9d8d2cc1 --- /dev/null +++ b/cmd/frontend/assets/wn8.go @@ -0,0 +1,24 @@ +package assets + +import ( + "path/filepath" + "strings" + + "github.com/cufee/aftermath/internal/stats/render/common/v1" +) + +func WN8IconFilename(rating float32) string { + name := strings.ReplaceAll(strings.ToLower(common.GetWN8TierName(rating)), " ", "_") + if rating < 1 { + name = "invalid" + } + return name + ".png" +} + +func WN8IconPath(rating float32) string { + return filepath.Join("/assets", "wn8", WN8IconFilename(rating)) +} + +func WN8IconPathSmall(rating float32) string { + return filepath.Join("/assets", "wn8", "small_"+WN8IconFilename(rating)) +} diff --git a/cmd/frontend/components/logo.templ b/cmd/frontend/components/logo.templ new file mode 100644 index 00000000..c4b602e8 --- /dev/null +++ b/cmd/frontend/components/logo.templ @@ -0,0 +1,7 @@ +package components + +import "fmt" + +templ Logo(size string) { +

Aftermath
+} diff --git a/cmd/frontend/components/logo_templ.go b/cmd/frontend/components/logo_templ.go new file mode 100644 index 00000000..fe08297b --- /dev/null +++ b/cmd/frontend/components/logo_templ.go @@ -0,0 +1,50 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.696 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "fmt" + +func Logo(size string) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
\"Aftermath\"
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/components/widget/default.templ b/cmd/frontend/components/widget/default.templ new file mode 100644 index 00000000..07ad5388 --- /dev/null +++ b/cmd/frontend/components/widget/default.templ @@ -0,0 +1,141 @@ +package widget + +import "fmt" +import "github.com/cufee/aftermath/internal/stats/prepare/session/v1" +import "github.com/cufee/aftermath/internal/stats/prepare/common/v1" +import "github.com/cufee/aftermath/cmd/frontend/assets" +import "github.com/cufee/aftermath/internal/stats/frame" + +type defaultWidget struct { + widget + vehicleCards []vehicleExtended +} + +type vehicleExtended struct { + session.VehicleCard + wn8 frame.Value +} + +func (v vehicleExtended) fromCard(card session.VehicleCard) vehicleExtended { + var wn8 frame.Value = frame.InvalidValue + for _, block := range card.Blocks { + if block.Tag == common.TagWN8 { + wn8 = block.Value + break + } + } + + return vehicleExtended{card, wn8} +} + +func (w widget) defaultWidget() templ.Component { + var vehicles []vehicleExtended + for i, card := range w.cards.Unrated.Vehicles { + if i >= w.unratedVehiclesLimit { + break + } + vehicles = append(vehicles, vehicleExtended{}.fromCard(card)) + } + + return defaultWidget{w, vehicles}.Render() +} + +templ (w defaultWidget) Render() { +
+ if w.showRatingOverview { + @w.overviewCard(w.cards.Rating.Overview) + } + if w.showUnratedOverview { + @w.overviewCard(w.cards.Unrated.Overview) + } + for _, card := range w.vehicleCards { + @w.vehicleCard(card) + } + if len(w.vehicleCards) > 0 { + @w.vehicleLegendCard(w.vehicleCards[0].Blocks) + } +
+} + +templ (w defaultWidget) overviewCard(card session.OverviewCard) { +
+ if w.overviewStyle.showTitle { + + { card.Title } + + } +
+ for _, column := range card.Blocks { + @w.overviewColumn(column) + } +
+
+} + +templ (w defaultWidget) vehicleCard(card vehicleExtended) { +
+ if w.vehicleStyle.showTitle { +
+ + { card.Title } + + +
+ } +
+ for _, block := range card.Blocks { + @w.block(block, w.vehicleStyle) + } +
+
+} + +templ (w defaultWidget) vehicleLegendCard(blocks []common.StatsBlock[session.BlockData]) { +
+
+ for _, block := range blocks { +
+ { block.Label } +
+ } +
+
+} + +templ (w defaultWidget) overviewColumn(column session.OverviewColumn) { + if column.Flavor == session.BlockFlavorRating || column.Flavor == session.BlockFlavorWN8 { + @w.specialOverviewColumn(column) + } else { +
+ for _, block := range column.Blocks { + @w.block(block, w.overviewStyle) + } +
+ } +} + +templ (w defaultWidget) specialOverviewColumn(column session.OverviewColumn) { +
+ for _, block := range column.Blocks { + if column.Flavor == session.BlockFlavorRating { + + } + if column.Flavor == session.BlockFlavorWN8 { + + } + @w.block(block, w.overviewStyle) + } +
+} + +templ (w defaultWidget) block(block common.StatsBlock[session.BlockData], style styleOptions) { +
+ { block.Data.Session.String() } + if style.showCareer { + { block.Data.Career.String() } + } + if style.showLabel { + { block.Label } + } +
+} diff --git a/cmd/frontend/components/widget/default_templ.go b/cmd/frontend/components/widget/default_templ.go new file mode 100644 index 00000000..23a95c88 --- /dev/null +++ b/cmd/frontend/components/widget/default_templ.go @@ -0,0 +1,535 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.696 +package widget + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "fmt" +import "github.com/cufee/aftermath/internal/stats/prepare/session/v1" +import "github.com/cufee/aftermath/internal/stats/prepare/common/v1" +import "github.com/cufee/aftermath/cmd/frontend/assets" +import "github.com/cufee/aftermath/internal/stats/frame" + +type defaultWidget struct { + widget + vehicleCards []vehicleExtended +} + +type vehicleExtended struct { + session.VehicleCard + wn8 frame.Value +} + +func (v vehicleExtended) fromCard(card session.VehicleCard) vehicleExtended { + var wn8 frame.Value = frame.InvalidValue + for _, block := range card.Blocks { + if block.Tag == common.TagWN8 { + wn8 = block.Value + break + } + } + + return vehicleExtended{card, wn8} +} + +func (w widget) defaultWidget() templ.Component { + var vehicles []vehicleExtended + for i, card := range w.cards.Unrated.Vehicles { + if i >= w.unratedVehiclesLimit { + break + } + vehicles = append(vehicles, vehicleExtended{}.fromCard(card)) + } + + return defaultWidget{w, vehicles}.Render() +} + +func (w defaultWidget) Render() templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if w.showRatingOverview { + templ_7745c5c3_Err = w.overviewCard(w.cards.Rating.Overview).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if w.showUnratedOverview { + templ_7745c5c3_Err = w.overviewCard(w.cards.Unrated.Overview).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + for _, card := range w.vehicleCards { + templ_7745c5c3_Err = w.vehicleCard(card).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(w.vehicleCards) > 0 { + templ_7745c5c3_Err = w.vehicleLegendCard(w.vehicleCards[0].Blocks).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (w defaultWidget) overviewCard(card session.OverviewCard) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if w.overviewStyle.showTitle { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(card.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 64, Col: 16} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + var templ_7745c5c3_Var4 = []any{fmt.Sprintf("columns overview-columns grid grid-cols-%d gap-1 items-center bg-black rounded-xl bg-opacity-95 p-4", len(card.Blocks))} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, column := range card.Blocks { + templ_7745c5c3_Err = w.overviewColumn(column).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (w defaultWidget) vehicleCard(card vehicleExtended) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if w.vehicleStyle.showTitle { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(card.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 80, Col: 17} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + var templ_7745c5c3_Var9 = []any{fmt.Sprintf("blocks vehicle-blocks grid grid-cols-%d gap-1 items-center", len(card.Blocks))} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, block := range card.Blocks { + templ_7745c5c3_Err = w.block(block, w.vehicleStyle).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (w defaultWidget) vehicleLegendCard(blocks []common.StatsBlock[session.BlockData]) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 = []any{fmt.Sprintf("blocks legend-blocks grid grid-cols-%d gap-1 items-center justify-center px-4", len(blocks))} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, block := range blocks { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(block.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 98, Col: 18} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (w defaultWidget) overviewColumn(column session.OverviewColumn) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var15 := templ.GetChildren(ctx) + if templ_7745c5c3_Var15 == nil { + templ_7745c5c3_Var15 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if column.Flavor == session.BlockFlavorRating || column.Flavor == session.BlockFlavorWN8 { + templ_7745c5c3_Err = w.specialOverviewColumn(column).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, block := range column.Blocks { + templ_7745c5c3_Err = w.block(block, w.overviewStyle).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (w defaultWidget) specialOverviewColumn(column session.OverviewColumn) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var16 := templ.GetChildren(ctx) + if templ_7745c5c3_Var16 == nil { + templ_7745c5c3_Var16 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, block := range column.Blocks { + if column.Flavor == session.BlockFlavorRating { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if column.Flavor == session.BlockFlavorWN8 { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = w.block(block, w.overviewStyle).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (w defaultWidget) block(block common.StatsBlock[session.BlockData], style styleOptions) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var19 := templ.GetChildren(ctx) + if templ_7745c5c3_Var19 == nil { + templ_7745c5c3_Var19 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(block.Data.Session.String()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 133, Col: 65} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if style.showCareer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(block.Data.Career.String()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 135, Col: 67} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if style.showLabel { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(block.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 138, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/components/widget/widget.templ b/cmd/frontend/components/widget/widget.templ new file mode 100644 index 00000000..ac781198 --- /dev/null +++ b/cmd/frontend/components/widget/widget.templ @@ -0,0 +1,124 @@ +package widget + +import "github.com/cufee/aftermath/cmd/frontend/logic" +import "fmt" +import "github.com/cufee/aftermath/internal/database/models" +import prepare "github.com/cufee/aftermath/internal/stats/prepare/session/v1" + +type WidgetFlavor string + +const ( + // WidgetFlavorTicker WidgetFlavor = "ticker" + WidgetFlavorDefault WidgetFlavor = "default" +) + +type WidgetOption func(*widget) + +func WithAutoReload() WidgetOption { + return func(w *widget) { w.autoReload = true } +} +func WithVehicleLimit(limit int) WidgetOption { + return func(w *widget) { w.unratedVehiclesLimit = limit } +} +func WithRatingOverview(shown bool) WidgetOption { + return func(w *widget) { w.showRatingOverview = shown } +} +func WithUnratedOverview(shown bool) WidgetOption { + return func(w *widget) { w.showUnratedOverview = shown } +} +func WithFlavor(flavor WidgetFlavor) WidgetOption { + return func(w *widget) { w.flavor = flavor } +} + +func Widget(account models.Account, cards prepare.Cards, options ...WidgetOption) templ.Component { + widget := widget{ + cards: cards, + account: account, + flavor: WidgetFlavorDefault, + autoReload: false, + vehicleStyle: styleOptions{showTitle: true}, + overviewStyle: styleOptions{showLabel: true}, + + showRatingOverview: true, + showUnratedOverview: true, + unratedVehiclesLimit: 3, + } + for _, apply := range options { + apply(&widget) + } + + return widget.Render() +} + +type styleOptions struct { + showTitle bool + showCareer bool + showLabel bool +} + +type widget struct { + cards prepare.Cards + account models.Account + + vehicleStyle styleOptions + overviewStyle styleOptions + + showRatingOverview bool + showUnratedOverview bool + unratedVehiclesLimit int + + flavor WidgetFlavor + autoReload bool +} + +templ (w widget) Render() { + + Aftermath - { w.account.Nickname } + + + + + + + + + + +
+ switch w.flavor { + default: + @w.defaultWidget() + } + if w.autoReload { + @logic.EmbedMinifiedScript(widgetRefresh(w.account.Realm, w.account.ID, w.account.LastBattleTime.Unix()), w.account.Realm, w.account.ID, w.account.LastBattleTime.Unix()) + } +
+} + +script widgetRefresh(realm string, accountID string, lastBattle int64) { + let apiHost = "" + switch (realm.toLowerCase()) { + case 'na': + apiHost = "wotblitz.com" + break; + case 'eu': + apiHost = "wotblitz.eu" + break; + case 'as': + apiHost = "wotblitz.asia" + break; + default: + throw new Error("Unknown realm: " + realm) + } + const refresh = () => { + fetch(`https://api.${apiHost}/wotb/account/info/?application_id=f44aa6f863c9327c63ba26be3db0d07f&account_id=${accountID}&fields=last_battle_time`) + .then(response => response.json()) + .then(data => { + if (data.data[accountID.toString()].last_battle_time > lastBattle) { + location.reload() + } + }) + .catch(error => { console.error(error); setTimeout(()=>location.reload(), 10000) }) + } + setInterval(refresh, 5000) +} diff --git a/cmd/frontend/components/widget/widget_templ.go b/cmd/frontend/components/widget/widget_templ.go new file mode 100644 index 00000000..33366abe --- /dev/null +++ b/cmd/frontend/components/widget/widget_templ.go @@ -0,0 +1,196 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.696 +package widget + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "github.com/cufee/aftermath/cmd/frontend/logic" +import "fmt" +import "github.com/cufee/aftermath/internal/database/models" +import prepare "github.com/cufee/aftermath/internal/stats/prepare/session/v1" + +type WidgetFlavor string + +const ( + // WidgetFlavorTicker WidgetFlavor = "ticker" + WidgetFlavorDefault WidgetFlavor = "default" +) + +type WidgetOption func(*widget) + +func WithAutoReload() WidgetOption { + return func(w *widget) { w.autoReload = true } +} +func WithVehicleLimit(limit int) WidgetOption { + return func(w *widget) { w.unratedVehiclesLimit = limit } +} +func WithRatingOverview(shown bool) WidgetOption { + return func(w *widget) { w.showRatingOverview = shown } +} +func WithUnratedOverview(shown bool) WidgetOption { + return func(w *widget) { w.showUnratedOverview = shown } +} +func WithFlavor(flavor WidgetFlavor) WidgetOption { + return func(w *widget) { w.flavor = flavor } +} + +func Widget(account models.Account, cards prepare.Cards, options ...WidgetOption) templ.Component { + widget := widget{ + cards: cards, + account: account, + flavor: WidgetFlavorDefault, + autoReload: false, + vehicleStyle: styleOptions{showTitle: true}, + overviewStyle: styleOptions{showLabel: true}, + + showRatingOverview: true, + showUnratedOverview: true, + unratedVehiclesLimit: 3, + } + for _, apply := range options { + apply(&widget) + } + + return widget.Render() +} + +type styleOptions struct { + showTitle bool + showCareer bool + showLabel bool +} + +type widget struct { + cards prepare.Cards + account models.Account + + vehicleStyle styleOptions + overviewStyle styleOptions + + showRatingOverview bool + showUnratedOverview bool + unratedVehiclesLimit int + + flavor WidgetFlavor + autoReload bool +} + +func (w widget) Render() templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Aftermath - ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(w.account.Nickname) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/widget.templ`, Line: 76, Col: 41} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + switch w.flavor { + default: + templ_7745c5c3_Err = w.defaultWidget().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if w.autoReload { + templ_7745c5c3_Err = logic.EmbedMinifiedScript(widgetRefresh(w.account.Realm, w.account.ID, w.account.LastBattleTime.Unix()), w.account.Realm, w.account.ID, w.account.LastBattleTime.Unix()).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func widgetRefresh(realm string, accountID string, lastBattle int64) templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_widgetRefresh_731f`, + Function: `function __templ_widgetRefresh_731f(realm, accountID, lastBattle){let apiHost = "" + switch (realm.toLowerCase()) { + case 'na': + apiHost = "wotblitz.com" + break; + case 'eu': + apiHost = "wotblitz.eu" + break; + case 'as': + apiHost = "wotblitz.asia" + break; + default: + throw new Error("Unknown realm: " + realm) + } + const refresh = () => { + fetch(` + "`" + `https://api.${apiHost}/wotb/account/info/?application_id=f44aa6f863c9327c63ba26be3db0d07f&account_id=${accountID}&fields=last_battle_time` + "`" + `) + .then(response => response.json()) + .then(data => { + if (data.data[accountID.toString()].last_battle_time > lastBattle) { + location.reload() + } + }) + .catch(error => { console.error(error); setTimeout(()=>location.reload(), 10000) }) + } + setInterval(refresh, 5000) +}`, + Call: templ.SafeScript(`__templ_widgetRefresh_731f`, realm, accountID, lastBattle), + CallInline: templ.SafeScriptInline(`__templ_widgetRefresh_731f`, realm, accountID, lastBattle), + } +} diff --git a/cmd/frontend/handler/context.go b/cmd/frontend/handler/context.go new file mode 100644 index 00000000..c6b39e94 --- /dev/null +++ b/cmd/frontend/handler/context.go @@ -0,0 +1,100 @@ +package handler + +import ( + "context" + "net/http" + "net/url" + "strings" + + "github.com/a-h/templ" + "github.com/cufee/aftermath/cmd/core" + "github.com/rs/zerolog/log" +) + +type Renderable interface { + Render(ctx *Context) error +} + +type Context struct { + core.Client + context.Context + + w http.ResponseWriter + r *http.Request +} + +func (ctx *Context) Req() *http.Request { + return ctx.r +} + +func (ctx *Context) Error(err error, context ...string) error { + + query := make(url.Values) + if err != nil { + query.Set("message", err.Error()) + } + if len(context) > 1 { + log.Err(err).Msg("error while rendering") // end user does not get any context, so we log the error instead + query.Set("message", strings.Join(context, ", ")) + } + + http.Redirect(ctx.w, ctx.r, "/error?"+query.Encode(), http.StatusTemporaryRedirect) + return nil // this would never cause an error +} + +func (ctx *Context) SetStatus(code int) { + ctx.w.WriteHeader(code) +} + +func newContext(core core.Client, w http.ResponseWriter, r *http.Request) *Context { + return &Context{core, r.Context(), w, r} +} + +type Layout func(ctx *Context, children ...templ.Component) (templ.Component, error) + +type Partial func(ctx *Context) (templ.Component, error) + +func (partial Partial) Render(ctx *Context) error { + content, err := partial(ctx) + if err != nil { + return ctx.Error(err) + } + + err = content.Render(ctx.Context, ctx.w) + if err != nil { + return ctx.Error(err) + } + + return nil +} + +type Page func(ctx *Context) (Layout, templ.Component, error) + +func (page Page) Render(ctx *Context) error { + layout, body, err := page(ctx) + if err != nil { + return ctx.Error(err, "failed to render the page") + } + + content, err := layout(ctx, body) + if err != nil { + return ctx.Error(err, "failed to render the layout") + } + + err = content.Render(ctx.Context, ctx.w) + if err != nil { + return ctx.Error(err, "failed to render content") + } + + return nil +} + +func Handler(core core.Client, content Renderable) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + err := content.Render(newContext(core, w, r)) + if err != nil { + // this should never be the case, we return an error to make early returns more convenient + log.Err(err).Msg("handler failed to render, this should never happen") + } + } +} diff --git a/cmd/frontend/handlers.go b/cmd/frontend/handlers.go new file mode 100644 index 00000000..f0f3d89c --- /dev/null +++ b/cmd/frontend/handlers.go @@ -0,0 +1,75 @@ +package frontend + +import ( + "embed" + "io/fs" + "net/http" + "path/filepath" + + "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/core/server" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/routes" + "github.com/cufee/aftermath/cmd/frontend/routes/widget" + "github.com/pkg/errors" +) + +//go:embed public +var publicFS embed.FS + +/* +Returns a slice of all handlers registered by the frontend +*/ +func Handlers(core core.Client) ([]server.Handler, error) { + assetsFS, err := fs.Sub(publicFS, "public") + if err != nil { + return nil, errors.Wrap(err, "failed to get embedded assets") + } + + // https://go.dev/blog/routing-enhancements + return []server.Handler{ + // assets + { + Path: "GET /assets/{$}", + Func: redirect("/"), + }, + { + Path: "GET /assets/{_...}", + Func: http.StripPrefix("/assets/", http.FileServerFS(assetsFS)).ServeHTTP, + }, + // wildcard to catch all invalid requests + { + Path: "GET /{pathname...}", + Func: handler.Handler(core, routes.ErrorNotFound), + }, + // common routes + { + Path: get("/"), + Func: handler.Handler(core, routes.Index), + }, + { + Path: get("/error"), + Func: handler.Handler(core, routes.GenericError), + }, + // widget + { + Path: get("/widget/{accountId}"), + Func: handler.Handler(core, widget.ConfigureWidget), + }, + { + Path: get("/widget/{accountId}/live"), + Func: handler.Handler(core, widget.LiveWidget), + }, + // app routes + // + }, nil +} + +func get(path string) string { + // we add the suffix in order to route past the wildcard handler correctly + return "GET " + filepath.Join(path, "{$}") +} + +func redirect(path string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, path, http.StatusPermanentRedirect) } +} diff --git a/cmd/frontend/layouts/main.templ b/cmd/frontend/layouts/main.templ new file mode 100644 index 00000000..6a10ee55 --- /dev/null +++ b/cmd/frontend/layouts/main.templ @@ -0,0 +1,58 @@ +package layouts + +import ( + "os" + "github.com/cufee/aftermath/cmd/frontend/handler" +) + +var appName = os.Getenv("WEBAPP_NAME") + +var Main handler.Layout = func(ctx *handler.Context, children ...templ.Component) (templ.Component, error) { + return main(children...), nil +} + +templ main(children ...templ.Component) { + + + + + + + + + + + + + { appName } + + +
+ for _, render := range children { + @render + } +
+ + + +} diff --git a/cmd/frontend/layouts/main_templ.go b/cmd/frontend/layouts/main_templ.go new file mode 100644 index 00000000..3b602fb6 --- /dev/null +++ b/cmd/frontend/layouts/main_templ.go @@ -0,0 +1,69 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.696 +package layouts + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import ( + "github.com/cufee/aftermath/cmd/frontend/handler" + "os" +) + +var appName = os.Getenv("WEBAPP_NAME") + +var Main handler.Layout = func(ctx *handler.Context, children ...templ.Component) (templ.Component, error) { + return main(children...), nil +} + +func main(children ...templ.Component) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(appName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/layouts/main.templ`, Line: 39, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, render := range children { + templ_7745c5c3_Err = render.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/logic/scripts.go b/cmd/frontend/logic/scripts.go new file mode 100644 index 00000000..e885f7e4 --- /dev/null +++ b/cmd/frontend/logic/scripts.go @@ -0,0 +1,75 @@ +package logic + +import ( + "bytes" + "context" + "encoding/json" + "io" + + "github.com/a-h/templ" + "github.com/tdewolff/minify/v2" + "github.com/tdewolff/minify/v2/js" +) + +func EmbedScript(script templ.ComponentScript, params ...interface{}) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { + if _, err = io.WriteString(w, `"); err != nil { + return err + } + return nil + }) +} + +var m = minify.New() + +func EmbedMinifiedScript(script templ.ComponentScript, params ...interface{}) templ.Component { + r := bytes.NewReader([]byte(script.Function)) + w := bytes.NewBuffer(nil) + err := js.Minify(m, w, r, nil) + if err == nil { + script.Function = w.String() + } + + return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { + if _, err = io.WriteString(w, `"); err != nil { + return err + } + return nil + }) +} diff --git a/cmd/frontend/public/favicon.ico b/cmd/frontend/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c3e53a365e285c5778cde17f07f767a785254e2b GIT binary patch literal 3890 zcmeH~O=}ZT6oyYyHHg~Sg^CifAWHoKMN~BIvu+l;Z$T96w;OG6=U)&?Rdnah{($1f zuDjB%0t&8#T3lEtJw9)8JMB#Ll1UR>*n~It+`0F>&&>UpnV1#$j-E9ATBU4u)Xc`v zD7uJrT%X2pt4RGkFu(NJ{0n+@%e<6o96&`3Sg*wt`;qxI;x+V>^+i+D=Eo`DqUW1l-JFN| zDhKYH;4wvA*EBDNSYnF3-7m{$+%Ayw1AUSYYoeg*Qn@{^9dEU%uX2*xv%+(2eYQ*m-wXISg9SdzW^;Hg9l}DfW6?|Uc>G;pI;o2kZeWGJ(Qy=2W zAozIBih-Unt_NT}C4Qn<$3oj|uYB!KJm%0(yz80q*EJ>lyXI%9f0aTUQKR%dUUw$@6b8^KS+}dH!elv?b5~E*4XdUAEqFvV6T0Y2T^r(L7gL zCqGyEjywPF{CnnO?8nyohvFR%XifwEPaT7=|1pb-bEYTV%kC=$_eM+A^@g4LWzC_5 fQcK0FrKVX+D`w>lvzb517lyn;&62ycUhU9tq7ivL literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/favicon.png b/cmd/frontend/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..09440087a38dd5fa71c6c56c6fca09dee349acc6 GIT binary patch literal 692 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 AdH?_b literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/icon-32.png b/cmd/frontend/public/icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..09440087a38dd5fa71c6c56c6fca09dee349acc6 GIT binary patch literal 692 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 literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/og-widget.jpg b/cmd/frontend/public/og-widget.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a1c8eb84a7fb5615033062be6668c5ca368a6e90 GIT binary patch literal 40334 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 UAA4yCpbkXup7Gtv+mD|A52cumk^lez literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/promo-invite.jpg b/cmd/frontend/public/promo-invite.jpg new file mode 100644 index 0000000000000000000000000000000000000000..adbfe7d3babdaa0e6eabd6aa467c4d1b4de2dd3d GIT binary patch literal 279770 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
cLpGU{0EqgGi}{GWu|GBO@s0 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
  • '; + const event = new CustomEvent("player-search", { detail: {query: nickname.value, realm} }); + document.dispatchEvent(event); + return + } + nickname.disabled = false; + } else { + nickname.disabled = true; + } + // clear results + results.innerHTML = 'Start typing to search'; +} + +script onNicknameInput() { + const nickname = document.getElementById("player-search-nickname"); + const results = document.getElementById("player-search-results"); + const select = document.getElementById("player-search-realm"); + const realm = select.value; + if (!realm) { + results.innerHTML = 'Start typing to search'; + nickname.disabled = true; + nickname.value = ''; + return; + } + if (!nickname.value) { + results.innerHTML = 'Start typing to search'; + return; + } + if (nickname.value.length < 5) { + results.innerHTML = 'Continue typing to search'; + return; + } + const event = new CustomEvent("player-search", { detail: {query: nickname.value, realm} }); + document.dispatchEvent(event); +} + +script searchEventHandler(appId string) { + const results = document.getElementById("player-search-results"); + const nickname = document.getElementById("player-search-nickname"); + + const getApiUrl = (realm, query) => { + let baseUrl = ""; + switch (realm) { + case "NA": + baseUrl = "https://api.wotblitz.com/wotb"; + break; + case "EU": + baseUrl = "https://api.wotblitz.eu/wotb"; + break; + case "AS": + baseUrl = "https://api.wotblitz.asia/wotb"; + break; + default: + return ""; + } + return baseUrl + "/account/list/" + "?application_id=" + appId + "&limit=5" + "&search=" + query; + } + + let debounceTimeout; + const debounceDelay = 500; + + document.addEventListener( + "player-search", + (evt) => { + clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(() => { + results.innerHTML = '
  • '; + + const { query, realm } = evt.detail; + const url = getApiUrl(realm, query); + if (!url) return; + + fetch(url).then(res => res.json()).then(data => { + if (data.status != "ok") { + results.innerHTML = `${data?.error?.message.toLocaleLowerCase().replaceAll("_", " ") || "Failed to get accounts"}`; + nickname.disabled = false; + return; + } + const elements = [] + for (const account of data.data || []) { + if (!account.account_id || !account.nickname) continue; + elements.push(`
  • ${account.nickname}
  • `); + } + if (elements.length == 0) { + results.innerHTML = 'No players found'; + nickname.disabled = false; + return; + } + results.innerHTML = elements.join(""); + return; + }).catch(e => { + console.log("failed to search for accounts",e); + results.innerHTML = 'No players found'; + nickname.disabled = false; + }); + }, debounceDelay); + }, + false, + ); } script handlePreviewOnHome() { - const ouEl = document.getElementById("widget-settings-ou") - const orEl = document.getElementById("widget-settings-or") - const vlEl = document.getElementById("widget-settings-vl") + const ouEl = document.getElementById("widget-settings-ou"); + const orEl = document.getElementById("widget-settings-or"); + const vlEl = document.getElementById("widget-settings-vl"); - const ou = ouEl.checked ? "1" : "0" - const or = orEl.checked ? "1" : "0" - const vl = vlEl.value - const newQuery = `?or=${or}&ou=${ou}&vl=${vl}` + const ou = ouEl.checked ? "1" : "0"; + const or = orEl.checked ? "1" : "0"; + const vl = vlEl.value; + const newQuery = `?or=${or}&ou=${ou}&vl=${vl}`; if (newQuery != window.location.search) { - ouEl.disabled = true - orEl.disabled = true - vlEl.disabled = true + ouEl.disabled = true; + orEl.disabled = true; + vlEl.disabled = true; fetch("/api/widget/mock"+newQuery).then((r) => r.text()).then((html) => { - document.getElementById("mock-widget").outerHTML = html + document.getElementById("mock-widget").outerHTML = html; const url = window.location.protocol + "//" + window.location.host + window.location.pathname + newQuery; window.history?.pushState({path:url},'',url); }).catch(e => console.log(e)).finally(() => { setTimeout(() => { - ouEl.disabled = false - orEl.disabled = false - vlEl.disabled = false - }, 500) - }) + ouEl.disabled = false; + orEl.disabled = false; + vlEl.disabled = false; + }, 500); + }); } } diff --git a/cmd/frontend/routes/widget/index_templ.go b/cmd/frontend/routes/widget/index_templ.go index c1c65f9c..7427e0b3 100644 --- a/cmd/frontend/routes/widget/index_templ.go +++ b/cmd/frontend/routes/widget/index_templ.go @@ -13,10 +13,14 @@ import ( cwidget "github.com/cufee/aftermath/cmd/frontend/components/widget" "github.com/cufee/aftermath/cmd/frontend/handler" "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/cmd/frontend/logic" "github.com/cufee/aftermath/cmd/frontend/routes/api/widget" + "os" "strconv" ) +var publicWgAppID = os.Getenv("WG_AUTH_APP_ID") + var WidgetHome handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { widget, err := widget.MockWidget(ctx) if err != nil { @@ -59,11 +63,45 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, onRealmSelect()) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, onNicknameInput()) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
      Start typing to search
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -89,7 +127,7 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = components.OBSMockup("/assets/widget-background.jpg").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = components.OBSMockup("/assets/widget-background.jpg").Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -97,39 +135,171 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + templ_7745c5c3_Err = logic.EmbedMinifiedScript(searchEventHandler(publicWgAppID), publicWgAppID).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } return templ_7745c5c3_Err }) } +func onRealmSelect() templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_onRealmSelect_da95`, + Function: `function __templ_onRealmSelect_da95(){const results = document.getElementById("player-search-results"); + const select = document.getElementById("player-search-realm"); + const realm = select.value; + // enable/disable field + const nickname = document.getElementById("player-search-nickname"); + if (realm) { + if (nickname.value.length >= 5) { + // if the input already exists - send a request + results.innerHTML = '
  • '; + const event = new CustomEvent("player-search", { detail: {query: nickname.value, realm} }); + document.dispatchEvent(event); + return + } + nickname.disabled = false; + } else { + nickname.disabled = true; + } + // clear results + results.innerHTML = 'Start typing to search'; +}`, + Call: templ.SafeScript(`__templ_onRealmSelect_da95`), + CallInline: templ.SafeScriptInline(`__templ_onRealmSelect_da95`), + } +} + +func onNicknameInput() templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_onNicknameInput_042c`, + Function: `function __templ_onNicknameInput_042c(){const nickname = document.getElementById("player-search-nickname"); + const results = document.getElementById("player-search-results"); + const select = document.getElementById("player-search-realm"); + const realm = select.value; + if (!realm) { + results.innerHTML = 'Start typing to search'; + nickname.disabled = true; + nickname.value = ''; + return; + } + if (!nickname.value) { + results.innerHTML = 'Start typing to search'; + return; + } + if (nickname.value.length < 5) { + results.innerHTML = 'Continue typing to search'; + return; + } + const event = new CustomEvent("player-search", { detail: {query: nickname.value, realm} }); + document.dispatchEvent(event); +}`, + Call: templ.SafeScript(`__templ_onNicknameInput_042c`), + CallInline: templ.SafeScriptInline(`__templ_onNicknameInput_042c`), + } +} + +func searchEventHandler(appId string) templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_searchEventHandler_f59f`, + Function: `function __templ_searchEventHandler_f59f(appId){const results = document.getElementById("player-search-results"); + const nickname = document.getElementById("player-search-nickname"); + + const getApiUrl = (realm, query) => { + let baseUrl = ""; + switch (realm) { + case "NA": + baseUrl = "https://api.wotblitz.com/wotb"; + break; + case "EU": + baseUrl = "https://api.wotblitz.eu/wotb"; + break; + case "AS": + baseUrl = "https://api.wotblitz.asia/wotb"; + break; + default: + return ""; + } + return baseUrl + "/account/list/" + "?application_id=" + appId + "&limit=5" + "&search=" + query; + } + + let debounceTimeout; + const debounceDelay = 500; + + document.addEventListener( + "player-search", + (evt) => { + clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(() => { + results.innerHTML = '
  • '; + + const { query, realm } = evt.detail; + const url = getApiUrl(realm, query); + if (!url) return; + + fetch(url).then(res => res.json()).then(data => { + if (data.status != "ok") { + results.innerHTML = ` + "`" + `${data?.error?.message.toLocaleLowerCase().replaceAll("_", " ") || "Failed to get accounts"}` + "`" + `; + nickname.disabled = false; + return; + } + const elements = [] + for (const account of data.data || []) { + if (!account.account_id || !account.nickname) continue; + elements.push(` + "`" + `
  • ${account.nickname}
  • ` + "`" + `); + } + if (elements.length == 0) { + results.innerHTML = 'No players found'; + nickname.disabled = false; + return; + } + results.innerHTML = elements.join(""); + return; + }).catch(e => { + console.log("failed to search for accounts",e); + results.innerHTML = 'No players found'; + nickname.disabled = false; + }); + }, debounceDelay); + }, + false, + ); +}`, + Call: templ.SafeScript(`__templ_searchEventHandler_f59f`, appId), + CallInline: templ.SafeScriptInline(`__templ_searchEventHandler_f59f`, appId), + } +} + func handlePreviewOnHome() templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_handlePreviewOnHome_66f0`, - Function: `function __templ_handlePreviewOnHome_66f0(){const ouEl = document.getElementById("widget-settings-ou") - const orEl = document.getElementById("widget-settings-or") - const vlEl = document.getElementById("widget-settings-vl") - - const ou = ouEl.checked ? "1" : "0" - const or = orEl.checked ? "1" : "0" - const vl = vlEl.value - const newQuery = ` + "`" + `?or=${or}&ou=${ou}&vl=${vl}` + "`" + ` + Name: `__templ_handlePreviewOnHome_2c46`, + Function: `function __templ_handlePreviewOnHome_2c46(){const ouEl = document.getElementById("widget-settings-ou"); + const orEl = document.getElementById("widget-settings-or"); + const vlEl = document.getElementById("widget-settings-vl"); + + const ou = ouEl.checked ? "1" : "0"; + const or = orEl.checked ? "1" : "0"; + const vl = vlEl.value; + const newQuery = ` + "`" + `?or=${or}&ou=${ou}&vl=${vl}` + "`" + `; if (newQuery != window.location.search) { - ouEl.disabled = true - orEl.disabled = true - vlEl.disabled = true + ouEl.disabled = true; + orEl.disabled = true; + vlEl.disabled = true; fetch("/api/widget/mock"+newQuery).then((r) => r.text()).then((html) => { - document.getElementById("mock-widget").outerHTML = html + document.getElementById("mock-widget").outerHTML = html; const url = window.location.protocol + "//" + window.location.host + window.location.pathname + newQuery; window.history?.pushState({path:url},'',url); }).catch(e => console.log(e)).finally(() => { setTimeout(() => { - ouEl.disabled = false - orEl.disabled = false - vlEl.disabled = false - }, 500) - }) + ouEl.disabled = false; + orEl.disabled = false; + vlEl.disabled = false; + }, 500); + }); } }`, - Call: templ.SafeScript(`__templ_handlePreviewOnHome_66f0`), - CallInline: templ.SafeScriptInline(`__templ_handlePreviewOnHome_66f0`), + Call: templ.SafeScript(`__templ_handlePreviewOnHome_2c46`), + CallInline: templ.SafeScriptInline(`__templ_handlePreviewOnHome_2c46`), } } From d06974c46e675886eff29b5d49569e8b902e212b Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 5 Jul 2024 13:55:18 -0400 Subject: [PATCH 264/341] added loading state on navigation --- cmd/frontend/routes/widget/index.templ | 9 +++++++-- cmd/frontend/routes/widget/index_templ.go | 17 +++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd/frontend/routes/widget/index.templ b/cmd/frontend/routes/widget/index.templ index ffec0d0d..9f9be33d 100644 --- a/cmd/frontend/routes/widget/index.templ +++ b/cmd/frontend/routes/widget/index.templ @@ -118,6 +118,11 @@ script searchEventHandler(appId string) { const results = document.getElementById("player-search-results"); const nickname = document.getElementById("player-search-nickname"); + const setResultsLoading = () => { + results.innerHTML = '
  • '; + } + window.setResultsLoading = setResultsLoading + const getApiUrl = (realm, query) => { let baseUrl = ""; switch (realm) { @@ -144,7 +149,7 @@ script searchEventHandler(appId string) { (evt) => { clearTimeout(debounceTimeout); debounceTimeout = setTimeout(() => { - results.innerHTML = '
  • '; + setResultsLoading() const { query, realm } = evt.detail; const url = getApiUrl(realm, query); @@ -159,7 +164,7 @@ script searchEventHandler(appId string) { const elements = [] for (const account of data.data || []) { if (!account.account_id || !account.nickname) continue; - elements.push(`
  • ${account.nickname}
  • `); + elements.push(`
  • ${account.nickname}
  • `); } if (elements.length == 0) { results.innerHTML = 'No players found'; diff --git a/cmd/frontend/routes/widget/index_templ.go b/cmd/frontend/routes/widget/index_templ.go index 7427e0b3..64e48378 100644 --- a/cmd/frontend/routes/widget/index_templ.go +++ b/cmd/frontend/routes/widget/index_templ.go @@ -202,10 +202,15 @@ func onNicknameInput() templ.ComponentScript { func searchEventHandler(appId string) templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_searchEventHandler_f59f`, - Function: `function __templ_searchEventHandler_f59f(appId){const results = document.getElementById("player-search-results"); + Name: `__templ_searchEventHandler_c17b`, + Function: `function __templ_searchEventHandler_c17b(appId){const results = document.getElementById("player-search-results"); const nickname = document.getElementById("player-search-nickname"); + const setResultsLoading = () => { + results.innerHTML = '
  • '; + } + window.setResultsLoading = setResultsLoading + const getApiUrl = (realm, query) => { let baseUrl = ""; switch (realm) { @@ -232,7 +237,7 @@ func searchEventHandler(appId string) templ.ComponentScript { (evt) => { clearTimeout(debounceTimeout); debounceTimeout = setTimeout(() => { - results.innerHTML = '
  • '; + setResultsLoading() const { query, realm } = evt.detail; const url = getApiUrl(realm, query); @@ -247,7 +252,7 @@ func searchEventHandler(appId string) templ.ComponentScript { const elements = [] for (const account of data.data || []) { if (!account.account_id || !account.nickname) continue; - elements.push(` + "`" + `
  • ${account.nickname}
  • ` + "`" + `); + elements.push(` + "`" + `
  • ${account.nickname}
  • ` + "`" + `); } if (elements.length == 0) { results.innerHTML = 'No players found'; @@ -266,8 +271,8 @@ func searchEventHandler(appId string) templ.ComponentScript { false, ); }`, - Call: templ.SafeScript(`__templ_searchEventHandler_f59f`, appId), - CallInline: templ.SafeScriptInline(`__templ_searchEventHandler_f59f`, appId), + Call: templ.SafeScript(`__templ_searchEventHandler_c17b`, appId), + CallInline: templ.SafeScriptInline(`__templ_searchEventHandler_c17b`, appId), } } From b752cab3b9f7929f6c3de49cc749e144c0c53665 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 5 Jul 2024 13:56:36 -0400 Subject: [PATCH 265/341] removed extra generate --- main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main.go b/main.go index db951222..4d887007 100644 --- a/main.go +++ b/main.go @@ -41,7 +41,6 @@ import ( "net/http/pprof" ) -//go:generate go generate ./internal/database/ent //go:generate go generate ./cmd/frontend/assets //go:embed static/* From 64dba914c91f0367119f9619ddf923877194e6c5 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 5 Jul 2024 13:59:39 -0400 Subject: [PATCH 266/341] always generating cards --- internal/stats/prepare/session/v1/card.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/stats/prepare/session/v1/card.go b/internal/stats/prepare/session/v1/card.go index d9a1a6bb..e7160723 100644 --- a/internal/stats/prepare/session/v1/card.go +++ b/internal/stats/prepare/session/v1/card.go @@ -22,8 +22,7 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] var cards Cards - // Rating battles overview - if session.RatingBattles.Battles > 0 { + { // Rating battles overview card, err := makeOverviewCard( ratingOverviewBlocks, session.RatingBattles.StatsFrame, @@ -42,8 +41,7 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] } cards.Rating.Overview = card } - // Regular battles overview - if session.RegularBattles.Battles > 0 || session.RatingBattles.Battles == 0 { + { // Regular battles overview card, err := makeOverviewCard( unratedOverviewBlocks, session.RegularBattles.StatsFrame, From 0ab7b4880a2b4d9050170847d0b86d211095bcd2 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 5 Jul 2024 14:00:40 -0400 Subject: [PATCH 267/341] changed highlights order --- internal/stats/prepare/session/v1/card.go | 2 +- internal/stats/prepare/session/v1/constants.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/stats/prepare/session/v1/card.go b/internal/stats/prepare/session/v1/card.go index e7160723..55b8db9a 100644 --- a/internal/stats/prepare/session/v1/card.go +++ b/internal/stats/prepare/session/v1/card.go @@ -122,11 +122,11 @@ func NewCards(session, career fetch.AccountStatsOverPeriod, glossary map[string] } // Vehicle Highlights - var minimumBattles float64 = 5 periodDays := session.PeriodEnd.Sub(session.PeriodStart).Hours() / 24 withFallback := func(battles float64) float64 { return math.Min(battles, float64(session.RegularBattles.Battles.Float())/float64(len(highlights))) } + var minimumBattles float64 = withFallback(5) if periodDays > 90 { minimumBattles = withFallback(100) } else if periodDays > 60 { diff --git a/internal/stats/prepare/session/v1/constants.go b/internal/stats/prepare/session/v1/constants.go index c64e8a86..f4f1a925 100644 --- a/internal/stats/prepare/session/v1/constants.go +++ b/internal/stats/prepare/session/v1/constants.go @@ -23,7 +23,7 @@ var ratingOverviewBlocks = []overviewColumnBlocks{ } var vehicleBlocks = []common.Tag{common.TagBattles, common.TagWinrate, common.TagAvgDamage, common.TagWN8} -var highlights = []common.Highlight{common.HighlightBattles, common.HighlightWN8, common.HighlightAvgDamage} +var highlights = []common.Highlight{common.HighlightWN8, common.HighlightAvgDamage, common.HighlightBattles} type Cards struct { Unrated UnratedCards `json:"unrated"` From 938b9fbc265ce5335b01c5a5bcce35d8595a3631 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 5 Jul 2024 14:05:44 -0400 Subject: [PATCH 268/341] added progress bar on requests --- cmd/frontend/components/navbar.templ | 67 ++++++++++++++++ cmd/frontend/components/navbar_templ.go | 101 +++++++++++++++++++++++- 2 files changed, 167 insertions(+), 1 deletion(-) diff --git a/cmd/frontend/components/navbar.templ b/cmd/frontend/components/navbar.templ index c57557de..30c0c8d8 100644 --- a/cmd/frontend/components/navbar.templ +++ b/cmd/frontend/components/navbar.templ @@ -28,6 +28,7 @@ var Navbar handler.Partial = func(ctx *handler.Context) (templ.Component, error) } templ navbar(props navbarProps) { + @navProgressAnimated()
    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Login
      • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 = []any{"btn btn-neutral" + logic.StringIfElse(" active", "", props.path == "/widget")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Widget
      • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 = []any{"btn btn-primary" + logic.StringIfElse(" active", "", props.path == "/login")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Login
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -140,13 +184,13 @@ func navMenuLink(label, href, currentPath string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var6 := templ.GetChildren(ctx) - if templ_7745c5c3_Var6 == nil { - templ_7745c5c3_Var6 = templ.NopComponent + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var7 = []any{navLinkClass(href, currentPath)} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...) + var templ_7745c5c3_Var11 = []any{navLinkClass(href, currentPath)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -154,8 +198,8 @@ func navMenuLink(label, href, currentPath string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var8 templ.SafeURL = templ.URL(href) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8))) + var templ_7745c5c3_Var12 templ.SafeURL = templ.URL(href) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -163,12 +207,12 @@ func navMenuLink(label, href, currentPath string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String()) + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String()) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 1, Col: 0} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -176,12 +220,12 @@ func navMenuLink(label, href, currentPath string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(label) + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 72, Col: 78} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 84, Col: 78} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -219,9 +263,9 @@ func navProgressAnimated(...templ.Component) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var11 := templ.GetChildren(ctx) - if templ_7745c5c3_Var11 == nil { - templ_7745c5c3_Var11 = templ.NopComponent + templ_7745c5c3_Var15 := templ.GetChildren(ctx) + if templ_7745c5c3_Var15 == nil { + templ_7745c5c3_Var15 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") diff --git a/cmd/frontend/routes/index.templ b/cmd/frontend/routes/index.templ index 652a8782..514d507e 100644 --- a/cmd/frontend/routes/index.templ +++ b/cmd/frontend/routes/index.templ @@ -12,12 +12,12 @@ var Index handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Compo templ index() {
    + @card(cardImage("/assets/promo-invite.jpg"), cardActionButton("Add Aftermath on Discord", "/invite")) { +

    Add Aftermath to your favorite server for an easier time tracking sessions!

    + } @card(cardImage("/assets/promo-join.jpg"), cardActionButton("Join Aftermath Official", "/join")) {

    Aftermath is making a comeback! We've revamped everything to be faster and more beautiful. Join our community server for the latest features and updates.

    } - @card(cardImage("/assets/promo-invite.jpg"), cardActionButton("Add Aftermath on Discord", "/invite")) { -

    The new bot is here! Add it to your server for an easier time tracking sessions. It's that simple – give Aftermath a try on your favorite server!

    - }
    } diff --git a/cmd/frontend/routes/index_templ.go b/cmd/frontend/routes/index_templ.go index 83153c9e..c671d99a 100644 --- a/cmd/frontend/routes/index_templ.go +++ b/cmd/frontend/routes/index_templ.go @@ -51,13 +51,13 @@ func index() templ.Component { }() } ctx = templ.InitializeContext(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Aftermath is making a comeback! We've revamped everything to be faster and more beautiful. Join our community server for the latest features and updates.

    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Add Aftermath to your favorite server for an easier time tracking sessions!

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = card(cardImage("/assets/promo-join.jpg"), cardActionButton("Join Aftermath Official", "/join")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = card(cardImage("/assets/promo-invite.jpg"), cardActionButton("Add Aftermath on Discord", "/invite")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -73,13 +73,13 @@ func index() templ.Component { }() } ctx = templ.InitializeContext(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    The new bot is here! Add it to your server for an easier time tracking sessions. It's that simple – give Aftermath a try on your favorite server!

    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Aftermath is making a comeback! We've revamped everything to be faster and more beautiful. Join our community server for the latest features and updates.

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = card(cardImage("/assets/promo-invite.jpg"), cardActionButton("Add Aftermath on Discord", "/invite")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = card(cardImage("/assets/promo-join.jpg"), cardActionButton("Join Aftermath Official", "/join")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/cmd/frontend/routes/widget/index.templ b/cmd/frontend/routes/widget/index.templ index 4d5bcb63..8a4984d1 100644 --- a/cmd/frontend/routes/widget/index.templ +++ b/cmd/frontend/routes/widget/index.templ @@ -42,7 +42,7 @@ templ widgetHome(widget templ.Component, or, ou bool, vl int) {
    @cwidget.Settings(handlePreviewOnHome(), or, ou, vl)
    - diff --git a/cmd/frontend/routes/widget/index_templ.go b/cmd/frontend/routes/widget/index_templ.go index a544c53d..1b7b4a8b 100644 --- a/cmd/frontend/routes/widget/index_templ.go +++ b/cmd/frontend/routes/widget/index_templ.go @@ -71,7 +71,7 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    • ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
      • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var6 = []any{"btn btn-neutral" + logic.StringIfElse(" active", "", props.path == "/widget")} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...) + var templ_7745c5c3_Var8 = []any{"btn btn-neutral" + logic.StringIfElse(" active", "", props.path == "/widget")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -132,38 +168,75 @@ func navbar(props navbarProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String()) + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String()) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 1, Col: 0} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Widget
      • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var8 = []any{"btn btn-primary" + logic.StringIfElse(" active", "", props.path == "/login")} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Widget
      • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String()) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 1, Col: 0} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + if !props.authenticated { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
      • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 = []any{"btn btn-primary" + logic.StringIfElse(" active", "", props.path == "/login")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Login
      • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
      • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 = []any{logic.StringIfElse("btn active", "btn", props.path == "/app")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Profile
      • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Login
    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -184,13 +257,13 @@ func navMenuLink(label, href, currentPath string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var10 := templ.GetChildren(ctx) - if templ_7745c5c3_Var10 == nil { - templ_7745c5c3_Var10 = templ.NopComponent + templ_7745c5c3_Var14 := templ.GetChildren(ctx) + if templ_7745c5c3_Var14 == nil { + templ_7745c5c3_Var14 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var11 = []any{navLinkClass(href, currentPath)} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...) + var templ_7745c5c3_Var15 = []any{navLinkClass(href, currentPath)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -198,8 +271,8 @@ func navMenuLink(label, href, currentPath string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 templ.SafeURL = templ.URL(href) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12))) + var templ_7745c5c3_Var16 templ.SafeURL = templ.URL(href) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var16))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -207,12 +280,12 @@ func navMenuLink(label, href, currentPath string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String()) + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var15).String()) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 1, Col: 0} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -220,12 +293,12 @@ func navMenuLink(label, href, currentPath string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(label) + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 84, Col: 78} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 91, Col: 78} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -263,9 +336,9 @@ func navProgressAnimated(...templ.Component) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var15 := templ.GetChildren(ctx) - if templ_7745c5c3_Var15 == nil { - templ_7745c5c3_Var15 = templ.NopComponent + templ_7745c5c3_Var19 := templ.GetChildren(ctx) + if templ_7745c5c3_Var19 == nil { + templ_7745c5c3_Var19 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") From ca9cdf11e212a7a51dfdcb21c14e0a4cef943929 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 5 Jul 2024 16:08:44 -0400 Subject: [PATCH 277/341] better path checks --- cmd/frontend/components/navbar.templ | 4 ++-- cmd/frontend/components/navbar_templ.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/frontend/components/navbar.templ b/cmd/frontend/components/navbar.templ index 26dadaeb..f437f5f2 100644 --- a/cmd/frontend/components/navbar.templ +++ b/cmd/frontend/components/navbar.templ @@ -36,14 +36,14 @@ templ navbar(props navbarProps) {
  • T$ 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%0AOuOhP=Y{DM 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 IX*y^B+0mmaxBvhE literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/promo-join.jpg b/cmd/frontend/public/promo-join.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2d1dd84326bd450776fd66ef3445bb2691f0ab94 GIT binary patch literal 61040 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 LIdbT({X71DViG1( literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/rating/bronze.png b/cmd/frontend/public/rating/bronze.png new file mode 100644 index 0000000000000000000000000000000000000000..9c9759bb506c9e79f0dcc30f336429b5ddd50e44 GIT binary patch literal 52350 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

    &obmDG_bGH>=|$;;10es~G`*1%N>?ns|FgB}b? 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 literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/rating/gold.png b/cmd/frontend/public/rating/gold.png new file mode 100644 index 0000000000000000000000000000000000000000..deb5feb38ed4582fd50b3b4e101dff6669852343 GIT binary patch literal 57541 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 z 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 uo#Hg4Hn^Cy%#pu>u?SR~CP!A+T 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

    ^8;KG^;J2K?c`fe9>GmEmE*;0ax_u< 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 literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/rating/silver.png b/cmd/frontend/public/rating/silver.png new file mode 100644 index 0000000000000000000000000000000000000000..4643af0b30bbf7b56ffd4701bdd95b018830b08e GIT binary patch literal 56479 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 literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/wn8/.gitignore b/cmd/frontend/public/wn8/.gitignore new file mode 100644 index 00000000..3f644593 --- /dev/null +++ b/cmd/frontend/public/wn8/.gitignore @@ -0,0 +1 @@ +**.* \ No newline at end of file diff --git a/cmd/frontend/routes/errors.templ b/cmd/frontend/routes/errors.templ new file mode 100644 index 00000000..cbd34c87 --- /dev/null +++ b/cmd/frontend/routes/errors.templ @@ -0,0 +1,33 @@ +package routes + +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/cufee/aftermath/cmd/frontend/layouts" +import "net/http" + +var GenericError handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + message := ctx.Req().URL.Query().Get("message") + + ctx.SetStatus(http.StatusInternalServerError) + return layouts.Main, errorPage(message), nil +} + +templ errorPage(message string) { +

    +} + +var ErrorNotFound handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + path := ctx.Req().PathValue("pathname") + + ctx.SetStatus(http.StatusNotFound) + return layouts.Main, notFoundPage(path), nil +} + +templ notFoundPage(path string) { +
    + 404 - Not Found + /{ path } +
    +} diff --git a/cmd/frontend/routes/errors_templ.go b/cmd/frontend/routes/errors_templ.go new file mode 100644 index 00000000..bb761d1a --- /dev/null +++ b/cmd/frontend/routes/errors_templ.go @@ -0,0 +1,103 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.696 +package routes + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/cufee/aftermath/cmd/frontend/layouts" +import "net/http" + +var GenericError handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + message := ctx.Req().URL.Query().Get("message") + + ctx.SetStatus(http.StatusInternalServerError) + return layouts.Main, errorPage(message), nil +} + +func errorPage(message string) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Something did not work ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(message) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 17, Col: 17} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +var ErrorNotFound handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + path := ctx.Req().PathValue("pathname") + + ctx.SetStatus(http.StatusNotFound) + return layouts.Main, notFoundPage(path), nil +} + +func notFoundPage(path string) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    404 - Not Found /") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(path) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 31, Col: 15} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/routes/index.templ b/cmd/frontend/routes/index.templ new file mode 100644 index 00000000..581aaa5b --- /dev/null +++ b/cmd/frontend/routes/index.templ @@ -0,0 +1,37 @@ +package routes + +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/cufee/aftermath/cmd/frontend/layouts" +import "github.com/cufee/aftermath/cmd/frontend/components" + +var Index handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + return layouts.Main, index(), nil +} + +templ index() { +
    +
    + @components.Logo("128") +
    +
    + +
    +
    Invite Aftermath
    +
    +

    The new bot is here! Add it to your server for an easier time tracking sessions. It's that simple – give Aftermath a try on your favorite server!

    + +
    +
    +
    +
    +} diff --git a/cmd/frontend/routes/index_templ.go b/cmd/frontend/routes/index_templ.go new file mode 100644 index 00000000..279553da --- /dev/null +++ b/cmd/frontend/routes/index_templ.go @@ -0,0 +1,51 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.696 +package routes + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/cufee/aftermath/cmd/frontend/layouts" +import "github.com/cufee/aftermath/cmd/frontend/components" + +var Index handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + return layouts.Main, index(), nil +} + +func index() templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.Logo("128").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    \"Join

    Aftermath is making a comeback! We've revamped everything to be faster and more beautiful. Join our community server for the latest features and updates.

    \"Invite

    The new bot is here! Add it to your server for an easier time tracking sessions. It's that simple – give Aftermath a try on your favorite server!

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/routes/widget/configure.templ b/cmd/frontend/routes/widget/configure.templ new file mode 100644 index 00000000..0019d498 --- /dev/null +++ b/cmd/frontend/routes/widget/configure.templ @@ -0,0 +1,72 @@ +package widget + +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/cufee/aftermath/cmd/frontend/layouts" +import "github.com/cufee/aftermath/cmd/frontend/components/widget" +import "golang.org/x/text/language" +import "time" +import "github.com/pkg/errors" +import "fmt" + +var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + accountID := ctx.Req().PathValue("accountId") + if accountID == "" { + return nil, nil, errors.New("invalid account id") + } + + account, err := ctx.Fetch().Account(ctx.Context, accountID) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now()) + if err != nil { + return nil, nil, err + } + + return layouts.Main, configureWidgetPage(widget.Widget(account, cards)), nil +} + +templ configureWidgetPage(widget templ.Component) { +
    +
    +
    +
    +
    + Regular Battles + + +
    +
    + Rating Battles + +
    +
    + +
    +
    +
    +
    + @widget +
    +
    +
    +} diff --git a/cmd/frontend/routes/widget/configure_templ.go b/cmd/frontend/routes/widget/configure_templ.go new file mode 100644 index 00000000..15b2e371 --- /dev/null +++ b/cmd/frontend/routes/widget/configure_templ.go @@ -0,0 +1,93 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.696 +package widget + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/cufee/aftermath/cmd/frontend/layouts" +import "github.com/cufee/aftermath/cmd/frontend/components/widget" +import "golang.org/x/text/language" +import "time" +import "github.com/pkg/errors" +import "fmt" + +var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + accountID := ctx.Req().PathValue("accountId") + if accountID == "" { + return nil, nil, errors.New("invalid account id") + } + + account, err := ctx.Fetch().Account(ctx.Context, accountID) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now()) + if err != nil { + return nil, nil, err + } + + return layouts.Main, configureWidgetPage(widget.Widget(account, cards)), nil +} + +func configureWidgetPage(widget templ.Component) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Regular Battles
    Rating Battles
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = widget.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/routes/widget/live.templ b/cmd/frontend/routes/widget/live.templ new file mode 100644 index 00000000..34e4f295 --- /dev/null +++ b/cmd/frontend/routes/widget/live.templ @@ -0,0 +1,45 @@ +package widget + +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/pkg/errors" +import "golang.org/x/text/language" +import "time" +import "github.com/cufee/aftermath/cmd/frontend/layouts" +import "github.com/cufee/aftermath/cmd/frontend/components/widget" +import "github.com/cufee/aftermath/internal/database/models" +import "github.com/cufee/aftermath/internal/stats/client/v1" +import "slices" + +var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + accountID := ctx.Req().PathValue("accountId") + if accountID == "" { + return nil, nil, errors.New("invalid account id") + } + + account, err := ctx.Fetch().Account(ctx.Context, accountID) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + + var opts []client.RequestOption + if ref := ctx.Req().URL.Query().Get("ref"); ref != "" { + opts = append(opts, client.WithReferenceID(ref)) + } + if t := ctx.Req().URL.Query().Get("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { + opts = append(opts, client.WithType(models.SnapshotType(t))) + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), opts...) + if err != nil { + return nil, nil, err + } + + return layouts.Main, liveWidget(widget.Widget(account, cards, widget.WithAutoReload())), nil +} + +templ liveWidget(widget templ.Component) { + @widget + +} diff --git a/cmd/frontend/routes/widget/live_templ.go b/cmd/frontend/routes/widget/live_templ.go new file mode 100644 index 00000000..1b66362b --- /dev/null +++ b/cmd/frontend/routes/widget/live_templ.go @@ -0,0 +1,76 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.696 +package widget + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/pkg/errors" +import "golang.org/x/text/language" +import "time" +import "github.com/cufee/aftermath/cmd/frontend/layouts" +import "github.com/cufee/aftermath/cmd/frontend/components/widget" +import "github.com/cufee/aftermath/internal/database/models" +import "github.com/cufee/aftermath/internal/stats/client/v1" +import "slices" + +var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + accountID := ctx.Req().PathValue("accountId") + if accountID == "" { + return nil, nil, errors.New("invalid account id") + } + + account, err := ctx.Fetch().Account(ctx.Context, accountID) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + + var opts []client.RequestOption + if ref := ctx.Req().URL.Query().Get("ref"); ref != "" { + opts = append(opts, client.WithReferenceID(ref)) + } + if t := ctx.Req().URL.Query().Get("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { + opts = append(opts, client.WithType(models.SnapshotType(t))) + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), opts...) + if err != nil { + return nil, nil, err + } + + return layouts.Main, liveWidget(widget.Widget(account, cards, widget.WithAutoReload())), nil +} + +func liveWidget(widget templ.Component) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = widget.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/dev_web.go b/dev_web.go new file mode 100644 index 00000000..c358a669 --- /dev/null +++ b/dev_web.go @@ -0,0 +1,73 @@ +//go:build ignore + +package main + +import ( + "embed" + "net/http" + "os" + + "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/core/server" + "github.com/cufee/aftermath/cmd/frontend" + "github.com/cufee/aftermath/internal/localization" + "github.com/cufee/aftermath/internal/stats/render/assets" + render "github.com/cufee/aftermath/internal/stats/render/common/v1" + "github.com/cufee/aftermath/tests" + "github.com/joho/godotenv" + "github.com/rs/zerolog/log" +) + +//go:generate go generate ./cmd/frontend/assets + +//go:embed static/* +var static embed.FS + +func main() { + err := godotenv.Load(".env") + if err != nil { + panic(err) + } + + // Assets for rendering + err = assets.LoadAssets(static, "static") + if err != nil { + log.Fatal().Msgf("assets#LoadAssets failed to load assets from static/ embed.FS %s", err) + } + err = render.InitLoadedAssets() + if err != nil { + log.Fatal().Msgf("render#InitLoadedAssets failed %s", err) + } + err = localization.LoadAssets(static, "static/localization") + if err != nil { + log.Fatal().Msgf("localization#LoadAssets failed %s", err) + } + + coreClient := core.NewClient(tests.StaticTestingFetch(), nil, tests.StaticTestingDatabase()) + handlers, err := frontend.Handlers(coreClient) + if err != nil { + panic(err) + } + + handlers = append(handlers, redirectHandlersFromEnv()...) + + listen := server.NewServer(os.Getenv("PORT"), handlers...) + listen() +} + +func redirectHandlersFromEnv() []server.Handler { + return []server.Handler{ + { + Path: "GET /invite/{$}", + Func: func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, os.Getenv("BOT_INVITE_LINK"), http.StatusTemporaryRedirect) + }, + }, + { + Path: "GET /join/{$}", + Func: func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, os.Getenv("PRIMARY_GUILD_INVITE_LINK"), http.StatusTemporaryRedirect) + }, + }, + } +} diff --git a/go.mod b/go.mod index 68658f09..224245ef 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.3 require ( entgo.io/ent v0.13.1 + github.com/a-h/templ v0.2.731 github.com/bwmarrin/discordgo v0.28.1 github.com/cufee/am-wg-proxy-next/v2 v2.1.7 github.com/disintegration/imaging v1.6.2 @@ -18,6 +19,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.9.0 + github.com/tdewolff/minify/v2 v2.20.34 go.dedis.ch/protobuf v1.0.11 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/image v0.17.0 @@ -41,6 +43,7 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/tdewolff/parse/v2 v2.7.15 // indirect github.com/zclconf/go-cty v1.14.4 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.24.0 // indirect diff --git a/go.sum b/go.sum index bf5c2b86..1b86d9e9 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE= entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/a-h/templ v0.2.731 h1:yiv4C7whSUsa36y65O06DPr/U/j3+WGB0RmvLOoVFXc= +github.com/a-h/templ v0.2.731/go.mod h1:IejA/ecDD0ul0dCvgCwp9t7bUZXVpGClEAdsqZQigi8= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= @@ -88,6 +90,13 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tdewolff/minify/v2 v2.20.34 h1:XueI6sQtgS7du45fyBCNkNfPQ9SINaYavMFNOxp37SA= +github.com/tdewolff/minify/v2 v2.20.34/go.mod h1:L1VYef/jwKw6Wwyk5A+T0mBjjn3mMPgmjjA688RNsxU= +github.com/tdewolff/parse/v2 v2.7.15 h1:hysDXtdGZIRF5UZXwpfn3ZWRbm+ru4l53/ajBRGpCTw= +github.com/tdewolff/parse/v2 v2.7.15/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= diff --git a/internal/database/models/vehicle_snapshot.go b/internal/database/models/vehicle_snapshot.go index c3152ce5..cff0cca7 100644 --- a/internal/database/models/vehicle_snapshot.go +++ b/internal/database/models/vehicle_snapshot.go @@ -9,8 +9,9 @@ import ( type SnapshotType string const ( - SnapshotTypeLive SnapshotType = "live" - SnapshotTypeDaily SnapshotType = "daily" + SnapshotTypeLive SnapshotType = "live" + SnapshotTypeDaily SnapshotType = "daily" + SnapshotTypeWidget SnapshotType = "widget" ) // Values provides list valid values for Enum. diff --git a/internal/stats/client/v1/client.go b/internal/stats/client/v1/client.go new file mode 100644 index 00000000..0dd9ebb9 --- /dev/null +++ b/internal/stats/client/v1/client.go @@ -0,0 +1,35 @@ +package client + +import ( + "context" + "time" + + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/external/wargaming" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + period "github.com/cufee/aftermath/internal/stats/prepare/period/v1" + session "github.com/cufee/aftermath/internal/stats/prepare/session/v1" + "golang.org/x/text/language" +) + +var _ Client = &client{} + +type client struct { + fetchClient fetch.Client + wargaming wargaming.Client + database database.Client + locale language.Tag +} + +type Client interface { + PeriodCards(ctx context.Context, accountId string, from time.Time, opts ...RequestOption) (period.Cards, Metadata, error) + PeriodImage(ctx context.Context, accountId string, from time.Time, opts ...RequestOption) (Image, Metadata, error) + SessionCards(ctx context.Context, accountId string, from time.Time, opts ...RequestOption) (session.Cards, Metadata, error) + SessionImage(ctx context.Context, accountId string, from time.Time, opts ...RequestOption) (Image, Metadata, error) + + // Replay(accountId string, from time.Time) (image.Image, error) +} + +func NewClient(fetch fetch.Client, database database.Client, wargaming wargaming.Client, locale language.Tag) Client { + return &client{fetch, wargaming, database, locale} +} diff --git a/internal/stats/renderer/v1/errors.go b/internal/stats/client/v1/errors.go similarity index 85% rename from internal/stats/renderer/v1/errors.go rename to internal/stats/client/v1/errors.go index a09051b0..d61d7179 100644 --- a/internal/stats/renderer/v1/errors.go +++ b/internal/stats/client/v1/errors.go @@ -1,4 +1,4 @@ -package renderer +package client import ( "github.com/pkg/errors" diff --git a/internal/stats/renderer/v1/image.go b/internal/stats/client/v1/image.go similarity index 97% rename from internal/stats/renderer/v1/image.go rename to internal/stats/client/v1/image.go index d82f9507..1a651052 100644 --- a/internal/stats/renderer/v1/image.go +++ b/internal/stats/client/v1/image.go @@ -1,4 +1,4 @@ -package renderer +package client import ( "image" diff --git a/internal/stats/renderer/v1/metadata.go b/internal/stats/client/v1/metadata.go similarity index 97% rename from internal/stats/renderer/v1/metadata.go rename to internal/stats/client/v1/metadata.go index e5ffd158..65b96fc4 100644 --- a/internal/stats/renderer/v1/metadata.go +++ b/internal/stats/client/v1/metadata.go @@ -1,4 +1,4 @@ -package renderer +package client import ( "time" diff --git a/internal/stats/client/v1/options.go b/internal/stats/client/v1/options.go new file mode 100644 index 00000000..26cdbb4f --- /dev/null +++ b/internal/stats/client/v1/options.go @@ -0,0 +1,58 @@ +package client + +import ( + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/fetch/v1" + "github.com/cufee/aftermath/internal/stats/render/common/v1" +) + +type requestOptions struct { + snapshotType models.SnapshotType + backgroundURL string + referenceID string + promoText []string + withWN8 bool +} + +type RequestOption func(o *requestOptions) + +func WithWN8() RequestOption { + return func(o *requestOptions) { o.withWN8 = true } +} +func WithReferenceID(refID string) RequestOption { + return func(o *requestOptions) { o.referenceID = refID } +} +func WithPromoText(text ...string) RequestOption { + return func(o *requestOptions) { o.promoText = append(o.promoText, text...) } +} +func WithType(t models.SnapshotType) RequestOption { + return func(o *requestOptions) { o.snapshotType = t } +} +func WithBackgroundURL(url string) RequestOption { + return func(o *requestOptions) { o.backgroundURL = url } +} + +func (o requestOptions) RenderOpts() []common.Option { + var copts []common.Option + if o.promoText != nil { + copts = append(copts, common.WithPromoText(o.promoText...)) + } + if o.backgroundURL != "" { + copts = append(copts, common.WithBackground(o.backgroundURL)) + } + return copts +} + +func (o requestOptions) FetchOpts() []fetch.StatsOption { + var opts []fetch.StatsOption + if o.snapshotType != "" { + opts = append(opts, fetch.WithType(o.snapshotType)) + } + if o.referenceID != "" { + opts = append(opts, fetch.WithReferenceID(o.referenceID)) + } + if o.withWN8 { + opts = append(opts, fetch.WithWN8()) + } + return opts +} diff --git a/internal/stats/renderer/v1/period.go b/internal/stats/client/v1/period.go similarity index 63% rename from internal/stats/renderer/v1/period.go rename to internal/stats/client/v1/period.go index bea9d49f..ced5231d 100644 --- a/internal/stats/renderer/v1/period.go +++ b/internal/stats/client/v1/period.go @@ -1,4 +1,4 @@ -package renderer +package client import ( "context" @@ -9,23 +9,27 @@ import ( "github.com/cufee/aftermath/internal/stats/fetch/v1" pcommon "github.com/cufee/aftermath/internal/stats/prepare/common/v1" prepare "github.com/cufee/aftermath/internal/stats/prepare/period/v1" - rcommon "github.com/cufee/aftermath/internal/stats/render/common/v1" render "github.com/cufee/aftermath/internal/stats/render/period/v1" ) -func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, opts ...rcommon.Option) (Image, Metadata, error) { +func (r *client) PeriodCards(ctx context.Context, accountId string, from time.Time, o ...RequestOption) (prepare.Cards, Metadata, error) { + var opts = requestOptions{} + for _, apply := range o { + apply(&opts) + } + meta := Metadata{Stats: make(map[string]fetch.AccountStatsOverPeriod)} printer, err := localization.NewPrinter("stats", r.locale) if err != nil { - return nil, meta, err + return prepare.Cards{}, meta, err } stop := meta.Timer("fetchClient#PeriodStats") stats, err := r.fetchClient.PeriodStats(ctx, accountId, from, fetch.WithWN8()) stop() if err != nil { - return nil, meta, err + return prepare.Cards{}, meta, err } meta.Stats["period"] = stats @@ -42,19 +46,30 @@ func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, glossary, err := r.database.GetVehicles(ctx, vehicles) if err != nil { - return nil, meta, err + return prepare.Cards{}, meta, err } stop() stop = meta.Timer("prepare#NewCards") cards, err := prepare.NewCards(stats, glossary, pcommon.WithPrinter(printer, r.locale)) stop() + + return cards, meta, err +} + +func (r *client) PeriodImage(ctx context.Context, accountId string, from time.Time, o ...RequestOption) (Image, Metadata, error) { + var opts = requestOptions{} + for _, apply := range o { + apply(&opts) + } + + cards, meta, err := r.PeriodCards(ctx, accountId, from, o...) if err != nil { return nil, meta, err } - stop = meta.Timer("render#CardsToImage") - image, err := render.CardsToImage(stats, cards, nil, opts...) + stop := meta.Timer("render#CardsToImage") + image, err := render.CardsToImage(meta.Stats["period"], cards, nil, opts.RenderOpts()...) stop() if err != nil { return nil, meta, err diff --git a/internal/stats/client/v1/replay.go b/internal/stats/client/v1/replay.go new file mode 100644 index 00000000..da13c8ef --- /dev/null +++ b/internal/stats/client/v1/replay.go @@ -0,0 +1 @@ +package client diff --git a/internal/stats/renderer/v1/session.go b/internal/stats/client/v1/session.go similarity index 59% rename from internal/stats/renderer/v1/session.go rename to internal/stats/client/v1/session.go index f1123e8b..15fb026a 100644 --- a/internal/stats/renderer/v1/session.go +++ b/internal/stats/client/v1/session.go @@ -1,4 +1,4 @@ -package renderer +package client import ( "context" @@ -12,48 +12,52 @@ import ( "github.com/cufee/aftermath/internal/stats/fetch/v1" "github.com/cufee/aftermath/internal/stats/prepare/common/v1" prepare "github.com/cufee/aftermath/internal/stats/prepare/session/v1" - options "github.com/cufee/aftermath/internal/stats/render/common/v1" render "github.com/cufee/aftermath/internal/stats/render/session/v1" "github.com/rs/zerolog/log" ) -func (r *renderer) Session(ctx context.Context, accountId string, from time.Time, opts ...options.Option) (Image, Metadata, error) { +func (c *client) SessionCards(ctx context.Context, accountId string, from time.Time, o ...RequestOption) (prepare.Cards, Metadata, error) { + var opts = requestOptions{} + for _, apply := range o { + apply(&opts) + } + meta := Metadata{Stats: make(map[string]fetch.AccountStatsOverPeriod)} stop := meta.Timer("database#GetAccountByID") - _, err := r.database.GetAccountByID(ctx, accountId) + _, err := c.database.GetAccountByID(ctx, accountId) stop() if err != nil { if database.IsNotFound(err) { - _, err := r.fetchClient.Account(ctx, accountId) // this will cache the account + _, err := c.fetchClient.Account(ctx, accountId) // this will cache the account if err != nil { - return nil, meta, err + return prepare.Cards{}, meta, err } go func(id string) { // record a session in the background ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer cancel() - _, err := logic.RecordAccountSnapshots(ctx, r.wargaming, r.database, r.wargaming.RealmFromAccountID(id), false, id) + _, err := logic.RecordAccountSnapshots(ctx, c.wargaming, c.database, c.wargaming.RealmFromAccountID(id), false, id) if err != nil { log.Err(err).Str("accountId", id).Msg("failed to record account snapshot") } }(accountId) - return nil, meta, ErrAccountNotTracked + return prepare.Cards{}, meta, ErrAccountNotTracked } - return nil, meta, err + return prepare.Cards{}, meta, err } - printer, err := localization.NewPrinter("stats", r.locale) + printer, err := localization.NewPrinter("stats", c.locale) if err != nil { - return nil, meta, err + return prepare.Cards{}, meta, err } stop = meta.Timer("fetchClient#SessionStats") if from.IsZero() { from = time.Now() } - session, career, err := r.fetchClient.SessionStats(ctx, accountId, from, fetch.WithWN8()) + session, career, err := c.fetchClient.SessionStats(ctx, accountId, from, opts.FetchOpts()...) stop() if err != nil { if errors.Is(fetch.ErrSessionNotFound, err) { @@ -62,13 +66,13 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer cancel() - _, err := logic.RecordAccountSnapshots(ctx, r.wargaming, r.database, r.wargaming.RealmFromAccountID(id), false, id) + _, err := logic.RecordAccountSnapshots(ctx, c.wargaming, c.database, c.wargaming.RealmFromAccountID(id), false, id) if err != nil { log.Err(err).Str("accountId", id).Msg("failed to record account snapshot") } }(accountId) } - return nil, meta, err + return prepare.Cards{}, meta, err } meta.Stats["career"] = career meta.Stats["session"] = session @@ -94,21 +98,35 @@ func (r *renderer) Session(ctx context.Context, accountId string, from time.Time } } - glossary, err := r.database.GetVehicles(ctx, vehicles) + glossary, err := c.database.GetVehicles(ctx, vehicles) if err != nil { - return nil, meta, err + return prepare.Cards{}, meta, err } stop() stop = meta.Timer("prepare#NewCards") - cards, err := prepare.NewCards(session, career, glossary, common.WithPrinter(printer, r.locale)) + cards, err := prepare.NewCards(session, career, glossary, common.WithPrinter(printer, c.locale)) stop() + if err != nil { + return prepare.Cards{}, meta, err + } + + return cards, meta, nil +} + +func (c *client) SessionImage(ctx context.Context, accountId string, from time.Time, o ...RequestOption) (Image, Metadata, error) { + var opts = requestOptions{} + for _, apply := range o { + apply(&opts) + } + + cards, meta, err := c.SessionCards(ctx, accountId, from, o...) if err != nil { return nil, meta, err } - stop = meta.Timer("render#CardsToImage") - image, err := render.CardsToImage(session, career, cards, nil, opts...) + stop := meta.Timer("render#CardsToImage") + image, err := render.CardsToImage(meta.Stats["session"], meta.Stats["career"], cards, nil, opts.RenderOpts()...) stop() if err != nil { return nil, meta, err diff --git a/internal/stats/fetch/v1/client.go b/internal/stats/fetch/v1/client.go index 8bcef83b..79a2ba70 100644 --- a/internal/stats/fetch/v1/client.go +++ b/internal/stats/fetch/v1/client.go @@ -58,7 +58,9 @@ type Client interface { } type statsOptions struct { - withWN8 bool + withWN8 bool + referenceID string + snapshotType models.SnapshotType } type StatsOption func(*statsOptions) @@ -68,3 +70,13 @@ func WithWN8() StatsOption { so.withWN8 = true } } +func WithType(sType models.SnapshotType) StatsOption { + return func(so *statsOptions) { + so.snapshotType = sType + } +} +func WithReferenceID(reference string) StatsOption { + return func(so *statsOptions) { + so.referenceID = reference + } +} diff --git a/internal/stats/fetch/v1/multisource.go b/internal/stats/fetch/v1/multisource.go index 060398e7..60e919a9 100644 --- a/internal/stats/fetch/v1/multisource.go +++ b/internal/stats/fetch/v1/multisource.go @@ -272,7 +272,7 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt } func (c *multiSourceClient) SessionStats(ctx context.Context, id string, sessionStart time.Time, opts ...StatsOption) (AccountStatsOverPeriod, AccountStatsOverPeriod, error) { - var options statsOptions + var options = statsOptions{snapshotType: models.SnapshotTypeDaily, referenceID: id} for _, apply := range opts { apply(&options) } @@ -311,13 +311,13 @@ func (c *multiSourceClient) SessionStats(ctx context.Context, id string, session group.Add(1) go func() { defer group.Done() - s, err := c.database.GetAccountSnapshot(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) + s, err := c.database.GetAccountSnapshot(ctx, id, options.referenceID, options.snapshotType, database.WithCreatedBefore(sessionBefore)) accountSnapshot = retry.DataWithErr[models.AccountSnapshot]{Data: s, Err: err} if err != nil { return } - v, err := c.database.GetVehicleSnapshots(ctx, id, id, models.SnapshotTypeDaily, database.WithCreatedBefore(sessionBefore)) + v, err := c.database.GetVehicleSnapshots(ctx, id, options.referenceID, options.snapshotType, database.WithCreatedBefore(sessionBefore)) vehiclesSnapshots = retry.DataWithErr[[]models.VehicleSnapshot]{Data: v, Err: err} }() diff --git a/internal/stats/prepare/session/v1/constants.go b/internal/stats/prepare/session/v1/constants.go index 6996f9bf..4d872179 100644 --- a/internal/stats/prepare/session/v1/constants.go +++ b/internal/stats/prepare/session/v1/constants.go @@ -12,7 +12,7 @@ type overviewColumnBlocks struct { var unratedOverviewBlocks = []overviewColumnBlocks{ {[]common.Tag{common.TagBattles, common.TagWinrate}, BlockFlavorDefault}, - {[]common.Tag{common.TagWN8}, BlockFlavorRating}, + {[]common.Tag{common.TagWN8}, BlockFlavorWN8}, {[]common.Tag{common.TagAvgDamage, common.TagDamageRatio}, BlockFlavorDefault}, } @@ -59,4 +59,5 @@ type blockFlavor string const ( BlockFlavorDefault blockFlavor = "default" BlockFlavorRating blockFlavor = "rating" + BlockFlavorWN8 blockFlavor = "wn8" ) diff --git a/internal/stats/render/common/v1/rating.go b/internal/stats/render/common/v1/rating.go new file mode 100644 index 00000000..53f6c62b --- /dev/null +++ b/internal/stats/render/common/v1/rating.go @@ -0,0 +1,45 @@ +package common + +import ( + "github.com/cufee/aftermath/internal/stats/frame" + "github.com/cufee/aftermath/internal/stats/render/assets" +) + +var iconsCache = make(map[string]Block, 6) + +func GetRatingIconName(rating float32) string { + switch { + case rating > 5000: + return "diamond" + case rating > 4000: + return "platinum" + case rating > 3000: + return "gold" + case rating > 2000: + return "silver" + case rating > 0: + return "bronze" + default: + return "calibration" + } +} + +func GetRatingIcon(rating frame.Value, size float64) (Block, bool) { + style := Style{Width: size, Height: size} + if rating.Float() < 0 { + style.BackgroundColor = TextAlt + } + name := "rating-" + GetRatingIconName(rating.Float()) + + if b, ok := iconsCache[name]; ok { + return b, true + } + + img, ok := assets.GetLoadedImage(name) + if !ok { + return Block{}, false + } + + iconsCache[name] = NewImageContent(style, img) + return iconsCache[name], true +} diff --git a/internal/stats/render/session/v1/blocks.go b/internal/stats/render/session/v1/blocks.go index 64220f45..64ff1315 100644 --- a/internal/stats/render/session/v1/blocks.go +++ b/internal/stats/render/session/v1/blocks.go @@ -41,7 +41,7 @@ func makeSpecialRatingColumn(block prepare.StatsBlock[session.BlockData], width case prepare.TagRankedRating: var column []common.Block - icon, ok := getRatingIcon(block.Value) + icon, ok := common.GetRatingIcon(block.Value, specialRatingIconSize) if ok { column = append(column, icon) } diff --git a/internal/stats/render/session/v1/icons.go b/internal/stats/render/session/v1/icons.go deleted file mode 100644 index 1684fbaf..00000000 --- a/internal/stats/render/session/v1/icons.go +++ /dev/null @@ -1,40 +0,0 @@ -package session - -import ( - "github.com/cufee/aftermath/internal/stats/frame" - "github.com/cufee/aftermath/internal/stats/render/assets" - "github.com/cufee/aftermath/internal/stats/render/common/v1" -) - -var iconsCache = make(map[string]common.Block, 6) - -func getRatingIcon(rating frame.Value) (common.Block, bool) { - style := common.Style{Width: specialRatingIconSize, Height: specialRatingIconSize} - name := "rating-" - switch { - case rating.Float() > 5000: - name += "diamond" - case rating.Float() > 4000: - name += "platinum" - case rating.Float() > 3000: - name += "gold" - case rating.Float() > 2000: - name += "silver" - case rating.Float() > 0: - name += "bronze" - default: - name += "calibration" - style.BackgroundColor = common.TextAlt - } - if b, ok := iconsCache[name]; ok { - return b, true - } - - img, ok := assets.GetLoadedImage(name) - if !ok { - return common.Block{}, false - } - - iconsCache[name] = common.NewImageContent(style, img) - return iconsCache[name], true -} diff --git a/internal/stats/renderer/v1/renderer.go b/internal/stats/renderer/v1/renderer.go deleted file mode 100644 index fab17367..00000000 --- a/internal/stats/renderer/v1/renderer.go +++ /dev/null @@ -1,32 +0,0 @@ -package renderer - -import ( - "context" - "time" - - "github.com/cufee/aftermath/internal/database" - "github.com/cufee/aftermath/internal/external/wargaming" - "github.com/cufee/aftermath/internal/stats/fetch/v1" - "github.com/cufee/aftermath/internal/stats/render/common/v1" - "golang.org/x/text/language" -) - -var _ Renderer = &renderer{} - -type renderer struct { - fetchClient fetch.Client - wargaming wargaming.Client - database database.Client - locale language.Tag -} - -type Renderer interface { - Period(ctx context.Context, accountId string, from time.Time, opts ...common.Option) (Image, Metadata, error) - Session(ctx context.Context, accountId string, from time.Time, opts ...common.Option) (Image, Metadata, error) - - // Replay(accountId string, from time.Time) (image.Image, error) -} - -func NewRenderer(fetch fetch.Client, database database.Client, wargaming wargaming.Client, locale language.Tag) *renderer { - return &renderer{fetch, wargaming, database, locale} -} diff --git a/internal/stats/renderer/v1/replay.go b/internal/stats/renderer/v1/replay.go deleted file mode 100644 index 35803f64..00000000 --- a/internal/stats/renderer/v1/replay.go +++ /dev/null @@ -1 +0,0 @@ -package renderer diff --git a/main.go b/main.go index 929ec2e9..aad48955 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ import ( "github.com/cufee/aftermath/cmd/core/scheduler" "github.com/cufee/aftermath/cmd/core/tasks" "github.com/cufee/aftermath/cmd/discord/commands" + "github.com/cufee/aftermath/cmd/frontend" "github.com/cufee/aftermath/cmd/core/server" "github.com/cufee/aftermath/cmd/core/server/handlers/private" @@ -42,6 +43,7 @@ import ( ) //go:generate go generate ./internal/database/ent +//go:generate go generate ./cmd/frontend/assets //go:embed static/* var static embed.FS @@ -102,12 +104,23 @@ func main() { go servePrivate() } + // will handle all GET routes with a wildcard + frontendHandlers, err := frontend.Handlers(liveCoreClient) + if err != nil { + log.Fatal().Err(err).Msg("frontend#Handlers failed") + } + discordHandlers := discordHandlersFromEnv(liveCoreClient) - // /discord/public/callback - // /discord/internal/callback + // POST /discord/public/callback + // POST /discord/internal/callback + + var handlers []server.Handler + handlers = append(handlers, discordHandlers...) + handlers = append(handlers, frontendHandlers...) + handlers = append(handlers, redirectHandlersFromEnv()...) port := os.Getenv("PORT") - servePublic := server.NewServer(port, discordHandlers...) + servePublic := server.NewServer(port, handlers...) log.Info().Str("port", port).Msg("starting a public server") go servePublic() @@ -261,3 +274,20 @@ func newDatabaseClientFromEnv() (database.Client, error) { return client, nil } + +func redirectHandlersFromEnv() []server.Handler { + return []server.Handler{ + { + Path: "GET /invite/{$}", + Func: func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, os.Getenv("BOT_INVITE_LINK"), http.StatusTemporaryRedirect) + }, + }, + { + Path: "GET /join/{$}", + Func: func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, os.Getenv("PRIMARY_GUILD_INVITE_LINK"), http.StatusTemporaryRedirect) + }, + }, + } +} diff --git a/render_test.go b/render_test.go index e995f5f6..6a0e37aa 100644 --- a/render_test.go +++ b/render_test.go @@ -6,8 +6,7 @@ import ( "testing" "time" - "github.com/cufee/aftermath/internal/stats/render/common/v1" - stats "github.com/cufee/aftermath/internal/stats/renderer/v1" + stats "github.com/cufee/aftermath/internal/stats/client/v1" "github.com/cufee/aftermath/tests" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" @@ -23,10 +22,10 @@ func TestRenderSession(t *testing.T) { loadStaticAssets(static) - renderer := stats.NewRenderer(tests.StaticTestingFetch(), tests.StaticTestingDatabase(), nil, language.English) + stats := stats.NewClient(tests.StaticTestingFetch(), tests.StaticTestingDatabase(), nil, language.English) { - image, _, err := renderer.Session(context.Background(), tests.DefaultAccountNAShort, time.Now(), common.WithBackground("")) + image, _, err := stats.SessionImage(context.Background(), tests.DefaultAccountNAShort, time.Now()) assert.NoError(t, err, "failed to render a session image") assert.NotNil(t, image, "image is nil") @@ -38,7 +37,7 @@ func TestRenderSession(t *testing.T) { assert.NoError(t, err, "failed to encode a png image") } { - image, _, err := renderer.Session(context.Background(), tests.DefaultAccountNA, time.Now(), common.WithBackground("")) + image, _, err := stats.SessionImage(context.Background(), tests.DefaultAccountNA, time.Now()) assert.NoError(t, err, "failed to render a session image") assert.NotNil(t, image, "image is nil") diff --git a/tests/static_database.go b/tests/static_database.go index 065c0c7a..6c2a4366 100644 --- a/tests/static_database.go +++ b/tests/static_database.go @@ -64,8 +64,11 @@ func (c *staticTestingDatabase) GetVehicles(ctx context.Context, ids []string) ( return vehicles, nil } func (c *staticTestingDatabase) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { - // TODO: get some kind of data in - return map[string]frame.StatsFrame{}, nil + averages := make(map[string]frame.StatsFrame) + for _, id := range ids { + averages[id] = DefaultStatsFrameSmall2 + } + return averages, nil } func (c *staticTestingDatabase) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) (map[string]error, error) { return nil, errors.New("UpsertVehicles not implemented") From cbc7f51bd41cdb22ba606ccad4a95d126f2e7a94 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 2 Jul 2024 20:48:29 -0400 Subject: [PATCH 239/341] fixed blank renders --- cmd/frontend/components/widget/default.templ | 7 ++++-- .../components/widget/default_templ.go | 25 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/cmd/frontend/components/widget/default.templ b/cmd/frontend/components/widget/default.templ index 07ad5388..cfa4a601 100644 --- a/cmd/frontend/components/widget/default.templ +++ b/cmd/frontend/components/widget/default.templ @@ -34,6 +34,9 @@ func (w widget) defaultWidget() templ.Component { if i >= w.unratedVehiclesLimit { break } + if len(card.Blocks) < 1 { + continue + } vehicles = append(vehicles, vehicleExtended{}.fromCard(card)) } @@ -42,10 +45,10 @@ func (w widget) defaultWidget() templ.Component { templ (w defaultWidget) Render() {
    - if w.showRatingOverview { + if w.showRatingOverview && len(w.cards.Rating.Overview.Blocks) > 0 { @w.overviewCard(w.cards.Rating.Overview) } - if w.showUnratedOverview { + if w.showUnratedOverview && len(w.cards.Unrated.Overview.Blocks) > 0 { @w.overviewCard(w.cards.Unrated.Overview) } for _, card := range w.vehicleCards { diff --git a/cmd/frontend/components/widget/default_templ.go b/cmd/frontend/components/widget/default_templ.go index 23a95c88..a90f5fdb 100644 --- a/cmd/frontend/components/widget/default_templ.go +++ b/cmd/frontend/components/widget/default_templ.go @@ -44,6 +44,9 @@ func (w widget) defaultWidget() templ.Component { if i >= w.unratedVehiclesLimit { break } + if len(card.Blocks) < 1 { + continue + } vehicles = append(vehicles, vehicleExtended{}.fromCard(card)) } @@ -67,13 +70,13 @@ func (w defaultWidget) Render() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if w.showRatingOverview { + if w.showRatingOverview && len(w.cards.Rating.Overview.Blocks) > 0 { templ_7745c5c3_Err = w.overviewCard(w.cards.Rating.Overview).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - if w.showUnratedOverview { + if w.showUnratedOverview && len(w.cards.Unrated.Overview.Blocks) > 0 { templ_7745c5c3_Err = w.overviewCard(w.cards.Unrated.Overview).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -127,7 +130,7 @@ func (w defaultWidget) overviewCard(card session.OverviewCard) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(card.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 64, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 67, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -202,7 +205,7 @@ func (w defaultWidget) vehicleCard(card vehicleExtended) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(card.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 80, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 83, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -215,7 +218,7 @@ func (w defaultWidget) vehicleCard(card vehicleExtended) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(assets.WN8IconPathSmall(card.wn8.Float())) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 82, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 85, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -312,7 +315,7 @@ func (w defaultWidget) vehicleLegendCard(blocks []common.StatsBlock[session.Bloc var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(block.Label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 98, Col: 18} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 101, Col: 18} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -401,7 +404,7 @@ func (w defaultWidget) specialOverviewColumn(column session.OverviewColumn) temp var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(assets.RatingIconPath(block.Value.Float())) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 121, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 124, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -424,7 +427,7 @@ func (w defaultWidget) specialOverviewColumn(column session.OverviewColumn) temp var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(assets.WN8IconPath(block.Value.Float())) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 124, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 127, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -475,7 +478,7 @@ func (w defaultWidget) block(block common.StatsBlock[session.BlockData], style s var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(block.Data.Session.String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 133, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 136, Col: 65} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -493,7 +496,7 @@ func (w defaultWidget) block(block common.StatsBlock[session.BlockData], style s var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(block.Data.Career.String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 135, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 138, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -512,7 +515,7 @@ func (w defaultWidget) block(block common.StatsBlock[session.BlockData], style s var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(block.Label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 138, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/widget/default.templ`, Line: 141, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { From 3b7a2a0e8033c880596ab83f0a19cad629859c16 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 2 Jul 2024 20:53:52 -0400 Subject: [PATCH 240/341] added generate --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 2816f085..9e23b37d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ # build a fully standalone binary with zero dependencies +RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . # Make a scratch container with required files and binary From c0d6e93e7ccc5d5b1e2fd6d5ef78f1dd42712f00 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 2 Jul 2024 20:56:08 -0400 Subject: [PATCH 241/341] tidy, generate --- Dockerfile | 2 +- internal/database/ent/db/account.go | 36 ++-- internal/database/ent/db/account/account.go | 72 +++---- internal/database/ent/db/account/where.go | 24 +-- internal/database/ent/db/account_create.go | 40 ++-- internal/database/ent/db/account_query.go | 88 ++++---- internal/database/ent/db/account_update.go | 216 ++++++++++---------- internal/database/ent/db/client.go | 20 +- internal/database/ent/db/mutation.go | 188 ++++++++--------- 9 files changed, 343 insertions(+), 343 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9e23b37d..0148d3d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ # build a fully standalone binary with zero dependencies -RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./... +RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate main.go RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . # Make a scratch container with required files and binary diff --git a/internal/database/ent/db/account.go b/internal/database/ent/db/account.go index c2ac1943..4412ee60 100644 --- a/internal/database/ent/db/account.go +++ b/internal/database/ent/db/account.go @@ -44,12 +44,12 @@ type Account struct { type AccountEdges struct { // Clan holds the value of the clan edge. Clan *Clan `json:"clan,omitempty"` - // AccountSnapshots holds the value of the account_snapshots edge. - AccountSnapshots []*AccountSnapshot `json:"account_snapshots,omitempty"` - // VehicleSnapshots holds the value of the vehicle_snapshots edge. - VehicleSnapshots []*VehicleSnapshot `json:"vehicle_snapshots,omitempty"` // AchievementSnapshots holds the value of the achievement_snapshots edge. AchievementSnapshots []*AchievementsSnapshot `json:"achievement_snapshots,omitempty"` + // VehicleSnapshots holds the value of the vehicle_snapshots edge. + VehicleSnapshots []*VehicleSnapshot `json:"vehicle_snapshots,omitempty"` + // AccountSnapshots holds the value of the account_snapshots edge. + AccountSnapshots []*AccountSnapshot `json:"account_snapshots,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. loadedTypes [4]bool @@ -66,13 +66,13 @@ func (e AccountEdges) ClanOrErr() (*Clan, error) { return nil, &NotLoadedError{edge: "clan"} } -// AccountSnapshotsOrErr returns the AccountSnapshots value or an error if the edge +// AchievementSnapshotsOrErr returns the AchievementSnapshots value or an error if the edge // was not loaded in eager-loading. -func (e AccountEdges) AccountSnapshotsOrErr() ([]*AccountSnapshot, error) { +func (e AccountEdges) AchievementSnapshotsOrErr() ([]*AchievementsSnapshot, error) { if e.loadedTypes[1] { - return e.AccountSnapshots, nil + return e.AchievementSnapshots, nil } - return nil, &NotLoadedError{edge: "account_snapshots"} + return nil, &NotLoadedError{edge: "achievement_snapshots"} } // VehicleSnapshotsOrErr returns the VehicleSnapshots value or an error if the edge @@ -84,13 +84,13 @@ func (e AccountEdges) VehicleSnapshotsOrErr() ([]*VehicleSnapshot, error) { return nil, &NotLoadedError{edge: "vehicle_snapshots"} } -// AchievementSnapshotsOrErr returns the AchievementSnapshots value or an error if the edge +// AccountSnapshotsOrErr returns the AccountSnapshots value or an error if the edge // was not loaded in eager-loading. -func (e AccountEdges) AchievementSnapshotsOrErr() ([]*AchievementsSnapshot, error) { +func (e AccountEdges) AccountSnapshotsOrErr() ([]*AccountSnapshot, error) { if e.loadedTypes[3] { - return e.AchievementSnapshots, nil + return e.AccountSnapshots, nil } - return nil, &NotLoadedError{edge: "achievement_snapshots"} + return nil, &NotLoadedError{edge: "account_snapshots"} } // scanValues returns the types for scanning values from sql.Rows. @@ -191,9 +191,9 @@ func (a *Account) QueryClan() *ClanQuery { return NewAccountClient(a.config).QueryClan(a) } -// QueryAccountSnapshots queries the "account_snapshots" edge of the Account entity. -func (a *Account) QueryAccountSnapshots() *AccountSnapshotQuery { - return NewAccountClient(a.config).QueryAccountSnapshots(a) +// QueryAchievementSnapshots queries the "achievement_snapshots" edge of the Account entity. +func (a *Account) QueryAchievementSnapshots() *AchievementsSnapshotQuery { + return NewAccountClient(a.config).QueryAchievementSnapshots(a) } // QueryVehicleSnapshots queries the "vehicle_snapshots" edge of the Account entity. @@ -201,9 +201,9 @@ func (a *Account) QueryVehicleSnapshots() *VehicleSnapshotQuery { return NewAccountClient(a.config).QueryVehicleSnapshots(a) } -// QueryAchievementSnapshots queries the "achievement_snapshots" edge of the Account entity. -func (a *Account) QueryAchievementSnapshots() *AchievementsSnapshotQuery { - return NewAccountClient(a.config).QueryAchievementSnapshots(a) +// QueryAccountSnapshots queries the "account_snapshots" edge of the Account entity. +func (a *Account) QueryAccountSnapshots() *AccountSnapshotQuery { + return NewAccountClient(a.config).QueryAccountSnapshots(a) } // Update returns a builder for updating this Account. diff --git a/internal/database/ent/db/account/account.go b/internal/database/ent/db/account/account.go index adb50e4e..7be72dac 100644 --- a/internal/database/ent/db/account/account.go +++ b/internal/database/ent/db/account/account.go @@ -32,12 +32,12 @@ const ( FieldClanID = "clan_id" // EdgeClan holds the string denoting the clan edge name in mutations. EdgeClan = "clan" - // EdgeAccountSnapshots holds the string denoting the account_snapshots edge name in mutations. - EdgeAccountSnapshots = "account_snapshots" - // EdgeVehicleSnapshots holds the string denoting the vehicle_snapshots edge name in mutations. - EdgeVehicleSnapshots = "vehicle_snapshots" // EdgeAchievementSnapshots holds the string denoting the achievement_snapshots edge name in mutations. EdgeAchievementSnapshots = "achievement_snapshots" + // EdgeVehicleSnapshots holds the string denoting the vehicle_snapshots edge name in mutations. + EdgeVehicleSnapshots = "vehicle_snapshots" + // EdgeAccountSnapshots holds the string denoting the account_snapshots edge name in mutations. + EdgeAccountSnapshots = "account_snapshots" // Table holds the table name of the account in the database. Table = "accounts" // ClanTable is the table that holds the clan relation/edge. @@ -47,20 +47,6 @@ const ( ClanInverseTable = "clans" // ClanColumn is the table column denoting the clan relation/edge. ClanColumn = "clan_id" - // AccountSnapshotsTable is the table that holds the account_snapshots relation/edge. - AccountSnapshotsTable = "account_snapshots" - // AccountSnapshotsInverseTable is the table name for the AccountSnapshot entity. - // It exists in this package in order to avoid circular dependency with the "accountsnapshot" package. - AccountSnapshotsInverseTable = "account_snapshots" - // AccountSnapshotsColumn is the table column denoting the account_snapshots relation/edge. - AccountSnapshotsColumn = "account_id" - // VehicleSnapshotsTable is the table that holds the vehicle_snapshots relation/edge. - VehicleSnapshotsTable = "vehicle_snapshots" - // VehicleSnapshotsInverseTable is the table name for the VehicleSnapshot entity. - // It exists in this package in order to avoid circular dependency with the "vehiclesnapshot" package. - VehicleSnapshotsInverseTable = "vehicle_snapshots" - // VehicleSnapshotsColumn is the table column denoting the vehicle_snapshots relation/edge. - VehicleSnapshotsColumn = "account_id" // AchievementSnapshotsTable is the table that holds the achievement_snapshots relation/edge. AchievementSnapshotsTable = "achievements_snapshots" // AchievementSnapshotsInverseTable is the table name for the AchievementsSnapshot entity. @@ -68,6 +54,20 @@ const ( AchievementSnapshotsInverseTable = "achievements_snapshots" // AchievementSnapshotsColumn is the table column denoting the achievement_snapshots relation/edge. AchievementSnapshotsColumn = "account_id" + // VehicleSnapshotsTable is the table that holds the vehicle_snapshots relation/edge. + VehicleSnapshotsTable = "vehicle_snapshots" + // VehicleSnapshotsInverseTable is the table name for the VehicleSnapshot entity. + // It exists in this package in order to avoid circular dependency with the "vehiclesnapshot" package. + VehicleSnapshotsInverseTable = "vehicle_snapshots" + // VehicleSnapshotsColumn is the table column denoting the vehicle_snapshots relation/edge. + VehicleSnapshotsColumn = "account_id" + // AccountSnapshotsTable is the table that holds the account_snapshots relation/edge. + AccountSnapshotsTable = "account_snapshots" + // AccountSnapshotsInverseTable is the table name for the AccountSnapshot entity. + // It exists in this package in order to avoid circular dependency with the "accountsnapshot" package. + AccountSnapshotsInverseTable = "account_snapshots" + // AccountSnapshotsColumn is the table column denoting the account_snapshots relation/edge. + AccountSnapshotsColumn = "account_id" ) // Columns holds all SQL columns for account fields. @@ -163,17 +163,17 @@ func ByClanField(field string, opts ...sql.OrderTermOption) OrderOption { } } -// ByAccountSnapshotsCount orders the results by account_snapshots count. -func ByAccountSnapshotsCount(opts ...sql.OrderTermOption) OrderOption { +// ByAchievementSnapshotsCount orders the results by achievement_snapshots count. +func ByAchievementSnapshotsCount(opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { - sqlgraph.OrderByNeighborsCount(s, newAccountSnapshotsStep(), opts...) + sqlgraph.OrderByNeighborsCount(s, newAchievementSnapshotsStep(), opts...) } } -// ByAccountSnapshots orders the results by account_snapshots terms. -func ByAccountSnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { +// ByAchievementSnapshots orders the results by achievement_snapshots terms. +func ByAchievementSnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { return func(s *sql.Selector) { - sqlgraph.OrderByNeighborTerms(s, newAccountSnapshotsStep(), append([]sql.OrderTerm{term}, terms...)...) + sqlgraph.OrderByNeighborTerms(s, newAchievementSnapshotsStep(), append([]sql.OrderTerm{term}, terms...)...) } } @@ -191,17 +191,17 @@ func ByVehicleSnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption } } -// ByAchievementSnapshotsCount orders the results by achievement_snapshots count. -func ByAchievementSnapshotsCount(opts ...sql.OrderTermOption) OrderOption { +// ByAccountSnapshotsCount orders the results by account_snapshots count. +func ByAccountSnapshotsCount(opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { - sqlgraph.OrderByNeighborsCount(s, newAchievementSnapshotsStep(), opts...) + sqlgraph.OrderByNeighborsCount(s, newAccountSnapshotsStep(), opts...) } } -// ByAchievementSnapshots orders the results by achievement_snapshots terms. -func ByAchievementSnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { +// ByAccountSnapshots orders the results by account_snapshots terms. +func ByAccountSnapshots(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { return func(s *sql.Selector) { - sqlgraph.OrderByNeighborTerms(s, newAchievementSnapshotsStep(), append([]sql.OrderTerm{term}, terms...)...) + sqlgraph.OrderByNeighborTerms(s, newAccountSnapshotsStep(), append([]sql.OrderTerm{term}, terms...)...) } } func newClanStep() *sqlgraph.Step { @@ -211,11 +211,11 @@ func newClanStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.M2O, true, ClanTable, ClanColumn), ) } -func newAccountSnapshotsStep() *sqlgraph.Step { +func newAchievementSnapshotsStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), - sqlgraph.To(AccountSnapshotsInverseTable, FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, AccountSnapshotsTable, AccountSnapshotsColumn), + sqlgraph.To(AchievementSnapshotsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AchievementSnapshotsTable, AchievementSnapshotsColumn), ) } func newVehicleSnapshotsStep() *sqlgraph.Step { @@ -225,10 +225,10 @@ func newVehicleSnapshotsStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2M, false, VehicleSnapshotsTable, VehicleSnapshotsColumn), ) } -func newAchievementSnapshotsStep() *sqlgraph.Step { +func newAccountSnapshotsStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), - sqlgraph.To(AchievementSnapshotsInverseTable, FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, AchievementSnapshotsTable, AchievementSnapshotsColumn), + sqlgraph.To(AccountSnapshotsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, AccountSnapshotsTable, AccountSnapshotsColumn), ) } diff --git a/internal/database/ent/db/account/where.go b/internal/database/ent/db/account/where.go index 2b170a4f..0c513cd9 100644 --- a/internal/database/ent/db/account/where.go +++ b/internal/database/ent/db/account/where.go @@ -503,21 +503,21 @@ func HasClanWith(preds ...predicate.Clan) predicate.Account { }) } -// HasAccountSnapshots applies the HasEdge predicate on the "account_snapshots" edge. -func HasAccountSnapshots() predicate.Account { +// HasAchievementSnapshots applies the HasEdge predicate on the "achievement_snapshots" edge. +func HasAchievementSnapshots() predicate.Account { return predicate.Account(func(s *sql.Selector) { step := sqlgraph.NewStep( sqlgraph.From(Table, FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, AccountSnapshotsTable, AccountSnapshotsColumn), + sqlgraph.Edge(sqlgraph.O2M, false, AchievementSnapshotsTable, AchievementSnapshotsColumn), ) sqlgraph.HasNeighbors(s, step) }) } -// HasAccountSnapshotsWith applies the HasEdge predicate on the "account_snapshots" edge with a given conditions (other predicates). -func HasAccountSnapshotsWith(preds ...predicate.AccountSnapshot) predicate.Account { +// HasAchievementSnapshotsWith applies the HasEdge predicate on the "achievement_snapshots" edge with a given conditions (other predicates). +func HasAchievementSnapshotsWith(preds ...predicate.AchievementsSnapshot) predicate.Account { return predicate.Account(func(s *sql.Selector) { - step := newAccountSnapshotsStep() + step := newAchievementSnapshotsStep() sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { for _, p := range preds { p(s) @@ -549,21 +549,21 @@ func HasVehicleSnapshotsWith(preds ...predicate.VehicleSnapshot) predicate.Accou }) } -// HasAchievementSnapshots applies the HasEdge predicate on the "achievement_snapshots" edge. -func HasAchievementSnapshots() predicate.Account { +// HasAccountSnapshots applies the HasEdge predicate on the "account_snapshots" edge. +func HasAccountSnapshots() predicate.Account { return predicate.Account(func(s *sql.Selector) { step := sqlgraph.NewStep( sqlgraph.From(Table, FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, AchievementSnapshotsTable, AchievementSnapshotsColumn), + sqlgraph.Edge(sqlgraph.O2M, false, AccountSnapshotsTable, AccountSnapshotsColumn), ) sqlgraph.HasNeighbors(s, step) }) } -// HasAchievementSnapshotsWith applies the HasEdge predicate on the "achievement_snapshots" edge with a given conditions (other predicates). -func HasAchievementSnapshotsWith(preds ...predicate.AchievementsSnapshot) predicate.Account { +// HasAccountSnapshotsWith applies the HasEdge predicate on the "account_snapshots" edge with a given conditions (other predicates). +func HasAccountSnapshotsWith(preds ...predicate.AccountSnapshot) predicate.Account { return predicate.Account(func(s *sql.Selector) { - step := newAchievementSnapshotsStep() + step := newAccountSnapshotsStep() sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { for _, p := range preds { p(s) diff --git a/internal/database/ent/db/account_create.go b/internal/database/ent/db/account_create.go index c54cc2b3..9ed86957 100644 --- a/internal/database/ent/db/account_create.go +++ b/internal/database/ent/db/account_create.go @@ -115,19 +115,19 @@ func (ac *AccountCreate) SetClan(c *Clan) *AccountCreate { return ac.SetClanID(c.ID) } -// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by IDs. -func (ac *AccountCreate) AddAccountSnapshotIDs(ids ...string) *AccountCreate { - ac.mutation.AddAccountSnapshotIDs(ids...) +// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. +func (ac *AccountCreate) AddAchievementSnapshotIDs(ids ...string) *AccountCreate { + ac.mutation.AddAchievementSnapshotIDs(ids...) return ac } -// AddAccountSnapshots adds the "account_snapshots" edges to the AccountSnapshot entity. -func (ac *AccountCreate) AddAccountSnapshots(a ...*AccountSnapshot) *AccountCreate { +// AddAchievementSnapshots adds the "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (ac *AccountCreate) AddAchievementSnapshots(a ...*AchievementsSnapshot) *AccountCreate { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return ac.AddAccountSnapshotIDs(ids...) + return ac.AddAchievementSnapshotIDs(ids...) } // AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. @@ -145,19 +145,19 @@ func (ac *AccountCreate) AddVehicleSnapshots(v ...*VehicleSnapshot) *AccountCrea return ac.AddVehicleSnapshotIDs(ids...) } -// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. -func (ac *AccountCreate) AddAchievementSnapshotIDs(ids ...string) *AccountCreate { - ac.mutation.AddAchievementSnapshotIDs(ids...) +// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by IDs. +func (ac *AccountCreate) AddAccountSnapshotIDs(ids ...string) *AccountCreate { + ac.mutation.AddAccountSnapshotIDs(ids...) return ac } -// AddAchievementSnapshots adds the "achievement_snapshots" edges to the AchievementsSnapshot entity. -func (ac *AccountCreate) AddAchievementSnapshots(a ...*AchievementsSnapshot) *AccountCreate { +// AddAccountSnapshots adds the "account_snapshots" edges to the AccountSnapshot entity. +func (ac *AccountCreate) AddAccountSnapshots(a ...*AccountSnapshot) *AccountCreate { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return ac.AddAchievementSnapshotIDs(ids...) + return ac.AddAccountSnapshotIDs(ids...) } // Mutation returns the AccountMutation object of the builder. @@ -322,15 +322,15 @@ func (ac *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) { _node.ClanID = nodes[0] _spec.Edges = append(_spec.Edges, edge) } - if nodes := ac.mutation.AccountSnapshotsIDs(); len(nodes) > 0 { + if nodes := ac.mutation.AchievementSnapshotsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AccountSnapshotsTable, - Columns: []string{account.AccountSnapshotsColumn}, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { @@ -354,15 +354,15 @@ func (ac *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } - if nodes := ac.mutation.AchievementSnapshotsIDs(); len(nodes) > 0 { + if nodes := ac.mutation.AccountSnapshotsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AchievementSnapshotsTable, - Columns: []string{account.AchievementSnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { diff --git a/internal/database/ent/db/account_query.go b/internal/database/ent/db/account_query.go index d3dc8f75..38604e4f 100644 --- a/internal/database/ent/db/account_query.go +++ b/internal/database/ent/db/account_query.go @@ -27,9 +27,9 @@ type AccountQuery struct { inters []Interceptor predicates []predicate.Account withClan *ClanQuery - withAccountSnapshots *AccountSnapshotQuery - withVehicleSnapshots *VehicleSnapshotQuery withAchievementSnapshots *AchievementsSnapshotQuery + withVehicleSnapshots *VehicleSnapshotQuery + withAccountSnapshots *AccountSnapshotQuery modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector @@ -89,9 +89,9 @@ func (aq *AccountQuery) QueryClan() *ClanQuery { return query } -// QueryAccountSnapshots chains the current query on the "account_snapshots" edge. -func (aq *AccountQuery) QueryAccountSnapshots() *AccountSnapshotQuery { - query := (&AccountSnapshotClient{config: aq.config}).Query() +// QueryAchievementSnapshots chains the current query on the "achievement_snapshots" edge. +func (aq *AccountQuery) QueryAchievementSnapshots() *AchievementsSnapshotQuery { + query := (&AchievementsSnapshotClient{config: aq.config}).Query() query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { if err := aq.prepareQuery(ctx); err != nil { return nil, err @@ -102,8 +102,8 @@ func (aq *AccountQuery) QueryAccountSnapshots() *AccountSnapshotQuery { } step := sqlgraph.NewStep( sqlgraph.From(account.Table, account.FieldID, selector), - sqlgraph.To(accountsnapshot.Table, accountsnapshot.FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, account.AccountSnapshotsTable, account.AccountSnapshotsColumn), + sqlgraph.To(achievementssnapshot.Table, achievementssnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.AchievementSnapshotsTable, account.AchievementSnapshotsColumn), ) fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) return fromU, nil @@ -133,9 +133,9 @@ func (aq *AccountQuery) QueryVehicleSnapshots() *VehicleSnapshotQuery { return query } -// QueryAchievementSnapshots chains the current query on the "achievement_snapshots" edge. -func (aq *AccountQuery) QueryAchievementSnapshots() *AchievementsSnapshotQuery { - query := (&AchievementsSnapshotClient{config: aq.config}).Query() +// QueryAccountSnapshots chains the current query on the "account_snapshots" edge. +func (aq *AccountQuery) QueryAccountSnapshots() *AccountSnapshotQuery { + query := (&AccountSnapshotClient{config: aq.config}).Query() query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { if err := aq.prepareQuery(ctx); err != nil { return nil, err @@ -146,8 +146,8 @@ func (aq *AccountQuery) QueryAchievementSnapshots() *AchievementsSnapshotQuery { } step := sqlgraph.NewStep( sqlgraph.From(account.Table, account.FieldID, selector), - sqlgraph.To(achievementssnapshot.Table, achievementssnapshot.FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, account.AchievementSnapshotsTable, account.AchievementSnapshotsColumn), + sqlgraph.To(accountsnapshot.Table, accountsnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.AccountSnapshotsTable, account.AccountSnapshotsColumn), ) fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step) return fromU, nil @@ -348,9 +348,9 @@ func (aq *AccountQuery) Clone() *AccountQuery { inters: append([]Interceptor{}, aq.inters...), predicates: append([]predicate.Account{}, aq.predicates...), withClan: aq.withClan.Clone(), - withAccountSnapshots: aq.withAccountSnapshots.Clone(), - withVehicleSnapshots: aq.withVehicleSnapshots.Clone(), withAchievementSnapshots: aq.withAchievementSnapshots.Clone(), + withVehicleSnapshots: aq.withVehicleSnapshots.Clone(), + withAccountSnapshots: aq.withAccountSnapshots.Clone(), // clone intermediate query. sql: aq.sql.Clone(), path: aq.path, @@ -368,14 +368,14 @@ func (aq *AccountQuery) WithClan(opts ...func(*ClanQuery)) *AccountQuery { return aq } -// WithAccountSnapshots tells the query-builder to eager-load the nodes that are connected to -// the "account_snapshots" edge. The optional arguments are used to configure the query builder of the edge. -func (aq *AccountQuery) WithAccountSnapshots(opts ...func(*AccountSnapshotQuery)) *AccountQuery { - query := (&AccountSnapshotClient{config: aq.config}).Query() +// WithAchievementSnapshots tells the query-builder to eager-load the nodes that are connected to +// the "achievement_snapshots" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AccountQuery) WithAchievementSnapshots(opts ...func(*AchievementsSnapshotQuery)) *AccountQuery { + query := (&AchievementsSnapshotClient{config: aq.config}).Query() for _, opt := range opts { opt(query) } - aq.withAccountSnapshots = query + aq.withAchievementSnapshots = query return aq } @@ -390,14 +390,14 @@ func (aq *AccountQuery) WithVehicleSnapshots(opts ...func(*VehicleSnapshotQuery) return aq } -// WithAchievementSnapshots tells the query-builder to eager-load the nodes that are connected to -// the "achievement_snapshots" edge. The optional arguments are used to configure the query builder of the edge. -func (aq *AccountQuery) WithAchievementSnapshots(opts ...func(*AchievementsSnapshotQuery)) *AccountQuery { - query := (&AchievementsSnapshotClient{config: aq.config}).Query() +// WithAccountSnapshots tells the query-builder to eager-load the nodes that are connected to +// the "account_snapshots" edge. The optional arguments are used to configure the query builder of the edge. +func (aq *AccountQuery) WithAccountSnapshots(opts ...func(*AccountSnapshotQuery)) *AccountQuery { + query := (&AccountSnapshotClient{config: aq.config}).Query() for _, opt := range opts { opt(query) } - aq.withAchievementSnapshots = query + aq.withAccountSnapshots = query return aq } @@ -481,9 +481,9 @@ func (aq *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco _spec = aq.querySpec() loadedTypes = [4]bool{ aq.withClan != nil, - aq.withAccountSnapshots != nil, - aq.withVehicleSnapshots != nil, aq.withAchievementSnapshots != nil, + aq.withVehicleSnapshots != nil, + aq.withAccountSnapshots != nil, } ) _spec.ScanValues = func(columns []string) ([]any, error) { @@ -513,10 +513,12 @@ func (aq *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco return nil, err } } - if query := aq.withAccountSnapshots; query != nil { - if err := aq.loadAccountSnapshots(ctx, query, nodes, - func(n *Account) { n.Edges.AccountSnapshots = []*AccountSnapshot{} }, - func(n *Account, e *AccountSnapshot) { n.Edges.AccountSnapshots = append(n.Edges.AccountSnapshots, e) }); err != nil { + if query := aq.withAchievementSnapshots; query != nil { + if err := aq.loadAchievementSnapshots(ctx, query, nodes, + func(n *Account) { n.Edges.AchievementSnapshots = []*AchievementsSnapshot{} }, + func(n *Account, e *AchievementsSnapshot) { + n.Edges.AchievementSnapshots = append(n.Edges.AchievementSnapshots, e) + }); err != nil { return nil, err } } @@ -527,12 +529,10 @@ func (aq *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco return nil, err } } - if query := aq.withAchievementSnapshots; query != nil { - if err := aq.loadAchievementSnapshots(ctx, query, nodes, - func(n *Account) { n.Edges.AchievementSnapshots = []*AchievementsSnapshot{} }, - func(n *Account, e *AchievementsSnapshot) { - n.Edges.AchievementSnapshots = append(n.Edges.AchievementSnapshots, e) - }); err != nil { + if query := aq.withAccountSnapshots; query != nil { + if err := aq.loadAccountSnapshots(ctx, query, nodes, + func(n *Account) { n.Edges.AccountSnapshots = []*AccountSnapshot{} }, + func(n *Account, e *AccountSnapshot) { n.Edges.AccountSnapshots = append(n.Edges.AccountSnapshots, e) }); err != nil { return nil, err } } @@ -568,7 +568,7 @@ func (aq *AccountQuery) loadClan(ctx context.Context, query *ClanQuery, nodes [] } return nil } -func (aq *AccountQuery) loadAccountSnapshots(ctx context.Context, query *AccountSnapshotQuery, nodes []*Account, init func(*Account), assign func(*Account, *AccountSnapshot)) error { +func (aq *AccountQuery) loadAchievementSnapshots(ctx context.Context, query *AchievementsSnapshotQuery, nodes []*Account, init func(*Account), assign func(*Account, *AchievementsSnapshot)) error { fks := make([]driver.Value, 0, len(nodes)) nodeids := make(map[string]*Account) for i := range nodes { @@ -579,10 +579,10 @@ func (aq *AccountQuery) loadAccountSnapshots(ctx context.Context, query *Account } } if len(query.ctx.Fields) > 0 { - query.ctx.AppendFieldOnce(accountsnapshot.FieldAccountID) + query.ctx.AppendFieldOnce(achievementssnapshot.FieldAccountID) } - query.Where(predicate.AccountSnapshot(func(s *sql.Selector) { - s.Where(sql.InValues(s.C(account.AccountSnapshotsColumn), fks...)) + query.Where(predicate.AchievementsSnapshot(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(account.AchievementSnapshotsColumn), fks...)) })) neighbors, err := query.All(ctx) if err != nil { @@ -628,7 +628,7 @@ func (aq *AccountQuery) loadVehicleSnapshots(ctx context.Context, query *Vehicle } return nil } -func (aq *AccountQuery) loadAchievementSnapshots(ctx context.Context, query *AchievementsSnapshotQuery, nodes []*Account, init func(*Account), assign func(*Account, *AchievementsSnapshot)) error { +func (aq *AccountQuery) loadAccountSnapshots(ctx context.Context, query *AccountSnapshotQuery, nodes []*Account, init func(*Account), assign func(*Account, *AccountSnapshot)) error { fks := make([]driver.Value, 0, len(nodes)) nodeids := make(map[string]*Account) for i := range nodes { @@ -639,10 +639,10 @@ func (aq *AccountQuery) loadAchievementSnapshots(ctx context.Context, query *Ach } } if len(query.ctx.Fields) > 0 { - query.ctx.AppendFieldOnce(achievementssnapshot.FieldAccountID) + query.ctx.AppendFieldOnce(accountsnapshot.FieldAccountID) } - query.Where(predicate.AchievementsSnapshot(func(s *sql.Selector) { - s.Where(sql.InValues(s.C(account.AchievementSnapshotsColumn), fks...)) + query.Where(predicate.AccountSnapshot(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(account.AccountSnapshotsColumn), fks...)) })) neighbors, err := query.All(ctx) if err != nil { diff --git a/internal/database/ent/db/account_update.go b/internal/database/ent/db/account_update.go index 7aca76d5..af4a3d52 100644 --- a/internal/database/ent/db/account_update.go +++ b/internal/database/ent/db/account_update.go @@ -134,19 +134,19 @@ func (au *AccountUpdate) SetClan(c *Clan) *AccountUpdate { return au.SetClanID(c.ID) } -// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by IDs. -func (au *AccountUpdate) AddAccountSnapshotIDs(ids ...string) *AccountUpdate { - au.mutation.AddAccountSnapshotIDs(ids...) +// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. +func (au *AccountUpdate) AddAchievementSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.AddAchievementSnapshotIDs(ids...) return au } -// AddAccountSnapshots adds the "account_snapshots" edges to the AccountSnapshot entity. -func (au *AccountUpdate) AddAccountSnapshots(a ...*AccountSnapshot) *AccountUpdate { +// AddAchievementSnapshots adds the "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (au *AccountUpdate) AddAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdate { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return au.AddAccountSnapshotIDs(ids...) + return au.AddAchievementSnapshotIDs(ids...) } // AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. @@ -164,19 +164,19 @@ func (au *AccountUpdate) AddVehicleSnapshots(v ...*VehicleSnapshot) *AccountUpda return au.AddVehicleSnapshotIDs(ids...) } -// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. -func (au *AccountUpdate) AddAchievementSnapshotIDs(ids ...string) *AccountUpdate { - au.mutation.AddAchievementSnapshotIDs(ids...) +// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by IDs. +func (au *AccountUpdate) AddAccountSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.AddAccountSnapshotIDs(ids...) return au } -// AddAchievementSnapshots adds the "achievement_snapshots" edges to the AchievementsSnapshot entity. -func (au *AccountUpdate) AddAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdate { +// AddAccountSnapshots adds the "account_snapshots" edges to the AccountSnapshot entity. +func (au *AccountUpdate) AddAccountSnapshots(a ...*AccountSnapshot) *AccountUpdate { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return au.AddAchievementSnapshotIDs(ids...) + return au.AddAccountSnapshotIDs(ids...) } // Mutation returns the AccountMutation object of the builder. @@ -190,25 +190,25 @@ func (au *AccountUpdate) ClearClan() *AccountUpdate { return au } -// ClearAccountSnapshots clears all "account_snapshots" edges to the AccountSnapshot entity. -func (au *AccountUpdate) ClearAccountSnapshots() *AccountUpdate { - au.mutation.ClearAccountSnapshots() +// ClearAchievementSnapshots clears all "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (au *AccountUpdate) ClearAchievementSnapshots() *AccountUpdate { + au.mutation.ClearAchievementSnapshots() return au } -// RemoveAccountSnapshotIDs removes the "account_snapshots" edge to AccountSnapshot entities by IDs. -func (au *AccountUpdate) RemoveAccountSnapshotIDs(ids ...string) *AccountUpdate { - au.mutation.RemoveAccountSnapshotIDs(ids...) +// RemoveAchievementSnapshotIDs removes the "achievement_snapshots" edge to AchievementsSnapshot entities by IDs. +func (au *AccountUpdate) RemoveAchievementSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.RemoveAchievementSnapshotIDs(ids...) return au } -// RemoveAccountSnapshots removes "account_snapshots" edges to AccountSnapshot entities. -func (au *AccountUpdate) RemoveAccountSnapshots(a ...*AccountSnapshot) *AccountUpdate { +// RemoveAchievementSnapshots removes "achievement_snapshots" edges to AchievementsSnapshot entities. +func (au *AccountUpdate) RemoveAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdate { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return au.RemoveAccountSnapshotIDs(ids...) + return au.RemoveAchievementSnapshotIDs(ids...) } // ClearVehicleSnapshots clears all "vehicle_snapshots" edges to the VehicleSnapshot entity. @@ -232,25 +232,25 @@ func (au *AccountUpdate) RemoveVehicleSnapshots(v ...*VehicleSnapshot) *AccountU return au.RemoveVehicleSnapshotIDs(ids...) } -// ClearAchievementSnapshots clears all "achievement_snapshots" edges to the AchievementsSnapshot entity. -func (au *AccountUpdate) ClearAchievementSnapshots() *AccountUpdate { - au.mutation.ClearAchievementSnapshots() +// ClearAccountSnapshots clears all "account_snapshots" edges to the AccountSnapshot entity. +func (au *AccountUpdate) ClearAccountSnapshots() *AccountUpdate { + au.mutation.ClearAccountSnapshots() return au } -// RemoveAchievementSnapshotIDs removes the "achievement_snapshots" edge to AchievementsSnapshot entities by IDs. -func (au *AccountUpdate) RemoveAchievementSnapshotIDs(ids ...string) *AccountUpdate { - au.mutation.RemoveAchievementSnapshotIDs(ids...) +// RemoveAccountSnapshotIDs removes the "account_snapshots" edge to AccountSnapshot entities by IDs. +func (au *AccountUpdate) RemoveAccountSnapshotIDs(ids ...string) *AccountUpdate { + au.mutation.RemoveAccountSnapshotIDs(ids...) return au } -// RemoveAchievementSnapshots removes "achievement_snapshots" edges to AchievementsSnapshot entities. -func (au *AccountUpdate) RemoveAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdate { +// RemoveAccountSnapshots removes "account_snapshots" edges to AccountSnapshot entities. +func (au *AccountUpdate) RemoveAccountSnapshots(a ...*AccountSnapshot) *AccountUpdate { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return au.RemoveAchievementSnapshotIDs(ids...) + return au.RemoveAccountSnapshotIDs(ids...) } // Save executes the query and returns the number of nodes affected by the update operation. @@ -369,28 +369,28 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } - if au.mutation.AccountSnapshotsCleared() { + if au.mutation.AchievementSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AccountSnapshotsTable, - Columns: []string{account.AccountSnapshotsColumn}, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), }, } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := au.mutation.RemovedAccountSnapshotsIDs(); len(nodes) > 0 && !au.mutation.AccountSnapshotsCleared() { + if nodes := au.mutation.RemovedAchievementSnapshotsIDs(); len(nodes) > 0 && !au.mutation.AchievementSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AccountSnapshotsTable, - Columns: []string{account.AccountSnapshotsColumn}, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { @@ -398,15 +398,15 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := au.mutation.AccountSnapshotsIDs(); len(nodes) > 0 { + if nodes := au.mutation.AchievementSnapshotsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AccountSnapshotsTable, - Columns: []string{account.AccountSnapshotsColumn}, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { @@ -459,28 +459,28 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } - if au.mutation.AchievementSnapshotsCleared() { + if au.mutation.AccountSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AchievementSnapshotsTable, - Columns: []string{account.AchievementSnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), }, } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := au.mutation.RemovedAchievementSnapshotsIDs(); len(nodes) > 0 && !au.mutation.AchievementSnapshotsCleared() { + if nodes := au.mutation.RemovedAccountSnapshotsIDs(); len(nodes) > 0 && !au.mutation.AccountSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AchievementSnapshotsTable, - Columns: []string{account.AchievementSnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { @@ -488,15 +488,15 @@ func (au *AccountUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := au.mutation.AchievementSnapshotsIDs(); len(nodes) > 0 { + if nodes := au.mutation.AccountSnapshotsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AchievementSnapshotsTable, - Columns: []string{account.AchievementSnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { @@ -627,19 +627,19 @@ func (auo *AccountUpdateOne) SetClan(c *Clan) *AccountUpdateOne { return auo.SetClanID(c.ID) } -// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by IDs. -func (auo *AccountUpdateOne) AddAccountSnapshotIDs(ids ...string) *AccountUpdateOne { - auo.mutation.AddAccountSnapshotIDs(ids...) +// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. +func (auo *AccountUpdateOne) AddAchievementSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.AddAchievementSnapshotIDs(ids...) return auo } -// AddAccountSnapshots adds the "account_snapshots" edges to the AccountSnapshot entity. -func (auo *AccountUpdateOne) AddAccountSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { +// AddAchievementSnapshots adds the "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (auo *AccountUpdateOne) AddAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdateOne { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return auo.AddAccountSnapshotIDs(ids...) + return auo.AddAchievementSnapshotIDs(ids...) } // AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by IDs. @@ -657,19 +657,19 @@ func (auo *AccountUpdateOne) AddVehicleSnapshots(v ...*VehicleSnapshot) *Account return auo.AddVehicleSnapshotIDs(ids...) } -// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. -func (auo *AccountUpdateOne) AddAchievementSnapshotIDs(ids ...string) *AccountUpdateOne { - auo.mutation.AddAchievementSnapshotIDs(ids...) +// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by IDs. +func (auo *AccountUpdateOne) AddAccountSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.AddAccountSnapshotIDs(ids...) return auo } -// AddAchievementSnapshots adds the "achievement_snapshots" edges to the AchievementsSnapshot entity. -func (auo *AccountUpdateOne) AddAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdateOne { +// AddAccountSnapshots adds the "account_snapshots" edges to the AccountSnapshot entity. +func (auo *AccountUpdateOne) AddAccountSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return auo.AddAchievementSnapshotIDs(ids...) + return auo.AddAccountSnapshotIDs(ids...) } // Mutation returns the AccountMutation object of the builder. @@ -683,25 +683,25 @@ func (auo *AccountUpdateOne) ClearClan() *AccountUpdateOne { return auo } -// ClearAccountSnapshots clears all "account_snapshots" edges to the AccountSnapshot entity. -func (auo *AccountUpdateOne) ClearAccountSnapshots() *AccountUpdateOne { - auo.mutation.ClearAccountSnapshots() +// ClearAchievementSnapshots clears all "achievement_snapshots" edges to the AchievementsSnapshot entity. +func (auo *AccountUpdateOne) ClearAchievementSnapshots() *AccountUpdateOne { + auo.mutation.ClearAchievementSnapshots() return auo } -// RemoveAccountSnapshotIDs removes the "account_snapshots" edge to AccountSnapshot entities by IDs. -func (auo *AccountUpdateOne) RemoveAccountSnapshotIDs(ids ...string) *AccountUpdateOne { - auo.mutation.RemoveAccountSnapshotIDs(ids...) +// RemoveAchievementSnapshotIDs removes the "achievement_snapshots" edge to AchievementsSnapshot entities by IDs. +func (auo *AccountUpdateOne) RemoveAchievementSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.RemoveAchievementSnapshotIDs(ids...) return auo } -// RemoveAccountSnapshots removes "account_snapshots" edges to AccountSnapshot entities. -func (auo *AccountUpdateOne) RemoveAccountSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { +// RemoveAchievementSnapshots removes "achievement_snapshots" edges to AchievementsSnapshot entities. +func (auo *AccountUpdateOne) RemoveAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdateOne { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return auo.RemoveAccountSnapshotIDs(ids...) + return auo.RemoveAchievementSnapshotIDs(ids...) } // ClearVehicleSnapshots clears all "vehicle_snapshots" edges to the VehicleSnapshot entity. @@ -725,25 +725,25 @@ func (auo *AccountUpdateOne) RemoveVehicleSnapshots(v ...*VehicleSnapshot) *Acco return auo.RemoveVehicleSnapshotIDs(ids...) } -// ClearAchievementSnapshots clears all "achievement_snapshots" edges to the AchievementsSnapshot entity. -func (auo *AccountUpdateOne) ClearAchievementSnapshots() *AccountUpdateOne { - auo.mutation.ClearAchievementSnapshots() +// ClearAccountSnapshots clears all "account_snapshots" edges to the AccountSnapshot entity. +func (auo *AccountUpdateOne) ClearAccountSnapshots() *AccountUpdateOne { + auo.mutation.ClearAccountSnapshots() return auo } -// RemoveAchievementSnapshotIDs removes the "achievement_snapshots" edge to AchievementsSnapshot entities by IDs. -func (auo *AccountUpdateOne) RemoveAchievementSnapshotIDs(ids ...string) *AccountUpdateOne { - auo.mutation.RemoveAchievementSnapshotIDs(ids...) +// RemoveAccountSnapshotIDs removes the "account_snapshots" edge to AccountSnapshot entities by IDs. +func (auo *AccountUpdateOne) RemoveAccountSnapshotIDs(ids ...string) *AccountUpdateOne { + auo.mutation.RemoveAccountSnapshotIDs(ids...) return auo } -// RemoveAchievementSnapshots removes "achievement_snapshots" edges to AchievementsSnapshot entities. -func (auo *AccountUpdateOne) RemoveAchievementSnapshots(a ...*AchievementsSnapshot) *AccountUpdateOne { +// RemoveAccountSnapshots removes "account_snapshots" edges to AccountSnapshot entities. +func (auo *AccountUpdateOne) RemoveAccountSnapshots(a ...*AccountSnapshot) *AccountUpdateOne { ids := make([]string, len(a)) for i := range a { ids[i] = a[i].ID } - return auo.RemoveAchievementSnapshotIDs(ids...) + return auo.RemoveAccountSnapshotIDs(ids...) } // Where appends a list predicates to the AccountUpdate builder. @@ -892,28 +892,28 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } _spec.Edges.Add = append(_spec.Edges.Add, edge) } - if auo.mutation.AccountSnapshotsCleared() { + if auo.mutation.AchievementSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AccountSnapshotsTable, - Columns: []string{account.AccountSnapshotsColumn}, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), }, } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := auo.mutation.RemovedAccountSnapshotsIDs(); len(nodes) > 0 && !auo.mutation.AccountSnapshotsCleared() { + if nodes := auo.mutation.RemovedAchievementSnapshotsIDs(); len(nodes) > 0 && !auo.mutation.AchievementSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AccountSnapshotsTable, - Columns: []string{account.AccountSnapshotsColumn}, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { @@ -921,15 +921,15 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := auo.mutation.AccountSnapshotsIDs(); len(nodes) > 0 { + if nodes := auo.mutation.AchievementSnapshotsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AccountSnapshotsTable, - Columns: []string{account.AccountSnapshotsColumn}, + Table: account.AchievementSnapshotsTable, + Columns: []string{account.AchievementSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { @@ -982,28 +982,28 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } _spec.Edges.Add = append(_spec.Edges.Add, edge) } - if auo.mutation.AchievementSnapshotsCleared() { + if auo.mutation.AccountSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AchievementSnapshotsTable, - Columns: []string{account.AchievementSnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), }, } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := auo.mutation.RemovedAchievementSnapshotsIDs(); len(nodes) > 0 && !auo.mutation.AchievementSnapshotsCleared() { + if nodes := auo.mutation.RemovedAccountSnapshotsIDs(); len(nodes) > 0 && !auo.mutation.AccountSnapshotsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AchievementSnapshotsTable, - Columns: []string{account.AchievementSnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { @@ -1011,15 +1011,15 @@ func (auo *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err e } _spec.Edges.Clear = append(_spec.Edges.Clear, edge) } - if nodes := auo.mutation.AchievementSnapshotsIDs(); len(nodes) > 0 { + if nodes := auo.mutation.AccountSnapshotsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, Inverse: false, - Table: account.AchievementSnapshotsTable, - Columns: []string{account.AchievementSnapshotsColumn}, + Table: account.AccountSnapshotsTable, + Columns: []string{account.AccountSnapshotsColumn}, Bidi: false, Target: &sqlgraph.EdgeTarget{ - IDSpec: sqlgraph.NewFieldSpec(achievementssnapshot.FieldID, field.TypeString), + IDSpec: sqlgraph.NewFieldSpec(accountsnapshot.FieldID, field.TypeString), }, } for _, k := range nodes { diff --git a/internal/database/ent/db/client.go b/internal/database/ent/db/client.go index e77ab304..2d0eb667 100644 --- a/internal/database/ent/db/client.go +++ b/internal/database/ent/db/client.go @@ -449,15 +449,15 @@ func (c *AccountClient) QueryClan(a *Account) *ClanQuery { return query } -// QueryAccountSnapshots queries the account_snapshots edge of a Account. -func (c *AccountClient) QueryAccountSnapshots(a *Account) *AccountSnapshotQuery { - query := (&AccountSnapshotClient{config: c.config}).Query() +// QueryAchievementSnapshots queries the achievement_snapshots edge of a Account. +func (c *AccountClient) QueryAchievementSnapshots(a *Account) *AchievementsSnapshotQuery { + query := (&AchievementsSnapshotClient{config: c.config}).Query() query.path = func(context.Context) (fromV *sql.Selector, _ error) { id := a.ID step := sqlgraph.NewStep( sqlgraph.From(account.Table, account.FieldID, id), - sqlgraph.To(accountsnapshot.Table, accountsnapshot.FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, account.AccountSnapshotsTable, account.AccountSnapshotsColumn), + sqlgraph.To(achievementssnapshot.Table, achievementssnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.AchievementSnapshotsTable, account.AchievementSnapshotsColumn), ) fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) return fromV, nil @@ -481,15 +481,15 @@ func (c *AccountClient) QueryVehicleSnapshots(a *Account) *VehicleSnapshotQuery return query } -// QueryAchievementSnapshots queries the achievement_snapshots edge of a Account. -func (c *AccountClient) QueryAchievementSnapshots(a *Account) *AchievementsSnapshotQuery { - query := (&AchievementsSnapshotClient{config: c.config}).Query() +// QueryAccountSnapshots queries the account_snapshots edge of a Account. +func (c *AccountClient) QueryAccountSnapshots(a *Account) *AccountSnapshotQuery { + query := (&AccountSnapshotClient{config: c.config}).Query() query.path = func(context.Context) (fromV *sql.Selector, _ error) { id := a.ID step := sqlgraph.NewStep( sqlgraph.From(account.Table, account.FieldID, id), - sqlgraph.To(achievementssnapshot.Table, achievementssnapshot.FieldID), - sqlgraph.Edge(sqlgraph.O2M, false, account.AchievementSnapshotsTable, account.AchievementSnapshotsColumn), + sqlgraph.To(accountsnapshot.Table, accountsnapshot.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, account.AccountSnapshotsTable, account.AccountSnapshotsColumn), ) fromV = sqlgraph.Neighbors(a.driver.Dialect(), step) return fromV, nil diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go index 23952010..15855c1d 100644 --- a/internal/database/ent/db/mutation.go +++ b/internal/database/ent/db/mutation.go @@ -74,15 +74,15 @@ type AccountMutation struct { clearedFields map[string]struct{} clan *string clearedclan bool - account_snapshots map[string]struct{} - removedaccount_snapshots map[string]struct{} - clearedaccount_snapshots bool - vehicle_snapshots map[string]struct{} - removedvehicle_snapshots map[string]struct{} - clearedvehicle_snapshots bool achievement_snapshots map[string]struct{} removedachievement_snapshots map[string]struct{} clearedachievement_snapshots bool + vehicle_snapshots map[string]struct{} + removedvehicle_snapshots map[string]struct{} + clearedvehicle_snapshots bool + account_snapshots map[string]struct{} + removedaccount_snapshots map[string]struct{} + clearedaccount_snapshots bool done bool oldValue func(context.Context) (*Account, error) predicates []predicate.Account @@ -520,58 +520,58 @@ func (m *AccountMutation) ResetClan() { m.clearedclan = false } -// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by ids. -func (m *AccountMutation) AddAccountSnapshotIDs(ids ...string) { - if m.account_snapshots == nil { - m.account_snapshots = make(map[string]struct{}) +// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by ids. +func (m *AccountMutation) AddAchievementSnapshotIDs(ids ...string) { + if m.achievement_snapshots == nil { + m.achievement_snapshots = make(map[string]struct{}) } for i := range ids { - m.account_snapshots[ids[i]] = struct{}{} + m.achievement_snapshots[ids[i]] = struct{}{} } } -// ClearAccountSnapshots clears the "account_snapshots" edge to the AccountSnapshot entity. -func (m *AccountMutation) ClearAccountSnapshots() { - m.clearedaccount_snapshots = true +// ClearAchievementSnapshots clears the "achievement_snapshots" edge to the AchievementsSnapshot entity. +func (m *AccountMutation) ClearAchievementSnapshots() { + m.clearedachievement_snapshots = true } -// AccountSnapshotsCleared reports if the "account_snapshots" edge to the AccountSnapshot entity was cleared. -func (m *AccountMutation) AccountSnapshotsCleared() bool { - return m.clearedaccount_snapshots +// AchievementSnapshotsCleared reports if the "achievement_snapshots" edge to the AchievementsSnapshot entity was cleared. +func (m *AccountMutation) AchievementSnapshotsCleared() bool { + return m.clearedachievement_snapshots } -// RemoveAccountSnapshotIDs removes the "account_snapshots" edge to the AccountSnapshot entity by IDs. -func (m *AccountMutation) RemoveAccountSnapshotIDs(ids ...string) { - if m.removedaccount_snapshots == nil { - m.removedaccount_snapshots = make(map[string]struct{}) +// RemoveAchievementSnapshotIDs removes the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. +func (m *AccountMutation) RemoveAchievementSnapshotIDs(ids ...string) { + if m.removedachievement_snapshots == nil { + m.removedachievement_snapshots = make(map[string]struct{}) } for i := range ids { - delete(m.account_snapshots, ids[i]) - m.removedaccount_snapshots[ids[i]] = struct{}{} + delete(m.achievement_snapshots, ids[i]) + m.removedachievement_snapshots[ids[i]] = struct{}{} } } -// RemovedAccountSnapshots returns the removed IDs of the "account_snapshots" edge to the AccountSnapshot entity. -func (m *AccountMutation) RemovedAccountSnapshotsIDs() (ids []string) { - for id := range m.removedaccount_snapshots { +// RemovedAchievementSnapshots returns the removed IDs of the "achievement_snapshots" edge to the AchievementsSnapshot entity. +func (m *AccountMutation) RemovedAchievementSnapshotsIDs() (ids []string) { + for id := range m.removedachievement_snapshots { ids = append(ids, id) } return } -// AccountSnapshotsIDs returns the "account_snapshots" edge IDs in the mutation. -func (m *AccountMutation) AccountSnapshotsIDs() (ids []string) { - for id := range m.account_snapshots { +// AchievementSnapshotsIDs returns the "achievement_snapshots" edge IDs in the mutation. +func (m *AccountMutation) AchievementSnapshotsIDs() (ids []string) { + for id := range m.achievement_snapshots { ids = append(ids, id) } return } -// ResetAccountSnapshots resets all changes to the "account_snapshots" edge. -func (m *AccountMutation) ResetAccountSnapshots() { - m.account_snapshots = nil - m.clearedaccount_snapshots = false - m.removedaccount_snapshots = nil +// ResetAchievementSnapshots resets all changes to the "achievement_snapshots" edge. +func (m *AccountMutation) ResetAchievementSnapshots() { + m.achievement_snapshots = nil + m.clearedachievement_snapshots = false + m.removedachievement_snapshots = nil } // AddVehicleSnapshotIDs adds the "vehicle_snapshots" edge to the VehicleSnapshot entity by ids. @@ -628,58 +628,58 @@ func (m *AccountMutation) ResetVehicleSnapshots() { m.removedvehicle_snapshots = nil } -// AddAchievementSnapshotIDs adds the "achievement_snapshots" edge to the AchievementsSnapshot entity by ids. -func (m *AccountMutation) AddAchievementSnapshotIDs(ids ...string) { - if m.achievement_snapshots == nil { - m.achievement_snapshots = make(map[string]struct{}) +// AddAccountSnapshotIDs adds the "account_snapshots" edge to the AccountSnapshot entity by ids. +func (m *AccountMutation) AddAccountSnapshotIDs(ids ...string) { + if m.account_snapshots == nil { + m.account_snapshots = make(map[string]struct{}) } for i := range ids { - m.achievement_snapshots[ids[i]] = struct{}{} + m.account_snapshots[ids[i]] = struct{}{} } } -// ClearAchievementSnapshots clears the "achievement_snapshots" edge to the AchievementsSnapshot entity. -func (m *AccountMutation) ClearAchievementSnapshots() { - m.clearedachievement_snapshots = true +// ClearAccountSnapshots clears the "account_snapshots" edge to the AccountSnapshot entity. +func (m *AccountMutation) ClearAccountSnapshots() { + m.clearedaccount_snapshots = true } -// AchievementSnapshotsCleared reports if the "achievement_snapshots" edge to the AchievementsSnapshot entity was cleared. -func (m *AccountMutation) AchievementSnapshotsCleared() bool { - return m.clearedachievement_snapshots +// AccountSnapshotsCleared reports if the "account_snapshots" edge to the AccountSnapshot entity was cleared. +func (m *AccountMutation) AccountSnapshotsCleared() bool { + return m.clearedaccount_snapshots } -// RemoveAchievementSnapshotIDs removes the "achievement_snapshots" edge to the AchievementsSnapshot entity by IDs. -func (m *AccountMutation) RemoveAchievementSnapshotIDs(ids ...string) { - if m.removedachievement_snapshots == nil { - m.removedachievement_snapshots = make(map[string]struct{}) +// RemoveAccountSnapshotIDs removes the "account_snapshots" edge to the AccountSnapshot entity by IDs. +func (m *AccountMutation) RemoveAccountSnapshotIDs(ids ...string) { + if m.removedaccount_snapshots == nil { + m.removedaccount_snapshots = make(map[string]struct{}) } for i := range ids { - delete(m.achievement_snapshots, ids[i]) - m.removedachievement_snapshots[ids[i]] = struct{}{} + delete(m.account_snapshots, ids[i]) + m.removedaccount_snapshots[ids[i]] = struct{}{} } } -// RemovedAchievementSnapshots returns the removed IDs of the "achievement_snapshots" edge to the AchievementsSnapshot entity. -func (m *AccountMutation) RemovedAchievementSnapshotsIDs() (ids []string) { - for id := range m.removedachievement_snapshots { +// RemovedAccountSnapshots returns the removed IDs of the "account_snapshots" edge to the AccountSnapshot entity. +func (m *AccountMutation) RemovedAccountSnapshotsIDs() (ids []string) { + for id := range m.removedaccount_snapshots { ids = append(ids, id) } return } -// AchievementSnapshotsIDs returns the "achievement_snapshots" edge IDs in the mutation. -func (m *AccountMutation) AchievementSnapshotsIDs() (ids []string) { - for id := range m.achievement_snapshots { +// AccountSnapshotsIDs returns the "account_snapshots" edge IDs in the mutation. +func (m *AccountMutation) AccountSnapshotsIDs() (ids []string) { + for id := range m.account_snapshots { ids = append(ids, id) } return } -// ResetAchievementSnapshots resets all changes to the "achievement_snapshots" edge. -func (m *AccountMutation) ResetAchievementSnapshots() { - m.achievement_snapshots = nil - m.clearedachievement_snapshots = false - m.removedachievement_snapshots = nil +// ResetAccountSnapshots resets all changes to the "account_snapshots" edge. +func (m *AccountMutation) ResetAccountSnapshots() { + m.account_snapshots = nil + m.clearedaccount_snapshots = false + m.removedaccount_snapshots = nil } // Where appends a list predicates to the AccountMutation builder. @@ -947,14 +947,14 @@ func (m *AccountMutation) AddedEdges() []string { if m.clan != nil { edges = append(edges, account.EdgeClan) } - if m.account_snapshots != nil { - edges = append(edges, account.EdgeAccountSnapshots) + if m.achievement_snapshots != nil { + edges = append(edges, account.EdgeAchievementSnapshots) } if m.vehicle_snapshots != nil { edges = append(edges, account.EdgeVehicleSnapshots) } - if m.achievement_snapshots != nil { - edges = append(edges, account.EdgeAchievementSnapshots) + if m.account_snapshots != nil { + edges = append(edges, account.EdgeAccountSnapshots) } return edges } @@ -967,9 +967,9 @@ func (m *AccountMutation) AddedIDs(name string) []ent.Value { if id := m.clan; id != nil { return []ent.Value{*id} } - case account.EdgeAccountSnapshots: - ids := make([]ent.Value, 0, len(m.account_snapshots)) - for id := range m.account_snapshots { + case account.EdgeAchievementSnapshots: + ids := make([]ent.Value, 0, len(m.achievement_snapshots)) + for id := range m.achievement_snapshots { ids = append(ids, id) } return ids @@ -979,9 +979,9 @@ func (m *AccountMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids - case account.EdgeAchievementSnapshots: - ids := make([]ent.Value, 0, len(m.achievement_snapshots)) - for id := range m.achievement_snapshots { + case account.EdgeAccountSnapshots: + ids := make([]ent.Value, 0, len(m.account_snapshots)) + for id := range m.account_snapshots { ids = append(ids, id) } return ids @@ -992,14 +992,14 @@ func (m *AccountMutation) AddedIDs(name string) []ent.Value { // RemovedEdges returns all edge names that were removed in this mutation. func (m *AccountMutation) RemovedEdges() []string { edges := make([]string, 0, 4) - if m.removedaccount_snapshots != nil { - edges = append(edges, account.EdgeAccountSnapshots) + if m.removedachievement_snapshots != nil { + edges = append(edges, account.EdgeAchievementSnapshots) } if m.removedvehicle_snapshots != nil { edges = append(edges, account.EdgeVehicleSnapshots) } - if m.removedachievement_snapshots != nil { - edges = append(edges, account.EdgeAchievementSnapshots) + if m.removedaccount_snapshots != nil { + edges = append(edges, account.EdgeAccountSnapshots) } return edges } @@ -1008,9 +1008,9 @@ func (m *AccountMutation) RemovedEdges() []string { // the given name in this mutation. func (m *AccountMutation) RemovedIDs(name string) []ent.Value { switch name { - case account.EdgeAccountSnapshots: - ids := make([]ent.Value, 0, len(m.removedaccount_snapshots)) - for id := range m.removedaccount_snapshots { + case account.EdgeAchievementSnapshots: + ids := make([]ent.Value, 0, len(m.removedachievement_snapshots)) + for id := range m.removedachievement_snapshots { ids = append(ids, id) } return ids @@ -1020,9 +1020,9 @@ func (m *AccountMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids - case account.EdgeAchievementSnapshots: - ids := make([]ent.Value, 0, len(m.removedachievement_snapshots)) - for id := range m.removedachievement_snapshots { + case account.EdgeAccountSnapshots: + ids := make([]ent.Value, 0, len(m.removedaccount_snapshots)) + for id := range m.removedaccount_snapshots { ids = append(ids, id) } return ids @@ -1036,14 +1036,14 @@ func (m *AccountMutation) ClearedEdges() []string { if m.clearedclan { edges = append(edges, account.EdgeClan) } - if m.clearedaccount_snapshots { - edges = append(edges, account.EdgeAccountSnapshots) + if m.clearedachievement_snapshots { + edges = append(edges, account.EdgeAchievementSnapshots) } if m.clearedvehicle_snapshots { edges = append(edges, account.EdgeVehicleSnapshots) } - if m.clearedachievement_snapshots { - edges = append(edges, account.EdgeAchievementSnapshots) + if m.clearedaccount_snapshots { + edges = append(edges, account.EdgeAccountSnapshots) } return edges } @@ -1054,12 +1054,12 @@ func (m *AccountMutation) EdgeCleared(name string) bool { switch name { case account.EdgeClan: return m.clearedclan - case account.EdgeAccountSnapshots: - return m.clearedaccount_snapshots - case account.EdgeVehicleSnapshots: - return m.clearedvehicle_snapshots case account.EdgeAchievementSnapshots: return m.clearedachievement_snapshots + case account.EdgeVehicleSnapshots: + return m.clearedvehicle_snapshots + case account.EdgeAccountSnapshots: + return m.clearedaccount_snapshots } return false } @@ -1082,14 +1082,14 @@ func (m *AccountMutation) ResetEdge(name string) error { case account.EdgeClan: m.ResetClan() return nil - case account.EdgeAccountSnapshots: - m.ResetAccountSnapshots() + case account.EdgeAchievementSnapshots: + m.ResetAchievementSnapshots() return nil case account.EdgeVehicleSnapshots: m.ResetVehicleSnapshots() return nil - case account.EdgeAchievementSnapshots: - m.ResetAchievementSnapshots() + case account.EdgeAccountSnapshots: + m.ResetAccountSnapshots() return nil } return fmt.Errorf("unknown Account edge %s", name) From d9beec8a72d58199dd6f2364caaff4cb43f17b65 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 2 Jul 2024 20:57:56 -0400 Subject: [PATCH 242/341] removed old comment --- main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main.go b/main.go index aad48955..db951222 100644 --- a/main.go +++ b/main.go @@ -39,7 +39,6 @@ import ( "net/http" "net/http/pprof" - // "github.com/pkg/profile" ) //go:generate go generate ./internal/database/ent From 2f7eb20de129a497645780b51724b740626ba726 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 2 Jul 2024 21:00:15 -0400 Subject: [PATCH 243/341] always passing background --- internal/stats/client/v1/options.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/stats/client/v1/options.go b/internal/stats/client/v1/options.go index 26cdbb4f..363d2074 100644 --- a/internal/stats/client/v1/options.go +++ b/internal/stats/client/v1/options.go @@ -37,9 +37,7 @@ func (o requestOptions) RenderOpts() []common.Option { if o.promoText != nil { copts = append(copts, common.WithPromoText(o.promoText...)) } - if o.backgroundURL != "" { - copts = append(copts, common.WithBackground(o.backgroundURL)) - } + copts = append(copts, common.WithBackground(o.backgroundURL)) return copts } From c74c2e01d494d95ab71f2e12ef356bc9532e9b29 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 2 Jul 2024 21:03:27 -0400 Subject: [PATCH 244/341] added back WN8 --- cmd/discord/commands/interactions.go | 4 ++-- cmd/discord/commands/session.go | 2 +- cmd/discord/commands/stats.go | 2 +- cmd/frontend/routes/widget/configure.templ | 3 ++- cmd/frontend/routes/widget/configure_templ.go | 5 +++-- cmd/frontend/routes/widget/live.templ | 2 +- cmd/frontend/routes/widget/live_templ.go | 2 +- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cmd/discord/commands/interactions.go b/cmd/discord/commands/interactions.go index a06720ae..4c52ebca 100644 --- a/cmd/discord/commands/interactions.go +++ b/cmd/discord/commands/interactions.go @@ -57,7 +57,7 @@ func init() { var meta stats.Metadata switch interaction.Command { case "stats": - img, mt, err := ctx.Core.Stats(ctx.Locale).PeriodImage(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, stats.WithBackgroundURL(interaction.Options.BackgroundImageURL)) + img, mt, err := ctx.Core.Stats(ctx.Locale).PeriodImage(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, stats.WithBackgroundURL(interaction.Options.BackgroundImageURL), stats.WithWN8()) if err != nil { return ctx.Err(err) } @@ -65,7 +65,7 @@ func init() { meta = mt case "session": - img, mt, err := ctx.Core.Stats(ctx.Locale).SessionImage(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, stats.WithBackgroundURL(interaction.Options.BackgroundImageURL)) + img, mt, err := ctx.Core.Stats(ctx.Locale).SessionImage(context.Background(), interaction.Options.AccountID, interaction.Options.PeriodStart, stats.WithBackgroundURL(interaction.Options.BackgroundImageURL), stats.WithWN8()) if err != nil { if errors.Is(err, fetch.ErrSessionNotFound) || errors.Is(err, stats.ErrAccountNotTracked) { return ctx.Reply().Send("stats_refresh_interaction_error_expired") diff --git a/cmd/discord/commands/session.go b/cmd/discord/commands/session.go index 23a709db..3d04e0b7 100644 --- a/cmd/discord/commands/session.go +++ b/cmd/discord/commands/session.go @@ -69,7 +69,7 @@ func init() { } } - image, meta, err := ctx.Core.Stats(ctx.Locale).SessionImage(context.Background(), accountID, options.PeriodStart, stats.WithBackgroundURL(backgroundURL)) + image, meta, err := ctx.Core.Stats(ctx.Locale).SessionImage(context.Background(), accountID, options.PeriodStart, stats.WithBackgroundURL(backgroundURL), stats.WithWN8()) if err != nil { if errors.Is(err, stats.ErrAccountNotTracked) || (errors.Is(err, fetch.ErrSessionNotFound) && options.Days < 1) { return ctx.Reply().Send("session_error_account_was_not_tracked") diff --git a/cmd/discord/commands/stats.go b/cmd/discord/commands/stats.go index 3483ea2c..b1a62021 100644 --- a/cmd/discord/commands/stats.go +++ b/cmd/discord/commands/stats.go @@ -61,7 +61,7 @@ func init() { // TODO: Get user background } - image, meta, err := ctx.Core.Stats(ctx.Locale).PeriodImage(context.Background(), accountID, options.PeriodStart, stats.WithBackgroundURL(backgroundURL)) + image, meta, err := ctx.Core.Stats(ctx.Locale).PeriodImage(context.Background(), accountID, options.PeriodStart, stats.WithBackgroundURL(backgroundURL), stats.WithWN8()) if err != nil { return ctx.Err(err) } diff --git a/cmd/frontend/routes/widget/configure.templ b/cmd/frontend/routes/widget/configure.templ index 0019d498..9f88403b 100644 --- a/cmd/frontend/routes/widget/configure.templ +++ b/cmd/frontend/routes/widget/configure.templ @@ -3,6 +3,7 @@ package widget import "github.com/cufee/aftermath/cmd/frontend/handler" import "github.com/cufee/aftermath/cmd/frontend/layouts" import "github.com/cufee/aftermath/cmd/frontend/components/widget" +import "github.com/cufee/aftermath/internal/stats/client/v1" import "golang.org/x/text/language" import "time" import "github.com/pkg/errors" @@ -19,7 +20,7 @@ var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, t return nil, nil, errors.New("invalid account id") } - cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now()) + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), client.WithWN8()) if err != nil { return nil, nil, err } diff --git a/cmd/frontend/routes/widget/configure_templ.go b/cmd/frontend/routes/widget/configure_templ.go index 15b2e371..b353aef5 100644 --- a/cmd/frontend/routes/widget/configure_templ.go +++ b/cmd/frontend/routes/widget/configure_templ.go @@ -13,6 +13,7 @@ import "bytes" import "github.com/cufee/aftermath/cmd/frontend/handler" import "github.com/cufee/aftermath/cmd/frontend/layouts" import "github.com/cufee/aftermath/cmd/frontend/components/widget" +import "github.com/cufee/aftermath/internal/stats/client/v1" import "golang.org/x/text/language" import "time" import "github.com/pkg/errors" @@ -29,7 +30,7 @@ var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, t return nil, nil, errors.New("invalid account id") } - cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now()) + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), client.WithWN8()) if err != nil { return nil, nil, err } @@ -62,7 +63,7 @@ func configureWidgetPage(widget templ.Component) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(i)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/widget/configure.templ`, Line: 47, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/widget/configure.templ`, Line: 48, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { diff --git a/cmd/frontend/routes/widget/live.templ b/cmd/frontend/routes/widget/live.templ index 34e4f295..e8a3f723 100644 --- a/cmd/frontend/routes/widget/live.templ +++ b/cmd/frontend/routes/widget/live.templ @@ -21,7 +21,7 @@ var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ. return nil, nil, errors.New("invalid account id") } - var opts []client.RequestOption + var opts = []client.RequestOption{client.WithWN8()} if ref := ctx.Req().URL.Query().Get("ref"); ref != "" { opts = append(opts, client.WithReferenceID(ref)) } diff --git a/cmd/frontend/routes/widget/live_templ.go b/cmd/frontend/routes/widget/live_templ.go index 1b66362b..994c9045 100644 --- a/cmd/frontend/routes/widget/live_templ.go +++ b/cmd/frontend/routes/widget/live_templ.go @@ -31,7 +31,7 @@ var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ. return nil, nil, errors.New("invalid account id") } - var opts []client.RequestOption + var opts = []client.RequestOption{client.WithWN8()} if ref := ctx.Req().URL.Query().Get("ref"); ref != "" { opts = append(opts, client.WithReferenceID(ref)) } From 967a767017c0bae7eece12d7dba6f74d4a1aef27 Mon Sep 17 00:00:00 2001 From: Vovko Date: Tue, 2 Jul 2024 21:04:14 -0400 Subject: [PATCH 245/341] unused write --- internal/stats/render/session/v1/blocks.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/stats/render/session/v1/blocks.go b/internal/stats/render/session/v1/blocks.go index 64ff1315..0322c06c 100644 --- a/internal/stats/render/session/v1/blocks.go +++ b/internal/stats/render/session/v1/blocks.go @@ -65,7 +65,6 @@ func blankBlock(width, height float64) common.Block { func vehicleWN8Icon(wn8 frame.Value) common.Block { ratingColors := common.GetWN8Colors(wn8.Float()) if wn8.Float() <= 0 { - ratingColors.Content = common.TextAlt ratingColors.Background = common.TextAlt } iconTop := common.AftermathLogo(ratingColors.Background, common.SmallLogoOptions()) From ddeb4de4778f089cf6d41ff17c34a8923f33171a Mon Sep 17 00:00:00 2001 From: Vovko Date: Wed, 3 Jul 2024 15:27:18 -0400 Subject: [PATCH 246/341] added discord auth --- .air.toml | 4 + .air.web.toml | 6 +- .env.example | 5 +- cmd/frontend/handler/context.go | 134 +- cmd/frontend/handler/errors.go | 7 + cmd/frontend/handlers.go | 29 +- cmd/frontend/logic/auth/contants.go | 20 + cmd/frontend/logic/auth/cookie.go | 34 + cmd/frontend/logic/discord/oauth.go | 107 + cmd/frontend/logic/discord/user.go | 44 + cmd/frontend/middleware/auth.go | 21 + cmd/frontend/routes/api/auth/discord.go | 91 + cmd/frontend/routes/app/index.templ | 21 + cmd/frontend/routes/app/index_templ.go | 61 + cmd/frontend/routes/errors.templ | 4 +- cmd/frontend/routes/errors_templ.go | 4 +- cmd/frontend/routes/login.go | 36 + cmd/frontend/routes/widget/configure.templ | 2 +- cmd/frontend/routes/widget/configure_templ.go | 2 +- cmd/frontend/routes/widget/live.templ | 6 +- cmd/frontend/routes/widget/live_templ.go | 6 +- go.sum | 10 + internal/database/auth.go | 110 + internal/database/client.go | 12 + internal/database/ent/db/authnonce.go | 177 + .../database/ent/db/authnonce/authnonce.go | 107 + internal/database/ent/db/authnonce/where.go | 370 ++ internal/database/ent/db/authnonce_create.go | 325 ++ internal/database/ent/db/authnonce_delete.go | 88 + internal/database/ent/db/authnonce_query.go | 548 +++ internal/database/ent/db/authnonce_update.go | 280 ++ internal/database/ent/db/client.go | 334 +- internal/database/ent/db/ent.go | 4 + internal/database/ent/db/expose.go | 26 + internal/database/ent/db/hook/hook.go | 24 + internal/database/ent/db/migrate/schema.go | 78 +- internal/database/ent/db/mutation.go | 3563 ++++++++++++----- .../database/ent/db/predicate/predicate.go | 6 + internal/database/ent/db/runtime.go | 52 +- internal/database/ent/db/session.go | 193 + internal/database/ent/db/session/session.go | 121 + internal/database/ent/db/session/where.go | 379 ++ internal/database/ent/db/session_create.go | 329 ++ internal/database/ent/db/session_delete.go | 88 + internal/database/ent/db/session_query.go | 627 +++ internal/database/ent/db/session_update.go | 302 ++ internal/database/ent/db/tx.go | 6 + internal/database/ent/db/user.go | 31 +- internal/database/ent/db/user/user.go | 40 + internal/database/ent/db/user/where.go | 93 + internal/database/ent/db/user_create.go | 57 + internal/database/ent/db/user_query.go | 76 +- internal/database/ent/db/user_update.go | 197 + .../ent/migrations/20240703151859.sql | 22 + .../ent/migrations/20240703160454.sql | 6 + .../ent/migrations/20240703160711.sql | 142 + .../ent/migrations/20240703180358.sql | 4 + internal/database/ent/migrations/atlas.sum | 6 +- internal/database/ent/schema/account.go | 7 +- internal/database/ent/schema/nonce.go | 31 + internal/database/ent/schema/session.go | 33 + internal/database/ent/schema/user.go | 12 +- internal/database/models/nonce.go | 34 + internal/database/models/session.go | 35 + internal/logic/strings.go | 15 + tests/static_database.go | 23 + 66 files changed, 8543 insertions(+), 1124 deletions(-) create mode 100644 cmd/frontend/handler/errors.go create mode 100644 cmd/frontend/logic/auth/contants.go create mode 100644 cmd/frontend/logic/auth/cookie.go create mode 100644 cmd/frontend/logic/discord/oauth.go create mode 100644 cmd/frontend/logic/discord/user.go create mode 100644 cmd/frontend/middleware/auth.go create mode 100644 cmd/frontend/routes/api/auth/discord.go create mode 100644 cmd/frontend/routes/app/index.templ create mode 100644 cmd/frontend/routes/app/index_templ.go create mode 100644 cmd/frontend/routes/login.go create mode 100644 internal/database/auth.go create mode 100644 internal/database/ent/db/authnonce.go create mode 100644 internal/database/ent/db/authnonce/authnonce.go create mode 100644 internal/database/ent/db/authnonce/where.go create mode 100644 internal/database/ent/db/authnonce_create.go create mode 100644 internal/database/ent/db/authnonce_delete.go create mode 100644 internal/database/ent/db/authnonce_query.go create mode 100644 internal/database/ent/db/authnonce_update.go create mode 100644 internal/database/ent/db/session.go create mode 100644 internal/database/ent/db/session/session.go create mode 100644 internal/database/ent/db/session/where.go create mode 100644 internal/database/ent/db/session_create.go create mode 100644 internal/database/ent/db/session_delete.go create mode 100644 internal/database/ent/db/session_query.go create mode 100644 internal/database/ent/db/session_update.go create mode 100644 internal/database/ent/migrations/20240703151859.sql create mode 100644 internal/database/ent/migrations/20240703160454.sql create mode 100644 internal/database/ent/migrations/20240703160711.sql create mode 100644 internal/database/ent/migrations/20240703180358.sql create mode 100644 internal/database/ent/schema/nonce.go create mode 100644 internal/database/ent/schema/session.go create mode 100644 internal/database/models/nonce.go create mode 100644 internal/database/models/session.go create mode 100644 internal/logic/strings.go diff --git a/.air.toml b/.air.toml index 9de18a51..86f040d7 100644 --- a/.air.toml +++ b/.air.toml @@ -9,3 +9,7 @@ stop_on_error = true enabled = true proxy_port = 8080 app_port = 9092 + +[log] +# Only show main log (silences watcher, build, runner) +main_only = false diff --git a/.air.web.toml b/.air.web.toml index 181cb47e..3b69524b 100644 --- a/.air.web.toml +++ b/.air.web.toml @@ -9,5 +9,9 @@ bin = "tmp/web" [proxy] enabled = true -proxy_port = 8081 +proxy_port = 8080 app_port = 9092 + +[log] +# Only show main log (silences watcher, build, runner) +main_only = false diff --git a/.env.example b/.env.example index 79768cef..505e0f42 100644 --- a/.env.example +++ b/.env.example @@ -26,7 +26,10 @@ DISCORD_TOKEN="" DISCORD_PUBLIC_KEY="" DISCORD_PRIMARY_GUILD_ID="" # Discord ID of the primary guild, some features will be locked to this guild only DISCORD_ERROR_REPORT_WEBHOOK_URL="" # A Discord webhook URL for a channel where the bot should report errors - +DISCORD_AUTH_DEFAULT_SCOPES="guilds identify" +DISCORD_AUTH_REDIRECT_URL="" +DISCORD_AUTH_CLIENT_SECRET="" +DISCORD_AUTH_CLIENT_ID="" # Optional components SCHEDULER_ENABLED="true" # Scheduler is responsible for refreshing glossary cache and recording sessions SCHEDULER_CONCURRENT_WORKERS="5" diff --git a/cmd/frontend/handler/context.go b/cmd/frontend/handler/context.go index c6b39e94..956b2a9b 100644 --- a/cmd/frontend/handler/context.go +++ b/cmd/frontend/handler/context.go @@ -8,33 +8,112 @@ import ( "github.com/a-h/templ" "github.com/cufee/aftermath/cmd/core" + "github.com/cufee/aftermath/cmd/frontend/logic/auth" + "github.com/pkg/errors" + + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/logic" "github.com/rs/zerolog/log" ) -type Renderable interface { - Render(ctx *Context) error +type Servable interface { + Serve(ctx *Context) error } +type Middleware func(ctx *Context, next func(ctx *Context) error) func(ctx *Context) error + type Context struct { core.Client context.Context + user *models.User + w http.ResponseWriter r *http.Request } -func (ctx *Context) Req() *http.Request { - return ctx.r +type Layout func(ctx *Context, children ...templ.Component) (templ.Component, error) +type Page func(ctx *Context) (Layout, templ.Component, error) +type Partial func(ctx *Context) (templ.Component, error) +type Endpoint func(ctx *Context) error + +var ( + _ Servable = new(Page) + _ Servable = new(Partial) + _ Servable = new(Endpoint) +) + +func (ctx *Context) Cookie(key string) (*http.Cookie, error) { + return ctx.r.Cookie(key) +} +func (ctx *Context) SetCookie(cookie *http.Cookie) { + http.SetCookie(ctx.w, cookie) +} +func (ctx *Context) Query(key string) string { + return ctx.r.URL.Query().Get(key) +} +func (ctx *Context) Form(key string) string { + return ctx.r.Form.Get(key) +} +func (ctx *Context) Path(key string) string { + return ctx.r.PathValue(key) +} +func (ctx *Context) RealIP() (string, bool) { + if ip := ctx.r.Header.Get("X-Forwarded-For"); ip != "" { + return ip, true + } + if ip := ctx.r.RemoteAddr; ip != "" { + return ip, true + } + return "", false +} + +/* +Returns a stable identifier based on requestor ip address +*/ +func (ctx *Context) Identifier() (string, error) { + if ip, ok := ctx.RealIP(); ok { + return logic.HashString(ip), nil + } + return "", errors.New("failed to extract ip address") } -func (ctx *Context) Error(err error, context ...string) error { +func (ctx *Context) SessionUser() (*models.User, error) { + if ctx.user != nil { + return ctx.user, nil + } + + cookie, err := ctx.Cookie(auth.SessionCookieName) + if err != nil || cookie == nil { + return nil, ErrSessionNotFound + } + if cookie.Value == "" { + return nil, ErrSessionNotFound + } + user, err := ctx.Database().UserFromSession(ctx.Context, cookie.Value) + if err != nil { + if database.IsNotFound(err) { + return nil, ErrSessionNotFound + } + return nil, err + } + + ctx.user = &user + return ctx.user, nil +} + +/* +Redirects a user to /error with an error message set as query param +*/ +func (ctx *Context) Error(err error, context ...string) error { query := make(url.Values) if err != nil { query.Set("message", err.Error()) } if len(context) > 1 { - log.Err(err).Msg("error while rendering") // end user does not get any context, so we log the error instead + log.Err(err).Msg("error while serving") // end user does not get any context, so we log the error instead query.Set("message", strings.Join(context, ", ")) } @@ -42,19 +121,20 @@ func (ctx *Context) Error(err error, context ...string) error { return nil // this would never cause an error } +func (ctx *Context) Redirect(path string, code int) error { + http.Redirect(ctx.w, ctx.r, path, code) + return nil // this would never cause an error +} + func (ctx *Context) SetStatus(code int) { ctx.w.WriteHeader(code) } func newContext(core core.Client, w http.ResponseWriter, r *http.Request) *Context { - return &Context{core, r.Context(), w, r} + return &Context{core, r.Context(), nil, w, r} } -type Layout func(ctx *Context, children ...templ.Component) (templ.Component, error) - -type Partial func(ctx *Context) (templ.Component, error) - -func (partial Partial) Render(ctx *Context) error { +func (partial Partial) Serve(ctx *Context) error { content, err := partial(ctx) if err != nil { return ctx.Error(err) @@ -68,13 +148,16 @@ func (partial Partial) Render(ctx *Context) error { return nil } -type Page func(ctx *Context) (Layout, templ.Component, error) - -func (page Page) Render(ctx *Context) error { +func (page Page) Serve(ctx *Context) error { layout, body, err := page(ctx) if err != nil { return ctx.Error(err, "failed to render the page") } + if layout == nil && body == nil { + return nil + } else if layout == nil { + return body.Render(ctx.Context, ctx.w) + } content, err := layout(ctx, body) if err != nil { @@ -89,12 +172,25 @@ func (page Page) Render(ctx *Context) error { return nil } -func Handler(core core.Client, content Renderable) http.HandlerFunc { +func (endpoint Endpoint) Serve(ctx *Context) error { + err := endpoint(ctx) + if err != nil { + return ctx.Error(err, "internal server error") + } + return nil +} + +func Chain(core core.Client, serve Servable, middleware ...Middleware) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - err := content.Render(newContext(core, w, r)) + ctx := newContext(core, w, r) + chain := serve.Serve + for i := len(middleware) - 1; i >= 0; i-- { + chain = middleware[i](ctx, chain) + } + err := chain(ctx) if err != nil { - // this should never be the case, we return an error to make early returns more convenient - log.Err(err).Msg("handler failed to render, this should never happen") + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Err(err).Msg("unhandled error in handler chain, this should never happen") } } } diff --git a/cmd/frontend/handler/errors.go b/cmd/frontend/handler/errors.go new file mode 100644 index 00000000..7797aa50 --- /dev/null +++ b/cmd/frontend/handler/errors.go @@ -0,0 +1,7 @@ +package handler + +import "github.com/pkg/errors" + +var ( + ErrSessionNotFound = errors.New("session not found") +) diff --git a/cmd/frontend/handlers.go b/cmd/frontend/handlers.go index f0f3d89c..640d0341 100644 --- a/cmd/frontend/handlers.go +++ b/cmd/frontend/handlers.go @@ -9,7 +9,10 @@ import ( "github.com/cufee/aftermath/cmd/core" "github.com/cufee/aftermath/cmd/core/server" "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/middleware" "github.com/cufee/aftermath/cmd/frontend/routes" + "github.com/cufee/aftermath/cmd/frontend/routes/api/auth" + "github.com/cufee/aftermath/cmd/frontend/routes/app" "github.com/cufee/aftermath/cmd/frontend/routes/widget" "github.com/pkg/errors" ) @@ -30,7 +33,7 @@ func Handlers(core core.Client) ([]server.Handler, error) { return []server.Handler{ // assets { - Path: "GET /assets/{$}", + Path: get("/assets"), Func: redirect("/"), }, { @@ -40,28 +43,40 @@ func Handlers(core core.Client) ([]server.Handler, error) { // wildcard to catch all invalid requests { Path: "GET /{pathname...}", - Func: handler.Handler(core, routes.ErrorNotFound), + Func: handler.Chain(core, routes.ErrorNotFound), }, // common routes { Path: get("/"), - Func: handler.Handler(core, routes.Index), + Func: handler.Chain(core, routes.Index), }, { Path: get("/error"), - Func: handler.Handler(core, routes.GenericError), + Func: handler.Chain(core, routes.GenericError), + }, + { + Path: get("/login"), + Func: handler.Chain(core, routes.Login), }, // widget { Path: get("/widget/{accountId}"), - Func: handler.Handler(core, widget.ConfigureWidget), + Func: handler.Chain(core, widget.ConfigureWidget), }, { Path: get("/widget/{accountId}/live"), - Func: handler.Handler(core, widget.LiveWidget), + Func: handler.Chain(core, widget.LiveWidget), }, // app routes - // + { + Path: get("/app"), + Func: handler.Chain(core, app.Index, middleware.SessionCheck), + }, + // api routes + { + Path: get("/api/auth/discord"), + Func: handler.Chain(core, auth.DiscordRedirect), + }, }, nil } diff --git a/cmd/frontend/logic/auth/contants.go b/cmd/frontend/logic/auth/contants.go new file mode 100644 index 00000000..af408da1 --- /dev/null +++ b/cmd/frontend/logic/auth/contants.go @@ -0,0 +1,20 @@ +package auth + +import "os" + +const SessionCookieName = "x-amth-session" +const AuthNonceCookieName = "x-amth-auth-nonce" + +var ( + DefaultCookiePath = os.Getenv("AUTH_COOKIE_PATH") + DefaultCookieDomain = os.Getenv("AUTH_COOKIE_DOMAIN") +) + +func init() { + if DefaultCookiePath == "" { + panic("AUTH_COOKIE_PATH cannot be left empty") + } + if DefaultCookieDomain == "" { + panic("AUTH_COOKIE_DOMAIN cannot be left empty") + } +} diff --git a/cmd/frontend/logic/auth/cookie.go b/cmd/frontend/logic/auth/cookie.go new file mode 100644 index 00000000..59162b77 --- /dev/null +++ b/cmd/frontend/logic/auth/cookie.go @@ -0,0 +1,34 @@ +package auth + +import ( + "net/http" + "time" +) + +func NewSessionCookie(value string, expiresAt time.Time) *http.Cookie { + return &http.Cookie{ + Name: SessionCookieName, + Value: value, + MaxAge: int(time.Until(expiresAt).Seconds()), + + Secure: true, + HttpOnly: true, + Path: DefaultCookiePath, + Domain: DefaultCookieDomain, + SameSite: http.SameSiteLaxMode, + } +} + +func NewNonceCookie(value string, expiresAt time.Time) *http.Cookie { + return &http.Cookie{ + Name: AuthNonceCookieName, + Value: value, + MaxAge: int(time.Until(expiresAt).Seconds()), + + Secure: true, + HttpOnly: true, + Path: DefaultCookiePath, + Domain: DefaultCookieDomain, + SameSite: http.SameSiteLaxMode, + } +} diff --git a/cmd/frontend/logic/discord/oauth.go b/cmd/frontend/logic/discord/oauth.go new file mode 100644 index 00000000..6d0d9809 --- /dev/null +++ b/cmd/frontend/logic/discord/oauth.go @@ -0,0 +1,107 @@ +package discord + +import ( + "encoding/json" + "io" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +type authToken struct { + Scope string `json:"scope"` + Type string `json:"token_type"` + Value string `json:"access_token"` + + ExpiresAt time.Time `json:"-"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` +} + +var defaultClient = http.Client{ + Timeout: time.Second * 5, +} + +const baseURL = "https://discord.com/oauth2" + +var defaultClientID = os.Getenv("DISCORD_AUTH_CLIENT_ID") +var defaultClientSecret = os.Getenv("DISCORD_AUTH_CLIENT_SECRET") + +var defaultScopes = os.Getenv("DISCORD_AUTH_DEFAULT_SCOPES") +var defaultRedirectURL = os.Getenv("DISCORD_AUTH_REDIRECT_URL") + +type oAuthURL struct { + scope string + state string + prompt string + clientID string + redirectURL string + responseType string +} + +func NewOAuthURL(state string) oAuthURL { + url := oAuthURL{ + prompt: "none", + state: state, + scope: defaultScopes, + clientID: defaultClientID, + redirectURL: defaultRedirectURL, + responseType: "code", + } + return url +} + +func (u oAuthURL) String() string { + // https://discord.com/developers/docs/topics/oauth2#authorization-code-grant + base := baseURL + "/authorize" + query := url.Values{} + query.Set("scope", u.scope) + query.Set("prompt", u.prompt) + query.Set("state", u.state) + query.Set("client_id", u.clientID) + query.Set("redirect_uri", u.redirectURL) + query.Set("response_type", u.responseType) + return base + "?" + query.Encode() +} + +func ExchangeOAuthCode(code, redirectURL string) (authToken, error) { + base := discordgo.EndpointOAuth2 + "token" + query := url.Values{} + query.Set("code", code) + query.Set("redirect_uri", defaultRedirectURL) + query.Set("grant_type", "authorization_code") + + req, err := http.NewRequest("POST", base, strings.NewReader(query.Encode())) + if err != nil { + return authToken{}, errors.Wrap(err, "failed to create an exchange request") + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.SetBasicAuth(defaultClientID, defaultClientSecret) + + res, err := defaultClient.Do(req) + if err != nil { + return authToken{}, errors.Wrap(err, "failed to make a POST request") + } + defer res.Body.Close() + if res.StatusCode != 200 { + body, _ := io.ReadAll(res.Body) + log.Error().Str("body", string(body)).Msg("bad status code") + return authToken{}, errors.Errorf("received status code %d", res.StatusCode) + } + + var data authToken + err = json.NewDecoder(res.Body).Decode(&data) + if err != nil { + return authToken{}, errors.Wrap(err, "failed to decode response") + } + + data.ExpiresAt = time.Now().Add(time.Second * time.Duration(data.ExpiresIn)) + return data, nil +} diff --git a/cmd/frontend/logic/discord/user.go b/cmd/frontend/logic/discord/user.go new file mode 100644 index 00000000..4940bebc --- /dev/null +++ b/cmd/frontend/logic/discord/user.go @@ -0,0 +1,44 @@ +package discord + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +type userResponse struct { + User discordgo.User `json:"user"` + Scopes []string `json:"scopes"` +} + +func GetUserFromToken(token authToken) (discordgo.User, error) { + url := discordgo.EndpointOAuth2 + "@me" + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return discordgo.User{}, errors.Wrap(err, "failed to create a GET request") + } + req.Header.Add("Authorization", "Bearer "+token.Value) + + res, err := defaultClient.Do(req) + if err != nil { + return discordgo.User{}, errors.Wrap(err, "GET request failed") + } + defer res.Body.Close() + if res.StatusCode != 200 { + body, _ := io.ReadAll(res.Body) + log.Error().Str("body", string(body)).Msg("bad status code") + return discordgo.User{}, errors.Errorf("received status code %d", res.StatusCode) + } + + var data userResponse + err = json.NewDecoder(res.Body).Decode(&data) + if err != nil { + return discordgo.User{}, errors.Wrap(err, "failed to decode user data") + } + + return data.User, nil +} diff --git a/cmd/frontend/middleware/auth.go b/cmd/frontend/middleware/auth.go new file mode 100644 index 00000000..396a3c96 --- /dev/null +++ b/cmd/frontend/middleware/auth.go @@ -0,0 +1,21 @@ +package middleware + +import ( + "net/http" + "time" + + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/logic/auth" +) + +var SessionCheck handler.Middleware = func(ctx *handler.Context, next func(ctx *handler.Context) error) func(ctx *handler.Context) error { + user, err := ctx.SessionUser() + if err == nil && user.ID != "" { + return next + } + + return func(ctx *handler.Context) error { + ctx.SetCookie(auth.NewSessionCookie("", time.Time{})) + return ctx.Redirect("/login", http.StatusTemporaryRedirect) + } +} diff --git a/cmd/frontend/routes/api/auth/discord.go b/cmd/frontend/routes/api/auth/discord.go new file mode 100644 index 00000000..8059ad9d --- /dev/null +++ b/cmd/frontend/routes/api/auth/discord.go @@ -0,0 +1,91 @@ +package auth + +import ( + "net/http" + "strings" + "time" + + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/logic/auth" + "github.com/cufee/aftermath/cmd/frontend/logic/discord" + "github.com/cufee/aftermath/internal/logic" + "github.com/rs/zerolog/log" +) + +var DiscordRedirect handler.Endpoint = func(ctx *handler.Context) error { + log.Debug().Msg("started handling a discord redirect") + + code := ctx.Query("code") + state := ctx.Query("state") + if code == "" || state == "" { + log.Debug().Msg("discord redirect missing state or code") + return ctx.Redirect("/login?message=session not found", http.StatusTemporaryRedirect) + } + + cookie, err := ctx.Cookie(auth.AuthNonceCookieName) + if err != nil || cookie == nil { + return ctx.Redirect("/login?message=session not found", http.StatusTemporaryRedirect) + } + if cookie.Value == "" { + return ctx.Redirect("/login?message=session not found", http.StatusTemporaryRedirect) + } + log.Debug().Str("identifier", cookie.Value).Msg("handling a discord redirect") + + nonce, err := ctx.Database().FindAuthNonce(ctx.Context, state) + if err != nil || !nonce.Active || nonce.ExpiresAt.Before(time.Now()) { + log.Debug().Msg("discord redirect missing or invalid nonce") + return ctx.Redirect("/login?message=session expired", http.StatusTemporaryRedirect) + } + err = ctx.Database().SetAuthNonceActive(ctx, nonce.ID, false) + if err != nil { + log.Err(err).Msg("failed to update nonce active status") + return ctx.Redirect("/login?message=session expired", http.StatusTemporaryRedirect) + } + + identifier, err := ctx.Identifier() + if err != nil { + log.Err(err).Msg("failed to extract an identifier from a request") + return ctx.Redirect("/login?message=session expired", http.StatusTemporaryRedirect) + } + + if nonce.Identifier != identifier { + log.Debug().Msg("discord redirect invalid identifier") + return ctx.Redirect("/login?message=session expired", http.StatusTemporaryRedirect) + } + + token, err := discord.ExchangeOAuthCode(code, nonce.Meta["redirectUrl"]) + if err != nil { + log.Err(err).Msg("failed to exchange code for token") + return ctx.Redirect("/login?message=failed to create a session", http.StatusTemporaryRedirect) + } + + user, err := discord.GetUserFromToken(token) + if err != nil { + log.Err(err).Msg("failed to get user from token") + return ctx.Redirect("/login?message=failed to create a session", http.StatusTemporaryRedirect) + } + if user.ID == "" { // just in case + log.Error().Msg("blank user id received from discord") + return ctx.Redirect("/login?message=failed to create a session", http.StatusTemporaryRedirect) + } + + sessionID, err := logic.RandomString(32) + if err != nil { + log.Err(err).Msg("failed to generate a session id") + return ctx.Redirect("/login?message=failed to create a session", http.StatusTemporaryRedirect) + } + + sess, err := ctx.Database().CreateSession(ctx.Context, sessionID, user.ID, time.Now().Add(time.Hour*24*7), nil) + if err != nil { + log.Err(err).Msg("failed to create a new user session") + return ctx.Redirect("/login?message=failed to create a session", http.StatusTemporaryRedirect) + } + + ctx.SetCookie(auth.NewSessionCookie(sess.PublicID, sess.ExpiresAt)) + + defer log.Debug().Msg("finished handling a discord redirect") + if path, ok := nonce.Meta["redirect"]; ok && strings.HasPrefix(path, "/") { + return ctx.Redirect(path, http.StatusTemporaryRedirect) + } + return ctx.Redirect("/app", http.StatusTemporaryRedirect) +} diff --git a/cmd/frontend/routes/app/index.templ b/cmd/frontend/routes/app/index.templ new file mode 100644 index 00000000..6eddfd42 --- /dev/null +++ b/cmd/frontend/routes/app/index.templ @@ -0,0 +1,21 @@ +package app + +import "net/http" +import "github.com/cufee/aftermath/internal/database/models" +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/cufee/aftermath/cmd/frontend/layouts" + +var Index handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + user, err := ctx.SessionUser() + if err != nil { + return nil, nil, ctx.Redirect("/login", http.StatusTemporaryRedirect) + } + return layouts.Main, index(user), nil +} + +templ index(user *models.User) { +
    + App page + {user.ID} +
    +} diff --git a/cmd/frontend/routes/app/index_templ.go b/cmd/frontend/routes/app/index_templ.go new file mode 100644 index 00000000..16e3e940 --- /dev/null +++ b/cmd/frontend/routes/app/index_templ.go @@ -0,0 +1,61 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.696 +package app + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "net/http" +import "github.com/cufee/aftermath/internal/database/models" +import "github.com/cufee/aftermath/cmd/frontend/handler" +import "github.com/cufee/aftermath/cmd/frontend/layouts" + +var Index handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + user, err := ctx.SessionUser() + if err != nil { + return nil, nil, ctx.Redirect("/login", http.StatusTemporaryRedirect) + } + return layouts.Main, index(user), nil +} + +func index(user *models.User) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    App page ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user.ID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/app/index.templ`, Line: 19, Col: 12} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/routes/errors.templ b/cmd/frontend/routes/errors.templ index cbd34c87..c3d4db65 100644 --- a/cmd/frontend/routes/errors.templ +++ b/cmd/frontend/routes/errors.templ @@ -5,7 +5,7 @@ import "github.com/cufee/aftermath/cmd/frontend/layouts" import "net/http" var GenericError handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - message := ctx.Req().URL.Query().Get("message") + message := ctx.Query("message") ctx.SetStatus(http.StatusInternalServerError) return layouts.Main, errorPage(message), nil @@ -19,7 +19,7 @@ templ errorPage(message string) { } var ErrorNotFound handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - path := ctx.Req().PathValue("pathname") + path := ctx.Path("pathname") ctx.SetStatus(http.StatusNotFound) return layouts.Main, notFoundPage(path), nil diff --git a/cmd/frontend/routes/errors_templ.go b/cmd/frontend/routes/errors_templ.go index bb761d1a..c457a73c 100644 --- a/cmd/frontend/routes/errors_templ.go +++ b/cmd/frontend/routes/errors_templ.go @@ -15,7 +15,7 @@ import "github.com/cufee/aftermath/cmd/frontend/layouts" import "net/http" var GenericError handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - message := ctx.Req().URL.Query().Get("message") + message := ctx.Query("message") ctx.SetStatus(http.StatusInternalServerError) return layouts.Main, errorPage(message), nil @@ -59,7 +59,7 @@ func errorPage(message string) templ.Component { } var ErrorNotFound handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - path := ctx.Req().PathValue("pathname") + path := ctx.Path("pathname") ctx.SetStatus(http.StatusNotFound) return layouts.Main, notFoundPage(path), nil diff --git a/cmd/frontend/routes/login.go b/cmd/frontend/routes/login.go new file mode 100644 index 00000000..c47c41af --- /dev/null +++ b/cmd/frontend/routes/login.go @@ -0,0 +1,36 @@ +package routes + +import ( + "net/http" + "time" + + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/logic/auth" + "github.com/cufee/aftermath/cmd/frontend/logic/discord" + "github.com/cufee/aftermath/internal/logic" + "github.com/rs/zerolog/log" +) + +var Login handler.Endpoint = func(ctx *handler.Context) error { + nonceID, err := logic.RandomString(32) + if err != nil { + return ctx.Error(err, "failed to authenticate") + } + + identifier, err := ctx.Identifier() + if err != nil { + return ctx.Error(err, "failed to extract an identifier") + } + log.Debug().Str("identifier", identifier).Msg("new login request") + + redirectURL := discord.NewOAuthURL(nonceID) + meta := map[string]string{"from": ctx.Query("from"), "redirectUrl": redirectURL.String()} + + nonce, err := ctx.Database().CreateAuthNonce(ctx.Context, nonceID, identifier, time.Now().Add(time.Minute*5), meta) + if err != nil { + return ctx.Error(err, "failed to authenticate") + } + + ctx.SetCookie(auth.NewNonceCookie(nonce.PublicID, nonce.ExpiresAt)) + return ctx.Redirect(redirectURL.String(), http.StatusTemporaryRedirect) +} diff --git a/cmd/frontend/routes/widget/configure.templ b/cmd/frontend/routes/widget/configure.templ index 9f88403b..7cc17bf4 100644 --- a/cmd/frontend/routes/widget/configure.templ +++ b/cmd/frontend/routes/widget/configure.templ @@ -10,7 +10,7 @@ import "github.com/pkg/errors" import "fmt" var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - accountID := ctx.Req().PathValue("accountId") + accountID := ctx.Path("accountId") if accountID == "" { return nil, nil, errors.New("invalid account id") } diff --git a/cmd/frontend/routes/widget/configure_templ.go b/cmd/frontend/routes/widget/configure_templ.go index b353aef5..755037b9 100644 --- a/cmd/frontend/routes/widget/configure_templ.go +++ b/cmd/frontend/routes/widget/configure_templ.go @@ -20,7 +20,7 @@ import "github.com/pkg/errors" import "fmt" var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - accountID := ctx.Req().PathValue("accountId") + accountID := ctx.Path("accountId") if accountID == "" { return nil, nil, errors.New("invalid account id") } diff --git a/cmd/frontend/routes/widget/live.templ b/cmd/frontend/routes/widget/live.templ index e8a3f723..31f52f8a 100644 --- a/cmd/frontend/routes/widget/live.templ +++ b/cmd/frontend/routes/widget/live.templ @@ -11,7 +11,7 @@ import "github.com/cufee/aftermath/internal/stats/client/v1" import "slices" var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - accountID := ctx.Req().PathValue("accountId") + accountID := ctx.Path("accountId") if accountID == "" { return nil, nil, errors.New("invalid account id") } @@ -22,10 +22,10 @@ var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ. } var opts = []client.RequestOption{client.WithWN8()} - if ref := ctx.Req().URL.Query().Get("ref"); ref != "" { + if ref := ctx.Query("ref"); ref != "" { opts = append(opts, client.WithReferenceID(ref)) } - if t := ctx.Req().URL.Query().Get("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { + if t := ctx.Query("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { opts = append(opts, client.WithType(models.SnapshotType(t))) } diff --git a/cmd/frontend/routes/widget/live_templ.go b/cmd/frontend/routes/widget/live_templ.go index 994c9045..b176d1c8 100644 --- a/cmd/frontend/routes/widget/live_templ.go +++ b/cmd/frontend/routes/widget/live_templ.go @@ -21,7 +21,7 @@ import "github.com/cufee/aftermath/internal/stats/client/v1" import "slices" var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - accountID := ctx.Req().PathValue("accountId") + accountID := ctx.Path("accountId") if accountID == "" { return nil, nil, errors.New("invalid account id") } @@ -32,10 +32,10 @@ var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ. } var opts = []client.RequestOption{client.WithWN8()} - if ref := ctx.Req().URL.Query().Get("ref"); ref != "" { + if ref := ctx.Query("ref"); ref != "" { opts = append(opts, client.WithReferenceID(ref)) } - if t := ctx.Req().URL.Query().Get("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { + if t := ctx.Query("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { opts = append(opts, client.WithType(models.SnapshotType(t))) } diff --git a/go.sum b/go.sum index 1b86d9e9..aa531aa1 100644 --- a/go.sum +++ b/go.sum @@ -60,12 +60,16 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -73,6 +77,8 @@ 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -81,6 +87,10 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/internal/database/auth.go b/internal/database/auth.go new file mode 100644 index 00000000..b874264a --- /dev/null +++ b/internal/database/auth.go @@ -0,0 +1,110 @@ +package database + +import ( + "context" + "time" + + "github.com/cufee/aftermath/internal/database/ent/db" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" + "github.com/cufee/aftermath/internal/database/ent/db/session" + "github.com/cufee/aftermath/internal/database/models" +) + +func toAuthNonce(record *db.AuthNonce) models.AuthNonce { + return models.AuthNonce{ + ID: record.ID, + Active: record.Active, + PublicID: record.PublicID, + Identifier: record.Identifier, + Meta: record.Metadata, + + CreatedAt: record.CreatedAt, + UpdatedAt: record.UpdatedAt, + ExpiresAt: record.ExpiresAt, + } +} + +func (c *client) CreateAuthNonce(ctx context.Context, publicID, identifier string, expiresAt time.Time, meta map[string]string) (models.AuthNonce, error) { + record, err := c.db.AuthNonce.Create().SetActive(true).SetExpiresAt(expiresAt).SetIdentifier(identifier).SetPublicID(publicID).SetMetadata(meta).Save(ctx) + if err != nil { + return models.AuthNonce{}, err + } + + nonce := toAuthNonce(record) + return nonce, nonce.Valid() +} + +func (c *client) FindAuthNonce(ctx context.Context, publicID string) (models.AuthNonce, error) { + record, err := c.db.AuthNonce.Query().Where(authnonce.PublicID(publicID), authnonce.Active(true), authnonce.ExpiresAtGT(time.Now())).First(ctx) + if err != nil { + return models.AuthNonce{}, err + } + + nonce := toAuthNonce(record) + return nonce, nonce.Valid() +} + +func (c *client) SetAuthNonceActive(ctx context.Context, nonceID string, active bool) error { + err := c.db.AuthNonce.UpdateOneID(nonceID).SetActive(active).Exec(ctx) + if err != nil { + return err + } + + return nil +} + +func toSession(record *db.Session) models.Session { + return models.Session{ + ID: record.ID, + UserID: record.UserID, + PublicID: record.PublicID, + Meta: record.Metadata, + + CreatedAt: record.CreatedAt, + UpdatedAt: record.UpdatedAt, + ExpiresAt: record.ExpiresAt, + } +} + +func (c *client) CreateSession(ctx context.Context, publicID, userID string, expiresAt time.Time, meta map[string]string) (models.Session, error) { + user, err := c.GetOrCreateUserByID(ctx, userID) + if err != nil { + return models.Session{}, err + } + + record, err := c.db.Session.Create().SetPublicID(publicID).SetUser(c.db.User.GetX(ctx, user.ID)).SetExpiresAt(expiresAt).SetMetadata(meta).Save(ctx) + if err != nil { + return models.Session{}, err + } + + nonce := toSession(record) + return nonce, nonce.Valid() +} + +func (c *client) FindSession(ctx context.Context, publicID string) (models.Session, error) { + record, err := c.db.Session.Query().Where(session.PublicID(publicID), session.ExpiresAtGT(time.Now())).First(ctx) + if err != nil { + return models.Session{}, err + } + + session := toSession(record) + return session, session.Valid() +} + +func (c *client) UserFromSession(ctx context.Context, publicID string) (models.User, error) { + record, err := c.db.Session.Query().Where(session.PublicID(publicID), session.ExpiresAtGT(time.Now())).QueryUser().First(ctx) + if err != nil { + return models.User{}, err + } + + return toUser(record, nil, nil, nil), nil +} + +func (c *client) SetSessionExpiresAt(ctx context.Context, sessionID string, expiresAt time.Time) error { + err := c.db.Session.UpdateOneID(sessionID).SetExpiresAt(expiresAt).Exec(ctx) + if err != nil { + return err + } + + return nil +} diff --git a/internal/database/client.go b/internal/database/client.go index cdcb8670..da875773 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -18,6 +18,17 @@ import ( var _ Client = &client{} +type AuthClient interface { + CreateAuthNonce(ctx context.Context, publicID, identifier string, expiresAt time.Time, meta map[string]string) (models.AuthNonce, error) + FindAuthNonce(ctx context.Context, publicID string) (models.AuthNonce, error) + SetAuthNonceActive(ctx context.Context, nonceID string, active bool) error + + CreateSession(ctx context.Context, publicID, userID string, expiresAt time.Time, meta map[string]string) (models.Session, error) + SetSessionExpiresAt(ctx context.Context, sessionID string, expiresAt time.Time) error + UserFromSession(ctx context.Context, publicID string) (models.User, error) + FindSession(ctx context.Context, publicID string) (models.Session, error) +} + type AccountsClient interface { GetAccounts(ctx context.Context, ids []string) ([]models.Account, error) GetAccountByID(ctx context.Context, id string) (models.Account, error) @@ -83,6 +94,7 @@ type DiscordDataClient interface { } type Client interface { + AuthClient UsersClient GlossaryClient diff --git a/internal/database/ent/db/authnonce.go b/internal/database/ent/db/authnonce.go new file mode 100644 index 00000000..84f2ca12 --- /dev/null +++ b/internal/database/ent/db/authnonce.go @@ -0,0 +1,177 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" +) + +// AuthNonce is the model entity for the AuthNonce schema. +type AuthNonce struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // Active holds the value of the "active" field. + Active bool `json:"active,omitempty"` + // ExpiresAt holds the value of the "expires_at" field. + ExpiresAt time.Time `json:"expires_at,omitempty"` + // Identifier holds the value of the "identifier" field. + Identifier string `json:"identifier,omitempty"` + // PublicID holds the value of the "public_id" field. + PublicID string `json:"public_id,omitempty"` + // Metadata holds the value of the "metadata" field. + Metadata map[string]string `json:"metadata,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*AuthNonce) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case authnonce.FieldMetadata: + values[i] = new([]byte) + case authnonce.FieldActive: + values[i] = new(sql.NullBool) + case authnonce.FieldID, authnonce.FieldIdentifier, authnonce.FieldPublicID: + values[i] = new(sql.NullString) + case authnonce.FieldCreatedAt, authnonce.FieldUpdatedAt, authnonce.FieldExpiresAt: + values[i] = new(sql.NullTime) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the AuthNonce fields. +func (an *AuthNonce) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case authnonce.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + an.ID = value.String + } + case authnonce.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + an.CreatedAt = value.Time + } + case authnonce.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + an.UpdatedAt = value.Time + } + case authnonce.FieldActive: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field active", values[i]) + } else if value.Valid { + an.Active = value.Bool + } + case authnonce.FieldExpiresAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field expires_at", values[i]) + } else if value.Valid { + an.ExpiresAt = value.Time + } + case authnonce.FieldIdentifier: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field identifier", values[i]) + } else if value.Valid { + an.Identifier = value.String + } + case authnonce.FieldPublicID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field public_id", values[i]) + } else if value.Valid { + an.PublicID = value.String + } + case authnonce.FieldMetadata: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field metadata", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &an.Metadata); err != nil { + return fmt.Errorf("unmarshal field metadata: %w", err) + } + } + default: + an.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the AuthNonce. +// This includes values selected through modifiers, order, etc. +func (an *AuthNonce) Value(name string) (ent.Value, error) { + return an.selectValues.Get(name) +} + +// Update returns a builder for updating this AuthNonce. +// Note that you need to call AuthNonce.Unwrap() before calling this method if this AuthNonce +// was returned from a transaction, and the transaction was committed or rolled back. +func (an *AuthNonce) Update() *AuthNonceUpdateOne { + return NewAuthNonceClient(an.config).UpdateOne(an) +} + +// Unwrap unwraps the AuthNonce entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (an *AuthNonce) Unwrap() *AuthNonce { + _tx, ok := an.config.driver.(*txDriver) + if !ok { + panic("db: AuthNonce is not a transactional entity") + } + an.config.driver = _tx.drv + return an +} + +// String implements the fmt.Stringer. +func (an *AuthNonce) String() string { + var builder strings.Builder + builder.WriteString("AuthNonce(") + builder.WriteString(fmt.Sprintf("id=%v, ", an.ID)) + builder.WriteString("created_at=") + builder.WriteString(an.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(an.UpdatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("active=") + builder.WriteString(fmt.Sprintf("%v", an.Active)) + builder.WriteString(", ") + builder.WriteString("expires_at=") + builder.WriteString(an.ExpiresAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("identifier=") + builder.WriteString(an.Identifier) + builder.WriteString(", ") + builder.WriteString("public_id=") + builder.WriteString(an.PublicID) + builder.WriteString(", ") + builder.WriteString("metadata=") + builder.WriteString(fmt.Sprintf("%v", an.Metadata)) + builder.WriteByte(')') + return builder.String() +} + +// AuthNonces is a parsable slice of AuthNonce. +type AuthNonces []*AuthNonce diff --git a/internal/database/ent/db/authnonce/authnonce.go b/internal/database/ent/db/authnonce/authnonce.go new file mode 100644 index 00000000..8a1741f0 --- /dev/null +++ b/internal/database/ent/db/authnonce/authnonce.go @@ -0,0 +1,107 @@ +// Code generated by ent, DO NOT EDIT. + +package authnonce + +import ( + "time" + + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the authnonce type in the database. + Label = "auth_nonce" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldActive holds the string denoting the active field in the database. + FieldActive = "active" + // FieldExpiresAt holds the string denoting the expires_at field in the database. + FieldExpiresAt = "expires_at" + // FieldIdentifier holds the string denoting the identifier field in the database. + FieldIdentifier = "identifier" + // FieldPublicID holds the string denoting the public_id field in the database. + FieldPublicID = "public_id" + // FieldMetadata holds the string denoting the metadata field in the database. + FieldMetadata = "metadata" + // Table holds the table name of the authnonce in the database. + Table = "auth_nonces" +) + +// Columns holds all SQL columns for authnonce fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldActive, + FieldExpiresAt, + FieldIdentifier, + FieldPublicID, + FieldMetadata, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // IdentifierValidator is a validator for the "identifier" field. It is called by the builders before save. + IdentifierValidator func(string) error + // PublicIDValidator is a validator for the "public_id" field. It is called by the builders before save. + PublicIDValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// OrderOption defines the ordering options for the AuthNonce queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByActive orders the results by the active field. +func ByActive(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldActive, opts...).ToFunc() +} + +// ByExpiresAt orders the results by the expires_at field. +func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldExpiresAt, opts...).ToFunc() +} + +// ByIdentifier orders the results by the identifier field. +func ByIdentifier(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldIdentifier, opts...).ToFunc() +} + +// ByPublicID orders the results by the public_id field. +func ByPublicID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPublicID, opts...).ToFunc() +} diff --git a/internal/database/ent/db/authnonce/where.go b/internal/database/ent/db/authnonce/where.go new file mode 100644 index 00000000..b4fc1a68 --- /dev/null +++ b/internal/database/ent/db/authnonce/where.go @@ -0,0 +1,370 @@ +// Code generated by ent, DO NOT EDIT. + +package authnonce + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Active applies equality check predicate on the "active" field. It's identical to ActiveEQ. +func Active(v bool) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldActive, v)) +} + +// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ. +func ExpiresAt(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldExpiresAt, v)) +} + +// Identifier applies equality check predicate on the "identifier" field. It's identical to IdentifierEQ. +func Identifier(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldIdentifier, v)) +} + +// PublicID applies equality check predicate on the "public_id" field. It's identical to PublicIDEQ. +func PublicID(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldPublicID, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// ActiveEQ applies the EQ predicate on the "active" field. +func ActiveEQ(v bool) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldActive, v)) +} + +// ActiveNEQ applies the NEQ predicate on the "active" field. +func ActiveNEQ(v bool) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNEQ(FieldActive, v)) +} + +// ExpiresAtEQ applies the EQ predicate on the "expires_at" field. +func ExpiresAtEQ(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldExpiresAt, v)) +} + +// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field. +func ExpiresAtNEQ(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNEQ(FieldExpiresAt, v)) +} + +// ExpiresAtIn applies the In predicate on the "expires_at" field. +func ExpiresAtIn(vs ...time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field. +func ExpiresAtNotIn(vs ...time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNotIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtGT applies the GT predicate on the "expires_at" field. +func ExpiresAtGT(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGT(FieldExpiresAt, v)) +} + +// ExpiresAtGTE applies the GTE predicate on the "expires_at" field. +func ExpiresAtGTE(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGTE(FieldExpiresAt, v)) +} + +// ExpiresAtLT applies the LT predicate on the "expires_at" field. +func ExpiresAtLT(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLT(FieldExpiresAt, v)) +} + +// ExpiresAtLTE applies the LTE predicate on the "expires_at" field. +func ExpiresAtLTE(v time.Time) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLTE(FieldExpiresAt, v)) +} + +// IdentifierEQ applies the EQ predicate on the "identifier" field. +func IdentifierEQ(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldIdentifier, v)) +} + +// IdentifierNEQ applies the NEQ predicate on the "identifier" field. +func IdentifierNEQ(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNEQ(FieldIdentifier, v)) +} + +// IdentifierIn applies the In predicate on the "identifier" field. +func IdentifierIn(vs ...string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldIn(FieldIdentifier, vs...)) +} + +// IdentifierNotIn applies the NotIn predicate on the "identifier" field. +func IdentifierNotIn(vs ...string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNotIn(FieldIdentifier, vs...)) +} + +// IdentifierGT applies the GT predicate on the "identifier" field. +func IdentifierGT(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGT(FieldIdentifier, v)) +} + +// IdentifierGTE applies the GTE predicate on the "identifier" field. +func IdentifierGTE(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGTE(FieldIdentifier, v)) +} + +// IdentifierLT applies the LT predicate on the "identifier" field. +func IdentifierLT(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLT(FieldIdentifier, v)) +} + +// IdentifierLTE applies the LTE predicate on the "identifier" field. +func IdentifierLTE(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLTE(FieldIdentifier, v)) +} + +// IdentifierContains applies the Contains predicate on the "identifier" field. +func IdentifierContains(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldContains(FieldIdentifier, v)) +} + +// IdentifierHasPrefix applies the HasPrefix predicate on the "identifier" field. +func IdentifierHasPrefix(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldHasPrefix(FieldIdentifier, v)) +} + +// IdentifierHasSuffix applies the HasSuffix predicate on the "identifier" field. +func IdentifierHasSuffix(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldHasSuffix(FieldIdentifier, v)) +} + +// IdentifierEqualFold applies the EqualFold predicate on the "identifier" field. +func IdentifierEqualFold(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEqualFold(FieldIdentifier, v)) +} + +// IdentifierContainsFold applies the ContainsFold predicate on the "identifier" field. +func IdentifierContainsFold(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldContainsFold(FieldIdentifier, v)) +} + +// PublicIDEQ applies the EQ predicate on the "public_id" field. +func PublicIDEQ(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEQ(FieldPublicID, v)) +} + +// PublicIDNEQ applies the NEQ predicate on the "public_id" field. +func PublicIDNEQ(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNEQ(FieldPublicID, v)) +} + +// PublicIDIn applies the In predicate on the "public_id" field. +func PublicIDIn(vs ...string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldIn(FieldPublicID, vs...)) +} + +// PublicIDNotIn applies the NotIn predicate on the "public_id" field. +func PublicIDNotIn(vs ...string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldNotIn(FieldPublicID, vs...)) +} + +// PublicIDGT applies the GT predicate on the "public_id" field. +func PublicIDGT(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGT(FieldPublicID, v)) +} + +// PublicIDGTE applies the GTE predicate on the "public_id" field. +func PublicIDGTE(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldGTE(FieldPublicID, v)) +} + +// PublicIDLT applies the LT predicate on the "public_id" field. +func PublicIDLT(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLT(FieldPublicID, v)) +} + +// PublicIDLTE applies the LTE predicate on the "public_id" field. +func PublicIDLTE(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldLTE(FieldPublicID, v)) +} + +// PublicIDContains applies the Contains predicate on the "public_id" field. +func PublicIDContains(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldContains(FieldPublicID, v)) +} + +// PublicIDHasPrefix applies the HasPrefix predicate on the "public_id" field. +func PublicIDHasPrefix(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldHasPrefix(FieldPublicID, v)) +} + +// PublicIDHasSuffix applies the HasSuffix predicate on the "public_id" field. +func PublicIDHasSuffix(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldHasSuffix(FieldPublicID, v)) +} + +// PublicIDEqualFold applies the EqualFold predicate on the "public_id" field. +func PublicIDEqualFold(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldEqualFold(FieldPublicID, v)) +} + +// PublicIDContainsFold applies the ContainsFold predicate on the "public_id" field. +func PublicIDContainsFold(v string) predicate.AuthNonce { + return predicate.AuthNonce(sql.FieldContainsFold(FieldPublicID, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.AuthNonce) predicate.AuthNonce { + return predicate.AuthNonce(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.AuthNonce) predicate.AuthNonce { + return predicate.AuthNonce(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.AuthNonce) predicate.AuthNonce { + return predicate.AuthNonce(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/authnonce_create.go b/internal/database/ent/db/authnonce_create.go new file mode 100644 index 00000000..e1ed2e5c --- /dev/null +++ b/internal/database/ent/db/authnonce_create.go @@ -0,0 +1,325 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" +) + +// AuthNonceCreate is the builder for creating a AuthNonce entity. +type AuthNonceCreate struct { + config + mutation *AuthNonceMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (anc *AuthNonceCreate) SetCreatedAt(t time.Time) *AuthNonceCreate { + anc.mutation.SetCreatedAt(t) + return anc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (anc *AuthNonceCreate) SetNillableCreatedAt(t *time.Time) *AuthNonceCreate { + if t != nil { + anc.SetCreatedAt(*t) + } + return anc +} + +// SetUpdatedAt sets the "updated_at" field. +func (anc *AuthNonceCreate) SetUpdatedAt(t time.Time) *AuthNonceCreate { + anc.mutation.SetUpdatedAt(t) + return anc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (anc *AuthNonceCreate) SetNillableUpdatedAt(t *time.Time) *AuthNonceCreate { + if t != nil { + anc.SetUpdatedAt(*t) + } + return anc +} + +// SetActive sets the "active" field. +func (anc *AuthNonceCreate) SetActive(b bool) *AuthNonceCreate { + anc.mutation.SetActive(b) + return anc +} + +// SetExpiresAt sets the "expires_at" field. +func (anc *AuthNonceCreate) SetExpiresAt(t time.Time) *AuthNonceCreate { + anc.mutation.SetExpiresAt(t) + return anc +} + +// SetIdentifier sets the "identifier" field. +func (anc *AuthNonceCreate) SetIdentifier(s string) *AuthNonceCreate { + anc.mutation.SetIdentifier(s) + return anc +} + +// SetPublicID sets the "public_id" field. +func (anc *AuthNonceCreate) SetPublicID(s string) *AuthNonceCreate { + anc.mutation.SetPublicID(s) + return anc +} + +// SetMetadata sets the "metadata" field. +func (anc *AuthNonceCreate) SetMetadata(m map[string]string) *AuthNonceCreate { + anc.mutation.SetMetadata(m) + return anc +} + +// SetID sets the "id" field. +func (anc *AuthNonceCreate) SetID(s string) *AuthNonceCreate { + anc.mutation.SetID(s) + return anc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (anc *AuthNonceCreate) SetNillableID(s *string) *AuthNonceCreate { + if s != nil { + anc.SetID(*s) + } + return anc +} + +// Mutation returns the AuthNonceMutation object of the builder. +func (anc *AuthNonceCreate) Mutation() *AuthNonceMutation { + return anc.mutation +} + +// Save creates the AuthNonce in the database. +func (anc *AuthNonceCreate) Save(ctx context.Context) (*AuthNonce, error) { + anc.defaults() + return withHooks(ctx, anc.sqlSave, anc.mutation, anc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (anc *AuthNonceCreate) SaveX(ctx context.Context) *AuthNonce { + v, err := anc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (anc *AuthNonceCreate) Exec(ctx context.Context) error { + _, err := anc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (anc *AuthNonceCreate) ExecX(ctx context.Context) { + if err := anc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (anc *AuthNonceCreate) defaults() { + if _, ok := anc.mutation.CreatedAt(); !ok { + v := authnonce.DefaultCreatedAt() + anc.mutation.SetCreatedAt(v) + } + if _, ok := anc.mutation.UpdatedAt(); !ok { + v := authnonce.DefaultUpdatedAt() + anc.mutation.SetUpdatedAt(v) + } + if _, ok := anc.mutation.ID(); !ok { + v := authnonce.DefaultID() + anc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (anc *AuthNonceCreate) check() error { + if _, ok := anc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "AuthNonce.created_at"`)} + } + if _, ok := anc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "AuthNonce.updated_at"`)} + } + if _, ok := anc.mutation.Active(); !ok { + return &ValidationError{Name: "active", err: errors.New(`db: missing required field "AuthNonce.active"`)} + } + if _, ok := anc.mutation.ExpiresAt(); !ok { + return &ValidationError{Name: "expires_at", err: errors.New(`db: missing required field "AuthNonce.expires_at"`)} + } + if _, ok := anc.mutation.Identifier(); !ok { + return &ValidationError{Name: "identifier", err: errors.New(`db: missing required field "AuthNonce.identifier"`)} + } + if v, ok := anc.mutation.Identifier(); ok { + if err := authnonce.IdentifierValidator(v); err != nil { + return &ValidationError{Name: "identifier", err: fmt.Errorf(`db: validator failed for field "AuthNonce.identifier": %w`, err)} + } + } + if _, ok := anc.mutation.PublicID(); !ok { + return &ValidationError{Name: "public_id", err: errors.New(`db: missing required field "AuthNonce.public_id"`)} + } + if v, ok := anc.mutation.PublicID(); ok { + if err := authnonce.PublicIDValidator(v); err != nil { + return &ValidationError{Name: "public_id", err: fmt.Errorf(`db: validator failed for field "AuthNonce.public_id": %w`, err)} + } + } + if _, ok := anc.mutation.Metadata(); !ok { + return &ValidationError{Name: "metadata", err: errors.New(`db: missing required field "AuthNonce.metadata"`)} + } + return nil +} + +func (anc *AuthNonceCreate) sqlSave(ctx context.Context) (*AuthNonce, error) { + if err := anc.check(); err != nil { + return nil, err + } + _node, _spec := anc.createSpec() + if err := sqlgraph.CreateNode(ctx, anc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected AuthNonce.ID type: %T", _spec.ID.Value) + } + } + anc.mutation.id = &_node.ID + anc.mutation.done = true + return _node, nil +} + +func (anc *AuthNonceCreate) createSpec() (*AuthNonce, *sqlgraph.CreateSpec) { + var ( + _node = &AuthNonce{config: anc.config} + _spec = sqlgraph.NewCreateSpec(authnonce.Table, sqlgraph.NewFieldSpec(authnonce.FieldID, field.TypeString)) + ) + if id, ok := anc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := anc.mutation.CreatedAt(); ok { + _spec.SetField(authnonce.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := anc.mutation.UpdatedAt(); ok { + _spec.SetField(authnonce.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if value, ok := anc.mutation.Active(); ok { + _spec.SetField(authnonce.FieldActive, field.TypeBool, value) + _node.Active = value + } + if value, ok := anc.mutation.ExpiresAt(); ok { + _spec.SetField(authnonce.FieldExpiresAt, field.TypeTime, value) + _node.ExpiresAt = value + } + if value, ok := anc.mutation.Identifier(); ok { + _spec.SetField(authnonce.FieldIdentifier, field.TypeString, value) + _node.Identifier = value + } + if value, ok := anc.mutation.PublicID(); ok { + _spec.SetField(authnonce.FieldPublicID, field.TypeString, value) + _node.PublicID = value + } + if value, ok := anc.mutation.Metadata(); ok { + _spec.SetField(authnonce.FieldMetadata, field.TypeJSON, value) + _node.Metadata = value + } + return _node, _spec +} + +// AuthNonceCreateBulk is the builder for creating many AuthNonce entities in bulk. +type AuthNonceCreateBulk struct { + config + err error + builders []*AuthNonceCreate +} + +// Save creates the AuthNonce entities in the database. +func (ancb *AuthNonceCreateBulk) Save(ctx context.Context) ([]*AuthNonce, error) { + if ancb.err != nil { + return nil, ancb.err + } + specs := make([]*sqlgraph.CreateSpec, len(ancb.builders)) + nodes := make([]*AuthNonce, len(ancb.builders)) + mutators := make([]Mutator, len(ancb.builders)) + for i := range ancb.builders { + func(i int, root context.Context) { + builder := ancb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AuthNonceMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, ancb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, ancb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, ancb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (ancb *AuthNonceCreateBulk) SaveX(ctx context.Context) []*AuthNonce { + v, err := ancb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ancb *AuthNonceCreateBulk) Exec(ctx context.Context) error { + _, err := ancb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ancb *AuthNonceCreateBulk) ExecX(ctx context.Context) { + if err := ancb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/authnonce_delete.go b/internal/database/ent/db/authnonce_delete.go new file mode 100644 index 00000000..de530b90 --- /dev/null +++ b/internal/database/ent/db/authnonce_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AuthNonceDelete is the builder for deleting a AuthNonce entity. +type AuthNonceDelete struct { + config + hooks []Hook + mutation *AuthNonceMutation +} + +// Where appends a list predicates to the AuthNonceDelete builder. +func (and *AuthNonceDelete) Where(ps ...predicate.AuthNonce) *AuthNonceDelete { + and.mutation.Where(ps...) + return and +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (and *AuthNonceDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, and.sqlExec, and.mutation, and.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (and *AuthNonceDelete) ExecX(ctx context.Context) int { + n, err := and.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (and *AuthNonceDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(authnonce.Table, sqlgraph.NewFieldSpec(authnonce.FieldID, field.TypeString)) + if ps := and.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, and.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + and.mutation.done = true + return affected, err +} + +// AuthNonceDeleteOne is the builder for deleting a single AuthNonce entity. +type AuthNonceDeleteOne struct { + and *AuthNonceDelete +} + +// Where appends a list predicates to the AuthNonceDelete builder. +func (ando *AuthNonceDeleteOne) Where(ps ...predicate.AuthNonce) *AuthNonceDeleteOne { + ando.and.mutation.Where(ps...) + return ando +} + +// Exec executes the deletion query. +func (ando *AuthNonceDeleteOne) Exec(ctx context.Context) error { + n, err := ando.and.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{authnonce.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (ando *AuthNonceDeleteOne) ExecX(ctx context.Context) { + if err := ando.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/authnonce_query.go b/internal/database/ent/db/authnonce_query.go new file mode 100644 index 00000000..962ee2fb --- /dev/null +++ b/internal/database/ent/db/authnonce_query.go @@ -0,0 +1,548 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AuthNonceQuery is the builder for querying AuthNonce entities. +type AuthNonceQuery struct { + config + ctx *QueryContext + order []authnonce.OrderOption + inters []Interceptor + predicates []predicate.AuthNonce + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AuthNonceQuery builder. +func (anq *AuthNonceQuery) Where(ps ...predicate.AuthNonce) *AuthNonceQuery { + anq.predicates = append(anq.predicates, ps...) + return anq +} + +// Limit the number of records to be returned by this query. +func (anq *AuthNonceQuery) Limit(limit int) *AuthNonceQuery { + anq.ctx.Limit = &limit + return anq +} + +// Offset to start from. +func (anq *AuthNonceQuery) Offset(offset int) *AuthNonceQuery { + anq.ctx.Offset = &offset + return anq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (anq *AuthNonceQuery) Unique(unique bool) *AuthNonceQuery { + anq.ctx.Unique = &unique + return anq +} + +// Order specifies how the records should be ordered. +func (anq *AuthNonceQuery) Order(o ...authnonce.OrderOption) *AuthNonceQuery { + anq.order = append(anq.order, o...) + return anq +} + +// First returns the first AuthNonce entity from the query. +// Returns a *NotFoundError when no AuthNonce was found. +func (anq *AuthNonceQuery) First(ctx context.Context) (*AuthNonce, error) { + nodes, err := anq.Limit(1).All(setContextOp(ctx, anq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{authnonce.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (anq *AuthNonceQuery) FirstX(ctx context.Context) *AuthNonce { + node, err := anq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first AuthNonce ID from the query. +// Returns a *NotFoundError when no AuthNonce ID was found. +func (anq *AuthNonceQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = anq.Limit(1).IDs(setContextOp(ctx, anq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{authnonce.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (anq *AuthNonceQuery) FirstIDX(ctx context.Context) string { + id, err := anq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single AuthNonce entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one AuthNonce entity is found. +// Returns a *NotFoundError when no AuthNonce entities are found. +func (anq *AuthNonceQuery) Only(ctx context.Context) (*AuthNonce, error) { + nodes, err := anq.Limit(2).All(setContextOp(ctx, anq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{authnonce.Label} + default: + return nil, &NotSingularError{authnonce.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (anq *AuthNonceQuery) OnlyX(ctx context.Context) *AuthNonce { + node, err := anq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only AuthNonce ID in the query. +// Returns a *NotSingularError when more than one AuthNonce ID is found. +// Returns a *NotFoundError when no entities are found. +func (anq *AuthNonceQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = anq.Limit(2).IDs(setContextOp(ctx, anq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{authnonce.Label} + default: + err = &NotSingularError{authnonce.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (anq *AuthNonceQuery) OnlyIDX(ctx context.Context) string { + id, err := anq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of AuthNonces. +func (anq *AuthNonceQuery) All(ctx context.Context) ([]*AuthNonce, error) { + ctx = setContextOp(ctx, anq.ctx, "All") + if err := anq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*AuthNonce, *AuthNonceQuery]() + return withInterceptors[[]*AuthNonce](ctx, anq, qr, anq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (anq *AuthNonceQuery) AllX(ctx context.Context) []*AuthNonce { + nodes, err := anq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of AuthNonce IDs. +func (anq *AuthNonceQuery) IDs(ctx context.Context) (ids []string, err error) { + if anq.ctx.Unique == nil && anq.path != nil { + anq.Unique(true) + } + ctx = setContextOp(ctx, anq.ctx, "IDs") + if err = anq.Select(authnonce.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (anq *AuthNonceQuery) IDsX(ctx context.Context) []string { + ids, err := anq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (anq *AuthNonceQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, anq.ctx, "Count") + if err := anq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, anq, querierCount[*AuthNonceQuery](), anq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (anq *AuthNonceQuery) CountX(ctx context.Context) int { + count, err := anq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (anq *AuthNonceQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, anq.ctx, "Exist") + switch _, err := anq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (anq *AuthNonceQuery) ExistX(ctx context.Context) bool { + exist, err := anq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AuthNonceQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (anq *AuthNonceQuery) Clone() *AuthNonceQuery { + if anq == nil { + return nil + } + return &AuthNonceQuery{ + config: anq.config, + ctx: anq.ctx.Clone(), + order: append([]authnonce.OrderOption{}, anq.order...), + inters: append([]Interceptor{}, anq.inters...), + predicates: append([]predicate.AuthNonce{}, anq.predicates...), + // clone intermediate query. + sql: anq.sql.Clone(), + path: anq.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.AuthNonce.Query(). +// GroupBy(authnonce.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (anq *AuthNonceQuery) GroupBy(field string, fields ...string) *AuthNonceGroupBy { + anq.ctx.Fields = append([]string{field}, fields...) + grbuild := &AuthNonceGroupBy{build: anq} + grbuild.flds = &anq.ctx.Fields + grbuild.label = authnonce.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// } +// +// client.AuthNonce.Query(). +// Select(authnonce.FieldCreatedAt). +// Scan(ctx, &v) +func (anq *AuthNonceQuery) Select(fields ...string) *AuthNonceSelect { + anq.ctx.Fields = append(anq.ctx.Fields, fields...) + sbuild := &AuthNonceSelect{AuthNonceQuery: anq} + sbuild.label = authnonce.Label + sbuild.flds, sbuild.scan = &anq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a AuthNonceSelect configured with the given aggregations. +func (anq *AuthNonceQuery) Aggregate(fns ...AggregateFunc) *AuthNonceSelect { + return anq.Select().Aggregate(fns...) +} + +func (anq *AuthNonceQuery) prepareQuery(ctx context.Context) error { + for _, inter := range anq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, anq); err != nil { + return err + } + } + } + for _, f := range anq.ctx.Fields { + if !authnonce.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if anq.path != nil { + prev, err := anq.path(ctx) + if err != nil { + return err + } + anq.sql = prev + } + return nil +} + +func (anq *AuthNonceQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthNonce, error) { + var ( + nodes = []*AuthNonce{} + _spec = anq.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*AuthNonce).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &AuthNonce{config: anq.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + if len(anq.modifiers) > 0 { + _spec.Modifiers = anq.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, anq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (anq *AuthNonceQuery) sqlCount(ctx context.Context) (int, error) { + _spec := anq.querySpec() + if len(anq.modifiers) > 0 { + _spec.Modifiers = anq.modifiers + } + _spec.Node.Columns = anq.ctx.Fields + if len(anq.ctx.Fields) > 0 { + _spec.Unique = anq.ctx.Unique != nil && *anq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, anq.driver, _spec) +} + +func (anq *AuthNonceQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(authnonce.Table, authnonce.Columns, sqlgraph.NewFieldSpec(authnonce.FieldID, field.TypeString)) + _spec.From = anq.sql + if unique := anq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if anq.path != nil { + _spec.Unique = true + } + if fields := anq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, authnonce.FieldID) + for i := range fields { + if fields[i] != authnonce.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := anq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := anq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := anq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := anq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (anq *AuthNonceQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(anq.driver.Dialect()) + t1 := builder.Table(authnonce.Table) + columns := anq.ctx.Fields + if len(columns) == 0 { + columns = authnonce.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if anq.sql != nil { + selector = anq.sql + selector.Select(selector.Columns(columns...)...) + } + if anq.ctx.Unique != nil && *anq.ctx.Unique { + selector.Distinct() + } + for _, m := range anq.modifiers { + m(selector) + } + for _, p := range anq.predicates { + p(selector) + } + for _, p := range anq.order { + p(selector) + } + if offset := anq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := anq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// Modify adds a query modifier for attaching custom logic to queries. +func (anq *AuthNonceQuery) Modify(modifiers ...func(s *sql.Selector)) *AuthNonceSelect { + anq.modifiers = append(anq.modifiers, modifiers...) + return anq.Select() +} + +// AuthNonceGroupBy is the group-by builder for AuthNonce entities. +type AuthNonceGroupBy struct { + selector + build *AuthNonceQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (angb *AuthNonceGroupBy) Aggregate(fns ...AggregateFunc) *AuthNonceGroupBy { + angb.fns = append(angb.fns, fns...) + return angb +} + +// Scan applies the selector query and scans the result into the given value. +func (angb *AuthNonceGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, angb.build.ctx, "GroupBy") + if err := angb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AuthNonceQuery, *AuthNonceGroupBy](ctx, angb.build, angb, angb.build.inters, v) +} + +func (angb *AuthNonceGroupBy) sqlScan(ctx context.Context, root *AuthNonceQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(angb.fns)) + for _, fn := range angb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*angb.flds)+len(angb.fns)) + for _, f := range *angb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*angb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := angb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// AuthNonceSelect is the builder for selecting fields of AuthNonce entities. +type AuthNonceSelect struct { + *AuthNonceQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ans *AuthNonceSelect) Aggregate(fns ...AggregateFunc) *AuthNonceSelect { + ans.fns = append(ans.fns, fns...) + return ans +} + +// Scan applies the selector query and scans the result into the given value. +func (ans *AuthNonceSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ans.ctx, "Select") + if err := ans.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AuthNonceQuery, *AuthNonceSelect](ctx, ans.AuthNonceQuery, ans, ans.inters, v) +} + +func (ans *AuthNonceSelect) sqlScan(ctx context.Context, root *AuthNonceQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(ans.fns)) + for _, fn := range ans.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*ans.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ans.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// Modify adds a query modifier for attaching custom logic to queries. +func (ans *AuthNonceSelect) Modify(modifiers ...func(s *sql.Selector)) *AuthNonceSelect { + ans.modifiers = append(ans.modifiers, modifiers...) + return ans +} diff --git a/internal/database/ent/db/authnonce_update.go b/internal/database/ent/db/authnonce_update.go new file mode 100644 index 00000000..70f5b091 --- /dev/null +++ b/internal/database/ent/db/authnonce_update.go @@ -0,0 +1,280 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// AuthNonceUpdate is the builder for updating AuthNonce entities. +type AuthNonceUpdate struct { + config + hooks []Hook + mutation *AuthNonceMutation + modifiers []func(*sql.UpdateBuilder) +} + +// Where appends a list predicates to the AuthNonceUpdate builder. +func (anu *AuthNonceUpdate) Where(ps ...predicate.AuthNonce) *AuthNonceUpdate { + anu.mutation.Where(ps...) + return anu +} + +// SetUpdatedAt sets the "updated_at" field. +func (anu *AuthNonceUpdate) SetUpdatedAt(t time.Time) *AuthNonceUpdate { + anu.mutation.SetUpdatedAt(t) + return anu +} + +// SetActive sets the "active" field. +func (anu *AuthNonceUpdate) SetActive(b bool) *AuthNonceUpdate { + anu.mutation.SetActive(b) + return anu +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (anu *AuthNonceUpdate) SetNillableActive(b *bool) *AuthNonceUpdate { + if b != nil { + anu.SetActive(*b) + } + return anu +} + +// SetMetadata sets the "metadata" field. +func (anu *AuthNonceUpdate) SetMetadata(m map[string]string) *AuthNonceUpdate { + anu.mutation.SetMetadata(m) + return anu +} + +// Mutation returns the AuthNonceMutation object of the builder. +func (anu *AuthNonceUpdate) Mutation() *AuthNonceMutation { + return anu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (anu *AuthNonceUpdate) Save(ctx context.Context) (int, error) { + anu.defaults() + return withHooks(ctx, anu.sqlSave, anu.mutation, anu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (anu *AuthNonceUpdate) SaveX(ctx context.Context) int { + affected, err := anu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (anu *AuthNonceUpdate) Exec(ctx context.Context) error { + _, err := anu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (anu *AuthNonceUpdate) ExecX(ctx context.Context) { + if err := anu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (anu *AuthNonceUpdate) defaults() { + if _, ok := anu.mutation.UpdatedAt(); !ok { + v := authnonce.UpdateDefaultUpdatedAt() + anu.mutation.SetUpdatedAt(v) + } +} + +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (anu *AuthNonceUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AuthNonceUpdate { + anu.modifiers = append(anu.modifiers, modifiers...) + return anu +} + +func (anu *AuthNonceUpdate) sqlSave(ctx context.Context) (n int, err error) { + _spec := sqlgraph.NewUpdateSpec(authnonce.Table, authnonce.Columns, sqlgraph.NewFieldSpec(authnonce.FieldID, field.TypeString)) + if ps := anu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := anu.mutation.UpdatedAt(); ok { + _spec.SetField(authnonce.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := anu.mutation.Active(); ok { + _spec.SetField(authnonce.FieldActive, field.TypeBool, value) + } + if value, ok := anu.mutation.Metadata(); ok { + _spec.SetField(authnonce.FieldMetadata, field.TypeJSON, value) + } + _spec.AddModifiers(anu.modifiers...) + if n, err = sqlgraph.UpdateNodes(ctx, anu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{authnonce.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + anu.mutation.done = true + return n, nil +} + +// AuthNonceUpdateOne is the builder for updating a single AuthNonce entity. +type AuthNonceUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AuthNonceMutation + modifiers []func(*sql.UpdateBuilder) +} + +// SetUpdatedAt sets the "updated_at" field. +func (anuo *AuthNonceUpdateOne) SetUpdatedAt(t time.Time) *AuthNonceUpdateOne { + anuo.mutation.SetUpdatedAt(t) + return anuo +} + +// SetActive sets the "active" field. +func (anuo *AuthNonceUpdateOne) SetActive(b bool) *AuthNonceUpdateOne { + anuo.mutation.SetActive(b) + return anuo +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (anuo *AuthNonceUpdateOne) SetNillableActive(b *bool) *AuthNonceUpdateOne { + if b != nil { + anuo.SetActive(*b) + } + return anuo +} + +// SetMetadata sets the "metadata" field. +func (anuo *AuthNonceUpdateOne) SetMetadata(m map[string]string) *AuthNonceUpdateOne { + anuo.mutation.SetMetadata(m) + return anuo +} + +// Mutation returns the AuthNonceMutation object of the builder. +func (anuo *AuthNonceUpdateOne) Mutation() *AuthNonceMutation { + return anuo.mutation +} + +// Where appends a list predicates to the AuthNonceUpdate builder. +func (anuo *AuthNonceUpdateOne) Where(ps ...predicate.AuthNonce) *AuthNonceUpdateOne { + anuo.mutation.Where(ps...) + return anuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (anuo *AuthNonceUpdateOne) Select(field string, fields ...string) *AuthNonceUpdateOne { + anuo.fields = append([]string{field}, fields...) + return anuo +} + +// Save executes the query and returns the updated AuthNonce entity. +func (anuo *AuthNonceUpdateOne) Save(ctx context.Context) (*AuthNonce, error) { + anuo.defaults() + return withHooks(ctx, anuo.sqlSave, anuo.mutation, anuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (anuo *AuthNonceUpdateOne) SaveX(ctx context.Context) *AuthNonce { + node, err := anuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (anuo *AuthNonceUpdateOne) Exec(ctx context.Context) error { + _, err := anuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (anuo *AuthNonceUpdateOne) ExecX(ctx context.Context) { + if err := anuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (anuo *AuthNonceUpdateOne) defaults() { + if _, ok := anuo.mutation.UpdatedAt(); !ok { + v := authnonce.UpdateDefaultUpdatedAt() + anuo.mutation.SetUpdatedAt(v) + } +} + +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (anuo *AuthNonceUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *AuthNonceUpdateOne { + anuo.modifiers = append(anuo.modifiers, modifiers...) + return anuo +} + +func (anuo *AuthNonceUpdateOne) sqlSave(ctx context.Context) (_node *AuthNonce, err error) { + _spec := sqlgraph.NewUpdateSpec(authnonce.Table, authnonce.Columns, sqlgraph.NewFieldSpec(authnonce.FieldID, field.TypeString)) + id, ok := anuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "AuthNonce.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := anuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, authnonce.FieldID) + for _, f := range fields { + if !authnonce.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != authnonce.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := anuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := anuo.mutation.UpdatedAt(); ok { + _spec.SetField(authnonce.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := anuo.mutation.Active(); ok { + _spec.SetField(authnonce.FieldActive, field.TypeBool, value) + } + if value, ok := anuo.mutation.Metadata(); ok { + _spec.SetField(authnonce.FieldMetadata, field.TypeJSON, value) + } + _spec.AddModifiers(anuo.modifiers...) + _node = &AuthNonce{config: anuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, anuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{authnonce.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + anuo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/client.go b/internal/database/ent/db/client.go index 2d0eb667..f179636d 100644 --- a/internal/database/ent/db/client.go +++ b/internal/database/ent/db/client.go @@ -20,9 +20,11 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" "github.com/cufee/aftermath/internal/database/ent/db/clan" "github.com/cufee/aftermath/internal/database/ent/db/crontask" "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/ent/db/session" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -49,12 +51,16 @@ type Client struct { AppConfiguration *AppConfigurationClient // ApplicationCommand is the client for interacting with the ApplicationCommand builders. ApplicationCommand *ApplicationCommandClient + // AuthNonce is the client for interacting with the AuthNonce builders. + AuthNonce *AuthNonceClient // Clan is the client for interacting with the Clan builders. Clan *ClanClient // CronTask is the client for interacting with the CronTask builders. CronTask *CronTaskClient // DiscordInteraction is the client for interacting with the DiscordInteraction builders. DiscordInteraction *DiscordInteractionClient + // Session is the client for interacting with the Session builders. + Session *SessionClient // User is the client for interacting with the User builders. User *UserClient // UserConnection is the client for interacting with the UserConnection builders. @@ -85,9 +91,11 @@ func (c *Client) init() { c.AchievementsSnapshot = NewAchievementsSnapshotClient(c.config) c.AppConfiguration = NewAppConfigurationClient(c.config) c.ApplicationCommand = NewApplicationCommandClient(c.config) + c.AuthNonce = NewAuthNonceClient(c.config) c.Clan = NewClanClient(c.config) c.CronTask = NewCronTaskClient(c.config) c.DiscordInteraction = NewDiscordInteractionClient(c.config) + c.Session = NewSessionClient(c.config) c.User = NewUserClient(c.config) c.UserConnection = NewUserConnectionClient(c.config) c.UserContent = NewUserContentClient(c.config) @@ -192,9 +200,11 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { AchievementsSnapshot: NewAchievementsSnapshotClient(cfg), AppConfiguration: NewAppConfigurationClient(cfg), ApplicationCommand: NewApplicationCommandClient(cfg), + AuthNonce: NewAuthNonceClient(cfg), Clan: NewClanClient(cfg), CronTask: NewCronTaskClient(cfg), DiscordInteraction: NewDiscordInteractionClient(cfg), + Session: NewSessionClient(cfg), User: NewUserClient(cfg), UserConnection: NewUserConnectionClient(cfg), UserContent: NewUserContentClient(cfg), @@ -226,9 +236,11 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) AchievementsSnapshot: NewAchievementsSnapshotClient(cfg), AppConfiguration: NewAppConfigurationClient(cfg), ApplicationCommand: NewApplicationCommandClient(cfg), + AuthNonce: NewAuthNonceClient(cfg), Clan: NewClanClient(cfg), CronTask: NewCronTaskClient(cfg), DiscordInteraction: NewDiscordInteractionClient(cfg), + Session: NewSessionClient(cfg), User: NewUserClient(cfg), UserConnection: NewUserConnectionClient(cfg), UserContent: NewUserContentClient(cfg), @@ -266,9 +278,9 @@ func (c *Client) Close() error { func (c *Client) Use(hooks ...Hook) { for _, n := range []interface{ Use(...Hook) }{ c.Account, c.AccountSnapshot, c.AchievementsSnapshot, c.AppConfiguration, - c.ApplicationCommand, c.Clan, c.CronTask, c.DiscordInteraction, c.User, - c.UserConnection, c.UserContent, c.UserSubscription, c.Vehicle, - c.VehicleAverage, c.VehicleSnapshot, + c.ApplicationCommand, c.AuthNonce, c.Clan, c.CronTask, c.DiscordInteraction, + c.Session, c.User, c.UserConnection, c.UserContent, c.UserSubscription, + c.Vehicle, c.VehicleAverage, c.VehicleSnapshot, } { n.Use(hooks...) } @@ -279,9 +291,9 @@ func (c *Client) Use(hooks ...Hook) { func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ c.Account, c.AccountSnapshot, c.AchievementsSnapshot, c.AppConfiguration, - c.ApplicationCommand, c.Clan, c.CronTask, c.DiscordInteraction, c.User, - c.UserConnection, c.UserContent, c.UserSubscription, c.Vehicle, - c.VehicleAverage, c.VehicleSnapshot, + c.ApplicationCommand, c.AuthNonce, c.Clan, c.CronTask, c.DiscordInteraction, + c.Session, c.User, c.UserConnection, c.UserContent, c.UserSubscription, + c.Vehicle, c.VehicleAverage, c.VehicleSnapshot, } { n.Intercept(interceptors...) } @@ -300,12 +312,16 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { return c.AppConfiguration.mutate(ctx, m) case *ApplicationCommandMutation: return c.ApplicationCommand.mutate(ctx, m) + case *AuthNonceMutation: + return c.AuthNonce.mutate(ctx, m) case *ClanMutation: return c.Clan.mutate(ctx, m) case *CronTaskMutation: return c.CronTask.mutate(ctx, m) case *DiscordInteractionMutation: return c.DiscordInteraction.mutate(ctx, m) + case *SessionMutation: + return c.Session.mutate(ctx, m) case *UserMutation: return c.User.mutate(ctx, m) case *UserConnectionMutation: @@ -1086,6 +1102,139 @@ func (c *ApplicationCommandClient) mutate(ctx context.Context, m *ApplicationCom } } +// AuthNonceClient is a client for the AuthNonce schema. +type AuthNonceClient struct { + config +} + +// NewAuthNonceClient returns a client for the AuthNonce from the given config. +func NewAuthNonceClient(c config) *AuthNonceClient { + return &AuthNonceClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `authnonce.Hooks(f(g(h())))`. +func (c *AuthNonceClient) Use(hooks ...Hook) { + c.hooks.AuthNonce = append(c.hooks.AuthNonce, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `authnonce.Intercept(f(g(h())))`. +func (c *AuthNonceClient) Intercept(interceptors ...Interceptor) { + c.inters.AuthNonce = append(c.inters.AuthNonce, interceptors...) +} + +// Create returns a builder for creating a AuthNonce entity. +func (c *AuthNonceClient) Create() *AuthNonceCreate { + mutation := newAuthNonceMutation(c.config, OpCreate) + return &AuthNonceCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of AuthNonce entities. +func (c *AuthNonceClient) CreateBulk(builders ...*AuthNonceCreate) *AuthNonceCreateBulk { + return &AuthNonceCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *AuthNonceClient) MapCreateBulk(slice any, setFunc func(*AuthNonceCreate, int)) *AuthNonceCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &AuthNonceCreateBulk{err: fmt.Errorf("calling to AuthNonceClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*AuthNonceCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &AuthNonceCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for AuthNonce. +func (c *AuthNonceClient) Update() *AuthNonceUpdate { + mutation := newAuthNonceMutation(c.config, OpUpdate) + return &AuthNonceUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AuthNonceClient) UpdateOne(an *AuthNonce) *AuthNonceUpdateOne { + mutation := newAuthNonceMutation(c.config, OpUpdateOne, withAuthNonce(an)) + return &AuthNonceUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AuthNonceClient) UpdateOneID(id string) *AuthNonceUpdateOne { + mutation := newAuthNonceMutation(c.config, OpUpdateOne, withAuthNonceID(id)) + return &AuthNonceUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for AuthNonce. +func (c *AuthNonceClient) Delete() *AuthNonceDelete { + mutation := newAuthNonceMutation(c.config, OpDelete) + return &AuthNonceDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AuthNonceClient) DeleteOne(an *AuthNonce) *AuthNonceDeleteOne { + return c.DeleteOneID(an.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AuthNonceClient) DeleteOneID(id string) *AuthNonceDeleteOne { + builder := c.Delete().Where(authnonce.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AuthNonceDeleteOne{builder} +} + +// Query returns a query builder for AuthNonce. +func (c *AuthNonceClient) Query() *AuthNonceQuery { + return &AuthNonceQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeAuthNonce}, + inters: c.Interceptors(), + } +} + +// Get returns a AuthNonce entity by its id. +func (c *AuthNonceClient) Get(ctx context.Context, id string) (*AuthNonce, error) { + return c.Query().Where(authnonce.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AuthNonceClient) GetX(ctx context.Context, id string) *AuthNonce { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *AuthNonceClient) Hooks() []Hook { + return c.hooks.AuthNonce +} + +// Interceptors returns the client interceptors. +func (c *AuthNonceClient) Interceptors() []Interceptor { + return c.inters.AuthNonce +} + +func (c *AuthNonceClient) mutate(ctx context.Context, m *AuthNonceMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&AuthNonceCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&AuthNonceUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&AuthNonceUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&AuthNonceDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown AuthNonce mutation op: %q", m.Op()) + } +} + // ClanClient is a client for the Clan schema. type ClanClient struct { config @@ -1517,6 +1666,155 @@ func (c *DiscordInteractionClient) mutate(ctx context.Context, m *DiscordInterac } } +// SessionClient is a client for the Session schema. +type SessionClient struct { + config +} + +// NewSessionClient returns a client for the Session from the given config. +func NewSessionClient(c config) *SessionClient { + return &SessionClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `session.Hooks(f(g(h())))`. +func (c *SessionClient) Use(hooks ...Hook) { + c.hooks.Session = append(c.hooks.Session, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `session.Intercept(f(g(h())))`. +func (c *SessionClient) Intercept(interceptors ...Interceptor) { + c.inters.Session = append(c.inters.Session, interceptors...) +} + +// Create returns a builder for creating a Session entity. +func (c *SessionClient) Create() *SessionCreate { + mutation := newSessionMutation(c.config, OpCreate) + return &SessionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Session entities. +func (c *SessionClient) CreateBulk(builders ...*SessionCreate) *SessionCreateBulk { + return &SessionCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *SessionClient) MapCreateBulk(slice any, setFunc func(*SessionCreate, int)) *SessionCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &SessionCreateBulk{err: fmt.Errorf("calling to SessionClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*SessionCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &SessionCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Session. +func (c *SessionClient) Update() *SessionUpdate { + mutation := newSessionMutation(c.config, OpUpdate) + return &SessionUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *SessionClient) UpdateOne(s *Session) *SessionUpdateOne { + mutation := newSessionMutation(c.config, OpUpdateOne, withSession(s)) + return &SessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *SessionClient) UpdateOneID(id string) *SessionUpdateOne { + mutation := newSessionMutation(c.config, OpUpdateOne, withSessionID(id)) + return &SessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Session. +func (c *SessionClient) Delete() *SessionDelete { + mutation := newSessionMutation(c.config, OpDelete) + return &SessionDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *SessionClient) DeleteOne(s *Session) *SessionDeleteOne { + return c.DeleteOneID(s.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *SessionClient) DeleteOneID(id string) *SessionDeleteOne { + builder := c.Delete().Where(session.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &SessionDeleteOne{builder} +} + +// Query returns a query builder for Session. +func (c *SessionClient) Query() *SessionQuery { + return &SessionQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeSession}, + inters: c.Interceptors(), + } +} + +// Get returns a Session entity by its id. +func (c *SessionClient) Get(ctx context.Context, id string) (*Session, error) { + return c.Query().Where(session.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *SessionClient) GetX(ctx context.Context, id string) *Session { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a Session. +func (c *SessionClient) QueryUser(s *Session) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := s.ID + step := sqlgraph.NewStep( + sqlgraph.From(session.Table, session.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, session.UserTable, session.UserColumn), + ) + fromV = sqlgraph.Neighbors(s.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *SessionClient) Hooks() []Hook { + return c.hooks.Session +} + +// Interceptors returns the client interceptors. +func (c *SessionClient) Interceptors() []Interceptor { + return c.inters.Session +} + +func (c *SessionClient) mutate(ctx context.Context, m *SessionMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&SessionCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&SessionUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&SessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&SessionDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown Session mutation op: %q", m.Op()) + } +} + // UserClient is a client for the User schema. type UserClient struct { config @@ -1689,6 +1987,22 @@ func (c *UserClient) QueryContent(u *User) *UserContentQuery { return query } +// QuerySessions queries the sessions edge of a User. +func (c *UserClient) QuerySessions(u *User) *SessionQuery { + query := (&SessionClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(session.Table, session.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.SessionsTable, user.SessionsColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *UserClient) Hooks() []Hook { return c.hooks.User @@ -2580,14 +2894,14 @@ func (c *VehicleSnapshotClient) mutate(ctx context.Context, m *VehicleSnapshotMu type ( hooks struct { Account, AccountSnapshot, AchievementsSnapshot, AppConfiguration, - ApplicationCommand, Clan, CronTask, DiscordInteraction, User, UserConnection, - UserContent, UserSubscription, Vehicle, VehicleAverage, + ApplicationCommand, AuthNonce, Clan, CronTask, DiscordInteraction, Session, + User, UserConnection, UserContent, UserSubscription, Vehicle, VehicleAverage, VehicleSnapshot []ent.Hook } inters struct { Account, AccountSnapshot, AchievementsSnapshot, AppConfiguration, - ApplicationCommand, Clan, CronTask, DiscordInteraction, User, UserConnection, - UserContent, UserSubscription, Vehicle, VehicleAverage, + ApplicationCommand, AuthNonce, Clan, CronTask, DiscordInteraction, Session, + User, UserConnection, UserContent, UserSubscription, Vehicle, VehicleAverage, VehicleSnapshot []ent.Interceptor } ) diff --git a/internal/database/ent/db/ent.go b/internal/database/ent/db/ent.go index 22bd881e..8bae5108 100644 --- a/internal/database/ent/db/ent.go +++ b/internal/database/ent/db/ent.go @@ -17,9 +17,11 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" "github.com/cufee/aftermath/internal/database/ent/db/clan" "github.com/cufee/aftermath/internal/database/ent/db/crontask" "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/ent/db/session" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -92,9 +94,11 @@ func checkColumn(table, column string) error { achievementssnapshot.Table: achievementssnapshot.ValidColumn, appconfiguration.Table: appconfiguration.ValidColumn, applicationcommand.Table: applicationcommand.ValidColumn, + authnonce.Table: authnonce.ValidColumn, clan.Table: clan.ValidColumn, crontask.Table: crontask.ValidColumn, discordinteraction.Table: discordinteraction.ValidColumn, + session.Table: session.ValidColumn, user.Table: user.ValidColumn, userconnection.Table: userconnection.ValidColumn, usercontent.Table: usercontent.ValidColumn, diff --git a/internal/database/ent/db/expose.go b/internal/database/ent/db/expose.go index 4cc0d120..cffec5a4 100644 --- a/internal/database/ent/db/expose.go +++ b/internal/database/ent/db/expose.go @@ -69,6 +69,19 @@ func (ac *ApplicationCommand) ScanValues(columns []string) ([]any, error) { return ac.scanValues(columns) } +func (an *AuthNonce) AssignValues(columns []string, values []any) error { + if an == nil { + return fmt.Errorf("AuthNonce(nil)") + } + return an.assignValues(columns, values) +} +func (an *AuthNonce) ScanValues(columns []string) ([]any, error) { + if an == nil { + return nil, fmt.Errorf("AuthNonce(nil)") + } + return an.scanValues(columns) +} + func (c *Clan) AssignValues(columns []string, values []any) error { if c == nil { return fmt.Errorf("Clan(nil)") @@ -108,6 +121,19 @@ func (di *DiscordInteraction) ScanValues(columns []string) ([]any, error) { return di.scanValues(columns) } +func (s *Session) AssignValues(columns []string, values []any) error { + if s == nil { + return fmt.Errorf("Session(nil)") + } + return s.assignValues(columns, values) +} +func (s *Session) ScanValues(columns []string) ([]any, error) { + if s == nil { + return nil, fmt.Errorf("Session(nil)") + } + return s.scanValues(columns) +} + func (u *User) AssignValues(columns []string, values []any) error { if u == nil { return fmt.Errorf("User(nil)") diff --git a/internal/database/ent/db/hook/hook.go b/internal/database/ent/db/hook/hook.go index f3a9e239..ab57723e 100644 --- a/internal/database/ent/db/hook/hook.go +++ b/internal/database/ent/db/hook/hook.go @@ -69,6 +69,18 @@ func (f ApplicationCommandFunc) Mutate(ctx context.Context, m db.Mutation) (db.V return nil, fmt.Errorf("unexpected mutation type %T. expect *db.ApplicationCommandMutation", m) } +// The AuthNonceFunc type is an adapter to allow the use of ordinary +// function as AuthNonce mutator. +type AuthNonceFunc func(context.Context, *db.AuthNonceMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f AuthNonceFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.AuthNonceMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.AuthNonceMutation", m) +} + // The ClanFunc type is an adapter to allow the use of ordinary // function as Clan mutator. type ClanFunc func(context.Context, *db.ClanMutation) (db.Value, error) @@ -105,6 +117,18 @@ func (f DiscordInteractionFunc) Mutate(ctx context.Context, m db.Mutation) (db.V return nil, fmt.Errorf("unexpected mutation type %T. expect *db.DiscordInteractionMutation", m) } +// The SessionFunc type is an adapter to allow the use of ordinary +// function as Session mutator. +type SessionFunc func(context.Context, *db.SessionMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f SessionFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.SessionMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.SessionMutation", m) +} + // The UserFunc type is an adapter to allow the use of ordinary // function as User mutator. type UserFunc func(context.Context, *db.UserMutation) (db.Value, error) diff --git a/internal/database/ent/db/migrate/schema.go b/internal/database/ent/db/migrate/schema.go index 8017db8f..095dfa27 100644 --- a/internal/database/ent/db/migrate/schema.go +++ b/internal/database/ent/db/migrate/schema.go @@ -85,7 +85,7 @@ var ( Symbol: "account_snapshots_accounts_account_snapshots", Columns: []*schema.Column{AccountSnapshotsColumns[10]}, RefColumns: []*schema.Column{AccountsColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, Indexes: []*schema.Index{ @@ -138,7 +138,7 @@ var ( Symbol: "achievements_snapshots_accounts_achievement_snapshots", Columns: []*schema.Column{AchievementsSnapshotsColumns[8]}, RefColumns: []*schema.Column{AccountsColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, Indexes: []*schema.Index{ @@ -218,6 +218,30 @@ var ( }, }, } + // AuthNoncesColumns holds the columns for the "auth_nonces" table. + AuthNoncesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "active", Type: field.TypeBool}, + {Name: "expires_at", Type: field.TypeTime}, + {Name: "identifier", Type: field.TypeString}, + {Name: "public_id", Type: field.TypeString, Unique: true}, + {Name: "metadata", Type: field.TypeJSON}, + } + // AuthNoncesTable holds the schema information for the "auth_nonces" table. + AuthNoncesTable = &schema.Table{ + Name: "auth_nonces", + Columns: AuthNoncesColumns, + PrimaryKey: []*schema.Column{AuthNoncesColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "authnonce_public_id_active_expires_at", + Unique: false, + Columns: []*schema.Column{AuthNoncesColumns[6], AuthNoncesColumns[3], AuthNoncesColumns[4]}, + }, + }, + } // ClansColumns holds the columns for the "clans" table. ClansColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, @@ -321,7 +345,7 @@ var ( Symbol: "discord_interactions_users_discord_interactions", Columns: []*schema.Column{DiscordInteractionsColumns[8]}, RefColumns: []*schema.Column{UsersColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, Indexes: []*schema.Index{ @@ -352,11 +376,43 @@ var ( }, }, } + // SessionsColumns holds the columns for the "sessions" table. + SessionsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "expires_at", Type: field.TypeTime}, + {Name: "public_id", Type: field.TypeString, Unique: true}, + {Name: "metadata", Type: field.TypeJSON}, + {Name: "user_id", Type: field.TypeString}, + } + // SessionsTable holds the schema information for the "sessions" table. + SessionsTable = &schema.Table{ + Name: "sessions", + Columns: SessionsColumns, + PrimaryKey: []*schema.Column{SessionsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "sessions_users_sessions", + Columns: []*schema.Column{SessionsColumns[6]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.Cascade, + }, + }, + Indexes: []*schema.Index{ + { + Name: "session_public_id_expires_at", + Unique: false, + Columns: []*schema.Column{SessionsColumns[4], SessionsColumns[3]}, + }, + }, + } // UsersColumns holds the columns for the "users" table. UsersColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, + {Name: "username", Type: field.TypeString, Default: ""}, {Name: "permissions", Type: field.TypeString, Default: ""}, {Name: "feature_flags", Type: field.TypeJSON, Nullable: true}, } @@ -371,6 +427,11 @@ var ( Unique: false, Columns: []*schema.Column{UsersColumns[0]}, }, + { + Name: "user_username", + Unique: false, + Columns: []*schema.Column{UsersColumns[3]}, + }, }, } // UserConnectionsColumns holds the columns for the "user_connections" table. @@ -394,7 +455,7 @@ var ( Symbol: "user_connections_users_connections", Columns: []*schema.Column{UserConnectionsColumns[7]}, RefColumns: []*schema.Column{UsersColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, Indexes: []*schema.Index{ @@ -451,7 +512,7 @@ var ( Symbol: "user_contents_users_content", Columns: []*schema.Column{UserContentsColumns[7]}, RefColumns: []*schema.Column{UsersColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, Indexes: []*schema.Index{ @@ -503,7 +564,7 @@ var ( Symbol: "user_subscriptions_users_subscriptions", Columns: []*schema.Column{UserSubscriptionsColumns[7]}, RefColumns: []*schema.Column{UsersColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, Indexes: []*schema.Index{ @@ -598,7 +659,7 @@ var ( Symbol: "vehicle_snapshots_accounts_vehicle_snapshots", Columns: []*schema.Column{VehicleSnapshotsColumns[9]}, RefColumns: []*schema.Column{AccountsColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, Indexes: []*schema.Index{ @@ -636,9 +697,11 @@ var ( AchievementsSnapshotsTable, AppConfigurationsTable, ApplicationCommandsTable, + AuthNoncesTable, ClansTable, CronTasksTable, DiscordInteractionsTable, + SessionsTable, UsersTable, UserConnectionsTable, UserContentsTable, @@ -654,6 +717,7 @@ func init() { AccountSnapshotsTable.ForeignKeys[0].RefTable = AccountsTable AchievementsSnapshotsTable.ForeignKeys[0].RefTable = AccountsTable DiscordInteractionsTable.ForeignKeys[0].RefTable = UsersTable + SessionsTable.ForeignKeys[0].RefTable = UsersTable UserConnectionsTable.ForeignKeys[0].RefTable = UsersTable UserContentsTable.ForeignKeys[0].RefTable = UsersTable UserSubscriptionsTable.ForeignKeys[0].RefTable = UsersTable diff --git a/internal/database/ent/db/mutation.go b/internal/database/ent/db/mutation.go index 15855c1d..148fcef9 100644 --- a/internal/database/ent/db/mutation.go +++ b/internal/database/ent/db/mutation.go @@ -16,10 +16,12 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" "github.com/cufee/aftermath/internal/database/ent/db/clan" "github.com/cufee/aftermath/internal/database/ent/db/crontask" "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/session" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -46,9 +48,11 @@ const ( TypeAchievementsSnapshot = "AchievementsSnapshot" TypeAppConfiguration = "AppConfiguration" TypeApplicationCommand = "ApplicationCommand" + TypeAuthNonce = "AuthNonce" TypeClan = "Clan" TypeCronTask = "CronTask" TypeDiscordInteraction = "DiscordInteraction" + TypeSession = "Session" TypeUser = "User" TypeUserConnection = "UserConnection" TypeUserContent = "UserContent" @@ -3954,39 +3958,36 @@ func (m *ApplicationCommandMutation) ResetEdge(name string) error { return fmt.Errorf("unknown ApplicationCommand edge %s", name) } -// ClanMutation represents an operation that mutates the Clan nodes in the graph. -type ClanMutation struct { +// AuthNonceMutation represents an operation that mutates the AuthNonce nodes in the graph. +type AuthNonceMutation struct { config - op Op - typ string - id *string - created_at *time.Time - updated_at *time.Time - tag *string - name *string - emblem_id *string - members *[]string - appendmembers []string - clearedFields map[string]struct{} - accounts map[string]struct{} - removedaccounts map[string]struct{} - clearedaccounts bool - done bool - oldValue func(context.Context) (*Clan, error) - predicates []predicate.Clan + op Op + typ string + id *string + created_at *time.Time + updated_at *time.Time + active *bool + expires_at *time.Time + identifier *string + public_id *string + metadata *map[string]string + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*AuthNonce, error) + predicates []predicate.AuthNonce } -var _ ent.Mutation = (*ClanMutation)(nil) +var _ ent.Mutation = (*AuthNonceMutation)(nil) -// clanOption allows management of the mutation configuration using functional options. -type clanOption func(*ClanMutation) +// authnonceOption allows management of the mutation configuration using functional options. +type authnonceOption func(*AuthNonceMutation) -// newClanMutation creates new mutation for the Clan entity. -func newClanMutation(c config, op Op, opts ...clanOption) *ClanMutation { - m := &ClanMutation{ +// newAuthNonceMutation creates new mutation for the AuthNonce entity. +func newAuthNonceMutation(c config, op Op, opts ...authnonceOption) *AuthNonceMutation { + m := &AuthNonceMutation{ config: c, op: op, - typ: TypeClan, + typ: TypeAuthNonce, clearedFields: make(map[string]struct{}), } for _, opt := range opts { @@ -3995,20 +3996,20 @@ func newClanMutation(c config, op Op, opts ...clanOption) *ClanMutation { return m } -// withClanID sets the ID field of the mutation. -func withClanID(id string) clanOption { - return func(m *ClanMutation) { +// withAuthNonceID sets the ID field of the mutation. +func withAuthNonceID(id string) authnonceOption { + return func(m *AuthNonceMutation) { var ( err error once sync.Once - value *Clan + value *AuthNonce ) - m.oldValue = func(ctx context.Context) (*Clan, error) { + m.oldValue = func(ctx context.Context) (*AuthNonce, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { - value, err = m.Client().Clan.Get(ctx, id) + value, err = m.Client().AuthNonce.Get(ctx, id) } }) return value, err @@ -4017,10 +4018,10 @@ func withClanID(id string) clanOption { } } -// withClan sets the old Clan of the mutation. -func withClan(node *Clan) clanOption { - return func(m *ClanMutation) { - m.oldValue = func(context.Context) (*Clan, error) { +// withAuthNonce sets the old AuthNonce of the mutation. +func withAuthNonce(node *AuthNonce) authnonceOption { + return func(m *AuthNonceMutation) { + m.oldValue = func(context.Context) (*AuthNonce, error) { return node, nil } m.id = &node.ID @@ -4029,7 +4030,7 @@ func withClan(node *Clan) clanOption { // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. -func (m ClanMutation) Client() *Client { +func (m AuthNonceMutation) Client() *Client { client := &Client{config: m.config} client.init() return client @@ -4037,7 +4038,7 @@ func (m ClanMutation) Client() *Client { // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. -func (m ClanMutation) Tx() (*Tx, error) { +func (m AuthNonceMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } @@ -4047,14 +4048,14 @@ func (m ClanMutation) Tx() (*Tx, error) { } // SetID sets the value of the id field. Note that this -// operation is only accepted on creation of Clan entities. -func (m *ClanMutation) SetID(id string) { +// operation is only accepted on creation of AuthNonce entities. +func (m *AuthNonceMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. -func (m *ClanMutation) ID() (id string, exists bool) { +func (m *AuthNonceMutation) ID() (id string, exists bool) { if m.id == nil { return } @@ -4065,7 +4066,7 @@ func (m *ClanMutation) ID() (id string, exists bool) { // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. -func (m *ClanMutation) IDs(ctx context.Context) ([]string, error) { +func (m *AuthNonceMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() @@ -4074,19 +4075,19 @@ func (m *ClanMutation) IDs(ctx context.Context) ([]string, error) { } fallthrough case m.op.Is(OpUpdate | OpDelete): - return m.Client().Clan.Query().Where(m.predicates...).IDs(ctx) + return m.Client().AuthNonce.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetCreatedAt sets the "created_at" field. -func (m *ClanMutation) SetCreatedAt(t time.Time) { +func (m *AuthNonceMutation) SetCreatedAt(t time.Time) { m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *ClanMutation) CreatedAt() (r time.Time, exists bool) { +func (m *AuthNonceMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -4094,10 +4095,10 @@ func (m *ClanMutation) CreatedAt() (r time.Time, exists bool) { return *v, true } -// OldCreatedAt returns the old "created_at" field's value of the Clan entity. -// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// OldCreatedAt returns the old "created_at" field's value of the AuthNonce entity. +// If the AuthNonce object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { +func (m *AuthNonceMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -4112,17 +4113,17 @@ func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error } // ResetCreatedAt resets all changes to the "created_at" field. -func (m *ClanMutation) ResetCreatedAt() { +func (m *AuthNonceMutation) ResetCreatedAt() { m.created_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *ClanMutation) SetUpdatedAt(t time.Time) { +func (m *AuthNonceMutation) SetUpdatedAt(t time.Time) { m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *ClanMutation) UpdatedAt() (r time.Time, exists bool) { +func (m *AuthNonceMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -4130,10 +4131,10 @@ func (m *ClanMutation) UpdatedAt() (r time.Time, exists bool) { return *v, true } -// OldUpdatedAt returns the old "updated_at" field's value of the Clan entity. -// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// OldUpdatedAt returns the old "updated_at" field's value of the AuthNonce entity. +// If the AuthNonce object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { +func (m *AuthNonceMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -4148,245 +4149,199 @@ func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error } // ResetUpdatedAt resets all changes to the "updated_at" field. -func (m *ClanMutation) ResetUpdatedAt() { +func (m *AuthNonceMutation) ResetUpdatedAt() { m.updated_at = nil } -// SetTag sets the "tag" field. -func (m *ClanMutation) SetTag(s string) { - m.tag = &s +// SetActive sets the "active" field. +func (m *AuthNonceMutation) SetActive(b bool) { + m.active = &b } -// Tag returns the value of the "tag" field in the mutation. -func (m *ClanMutation) Tag() (r string, exists bool) { - v := m.tag +// Active returns the value of the "active" field in the mutation. +func (m *AuthNonceMutation) Active() (r bool, exists bool) { + v := m.active if v == nil { return } return *v, true } -// OldTag returns the old "tag" field's value of the Clan entity. -// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// OldActive returns the old "active" field's value of the AuthNonce entity. +// If the AuthNonce object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldTag(ctx context.Context) (v string, err error) { +func (m *AuthNonceMutation) OldActive(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldTag is only allowed on UpdateOne operations") + return v, errors.New("OldActive is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldTag requires an ID field in the mutation") + return v, errors.New("OldActive requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldTag: %w", err) + return v, fmt.Errorf("querying old value for OldActive: %w", err) } - return oldValue.Tag, nil + return oldValue.Active, nil } -// ResetTag resets all changes to the "tag" field. -func (m *ClanMutation) ResetTag() { - m.tag = nil +// ResetActive resets all changes to the "active" field. +func (m *AuthNonceMutation) ResetActive() { + m.active = nil } -// SetName sets the "name" field. -func (m *ClanMutation) SetName(s string) { - m.name = &s +// SetExpiresAt sets the "expires_at" field. +func (m *AuthNonceMutation) SetExpiresAt(t time.Time) { + m.expires_at = &t } -// Name returns the value of the "name" field in the mutation. -func (m *ClanMutation) Name() (r string, exists bool) { - v := m.name +// ExpiresAt returns the value of the "expires_at" field in the mutation. +func (m *AuthNonceMutation) ExpiresAt() (r time.Time, exists bool) { + v := m.expires_at if v == nil { return } return *v, true } -// OldName returns the old "name" field's value of the Clan entity. -// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// OldExpiresAt returns the old "expires_at" field's value of the AuthNonce entity. +// If the AuthNonce object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldName(ctx context.Context) (v string, err error) { +func (m *AuthNonceMutation) OldExpiresAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldName is only allowed on UpdateOne operations") + return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldName requires an ID field in the mutation") + return v, errors.New("OldExpiresAt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldName: %w", err) + return v, fmt.Errorf("querying old value for OldExpiresAt: %w", err) } - return oldValue.Name, nil + return oldValue.ExpiresAt, nil } -// ResetName resets all changes to the "name" field. -func (m *ClanMutation) ResetName() { - m.name = nil +// ResetExpiresAt resets all changes to the "expires_at" field. +func (m *AuthNonceMutation) ResetExpiresAt() { + m.expires_at = nil } -// SetEmblemID sets the "emblem_id" field. -func (m *ClanMutation) SetEmblemID(s string) { - m.emblem_id = &s +// SetIdentifier sets the "identifier" field. +func (m *AuthNonceMutation) SetIdentifier(s string) { + m.identifier = &s } -// EmblemID returns the value of the "emblem_id" field in the mutation. -func (m *ClanMutation) EmblemID() (r string, exists bool) { - v := m.emblem_id +// Identifier returns the value of the "identifier" field in the mutation. +func (m *AuthNonceMutation) Identifier() (r string, exists bool) { + v := m.identifier if v == nil { return } return *v, true } -// OldEmblemID returns the old "emblem_id" field's value of the Clan entity. -// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// OldIdentifier returns the old "identifier" field's value of the AuthNonce entity. +// If the AuthNonce object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldEmblemID(ctx context.Context) (v string, err error) { +func (m *AuthNonceMutation) OldIdentifier(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldEmblemID is only allowed on UpdateOne operations") + return v, errors.New("OldIdentifier is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldEmblemID requires an ID field in the mutation") + return v, errors.New("OldIdentifier requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldEmblemID: %w", err) + return v, fmt.Errorf("querying old value for OldIdentifier: %w", err) } - return oldValue.EmblemID, nil -} - -// ClearEmblemID clears the value of the "emblem_id" field. -func (m *ClanMutation) ClearEmblemID() { - m.emblem_id = nil - m.clearedFields[clan.FieldEmblemID] = struct{}{} -} - -// EmblemIDCleared returns if the "emblem_id" field was cleared in this mutation. -func (m *ClanMutation) EmblemIDCleared() bool { - _, ok := m.clearedFields[clan.FieldEmblemID] - return ok + return oldValue.Identifier, nil } -// ResetEmblemID resets all changes to the "emblem_id" field. -func (m *ClanMutation) ResetEmblemID() { - m.emblem_id = nil - delete(m.clearedFields, clan.FieldEmblemID) +// ResetIdentifier resets all changes to the "identifier" field. +func (m *AuthNonceMutation) ResetIdentifier() { + m.identifier = nil } -// SetMembers sets the "members" field. -func (m *ClanMutation) SetMembers(s []string) { - m.members = &s - m.appendmembers = nil +// SetPublicID sets the "public_id" field. +func (m *AuthNonceMutation) SetPublicID(s string) { + m.public_id = &s } -// Members returns the value of the "members" field in the mutation. -func (m *ClanMutation) Members() (r []string, exists bool) { - v := m.members +// PublicID returns the value of the "public_id" field in the mutation. +func (m *AuthNonceMutation) PublicID() (r string, exists bool) { + v := m.public_id if v == nil { return } return *v, true } -// OldMembers returns the old "members" field's value of the Clan entity. -// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// OldPublicID returns the old "public_id" field's value of the AuthNonce entity. +// If the AuthNonce object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ClanMutation) OldMembers(ctx context.Context) (v []string, err error) { +func (m *AuthNonceMutation) OldPublicID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldMembers is only allowed on UpdateOne operations") + return v, errors.New("OldPublicID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldMembers requires an ID field in the mutation") + return v, errors.New("OldPublicID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldMembers: %w", err) + return v, fmt.Errorf("querying old value for OldPublicID: %w", err) } - return oldValue.Members, nil -} - -// AppendMembers adds s to the "members" field. -func (m *ClanMutation) AppendMembers(s []string) { - m.appendmembers = append(m.appendmembers, s...) + return oldValue.PublicID, nil } -// AppendedMembers returns the list of values that were appended to the "members" field in this mutation. -func (m *ClanMutation) AppendedMembers() ([]string, bool) { - if len(m.appendmembers) == 0 { - return nil, false - } - return m.appendmembers, true +// ResetPublicID resets all changes to the "public_id" field. +func (m *AuthNonceMutation) ResetPublicID() { + m.public_id = nil } -// ResetMembers resets all changes to the "members" field. -func (m *ClanMutation) ResetMembers() { - m.members = nil - m.appendmembers = nil +// SetMetadata sets the "metadata" field. +func (m *AuthNonceMutation) SetMetadata(value map[string]string) { + m.metadata = &value } -// AddAccountIDs adds the "accounts" edge to the Account entity by ids. -func (m *ClanMutation) AddAccountIDs(ids ...string) { - if m.accounts == nil { - m.accounts = make(map[string]struct{}) - } - for i := range ids { - m.accounts[ids[i]] = struct{}{} +// Metadata returns the value of the "metadata" field in the mutation. +func (m *AuthNonceMutation) Metadata() (r map[string]string, exists bool) { + v := m.metadata + if v == nil { + return } + return *v, true } -// ClearAccounts clears the "accounts" edge to the Account entity. -func (m *ClanMutation) ClearAccounts() { - m.clearedaccounts = true -} - -// AccountsCleared reports if the "accounts" edge to the Account entity was cleared. -func (m *ClanMutation) AccountsCleared() bool { - return m.clearedaccounts -} - -// RemoveAccountIDs removes the "accounts" edge to the Account entity by IDs. -func (m *ClanMutation) RemoveAccountIDs(ids ...string) { - if m.removedaccounts == nil { - m.removedaccounts = make(map[string]struct{}) - } - for i := range ids { - delete(m.accounts, ids[i]) - m.removedaccounts[ids[i]] = struct{}{} +// OldMetadata returns the old "metadata" field's value of the AuthNonce entity. +// If the AuthNonce object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AuthNonceMutation) OldMetadata(ctx context.Context) (v map[string]string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMetadata is only allowed on UpdateOne operations") } -} - -// RemovedAccounts returns the removed IDs of the "accounts" edge to the Account entity. -func (m *ClanMutation) RemovedAccountsIDs() (ids []string) { - for id := range m.removedaccounts { - ids = append(ids, id) + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMetadata requires an ID field in the mutation") } - return -} - -// AccountsIDs returns the "accounts" edge IDs in the mutation. -func (m *ClanMutation) AccountsIDs() (ids []string) { - for id := range m.accounts { - ids = append(ids, id) + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMetadata: %w", err) } - return + return oldValue.Metadata, nil } -// ResetAccounts resets all changes to the "accounts" edge. -func (m *ClanMutation) ResetAccounts() { - m.accounts = nil - m.clearedaccounts = false - m.removedaccounts = nil +// ResetMetadata resets all changes to the "metadata" field. +func (m *AuthNonceMutation) ResetMetadata() { + m.metadata = nil } -// Where appends a list predicates to the ClanMutation builder. -func (m *ClanMutation) Where(ps ...predicate.Clan) { +// Where appends a list predicates to the AuthNonceMutation builder. +func (m *AuthNonceMutation) Where(ps ...predicate.AuthNonce) { m.predicates = append(m.predicates, ps...) } -// WhereP appends storage-level predicates to the ClanMutation builder. Using this method, +// WhereP appends storage-level predicates to the AuthNonceMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. -func (m *ClanMutation) WhereP(ps ...func(*sql.Selector)) { - p := make([]predicate.Clan, len(ps)) +func (m *AuthNonceMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.AuthNonce, len(ps)) for i := range ps { p[i] = ps[i] } @@ -4394,42 +4349,45 @@ func (m *ClanMutation) WhereP(ps ...func(*sql.Selector)) { } // Op returns the operation name. -func (m *ClanMutation) Op() Op { +func (m *AuthNonceMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. -func (m *ClanMutation) SetOp(op Op) { +func (m *AuthNonceMutation) SetOp(op Op) { m.op = op } -// Type returns the node type of this mutation (Clan). -func (m *ClanMutation) Type() string { +// Type returns the node type of this mutation (AuthNonce). +func (m *AuthNonceMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). -func (m *ClanMutation) Fields() []string { - fields := make([]string, 0, 6) +func (m *AuthNonceMutation) Fields() []string { + fields := make([]string, 0, 7) if m.created_at != nil { - fields = append(fields, clan.FieldCreatedAt) + fields = append(fields, authnonce.FieldCreatedAt) } if m.updated_at != nil { - fields = append(fields, clan.FieldUpdatedAt) + fields = append(fields, authnonce.FieldUpdatedAt) } - if m.tag != nil { - fields = append(fields, clan.FieldTag) + if m.active != nil { + fields = append(fields, authnonce.FieldActive) } - if m.name != nil { - fields = append(fields, clan.FieldName) + if m.expires_at != nil { + fields = append(fields, authnonce.FieldExpiresAt) } - if m.emblem_id != nil { - fields = append(fields, clan.FieldEmblemID) + if m.identifier != nil { + fields = append(fields, authnonce.FieldIdentifier) } - if m.members != nil { - fields = append(fields, clan.FieldMembers) + if m.public_id != nil { + fields = append(fields, authnonce.FieldPublicID) + } + if m.metadata != nil { + fields = append(fields, authnonce.FieldMetadata) } return fields } @@ -4437,20 +4395,22 @@ func (m *ClanMutation) Fields() []string { // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. -func (m *ClanMutation) Field(name string) (ent.Value, bool) { +func (m *AuthNonceMutation) Field(name string) (ent.Value, bool) { switch name { - case clan.FieldCreatedAt: + case authnonce.FieldCreatedAt: return m.CreatedAt() - case clan.FieldUpdatedAt: + case authnonce.FieldUpdatedAt: return m.UpdatedAt() - case clan.FieldTag: - return m.Tag() - case clan.FieldName: - return m.Name() - case clan.FieldEmblemID: - return m.EmblemID() - case clan.FieldMembers: - return m.Members() + case authnonce.FieldActive: + return m.Active() + case authnonce.FieldExpiresAt: + return m.ExpiresAt() + case authnonce.FieldIdentifier: + return m.Identifier() + case authnonce.FieldPublicID: + return m.PublicID() + case authnonce.FieldMetadata: + return m.Metadata() } return nil, false } @@ -4458,272 +4418,235 @@ func (m *ClanMutation) Field(name string) (ent.Value, bool) { // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. -func (m *ClanMutation) OldField(ctx context.Context, name string) (ent.Value, error) { +func (m *AuthNonceMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { - case clan.FieldCreatedAt: + case authnonce.FieldCreatedAt: return m.OldCreatedAt(ctx) - case clan.FieldUpdatedAt: + case authnonce.FieldUpdatedAt: return m.OldUpdatedAt(ctx) - case clan.FieldTag: - return m.OldTag(ctx) - case clan.FieldName: - return m.OldName(ctx) - case clan.FieldEmblemID: - return m.OldEmblemID(ctx) - case clan.FieldMembers: - return m.OldMembers(ctx) + case authnonce.FieldActive: + return m.OldActive(ctx) + case authnonce.FieldExpiresAt: + return m.OldExpiresAt(ctx) + case authnonce.FieldIdentifier: + return m.OldIdentifier(ctx) + case authnonce.FieldPublicID: + return m.OldPublicID(ctx) + case authnonce.FieldMetadata: + return m.OldMetadata(ctx) } - return nil, fmt.Errorf("unknown Clan field %s", name) + return nil, fmt.Errorf("unknown AuthNonce field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. -func (m *ClanMutation) SetField(name string, value ent.Value) error { +func (m *AuthNonceMutation) SetField(name string, value ent.Value) error { switch name { - case clan.FieldCreatedAt: + case authnonce.FieldCreatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil - case clan.FieldUpdatedAt: + case authnonce.FieldUpdatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUpdatedAt(v) return nil - case clan.FieldTag: - v, ok := value.(string) + case authnonce.FieldActive: + v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetTag(v) + m.SetActive(v) return nil - case clan.FieldName: + case authnonce.FieldExpiresAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetExpiresAt(v) + return nil + case authnonce.FieldIdentifier: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetName(v) + m.SetIdentifier(v) return nil - case clan.FieldEmblemID: + case authnonce.FieldPublicID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetEmblemID(v) + m.SetPublicID(v) return nil - case clan.FieldMembers: - v, ok := value.([]string) + case authnonce.FieldMetadata: + v, ok := value.(map[string]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetMembers(v) + m.SetMetadata(v) return nil } - return fmt.Errorf("unknown Clan field %s", name) + return fmt.Errorf("unknown AuthNonce field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. -func (m *ClanMutation) AddedFields() []string { +func (m *AuthNonceMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. -func (m *ClanMutation) AddedField(name string) (ent.Value, bool) { +func (m *AuthNonceMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. -func (m *ClanMutation) AddField(name string, value ent.Value) error { +func (m *AuthNonceMutation) AddField(name string, value ent.Value) error { switch name { } - return fmt.Errorf("unknown Clan numeric field %s", name) + return fmt.Errorf("unknown AuthNonce numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. -func (m *ClanMutation) ClearedFields() []string { - var fields []string - if m.FieldCleared(clan.FieldEmblemID) { - fields = append(fields, clan.FieldEmblemID) - } - return fields +func (m *AuthNonceMutation) ClearedFields() []string { + return nil } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. -func (m *ClanMutation) FieldCleared(name string) bool { +func (m *AuthNonceMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. -func (m *ClanMutation) ClearField(name string) error { - switch name { - case clan.FieldEmblemID: - m.ClearEmblemID() - return nil - } - return fmt.Errorf("unknown Clan nullable field %s", name) +func (m *AuthNonceMutation) ClearField(name string) error { + return fmt.Errorf("unknown AuthNonce nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. -func (m *ClanMutation) ResetField(name string) error { +func (m *AuthNonceMutation) ResetField(name string) error { switch name { - case clan.FieldCreatedAt: + case authnonce.FieldCreatedAt: m.ResetCreatedAt() return nil - case clan.FieldUpdatedAt: + case authnonce.FieldUpdatedAt: m.ResetUpdatedAt() return nil - case clan.FieldTag: - m.ResetTag() + case authnonce.FieldActive: + m.ResetActive() return nil - case clan.FieldName: - m.ResetName() + case authnonce.FieldExpiresAt: + m.ResetExpiresAt() return nil - case clan.FieldEmblemID: - m.ResetEmblemID() + case authnonce.FieldIdentifier: + m.ResetIdentifier() return nil - case clan.FieldMembers: - m.ResetMembers() + case authnonce.FieldPublicID: + m.ResetPublicID() + return nil + case authnonce.FieldMetadata: + m.ResetMetadata() return nil } - return fmt.Errorf("unknown Clan field %s", name) + return fmt.Errorf("unknown AuthNonce field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. -func (m *ClanMutation) AddedEdges() []string { - edges := make([]string, 0, 1) - if m.accounts != nil { - edges = append(edges, clan.EdgeAccounts) - } +func (m *AuthNonceMutation) AddedEdges() []string { + edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. -func (m *ClanMutation) AddedIDs(name string) []ent.Value { - switch name { - case clan.EdgeAccounts: - ids := make([]ent.Value, 0, len(m.accounts)) - for id := range m.accounts { - ids = append(ids, id) - } - return ids - } +func (m *AuthNonceMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. -func (m *ClanMutation) RemovedEdges() []string { - edges := make([]string, 0, 1) - if m.removedaccounts != nil { - edges = append(edges, clan.EdgeAccounts) - } +func (m *AuthNonceMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. -func (m *ClanMutation) RemovedIDs(name string) []ent.Value { - switch name { - case clan.EdgeAccounts: - ids := make([]ent.Value, 0, len(m.removedaccounts)) - for id := range m.removedaccounts { - ids = append(ids, id) - } - return ids - } +func (m *AuthNonceMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. -func (m *ClanMutation) ClearedEdges() []string { - edges := make([]string, 0, 1) - if m.clearedaccounts { - edges = append(edges, clan.EdgeAccounts) - } +func (m *AuthNonceMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. -func (m *ClanMutation) EdgeCleared(name string) bool { - switch name { - case clan.EdgeAccounts: - return m.clearedaccounts - } +func (m *AuthNonceMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. -func (m *ClanMutation) ClearEdge(name string) error { - switch name { - } - return fmt.Errorf("unknown Clan unique edge %s", name) +func (m *AuthNonceMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown AuthNonce unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. -func (m *ClanMutation) ResetEdge(name string) error { - switch name { - case clan.EdgeAccounts: - m.ResetAccounts() - return nil - } - return fmt.Errorf("unknown Clan edge %s", name) +func (m *AuthNonceMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown AuthNonce edge %s", name) } -// CronTaskMutation represents an operation that mutates the CronTask nodes in the graph. -type CronTaskMutation struct { +// ClanMutation represents an operation that mutates the Clan nodes in the graph. +type ClanMutation struct { config op Op typ string id *string created_at *time.Time updated_at *time.Time - _type *models.TaskType - reference_id *string - targets *[]string - appendtargets []string - status *models.TaskStatus - scheduled_after *time.Time - last_run *time.Time - tries_left *int - addtries_left *int - logs *[]models.TaskLog - appendlogs []models.TaskLog - data *map[string]string + tag *string + name *string + emblem_id *string + members *[]string + appendmembers []string clearedFields map[string]struct{} + accounts map[string]struct{} + removedaccounts map[string]struct{} + clearedaccounts bool done bool - oldValue func(context.Context) (*CronTask, error) - predicates []predicate.CronTask + oldValue func(context.Context) (*Clan, error) + predicates []predicate.Clan } -var _ ent.Mutation = (*CronTaskMutation)(nil) +var _ ent.Mutation = (*ClanMutation)(nil) -// crontaskOption allows management of the mutation configuration using functional options. -type crontaskOption func(*CronTaskMutation) +// clanOption allows management of the mutation configuration using functional options. +type clanOption func(*ClanMutation) -// newCronTaskMutation creates new mutation for the CronTask entity. -func newCronTaskMutation(c config, op Op, opts ...crontaskOption) *CronTaskMutation { - m := &CronTaskMutation{ +// newClanMutation creates new mutation for the Clan entity. +func newClanMutation(c config, op Op, opts ...clanOption) *ClanMutation { + m := &ClanMutation{ config: c, op: op, - typ: TypeCronTask, + typ: TypeClan, clearedFields: make(map[string]struct{}), } for _, opt := range opts { @@ -4732,20 +4655,20 @@ func newCronTaskMutation(c config, op Op, opts ...crontaskOption) *CronTaskMutat return m } -// withCronTaskID sets the ID field of the mutation. -func withCronTaskID(id string) crontaskOption { - return func(m *CronTaskMutation) { +// withClanID sets the ID field of the mutation. +func withClanID(id string) clanOption { + return func(m *ClanMutation) { var ( err error once sync.Once - value *CronTask + value *Clan ) - m.oldValue = func(ctx context.Context) (*CronTask, error) { + m.oldValue = func(ctx context.Context) (*Clan, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { - value, err = m.Client().CronTask.Get(ctx, id) + value, err = m.Client().Clan.Get(ctx, id) } }) return value, err @@ -4754,10 +4677,10 @@ func withCronTaskID(id string) crontaskOption { } } -// withCronTask sets the old CronTask of the mutation. -func withCronTask(node *CronTask) crontaskOption { - return func(m *CronTaskMutation) { - m.oldValue = func(context.Context) (*CronTask, error) { +// withClan sets the old Clan of the mutation. +func withClan(node *Clan) clanOption { + return func(m *ClanMutation) { + m.oldValue = func(context.Context) (*Clan, error) { return node, nil } m.id = &node.ID @@ -4766,7 +4689,7 @@ func withCronTask(node *CronTask) crontaskOption { // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. -func (m CronTaskMutation) Client() *Client { +func (m ClanMutation) Client() *Client { client := &Client{config: m.config} client.init() return client @@ -4774,7 +4697,7 @@ func (m CronTaskMutation) Client() *Client { // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. -func (m CronTaskMutation) Tx() (*Tx, error) { +func (m ClanMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } @@ -4784,14 +4707,14 @@ func (m CronTaskMutation) Tx() (*Tx, error) { } // SetID sets the value of the id field. Note that this -// operation is only accepted on creation of CronTask entities. -func (m *CronTaskMutation) SetID(id string) { +// operation is only accepted on creation of Clan entities. +func (m *ClanMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. -func (m *CronTaskMutation) ID() (id string, exists bool) { +func (m *ClanMutation) ID() (id string, exists bool) { if m.id == nil { return } @@ -4802,7 +4725,7 @@ func (m *CronTaskMutation) ID() (id string, exists bool) { // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. -func (m *CronTaskMutation) IDs(ctx context.Context) ([]string, error) { +func (m *ClanMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() @@ -4811,19 +4734,19 @@ func (m *CronTaskMutation) IDs(ctx context.Context) ([]string, error) { } fallthrough case m.op.Is(OpUpdate | OpDelete): - return m.Client().CronTask.Query().Where(m.predicates...).IDs(ctx) + return m.Client().Clan.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetCreatedAt sets the "created_at" field. -func (m *CronTaskMutation) SetCreatedAt(t time.Time) { +func (m *ClanMutation) SetCreatedAt(t time.Time) { m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *CronTaskMutation) CreatedAt() (r time.Time, exists bool) { +func (m *ClanMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -4831,10 +4754,10 @@ func (m *CronTaskMutation) CreatedAt() (r time.Time, exists bool) { return *v, true } -// OldCreatedAt returns the old "created_at" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldCreatedAt returns the old "created_at" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { +func (m *ClanMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -4849,17 +4772,17 @@ func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v time.Time, err e } // ResetCreatedAt resets all changes to the "created_at" field. -func (m *CronTaskMutation) ResetCreatedAt() { +func (m *ClanMutation) ResetCreatedAt() { m.created_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *CronTaskMutation) SetUpdatedAt(t time.Time) { +func (m *ClanMutation) SetUpdatedAt(t time.Time) { m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *CronTaskMutation) UpdatedAt() (r time.Time, exists bool) { +func (m *ClanMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -4867,10 +4790,10 @@ func (m *CronTaskMutation) UpdatedAt() (r time.Time, exists bool) { return *v, true } -// OldUpdatedAt returns the old "updated_at" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldUpdatedAt returns the old "updated_at" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { +func (m *ClanMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -4885,39 +4808,776 @@ func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err e } // ResetUpdatedAt resets all changes to the "updated_at" field. -func (m *CronTaskMutation) ResetUpdatedAt() { +func (m *ClanMutation) ResetUpdatedAt() { m.updated_at = nil } -// SetType sets the "type" field. -func (m *CronTaskMutation) SetType(mt models.TaskType) { - m._type = &mt +// SetTag sets the "tag" field. +func (m *ClanMutation) SetTag(s string) { + m.tag = &s } -// GetType returns the value of the "type" field in the mutation. -func (m *CronTaskMutation) GetType() (r models.TaskType, exists bool) { - v := m._type +// Tag returns the value of the "tag" field in the mutation. +func (m *ClanMutation) Tag() (r string, exists bool) { + v := m.tag if v == nil { return } return *v, true } -// OldType returns the old "type" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldTag returns the old "tag" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldType(ctx context.Context) (v models.TaskType, err error) { +func (m *ClanMutation) OldTag(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldType is only allowed on UpdateOne operations") + return v, errors.New("OldTag is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldType requires an ID field in the mutation") + return v, errors.New("OldTag requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldType: %w", err) + return v, fmt.Errorf("querying old value for OldTag: %w", err) } - return oldValue.Type, nil + return oldValue.Tag, nil +} + +// ResetTag resets all changes to the "tag" field. +func (m *ClanMutation) ResetTag() { + m.tag = nil +} + +// SetName sets the "name" field. +func (m *ClanMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *ClanMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "name" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ClanMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "name" field. +func (m *ClanMutation) ResetName() { + m.name = nil +} + +// SetEmblemID sets the "emblem_id" field. +func (m *ClanMutation) SetEmblemID(s string) { + m.emblem_id = &s +} + +// EmblemID returns the value of the "emblem_id" field in the mutation. +func (m *ClanMutation) EmblemID() (r string, exists bool) { + v := m.emblem_id + if v == nil { + return + } + return *v, true +} + +// OldEmblemID returns the old "emblem_id" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ClanMutation) OldEmblemID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldEmblemID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldEmblemID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldEmblemID: %w", err) + } + return oldValue.EmblemID, nil +} + +// ClearEmblemID clears the value of the "emblem_id" field. +func (m *ClanMutation) ClearEmblemID() { + m.emblem_id = nil + m.clearedFields[clan.FieldEmblemID] = struct{}{} +} + +// EmblemIDCleared returns if the "emblem_id" field was cleared in this mutation. +func (m *ClanMutation) EmblemIDCleared() bool { + _, ok := m.clearedFields[clan.FieldEmblemID] + return ok +} + +// ResetEmblemID resets all changes to the "emblem_id" field. +func (m *ClanMutation) ResetEmblemID() { + m.emblem_id = nil + delete(m.clearedFields, clan.FieldEmblemID) +} + +// SetMembers sets the "members" field. +func (m *ClanMutation) SetMembers(s []string) { + m.members = &s + m.appendmembers = nil +} + +// Members returns the value of the "members" field in the mutation. +func (m *ClanMutation) Members() (r []string, exists bool) { + v := m.members + if v == nil { + return + } + return *v, true +} + +// OldMembers returns the old "members" field's value of the Clan entity. +// If the Clan object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ClanMutation) OldMembers(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMembers is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMembers requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMembers: %w", err) + } + return oldValue.Members, nil +} + +// AppendMembers adds s to the "members" field. +func (m *ClanMutation) AppendMembers(s []string) { + m.appendmembers = append(m.appendmembers, s...) +} + +// AppendedMembers returns the list of values that were appended to the "members" field in this mutation. +func (m *ClanMutation) AppendedMembers() ([]string, bool) { + if len(m.appendmembers) == 0 { + return nil, false + } + return m.appendmembers, true +} + +// ResetMembers resets all changes to the "members" field. +func (m *ClanMutation) ResetMembers() { + m.members = nil + m.appendmembers = nil +} + +// AddAccountIDs adds the "accounts" edge to the Account entity by ids. +func (m *ClanMutation) AddAccountIDs(ids ...string) { + if m.accounts == nil { + m.accounts = make(map[string]struct{}) + } + for i := range ids { + m.accounts[ids[i]] = struct{}{} + } +} + +// ClearAccounts clears the "accounts" edge to the Account entity. +func (m *ClanMutation) ClearAccounts() { + m.clearedaccounts = true +} + +// AccountsCleared reports if the "accounts" edge to the Account entity was cleared. +func (m *ClanMutation) AccountsCleared() bool { + return m.clearedaccounts +} + +// RemoveAccountIDs removes the "accounts" edge to the Account entity by IDs. +func (m *ClanMutation) RemoveAccountIDs(ids ...string) { + if m.removedaccounts == nil { + m.removedaccounts = make(map[string]struct{}) + } + for i := range ids { + delete(m.accounts, ids[i]) + m.removedaccounts[ids[i]] = struct{}{} + } +} + +// RemovedAccounts returns the removed IDs of the "accounts" edge to the Account entity. +func (m *ClanMutation) RemovedAccountsIDs() (ids []string) { + for id := range m.removedaccounts { + ids = append(ids, id) + } + return +} + +// AccountsIDs returns the "accounts" edge IDs in the mutation. +func (m *ClanMutation) AccountsIDs() (ids []string) { + for id := range m.accounts { + ids = append(ids, id) + } + return +} + +// ResetAccounts resets all changes to the "accounts" edge. +func (m *ClanMutation) ResetAccounts() { + m.accounts = nil + m.clearedaccounts = false + m.removedaccounts = nil +} + +// Where appends a list predicates to the ClanMutation builder. +func (m *ClanMutation) Where(ps ...predicate.Clan) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ClanMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ClanMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Clan, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ClanMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ClanMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Clan). +func (m *ClanMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *ClanMutation) Fields() []string { + fields := make([]string, 0, 6) + if m.created_at != nil { + fields = append(fields, clan.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, clan.FieldUpdatedAt) + } + if m.tag != nil { + fields = append(fields, clan.FieldTag) + } + if m.name != nil { + fields = append(fields, clan.FieldName) + } + if m.emblem_id != nil { + fields = append(fields, clan.FieldEmblemID) + } + if m.members != nil { + fields = append(fields, clan.FieldMembers) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *ClanMutation) Field(name string) (ent.Value, bool) { + switch name { + case clan.FieldCreatedAt: + return m.CreatedAt() + case clan.FieldUpdatedAt: + return m.UpdatedAt() + case clan.FieldTag: + return m.Tag() + case clan.FieldName: + return m.Name() + case clan.FieldEmblemID: + return m.EmblemID() + case clan.FieldMembers: + return m.Members() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *ClanMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case clan.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case clan.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case clan.FieldTag: + return m.OldTag(ctx) + case clan.FieldName: + return m.OldName(ctx) + case clan.FieldEmblemID: + return m.OldEmblemID(ctx) + case clan.FieldMembers: + return m.OldMembers(ctx) + } + return nil, fmt.Errorf("unknown Clan field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ClanMutation) SetField(name string, value ent.Value) error { + switch name { + case clan.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case clan.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case clan.FieldTag: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTag(v) + return nil + case clan.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case clan.FieldEmblemID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetEmblemID(v) + return nil + case clan.FieldMembers: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMembers(v) + return nil + } + return fmt.Errorf("unknown Clan field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ClanMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *ClanMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ClanMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown Clan numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ClanMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(clan.FieldEmblemID) { + fields = append(fields, clan.FieldEmblemID) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ClanMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *ClanMutation) ClearField(name string) error { + switch name { + case clan.FieldEmblemID: + m.ClearEmblemID() + return nil + } + return fmt.Errorf("unknown Clan nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *ClanMutation) ResetField(name string) error { + switch name { + case clan.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case clan.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case clan.FieldTag: + m.ResetTag() + return nil + case clan.FieldName: + m.ResetName() + return nil + case clan.FieldEmblemID: + m.ResetEmblemID() + return nil + case clan.FieldMembers: + m.ResetMembers() + return nil + } + return fmt.Errorf("unknown Clan field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ClanMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.accounts != nil { + edges = append(edges, clan.EdgeAccounts) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ClanMutation) AddedIDs(name string) []ent.Value { + switch name { + case clan.EdgeAccounts: + ids := make([]ent.Value, 0, len(m.accounts)) + for id := range m.accounts { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ClanMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + if m.removedaccounts != nil { + edges = append(edges, clan.EdgeAccounts) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *ClanMutation) RemovedIDs(name string) []ent.Value { + switch name { + case clan.EdgeAccounts: + ids := make([]ent.Value, 0, len(m.removedaccounts)) + for id := range m.removedaccounts { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ClanMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedaccounts { + edges = append(edges, clan.EdgeAccounts) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ClanMutation) EdgeCleared(name string) bool { + switch name { + case clan.EdgeAccounts: + return m.clearedaccounts + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *ClanMutation) ClearEdge(name string) error { + switch name { + } + return fmt.Errorf("unknown Clan unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *ClanMutation) ResetEdge(name string) error { + switch name { + case clan.EdgeAccounts: + m.ResetAccounts() + return nil + } + return fmt.Errorf("unknown Clan edge %s", name) +} + +// CronTaskMutation represents an operation that mutates the CronTask nodes in the graph. +type CronTaskMutation struct { + config + op Op + typ string + id *string + created_at *time.Time + updated_at *time.Time + _type *models.TaskType + reference_id *string + targets *[]string + appendtargets []string + status *models.TaskStatus + scheduled_after *time.Time + last_run *time.Time + tries_left *int + addtries_left *int + logs *[]models.TaskLog + appendlogs []models.TaskLog + data *map[string]string + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*CronTask, error) + predicates []predicate.CronTask +} + +var _ ent.Mutation = (*CronTaskMutation)(nil) + +// crontaskOption allows management of the mutation configuration using functional options. +type crontaskOption func(*CronTaskMutation) + +// newCronTaskMutation creates new mutation for the CronTask entity. +func newCronTaskMutation(c config, op Op, opts ...crontaskOption) *CronTaskMutation { + m := &CronTaskMutation{ + config: c, + op: op, + typ: TypeCronTask, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withCronTaskID sets the ID field of the mutation. +func withCronTaskID(id string) crontaskOption { + return func(m *CronTaskMutation) { + var ( + err error + once sync.Once + value *CronTask + ) + m.oldValue = func(ctx context.Context) (*CronTask, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().CronTask.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withCronTask sets the old CronTask of the mutation. +func withCronTask(node *CronTask) crontaskOption { + return func(m *CronTaskMutation) { + m.oldValue = func(context.Context) (*CronTask, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m CronTaskMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m CronTaskMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of CronTask entities. +func (m *CronTaskMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *CronTaskMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *CronTaskMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().CronTask.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *CronTaskMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *CronTaskMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *CronTaskMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *CronTaskMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *CronTaskMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *CronTaskMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// SetType sets the "type" field. +func (m *CronTaskMutation) SetType(mt models.TaskType) { + m._type = &mt +} + +// GetType returns the value of the "type" field in the mutation. +func (m *CronTaskMutation) GetType() (r models.TaskType, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldType(ctx context.Context) (v models.TaskType, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil } // ResetType resets all changes to the "type" field. @@ -4925,353 +5585,1157 @@ func (m *CronTaskMutation) ResetType() { m._type = nil } -// SetReferenceID sets the "reference_id" field. -func (m *CronTaskMutation) SetReferenceID(s string) { - m.reference_id = &s +// SetReferenceID sets the "reference_id" field. +func (m *CronTaskMutation) SetReferenceID(s string) { + m.reference_id = &s +} + +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *CronTaskMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id + if v == nil { + return + } + return *v, true +} + +// OldReferenceID returns the old "reference_id" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldReferenceID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferenceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + } + return oldValue.ReferenceID, nil +} + +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *CronTaskMutation) ResetReferenceID() { + m.reference_id = nil +} + +// SetTargets sets the "targets" field. +func (m *CronTaskMutation) SetTargets(s []string) { + m.targets = &s + m.appendtargets = nil +} + +// Targets returns the value of the "targets" field in the mutation. +func (m *CronTaskMutation) Targets() (r []string, exists bool) { + v := m.targets + if v == nil { + return + } + return *v, true +} + +// OldTargets returns the old "targets" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldTargets(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTargets is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTargets requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTargets: %w", err) + } + return oldValue.Targets, nil +} + +// AppendTargets adds s to the "targets" field. +func (m *CronTaskMutation) AppendTargets(s []string) { + m.appendtargets = append(m.appendtargets, s...) +} + +// AppendedTargets returns the list of values that were appended to the "targets" field in this mutation. +func (m *CronTaskMutation) AppendedTargets() ([]string, bool) { + if len(m.appendtargets) == 0 { + return nil, false + } + return m.appendtargets, true +} + +// ResetTargets resets all changes to the "targets" field. +func (m *CronTaskMutation) ResetTargets() { + m.targets = nil + m.appendtargets = nil +} + +// SetStatus sets the "status" field. +func (m *CronTaskMutation) SetStatus(ms models.TaskStatus) { + m.status = &ms +} + +// Status returns the value of the "status" field in the mutation. +func (m *CronTaskMutation) Status() (r models.TaskStatus, exists bool) { + v := m.status + if v == nil { + return + } + return *v, true +} + +// OldStatus returns the old "status" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldStatus(ctx context.Context) (v models.TaskStatus, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldStatus is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldStatus requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStatus: %w", err) + } + return oldValue.Status, nil +} + +// ResetStatus resets all changes to the "status" field. +func (m *CronTaskMutation) ResetStatus() { + m.status = nil +} + +// SetScheduledAfter sets the "scheduled_after" field. +func (m *CronTaskMutation) SetScheduledAfter(t time.Time) { + m.scheduled_after = &t +} + +// ScheduledAfter returns the value of the "scheduled_after" field in the mutation. +func (m *CronTaskMutation) ScheduledAfter() (r time.Time, exists bool) { + v := m.scheduled_after + if v == nil { + return + } + return *v, true +} + +// OldScheduledAfter returns the old "scheduled_after" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldScheduledAfter(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldScheduledAfter is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldScheduledAfter requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldScheduledAfter: %w", err) + } + return oldValue.ScheduledAfter, nil +} + +// ResetScheduledAfter resets all changes to the "scheduled_after" field. +func (m *CronTaskMutation) ResetScheduledAfter() { + m.scheduled_after = nil +} + +// SetLastRun sets the "last_run" field. +func (m *CronTaskMutation) SetLastRun(t time.Time) { + m.last_run = &t +} + +// LastRun returns the value of the "last_run" field in the mutation. +func (m *CronTaskMutation) LastRun() (r time.Time, exists bool) { + v := m.last_run + if v == nil { + return + } + return *v, true +} + +// OldLastRun returns the old "last_run" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldLastRun(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLastRun is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLastRun requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLastRun: %w", err) + } + return oldValue.LastRun, nil +} + +// ResetLastRun resets all changes to the "last_run" field. +func (m *CronTaskMutation) ResetLastRun() { + m.last_run = nil +} + +// SetTriesLeft sets the "tries_left" field. +func (m *CronTaskMutation) SetTriesLeft(i int) { + m.tries_left = &i + m.addtries_left = nil +} + +// TriesLeft returns the value of the "tries_left" field in the mutation. +func (m *CronTaskMutation) TriesLeft() (r int, exists bool) { + v := m.tries_left + if v == nil { + return + } + return *v, true +} + +// OldTriesLeft returns the old "tries_left" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldTriesLeft(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTriesLeft is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTriesLeft requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTriesLeft: %w", err) + } + return oldValue.TriesLeft, nil +} + +// AddTriesLeft adds i to the "tries_left" field. +func (m *CronTaskMutation) AddTriesLeft(i int) { + if m.addtries_left != nil { + *m.addtries_left += i + } else { + m.addtries_left = &i + } +} + +// AddedTriesLeft returns the value that was added to the "tries_left" field in this mutation. +func (m *CronTaskMutation) AddedTriesLeft() (r int, exists bool) { + v := m.addtries_left + if v == nil { + return + } + return *v, true +} + +// ResetTriesLeft resets all changes to the "tries_left" field. +func (m *CronTaskMutation) ResetTriesLeft() { + m.tries_left = nil + m.addtries_left = nil +} + +// SetLogs sets the "logs" field. +func (m *CronTaskMutation) SetLogs(ml []models.TaskLog) { + m.logs = &ml + m.appendlogs = nil +} + +// Logs returns the value of the "logs" field in the mutation. +func (m *CronTaskMutation) Logs() (r []models.TaskLog, exists bool) { + v := m.logs + if v == nil { + return + } + return *v, true +} + +// OldLogs returns the old "logs" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldLogs(ctx context.Context) (v []models.TaskLog, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLogs is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLogs requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLogs: %w", err) + } + return oldValue.Logs, nil +} + +// AppendLogs adds ml to the "logs" field. +func (m *CronTaskMutation) AppendLogs(ml []models.TaskLog) { + m.appendlogs = append(m.appendlogs, ml...) +} + +// AppendedLogs returns the list of values that were appended to the "logs" field in this mutation. +func (m *CronTaskMutation) AppendedLogs() ([]models.TaskLog, bool) { + if len(m.appendlogs) == 0 { + return nil, false + } + return m.appendlogs, true +} + +// ResetLogs resets all changes to the "logs" field. +func (m *CronTaskMutation) ResetLogs() { + m.logs = nil + m.appendlogs = nil +} + +// SetData sets the "data" field. +func (m *CronTaskMutation) SetData(value map[string]string) { + m.data = &value +} + +// Data returns the value of the "data" field in the mutation. +func (m *CronTaskMutation) Data() (r map[string]string, exists bool) { + v := m.data + if v == nil { + return + } + return *v, true +} + +// OldData returns the old "data" field's value of the CronTask entity. +// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CronTaskMutation) OldData(ctx context.Context) (v map[string]string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldData is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldData requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldData: %w", err) + } + return oldValue.Data, nil +} + +// ResetData resets all changes to the "data" field. +func (m *CronTaskMutation) ResetData() { + m.data = nil +} + +// Where appends a list predicates to the CronTaskMutation builder. +func (m *CronTaskMutation) Where(ps ...predicate.CronTask) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the CronTaskMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *CronTaskMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.CronTask, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *CronTaskMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *CronTaskMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (CronTask). +func (m *CronTaskMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *CronTaskMutation) Fields() []string { + fields := make([]string, 0, 11) + if m.created_at != nil { + fields = append(fields, crontask.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, crontask.FieldUpdatedAt) + } + if m._type != nil { + fields = append(fields, crontask.FieldType) + } + if m.reference_id != nil { + fields = append(fields, crontask.FieldReferenceID) + } + if m.targets != nil { + fields = append(fields, crontask.FieldTargets) + } + if m.status != nil { + fields = append(fields, crontask.FieldStatus) + } + if m.scheduled_after != nil { + fields = append(fields, crontask.FieldScheduledAfter) + } + if m.last_run != nil { + fields = append(fields, crontask.FieldLastRun) + } + if m.tries_left != nil { + fields = append(fields, crontask.FieldTriesLeft) + } + if m.logs != nil { + fields = append(fields, crontask.FieldLogs) + } + if m.data != nil { + fields = append(fields, crontask.FieldData) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *CronTaskMutation) Field(name string) (ent.Value, bool) { + switch name { + case crontask.FieldCreatedAt: + return m.CreatedAt() + case crontask.FieldUpdatedAt: + return m.UpdatedAt() + case crontask.FieldType: + return m.GetType() + case crontask.FieldReferenceID: + return m.ReferenceID() + case crontask.FieldTargets: + return m.Targets() + case crontask.FieldStatus: + return m.Status() + case crontask.FieldScheduledAfter: + return m.ScheduledAfter() + case crontask.FieldLastRun: + return m.LastRun() + case crontask.FieldTriesLeft: + return m.TriesLeft() + case crontask.FieldLogs: + return m.Logs() + case crontask.FieldData: + return m.Data() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *CronTaskMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case crontask.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case crontask.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case crontask.FieldType: + return m.OldType(ctx) + case crontask.FieldReferenceID: + return m.OldReferenceID(ctx) + case crontask.FieldTargets: + return m.OldTargets(ctx) + case crontask.FieldStatus: + return m.OldStatus(ctx) + case crontask.FieldScheduledAfter: + return m.OldScheduledAfter(ctx) + case crontask.FieldLastRun: + return m.OldLastRun(ctx) + case crontask.FieldTriesLeft: + return m.OldTriesLeft(ctx) + case crontask.FieldLogs: + return m.OldLogs(ctx) + case crontask.FieldData: + return m.OldData(ctx) + } + return nil, fmt.Errorf("unknown CronTask field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *CronTaskMutation) SetField(name string, value ent.Value) error { + switch name { + case crontask.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case crontask.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case crontask.FieldType: + v, ok := value.(models.TaskType) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case crontask.FieldReferenceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferenceID(v) + return nil + case crontask.FieldTargets: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTargets(v) + return nil + case crontask.FieldStatus: + v, ok := value.(models.TaskStatus) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStatus(v) + return nil + case crontask.FieldScheduledAfter: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetScheduledAfter(v) + return nil + case crontask.FieldLastRun: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLastRun(v) + return nil + case crontask.FieldTriesLeft: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTriesLeft(v) + return nil + case crontask.FieldLogs: + v, ok := value.([]models.TaskLog) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLogs(v) + return nil + case crontask.FieldData: + v, ok := value.(map[string]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetData(v) + return nil + } + return fmt.Errorf("unknown CronTask field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *CronTaskMutation) AddedFields() []string { + var fields []string + if m.addtries_left != nil { + fields = append(fields, crontask.FieldTriesLeft) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *CronTaskMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case crontask.FieldTriesLeft: + return m.AddedTriesLeft() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *CronTaskMutation) AddField(name string, value ent.Value) error { + switch name { + case crontask.FieldTriesLeft: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddTriesLeft(v) + return nil + } + return fmt.Errorf("unknown CronTask numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *CronTaskMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *CronTaskMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *CronTaskMutation) ClearField(name string) error { + return fmt.Errorf("unknown CronTask nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *CronTaskMutation) ResetField(name string) error { + switch name { + case crontask.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case crontask.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case crontask.FieldType: + m.ResetType() + return nil + case crontask.FieldReferenceID: + m.ResetReferenceID() + return nil + case crontask.FieldTargets: + m.ResetTargets() + return nil + case crontask.FieldStatus: + m.ResetStatus() + return nil + case crontask.FieldScheduledAfter: + m.ResetScheduledAfter() + return nil + case crontask.FieldLastRun: + m.ResetLastRun() + return nil + case crontask.FieldTriesLeft: + m.ResetTriesLeft() + return nil + case crontask.FieldLogs: + m.ResetLogs() + return nil + case crontask.FieldData: + m.ResetData() + return nil + } + return fmt.Errorf("unknown CronTask field %s", name) } -// ReferenceID returns the value of the "reference_id" field in the mutation. -func (m *CronTaskMutation) ReferenceID() (r string, exists bool) { - v := m.reference_id +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *CronTaskMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *CronTaskMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *CronTaskMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *CronTaskMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *CronTaskMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *CronTaskMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *CronTaskMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown CronTask unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *CronTaskMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown CronTask edge %s", name) +} + +// DiscordInteractionMutation represents an operation that mutates the DiscordInteraction nodes in the graph. +type DiscordInteractionMutation struct { + config + op Op + typ string + id *string + created_at *time.Time + updated_at *time.Time + command *string + reference_id *string + _type *models.DiscordInteractionType + locale *string + options *models.DiscordInteractionOptions + clearedFields map[string]struct{} + user *string + cleareduser bool + done bool + oldValue func(context.Context) (*DiscordInteraction, error) + predicates []predicate.DiscordInteraction +} + +var _ ent.Mutation = (*DiscordInteractionMutation)(nil) + +// discordinteractionOption allows management of the mutation configuration using functional options. +type discordinteractionOption func(*DiscordInteractionMutation) + +// newDiscordInteractionMutation creates new mutation for the DiscordInteraction entity. +func newDiscordInteractionMutation(c config, op Op, opts ...discordinteractionOption) *DiscordInteractionMutation { + m := &DiscordInteractionMutation{ + config: c, + op: op, + typ: TypeDiscordInteraction, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withDiscordInteractionID sets the ID field of the mutation. +func withDiscordInteractionID(id string) discordinteractionOption { + return func(m *DiscordInteractionMutation) { + var ( + err error + once sync.Once + value *DiscordInteraction + ) + m.oldValue = func(ctx context.Context) (*DiscordInteraction, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().DiscordInteraction.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withDiscordInteraction sets the old DiscordInteraction of the mutation. +func withDiscordInteraction(node *DiscordInteraction) discordinteractionOption { + return func(m *DiscordInteractionMutation) { + m.oldValue = func(context.Context) (*DiscordInteraction, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m DiscordInteractionMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m DiscordInteractionMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of DiscordInteraction entities. +func (m *DiscordInteractionMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *DiscordInteractionMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *DiscordInteractionMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().DiscordInteraction.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *DiscordInteractionMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *DiscordInteractionMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at if v == nil { return } return *v, true } -// OldReferenceID returns the old "reference_id" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldCreatedAt returns the old "created_at" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldReferenceID(ctx context.Context) (v string, err error) { +func (m *DiscordInteractionMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldReferenceID requires an ID field in the mutation") + return v, errors.New("OldCreatedAt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) } - return oldValue.ReferenceID, nil + return oldValue.CreatedAt, nil } -// ResetReferenceID resets all changes to the "reference_id" field. -func (m *CronTaskMutation) ResetReferenceID() { - m.reference_id = nil +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *DiscordInteractionMutation) ResetCreatedAt() { + m.created_at = nil } -// SetTargets sets the "targets" field. -func (m *CronTaskMutation) SetTargets(s []string) { - m.targets = &s - m.appendtargets = nil +// SetUpdatedAt sets the "updated_at" field. +func (m *DiscordInteractionMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } -// Targets returns the value of the "targets" field in the mutation. -func (m *CronTaskMutation) Targets() (r []string, exists bool) { - v := m.targets +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *DiscordInteractionMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at if v == nil { return } return *v, true } -// OldTargets returns the old "targets" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldUpdatedAt returns the old "updated_at" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldTargets(ctx context.Context) (v []string, err error) { +func (m *DiscordInteractionMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldTargets is only allowed on UpdateOne operations") + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldTargets requires an ID field in the mutation") + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldTargets: %w", err) - } - return oldValue.Targets, nil -} - -// AppendTargets adds s to the "targets" field. -func (m *CronTaskMutation) AppendTargets(s []string) { - m.appendtargets = append(m.appendtargets, s...) -} - -// AppendedTargets returns the list of values that were appended to the "targets" field in this mutation. -func (m *CronTaskMutation) AppendedTargets() ([]string, bool) { - if len(m.appendtargets) == 0 { - return nil, false + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) } - return m.appendtargets, true + return oldValue.UpdatedAt, nil } -// ResetTargets resets all changes to the "targets" field. -func (m *CronTaskMutation) ResetTargets() { - m.targets = nil - m.appendtargets = nil +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *DiscordInteractionMutation) ResetUpdatedAt() { + m.updated_at = nil } -// SetStatus sets the "status" field. -func (m *CronTaskMutation) SetStatus(ms models.TaskStatus) { - m.status = &ms +// SetCommand sets the "command" field. +func (m *DiscordInteractionMutation) SetCommand(s string) { + m.command = &s } -// Status returns the value of the "status" field in the mutation. -func (m *CronTaskMutation) Status() (r models.TaskStatus, exists bool) { - v := m.status +// Command returns the value of the "command" field in the mutation. +func (m *DiscordInteractionMutation) Command() (r string, exists bool) { + v := m.command if v == nil { return } return *v, true } -// OldStatus returns the old "status" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldCommand returns the old "command" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldStatus(ctx context.Context) (v models.TaskStatus, err error) { +func (m *DiscordInteractionMutation) OldCommand(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldStatus is only allowed on UpdateOne operations") + return v, errors.New("OldCommand is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldStatus requires an ID field in the mutation") + return v, errors.New("OldCommand requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldStatus: %w", err) + return v, fmt.Errorf("querying old value for OldCommand: %w", err) } - return oldValue.Status, nil + return oldValue.Command, nil } -// ResetStatus resets all changes to the "status" field. -func (m *CronTaskMutation) ResetStatus() { - m.status = nil +// ResetCommand resets all changes to the "command" field. +func (m *DiscordInteractionMutation) ResetCommand() { + m.command = nil } -// SetScheduledAfter sets the "scheduled_after" field. -func (m *CronTaskMutation) SetScheduledAfter(t time.Time) { - m.scheduled_after = &t +// SetUserID sets the "user_id" field. +func (m *DiscordInteractionMutation) SetUserID(s string) { + m.user = &s } -// ScheduledAfter returns the value of the "scheduled_after" field in the mutation. -func (m *CronTaskMutation) ScheduledAfter() (r time.Time, exists bool) { - v := m.scheduled_after +// UserID returns the value of the "user_id" field in the mutation. +func (m *DiscordInteractionMutation) UserID() (r string, exists bool) { + v := m.user if v == nil { return } return *v, true } -// OldScheduledAfter returns the old "scheduled_after" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldUserID returns the old "user_id" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldScheduledAfter(ctx context.Context) (v time.Time, err error) { +func (m *DiscordInteractionMutation) OldUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldScheduledAfter is only allowed on UpdateOne operations") + return v, errors.New("OldUserID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldScheduledAfter requires an ID field in the mutation") + return v, errors.New("OldUserID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldScheduledAfter: %w", err) + return v, fmt.Errorf("querying old value for OldUserID: %w", err) } - return oldValue.ScheduledAfter, nil + return oldValue.UserID, nil } -// ResetScheduledAfter resets all changes to the "scheduled_after" field. -func (m *CronTaskMutation) ResetScheduledAfter() { - m.scheduled_after = nil +// ResetUserID resets all changes to the "user_id" field. +func (m *DiscordInteractionMutation) ResetUserID() { + m.user = nil } -// SetLastRun sets the "last_run" field. -func (m *CronTaskMutation) SetLastRun(t time.Time) { - m.last_run = &t +// SetReferenceID sets the "reference_id" field. +func (m *DiscordInteractionMutation) SetReferenceID(s string) { + m.reference_id = &s } -// LastRun returns the value of the "last_run" field in the mutation. -func (m *CronTaskMutation) LastRun() (r time.Time, exists bool) { - v := m.last_run +// ReferenceID returns the value of the "reference_id" field in the mutation. +func (m *DiscordInteractionMutation) ReferenceID() (r string, exists bool) { + v := m.reference_id if v == nil { return } return *v, true } -// OldLastRun returns the old "last_run" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldReferenceID returns the old "reference_id" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldLastRun(ctx context.Context) (v time.Time, err error) { +func (m *DiscordInteractionMutation) OldReferenceID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldLastRun is only allowed on UpdateOne operations") + return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldLastRun requires an ID field in the mutation") + return v, errors.New("OldReferenceID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldLastRun: %w", err) + return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) } - return oldValue.LastRun, nil + return oldValue.ReferenceID, nil } -// ResetLastRun resets all changes to the "last_run" field. -func (m *CronTaskMutation) ResetLastRun() { - m.last_run = nil +// ResetReferenceID resets all changes to the "reference_id" field. +func (m *DiscordInteractionMutation) ResetReferenceID() { + m.reference_id = nil } -// SetTriesLeft sets the "tries_left" field. -func (m *CronTaskMutation) SetTriesLeft(i int) { - m.tries_left = &i - m.addtries_left = nil +// SetType sets the "type" field. +func (m *DiscordInteractionMutation) SetType(mit models.DiscordInteractionType) { + m._type = &mit } -// TriesLeft returns the value of the "tries_left" field in the mutation. -func (m *CronTaskMutation) TriesLeft() (r int, exists bool) { - v := m.tries_left +// GetType returns the value of the "type" field in the mutation. +func (m *DiscordInteractionMutation) GetType() (r models.DiscordInteractionType, exists bool) { + v := m._type if v == nil { return } return *v, true } -// OldTriesLeft returns the old "tries_left" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldType returns the old "type" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldTriesLeft(ctx context.Context) (v int, err error) { +func (m *DiscordInteractionMutation) OldType(ctx context.Context) (v models.DiscordInteractionType, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldTriesLeft is only allowed on UpdateOne operations") + return v, errors.New("OldType is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldTriesLeft requires an ID field in the mutation") + return v, errors.New("OldType requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldTriesLeft: %w", err) - } - return oldValue.TriesLeft, nil -} - -// AddTriesLeft adds i to the "tries_left" field. -func (m *CronTaskMutation) AddTriesLeft(i int) { - if m.addtries_left != nil { - *m.addtries_left += i - } else { - m.addtries_left = &i - } -} - -// AddedTriesLeft returns the value that was added to the "tries_left" field in this mutation. -func (m *CronTaskMutation) AddedTriesLeft() (r int, exists bool) { - v := m.addtries_left - if v == nil { - return + return v, fmt.Errorf("querying old value for OldType: %w", err) } - return *v, true + return oldValue.Type, nil } -// ResetTriesLeft resets all changes to the "tries_left" field. -func (m *CronTaskMutation) ResetTriesLeft() { - m.tries_left = nil - m.addtries_left = nil +// ResetType resets all changes to the "type" field. +func (m *DiscordInteractionMutation) ResetType() { + m._type = nil } -// SetLogs sets the "logs" field. -func (m *CronTaskMutation) SetLogs(ml []models.TaskLog) { - m.logs = &ml - m.appendlogs = nil +// SetLocale sets the "locale" field. +func (m *DiscordInteractionMutation) SetLocale(s string) { + m.locale = &s } -// Logs returns the value of the "logs" field in the mutation. -func (m *CronTaskMutation) Logs() (r []models.TaskLog, exists bool) { - v := m.logs +// Locale returns the value of the "locale" field in the mutation. +func (m *DiscordInteractionMutation) Locale() (r string, exists bool) { + v := m.locale if v == nil { return } return *v, true } -// OldLogs returns the old "logs" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldLocale returns the old "locale" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldLogs(ctx context.Context) (v []models.TaskLog, err error) { +func (m *DiscordInteractionMutation) OldLocale(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldLogs is only allowed on UpdateOne operations") + return v, errors.New("OldLocale is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldLogs requires an ID field in the mutation") + return v, errors.New("OldLocale requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldLogs: %w", err) - } - return oldValue.Logs, nil -} - -// AppendLogs adds ml to the "logs" field. -func (m *CronTaskMutation) AppendLogs(ml []models.TaskLog) { - m.appendlogs = append(m.appendlogs, ml...) -} - -// AppendedLogs returns the list of values that were appended to the "logs" field in this mutation. -func (m *CronTaskMutation) AppendedLogs() ([]models.TaskLog, bool) { - if len(m.appendlogs) == 0 { - return nil, false + return v, fmt.Errorf("querying old value for OldLocale: %w", err) } - return m.appendlogs, true + return oldValue.Locale, nil } -// ResetLogs resets all changes to the "logs" field. -func (m *CronTaskMutation) ResetLogs() { - m.logs = nil - m.appendlogs = nil +// ResetLocale resets all changes to the "locale" field. +func (m *DiscordInteractionMutation) ResetLocale() { + m.locale = nil } -// SetData sets the "data" field. -func (m *CronTaskMutation) SetData(value map[string]string) { - m.data = &value +// SetOptions sets the "options" field. +func (m *DiscordInteractionMutation) SetOptions(mio models.DiscordInteractionOptions) { + m.options = &mio } -// Data returns the value of the "data" field in the mutation. -func (m *CronTaskMutation) Data() (r map[string]string, exists bool) { - v := m.data +// Options returns the value of the "options" field in the mutation. +func (m *DiscordInteractionMutation) Options() (r models.DiscordInteractionOptions, exists bool) { + v := m.options if v == nil { return } return *v, true } -// OldData returns the old "data" field's value of the CronTask entity. -// If the CronTask object wasn't provided to the builder, the object is fetched from the database. +// OldOptions returns the old "options" field's value of the DiscordInteraction entity. +// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CronTaskMutation) OldData(ctx context.Context) (v map[string]string, err error) { +func (m *DiscordInteractionMutation) OldOptions(ctx context.Context) (v models.DiscordInteractionOptions, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldData is only allowed on UpdateOne operations") + return v, errors.New("OldOptions is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldData requires an ID field in the mutation") + return v, errors.New("OldOptions requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldData: %w", err) + return v, fmt.Errorf("querying old value for OldOptions: %w", err) } - return oldValue.Data, nil + return oldValue.Options, nil } -// ResetData resets all changes to the "data" field. -func (m *CronTaskMutation) ResetData() { - m.data = nil +// ResetOptions resets all changes to the "options" field. +func (m *DiscordInteractionMutation) ResetOptions() { + m.options = nil } -// Where appends a list predicates to the CronTaskMutation builder. -func (m *CronTaskMutation) Where(ps ...predicate.CronTask) { +// ClearUser clears the "user" edge to the User entity. +func (m *DiscordInteractionMutation) ClearUser() { + m.cleareduser = true + m.clearedFields[discordinteraction.FieldUserID] = struct{}{} +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *DiscordInteractionMutation) UserCleared() bool { + return m.cleareduser +} + +// UserIDs returns the "user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserID instead. It exists only for internal usage by the builders. +func (m *DiscordInteractionMutation) UserIDs() (ids []string) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *DiscordInteractionMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// Where appends a list predicates to the DiscordInteractionMutation builder. +func (m *DiscordInteractionMutation) Where(ps ...predicate.DiscordInteraction) { m.predicates = append(m.predicates, ps...) } -// WhereP appends storage-level predicates to the CronTaskMutation builder. Using this method, +// WhereP appends storage-level predicates to the DiscordInteractionMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. -func (m *CronTaskMutation) WhereP(ps ...func(*sql.Selector)) { - p := make([]predicate.CronTask, len(ps)) +func (m *DiscordInteractionMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.DiscordInteraction, len(ps)) for i := range ps { p[i] = ps[i] } @@ -5279,57 +6743,48 @@ func (m *CronTaskMutation) WhereP(ps ...func(*sql.Selector)) { } // Op returns the operation name. -func (m *CronTaskMutation) Op() Op { +func (m *DiscordInteractionMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. -func (m *CronTaskMutation) SetOp(op Op) { +func (m *DiscordInteractionMutation) SetOp(op Op) { m.op = op } -// Type returns the node type of this mutation (CronTask). -func (m *CronTaskMutation) Type() string { +// Type returns the node type of this mutation (DiscordInteraction). +func (m *DiscordInteractionMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). -func (m *CronTaskMutation) Fields() []string { - fields := make([]string, 0, 11) +func (m *DiscordInteractionMutation) Fields() []string { + fields := make([]string, 0, 8) if m.created_at != nil { - fields = append(fields, crontask.FieldCreatedAt) + fields = append(fields, discordinteraction.FieldCreatedAt) } if m.updated_at != nil { - fields = append(fields, crontask.FieldUpdatedAt) - } - if m._type != nil { - fields = append(fields, crontask.FieldType) - } - if m.reference_id != nil { - fields = append(fields, crontask.FieldReferenceID) - } - if m.targets != nil { - fields = append(fields, crontask.FieldTargets) + fields = append(fields, discordinteraction.FieldUpdatedAt) } - if m.status != nil { - fields = append(fields, crontask.FieldStatus) + if m.command != nil { + fields = append(fields, discordinteraction.FieldCommand) } - if m.scheduled_after != nil { - fields = append(fields, crontask.FieldScheduledAfter) + if m.user != nil { + fields = append(fields, discordinteraction.FieldUserID) } - if m.last_run != nil { - fields = append(fields, crontask.FieldLastRun) + if m.reference_id != nil { + fields = append(fields, discordinteraction.FieldReferenceID) } - if m.tries_left != nil { - fields = append(fields, crontask.FieldTriesLeft) + if m._type != nil { + fields = append(fields, discordinteraction.FieldType) } - if m.logs != nil { - fields = append(fields, crontask.FieldLogs) + if m.locale != nil { + fields = append(fields, discordinteraction.FieldLocale) } - if m.data != nil { - fields = append(fields, crontask.FieldData) + if m.options != nil { + fields = append(fields, discordinteraction.FieldOptions) } return fields } @@ -5337,30 +6792,24 @@ func (m *CronTaskMutation) Fields() []string { // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. -func (m *CronTaskMutation) Field(name string) (ent.Value, bool) { +func (m *DiscordInteractionMutation) Field(name string) (ent.Value, bool) { switch name { - case crontask.FieldCreatedAt: + case discordinteraction.FieldCreatedAt: return m.CreatedAt() - case crontask.FieldUpdatedAt: + case discordinteraction.FieldUpdatedAt: return m.UpdatedAt() - case crontask.FieldType: - return m.GetType() - case crontask.FieldReferenceID: + case discordinteraction.FieldCommand: + return m.Command() + case discordinteraction.FieldUserID: + return m.UserID() + case discordinteraction.FieldReferenceID: return m.ReferenceID() - case crontask.FieldTargets: - return m.Targets() - case crontask.FieldStatus: - return m.Status() - case crontask.FieldScheduledAfter: - return m.ScheduledAfter() - case crontask.FieldLastRun: - return m.LastRun() - case crontask.FieldTriesLeft: - return m.TriesLeft() - case crontask.FieldLogs: - return m.Logs() - case crontask.FieldData: - return m.Data() + case discordinteraction.FieldType: + return m.GetType() + case discordinteraction.FieldLocale: + return m.Locale() + case discordinteraction.FieldOptions: + return m.Options() } return nil, false } @@ -5368,297 +6817,270 @@ func (m *CronTaskMutation) Field(name string) (ent.Value, bool) { // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. -func (m *CronTaskMutation) OldField(ctx context.Context, name string) (ent.Value, error) { +func (m *DiscordInteractionMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { - case crontask.FieldCreatedAt: + case discordinteraction.FieldCreatedAt: return m.OldCreatedAt(ctx) - case crontask.FieldUpdatedAt: + case discordinteraction.FieldUpdatedAt: return m.OldUpdatedAt(ctx) - case crontask.FieldType: - return m.OldType(ctx) - case crontask.FieldReferenceID: + case discordinteraction.FieldCommand: + return m.OldCommand(ctx) + case discordinteraction.FieldUserID: + return m.OldUserID(ctx) + case discordinteraction.FieldReferenceID: return m.OldReferenceID(ctx) - case crontask.FieldTargets: - return m.OldTargets(ctx) - case crontask.FieldStatus: - return m.OldStatus(ctx) - case crontask.FieldScheduledAfter: - return m.OldScheduledAfter(ctx) - case crontask.FieldLastRun: - return m.OldLastRun(ctx) - case crontask.FieldTriesLeft: - return m.OldTriesLeft(ctx) - case crontask.FieldLogs: - return m.OldLogs(ctx) - case crontask.FieldData: - return m.OldData(ctx) + case discordinteraction.FieldType: + return m.OldType(ctx) + case discordinteraction.FieldLocale: + return m.OldLocale(ctx) + case discordinteraction.FieldOptions: + return m.OldOptions(ctx) } - return nil, fmt.Errorf("unknown CronTask field %s", name) + return nil, fmt.Errorf("unknown DiscordInteraction field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. -func (m *CronTaskMutation) SetField(name string, value ent.Value) error { +func (m *DiscordInteractionMutation) SetField(name string, value ent.Value) error { switch name { - case crontask.FieldCreatedAt: + case discordinteraction.FieldCreatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil - case crontask.FieldUpdatedAt: + case discordinteraction.FieldUpdatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUpdatedAt(v) return nil - case crontask.FieldType: - v, ok := value.(models.TaskType) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetType(v) - return nil - case crontask.FieldReferenceID: + case discordinteraction.FieldCommand: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetReferenceID(v) - return nil - case crontask.FieldTargets: - v, ok := value.([]string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetTargets(v) - return nil - case crontask.FieldStatus: - v, ok := value.(models.TaskStatus) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetStatus(v) + m.SetCommand(v) return nil - case crontask.FieldScheduledAfter: - v, ok := value.(time.Time) + case discordinteraction.FieldUserID: + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetScheduledAfter(v) + m.SetUserID(v) return nil - case crontask.FieldLastRun: - v, ok := value.(time.Time) + case discordinteraction.FieldReferenceID: + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetLastRun(v) + m.SetReferenceID(v) return nil - case crontask.FieldTriesLeft: - v, ok := value.(int) + case discordinteraction.FieldType: + v, ok := value.(models.DiscordInteractionType) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetTriesLeft(v) + m.SetType(v) return nil - case crontask.FieldLogs: - v, ok := value.([]models.TaskLog) + case discordinteraction.FieldLocale: + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetLogs(v) + m.SetLocale(v) return nil - case crontask.FieldData: - v, ok := value.(map[string]string) + case discordinteraction.FieldOptions: + v, ok := value.(models.DiscordInteractionOptions) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetData(v) + m.SetOptions(v) return nil } - return fmt.Errorf("unknown CronTask field %s", name) + return fmt.Errorf("unknown DiscordInteraction field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. -func (m *CronTaskMutation) AddedFields() []string { - var fields []string - if m.addtries_left != nil { - fields = append(fields, crontask.FieldTriesLeft) - } - return fields +func (m *DiscordInteractionMutation) AddedFields() []string { + return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. -func (m *CronTaskMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case crontask.FieldTriesLeft: - return m.AddedTriesLeft() - } +func (m *DiscordInteractionMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. -func (m *CronTaskMutation) AddField(name string, value ent.Value) error { +func (m *DiscordInteractionMutation) AddField(name string, value ent.Value) error { switch name { - case crontask.FieldTriesLeft: - v, ok := value.(int) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddTriesLeft(v) - return nil } - return fmt.Errorf("unknown CronTask numeric field %s", name) + return fmt.Errorf("unknown DiscordInteraction numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. -func (m *CronTaskMutation) ClearedFields() []string { +func (m *DiscordInteractionMutation) ClearedFields() []string { return nil } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. -func (m *CronTaskMutation) FieldCleared(name string) bool { +func (m *DiscordInteractionMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. -func (m *CronTaskMutation) ClearField(name string) error { - return fmt.Errorf("unknown CronTask nullable field %s", name) +func (m *DiscordInteractionMutation) ClearField(name string) error { + return fmt.Errorf("unknown DiscordInteraction nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. -func (m *CronTaskMutation) ResetField(name string) error { +func (m *DiscordInteractionMutation) ResetField(name string) error { switch name { - case crontask.FieldCreatedAt: + case discordinteraction.FieldCreatedAt: m.ResetCreatedAt() return nil - case crontask.FieldUpdatedAt: + case discordinteraction.FieldUpdatedAt: m.ResetUpdatedAt() return nil - case crontask.FieldType: - m.ResetType() - return nil - case crontask.FieldReferenceID: - m.ResetReferenceID() - return nil - case crontask.FieldTargets: - m.ResetTargets() - return nil - case crontask.FieldStatus: - m.ResetStatus() + case discordinteraction.FieldCommand: + m.ResetCommand() return nil - case crontask.FieldScheduledAfter: - m.ResetScheduledAfter() + case discordinteraction.FieldUserID: + m.ResetUserID() return nil - case crontask.FieldLastRun: - m.ResetLastRun() + case discordinteraction.FieldReferenceID: + m.ResetReferenceID() return nil - case crontask.FieldTriesLeft: - m.ResetTriesLeft() + case discordinteraction.FieldType: + m.ResetType() return nil - case crontask.FieldLogs: - m.ResetLogs() + case discordinteraction.FieldLocale: + m.ResetLocale() return nil - case crontask.FieldData: - m.ResetData() + case discordinteraction.FieldOptions: + m.ResetOptions() return nil } - return fmt.Errorf("unknown CronTask field %s", name) + return fmt.Errorf("unknown DiscordInteraction field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. -func (m *CronTaskMutation) AddedEdges() []string { - edges := make([]string, 0, 0) +func (m *DiscordInteractionMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.user != nil { + edges = append(edges, discordinteraction.EdgeUser) + } return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. -func (m *CronTaskMutation) AddedIDs(name string) []ent.Value { +func (m *DiscordInteractionMutation) AddedIDs(name string) []ent.Value { + switch name { + case discordinteraction.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + } return nil } // RemovedEdges returns all edge names that were removed in this mutation. -func (m *CronTaskMutation) RemovedEdges() []string { - edges := make([]string, 0, 0) +func (m *DiscordInteractionMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. -func (m *CronTaskMutation) RemovedIDs(name string) []ent.Value { +func (m *DiscordInteractionMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. -func (m *CronTaskMutation) ClearedEdges() []string { - edges := make([]string, 0, 0) +func (m *DiscordInteractionMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.cleareduser { + edges = append(edges, discordinteraction.EdgeUser) + } return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. -func (m *CronTaskMutation) EdgeCleared(name string) bool { +func (m *DiscordInteractionMutation) EdgeCleared(name string) bool { + switch name { + case discordinteraction.EdgeUser: + return m.cleareduser + } return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. -func (m *CronTaskMutation) ClearEdge(name string) error { - return fmt.Errorf("unknown CronTask unique edge %s", name) +func (m *DiscordInteractionMutation) ClearEdge(name string) error { + switch name { + case discordinteraction.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown DiscordInteraction unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. -func (m *CronTaskMutation) ResetEdge(name string) error { - return fmt.Errorf("unknown CronTask edge %s", name) +func (m *DiscordInteractionMutation) ResetEdge(name string) error { + switch name { + case discordinteraction.EdgeUser: + m.ResetUser() + return nil + } + return fmt.Errorf("unknown DiscordInteraction edge %s", name) } -// DiscordInteractionMutation represents an operation that mutates the DiscordInteraction nodes in the graph. -type DiscordInteractionMutation struct { +// SessionMutation represents an operation that mutates the Session nodes in the graph. +type SessionMutation struct { config op Op typ string id *string created_at *time.Time updated_at *time.Time - command *string - reference_id *string - _type *models.DiscordInteractionType - locale *string - options *models.DiscordInteractionOptions + expires_at *time.Time + public_id *string + metadata *map[string]string clearedFields map[string]struct{} user *string cleareduser bool done bool - oldValue func(context.Context) (*DiscordInteraction, error) - predicates []predicate.DiscordInteraction + oldValue func(context.Context) (*Session, error) + predicates []predicate.Session } -var _ ent.Mutation = (*DiscordInteractionMutation)(nil) +var _ ent.Mutation = (*SessionMutation)(nil) -// discordinteractionOption allows management of the mutation configuration using functional options. -type discordinteractionOption func(*DiscordInteractionMutation) +// sessionOption allows management of the mutation configuration using functional options. +type sessionOption func(*SessionMutation) -// newDiscordInteractionMutation creates new mutation for the DiscordInteraction entity. -func newDiscordInteractionMutation(c config, op Op, opts ...discordinteractionOption) *DiscordInteractionMutation { - m := &DiscordInteractionMutation{ +// newSessionMutation creates new mutation for the Session entity. +func newSessionMutation(c config, op Op, opts ...sessionOption) *SessionMutation { + m := &SessionMutation{ config: c, op: op, - typ: TypeDiscordInteraction, + typ: TypeSession, clearedFields: make(map[string]struct{}), } for _, opt := range opts { @@ -5667,20 +7089,20 @@ func newDiscordInteractionMutation(c config, op Op, opts ...discordinteractionOp return m } -// withDiscordInteractionID sets the ID field of the mutation. -func withDiscordInteractionID(id string) discordinteractionOption { - return func(m *DiscordInteractionMutation) { +// withSessionID sets the ID field of the mutation. +func withSessionID(id string) sessionOption { + return func(m *SessionMutation) { var ( err error once sync.Once - value *DiscordInteraction + value *Session ) - m.oldValue = func(ctx context.Context) (*DiscordInteraction, error) { + m.oldValue = func(ctx context.Context) (*Session, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { - value, err = m.Client().DiscordInteraction.Get(ctx, id) + value, err = m.Client().Session.Get(ctx, id) } }) return value, err @@ -5689,10 +7111,10 @@ func withDiscordInteractionID(id string) discordinteractionOption { } } -// withDiscordInteraction sets the old DiscordInteraction of the mutation. -func withDiscordInteraction(node *DiscordInteraction) discordinteractionOption { - return func(m *DiscordInteractionMutation) { - m.oldValue = func(context.Context) (*DiscordInteraction, error) { +// withSession sets the old Session of the mutation. +func withSession(node *Session) sessionOption { + return func(m *SessionMutation) { + m.oldValue = func(context.Context) (*Session, error) { return node, nil } m.id = &node.ID @@ -5701,7 +7123,7 @@ func withDiscordInteraction(node *DiscordInteraction) discordinteractionOption { // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. -func (m DiscordInteractionMutation) Client() *Client { +func (m SessionMutation) Client() *Client { client := &Client{config: m.config} client.init() return client @@ -5709,7 +7131,7 @@ func (m DiscordInteractionMutation) Client() *Client { // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. -func (m DiscordInteractionMutation) Tx() (*Tx, error) { +func (m SessionMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } @@ -5719,14 +7141,14 @@ func (m DiscordInteractionMutation) Tx() (*Tx, error) { } // SetID sets the value of the id field. Note that this -// operation is only accepted on creation of DiscordInteraction entities. -func (m *DiscordInteractionMutation) SetID(id string) { +// operation is only accepted on creation of Session entities. +func (m *SessionMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. -func (m *DiscordInteractionMutation) ID() (id string, exists bool) { +func (m *SessionMutation) ID() (id string, exists bool) { if m.id == nil { return } @@ -5737,7 +7159,7 @@ func (m *DiscordInteractionMutation) ID() (id string, exists bool) { // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. -func (m *DiscordInteractionMutation) IDs(ctx context.Context) ([]string, error) { +func (m *SessionMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() @@ -5746,19 +7168,19 @@ func (m *DiscordInteractionMutation) IDs(ctx context.Context) ([]string, error) } fallthrough case m.op.Is(OpUpdate | OpDelete): - return m.Client().DiscordInteraction.Query().Where(m.predicates...).IDs(ctx) + return m.Client().Session.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetCreatedAt sets the "created_at" field. -func (m *DiscordInteractionMutation) SetCreatedAt(t time.Time) { +func (m *SessionMutation) SetCreatedAt(t time.Time) { m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. -func (m *DiscordInteractionMutation) CreatedAt() (r time.Time, exists bool) { +func (m *SessionMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return @@ -5766,10 +7188,10 @@ func (m *DiscordInteractionMutation) CreatedAt() (r time.Time, exists bool) { return *v, true } -// OldCreatedAt returns the old "created_at" field's value of the DiscordInteraction entity. -// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// OldCreatedAt returns the old "created_at" field's value of the Session entity. +// If the Session object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { +func (m *SessionMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } @@ -5784,17 +7206,17 @@ func (m *DiscordInteractionMutation) OldCreatedAt(ctx context.Context) (v time.T } // ResetCreatedAt resets all changes to the "created_at" field. -func (m *DiscordInteractionMutation) ResetCreatedAt() { +func (m *SessionMutation) ResetCreatedAt() { m.created_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *DiscordInteractionMutation) SetUpdatedAt(t time.Time) { +func (m *SessionMutation) SetUpdatedAt(t time.Time) { m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *DiscordInteractionMutation) UpdatedAt() (r time.Time, exists bool) { +func (m *SessionMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -5802,10 +7224,10 @@ func (m *DiscordInteractionMutation) UpdatedAt() (r time.Time, exists bool) { return *v, true } -// OldUpdatedAt returns the old "updated_at" field's value of the DiscordInteraction entity. -// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// OldUpdatedAt returns the old "updated_at" field's value of the Session entity. +// If the Session object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { +func (m *SessionMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } @@ -5820,53 +7242,53 @@ func (m *DiscordInteractionMutation) OldUpdatedAt(ctx context.Context) (v time.T } // ResetUpdatedAt resets all changes to the "updated_at" field. -func (m *DiscordInteractionMutation) ResetUpdatedAt() { +func (m *SessionMutation) ResetUpdatedAt() { m.updated_at = nil } -// SetCommand sets the "command" field. -func (m *DiscordInteractionMutation) SetCommand(s string) { - m.command = &s +// SetExpiresAt sets the "expires_at" field. +func (m *SessionMutation) SetExpiresAt(t time.Time) { + m.expires_at = &t } -// Command returns the value of the "command" field in the mutation. -func (m *DiscordInteractionMutation) Command() (r string, exists bool) { - v := m.command +// ExpiresAt returns the value of the "expires_at" field in the mutation. +func (m *SessionMutation) ExpiresAt() (r time.Time, exists bool) { + v := m.expires_at if v == nil { return } return *v, true } -// OldCommand returns the old "command" field's value of the DiscordInteraction entity. -// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// OldExpiresAt returns the old "expires_at" field's value of the Session entity. +// If the Session object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldCommand(ctx context.Context) (v string, err error) { +func (m *SessionMutation) OldExpiresAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCommand is only allowed on UpdateOne operations") + return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldCommand requires an ID field in the mutation") + return v, errors.New("OldExpiresAt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldCommand: %w", err) + return v, fmt.Errorf("querying old value for OldExpiresAt: %w", err) } - return oldValue.Command, nil + return oldValue.ExpiresAt, nil } -// ResetCommand resets all changes to the "command" field. -func (m *DiscordInteractionMutation) ResetCommand() { - m.command = nil +// ResetExpiresAt resets all changes to the "expires_at" field. +func (m *SessionMutation) ResetExpiresAt() { + m.expires_at = nil } // SetUserID sets the "user_id" field. -func (m *DiscordInteractionMutation) SetUserID(s string) { +func (m *SessionMutation) SetUserID(s string) { m.user = &s } // UserID returns the value of the "user_id" field in the mutation. -func (m *DiscordInteractionMutation) UserID() (r string, exists bool) { +func (m *SessionMutation) UserID() (r string, exists bool) { v := m.user if v == nil { return @@ -5874,10 +7296,10 @@ func (m *DiscordInteractionMutation) UserID() (r string, exists bool) { return *v, true } -// OldUserID returns the old "user_id" field's value of the DiscordInteraction entity. -// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// OldUserID returns the old "user_id" field's value of the Session entity. +// If the Session object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldUserID(ctx context.Context) (v string, err error) { +func (m *SessionMutation) OldUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUserID is only allowed on UpdateOne operations") } @@ -5891,170 +7313,98 @@ func (m *DiscordInteractionMutation) OldUserID(ctx context.Context) (v string, e return oldValue.UserID, nil } -// ResetUserID resets all changes to the "user_id" field. -func (m *DiscordInteractionMutation) ResetUserID() { - m.user = nil -} - -// SetReferenceID sets the "reference_id" field. -func (m *DiscordInteractionMutation) SetReferenceID(s string) { - m.reference_id = &s -} - -// ReferenceID returns the value of the "reference_id" field in the mutation. -func (m *DiscordInteractionMutation) ReferenceID() (r string, exists bool) { - v := m.reference_id - if v == nil { - return - } - return *v, true -} - -// OldReferenceID returns the old "reference_id" field's value of the DiscordInteraction entity. -// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldReferenceID(ctx context.Context) (v string, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldReferenceID is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldReferenceID requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldReferenceID: %w", err) - } - return oldValue.ReferenceID, nil -} - -// ResetReferenceID resets all changes to the "reference_id" field. -func (m *DiscordInteractionMutation) ResetReferenceID() { - m.reference_id = nil -} - -// SetType sets the "type" field. -func (m *DiscordInteractionMutation) SetType(mit models.DiscordInteractionType) { - m._type = &mit -} - -// GetType returns the value of the "type" field in the mutation. -func (m *DiscordInteractionMutation) GetType() (r models.DiscordInteractionType, exists bool) { - v := m._type - if v == nil { - return - } - return *v, true -} - -// OldType returns the old "type" field's value of the DiscordInteraction entity. -// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldType(ctx context.Context) (v models.DiscordInteractionType, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldType is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldType requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldType: %w", err) - } - return oldValue.Type, nil -} - -// ResetType resets all changes to the "type" field. -func (m *DiscordInteractionMutation) ResetType() { - m._type = nil -} - -// SetLocale sets the "locale" field. -func (m *DiscordInteractionMutation) SetLocale(s string) { - m.locale = &s +// ResetUserID resets all changes to the "user_id" field. +func (m *SessionMutation) ResetUserID() { + m.user = nil } -// Locale returns the value of the "locale" field in the mutation. -func (m *DiscordInteractionMutation) Locale() (r string, exists bool) { - v := m.locale +// SetPublicID sets the "public_id" field. +func (m *SessionMutation) SetPublicID(s string) { + m.public_id = &s +} + +// PublicID returns the value of the "public_id" field in the mutation. +func (m *SessionMutation) PublicID() (r string, exists bool) { + v := m.public_id if v == nil { return } return *v, true } -// OldLocale returns the old "locale" field's value of the DiscordInteraction entity. -// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// OldPublicID returns the old "public_id" field's value of the Session entity. +// If the Session object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldLocale(ctx context.Context) (v string, err error) { +func (m *SessionMutation) OldPublicID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldLocale is only allowed on UpdateOne operations") + return v, errors.New("OldPublicID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldLocale requires an ID field in the mutation") + return v, errors.New("OldPublicID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldLocale: %w", err) + return v, fmt.Errorf("querying old value for OldPublicID: %w", err) } - return oldValue.Locale, nil + return oldValue.PublicID, nil } -// ResetLocale resets all changes to the "locale" field. -func (m *DiscordInteractionMutation) ResetLocale() { - m.locale = nil +// ResetPublicID resets all changes to the "public_id" field. +func (m *SessionMutation) ResetPublicID() { + m.public_id = nil } -// SetOptions sets the "options" field. -func (m *DiscordInteractionMutation) SetOptions(mio models.DiscordInteractionOptions) { - m.options = &mio +// SetMetadata sets the "metadata" field. +func (m *SessionMutation) SetMetadata(value map[string]string) { + m.metadata = &value } -// Options returns the value of the "options" field in the mutation. -func (m *DiscordInteractionMutation) Options() (r models.DiscordInteractionOptions, exists bool) { - v := m.options +// Metadata returns the value of the "metadata" field in the mutation. +func (m *SessionMutation) Metadata() (r map[string]string, exists bool) { + v := m.metadata if v == nil { return } return *v, true } -// OldOptions returns the old "options" field's value of the DiscordInteraction entity. -// If the DiscordInteraction object wasn't provided to the builder, the object is fetched from the database. +// OldMetadata returns the old "metadata" field's value of the Session entity. +// If the Session object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *DiscordInteractionMutation) OldOptions(ctx context.Context) (v models.DiscordInteractionOptions, err error) { +func (m *SessionMutation) OldMetadata(ctx context.Context) (v map[string]string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldOptions is only allowed on UpdateOne operations") + return v, errors.New("OldMetadata is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldOptions requires an ID field in the mutation") + return v, errors.New("OldMetadata requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldOptions: %w", err) + return v, fmt.Errorf("querying old value for OldMetadata: %w", err) } - return oldValue.Options, nil + return oldValue.Metadata, nil } -// ResetOptions resets all changes to the "options" field. -func (m *DiscordInteractionMutation) ResetOptions() { - m.options = nil +// ResetMetadata resets all changes to the "metadata" field. +func (m *SessionMutation) ResetMetadata() { + m.metadata = nil } // ClearUser clears the "user" edge to the User entity. -func (m *DiscordInteractionMutation) ClearUser() { +func (m *SessionMutation) ClearUser() { m.cleareduser = true - m.clearedFields[discordinteraction.FieldUserID] = struct{}{} + m.clearedFields[session.FieldUserID] = struct{}{} } // UserCleared reports if the "user" edge to the User entity was cleared. -func (m *DiscordInteractionMutation) UserCleared() bool { +func (m *SessionMutation) UserCleared() bool { return m.cleareduser } // UserIDs returns the "user" edge IDs in the mutation. // Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use // UserID instead. It exists only for internal usage by the builders. -func (m *DiscordInteractionMutation) UserIDs() (ids []string) { +func (m *SessionMutation) UserIDs() (ids []string) { if id := m.user; id != nil { ids = append(ids, *id) } @@ -6062,20 +7412,20 @@ func (m *DiscordInteractionMutation) UserIDs() (ids []string) { } // ResetUser resets all changes to the "user" edge. -func (m *DiscordInteractionMutation) ResetUser() { +func (m *SessionMutation) ResetUser() { m.user = nil m.cleareduser = false } -// Where appends a list predicates to the DiscordInteractionMutation builder. -func (m *DiscordInteractionMutation) Where(ps ...predicate.DiscordInteraction) { +// Where appends a list predicates to the SessionMutation builder. +func (m *SessionMutation) Where(ps ...predicate.Session) { m.predicates = append(m.predicates, ps...) } -// WhereP appends storage-level predicates to the DiscordInteractionMutation builder. Using this method, +// WhereP appends storage-level predicates to the SessionMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. -func (m *DiscordInteractionMutation) WhereP(ps ...func(*sql.Selector)) { - p := make([]predicate.DiscordInteraction, len(ps)) +func (m *SessionMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Session, len(ps)) for i := range ps { p[i] = ps[i] } @@ -6083,48 +7433,42 @@ func (m *DiscordInteractionMutation) WhereP(ps ...func(*sql.Selector)) { } // Op returns the operation name. -func (m *DiscordInteractionMutation) Op() Op { +func (m *SessionMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. -func (m *DiscordInteractionMutation) SetOp(op Op) { +func (m *SessionMutation) SetOp(op Op) { m.op = op } -// Type returns the node type of this mutation (DiscordInteraction). -func (m *DiscordInteractionMutation) Type() string { +// Type returns the node type of this mutation (Session). +func (m *SessionMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). -func (m *DiscordInteractionMutation) Fields() []string { - fields := make([]string, 0, 8) +func (m *SessionMutation) Fields() []string { + fields := make([]string, 0, 6) if m.created_at != nil { - fields = append(fields, discordinteraction.FieldCreatedAt) + fields = append(fields, session.FieldCreatedAt) } if m.updated_at != nil { - fields = append(fields, discordinteraction.FieldUpdatedAt) + fields = append(fields, session.FieldUpdatedAt) } - if m.command != nil { - fields = append(fields, discordinteraction.FieldCommand) + if m.expires_at != nil { + fields = append(fields, session.FieldExpiresAt) } if m.user != nil { - fields = append(fields, discordinteraction.FieldUserID) - } - if m.reference_id != nil { - fields = append(fields, discordinteraction.FieldReferenceID) + fields = append(fields, session.FieldUserID) } - if m._type != nil { - fields = append(fields, discordinteraction.FieldType) - } - if m.locale != nil { - fields = append(fields, discordinteraction.FieldLocale) + if m.public_id != nil { + fields = append(fields, session.FieldPublicID) } - if m.options != nil { - fields = append(fields, discordinteraction.FieldOptions) + if m.metadata != nil { + fields = append(fields, session.FieldMetadata) } return fields } @@ -6132,24 +7476,20 @@ func (m *DiscordInteractionMutation) Fields() []string { // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. -func (m *DiscordInteractionMutation) Field(name string) (ent.Value, bool) { +func (m *SessionMutation) Field(name string) (ent.Value, bool) { switch name { - case discordinteraction.FieldCreatedAt: + case session.FieldCreatedAt: return m.CreatedAt() - case discordinteraction.FieldUpdatedAt: + case session.FieldUpdatedAt: return m.UpdatedAt() - case discordinteraction.FieldCommand: - return m.Command() - case discordinteraction.FieldUserID: + case session.FieldExpiresAt: + return m.ExpiresAt() + case session.FieldUserID: return m.UserID() - case discordinteraction.FieldReferenceID: - return m.ReferenceID() - case discordinteraction.FieldType: - return m.GetType() - case discordinteraction.FieldLocale: - return m.Locale() - case discordinteraction.FieldOptions: - return m.Options() + case session.FieldPublicID: + return m.PublicID() + case session.FieldMetadata: + return m.Metadata() } return nil, false } @@ -6157,180 +7497,156 @@ func (m *DiscordInteractionMutation) Field(name string) (ent.Value, bool) { // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. -func (m *DiscordInteractionMutation) OldField(ctx context.Context, name string) (ent.Value, error) { +func (m *SessionMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { - case discordinteraction.FieldCreatedAt: + case session.FieldCreatedAt: return m.OldCreatedAt(ctx) - case discordinteraction.FieldUpdatedAt: + case session.FieldUpdatedAt: return m.OldUpdatedAt(ctx) - case discordinteraction.FieldCommand: - return m.OldCommand(ctx) - case discordinteraction.FieldUserID: + case session.FieldExpiresAt: + return m.OldExpiresAt(ctx) + case session.FieldUserID: return m.OldUserID(ctx) - case discordinteraction.FieldReferenceID: - return m.OldReferenceID(ctx) - case discordinteraction.FieldType: - return m.OldType(ctx) - case discordinteraction.FieldLocale: - return m.OldLocale(ctx) - case discordinteraction.FieldOptions: - return m.OldOptions(ctx) + case session.FieldPublicID: + return m.OldPublicID(ctx) + case session.FieldMetadata: + return m.OldMetadata(ctx) } - return nil, fmt.Errorf("unknown DiscordInteraction field %s", name) + return nil, fmt.Errorf("unknown Session field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. -func (m *DiscordInteractionMutation) SetField(name string, value ent.Value) error { +func (m *SessionMutation) SetField(name string, value ent.Value) error { switch name { - case discordinteraction.FieldCreatedAt: + case session.FieldCreatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil - case discordinteraction.FieldUpdatedAt: + case session.FieldUpdatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUpdatedAt(v) return nil - case discordinteraction.FieldCommand: - v, ok := value.(string) + case session.FieldExpiresAt: + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetCommand(v) + m.SetExpiresAt(v) return nil - case discordinteraction.FieldUserID: + case session.FieldUserID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUserID(v) return nil - case discordinteraction.FieldReferenceID: - v, ok := value.(string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetReferenceID(v) - return nil - case discordinteraction.FieldType: - v, ok := value.(models.DiscordInteractionType) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetType(v) - return nil - case discordinteraction.FieldLocale: + case session.FieldPublicID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetLocale(v) + m.SetPublicID(v) return nil - case discordinteraction.FieldOptions: - v, ok := value.(models.DiscordInteractionOptions) + case session.FieldMetadata: + v, ok := value.(map[string]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetOptions(v) + m.SetMetadata(v) return nil } - return fmt.Errorf("unknown DiscordInteraction field %s", name) + return fmt.Errorf("unknown Session field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. -func (m *DiscordInteractionMutation) AddedFields() []string { +func (m *SessionMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. -func (m *DiscordInteractionMutation) AddedField(name string) (ent.Value, bool) { +func (m *SessionMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. -func (m *DiscordInteractionMutation) AddField(name string, value ent.Value) error { +func (m *SessionMutation) AddField(name string, value ent.Value) error { switch name { } - return fmt.Errorf("unknown DiscordInteraction numeric field %s", name) + return fmt.Errorf("unknown Session numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. -func (m *DiscordInteractionMutation) ClearedFields() []string { +func (m *SessionMutation) ClearedFields() []string { return nil } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. -func (m *DiscordInteractionMutation) FieldCleared(name string) bool { +func (m *SessionMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. -func (m *DiscordInteractionMutation) ClearField(name string) error { - return fmt.Errorf("unknown DiscordInteraction nullable field %s", name) +func (m *SessionMutation) ClearField(name string) error { + return fmt.Errorf("unknown Session nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. -func (m *DiscordInteractionMutation) ResetField(name string) error { +func (m *SessionMutation) ResetField(name string) error { switch name { - case discordinteraction.FieldCreatedAt: + case session.FieldCreatedAt: m.ResetCreatedAt() return nil - case discordinteraction.FieldUpdatedAt: + case session.FieldUpdatedAt: m.ResetUpdatedAt() return nil - case discordinteraction.FieldCommand: - m.ResetCommand() + case session.FieldExpiresAt: + m.ResetExpiresAt() return nil - case discordinteraction.FieldUserID: + case session.FieldUserID: m.ResetUserID() return nil - case discordinteraction.FieldReferenceID: - m.ResetReferenceID() - return nil - case discordinteraction.FieldType: - m.ResetType() - return nil - case discordinteraction.FieldLocale: - m.ResetLocale() + case session.FieldPublicID: + m.ResetPublicID() return nil - case discordinteraction.FieldOptions: - m.ResetOptions() + case session.FieldMetadata: + m.ResetMetadata() return nil } - return fmt.Errorf("unknown DiscordInteraction field %s", name) + return fmt.Errorf("unknown Session field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. -func (m *DiscordInteractionMutation) AddedEdges() []string { +func (m *SessionMutation) AddedEdges() []string { edges := make([]string, 0, 1) if m.user != nil { - edges = append(edges, discordinteraction.EdgeUser) + edges = append(edges, session.EdgeUser) } return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. -func (m *DiscordInteractionMutation) AddedIDs(name string) []ent.Value { +func (m *SessionMutation) AddedIDs(name string) []ent.Value { switch name { - case discordinteraction.EdgeUser: + case session.EdgeUser: if id := m.user; id != nil { return []ent.Value{*id} } @@ -6339,31 +7655,31 @@ func (m *DiscordInteractionMutation) AddedIDs(name string) []ent.Value { } // RemovedEdges returns all edge names that were removed in this mutation. -func (m *DiscordInteractionMutation) RemovedEdges() []string { +func (m *SessionMutation) RemovedEdges() []string { edges := make([]string, 0, 1) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. -func (m *DiscordInteractionMutation) RemovedIDs(name string) []ent.Value { +func (m *SessionMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. -func (m *DiscordInteractionMutation) ClearedEdges() []string { +func (m *SessionMutation) ClearedEdges() []string { edges := make([]string, 0, 1) if m.cleareduser { - edges = append(edges, discordinteraction.EdgeUser) + edges = append(edges, session.EdgeUser) } return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. -func (m *DiscordInteractionMutation) EdgeCleared(name string) bool { +func (m *SessionMutation) EdgeCleared(name string) bool { switch name { - case discordinteraction.EdgeUser: + case session.EdgeUser: return m.cleareduser } return false @@ -6371,24 +7687,24 @@ func (m *DiscordInteractionMutation) EdgeCleared(name string) bool { // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. -func (m *DiscordInteractionMutation) ClearEdge(name string) error { +func (m *SessionMutation) ClearEdge(name string) error { switch name { - case discordinteraction.EdgeUser: + case session.EdgeUser: m.ClearUser() return nil } - return fmt.Errorf("unknown DiscordInteraction unique edge %s", name) + return fmt.Errorf("unknown Session unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. -func (m *DiscordInteractionMutation) ResetEdge(name string) error { +func (m *SessionMutation) ResetEdge(name string) error { switch name { - case discordinteraction.EdgeUser: + case session.EdgeUser: m.ResetUser() return nil } - return fmt.Errorf("unknown DiscordInteraction edge %s", name) + return fmt.Errorf("unknown Session edge %s", name) } // UserMutation represents an operation that mutates the User nodes in the graph. @@ -6399,6 +7715,7 @@ type UserMutation struct { id *string created_at *time.Time updated_at *time.Time + username *string permissions *string feature_flags *[]string appendfeature_flags []string @@ -6415,6 +7732,9 @@ type UserMutation struct { content map[string]struct{} removedcontent map[string]struct{} clearedcontent bool + sessions map[string]struct{} + removedsessions map[string]struct{} + clearedsessions bool done bool oldValue func(context.Context) (*User, error) predicates []predicate.User @@ -6596,6 +7916,42 @@ func (m *UserMutation) ResetUpdatedAt() { m.updated_at = nil } +// SetUsername sets the "username" field. +func (m *UserMutation) SetUsername(s string) { + m.username = &s +} + +// Username returns the value of the "username" field in the mutation. +func (m *UserMutation) Username() (r string, exists bool) { + v := m.username + if v == nil { + return + } + return *v, true +} + +// OldUsername returns the old "username" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldUsername(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUsername is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUsername requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUsername: %w", err) + } + return oldValue.Username, nil +} + +// ResetUsername resets all changes to the "username" field. +func (m *UserMutation) ResetUsername() { + m.username = nil +} + // SetPermissions sets the "permissions" field. func (m *UserMutation) SetPermissions(s string) { m.permissions = &s @@ -6913,6 +8269,60 @@ func (m *UserMutation) ResetContent() { m.removedcontent = nil } +// AddSessionIDs adds the "sessions" edge to the Session entity by ids. +func (m *UserMutation) AddSessionIDs(ids ...string) { + if m.sessions == nil { + m.sessions = make(map[string]struct{}) + } + for i := range ids { + m.sessions[ids[i]] = struct{}{} + } +} + +// ClearSessions clears the "sessions" edge to the Session entity. +func (m *UserMutation) ClearSessions() { + m.clearedsessions = true +} + +// SessionsCleared reports if the "sessions" edge to the Session entity was cleared. +func (m *UserMutation) SessionsCleared() bool { + return m.clearedsessions +} + +// RemoveSessionIDs removes the "sessions" edge to the Session entity by IDs. +func (m *UserMutation) RemoveSessionIDs(ids ...string) { + if m.removedsessions == nil { + m.removedsessions = make(map[string]struct{}) + } + for i := range ids { + delete(m.sessions, ids[i]) + m.removedsessions[ids[i]] = struct{}{} + } +} + +// RemovedSessions returns the removed IDs of the "sessions" edge to the Session entity. +func (m *UserMutation) RemovedSessionsIDs() (ids []string) { + for id := range m.removedsessions { + ids = append(ids, id) + } + return +} + +// SessionsIDs returns the "sessions" edge IDs in the mutation. +func (m *UserMutation) SessionsIDs() (ids []string) { + for id := range m.sessions { + ids = append(ids, id) + } + return +} + +// ResetSessions resets all changes to the "sessions" edge. +func (m *UserMutation) ResetSessions() { + m.sessions = nil + m.clearedsessions = false + m.removedsessions = nil +} + // Where appends a list predicates to the UserMutation builder. func (m *UserMutation) Where(ps ...predicate.User) { m.predicates = append(m.predicates, ps...) @@ -6947,13 +8357,16 @@ func (m *UserMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *UserMutation) Fields() []string { - fields := make([]string, 0, 4) + fields := make([]string, 0, 5) if m.created_at != nil { fields = append(fields, user.FieldCreatedAt) } if m.updated_at != nil { fields = append(fields, user.FieldUpdatedAt) } + if m.username != nil { + fields = append(fields, user.FieldUsername) + } if m.permissions != nil { fields = append(fields, user.FieldPermissions) } @@ -6972,6 +8385,8 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) { return m.CreatedAt() case user.FieldUpdatedAt: return m.UpdatedAt() + case user.FieldUsername: + return m.Username() case user.FieldPermissions: return m.Permissions() case user.FieldFeatureFlags: @@ -6989,6 +8404,8 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldCreatedAt(ctx) case user.FieldUpdatedAt: return m.OldUpdatedAt(ctx) + case user.FieldUsername: + return m.OldUsername(ctx) case user.FieldPermissions: return m.OldPermissions(ctx) case user.FieldFeatureFlags: @@ -7016,6 +8433,13 @@ func (m *UserMutation) SetField(name string, value ent.Value) error { } m.SetUpdatedAt(v) return nil + case user.FieldUsername: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUsername(v) + return nil case user.FieldPermissions: v, ok := value.(string) if !ok { @@ -7094,6 +8518,9 @@ func (m *UserMutation) ResetField(name string) error { case user.FieldUpdatedAt: m.ResetUpdatedAt() return nil + case user.FieldUsername: + m.ResetUsername() + return nil case user.FieldPermissions: m.ResetPermissions() return nil @@ -7106,7 +8533,7 @@ func (m *UserMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *UserMutation) AddedEdges() []string { - edges := make([]string, 0, 4) + edges := make([]string, 0, 5) if m.discord_interactions != nil { edges = append(edges, user.EdgeDiscordInteractions) } @@ -7119,6 +8546,9 @@ func (m *UserMutation) AddedEdges() []string { if m.content != nil { edges = append(edges, user.EdgeContent) } + if m.sessions != nil { + edges = append(edges, user.EdgeSessions) + } return edges } @@ -7150,13 +8580,19 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case user.EdgeSessions: + ids := make([]ent.Value, 0, len(m.sessions)) + for id := range m.sessions { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *UserMutation) RemovedEdges() []string { - edges := make([]string, 0, 4) + edges := make([]string, 0, 5) if m.removeddiscord_interactions != nil { edges = append(edges, user.EdgeDiscordInteractions) } @@ -7169,6 +8605,9 @@ func (m *UserMutation) RemovedEdges() []string { if m.removedcontent != nil { edges = append(edges, user.EdgeContent) } + if m.removedsessions != nil { + edges = append(edges, user.EdgeSessions) + } return edges } @@ -7200,13 +8639,19 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case user.EdgeSessions: + ids := make([]ent.Value, 0, len(m.removedsessions)) + for id := range m.removedsessions { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *UserMutation) ClearedEdges() []string { - edges := make([]string, 0, 4) + edges := make([]string, 0, 5) if m.cleareddiscord_interactions { edges = append(edges, user.EdgeDiscordInteractions) } @@ -7219,6 +8664,9 @@ func (m *UserMutation) ClearedEdges() []string { if m.clearedcontent { edges = append(edges, user.EdgeContent) } + if m.clearedsessions { + edges = append(edges, user.EdgeSessions) + } return edges } @@ -7234,6 +8682,8 @@ func (m *UserMutation) EdgeCleared(name string) bool { return m.clearedconnections case user.EdgeContent: return m.clearedcontent + case user.EdgeSessions: + return m.clearedsessions } return false } @@ -7262,6 +8712,9 @@ func (m *UserMutation) ResetEdge(name string) error { case user.EdgeContent: m.ResetContent() return nil + case user.EdgeSessions: + m.ResetSessions() + return nil } return fmt.Errorf("unknown User edge %s", name) } diff --git a/internal/database/ent/db/predicate/predicate.go b/internal/database/ent/db/predicate/predicate.go index 9814087f..5f57ffbb 100644 --- a/internal/database/ent/db/predicate/predicate.go +++ b/internal/database/ent/db/predicate/predicate.go @@ -21,6 +21,9 @@ type AppConfiguration func(*sql.Selector) // ApplicationCommand is the predicate function for applicationcommand builders. type ApplicationCommand func(*sql.Selector) +// AuthNonce is the predicate function for authnonce builders. +type AuthNonce func(*sql.Selector) + // Clan is the predicate function for clan builders. type Clan func(*sql.Selector) @@ -30,6 +33,9 @@ type CronTask func(*sql.Selector) // DiscordInteraction is the predicate function for discordinteraction builders. type DiscordInteraction func(*sql.Selector) +// Session is the predicate function for session builders. +type Session func(*sql.Selector) + // User is the predicate function for user builders. type User func(*sql.Selector) diff --git a/internal/database/ent/db/runtime.go b/internal/database/ent/db/runtime.go index 7e0f56ad..34a91098 100644 --- a/internal/database/ent/db/runtime.go +++ b/internal/database/ent/db/runtime.go @@ -10,9 +10,11 @@ import ( "github.com/cufee/aftermath/internal/database/ent/db/achievementssnapshot" "github.com/cufee/aftermath/internal/database/ent/db/appconfiguration" "github.com/cufee/aftermath/internal/database/ent/db/applicationcommand" + "github.com/cufee/aftermath/internal/database/ent/db/authnonce" "github.com/cufee/aftermath/internal/database/ent/db/clan" "github.com/cufee/aftermath/internal/database/ent/db/crontask" "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/ent/db/session" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -161,6 +163,30 @@ func init() { applicationcommandDescID := applicationcommandFields[0].Descriptor() // applicationcommand.DefaultID holds the default value on creation for the id field. applicationcommand.DefaultID = applicationcommandDescID.Default.(func() string) + authnonceFields := schema.AuthNonce{}.Fields() + _ = authnonceFields + // authnonceDescCreatedAt is the schema descriptor for created_at field. + authnonceDescCreatedAt := authnonceFields[1].Descriptor() + // authnonce.DefaultCreatedAt holds the default value on creation for the created_at field. + authnonce.DefaultCreatedAt = authnonceDescCreatedAt.Default.(func() time.Time) + // authnonceDescUpdatedAt is the schema descriptor for updated_at field. + authnonceDescUpdatedAt := authnonceFields[2].Descriptor() + // authnonce.DefaultUpdatedAt holds the default value on creation for the updated_at field. + authnonce.DefaultUpdatedAt = authnonceDescUpdatedAt.Default.(func() time.Time) + // authnonce.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + authnonce.UpdateDefaultUpdatedAt = authnonceDescUpdatedAt.UpdateDefault.(func() time.Time) + // authnonceDescIdentifier is the schema descriptor for identifier field. + authnonceDescIdentifier := authnonceFields[5].Descriptor() + // authnonce.IdentifierValidator is a validator for the "identifier" field. It is called by the builders before save. + authnonce.IdentifierValidator = authnonceDescIdentifier.Validators[0].(func(string) error) + // authnonceDescPublicID is the schema descriptor for public_id field. + authnonceDescPublicID := authnonceFields[6].Descriptor() + // authnonce.PublicIDValidator is a validator for the "public_id" field. It is called by the builders before save. + authnonce.PublicIDValidator = authnonceDescPublicID.Validators[0].(func(string) error) + // authnonceDescID is the schema descriptor for id field. + authnonceDescID := authnonceFields[0].Descriptor() + // authnonce.DefaultID holds the default value on creation for the id field. + authnonce.DefaultID = authnonceDescID.Default.(func() string) clanFields := schema.Clan{}.Fields() _ = clanFields // clanDescCreatedAt is the schema descriptor for created_at field. @@ -237,6 +263,26 @@ func init() { discordinteractionDescID := discordinteractionFields[0].Descriptor() // discordinteraction.DefaultID holds the default value on creation for the id field. discordinteraction.DefaultID = discordinteractionDescID.Default.(func() string) + sessionFields := schema.Session{}.Fields() + _ = sessionFields + // sessionDescCreatedAt is the schema descriptor for created_at field. + sessionDescCreatedAt := sessionFields[1].Descriptor() + // session.DefaultCreatedAt holds the default value on creation for the created_at field. + session.DefaultCreatedAt = sessionDescCreatedAt.Default.(func() time.Time) + // sessionDescUpdatedAt is the schema descriptor for updated_at field. + sessionDescUpdatedAt := sessionFields[2].Descriptor() + // session.DefaultUpdatedAt holds the default value on creation for the updated_at field. + session.DefaultUpdatedAt = sessionDescUpdatedAt.Default.(func() time.Time) + // session.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + session.UpdateDefaultUpdatedAt = sessionDescUpdatedAt.UpdateDefault.(func() time.Time) + // sessionDescPublicID is the schema descriptor for public_id field. + sessionDescPublicID := sessionFields[5].Descriptor() + // session.PublicIDValidator is a validator for the "public_id" field. It is called by the builders before save. + session.PublicIDValidator = sessionDescPublicID.Validators[0].(func(string) error) + // sessionDescID is the schema descriptor for id field. + sessionDescID := sessionFields[0].Descriptor() + // session.DefaultID holds the default value on creation for the id field. + session.DefaultID = sessionDescID.Default.(func() string) userFields := schema.User{}.Fields() _ = userFields // userDescCreatedAt is the schema descriptor for created_at field. @@ -249,8 +295,12 @@ func init() { user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time) // user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() time.Time) + // userDescUsername is the schema descriptor for username field. + userDescUsername := userFields[3].Descriptor() + // user.DefaultUsername holds the default value on creation for the username field. + user.DefaultUsername = userDescUsername.Default.(string) // userDescPermissions is the schema descriptor for permissions field. - userDescPermissions := userFields[3].Descriptor() + userDescPermissions := userFields[4].Descriptor() // user.DefaultPermissions holds the default value on creation for the permissions field. user.DefaultPermissions = userDescPermissions.Default.(string) userconnectionFields := schema.UserConnection{}.Fields() diff --git a/internal/database/ent/db/session.go b/internal/database/ent/db/session.go new file mode 100644 index 00000000..34f65e03 --- /dev/null +++ b/internal/database/ent/db/session.go @@ -0,0 +1,193 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/cufee/aftermath/internal/database/ent/db/session" + "github.com/cufee/aftermath/internal/database/ent/db/user" +) + +// Session is the model entity for the Session schema. +type Session struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // ExpiresAt holds the value of the "expires_at" field. + ExpiresAt time.Time `json:"expires_at,omitempty"` + // UserID holds the value of the "user_id" field. + UserID string `json:"user_id,omitempty"` + // PublicID holds the value of the "public_id" field. + PublicID string `json:"public_id,omitempty"` + // Metadata holds the value of the "metadata" field. + Metadata map[string]string `json:"metadata,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the SessionQuery when eager-loading is set. + Edges SessionEdges `json:"edges"` + selectValues sql.SelectValues +} + +// SessionEdges holds the relations/edges for other nodes in the graph. +type SessionEdges struct { + // User holds the value of the user edge. + User *User `json:"user,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e SessionEdges) UserOrErr() (*User, error) { + if e.User != nil { + return e.User, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: user.Label} + } + return nil, &NotLoadedError{edge: "user"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Session) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case session.FieldMetadata: + values[i] = new([]byte) + case session.FieldID, session.FieldUserID, session.FieldPublicID: + values[i] = new(sql.NullString) + case session.FieldCreatedAt, session.FieldUpdatedAt, session.FieldExpiresAt: + values[i] = new(sql.NullTime) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Session fields. +func (s *Session) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case session.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + s.ID = value.String + } + case session.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + s.CreatedAt = value.Time + } + case session.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + s.UpdatedAt = value.Time + } + case session.FieldExpiresAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field expires_at", values[i]) + } else if value.Valid { + s.ExpiresAt = value.Time + } + case session.FieldUserID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field user_id", values[i]) + } else if value.Valid { + s.UserID = value.String + } + case session.FieldPublicID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field public_id", values[i]) + } else if value.Valid { + s.PublicID = value.String + } + case session.FieldMetadata: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field metadata", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &s.Metadata); err != nil { + return fmt.Errorf("unmarshal field metadata: %w", err) + } + } + default: + s.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the Session. +// This includes values selected through modifiers, order, etc. +func (s *Session) Value(name string) (ent.Value, error) { + return s.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the Session entity. +func (s *Session) QueryUser() *UserQuery { + return NewSessionClient(s.config).QueryUser(s) +} + +// Update returns a builder for updating this Session. +// Note that you need to call Session.Unwrap() before calling this method if this Session +// was returned from a transaction, and the transaction was committed or rolled back. +func (s *Session) Update() *SessionUpdateOne { + return NewSessionClient(s.config).UpdateOne(s) +} + +// Unwrap unwraps the Session entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (s *Session) Unwrap() *Session { + _tx, ok := s.config.driver.(*txDriver) + if !ok { + panic("db: Session is not a transactional entity") + } + s.config.driver = _tx.drv + return s +} + +// String implements the fmt.Stringer. +func (s *Session) String() string { + var builder strings.Builder + builder.WriteString("Session(") + builder.WriteString(fmt.Sprintf("id=%v, ", s.ID)) + builder.WriteString("created_at=") + builder.WriteString(s.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(s.UpdatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("expires_at=") + builder.WriteString(s.ExpiresAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("user_id=") + builder.WriteString(s.UserID) + builder.WriteString(", ") + builder.WriteString("public_id=") + builder.WriteString(s.PublicID) + builder.WriteString(", ") + builder.WriteString("metadata=") + builder.WriteString(fmt.Sprintf("%v", s.Metadata)) + builder.WriteByte(')') + return builder.String() +} + +// Sessions is a parsable slice of Session. +type Sessions []*Session diff --git a/internal/database/ent/db/session/session.go b/internal/database/ent/db/session/session.go new file mode 100644 index 00000000..52bb3557 --- /dev/null +++ b/internal/database/ent/db/session/session.go @@ -0,0 +1,121 @@ +// Code generated by ent, DO NOT EDIT. + +package session + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the session type in the database. + Label = "session" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldExpiresAt holds the string denoting the expires_at field in the database. + FieldExpiresAt = "expires_at" + // FieldUserID holds the string denoting the user_id field in the database. + FieldUserID = "user_id" + // FieldPublicID holds the string denoting the public_id field in the database. + FieldPublicID = "public_id" + // FieldMetadata holds the string denoting the metadata field in the database. + FieldMetadata = "metadata" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // Table holds the table name of the session in the database. + Table = "sessions" + // UserTable is the table that holds the user relation/edge. + UserTable = "sessions" + // UserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + UserInverseTable = "users" + // UserColumn is the table column denoting the user relation/edge. + UserColumn = "user_id" +) + +// Columns holds all SQL columns for session fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldExpiresAt, + FieldUserID, + FieldPublicID, + FieldMetadata, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // PublicIDValidator is a validator for the "public_id" field. It is called by the builders before save. + PublicIDValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// OrderOption defines the ordering options for the Session queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByExpiresAt orders the results by the expires_at field. +func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldExpiresAt, opts...).ToFunc() +} + +// ByUserID orders the results by the user_id field. +func ByUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserID, opts...).ToFunc() +} + +// ByPublicID orders the results by the public_id field. +func ByPublicID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPublicID, opts...).ToFunc() +} + +// ByUserField orders the results by user field. +func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...)) + } +} +func newUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) +} diff --git a/internal/database/ent/db/session/where.go b/internal/database/ent/db/session/where.go new file mode 100644 index 00000000..6409fe74 --- /dev/null +++ b/internal/database/ent/db/session/where.go @@ -0,0 +1,379 @@ +// Code generated by ent, DO NOT EDIT. + +package session + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.Session { + return predicate.Session(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.Session { + return predicate.Session(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.Session { + return predicate.Session(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.Session { + return predicate.Session(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.Session { + return predicate.Session(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.Session { + return predicate.Session(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.Session { + return predicate.Session(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.Session { + return predicate.Session(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.Session { + return predicate.Session(sql.FieldContainsFold(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ. +func ExpiresAt(v time.Time) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldExpiresAt, v)) +} + +// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. +func UserID(v string) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldUserID, v)) +} + +// PublicID applies equality check predicate on the "public_id" field. It's identical to PublicIDEQ. +func PublicID(v string) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldPublicID, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.Session { + return predicate.Session(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.Session { + return predicate.Session(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.Session { + return predicate.Session(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.Session { + return predicate.Session(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.Session { + return predicate.Session(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.Session { + return predicate.Session(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.Session { + return predicate.Session(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.Session { + return predicate.Session(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.Session { + return predicate.Session(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.Session { + return predicate.Session(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.Session { + return predicate.Session(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.Session { + return predicate.Session(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.Session { + return predicate.Session(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.Session { + return predicate.Session(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// ExpiresAtEQ applies the EQ predicate on the "expires_at" field. +func ExpiresAtEQ(v time.Time) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldExpiresAt, v)) +} + +// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field. +func ExpiresAtNEQ(v time.Time) predicate.Session { + return predicate.Session(sql.FieldNEQ(FieldExpiresAt, v)) +} + +// ExpiresAtIn applies the In predicate on the "expires_at" field. +func ExpiresAtIn(vs ...time.Time) predicate.Session { + return predicate.Session(sql.FieldIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field. +func ExpiresAtNotIn(vs ...time.Time) predicate.Session { + return predicate.Session(sql.FieldNotIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtGT applies the GT predicate on the "expires_at" field. +func ExpiresAtGT(v time.Time) predicate.Session { + return predicate.Session(sql.FieldGT(FieldExpiresAt, v)) +} + +// ExpiresAtGTE applies the GTE predicate on the "expires_at" field. +func ExpiresAtGTE(v time.Time) predicate.Session { + return predicate.Session(sql.FieldGTE(FieldExpiresAt, v)) +} + +// ExpiresAtLT applies the LT predicate on the "expires_at" field. +func ExpiresAtLT(v time.Time) predicate.Session { + return predicate.Session(sql.FieldLT(FieldExpiresAt, v)) +} + +// ExpiresAtLTE applies the LTE predicate on the "expires_at" field. +func ExpiresAtLTE(v time.Time) predicate.Session { + return predicate.Session(sql.FieldLTE(FieldExpiresAt, v)) +} + +// UserIDEQ applies the EQ predicate on the "user_id" field. +func UserIDEQ(v string) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldUserID, v)) +} + +// UserIDNEQ applies the NEQ predicate on the "user_id" field. +func UserIDNEQ(v string) predicate.Session { + return predicate.Session(sql.FieldNEQ(FieldUserID, v)) +} + +// UserIDIn applies the In predicate on the "user_id" field. +func UserIDIn(vs ...string) predicate.Session { + return predicate.Session(sql.FieldIn(FieldUserID, vs...)) +} + +// UserIDNotIn applies the NotIn predicate on the "user_id" field. +func UserIDNotIn(vs ...string) predicate.Session { + return predicate.Session(sql.FieldNotIn(FieldUserID, vs...)) +} + +// UserIDGT applies the GT predicate on the "user_id" field. +func UserIDGT(v string) predicate.Session { + return predicate.Session(sql.FieldGT(FieldUserID, v)) +} + +// UserIDGTE applies the GTE predicate on the "user_id" field. +func UserIDGTE(v string) predicate.Session { + return predicate.Session(sql.FieldGTE(FieldUserID, v)) +} + +// UserIDLT applies the LT predicate on the "user_id" field. +func UserIDLT(v string) predicate.Session { + return predicate.Session(sql.FieldLT(FieldUserID, v)) +} + +// UserIDLTE applies the LTE predicate on the "user_id" field. +func UserIDLTE(v string) predicate.Session { + return predicate.Session(sql.FieldLTE(FieldUserID, v)) +} + +// UserIDContains applies the Contains predicate on the "user_id" field. +func UserIDContains(v string) predicate.Session { + return predicate.Session(sql.FieldContains(FieldUserID, v)) +} + +// UserIDHasPrefix applies the HasPrefix predicate on the "user_id" field. +func UserIDHasPrefix(v string) predicate.Session { + return predicate.Session(sql.FieldHasPrefix(FieldUserID, v)) +} + +// UserIDHasSuffix applies the HasSuffix predicate on the "user_id" field. +func UserIDHasSuffix(v string) predicate.Session { + return predicate.Session(sql.FieldHasSuffix(FieldUserID, v)) +} + +// UserIDEqualFold applies the EqualFold predicate on the "user_id" field. +func UserIDEqualFold(v string) predicate.Session { + return predicate.Session(sql.FieldEqualFold(FieldUserID, v)) +} + +// UserIDContainsFold applies the ContainsFold predicate on the "user_id" field. +func UserIDContainsFold(v string) predicate.Session { + return predicate.Session(sql.FieldContainsFold(FieldUserID, v)) +} + +// PublicIDEQ applies the EQ predicate on the "public_id" field. +func PublicIDEQ(v string) predicate.Session { + return predicate.Session(sql.FieldEQ(FieldPublicID, v)) +} + +// PublicIDNEQ applies the NEQ predicate on the "public_id" field. +func PublicIDNEQ(v string) predicate.Session { + return predicate.Session(sql.FieldNEQ(FieldPublicID, v)) +} + +// PublicIDIn applies the In predicate on the "public_id" field. +func PublicIDIn(vs ...string) predicate.Session { + return predicate.Session(sql.FieldIn(FieldPublicID, vs...)) +} + +// PublicIDNotIn applies the NotIn predicate on the "public_id" field. +func PublicIDNotIn(vs ...string) predicate.Session { + return predicate.Session(sql.FieldNotIn(FieldPublicID, vs...)) +} + +// PublicIDGT applies the GT predicate on the "public_id" field. +func PublicIDGT(v string) predicate.Session { + return predicate.Session(sql.FieldGT(FieldPublicID, v)) +} + +// PublicIDGTE applies the GTE predicate on the "public_id" field. +func PublicIDGTE(v string) predicate.Session { + return predicate.Session(sql.FieldGTE(FieldPublicID, v)) +} + +// PublicIDLT applies the LT predicate on the "public_id" field. +func PublicIDLT(v string) predicate.Session { + return predicate.Session(sql.FieldLT(FieldPublicID, v)) +} + +// PublicIDLTE applies the LTE predicate on the "public_id" field. +func PublicIDLTE(v string) predicate.Session { + return predicate.Session(sql.FieldLTE(FieldPublicID, v)) +} + +// PublicIDContains applies the Contains predicate on the "public_id" field. +func PublicIDContains(v string) predicate.Session { + return predicate.Session(sql.FieldContains(FieldPublicID, v)) +} + +// PublicIDHasPrefix applies the HasPrefix predicate on the "public_id" field. +func PublicIDHasPrefix(v string) predicate.Session { + return predicate.Session(sql.FieldHasPrefix(FieldPublicID, v)) +} + +// PublicIDHasSuffix applies the HasSuffix predicate on the "public_id" field. +func PublicIDHasSuffix(v string) predicate.Session { + return predicate.Session(sql.FieldHasSuffix(FieldPublicID, v)) +} + +// PublicIDEqualFold applies the EqualFold predicate on the "public_id" field. +func PublicIDEqualFold(v string) predicate.Session { + return predicate.Session(sql.FieldEqualFold(FieldPublicID, v)) +} + +// PublicIDContainsFold applies the ContainsFold predicate on the "public_id" field. +func PublicIDContainsFold(v string) predicate.Session { + return predicate.Session(sql.FieldContainsFold(FieldPublicID, v)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.Session { + return predicate.Session(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). +func HasUserWith(preds ...predicate.User) predicate.Session { + return predicate.Session(func(s *sql.Selector) { + step := newUserStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Session) predicate.Session { + return predicate.Session(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Session) predicate.Session { + return predicate.Session(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Session) predicate.Session { + return predicate.Session(sql.NotPredicates(p)) +} diff --git a/internal/database/ent/db/session_create.go b/internal/database/ent/db/session_create.go new file mode 100644 index 00000000..38e5c37f --- /dev/null +++ b/internal/database/ent/db/session_create.go @@ -0,0 +1,329 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/session" + "github.com/cufee/aftermath/internal/database/ent/db/user" +) + +// SessionCreate is the builder for creating a Session entity. +type SessionCreate struct { + config + mutation *SessionMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (sc *SessionCreate) SetCreatedAt(t time.Time) *SessionCreate { + sc.mutation.SetCreatedAt(t) + return sc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (sc *SessionCreate) SetNillableCreatedAt(t *time.Time) *SessionCreate { + if t != nil { + sc.SetCreatedAt(*t) + } + return sc +} + +// SetUpdatedAt sets the "updated_at" field. +func (sc *SessionCreate) SetUpdatedAt(t time.Time) *SessionCreate { + sc.mutation.SetUpdatedAt(t) + return sc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (sc *SessionCreate) SetNillableUpdatedAt(t *time.Time) *SessionCreate { + if t != nil { + sc.SetUpdatedAt(*t) + } + return sc +} + +// SetExpiresAt sets the "expires_at" field. +func (sc *SessionCreate) SetExpiresAt(t time.Time) *SessionCreate { + sc.mutation.SetExpiresAt(t) + return sc +} + +// SetUserID sets the "user_id" field. +func (sc *SessionCreate) SetUserID(s string) *SessionCreate { + sc.mutation.SetUserID(s) + return sc +} + +// SetPublicID sets the "public_id" field. +func (sc *SessionCreate) SetPublicID(s string) *SessionCreate { + sc.mutation.SetPublicID(s) + return sc +} + +// SetMetadata sets the "metadata" field. +func (sc *SessionCreate) SetMetadata(m map[string]string) *SessionCreate { + sc.mutation.SetMetadata(m) + return sc +} + +// SetID sets the "id" field. +func (sc *SessionCreate) SetID(s string) *SessionCreate { + sc.mutation.SetID(s) + return sc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (sc *SessionCreate) SetNillableID(s *string) *SessionCreate { + if s != nil { + sc.SetID(*s) + } + return sc +} + +// SetUser sets the "user" edge to the User entity. +func (sc *SessionCreate) SetUser(u *User) *SessionCreate { + return sc.SetUserID(u.ID) +} + +// Mutation returns the SessionMutation object of the builder. +func (sc *SessionCreate) Mutation() *SessionMutation { + return sc.mutation +} + +// Save creates the Session in the database. +func (sc *SessionCreate) Save(ctx context.Context) (*Session, error) { + sc.defaults() + return withHooks(ctx, sc.sqlSave, sc.mutation, sc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (sc *SessionCreate) SaveX(ctx context.Context) *Session { + v, err := sc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (sc *SessionCreate) Exec(ctx context.Context) error { + _, err := sc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (sc *SessionCreate) ExecX(ctx context.Context) { + if err := sc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (sc *SessionCreate) defaults() { + if _, ok := sc.mutation.CreatedAt(); !ok { + v := session.DefaultCreatedAt() + sc.mutation.SetCreatedAt(v) + } + if _, ok := sc.mutation.UpdatedAt(); !ok { + v := session.DefaultUpdatedAt() + sc.mutation.SetUpdatedAt(v) + } + if _, ok := sc.mutation.ID(); !ok { + v := session.DefaultID() + sc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (sc *SessionCreate) check() error { + if _, ok := sc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "Session.created_at"`)} + } + if _, ok := sc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "Session.updated_at"`)} + } + if _, ok := sc.mutation.ExpiresAt(); !ok { + return &ValidationError{Name: "expires_at", err: errors.New(`db: missing required field "Session.expires_at"`)} + } + if _, ok := sc.mutation.UserID(); !ok { + return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "Session.user_id"`)} + } + if _, ok := sc.mutation.PublicID(); !ok { + return &ValidationError{Name: "public_id", err: errors.New(`db: missing required field "Session.public_id"`)} + } + if v, ok := sc.mutation.PublicID(); ok { + if err := session.PublicIDValidator(v); err != nil { + return &ValidationError{Name: "public_id", err: fmt.Errorf(`db: validator failed for field "Session.public_id": %w`, err)} + } + } + if _, ok := sc.mutation.Metadata(); !ok { + return &ValidationError{Name: "metadata", err: errors.New(`db: missing required field "Session.metadata"`)} + } + if _, ok := sc.mutation.UserID(); !ok { + return &ValidationError{Name: "user", err: errors.New(`db: missing required edge "Session.user"`)} + } + return nil +} + +func (sc *SessionCreate) sqlSave(ctx context.Context) (*Session, error) { + if err := sc.check(); err != nil { + return nil, err + } + _node, _spec := sc.createSpec() + if err := sqlgraph.CreateNode(ctx, sc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected Session.ID type: %T", _spec.ID.Value) + } + } + sc.mutation.id = &_node.ID + sc.mutation.done = true + return _node, nil +} + +func (sc *SessionCreate) createSpec() (*Session, *sqlgraph.CreateSpec) { + var ( + _node = &Session{config: sc.config} + _spec = sqlgraph.NewCreateSpec(session.Table, sqlgraph.NewFieldSpec(session.FieldID, field.TypeString)) + ) + if id, ok := sc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := sc.mutation.CreatedAt(); ok { + _spec.SetField(session.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := sc.mutation.UpdatedAt(); ok { + _spec.SetField(session.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if value, ok := sc.mutation.ExpiresAt(); ok { + _spec.SetField(session.FieldExpiresAt, field.TypeTime, value) + _node.ExpiresAt = value + } + if value, ok := sc.mutation.PublicID(); ok { + _spec.SetField(session.FieldPublicID, field.TypeString, value) + _node.PublicID = value + } + if value, ok := sc.mutation.Metadata(); ok { + _spec.SetField(session.FieldMetadata, field.TypeJSON, value) + _node.Metadata = value + } + if nodes := sc.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: session.UserTable, + Columns: []string{session.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.UserID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// SessionCreateBulk is the builder for creating many Session entities in bulk. +type SessionCreateBulk struct { + config + err error + builders []*SessionCreate +} + +// Save creates the Session entities in the database. +func (scb *SessionCreateBulk) Save(ctx context.Context) ([]*Session, error) { + if scb.err != nil { + return nil, scb.err + } + specs := make([]*sqlgraph.CreateSpec, len(scb.builders)) + nodes := make([]*Session, len(scb.builders)) + mutators := make([]Mutator, len(scb.builders)) + for i := range scb.builders { + func(i int, root context.Context) { + builder := scb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SessionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, scb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, scb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, scb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (scb *SessionCreateBulk) SaveX(ctx context.Context) []*Session { + v, err := scb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (scb *SessionCreateBulk) Exec(ctx context.Context) error { + _, err := scb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (scb *SessionCreateBulk) ExecX(ctx context.Context) { + if err := scb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/session_delete.go b/internal/database/ent/db/session_delete.go new file mode 100644 index 00000000..19677342 --- /dev/null +++ b/internal/database/ent/db/session_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/session" +) + +// SessionDelete is the builder for deleting a Session entity. +type SessionDelete struct { + config + hooks []Hook + mutation *SessionMutation +} + +// Where appends a list predicates to the SessionDelete builder. +func (sd *SessionDelete) Where(ps ...predicate.Session) *SessionDelete { + sd.mutation.Where(ps...) + return sd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (sd *SessionDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, sd.sqlExec, sd.mutation, sd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (sd *SessionDelete) ExecX(ctx context.Context) int { + n, err := sd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (sd *SessionDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(session.Table, sqlgraph.NewFieldSpec(session.FieldID, field.TypeString)) + if ps := sd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, sd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + sd.mutation.done = true + return affected, err +} + +// SessionDeleteOne is the builder for deleting a single Session entity. +type SessionDeleteOne struct { + sd *SessionDelete +} + +// Where appends a list predicates to the SessionDelete builder. +func (sdo *SessionDeleteOne) Where(ps ...predicate.Session) *SessionDeleteOne { + sdo.sd.mutation.Where(ps...) + return sdo +} + +// Exec executes the deletion query. +func (sdo *SessionDeleteOne) Exec(ctx context.Context) error { + n, err := sdo.sd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{session.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (sdo *SessionDeleteOne) ExecX(ctx context.Context) { + if err := sdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/database/ent/db/session_query.go b/internal/database/ent/db/session_query.go new file mode 100644 index 00000000..06db7534 --- /dev/null +++ b/internal/database/ent/db/session_query.go @@ -0,0 +1,627 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/session" + "github.com/cufee/aftermath/internal/database/ent/db/user" +) + +// SessionQuery is the builder for querying Session entities. +type SessionQuery struct { + config + ctx *QueryContext + order []session.OrderOption + inters []Interceptor + predicates []predicate.Session + withUser *UserQuery + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the SessionQuery builder. +func (sq *SessionQuery) Where(ps ...predicate.Session) *SessionQuery { + sq.predicates = append(sq.predicates, ps...) + return sq +} + +// Limit the number of records to be returned by this query. +func (sq *SessionQuery) Limit(limit int) *SessionQuery { + sq.ctx.Limit = &limit + return sq +} + +// Offset to start from. +func (sq *SessionQuery) Offset(offset int) *SessionQuery { + sq.ctx.Offset = &offset + return sq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (sq *SessionQuery) Unique(unique bool) *SessionQuery { + sq.ctx.Unique = &unique + return sq +} + +// Order specifies how the records should be ordered. +func (sq *SessionQuery) Order(o ...session.OrderOption) *SessionQuery { + sq.order = append(sq.order, o...) + return sq +} + +// QueryUser chains the current query on the "user" edge. +func (sq *SessionQuery) QueryUser() *UserQuery { + query := (&UserClient{config: sq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := sq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := sq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(session.Table, session.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, session.UserTable, session.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(sq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first Session entity from the query. +// Returns a *NotFoundError when no Session was found. +func (sq *SessionQuery) First(ctx context.Context) (*Session, error) { + nodes, err := sq.Limit(1).All(setContextOp(ctx, sq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{session.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (sq *SessionQuery) FirstX(ctx context.Context) *Session { + node, err := sq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Session ID from the query. +// Returns a *NotFoundError when no Session ID was found. +func (sq *SessionQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = sq.Limit(1).IDs(setContextOp(ctx, sq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{session.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (sq *SessionQuery) FirstIDX(ctx context.Context) string { + id, err := sq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Session entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Session entity is found. +// Returns a *NotFoundError when no Session entities are found. +func (sq *SessionQuery) Only(ctx context.Context) (*Session, error) { + nodes, err := sq.Limit(2).All(setContextOp(ctx, sq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{session.Label} + default: + return nil, &NotSingularError{session.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (sq *SessionQuery) OnlyX(ctx context.Context) *Session { + node, err := sq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Session ID in the query. +// Returns a *NotSingularError when more than one Session ID is found. +// Returns a *NotFoundError when no entities are found. +func (sq *SessionQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = sq.Limit(2).IDs(setContextOp(ctx, sq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{session.Label} + default: + err = &NotSingularError{session.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (sq *SessionQuery) OnlyIDX(ctx context.Context) string { + id, err := sq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Sessions. +func (sq *SessionQuery) All(ctx context.Context) ([]*Session, error) { + ctx = setContextOp(ctx, sq.ctx, "All") + if err := sq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Session, *SessionQuery]() + return withInterceptors[[]*Session](ctx, sq, qr, sq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (sq *SessionQuery) AllX(ctx context.Context) []*Session { + nodes, err := sq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Session IDs. +func (sq *SessionQuery) IDs(ctx context.Context) (ids []string, err error) { + if sq.ctx.Unique == nil && sq.path != nil { + sq.Unique(true) + } + ctx = setContextOp(ctx, sq.ctx, "IDs") + if err = sq.Select(session.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (sq *SessionQuery) IDsX(ctx context.Context) []string { + ids, err := sq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (sq *SessionQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, sq.ctx, "Count") + if err := sq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, sq, querierCount[*SessionQuery](), sq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (sq *SessionQuery) CountX(ctx context.Context) int { + count, err := sq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (sq *SessionQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, sq.ctx, "Exist") + switch _, err := sq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (sq *SessionQuery) ExistX(ctx context.Context) bool { + exist, err := sq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the SessionQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (sq *SessionQuery) Clone() *SessionQuery { + if sq == nil { + return nil + } + return &SessionQuery{ + config: sq.config, + ctx: sq.ctx.Clone(), + order: append([]session.OrderOption{}, sq.order...), + inters: append([]Interceptor{}, sq.inters...), + predicates: append([]predicate.Session{}, sq.predicates...), + withUser: sq.withUser.Clone(), + // clone intermediate query. + sql: sq.sql.Clone(), + path: sq.path, + } +} + +// WithUser tells the query-builder to eager-load the nodes that are connected to +// the "user" edge. The optional arguments are used to configure the query builder of the edge. +func (sq *SessionQuery) WithUser(opts ...func(*UserQuery)) *SessionQuery { + query := (&UserClient{config: sq.config}).Query() + for _, opt := range opts { + opt(query) + } + sq.withUser = query + return sq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Session.Query(). +// GroupBy(session.FieldCreatedAt). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (sq *SessionQuery) GroupBy(field string, fields ...string) *SessionGroupBy { + sq.ctx.Fields = append([]string{field}, fields...) + grbuild := &SessionGroupBy{build: sq} + grbuild.flds = &sq.ctx.Fields + grbuild.label = session.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// } +// +// client.Session.Query(). +// Select(session.FieldCreatedAt). +// Scan(ctx, &v) +func (sq *SessionQuery) Select(fields ...string) *SessionSelect { + sq.ctx.Fields = append(sq.ctx.Fields, fields...) + sbuild := &SessionSelect{SessionQuery: sq} + sbuild.label = session.Label + sbuild.flds, sbuild.scan = &sq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a SessionSelect configured with the given aggregations. +func (sq *SessionQuery) Aggregate(fns ...AggregateFunc) *SessionSelect { + return sq.Select().Aggregate(fns...) +} + +func (sq *SessionQuery) prepareQuery(ctx context.Context) error { + for _, inter := range sq.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, sq); err != nil { + return err + } + } + } + for _, f := range sq.ctx.Fields { + if !session.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if sq.path != nil { + prev, err := sq.path(ctx) + if err != nil { + return err + } + sq.sql = prev + } + return nil +} + +func (sq *SessionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Session, error) { + var ( + nodes = []*Session{} + _spec = sq.querySpec() + loadedTypes = [1]bool{ + sq.withUser != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*Session).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &Session{config: sq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + if len(sq.modifiers) > 0 { + _spec.Modifiers = sq.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, sq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := sq.withUser; query != nil { + if err := sq.loadUser(ctx, query, nodes, nil, + func(n *Session, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (sq *SessionQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*Session, init func(*Session), assign func(*Session, *User)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*Session) + for i := range nodes { + fk := nodes[i].UserID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (sq *SessionQuery) sqlCount(ctx context.Context) (int, error) { + _spec := sq.querySpec() + if len(sq.modifiers) > 0 { + _spec.Modifiers = sq.modifiers + } + _spec.Node.Columns = sq.ctx.Fields + if len(sq.ctx.Fields) > 0 { + _spec.Unique = sq.ctx.Unique != nil && *sq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, sq.driver, _spec) +} + +func (sq *SessionQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(session.Table, session.Columns, sqlgraph.NewFieldSpec(session.FieldID, field.TypeString)) + _spec.From = sq.sql + if unique := sq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if sq.path != nil { + _spec.Unique = true + } + if fields := sq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, session.FieldID) + for i := range fields { + if fields[i] != session.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if sq.withUser != nil { + _spec.Node.AddColumnOnce(session.FieldUserID) + } + } + if ps := sq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := sq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := sq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := sq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (sq *SessionQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(sq.driver.Dialect()) + t1 := builder.Table(session.Table) + columns := sq.ctx.Fields + if len(columns) == 0 { + columns = session.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if sq.sql != nil { + selector = sq.sql + selector.Select(selector.Columns(columns...)...) + } + if sq.ctx.Unique != nil && *sq.ctx.Unique { + selector.Distinct() + } + for _, m := range sq.modifiers { + m(selector) + } + for _, p := range sq.predicates { + p(selector) + } + for _, p := range sq.order { + p(selector) + } + if offset := sq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := sq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// Modify adds a query modifier for attaching custom logic to queries. +func (sq *SessionQuery) Modify(modifiers ...func(s *sql.Selector)) *SessionSelect { + sq.modifiers = append(sq.modifiers, modifiers...) + return sq.Select() +} + +// SessionGroupBy is the group-by builder for Session entities. +type SessionGroupBy struct { + selector + build *SessionQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (sgb *SessionGroupBy) Aggregate(fns ...AggregateFunc) *SessionGroupBy { + sgb.fns = append(sgb.fns, fns...) + return sgb +} + +// Scan applies the selector query and scans the result into the given value. +func (sgb *SessionGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, sgb.build.ctx, "GroupBy") + if err := sgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SessionQuery, *SessionGroupBy](ctx, sgb.build, sgb, sgb.build.inters, v) +} + +func (sgb *SessionGroupBy) sqlScan(ctx context.Context, root *SessionQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(sgb.fns)) + for _, fn := range sgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*sgb.flds)+len(sgb.fns)) + for _, f := range *sgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*sgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := sgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// SessionSelect is the builder for selecting fields of Session entities. +type SessionSelect struct { + *SessionQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ss *SessionSelect) Aggregate(fns ...AggregateFunc) *SessionSelect { + ss.fns = append(ss.fns, fns...) + return ss +} + +// Scan applies the selector query and scans the result into the given value. +func (ss *SessionSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ss.ctx, "Select") + if err := ss.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SessionQuery, *SessionSelect](ctx, ss.SessionQuery, ss, ss.inters, v) +} + +func (ss *SessionSelect) sqlScan(ctx context.Context, root *SessionQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(ss.fns)) + for _, fn := range ss.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*ss.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ss.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// Modify adds a query modifier for attaching custom logic to queries. +func (ss *SessionSelect) Modify(modifiers ...func(s *sql.Selector)) *SessionSelect { + ss.modifiers = append(ss.modifiers, modifiers...) + return ss +} diff --git a/internal/database/ent/db/session_update.go b/internal/database/ent/db/session_update.go new file mode 100644 index 00000000..b3c596d8 --- /dev/null +++ b/internal/database/ent/db/session_update.go @@ -0,0 +1,302 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/session" +) + +// SessionUpdate is the builder for updating Session entities. +type SessionUpdate struct { + config + hooks []Hook + mutation *SessionMutation + modifiers []func(*sql.UpdateBuilder) +} + +// Where appends a list predicates to the SessionUpdate builder. +func (su *SessionUpdate) Where(ps ...predicate.Session) *SessionUpdate { + su.mutation.Where(ps...) + return su +} + +// SetUpdatedAt sets the "updated_at" field. +func (su *SessionUpdate) SetUpdatedAt(t time.Time) *SessionUpdate { + su.mutation.SetUpdatedAt(t) + return su +} + +// SetExpiresAt sets the "expires_at" field. +func (su *SessionUpdate) SetExpiresAt(t time.Time) *SessionUpdate { + su.mutation.SetExpiresAt(t) + return su +} + +// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. +func (su *SessionUpdate) SetNillableExpiresAt(t *time.Time) *SessionUpdate { + if t != nil { + su.SetExpiresAt(*t) + } + return su +} + +// SetMetadata sets the "metadata" field. +func (su *SessionUpdate) SetMetadata(m map[string]string) *SessionUpdate { + su.mutation.SetMetadata(m) + return su +} + +// Mutation returns the SessionMutation object of the builder. +func (su *SessionUpdate) Mutation() *SessionMutation { + return su.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (su *SessionUpdate) Save(ctx context.Context) (int, error) { + su.defaults() + return withHooks(ctx, su.sqlSave, su.mutation, su.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (su *SessionUpdate) SaveX(ctx context.Context) int { + affected, err := su.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (su *SessionUpdate) Exec(ctx context.Context) error { + _, err := su.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (su *SessionUpdate) ExecX(ctx context.Context) { + if err := su.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (su *SessionUpdate) defaults() { + if _, ok := su.mutation.UpdatedAt(); !ok { + v := session.UpdateDefaultUpdatedAt() + su.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (su *SessionUpdate) check() error { + if _, ok := su.mutation.UserID(); su.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "Session.user"`) + } + return nil +} + +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (su *SessionUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *SessionUpdate { + su.modifiers = append(su.modifiers, modifiers...) + return su +} + +func (su *SessionUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := su.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(session.Table, session.Columns, sqlgraph.NewFieldSpec(session.FieldID, field.TypeString)) + if ps := su.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := su.mutation.UpdatedAt(); ok { + _spec.SetField(session.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := su.mutation.ExpiresAt(); ok { + _spec.SetField(session.FieldExpiresAt, field.TypeTime, value) + } + if value, ok := su.mutation.Metadata(); ok { + _spec.SetField(session.FieldMetadata, field.TypeJSON, value) + } + _spec.AddModifiers(su.modifiers...) + if n, err = sqlgraph.UpdateNodes(ctx, su.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{session.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + su.mutation.done = true + return n, nil +} + +// SessionUpdateOne is the builder for updating a single Session entity. +type SessionUpdateOne struct { + config + fields []string + hooks []Hook + mutation *SessionMutation + modifiers []func(*sql.UpdateBuilder) +} + +// SetUpdatedAt sets the "updated_at" field. +func (suo *SessionUpdateOne) SetUpdatedAt(t time.Time) *SessionUpdateOne { + suo.mutation.SetUpdatedAt(t) + return suo +} + +// SetExpiresAt sets the "expires_at" field. +func (suo *SessionUpdateOne) SetExpiresAt(t time.Time) *SessionUpdateOne { + suo.mutation.SetExpiresAt(t) + return suo +} + +// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. +func (suo *SessionUpdateOne) SetNillableExpiresAt(t *time.Time) *SessionUpdateOne { + if t != nil { + suo.SetExpiresAt(*t) + } + return suo +} + +// SetMetadata sets the "metadata" field. +func (suo *SessionUpdateOne) SetMetadata(m map[string]string) *SessionUpdateOne { + suo.mutation.SetMetadata(m) + return suo +} + +// Mutation returns the SessionMutation object of the builder. +func (suo *SessionUpdateOne) Mutation() *SessionMutation { + return suo.mutation +} + +// Where appends a list predicates to the SessionUpdate builder. +func (suo *SessionUpdateOne) Where(ps ...predicate.Session) *SessionUpdateOne { + suo.mutation.Where(ps...) + return suo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (suo *SessionUpdateOne) Select(field string, fields ...string) *SessionUpdateOne { + suo.fields = append([]string{field}, fields...) + return suo +} + +// Save executes the query and returns the updated Session entity. +func (suo *SessionUpdateOne) Save(ctx context.Context) (*Session, error) { + suo.defaults() + return withHooks(ctx, suo.sqlSave, suo.mutation, suo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (suo *SessionUpdateOne) SaveX(ctx context.Context) *Session { + node, err := suo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (suo *SessionUpdateOne) Exec(ctx context.Context) error { + _, err := suo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (suo *SessionUpdateOne) ExecX(ctx context.Context) { + if err := suo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (suo *SessionUpdateOne) defaults() { + if _, ok := suo.mutation.UpdatedAt(); !ok { + v := session.UpdateDefaultUpdatedAt() + suo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (suo *SessionUpdateOne) check() error { + if _, ok := suo.mutation.UserID(); suo.mutation.UserCleared() && !ok { + return errors.New(`db: clearing a required unique edge "Session.user"`) + } + return nil +} + +// Modify adds a statement modifier for attaching custom logic to the UPDATE statement. +func (suo *SessionUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *SessionUpdateOne { + suo.modifiers = append(suo.modifiers, modifiers...) + return suo +} + +func (suo *SessionUpdateOne) sqlSave(ctx context.Context) (_node *Session, err error) { + if err := suo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(session.Table, session.Columns, sqlgraph.NewFieldSpec(session.FieldID, field.TypeString)) + id, ok := suo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "Session.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := suo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, session.FieldID) + for _, f := range fields { + if !session.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != session.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := suo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := suo.mutation.UpdatedAt(); ok { + _spec.SetField(session.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := suo.mutation.ExpiresAt(); ok { + _spec.SetField(session.FieldExpiresAt, field.TypeTime, value) + } + if value, ok := suo.mutation.Metadata(); ok { + _spec.SetField(session.FieldMetadata, field.TypeJSON, value) + } + _spec.AddModifiers(suo.modifiers...) + _node = &Session{config: suo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, suo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{session.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + suo.mutation.done = true + return _node, nil +} diff --git a/internal/database/ent/db/tx.go b/internal/database/ent/db/tx.go index 9852c615..72a6edce 100644 --- a/internal/database/ent/db/tx.go +++ b/internal/database/ent/db/tx.go @@ -24,12 +24,16 @@ type Tx struct { AppConfiguration *AppConfigurationClient // ApplicationCommand is the client for interacting with the ApplicationCommand builders. ApplicationCommand *ApplicationCommandClient + // AuthNonce is the client for interacting with the AuthNonce builders. + AuthNonce *AuthNonceClient // Clan is the client for interacting with the Clan builders. Clan *ClanClient // CronTask is the client for interacting with the CronTask builders. CronTask *CronTaskClient // DiscordInteraction is the client for interacting with the DiscordInteraction builders. DiscordInteraction *DiscordInteractionClient + // Session is the client for interacting with the Session builders. + Session *SessionClient // User is the client for interacting with the User builders. User *UserClient // UserConnection is the client for interacting with the UserConnection builders. @@ -180,9 +184,11 @@ func (tx *Tx) init() { tx.AchievementsSnapshot = NewAchievementsSnapshotClient(tx.config) tx.AppConfiguration = NewAppConfigurationClient(tx.config) tx.ApplicationCommand = NewApplicationCommandClient(tx.config) + tx.AuthNonce = NewAuthNonceClient(tx.config) tx.Clan = NewClanClient(tx.config) tx.CronTask = NewCronTaskClient(tx.config) tx.DiscordInteraction = NewDiscordInteractionClient(tx.config) + tx.Session = NewSessionClient(tx.config) tx.User = NewUserClient(tx.config) tx.UserConnection = NewUserConnectionClient(tx.config) tx.UserContent = NewUserContentClient(tx.config) diff --git a/internal/database/ent/db/user.go b/internal/database/ent/db/user.go index f94bd4f6..a4d68628 100644 --- a/internal/database/ent/db/user.go +++ b/internal/database/ent/db/user.go @@ -22,6 +22,8 @@ type User struct { CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. UpdatedAt time.Time `json:"updated_at,omitempty"` + // Username holds the value of the "username" field. + Username string `json:"username,omitempty"` // Permissions holds the value of the "permissions" field. Permissions string `json:"permissions,omitempty"` // FeatureFlags holds the value of the "feature_flags" field. @@ -42,9 +44,11 @@ type UserEdges struct { Connections []*UserConnection `json:"connections,omitempty"` // Content holds the value of the content edge. Content []*UserContent `json:"content,omitempty"` + // Sessions holds the value of the sessions edge. + Sessions []*Session `json:"sessions,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [4]bool + loadedTypes [5]bool } // DiscordInteractionsOrErr returns the DiscordInteractions value or an error if the edge @@ -83,6 +87,15 @@ func (e UserEdges) ContentOrErr() ([]*UserContent, error) { return nil, &NotLoadedError{edge: "content"} } +// SessionsOrErr returns the Sessions value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) SessionsOrErr() ([]*Session, error) { + if e.loadedTypes[4] { + return e.Sessions, nil + } + return nil, &NotLoadedError{edge: "sessions"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*User) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -90,7 +103,7 @@ func (*User) scanValues(columns []string) ([]any, error) { switch columns[i] { case user.FieldFeatureFlags: values[i] = new([]byte) - case user.FieldID, user.FieldPermissions: + case user.FieldID, user.FieldUsername, user.FieldPermissions: values[i] = new(sql.NullString) case user.FieldCreatedAt, user.FieldUpdatedAt: values[i] = new(sql.NullTime) @@ -127,6 +140,12 @@ func (u *User) assignValues(columns []string, values []any) error { } else if value.Valid { u.UpdatedAt = value.Time } + case user.FieldUsername: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field username", values[i]) + } else if value.Valid { + u.Username = value.String + } case user.FieldPermissions: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field permissions", values[i]) @@ -174,6 +193,11 @@ func (u *User) QueryContent() *UserContentQuery { return NewUserClient(u.config).QueryContent(u) } +// QuerySessions queries the "sessions" edge of the User entity. +func (u *User) QuerySessions() *SessionQuery { + return NewUserClient(u.config).QuerySessions(u) +} + // Update returns a builder for updating this User. // Note that you need to call User.Unwrap() before calling this method if this User // was returned from a transaction, and the transaction was committed or rolled back. @@ -203,6 +227,9 @@ func (u *User) String() string { builder.WriteString("updated_at=") builder.WriteString(u.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") + builder.WriteString("username=") + builder.WriteString(u.Username) + builder.WriteString(", ") builder.WriteString("permissions=") builder.WriteString(u.Permissions) builder.WriteString(", ") diff --git a/internal/database/ent/db/user/user.go b/internal/database/ent/db/user/user.go index e4787254..4e21b04c 100644 --- a/internal/database/ent/db/user/user.go +++ b/internal/database/ent/db/user/user.go @@ -18,6 +18,8 @@ const ( FieldCreatedAt = "created_at" // FieldUpdatedAt holds the string denoting the updated_at field in the database. FieldUpdatedAt = "updated_at" + // FieldUsername holds the string denoting the username field in the database. + FieldUsername = "username" // FieldPermissions holds the string denoting the permissions field in the database. FieldPermissions = "permissions" // FieldFeatureFlags holds the string denoting the feature_flags field in the database. @@ -30,6 +32,8 @@ const ( EdgeConnections = "connections" // EdgeContent holds the string denoting the content edge name in mutations. EdgeContent = "content" + // EdgeSessions holds the string denoting the sessions edge name in mutations. + EdgeSessions = "sessions" // Table holds the table name of the user in the database. Table = "users" // DiscordInteractionsTable is the table that holds the discord_interactions relation/edge. @@ -60,6 +64,13 @@ const ( ContentInverseTable = "user_contents" // ContentColumn is the table column denoting the content relation/edge. ContentColumn = "user_id" + // SessionsTable is the table that holds the sessions relation/edge. + SessionsTable = "sessions" + // SessionsInverseTable is the table name for the Session entity. + // It exists in this package in order to avoid circular dependency with the "session" package. + SessionsInverseTable = "sessions" + // SessionsColumn is the table column denoting the sessions relation/edge. + SessionsColumn = "user_id" ) // Columns holds all SQL columns for user fields. @@ -67,6 +78,7 @@ var Columns = []string{ FieldID, FieldCreatedAt, FieldUpdatedAt, + FieldUsername, FieldPermissions, FieldFeatureFlags, } @@ -88,6 +100,8 @@ var ( DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. UpdateDefaultUpdatedAt func() time.Time + // DefaultUsername holds the default value on creation for the "username" field. + DefaultUsername string // DefaultPermissions holds the default value on creation for the "permissions" field. DefaultPermissions string ) @@ -110,6 +124,11 @@ func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() } +// ByUsername orders the results by the username field. +func ByUsername(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUsername, opts...).ToFunc() +} + // ByPermissions orders the results by the permissions field. func ByPermissions(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldPermissions, opts...).ToFunc() @@ -170,6 +189,20 @@ func ByContent(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { sqlgraph.OrderByNeighborTerms(s, newContentStep(), append([]sql.OrderTerm{term}, terms...)...) } } + +// BySessionsCount orders the results by sessions count. +func BySessionsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newSessionsStep(), opts...) + } +} + +// BySessions orders the results by sessions terms. +func BySessions(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newSessionsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newDiscordInteractionsStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -198,3 +231,10 @@ func newContentStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2M, false, ContentTable, ContentColumn), ) } +func newSessionsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(SessionsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SessionsTable, SessionsColumn), + ) +} diff --git a/internal/database/ent/db/user/where.go b/internal/database/ent/db/user/where.go index 989d46ba..4a8bebd1 100644 --- a/internal/database/ent/db/user/where.go +++ b/internal/database/ent/db/user/where.go @@ -75,6 +75,11 @@ func UpdatedAt(v time.Time) predicate.User { return predicate.User(sql.FieldEQ(FieldUpdatedAt, v)) } +// Username applies equality check predicate on the "username" field. It's identical to UsernameEQ. +func Username(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldUsername, v)) +} + // Permissions applies equality check predicate on the "permissions" field. It's identical to PermissionsEQ. func Permissions(v string) predicate.User { return predicate.User(sql.FieldEQ(FieldPermissions, v)) @@ -160,6 +165,71 @@ func UpdatedAtLTE(v time.Time) predicate.User { return predicate.User(sql.FieldLTE(FieldUpdatedAt, v)) } +// UsernameEQ applies the EQ predicate on the "username" field. +func UsernameEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldUsername, v)) +} + +// UsernameNEQ applies the NEQ predicate on the "username" field. +func UsernameNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldUsername, v)) +} + +// UsernameIn applies the In predicate on the "username" field. +func UsernameIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldUsername, vs...)) +} + +// UsernameNotIn applies the NotIn predicate on the "username" field. +func UsernameNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldUsername, vs...)) +} + +// UsernameGT applies the GT predicate on the "username" field. +func UsernameGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldUsername, v)) +} + +// UsernameGTE applies the GTE predicate on the "username" field. +func UsernameGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldUsername, v)) +} + +// UsernameLT applies the LT predicate on the "username" field. +func UsernameLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldUsername, v)) +} + +// UsernameLTE applies the LTE predicate on the "username" field. +func UsernameLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldUsername, v)) +} + +// UsernameContains applies the Contains predicate on the "username" field. +func UsernameContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldUsername, v)) +} + +// UsernameHasPrefix applies the HasPrefix predicate on the "username" field. +func UsernameHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldUsername, v)) +} + +// UsernameHasSuffix applies the HasSuffix predicate on the "username" field. +func UsernameHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldUsername, v)) +} + +// UsernameEqualFold applies the EqualFold predicate on the "username" field. +func UsernameEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldUsername, v)) +} + +// UsernameContainsFold applies the ContainsFold predicate on the "username" field. +func UsernameContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldUsername, v)) +} + // PermissionsEQ applies the EQ predicate on the "permissions" field. func PermissionsEQ(v string) predicate.User { return predicate.User(sql.FieldEQ(FieldPermissions, v)) @@ -327,6 +397,29 @@ func HasContentWith(preds ...predicate.UserContent) predicate.User { }) } +// HasSessions applies the HasEdge predicate on the "sessions" edge. +func HasSessions() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SessionsTable, SessionsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasSessionsWith applies the HasEdge predicate on the "sessions" edge with a given conditions (other predicates). +func HasSessionsWith(preds ...predicate.Session) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newSessionsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.User) predicate.User { return predicate.User(sql.AndPredicates(predicates...)) diff --git a/internal/database/ent/db/user_create.go b/internal/database/ent/db/user_create.go index 8cf24699..0f2d6dc1 100644 --- a/internal/database/ent/db/user_create.go +++ b/internal/database/ent/db/user_create.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" + "github.com/cufee/aftermath/internal/database/ent/db/session" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -52,6 +53,20 @@ func (uc *UserCreate) SetNillableUpdatedAt(t *time.Time) *UserCreate { return uc } +// SetUsername sets the "username" field. +func (uc *UserCreate) SetUsername(s string) *UserCreate { + uc.mutation.SetUsername(s) + return uc +} + +// SetNillableUsername sets the "username" field if the given value is not nil. +func (uc *UserCreate) SetNillableUsername(s *string) *UserCreate { + if s != nil { + uc.SetUsername(*s) + } + return uc +} + // SetPermissions sets the "permissions" field. func (uc *UserCreate) SetPermissions(s string) *UserCreate { uc.mutation.SetPermissions(s) @@ -138,6 +153,21 @@ func (uc *UserCreate) AddContent(u ...*UserContent) *UserCreate { return uc.AddContentIDs(ids...) } +// AddSessionIDs adds the "sessions" edge to the Session entity by IDs. +func (uc *UserCreate) AddSessionIDs(ids ...string) *UserCreate { + uc.mutation.AddSessionIDs(ids...) + return uc +} + +// AddSessions adds the "sessions" edges to the Session entity. +func (uc *UserCreate) AddSessions(s ...*Session) *UserCreate { + ids := make([]string, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return uc.AddSessionIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (uc *UserCreate) Mutation() *UserMutation { return uc.mutation @@ -181,6 +211,10 @@ func (uc *UserCreate) defaults() { v := user.DefaultUpdatedAt() uc.mutation.SetUpdatedAt(v) } + if _, ok := uc.mutation.Username(); !ok { + v := user.DefaultUsername + uc.mutation.SetUsername(v) + } if _, ok := uc.mutation.Permissions(); !ok { v := user.DefaultPermissions uc.mutation.SetPermissions(v) @@ -195,6 +229,9 @@ func (uc *UserCreate) check() error { if _, ok := uc.mutation.UpdatedAt(); !ok { return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "User.updated_at"`)} } + if _, ok := uc.mutation.Username(); !ok { + return &ValidationError{Name: "username", err: errors.New(`db: missing required field "User.username"`)} + } if _, ok := uc.mutation.Permissions(); !ok { return &ValidationError{Name: "permissions", err: errors.New(`db: missing required field "User.permissions"`)} } @@ -241,6 +278,10 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } + if value, ok := uc.mutation.Username(); ok { + _spec.SetField(user.FieldUsername, field.TypeString, value) + _node.Username = value + } if value, ok := uc.mutation.Permissions(); ok { _spec.SetField(user.FieldPermissions, field.TypeString, value) _node.Permissions = value @@ -313,6 +354,22 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := uc.mutation.SessionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SessionsTable, + Columns: []string{user.SessionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(session.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/internal/database/ent/db/user_query.go b/internal/database/ent/db/user_query.go index 2e37a52c..ef3b78e1 100644 --- a/internal/database/ent/db/user_query.go +++ b/internal/database/ent/db/user_query.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/schema/field" "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/session" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -30,6 +31,7 @@ type UserQuery struct { withSubscriptions *UserSubscriptionQuery withConnections *UserConnectionQuery withContent *UserContentQuery + withSessions *SessionQuery modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector @@ -155,6 +157,28 @@ func (uq *UserQuery) QueryContent() *UserContentQuery { return query } +// QuerySessions chains the current query on the "sessions" edge. +func (uq *UserQuery) QuerySessions() *SessionQuery { + query := (&SessionClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(session.Table, session.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.SessionsTable, user.SessionsColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first User entity from the query. // Returns a *NotFoundError when no User was found. func (uq *UserQuery) First(ctx context.Context) (*User, error) { @@ -351,6 +375,7 @@ func (uq *UserQuery) Clone() *UserQuery { withSubscriptions: uq.withSubscriptions.Clone(), withConnections: uq.withConnections.Clone(), withContent: uq.withContent.Clone(), + withSessions: uq.withSessions.Clone(), // clone intermediate query. sql: uq.sql.Clone(), path: uq.path, @@ -401,6 +426,17 @@ func (uq *UserQuery) WithContent(opts ...func(*UserContentQuery)) *UserQuery { return uq } +// WithSessions tells the query-builder to eager-load the nodes that are connected to +// the "sessions" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithSessions(opts ...func(*SessionQuery)) *UserQuery { + query := (&SessionClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withSessions = query + return uq +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -479,11 +515,12 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e var ( nodes = []*User{} _spec = uq.querySpec() - loadedTypes = [4]bool{ + loadedTypes = [5]bool{ uq.withDiscordInteractions != nil, uq.withSubscriptions != nil, uq.withConnections != nil, uq.withContent != nil, + uq.withSessions != nil, } ) _spec.ScanValues = func(columns []string) ([]any, error) { @@ -537,6 +574,13 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e return nil, err } } + if query := uq.withSessions; query != nil { + if err := uq.loadSessions(ctx, query, nodes, + func(n *User) { n.Edges.Sessions = []*Session{} }, + func(n *User, e *Session) { n.Edges.Sessions = append(n.Edges.Sessions, e) }); err != nil { + return nil, err + } + } return nodes, nil } @@ -660,6 +704,36 @@ func (uq *UserQuery) loadContent(ctx context.Context, query *UserContentQuery, n } return nil } +func (uq *UserQuery) loadSessions(ctx context.Context, query *SessionQuery, nodes []*User, init func(*User), assign func(*User, *Session)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(session.FieldUserID) + } + query.Where(predicate.Session(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.SessionsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.UserID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) { _spec := uq.querySpec() diff --git a/internal/database/ent/db/user_update.go b/internal/database/ent/db/user_update.go index 57c92a14..1b4b1997 100644 --- a/internal/database/ent/db/user_update.go +++ b/internal/database/ent/db/user_update.go @@ -14,6 +14,7 @@ import ( "entgo.io/ent/schema/field" "github.com/cufee/aftermath/internal/database/ent/db/discordinteraction" "github.com/cufee/aftermath/internal/database/ent/db/predicate" + "github.com/cufee/aftermath/internal/database/ent/db/session" "github.com/cufee/aftermath/internal/database/ent/db/user" "github.com/cufee/aftermath/internal/database/ent/db/userconnection" "github.com/cufee/aftermath/internal/database/ent/db/usercontent" @@ -40,6 +41,20 @@ func (uu *UserUpdate) SetUpdatedAt(t time.Time) *UserUpdate { return uu } +// SetUsername sets the "username" field. +func (uu *UserUpdate) SetUsername(s string) *UserUpdate { + uu.mutation.SetUsername(s) + return uu +} + +// SetNillableUsername sets the "username" field if the given value is not nil. +func (uu *UserUpdate) SetNillableUsername(s *string) *UserUpdate { + if s != nil { + uu.SetUsername(*s) + } + return uu +} + // SetPermissions sets the "permissions" field. func (uu *UserUpdate) SetPermissions(s string) *UserUpdate { uu.mutation.SetPermissions(s) @@ -132,6 +147,21 @@ func (uu *UserUpdate) AddContent(u ...*UserContent) *UserUpdate { return uu.AddContentIDs(ids...) } +// AddSessionIDs adds the "sessions" edge to the Session entity by IDs. +func (uu *UserUpdate) AddSessionIDs(ids ...string) *UserUpdate { + uu.mutation.AddSessionIDs(ids...) + return uu +} + +// AddSessions adds the "sessions" edges to the Session entity. +func (uu *UserUpdate) AddSessions(s ...*Session) *UserUpdate { + ids := make([]string, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return uu.AddSessionIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (uu *UserUpdate) Mutation() *UserMutation { return uu.mutation @@ -221,6 +251,27 @@ func (uu *UserUpdate) RemoveContent(u ...*UserContent) *UserUpdate { return uu.RemoveContentIDs(ids...) } +// ClearSessions clears all "sessions" edges to the Session entity. +func (uu *UserUpdate) ClearSessions() *UserUpdate { + uu.mutation.ClearSessions() + return uu +} + +// RemoveSessionIDs removes the "sessions" edge to Session entities by IDs. +func (uu *UserUpdate) RemoveSessionIDs(ids ...string) *UserUpdate { + uu.mutation.RemoveSessionIDs(ids...) + return uu +} + +// RemoveSessions removes "sessions" edges to Session entities. +func (uu *UserUpdate) RemoveSessions(s ...*Session) *UserUpdate { + ids := make([]string, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return uu.RemoveSessionIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (uu *UserUpdate) Save(ctx context.Context) (int, error) { uu.defaults() @@ -275,6 +326,9 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := uu.mutation.UpdatedAt(); ok { _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) } + if value, ok := uu.mutation.Username(); ok { + _spec.SetField(user.FieldUsername, field.TypeString, value) + } if value, ok := uu.mutation.Permissions(); ok { _spec.SetField(user.FieldPermissions, field.TypeString, value) } @@ -469,6 +523,51 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if uu.mutation.SessionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SessionsTable, + Columns: []string{user.SessionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(session.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.RemovedSessionsIDs(); len(nodes) > 0 && !uu.mutation.SessionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SessionsTable, + Columns: []string{user.SessionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(session.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.SessionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SessionsTable, + Columns: []string{user.SessionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(session.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _spec.AddModifiers(uu.modifiers...) if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { @@ -497,6 +596,20 @@ func (uuo *UserUpdateOne) SetUpdatedAt(t time.Time) *UserUpdateOne { return uuo } +// SetUsername sets the "username" field. +func (uuo *UserUpdateOne) SetUsername(s string) *UserUpdateOne { + uuo.mutation.SetUsername(s) + return uuo +} + +// SetNillableUsername sets the "username" field if the given value is not nil. +func (uuo *UserUpdateOne) SetNillableUsername(s *string) *UserUpdateOne { + if s != nil { + uuo.SetUsername(*s) + } + return uuo +} + // SetPermissions sets the "permissions" field. func (uuo *UserUpdateOne) SetPermissions(s string) *UserUpdateOne { uuo.mutation.SetPermissions(s) @@ -589,6 +702,21 @@ func (uuo *UserUpdateOne) AddContent(u ...*UserContent) *UserUpdateOne { return uuo.AddContentIDs(ids...) } +// AddSessionIDs adds the "sessions" edge to the Session entity by IDs. +func (uuo *UserUpdateOne) AddSessionIDs(ids ...string) *UserUpdateOne { + uuo.mutation.AddSessionIDs(ids...) + return uuo +} + +// AddSessions adds the "sessions" edges to the Session entity. +func (uuo *UserUpdateOne) AddSessions(s ...*Session) *UserUpdateOne { + ids := make([]string, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return uuo.AddSessionIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (uuo *UserUpdateOne) Mutation() *UserMutation { return uuo.mutation @@ -678,6 +806,27 @@ func (uuo *UserUpdateOne) RemoveContent(u ...*UserContent) *UserUpdateOne { return uuo.RemoveContentIDs(ids...) } +// ClearSessions clears all "sessions" edges to the Session entity. +func (uuo *UserUpdateOne) ClearSessions() *UserUpdateOne { + uuo.mutation.ClearSessions() + return uuo +} + +// RemoveSessionIDs removes the "sessions" edge to Session entities by IDs. +func (uuo *UserUpdateOne) RemoveSessionIDs(ids ...string) *UserUpdateOne { + uuo.mutation.RemoveSessionIDs(ids...) + return uuo +} + +// RemoveSessions removes "sessions" edges to Session entities. +func (uuo *UserUpdateOne) RemoveSessions(s ...*Session) *UserUpdateOne { + ids := make([]string, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return uuo.RemoveSessionIDs(ids...) +} + // Where appends a list predicates to the UserUpdate builder. func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne { uuo.mutation.Where(ps...) @@ -762,6 +911,9 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) if value, ok := uuo.mutation.UpdatedAt(); ok { _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) } + if value, ok := uuo.mutation.Username(); ok { + _spec.SetField(user.FieldUsername, field.TypeString, value) + } if value, ok := uuo.mutation.Permissions(); ok { _spec.SetField(user.FieldPermissions, field.TypeString, value) } @@ -956,6 +1108,51 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if uuo.mutation.SessionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SessionsTable, + Columns: []string{user.SessionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(session.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.RemovedSessionsIDs(); len(nodes) > 0 && !uuo.mutation.SessionsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SessionsTable, + Columns: []string{user.SessionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(session.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.SessionsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.SessionsTable, + Columns: []string{user.SessionsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(session.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _spec.AddModifiers(uuo.modifiers...) _node = &User{config: uuo.config} _spec.Assign = _node.assignValues diff --git a/internal/database/ent/migrations/20240703151859.sql b/internal/database/ent/migrations/20240703151859.sql new file mode 100644 index 00000000..b2ceca0a --- /dev/null +++ b/internal/database/ent/migrations/20240703151859.sql @@ -0,0 +1,22 @@ +-- Disable the enforcement of foreign-keys constraints +PRAGMA foreign_keys = off; +-- Create "new_users" table +CREATE TABLE `new_users` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `username` text NOT NULL DEFAULT (''), `permissions` text NOT NULL DEFAULT (''), `feature_flags` json NULL, PRIMARY KEY (`id`)); +-- Copy rows from old table "users" to new temporary table "new_users" +INSERT INTO `new_users` (`id`, `created_at`, `updated_at`, `permissions`, `feature_flags`) SELECT `id`, `created_at`, `updated_at`, `permissions`, `feature_flags` FROM `users`; +-- Drop "users" table after copying rows +DROP TABLE `users`; +-- Rename temporary table "new_users" to "users" +ALTER TABLE `new_users` RENAME TO `users`; +-- Create index "user_id" to table: "users" +CREATE INDEX `user_id` ON `users` (`id`); +-- Create index "user_username" to table: "users" +CREATE INDEX `user_username` ON `users` (`username`); +-- Create "auth_nonces" table +CREATE TABLE `auth_nonces` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `active` bool NOT NULL, `expires_at` datetime NOT NULL, `identifier` text NOT NULL, `public_id` text NOT NULL, PRIMARY KEY (`id`)); +-- Create index "auth_nonces_public_id_key" to table: "auth_nonces" +CREATE UNIQUE INDEX `auth_nonces_public_id_key` ON `auth_nonces` (`public_id`); +-- Create index "authnonce_public_id_active_expires_at" to table: "auth_nonces" +CREATE INDEX `authnonce_public_id_active_expires_at` ON `auth_nonces` (`public_id`, `active`, `expires_at`); +-- Enable back the enforcement of foreign-keys constraints +PRAGMA foreign_keys = on; diff --git a/internal/database/ent/migrations/20240703160454.sql b/internal/database/ent/migrations/20240703160454.sql new file mode 100644 index 00000000..dd5c1c46 --- /dev/null +++ b/internal/database/ent/migrations/20240703160454.sql @@ -0,0 +1,6 @@ +-- Create "sessions" table +CREATE TABLE `sessions` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `expires_at` datetime NOT NULL, `public_id` text NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `sessions_users_sessions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION); +-- Create index "sessions_public_id_key" to table: "sessions" +CREATE UNIQUE INDEX `sessions_public_id_key` ON `sessions` (`public_id`); +-- Create index "session_public_id_expires_at" to table: "sessions" +CREATE INDEX `session_public_id_expires_at` ON `sessions` (`public_id`, `expires_at`); diff --git a/internal/database/ent/migrations/20240703160711.sql b/internal/database/ent/migrations/20240703160711.sql new file mode 100644 index 00000000..3e23cb09 --- /dev/null +++ b/internal/database/ent/migrations/20240703160711.sql @@ -0,0 +1,142 @@ +-- Disable the enforcement of foreign-keys constraints +PRAGMA foreign_keys = off; +-- Create "new_account_snapshots" table +CREATE TABLE `new_account_snapshots` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `last_battle_time` datetime NOT NULL, `reference_id` text NOT NULL, `rating_battles` integer NOT NULL, `rating_frame` json NOT NULL, `regular_battles` integer NOT NULL, `regular_frame` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `account_snapshots_accounts_account_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "account_snapshots" to new temporary table "new_account_snapshots" +INSERT INTO `new_account_snapshots` (`id`, `created_at`, `updated_at`, `type`, `last_battle_time`, `reference_id`, `rating_battles`, `rating_frame`, `regular_battles`, `regular_frame`, `account_id`) SELECT `id`, `created_at`, `updated_at`, `type`, `last_battle_time`, `reference_id`, `rating_battles`, `rating_frame`, `regular_battles`, `regular_frame`, `account_id` FROM `account_snapshots`; +-- Drop "account_snapshots" table after copying rows +DROP TABLE `account_snapshots`; +-- Rename temporary table "new_account_snapshots" to "account_snapshots" +ALTER TABLE `new_account_snapshots` RENAME TO `account_snapshots`; +-- Create index "accountsnapshot_id" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_id` ON `account_snapshots` (`id`); +-- Create index "accountsnapshot_created_at" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_created_at` ON `account_snapshots` (`created_at`); +-- Create index "accountsnapshot_type_account_id_created_at" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_type_account_id_created_at` ON `account_snapshots` (`type`, `account_id`, `created_at`); +-- Create index "accountsnapshot_type_account_id_reference_id" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_type_account_id_reference_id` ON `account_snapshots` (`type`, `account_id`, `reference_id`); +-- Create index "accountsnapshot_type_account_id_reference_id_created_at" to table: "account_snapshots" +CREATE INDEX `accountsnapshot_type_account_id_reference_id_created_at` ON `account_snapshots` (`type`, `account_id`, `reference_id`, `created_at`); +-- Create "new_achievements_snapshots" table +CREATE TABLE `new_achievements_snapshots` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `battles` integer NOT NULL, `last_battle_time` datetime NOT NULL, `data` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `achievements_snapshots_accounts_achievement_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "achievements_snapshots" to new temporary table "new_achievements_snapshots" +INSERT INTO `new_achievements_snapshots` (`id`, `created_at`, `updated_at`, `type`, `reference_id`, `battles`, `last_battle_time`, `data`, `account_id`) SELECT `id`, `created_at`, `updated_at`, `type`, `reference_id`, `battles`, `last_battle_time`, `data`, `account_id` FROM `achievements_snapshots`; +-- Drop "achievements_snapshots" table after copying rows +DROP TABLE `achievements_snapshots`; +-- Rename temporary table "new_achievements_snapshots" to "achievements_snapshots" +ALTER TABLE `new_achievements_snapshots` RENAME TO `achievements_snapshots`; +-- Create index "achievementssnapshot_id" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_id` ON `achievements_snapshots` (`id`); +-- Create index "achievementssnapshot_created_at" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_created_at` ON `achievements_snapshots` (`created_at`); +-- Create index "achievementssnapshot_account_id_reference_id" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_account_id_reference_id` ON `achievements_snapshots` (`account_id`, `reference_id`); +-- Create index "achievementssnapshot_account_id_reference_id_created_at" to table: "achievements_snapshots" +CREATE INDEX `achievementssnapshot_account_id_reference_id_created_at` ON `achievements_snapshots` (`account_id`, `reference_id`, `created_at`); +-- Create "new_discord_interactions" table +CREATE TABLE `new_discord_interactions` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `command` text NOT NULL, `reference_id` text NOT NULL, `type` text NOT NULL, `locale` text NOT NULL, `options` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `discord_interactions_users_discord_interactions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "discord_interactions" to new temporary table "new_discord_interactions" +INSERT INTO `new_discord_interactions` (`id`, `created_at`, `updated_at`, `command`, `reference_id`, `type`, `locale`, `options`, `user_id`) SELECT `id`, `created_at`, `updated_at`, `command`, `reference_id`, `type`, `locale`, `options`, `user_id` FROM `discord_interactions`; +-- Drop "discord_interactions" table after copying rows +DROP TABLE `discord_interactions`; +-- Rename temporary table "new_discord_interactions" to "discord_interactions" +ALTER TABLE `new_discord_interactions` RENAME TO `discord_interactions`; +-- Create index "discordinteraction_id" to table: "discord_interactions" +CREATE INDEX `discordinteraction_id` ON `discord_interactions` (`id`); +-- Create index "discordinteraction_command" to table: "discord_interactions" +CREATE INDEX `discordinteraction_command` ON `discord_interactions` (`command`); +-- Create index "discordinteraction_user_id" to table: "discord_interactions" +CREATE INDEX `discordinteraction_user_id` ON `discord_interactions` (`user_id`); +-- Create index "discordinteraction_user_id_type" to table: "discord_interactions" +CREATE INDEX `discordinteraction_user_id_type` ON `discord_interactions` (`user_id`, `type`); +-- Create index "discordinteraction_reference_id" to table: "discord_interactions" +CREATE INDEX `discordinteraction_reference_id` ON `discord_interactions` (`reference_id`); +-- Create "new_user_connections" table +CREATE TABLE `new_user_connections` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `permissions` text NULL DEFAULT (''), `metadata` json NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_connections_users_connections` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "user_connections" to new temporary table "new_user_connections" +INSERT INTO `new_user_connections` (`id`, `created_at`, `updated_at`, `type`, `reference_id`, `permissions`, `metadata`, `user_id`) SELECT `id`, `created_at`, `updated_at`, `type`, `reference_id`, `permissions`, `metadata`, `user_id` FROM `user_connections`; +-- Drop "user_connections" table after copying rows +DROP TABLE `user_connections`; +-- Rename temporary table "new_user_connections" to "user_connections" +ALTER TABLE `new_user_connections` RENAME TO `user_connections`; +-- Create index "userconnection_id" to table: "user_connections" +CREATE INDEX `userconnection_id` ON `user_connections` (`id`); +-- Create index "userconnection_user_id" to table: "user_connections" +CREATE INDEX `userconnection_user_id` ON `user_connections` (`user_id`); +-- Create index "userconnection_type_user_id" to table: "user_connections" +CREATE INDEX `userconnection_type_user_id` ON `user_connections` (`type`, `user_id`); +-- Create index "userconnection_reference_id" to table: "user_connections" +CREATE INDEX `userconnection_reference_id` ON `user_connections` (`reference_id`); +-- Create index "userconnection_type_reference_id" to table: "user_connections" +CREATE INDEX `userconnection_type_reference_id` ON `user_connections` (`type`, `reference_id`); +-- Create index "userconnection_reference_id_user_id_type" to table: "user_connections" +CREATE UNIQUE INDEX `userconnection_reference_id_user_id_type` ON `user_connections` (`reference_id`, `user_id`, `type`); +-- Create "new_user_contents" table +CREATE TABLE `new_user_contents` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `reference_id` text NOT NULL, `value` text NOT NULL, `metadata` json NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_contents_users_content` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "user_contents" to new temporary table "new_user_contents" +INSERT INTO `new_user_contents` (`id`, `created_at`, `updated_at`, `type`, `reference_id`, `value`, `metadata`, `user_id`) SELECT `id`, `created_at`, `updated_at`, `type`, `reference_id`, `value`, `metadata`, `user_id` FROM `user_contents`; +-- Drop "user_contents" table after copying rows +DROP TABLE `user_contents`; +-- Rename temporary table "new_user_contents" to "user_contents" +ALTER TABLE `new_user_contents` RENAME TO `user_contents`; +-- Create index "usercontent_id" to table: "user_contents" +CREATE INDEX `usercontent_id` ON `user_contents` (`id`); +-- Create index "usercontent_user_id" to table: "user_contents" +CREATE INDEX `usercontent_user_id` ON `user_contents` (`user_id`); +-- Create index "usercontent_type_user_id" to table: "user_contents" +CREATE INDEX `usercontent_type_user_id` ON `user_contents` (`type`, `user_id`); +-- Create index "usercontent_reference_id" to table: "user_contents" +CREATE INDEX `usercontent_reference_id` ON `user_contents` (`reference_id`); +-- Create index "usercontent_type_reference_id" to table: "user_contents" +CREATE INDEX `usercontent_type_reference_id` ON `user_contents` (`type`, `reference_id`); +-- Create "new_user_subscriptions" table +CREATE TABLE `new_user_subscriptions` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `expires_at` datetime NOT NULL, `permissions` text NOT NULL, `reference_id` text NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_subscriptions_users_subscriptions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "user_subscriptions" to new temporary table "new_user_subscriptions" +INSERT INTO `new_user_subscriptions` (`id`, `created_at`, `updated_at`, `type`, `expires_at`, `permissions`, `reference_id`, `user_id`) SELECT `id`, `created_at`, `updated_at`, `type`, `expires_at`, `permissions`, `reference_id`, `user_id` FROM `user_subscriptions`; +-- Drop "user_subscriptions" table after copying rows +DROP TABLE `user_subscriptions`; +-- Rename temporary table "new_user_subscriptions" to "user_subscriptions" +ALTER TABLE `new_user_subscriptions` RENAME TO `user_subscriptions`; +-- Create index "usersubscription_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_id` ON `user_subscriptions` (`id`); +-- Create index "usersubscription_user_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_user_id` ON `user_subscriptions` (`user_id`); +-- Create index "usersubscription_type_user_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_type_user_id` ON `user_subscriptions` (`type`, `user_id`); +-- Create index "usersubscription_expires_at" to table: "user_subscriptions" +CREATE INDEX `usersubscription_expires_at` ON `user_subscriptions` (`expires_at`); +-- Create index "usersubscription_expires_at_user_id" to table: "user_subscriptions" +CREATE INDEX `usersubscription_expires_at_user_id` ON `user_subscriptions` (`expires_at`, `user_id`); +-- Create "new_vehicle_snapshots" table +CREATE TABLE `new_vehicle_snapshots` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL, `vehicle_id` text NOT NULL, `reference_id` text NOT NULL, `battles` integer NOT NULL, `last_battle_time` datetime NOT NULL, `frame` json NOT NULL, `account_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `vehicle_snapshots_accounts_vehicle_snapshots` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "vehicle_snapshots" to new temporary table "new_vehicle_snapshots" +INSERT INTO `new_vehicle_snapshots` (`id`, `created_at`, `updated_at`, `type`, `vehicle_id`, `reference_id`, `battles`, `last_battle_time`, `frame`, `account_id`) SELECT `id`, `created_at`, `updated_at`, `type`, `vehicle_id`, `reference_id`, `battles`, `last_battle_time`, `frame`, `account_id` FROM `vehicle_snapshots`; +-- Drop "vehicle_snapshots" table after copying rows +DROP TABLE `vehicle_snapshots`; +-- Rename temporary table "new_vehicle_snapshots" to "vehicle_snapshots" +ALTER TABLE `new_vehicle_snapshots` RENAME TO `vehicle_snapshots`; +-- Create index "vehiclesnapshot_id" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_id` ON `vehicle_snapshots` (`id`); +-- Create index "vehiclesnapshot_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_created_at` ON `vehicle_snapshots` (`created_at`); +-- Create index "vehiclesnapshot_vehicle_id_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_vehicle_id_created_at` ON `vehicle_snapshots` (`vehicle_id`, `created_at`); +-- Create index "vehiclesnapshot_account_id_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_account_id_created_at` ON `vehicle_snapshots` (`account_id`, `created_at`); +-- Create index "vehiclesnapshot_account_id_type_created_at" to table: "vehicle_snapshots" +CREATE INDEX `vehiclesnapshot_account_id_type_created_at` ON `vehicle_snapshots` (`account_id`, `type`, `created_at`); +-- Create "new_sessions" table +CREATE TABLE `new_sessions` (`id` text NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `expires_at` datetime NOT NULL, `public_id` text NOT NULL, `user_id` text NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `sessions_users_sessions` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "sessions" to new temporary table "new_sessions" +INSERT INTO `new_sessions` (`id`, `created_at`, `updated_at`, `expires_at`, `public_id`, `user_id`) SELECT `id`, `created_at`, `updated_at`, `expires_at`, `public_id`, `user_id` FROM `sessions`; +-- Drop "sessions" table after copying rows +DROP TABLE `sessions`; +-- Rename temporary table "new_sessions" to "sessions" +ALTER TABLE `new_sessions` RENAME TO `sessions`; +-- Create index "sessions_public_id_key" to table: "sessions" +CREATE UNIQUE INDEX `sessions_public_id_key` ON `sessions` (`public_id`); +-- Create index "session_public_id_expires_at" to table: "sessions" +CREATE INDEX `session_public_id_expires_at` ON `sessions` (`public_id`, `expires_at`); +-- Enable back the enforcement of foreign-keys constraints +PRAGMA foreign_keys = on; diff --git a/internal/database/ent/migrations/20240703180358.sql b/internal/database/ent/migrations/20240703180358.sql new file mode 100644 index 00000000..4162fa2b --- /dev/null +++ b/internal/database/ent/migrations/20240703180358.sql @@ -0,0 +1,4 @@ +-- Add column "metadata" to table: "auth_nonces" +ALTER TABLE `auth_nonces` ADD COLUMN `metadata` json NOT NULL; +-- Add column "metadata" to table: "sessions" +ALTER TABLE `sessions` ADD COLUMN `metadata` json NOT NULL; diff --git a/internal/database/ent/migrations/atlas.sum b/internal/database/ent/migrations/atlas.sum index 2251e9e6..e7b6993f 100644 --- a/internal/database/ent/migrations/atlas.sum +++ b/internal/database/ent/migrations/atlas.sum @@ -1,3 +1,7 @@ -h1:ZYxvqqhpfA/QIZSU/etxpGhuHYFfgwF3/i9HL04YT0E= +h1:FmVqQkU1ia4ydQNcB3kqRiXuPqjEdvnRb+sVMIJRRPA= 20240627171532.sql h1:480JhjRCpyyix192z/L92yEI7LSsgJhuiK0ljat55TI= 20240627200953.sql h1:VUQZd1HQrxITMUtNO9HRtnDE8wgtWgRyzduACb3D3u4= +20240703151859.sql h1:1QQD7uLkRsLESNATRcGMg7k1C+sxYFQh/wI7BXrsKUA= +20240703160454.sql h1:8KsIVNWGQGGSkh/cvdYdg1SLIRG0BqY262QOu0Icqu4= +20240703160711.sql h1:26t/4QYITq6eAo2hif8sCLRpuHsCHXt6nyvDnX+Y1oQ= +20240703180358.sql h1:gxgh1nrquDAOFGT7yGzgqpy5EfQ4Dvgd6c2NMPM3iIs= diff --git a/internal/database/ent/schema/account.go b/internal/database/ent/schema/account.go index c27e22df..ce4ca153 100644 --- a/internal/database/ent/schema/account.go +++ b/internal/database/ent/schema/account.go @@ -2,6 +2,7 @@ package schema import ( "entgo.io/ent" + "entgo.io/ent/dialect/entsql" "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" "entgo.io/ent/schema/index" @@ -41,9 +42,9 @@ func (Account) Fields() []ent.Field { func (Account) Edges() []ent.Edge { return []ent.Edge{ edge.From("clan", Clan.Type).Ref("accounts").Field("clan_id").Unique(), - edge.To("achievement_snapshots", AchievementsSnapshot.Type), - edge.To("vehicle_snapshots", VehicleSnapshot.Type), - edge.To("account_snapshots", AccountSnapshot.Type), + edge.To("achievement_snapshots", AchievementsSnapshot.Type).Annotations(entsql.OnDelete(entsql.Cascade)), + edge.To("vehicle_snapshots", VehicleSnapshot.Type).Annotations(entsql.OnDelete(entsql.Cascade)), + edge.To("account_snapshots", AccountSnapshot.Type).Annotations(entsql.OnDelete(entsql.Cascade)), } } diff --git a/internal/database/ent/schema/nonce.go b/internal/database/ent/schema/nonce.go new file mode 100644 index 00000000..86baceaa --- /dev/null +++ b/internal/database/ent/schema/nonce.go @@ -0,0 +1,31 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +type AuthNonce struct { + ent.Schema +} + +func (AuthNonce) Fields() []ent.Field { + return append(defaultFields, + field.Bool("active"), + field.Time("expires_at").Immutable(), + field.String("identifier").NotEmpty().Immutable(), + field.String("public_id").NotEmpty().Immutable().Unique(), + field.JSON("metadata", map[string]string{}), + ) +} + +func (AuthNonce) Edges() []ent.Edge { + return nil +} + +func (AuthNonce) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("public_id", "active", "expires_at"), + } +} diff --git a/internal/database/ent/schema/session.go b/internal/database/ent/schema/session.go new file mode 100644 index 00000000..d9c20e7d --- /dev/null +++ b/internal/database/ent/schema/session.go @@ -0,0 +1,33 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +type Session struct { + ent.Schema +} + +func (Session) Fields() []ent.Field { + return append(defaultFields, + field.Time("expires_at"), + field.String("user_id").Immutable(), + field.String("public_id").NotEmpty().Immutable().Unique(), + field.JSON("metadata", map[string]string{}), + ) +} + +func (Session) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("user", User.Type).Ref("sessions").Field("user_id").Required().Immutable().Unique(), + } +} + +func (Session) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("public_id", "expires_at"), + } +} diff --git a/internal/database/ent/schema/user.go b/internal/database/ent/schema/user.go index aec9bc0f..338c0e79 100644 --- a/internal/database/ent/schema/user.go +++ b/internal/database/ent/schema/user.go @@ -2,6 +2,7 @@ package schema import ( "entgo.io/ent" + "entgo.io/ent/dialect/entsql" "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" "entgo.io/ent/schema/index" @@ -25,6 +26,7 @@ func (User) Fields() []ent.Field { Default(timeNow). UpdateDefault(timeNow), // + field.String("username").Default(""), field.String("permissions").Default(""), field.Strings("feature_flags").Optional(), } @@ -33,15 +35,17 @@ func (User) Fields() []ent.Field { // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ - edge.To("discord_interactions", DiscordInteraction.Type), - edge.To("subscriptions", UserSubscription.Type), - edge.To("connections", UserConnection.Type), - edge.To("content", UserContent.Type), + edge.To("discord_interactions", DiscordInteraction.Type).Annotations(entsql.OnDelete(entsql.Cascade)), + edge.To("subscriptions", UserSubscription.Type).Annotations(entsql.OnDelete(entsql.Cascade)), + edge.To("connections", UserConnection.Type).Annotations(entsql.OnDelete(entsql.Cascade)), + edge.To("content", UserContent.Type).Annotations(entsql.OnDelete(entsql.Cascade)), + edge.To("sessions", Session.Type).Annotations(entsql.OnDelete(entsql.Cascade)), } } func (User) Indexes() []ent.Index { return []ent.Index{ index.Fields("id"), + index.Fields("username"), } } diff --git a/internal/database/models/nonce.go b/internal/database/models/nonce.go new file mode 100644 index 00000000..deb191c7 --- /dev/null +++ b/internal/database/models/nonce.go @@ -0,0 +1,34 @@ +package models + +import ( + "time" + + "github.com/pkg/errors" +) + +var ErrInvalidNonce = errors.New("invalid nonce") +var ErrNonceExpired = errors.New("nonce expired") + +type AuthNonce struct { + ID string + + Active bool + PublicID string + Identifier string + + CreatedAt time.Time + UpdatedAt time.Time + ExpiresAt time.Time + + Meta map[string]string +} + +func (n AuthNonce) Valid() error { + if n.ExpiresAt.Before(time.Now()) { + return ErrNonceExpired + } + if n.Active && n.PublicID != "" && n.Identifier != "" { + return nil + } + return ErrInvalidNonce +} diff --git a/internal/database/models/session.go b/internal/database/models/session.go new file mode 100644 index 00000000..fa93af6a --- /dev/null +++ b/internal/database/models/session.go @@ -0,0 +1,35 @@ +package models + +import ( + "time" + + "github.com/pkg/errors" +) + +var ( + ErrSessionExpired = errors.New("session expired") + ErrSessionInvalid = errors.New("session invalid") +) + +type Session struct { + ID string + + UserID string + PublicID string + + CreatedAt time.Time + UpdatedAt time.Time + ExpiresAt time.Time + + Meta map[string]string +} + +func (n Session) Valid() error { + if n.ExpiresAt.Before(time.Now()) { + return ErrSessionExpired + } + if n.UserID != "" && n.PublicID != "" { + return nil + } + return ErrSessionInvalid +} diff --git a/internal/logic/strings.go b/internal/logic/strings.go new file mode 100644 index 00000000..8d139873 --- /dev/null +++ b/internal/logic/strings.go @@ -0,0 +1,15 @@ +package logic + +import ( + "crypto/rand" + "fmt" +) + +func RandomString(length int) (string, error) { + data := make([]byte, length) + _, err := rand.Read(data) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", data), nil +} diff --git a/tests/static_database.go b/tests/static_database.go index 6c2a4366..d2abba2c 100644 --- a/tests/static_database.go +++ b/tests/static_database.go @@ -169,3 +169,26 @@ func (c *staticTestingDatabase) GetDiscordInteraction(ctx context.Context, refer func (c *staticTestingDatabase) DeleteExpiredInteractions(ctx context.Context, expiration time.Time) error { return errors.New("DeleteExpiredInteractions not implemented") } + +func (c *staticTestingDatabase) CreateAuthNonce(ctx context.Context, publicID, identifier string, expiresAt time.Time, meta map[string]string) (models.AuthNonce, error) { + return models.AuthNonce{}, errors.New("CreateAuthNonce not implemented") +} +func (c *staticTestingDatabase) FindAuthNonce(ctx context.Context, publicID string) (models.AuthNonce, error) { + return models.AuthNonce{}, errors.New("FindAuthNonce not implemented") +} +func (c *staticTestingDatabase) SetAuthNonceActive(ctx context.Context, nonceID string, active bool) error { + return errors.New("SetAuthNonceActive not implemented") +} + +func (c *staticTestingDatabase) CreateSession(ctx context.Context, publicID, userID string, expiresAt time.Time, meta map[string]string) (models.Session, error) { + return models.Session{}, errors.New("CreateSession not implementer") +} +func (c *staticTestingDatabase) SetSessionExpiresAt(ctx context.Context, sessionID string, expiresAt time.Time) error { + return errors.New("SetSessionExpiresAt not implementer") +} +func (c *staticTestingDatabase) FindSession(ctx context.Context, publicID string) (models.Session, error) { + return models.Session{}, errors.New("FindSession not implementer") +} +func (c *staticTestingDatabase) UserFromSession(ctx context.Context, publicID string) (models.User, error) { + return models.User{}, errors.New("UserFromSession not implementer") +} From 4e60c16b55c44a6a8264a7a5906026a83f360f32 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 10:47:50 -0400 Subject: [PATCH 247/341] mostly formatting and ui --- cmd/frontend/components/logo_templ.go | 22 +- cmd/frontend/components/navbar.templ | 67 +++++ cmd/frontend/components/navbar_templ.go | 193 +++++++++++++++ .../components/widget/default_templ.go | 118 +++++---- .../components/widget/widget_templ.go | 22 +- cmd/frontend/handler/context.go | 10 + cmd/frontend/handlers.go | 12 + cmd/frontend/layouts/main.templ | 42 ++-- cmd/frontend/layouts/main_templ.go | 69 ++++-- cmd/frontend/logic/strings.go | 8 + cmd/frontend/routes/app/index_templ.go | 22 +- cmd/frontend/routes/app/widgets.templ | 102 ++++++++ cmd/frontend/routes/app/widgets_templ.go | 233 ++++++++++++++++++ cmd/frontend/routes/errors.templ | 11 +- cmd/frontend/routes/errors_templ.go | 53 ++-- cmd/frontend/routes/index.templ | 54 ++-- cmd/frontend/routes/index_templ.go | 224 +++++++++++++++-- cmd/frontend/routes/widget/configure.templ | 19 +- cmd/frontend/routes/widget/configure_templ.go | 43 ++-- cmd/frontend/routes/widget/live.templ | 50 +++- cmd/frontend/routes/widget/live_templ.go | 70 ++++-- cmd/frontend/routes/widget/personal.templ | 76 ++++++ cmd/frontend/routes/widget/personal_templ.go | 97 ++++++++ internal/database/models/user.go | 40 ++- internal/database/models/widget_options.go | 28 +++ tests/static_data.go | 13 + tests/static_database.go | 25 +- 27 files changed, 1467 insertions(+), 256 deletions(-) create mode 100644 cmd/frontend/components/navbar.templ create mode 100644 cmd/frontend/components/navbar_templ.go create mode 100644 cmd/frontend/logic/strings.go create mode 100644 cmd/frontend/routes/app/widgets.templ create mode 100644 cmd/frontend/routes/app/widgets_templ.go create mode 100644 cmd/frontend/routes/widget/personal.templ create mode 100644 cmd/frontend/routes/widget/personal_templ.go create mode 100644 internal/database/models/widget_options.go diff --git a/cmd/frontend/components/logo_templ.go b/cmd/frontend/components/logo_templ.go index fe08297b..7e4f5f4d 100644 --- a/cmd/frontend/components/logo_templ.go +++ b/cmd/frontend/components/logo_templ.go @@ -1,23 +1,26 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.696 +// templ: version: v0.2.731 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" +import templruntime "github.com/a-h/templ/runtime" import "fmt" func Logo(size string) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var1 := templ.GetChildren(ctx) @@ -42,9 +45,6 @@ func Logo(size string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } diff --git a/cmd/frontend/components/navbar.templ b/cmd/frontend/components/navbar.templ new file mode 100644 index 00000000..09b4fa99 --- /dev/null +++ b/cmd/frontend/components/navbar.templ @@ -0,0 +1,67 @@ +package components + +import ( + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/logic" + "os" + "strings" +) + +var appName = os.Getenv("WEBAPP_NAME") + +type navbarProps struct { + authenticated bool + path string +} + +var Navbar handler.Partial = func(ctx *handler.Context) (templ.Component, error) { + var props navbarProps + props.path = ctx.URL().Path + + user, err := ctx.SessionUser() + if err == nil { + // props.authenticated = user.ID != "" + _ = user + } + + return navbar(props), nil +} + +templ navbar(props navbarProps) { +
    + +
    +} + +templ navMenuLink(label, href, currentPath string) { + { label } +} + +func navLinkClass(path, requiredPath string) string { + base := "underline-offset-4 link" + + if !strings.HasSuffix(path, "/") { + path += "/" + } + if path == requiredPath { + return base + } + + return base + " " + "link-hover" +} diff --git a/cmd/frontend/components/navbar_templ.go b/cmd/frontend/components/navbar_templ.go new file mode 100644 index 00000000..2d384ef6 --- /dev/null +++ b/cmd/frontend/components/navbar_templ.go @@ -0,0 +1,193 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/logic" + "os" + "strings" +) + +var appName = os.Getenv("WEBAPP_NAME") + +type navbarProps struct { + authenticated bool + path string +} + +var Navbar handler.Partial = func(ctx *handler.Context) (templ.Component, error) { + var props navbarProps + props.path = ctx.URL().Path + + user, err := ctx.SessionUser() + if err == nil { + // props.authenticated = user.ID != "" + _ = user + } + + return navbar(props), nil +} + +func navbar(props navbarProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Logo("32").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 = []any{logic.StringIfElse("active", "", props.path == "/widget")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Widget
    • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 = []any{logic.StringIfElse("btn active", "btn", props.path == "/login")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Login
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func navMenuLink(label, href, currentPath string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var7 = []any{navLinkClass(href, currentPath)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 53, Col: 78} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func navLinkClass(path, requiredPath string) string { + base := "underline-offset-4 link" + + if !strings.HasSuffix(path, "/") { + path += "/" + } + if path == requiredPath { + return base + } + + return base + " " + "link-hover" +} diff --git a/cmd/frontend/components/widget/default_templ.go b/cmd/frontend/components/widget/default_templ.go index a90f5fdb..fc252866 100644 --- a/cmd/frontend/components/widget/default_templ.go +++ b/cmd/frontend/components/widget/default_templ.go @@ -1,14 +1,12 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.696 +// templ: version: v0.2.731 package widget //lint:file-ignore SA4006 This context is only used if a nested component is present. import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" +import templruntime "github.com/a-h/templ/runtime" import "fmt" import "github.com/cufee/aftermath/internal/stats/prepare/session/v1" @@ -54,11 +52,16 @@ func (w widget) defaultWidget() templ.Component { } func (w defaultWidget) Render() templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var1 := templ.GetChildren(ctx) @@ -98,19 +101,21 @@ func (w defaultWidget) Render() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } func (w defaultWidget) overviewCard(card session.OverviewCard) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var2 := templ.GetChildren(ctx) @@ -173,19 +178,21 @@ func (w defaultWidget) overviewCard(card session.OverviewCard) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } func (w defaultWidget) vehicleCard(card vehicleExtended) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var6 := templ.GetChildren(ctx) @@ -261,19 +268,21 @@ func (w defaultWidget) vehicleCard(card vehicleExtended) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } func (w defaultWidget) vehicleLegendCard(blocks []common.StatsBlock[session.BlockData]) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var11 := templ.GetChildren(ctx) @@ -330,19 +339,21 @@ func (w defaultWidget) vehicleLegendCard(blocks []common.StatsBlock[session.Bloc if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } func (w defaultWidget) overviewColumn(column session.OverviewColumn) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var15 := templ.GetChildren(ctx) @@ -371,19 +382,21 @@ func (w defaultWidget) overviewColumn(column session.OverviewColumn) templ.Compo return templ_7745c5c3_Err } } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } func (w defaultWidget) specialOverviewColumn(column session.OverviewColumn) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var16 := templ.GetChildren(ctx) @@ -451,19 +464,21 @@ func (w defaultWidget) specialOverviewColumn(column session.OverviewColumn) temp if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } func (w defaultWidget) block(block common.StatsBlock[session.BlockData], style styleOptions) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var19 := templ.GetChildren(ctx) @@ -530,9 +545,6 @@ func (w defaultWidget) block(block common.StatsBlock[session.BlockData], style s if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } diff --git a/cmd/frontend/components/widget/widget_templ.go b/cmd/frontend/components/widget/widget_templ.go index 33366abe..f4802841 100644 --- a/cmd/frontend/components/widget/widget_templ.go +++ b/cmd/frontend/components/widget/widget_templ.go @@ -1,14 +1,12 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.696 +// templ: version: v0.2.731 package widget //lint:file-ignore SA4006 This context is only used if a nested component is present. import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" +import templruntime "github.com/a-h/templ/runtime" import "github.com/cufee/aftermath/cmd/frontend/logic" import "fmt" @@ -82,11 +80,16 @@ type widget struct { } func (w widget) Render() templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var1 := templ.GetChildren(ctx) @@ -154,9 +157,6 @@ func (w widget) Render() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } diff --git a/cmd/frontend/handler/context.go b/cmd/frontend/handler/context.go index 956b2a9b..e8d02055 100644 --- a/cmd/frontend/handler/context.go +++ b/cmd/frontend/handler/context.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "net/url" + "os" "strings" "github.com/a-h/templ" @@ -17,6 +18,8 @@ import ( "github.com/rs/zerolog/log" ) +var devMode = os.Getenv("AUTH_DEV_MODE") == "true" + type Servable interface { Serve(ctx *Context) error } @@ -59,6 +62,9 @@ func (ctx *Context) Form(key string) string { func (ctx *Context) Path(key string) string { return ctx.r.PathValue(key) } +func (ctx *Context) URL() *url.URL { + return ctx.r.URL +} func (ctx *Context) RealIP() (string, bool) { if ip := ctx.r.Header.Get("X-Forwarded-For"); ip != "" { return ip, true @@ -83,6 +89,10 @@ func (ctx *Context) SessionUser() (*models.User, error) { if ctx.user != nil { return ctx.user, nil } + if devMode { + user, _ := ctx.Database().UserFromSession(ctx.Context, "dev-user") + return &user, nil + } cookie, err := ctx.Cookie(auth.SessionCookieName) if err != nil || cookie == nil { diff --git a/cmd/frontend/handlers.go b/cmd/frontend/handlers.go index 640d0341..62dfec16 100644 --- a/cmd/frontend/handlers.go +++ b/cmd/frontend/handlers.go @@ -67,11 +67,23 @@ func Handlers(core core.Client) ([]server.Handler, error) { Path: get("/widget/{accountId}/live"), Func: handler.Chain(core, widget.LiveWidget), }, + { + Path: get("/widget/personal"), + Func: redirect("/app/widget"), + }, + { + Path: get("/widget/personal/{widgetId}/live"), + Func: handler.Chain(core, widget.PersonalLiveWidget), + }, // app routes { Path: get("/app"), Func: handler.Chain(core, app.Index, middleware.SessionCheck), }, + { + Path: get("/app/widgets"), + Func: handler.Chain(core, app.Widgets, middleware.SessionCheck), + }, // api routes { Path: get("/api/auth/discord"), diff --git a/cmd/frontend/layouts/main.templ b/cmd/frontend/layouts/main.templ index 6a10ee55..252ea782 100644 --- a/cmd/frontend/layouts/main.templ +++ b/cmd/frontend/layouts/main.templ @@ -1,17 +1,24 @@ package layouts import ( - "os" + "github.com/cufee/aftermath/cmd/frontend/components" "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/logic" + "os" ) var appName = os.Getenv("WEBAPP_NAME") var Main handler.Layout = func(ctx *handler.Context, children ...templ.Component) (templ.Component, error) { - return main(children...), nil + navbar, err := components.Navbar(ctx) + if err != nil { + return nil, ctx.Error(err, "failed to render a page") + } + + return main(navbar, children...), nil } -templ main(children ...templ.Component) { +templ main(navbar templ.Component, children ...templ.Component) { @@ -23,23 +30,12 @@ templ main(children ...templ.Component) { - + @logic.EmbedMinifiedScript(tailwindConfig()) { appName }
    + @navbar for _, render := range children { @render } @@ -56,3 +52,17 @@ templ main(children ...templ.Component) { } + +script tailwindConfig() { + tailwind.config = { + theme: { + borderRadius: { + "sm": "10px", + "md": "15px", + "lg": "20px", + "xl": "25px", + 'full': '9999px', + } + } + } +} diff --git a/cmd/frontend/layouts/main_templ.go b/cmd/frontend/layouts/main_templ.go index 3b602fb6..f38cdafb 100644 --- a/cmd/frontend/layouts/main_templ.go +++ b/cmd/frontend/layouts/main_templ.go @@ -1,32 +1,42 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.696 +// templ: version: v0.2.731 package layouts //lint:file-ignore SA4006 This context is only used if a nested component is present. import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" +import templruntime "github.com/a-h/templ/runtime" import ( + "github.com/cufee/aftermath/cmd/frontend/components" "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/logic" "os" ) var appName = os.Getenv("WEBAPP_NAME") var Main handler.Layout = func(ctx *handler.Context, children ...templ.Component) (templ.Component, error) { - return main(children...), nil + navbar, err := components.Navbar(ctx) + if err != nil { + return nil, ctx.Error(err, "failed to render a page") + } + + return main(navbar, children...), nil } -func main(children ...templ.Component) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) +func main(navbar templ.Component, children ...templ.Component) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var1 := templ.GetChildren(ctx) @@ -34,14 +44,22 @@ func main(children ...templ.Component) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\" data-theme=\"dark\"><head><meta charset=\"utf-8\"><meta name=\"color-scheme\" content=\"light\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"https://cdn.jsdelivr.net/npm/daisyui@4.11.1/dist/full.min.css\" rel=\"stylesheet\" type=\"text/css\"><script src=\"https://unpkg.com/htmx.org@1.9.12\"></script><script src=\"https://unpkg.com/htmx.org/dist/ext/head-support.js\"></script><script src=\"https://unpkg.com/htmx.org@1.9.12/dist/ext/multi-swap.js\"></script><script src=\"https://cdn.tailwindcss.com\"></script>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = logic.EmbedMinifiedScript(tailwindConfig()).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<title>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(appName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/layouts/main.templ`, Line: 39, Col: 19} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/layouts/main.templ`, Line: 34, Col: 19} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -51,6 +69,10 @@ func main(children ...templ.Component) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + templ_7745c5c3_Err = navbar.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } for _, render := range children { templ_7745c5c3_Err = render.Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { @@ -61,9 +83,26 @@ func main(children ...templ.Component) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } + +func tailwindConfig() templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_tailwindConfig_0472`, + Function: `function __templ_tailwindConfig_0472(){tailwind.config = { + theme: { + borderRadius: { + "sm": "10px", + "md": "15px", + "lg": "20px", + "xl": "25px", + 'full': '9999px', + } + } + } +}`, + Call: templ.SafeScript(`__templ_tailwindConfig_0472`), + CallInline: templ.SafeScriptInline(`__templ_tailwindConfig_0472`), + } +} diff --git a/cmd/frontend/logic/strings.go b/cmd/frontend/logic/strings.go new file mode 100644 index 00000000..4fba48cd --- /dev/null +++ b/cmd/frontend/logic/strings.go @@ -0,0 +1,8 @@ +package logic + +func StringIfElse(onTrue, onFalse string, condition bool) string { + if condition { + return onTrue + } + return onFalse +} diff --git a/cmd/frontend/routes/app/index_templ.go b/cmd/frontend/routes/app/index_templ.go index 16e3e940..4cc53393 100644 --- a/cmd/frontend/routes/app/index_templ.go +++ b/cmd/frontend/routes/app/index_templ.go @@ -1,14 +1,12 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.696 +// templ: version: v0.2.731 package app //lint:file-ignore SA4006 This context is only used if a nested component is present. import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" +import templruntime "github.com/a-h/templ/runtime" import "net/http" import "github.com/cufee/aftermath/internal/database/models" @@ -24,11 +22,16 @@ var Index handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Compo } func index(user *models.User) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var1 := templ.GetChildren(ctx) @@ -53,9 +56,6 @@ func index(user *models.User) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } diff --git a/cmd/frontend/routes/app/widgets.templ b/cmd/frontend/routes/app/widgets.templ new file mode 100644 index 00000000..00b35f75 --- /dev/null +++ b/cmd/frontend/routes/app/widgets.templ @@ -0,0 +1,102 @@ +package app + +import ( + "fmt" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/cmd/frontend/logic" + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" + "github.com/pkg/errors" + "net/http" +) + +var Widgets handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + user, err := ctx.SessionUser() + if err != nil { + return nil, nil, ctx.Redirect("/login", http.StatusTemporaryRedirect) + } + + extendedUser, err := ctx.Database().GetUserByID(ctx, user.ID, database.WithConnections()) + if err != nil { + return nil, nil, ctx.Redirect("/login", http.StatusTemporaryRedirect) + } + + var accountIDs []string + connections, _ := extendedUser.FilterConnections(models.ConnectionTypeWargaming, nil) + for _, conn := range connections { + accountIDs = append(accountIDs, conn.ReferenceID) + } + + var accounts []models.Account + if len(accountIDs) > 0 { + accounts, err = ctx.Database().GetAccounts(ctx.Context, accountIDs) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + } + + return layouts.Main, widgetConfiguratorPage(accounts, "account1"), nil +} + +templ widgetConfiguratorPage(accounts []models.Account, selectedId string) { + <div class="flex flex-row justify-center gap-4 flex-wrap min-w-max"> + <div class="flex flex-col gap-2"> + <ul class="menu bg-base-200 rounded-box min-w-max gap-1"> + for _, account := range accounts { + <li class="group"> + <a class={ "flex flex-row gap-2 justify-between" + logic.StringIfElse(" active", "", account.ID == selectedId) }> + <span>{ account.Nickname }</span> + <span class={ "badge group-hover:bg-gray-900 " + logic.StringIfElse("bg-gray-900", "badge-neutral", account.ID == selectedId) }>{ account.Realm }</span> + </a> + </li> + } + <li> + <a class={ "flex flex-row gap-1 justify-between" + logic.StringIfElse(" active", "", selectedId == "") }> + <span>Link</span> + <span>+</span> + </a> + </li> + </ul> + </div> + <div class="grow flex flex-col max-w-xl gap-2"> + @widgetOptions() + // @widget.Widget() + </div> + </div> +} + +templ widgetOptions() { + <div class="flex flex-col items-center justify-center gap-4"> + <div class="form-control w-full flex gap-2"> + <div class="flex flex-col bg-base-200 rounded-lg p-4"> + <span class="text-lg">Regular Battles</span> + <label class="label cursor-pointer"> + <span class="label-text">Show Overview Card</span> + <input type="checkbox" class="toggle toggle-secondary" checked="checked"/> + </label> + <label class="label cursor-pointer flex flex-col items-start gap-1"> + <span class="label-text">Vehicle Cards</span> + <input type="range" min="0" max="10" value="3" class="range" step="1"/> + <div class="flex w-full justify-between px-2 text-xs"> + for i := range 11 { + <div class="flex flex-col items-center"> + <span>{ fmt.Sprint(i) }</span> + </div> + } + </div> + </label> + </div> + <div class="flex flex-col bg-base-200 rounded-lg p-4"> + <span class="text-lg">Rating Battles</span> + <label class="label cursor-pointer"> + <span class="label-text">Show Overview Card</span> + <input type="checkbox" class="toggle toggle-secondary" checked="checked"/> + </label> + </div> + </div> + <button class="btn btn-primary"> + copy link + </button> + </div> +} diff --git a/cmd/frontend/routes/app/widgets_templ.go b/cmd/frontend/routes/app/widgets_templ.go new file mode 100644 index 00000000..3a049f64 --- /dev/null +++ b/cmd/frontend/routes/app/widgets_templ.go @@ -0,0 +1,233 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package app + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/cmd/frontend/logic" + "github.com/cufee/aftermath/internal/database" + "github.com/cufee/aftermath/internal/database/models" + "github.com/pkg/errors" + "net/http" +) + +var Widgets handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + user, err := ctx.SessionUser() + if err != nil { + return nil, nil, ctx.Redirect("/login", http.StatusTemporaryRedirect) + } + + extendedUser, err := ctx.Database().GetUserByID(ctx, user.ID, database.WithConnections()) + if err != nil { + return nil, nil, ctx.Redirect("/login", http.StatusTemporaryRedirect) + } + + var accountIDs []string + connections, _ := extendedUser.FilterConnections(models.ConnectionTypeWargaming, nil) + for _, conn := range connections { + accountIDs = append(accountIDs, conn.ReferenceID) + } + + var accounts []models.Account + if len(accountIDs) > 0 { + accounts, err = ctx.Database().GetAccounts(ctx.Context, accountIDs) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + } + + return layouts.Main, widgetConfiguratorPage(accounts, "account1"), nil +} + +func widgetConfiguratorPage(accounts []models.Account, selectedId string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex flex-row justify-center gap-4 flex-wrap min-w-max\"><div class=\"flex flex-col gap-2\"><ul class=\"menu bg-base-200 rounded-box min-w-max gap-1\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, account := range accounts { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"group\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 = []any{"flex flex-row gap-2 justify-between" + logic.StringIfElse(" active", "", account.ID == selectedId)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/app/widgets.templ`, Line: 1, Col: 0} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><span>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(account.Nickname) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/app/widgets.templ`, Line: 49, Col: 31} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 = []any{"badge group-hover:bg-gray-900 " + logic.StringIfElse("bg-gray-900", "badge-neutral", account.ID == selectedId)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var5).String()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/app/widgets.templ`, Line: 1, Col: 0} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(account.Realm) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/app/widgets.templ`, Line: 50, Col: 150} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></a></li>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 = []any{"flex flex-row gap-1 justify-between" + logic.StringIfElse(" active", "", selectedId == "")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/app/widgets.templ`, Line: 1, Col: 0} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><span>Link</span> <span>+</span></a></li></ul></div><div class=\"grow flex flex-col max-w-xl gap-2\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = widgetOptions().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func widgetOptions() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex flex-col items-center justify-center gap-4\"><div class=\"form-control w-full flex gap-2\"><div class=\"flex flex-col bg-base-200 rounded-lg p-4\"><span class=\"text-lg\">Regular Battles</span> <label class=\"label cursor-pointer\"><span class=\"label-text\">Show Overview Card</span> <input type=\"checkbox\" class=\"toggle toggle-secondary\" checked=\"checked\"></label> <label class=\"label cursor-pointer flex flex-col items-start gap-1\"><span class=\"label-text\">Vehicle Cards</span> <input type=\"range\" min=\"0\" max=\"10\" value=\"3\" class=\"range\" step=\"1\"><div class=\"flex w-full justify-between px-2 text-xs\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i := range 11 { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex flex-col items-center\"><span>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(i)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/app/widgets.templ`, Line: 84, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></label></div><div class=\"flex flex-col bg-base-200 rounded-lg p-4\"><span class=\"text-lg\">Rating Battles</span> <label class=\"label cursor-pointer\"><span class=\"label-text\">Show Overview Card</span> <input type=\"checkbox\" class=\"toggle toggle-secondary\" checked=\"checked\"></label></div></div><button class=\"btn btn-primary\">copy link</button></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/routes/errors.templ b/cmd/frontend/routes/errors.templ index c3d4db65..f299728e 100644 --- a/cmd/frontend/routes/errors.templ +++ b/cmd/frontend/routes/errors.templ @@ -1,13 +1,14 @@ package routes -import "github.com/cufee/aftermath/cmd/frontend/handler" -import "github.com/cufee/aftermath/cmd/frontend/layouts" -import "net/http" +import ( + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" +) var GenericError handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { message := ctx.Query("message") - ctx.SetStatus(http.StatusInternalServerError) + // ctx.SetStatus(http.StatusInternalServerError) return layouts.Main, errorPage(message), nil } @@ -21,7 +22,7 @@ templ errorPage(message string) { var ErrorNotFound handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { path := ctx.Path("pathname") - ctx.SetStatus(http.StatusNotFound) + // ctx.SetStatus(http.StatusNotFound) return layouts.Main, notFoundPage(path), nil } diff --git a/cmd/frontend/routes/errors_templ.go b/cmd/frontend/routes/errors_templ.go index c457a73c..e6a3617b 100644 --- a/cmd/frontend/routes/errors_templ.go +++ b/cmd/frontend/routes/errors_templ.go @@ -1,32 +1,36 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.696 +// templ: version: v0.2.731 package routes //lint:file-ignore SA4006 This context is only used if a nested component is present. import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" +import templruntime "github.com/a-h/templ/runtime" -import "github.com/cufee/aftermath/cmd/frontend/handler" -import "github.com/cufee/aftermath/cmd/frontend/layouts" -import "net/http" +import ( + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" +) var GenericError handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { message := ctx.Query("message") - ctx.SetStatus(http.StatusInternalServerError) + // ctx.SetStatus(http.StatusInternalServerError) return layouts.Main, errorPage(message), nil } func errorPage(message string) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var1 := templ.GetChildren(ctx) @@ -41,7 +45,7 @@ func errorPage(message string) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(message) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 17, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 18, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -51,9 +55,6 @@ func errorPage(message string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } @@ -61,16 +62,21 @@ func errorPage(message string) templ.Component { var ErrorNotFound handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { path := ctx.Path("pathname") - ctx.SetStatus(http.StatusNotFound) + // ctx.SetStatus(http.StatusNotFound) return layouts.Main, notFoundPage(path), nil } func notFoundPage(path string) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var3 := templ.GetChildren(ctx) @@ -85,7 +91,7 @@ func notFoundPage(path string) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(path) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 31, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 32, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -95,9 +101,6 @@ func notFoundPage(path string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } diff --git a/cmd/frontend/routes/index.templ b/cmd/frontend/routes/index.templ index 581aaa5b..b3822a69 100644 --- a/cmd/frontend/routes/index.templ +++ b/cmd/frontend/routes/index.templ @@ -1,8 +1,9 @@ package routes -import "github.com/cufee/aftermath/cmd/frontend/handler" -import "github.com/cufee/aftermath/cmd/frontend/layouts" -import "github.com/cufee/aftermath/cmd/frontend/components" +import ( + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" +) var Index handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { return layouts.Main, index(), nil @@ -10,27 +11,40 @@ var Index handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Compo templ index() { <div class="flex flex-col justify-center gap-8 m-auto" id="landing"> - <div class="flex items-center justify-center"> - @components.Logo("128") + <div class="flex flex-row flex-wrap"> + @card(cardImage("/assets/promo-join.jpg"), cardActionButton("Join Aftermath Official", "/join")) { + <p>Aftermath is making a comeback! We've revamped everything to be faster and more beautiful. Join our community server for the latest features and updates.</p> + } + @card(cardImage("/assets/promo-invite.jpg"), cardActionButton("Add Aftermath on Discord", "/invite")) { + <p>The new bot is here! Add it to your server for an easier time tracking sessions. It's that simple – give Aftermath a try on your favorite server!</p> + } </div> - <div class="grid gap-4 p-4 md:grid-cols-2"> - <div class="shadow-xl card bg-base-300"> - <figure><img src="/assets/promo-join.jpg" alt="Join Aftermath Official"/></figure> - <div class="card-body"> - <p>Aftermath is making a comeback! We've revamped everything to be faster and more beautiful. Join our community server for the latest features and updates.</p> - <div class="justify-center card-actions"> - <a href="/join" class="btn btn-primary">Join Aftermath Official</a> - </div> + </div> +} + +templ cardImage(path string) { + <img src={ path } class="max-h-24 md:max-h-48 object-cover w-full" alt="Join Aftermath Official"/> +} + +templ cardActionButton(label, path string) { + <a href={ templ.URL(path) } class="btn btn-primary">{ label }</a> +} + +templ card(image templ.Component, button templ.Component) { + <div class="grow md:basis-1/2 p-2"> + <div class="shadow-xl card bg-base-300 overflow-hidden h-full"> + if image != nil { + <div class="overflow-hidden"> + @image </div> - </div> - <div class="shadow-xl card bg-base-300"> - <figure><img src="/assets/promo-invite.jpg" alt="Invite Aftermath"/></figure> - <div class="card-body"> - <p>The new bot is here! Add it to your server for an easier time tracking sessions. It's that simple – give Aftermath a try on your favorite server!</p> + } + <div class="card-body gap-4"> + { children... } + if button != nil { <div class="justify-center card-actions"> - <a href="/invite" class="btn btn-primary">Invite Aftermath</a> + @button </div> - </div> + } </div> </div> </div> diff --git a/cmd/frontend/routes/index_templ.go b/cmd/frontend/routes/index_templ.go index 279553da..98fa1ccb 100644 --- a/cmd/frontend/routes/index_templ.go +++ b/cmd/frontend/routes/index_templ.go @@ -1,29 +1,33 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.696 +// templ: version: v0.2.731 package routes //lint:file-ignore SA4006 This context is only used if a nested component is present. import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" +import templruntime "github.com/a-h/templ/runtime" -import "github.com/cufee/aftermath/cmd/frontend/handler" -import "github.com/cufee/aftermath/cmd/frontend/layouts" -import "github.com/cufee/aftermath/cmd/frontend/components" +import ( + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" +) var Index handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { return layouts.Main, index(), nil } func index() templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var1 := templ.GetChildren(ctx) @@ -31,20 +35,210 @@ func index() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex flex-col justify-center gap-8 m-auto\" id=\"landing\"><div class=\"flex items-center justify-center\">") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex flex-col justify-center gap-8 m-auto\" id=\"landing\"><div class=\"flex flex-row flex-wrap\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = components.Logo("128").Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>Aftermath is making a comeback! We've revamped everything to be faster and more beautiful. Join our community server for the latest features and updates.</p>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = card(cardImage("/assets/promo-join.jpg"), cardActionButton("Join Aftermath Official", "/join")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>The new bot is here! Add it to your server for an easier time tracking sessions. It's that simple – give Aftermath a try on your favorite server!</p>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = card(cardImage("/assets/promo-invite.jpg"), cardActionButton("Add Aftermath on Discord", "/invite")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func cardImage(path string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img src=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(path) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/index.templ`, Line: 26, Col: 16} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"max-h-24 md:max-h-48 object-cover w-full\" alt=\"Join Aftermath Official\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func cardActionButton(label, path string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 templ.SafeURL = templ.URL(path) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"btn btn-primary\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"grid gap-4 p-4 md:grid-cols-2\"><div class=\"shadow-xl card bg-base-300\"><figure><img src=\"/assets/promo-join.jpg\" alt=\"Join Aftermath Official\"></figure><div class=\"card-body\"><p>Aftermath is making a comeback! We've revamped everything to be faster and more beautiful. Join our community server for the latest features and updates.</p><div class=\"justify-center card-actions\"><a href=\"/join\" class=\"btn btn-primary\">Join Aftermath Official</a></div></div></div><div class=\"shadow-xl card bg-base-300\"><figure><img src=\"/assets/promo-invite.jpg\" alt=\"Invite Aftermath\"></figure><div class=\"card-body\"><p>The new bot is here! Add it to your server for an easier time tracking sessions. It's that simple – give Aftermath a try on your favorite server!</p><div class=\"justify-center card-actions\"><a href=\"/invite\" class=\"btn btn-primary\">Invite Aftermath</a></div></div></div></div></div>") + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/index.templ`, Line: 30, Col: 60} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func card(image templ.Component, button templ.Component) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"grow md:basis-1/2 p-2\"><div class=\"shadow-xl card bg-base-300 overflow-hidden h-full\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if image != nil { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"overflow-hidden\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = image.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"card-body gap-4\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var9.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if button != nil { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"justify-center card-actions\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = button.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err } return templ_7745c5c3_Err }) diff --git a/cmd/frontend/routes/widget/configure.templ b/cmd/frontend/routes/widget/configure.templ index 7cc17bf4..d3b012c5 100644 --- a/cmd/frontend/routes/widget/configure.templ +++ b/cmd/frontend/routes/widget/configure.templ @@ -1,13 +1,16 @@ package widget -import "github.com/cufee/aftermath/cmd/frontend/handler" -import "github.com/cufee/aftermath/cmd/frontend/layouts" -import "github.com/cufee/aftermath/cmd/frontend/components/widget" -import "github.com/cufee/aftermath/internal/stats/client/v1" -import "golang.org/x/text/language" -import "time" -import "github.com/pkg/errors" -import "fmt" +import ( + "context" + "fmt" + "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/internal/stats/client/v1" + "github.com/pkg/errors" + "golang.org/x/text/language" + "time" +) var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { accountID := ctx.Path("accountId") diff --git a/cmd/frontend/routes/widget/configure_templ.go b/cmd/frontend/routes/widget/configure_templ.go index 755037b9..6331a92e 100644 --- a/cmd/frontend/routes/widget/configure_templ.go +++ b/cmd/frontend/routes/widget/configure_templ.go @@ -1,23 +1,24 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.696 +// templ: version: v0.2.731 package widget //lint:file-ignore SA4006 This context is only used if a nested component is present. import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" +import templruntime "github.com/a-h/templ/runtime" -import "github.com/cufee/aftermath/cmd/frontend/handler" -import "github.com/cufee/aftermath/cmd/frontend/layouts" -import "github.com/cufee/aftermath/cmd/frontend/components/widget" -import "github.com/cufee/aftermath/internal/stats/client/v1" -import "golang.org/x/text/language" -import "time" -import "github.com/pkg/errors" -import "fmt" +import ( + "context" + "fmt" + "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/internal/stats/client/v1" + "github.com/pkg/errors" + "golang.org/x/text/language" + "time" +) var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { accountID := ctx.Path("accountId") @@ -39,11 +40,16 @@ var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, t } func configureWidgetPage(widget templ.Component) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var1 := templ.GetChildren(ctx) @@ -63,7 +69,7 @@ func configureWidgetPage(widget templ.Component) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(i)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/widget/configure.templ`, Line: 48, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/widget/configure.templ`, Line: 51, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -86,9 +92,6 @@ func configureWidgetPage(widget templ.Component) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } diff --git a/cmd/frontend/routes/widget/live.templ b/cmd/frontend/routes/widget/live.templ index 31f52f8a..ef76ce7b 100644 --- a/cmd/frontend/routes/widget/live.templ +++ b/cmd/frontend/routes/widget/live.templ @@ -1,14 +1,44 @@ package widget -import "github.com/cufee/aftermath/cmd/frontend/handler" -import "github.com/pkg/errors" -import "golang.org/x/text/language" -import "time" -import "github.com/cufee/aftermath/cmd/frontend/layouts" -import "github.com/cufee/aftermath/cmd/frontend/components/widget" -import "github.com/cufee/aftermath/internal/database/models" -import "github.com/cufee/aftermath/internal/stats/client/v1" -import "slices" +import ( + "context" + "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/client/v1" + "github.com/pkg/errors" + "golang.org/x/text/language" + "slices" + "time" +) + +var PersonalLiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + userID := ctx.Path("userId") + if userID == "" { + return nil, nil, errors.New("invalid account id") + } + + account, err := ctx.Fetch().Account(ctx.Context, userID) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + + var opts = []client.RequestOption{client.WithWN8()} + if ref := ctx.Query("ref"); ref != "" { + opts = append(opts, client.WithReferenceID(ref)) + } + if t := ctx.Query("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { + opts = append(opts, client.WithType(models.SnapshotType(t))) + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), opts...) + if err != nil { + return nil, nil, err + } + + return layouts.Main, liveWidget(widget.Widget(account, cards, widget.WithAutoReload())), nil +} var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { accountID := ctx.Path("accountId") @@ -21,7 +51,7 @@ var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ. return nil, nil, errors.New("invalid account id") } - var opts = []client.RequestOption{client.WithWN8()} + var opts = []client.RequestOption{client.WithWN8()} if ref := ctx.Query("ref"); ref != "" { opts = append(opts, client.WithReferenceID(ref)) } diff --git a/cmd/frontend/routes/widget/live_templ.go b/cmd/frontend/routes/widget/live_templ.go index b176d1c8..0e1ebfb9 100644 --- a/cmd/frontend/routes/widget/live_templ.go +++ b/cmd/frontend/routes/widget/live_templ.go @@ -1,24 +1,52 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.696 +// templ: version: v0.2.731 package widget //lint:file-ignore SA4006 This context is only used if a nested component is present. import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" +import templruntime "github.com/a-h/templ/runtime" -import "github.com/cufee/aftermath/cmd/frontend/handler" -import "github.com/pkg/errors" -import "golang.org/x/text/language" -import "time" -import "github.com/cufee/aftermath/cmd/frontend/layouts" -import "github.com/cufee/aftermath/cmd/frontend/components/widget" -import "github.com/cufee/aftermath/internal/database/models" -import "github.com/cufee/aftermath/internal/stats/client/v1" -import "slices" +import ( + "context" + "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/client/v1" + "github.com/pkg/errors" + "golang.org/x/text/language" + "slices" + "time" +) + +var PersonalLiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + userID := ctx.Path("userId") + if userID == "" { + return nil, nil, errors.New("invalid account id") + } + + account, err := ctx.Fetch().Account(ctx.Context, userID) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + + var opts = []client.RequestOption{client.WithWN8()} + if ref := ctx.Query("ref"); ref != "" { + opts = append(opts, client.WithReferenceID(ref)) + } + if t := ctx.Query("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { + opts = append(opts, client.WithType(models.SnapshotType(t))) + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), opts...) + if err != nil { + return nil, nil, err + } + + return layouts.Main, liveWidget(widget.Widget(account, cards, widget.WithAutoReload())), nil +} var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { accountID := ctx.Path("accountId") @@ -48,11 +76,16 @@ var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ. } func liveWidget(widget templ.Component) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Var1 := templ.GetChildren(ctx) @@ -68,9 +101,6 @@ func liveWidget(widget templ.Component) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } return templ_7745c5c3_Err }) } diff --git a/cmd/frontend/routes/widget/personal.templ b/cmd/frontend/routes/widget/personal.templ new file mode 100644 index 00000000..7817451a --- /dev/null +++ b/cmd/frontend/routes/widget/personal.templ @@ -0,0 +1,76 @@ +package widget + +import ( + "context" + "fmt" + "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/internal/stats/client/v1" + "github.com/pkg/errors" + "golang.org/x/text/language" + "time" +) + +var PersonalWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + userID := ctx.Path("userId") + if userID == "" { + return nil, nil, errors.New("invalid user id id") + } + + account, err := ctx.Fetch().Account(ctx.Context, userID) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), client.WithWN8()) + if err != nil { + return nil, nil, err + } + + return layouts.Main, personalWidgetPage(widget.Widget(account, cards)), nil +} + +templ personalWidgetPage(widget templ.Component) { + <div class="flex flex-row justify-center gap-4 flex-wrap min-w-max"> + <div class="grow flex flex-col max-w-xl gap-2"> + <div class="flex flex-col items-center justify-center gap-4"> + <div class="form-control w-full flex gap-2"> + <div class="flex flex-col bg-base-200 rounded-lg p-4"> + <span class="text-lg">Regular Battles</span> + <label class="label cursor-pointer"> + <span class="label-text">Show Overview Card</span> + <input type="checkbox" class="toggle toggle-secondary" checked="checked" disabled/> + </label> + <label class="label cursor-pointer flex flex-col items-start gap-1"> + <span class="label-text">Vehicle Cards</span> + <input type="range" min="0" max="10" value="3" class="range" step="1" disabled/> + <div class="flex w-full justify-between px-2 text-xs"> + for i := range 11 { + <div class="flex flex-col items-center"> + <span>{ fmt.Sprint(i) }</span> + </div> + } + </div> + </label> + </div> + <div class="flex flex-col bg-base-200 rounded-lg p-4"> + <span class="text-lg">Rating Battles</span> + <label class="label cursor-pointer"> + <span class="label-text">Show Overview Card</span> + <input type="checkbox" class="toggle toggle-secondary" checked="checked" disabled/> + </label> + </div> + </div> + <button class="btn btn-primary"> + copy link + </button> + </div> + </div> + <div class="flex flex-col gap-2"> + <div class="max-m-max"> + @widget + </div> + </div> + </div> +} diff --git a/cmd/frontend/routes/widget/personal_templ.go b/cmd/frontend/routes/widget/personal_templ.go new file mode 100644 index 00000000..935f05ed --- /dev/null +++ b/cmd/frontend/routes/widget/personal_templ.go @@ -0,0 +1,97 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package widget + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "context" + "fmt" + "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/internal/stats/client/v1" + "github.com/pkg/errors" + "golang.org/x/text/language" + "time" +) + +var PersonalWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + userID := ctx.Path("userId") + if userID == "" { + return nil, nil, errors.New("invalid user id id") + } + + account, err := ctx.Fetch().Account(ctx.Context, userID) + if err != nil { + return nil, nil, errors.New("invalid account id") + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), client.WithWN8()) + if err != nil { + return nil, nil, err + } + + return layouts.Main, personalWidgetPage(widget.Widget(account, cards)), nil +} + +func personalWidgetPage(widget templ.Component) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex flex-row justify-center gap-4 flex-wrap min-w-max\"><div class=\"grow flex flex-col max-w-xl gap-2\"><div class=\"flex flex-col items-center justify-center gap-4\"><div class=\"form-control w-full flex gap-2\"><div class=\"flex flex-col bg-base-200 rounded-lg p-4\"><span class=\"text-lg\">Regular Battles</span> <label class=\"label cursor-pointer\"><span class=\"label-text\">Show Overview Card</span> <input type=\"checkbox\" class=\"toggle toggle-secondary\" checked=\"checked\" disabled></label> <label class=\"label cursor-pointer flex flex-col items-start gap-1\"><span class=\"label-text\">Vehicle Cards</span> <input type=\"range\" min=\"0\" max=\"10\" value=\"3\" class=\"range\" step=\"1\" disabled><div class=\"flex w-full justify-between px-2 text-xs\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i := range 11 { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex flex-col items-center\"><span>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(i)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/widget/personal.templ`, Line: 51, Col: 31} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></label></div><div class=\"flex flex-col bg-base-200 rounded-lg p-4\"><span class=\"text-lg\">Rating Battles</span> <label class=\"label cursor-pointer\"><span class=\"label-text\">Show Overview Card</span> <input type=\"checkbox\" class=\"toggle toggle-secondary\" checked=\"checked\" disabled></label></div></div><button class=\"btn btn-primary\">copy link</button></div></div><div class=\"flex flex-col gap-2\"><div class=\"max-m-max\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = widget.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/internal/database/models/user.go b/internal/database/models/user.go index e0c8b1fc..4e271ee2 100644 --- a/internal/database/models/user.go +++ b/internal/database/models/user.go @@ -35,6 +35,16 @@ func (u User) HasPermission(value permissions.Permissions) bool { } func (u User) Connection(kind ConnectionType, conditions map[string]any) (UserConnection, bool) { + valid, ok := u.FilterConnections(kind, conditions) + if !ok { + return UserConnection{}, false + } + return valid[0], true +} + +func (u User) FilterConnections(kind ConnectionType, conditions map[string]any) ([]UserConnection, bool) { + var valid []UserConnection + outerLoop: for _, connection := range u.Connections { if connection.Type == kind { @@ -43,26 +53,44 @@ outerLoop: continue outerLoop } } - return connection, true + valid = append(valid, connection) } } - return UserConnection{}, false + + return valid, len(valid) > 0 } func (u User) Subscription(kind SubscriptionType) (UserSubscription, bool) { + valid, ok := u.FilterSubscriptions(kind) + if !ok { + return UserSubscription{}, false + } + return valid[0], true +} + +func (u User) FilterSubscriptions(kind SubscriptionType) ([]UserSubscription, bool) { + var valid []UserSubscription for _, subscription := range u.Subscriptions { if subscription.Type == kind { - return subscription, true + valid = append(valid, subscription) } } - return UserSubscription{}, false + return valid, len(valid) > 0 } func (u User) Content(kind UserContentType) (UserContent, bool) { + valid, ok := u.FilterContent(kind) + if !ok { + return UserContent{}, false + } + return valid[0], true +} +func (u User) FilterContent(kind UserContentType) ([]UserContent, bool) { + var valid []UserContent for _, content := range u.Uploads { if content.Type == kind { - return content, true + valid = append(valid, content) } } - return UserContent{}, false + return valid, len(valid) > 0 } diff --git a/internal/database/models/widget_options.go b/internal/database/models/widget_options.go new file mode 100644 index 00000000..be34cb8d --- /dev/null +++ b/internal/database/models/widget_options.go @@ -0,0 +1,28 @@ +package models + +import "time" + +type WidgetOptions struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + + UserID string + AccountID string + + Style WidgetStyling +} + +type WidgetStyling struct { + Flavor string + UnratedOverview WidgetCardStyle + RatingOverview WidgetCardStyle + Vehicles WidgetCardStyle +} + +type WidgetCardStyle struct { + ShowTitle bool + ShowCareer bool + ShowLabel bool + Blocks []string +} diff --git a/tests/static_data.go b/tests/static_data.go index 93dcc80c..1ac8f29a 100644 --- a/tests/static_data.go +++ b/tests/static_data.go @@ -1,6 +1,7 @@ package tests import ( + "fmt" "time" "github.com/cufee/aftermath/internal/database/models" @@ -113,3 +114,15 @@ func DefaultVehicleStatsFrameSmall2(id string) frame.VehicleStatsFrame { StatsFrame: &f, } } + +var ( + DefaultUserWithEdges = models.User{ID: "user1", Connections: []models.UserConnection{Connection(models.ConnectionTypeWargaming)}} +) + +func Connection(kind models.ConnectionType) models.UserConnection { + return models.UserConnection{ + ID: fmt.Sprint(time.Now().Nanosecond()), + ReferenceID: DefaultAccountNA, + Type: kind, + } +} diff --git a/tests/static_database.go b/tests/static_database.go index d2abba2c..44689e87 100644 --- a/tests/static_database.go +++ b/tests/static_database.go @@ -78,13 +78,18 @@ func (c *staticTestingDatabase) UpsertVehicleAverages(ctx context.Context, avera } func (c *staticTestingDatabase) GetUserByID(ctx context.Context, id string, opts ...database.UserGetOption) (models.User, error) { - return models.User{}, errors.New("GetUserByID not implemented") + return DefaultUserWithEdges, nil } func (c *staticTestingDatabase) GetOrCreateUserByID(ctx context.Context, id string, opts ...database.UserGetOption) (models.User, error) { - return models.User{}, errors.New("GetOrCreateUserByID not implemented") + return c.GetUserByID(ctx, id) } func (c *staticTestingDatabase) UpsertUserWithPermissions(ctx context.Context, userID string, perms permissions.Permissions) (models.User, error) { - return models.User{}, errors.New("UpsertUserWithPermissions not implemented") + u, err := c.GetUserByID(ctx, userID) + if err != nil { + return u, err + } + u.Permissions = perms + return u, nil } func (c *staticTestingDatabase) UpdateConnection(ctx context.Context, connection models.UserConnection) (models.UserConnection, error) { return models.UserConnection{}, errors.New("UpdateConnection not implemented") @@ -171,24 +176,24 @@ func (c *staticTestingDatabase) DeleteExpiredInteractions(ctx context.Context, e } func (c *staticTestingDatabase) CreateAuthNonce(ctx context.Context, publicID, identifier string, expiresAt time.Time, meta map[string]string) (models.AuthNonce, error) { - return models.AuthNonce{}, errors.New("CreateAuthNonce not implemented") + return models.AuthNonce{ID: "nonce1", Active: true, PublicID: "nonce1", Identifier: "ident1", ExpiresAt: time.Date(9999, 0, 0, 0, 0, 0, 0, time.UTC)}, nil } func (c *staticTestingDatabase) FindAuthNonce(ctx context.Context, publicID string) (models.AuthNonce, error) { - return models.AuthNonce{}, errors.New("FindAuthNonce not implemented") + return models.AuthNonce{ID: "nonce1", Active: true, PublicID: "nonce1", Identifier: "ident1", ExpiresAt: time.Date(9999, 0, 0, 0, 0, 0, 0, time.UTC)}, nil } func (c *staticTestingDatabase) SetAuthNonceActive(ctx context.Context, nonceID string, active bool) error { - return errors.New("SetAuthNonceActive not implemented") + return nil } func (c *staticTestingDatabase) CreateSession(ctx context.Context, publicID, userID string, expiresAt time.Time, meta map[string]string) (models.Session, error) { - return models.Session{}, errors.New("CreateSession not implementer") + return models.Session{ID: "session1", UserID: "user1", PublicID: "cookie1", ExpiresAt: time.Date(9999, 0, 0, 0, 0, 0, 0, time.UTC)}, nil } func (c *staticTestingDatabase) SetSessionExpiresAt(ctx context.Context, sessionID string, expiresAt time.Time) error { - return errors.New("SetSessionExpiresAt not implementer") + return nil } func (c *staticTestingDatabase) FindSession(ctx context.Context, publicID string) (models.Session, error) { - return models.Session{}, errors.New("FindSession not implementer") + return models.Session{ID: "session1", UserID: "user1", PublicID: "cookie1", ExpiresAt: time.Date(9999, 0, 0, 0, 0, 0, 0, time.UTC)}, nil } func (c *staticTestingDatabase) UserFromSession(ctx context.Context, publicID string) (models.User, error) { - return models.User{}, errors.New("UserFromSession not implementer") + return models.User{ID: "user1"}, nil } From 1aec05e211036afc213f6c13ac56c50cc58942de Mon Sep 17 00:00:00 2001 From: Vovko <vkouzin@outlook.com> Date: Thu, 4 Jul 2024 14:18:39 -0400 Subject: [PATCH 248/341] added gomemlimit, ui work --- .air.toml | 1 + Dockerfile | 4 +- cmd/core/queue/queue.go | 186 +++++++++++----------- cmd/frontend/components/navbar.templ | 28 +++- cmd/frontend/components/navbar_templ.go | 8 +- cmd/frontend/components/obs.templ | 71 +++++++++ cmd/frontend/components/obs_templ.go | 133 ++++++++++++++++ cmd/frontend/handlers.go | 4 + cmd/frontend/layouts/main.templ | 6 +- cmd/frontend/layouts/main_templ.go | 6 +- cmd/frontend/public/widget-background.jpg | Bin 0 -> 8506 bytes cmd/frontend/routes/api/widget/mock.go | 11 ++ cmd/frontend/routes/errors.templ | 38 +++-- cmd/frontend/routes/errors_templ.go | 120 ++++++++++---- cmd/frontend/routes/widget/index.templ | 35 ++++ cmd/frontend/routes/widget/index_templ.go | 72 +++++++++ docker-compose.yaml | 2 + 17 files changed, 577 insertions(+), 148 deletions(-) create mode 100644 cmd/frontend/components/obs.templ create mode 100644 cmd/frontend/components/obs_templ.go create mode 100644 cmd/frontend/public/widget-background.jpg create mode 100644 cmd/frontend/routes/api/widget/mock.go create mode 100644 cmd/frontend/routes/widget/index.templ create mode 100644 cmd/frontend/routes/widget/index_templ.go diff --git a/.air.toml b/.air.toml index 86f040d7..57af028c 100644 --- a/.air.toml +++ b/.air.toml @@ -2,6 +2,7 @@ include_ext = ["go", "tmpl", "templ", "html"] exclude_regex = [".*_templ.go"] pre_cmd = ["templ generate"] +cmd = "GODEBUG=gctrace=1 go build -o ./tmp/main ." send_interrupt = true stop_on_error = true diff --git a/Dockerfile b/Dockerfile index 0148d3d6..9b53de01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ FROM golang:1.22.3-bookworm as builder +ARG GO_MEM_LIMIT=122MiB + WORKDIR /workspace COPY go.mod go.sum ./ @@ -9,7 +11,7 @@ COPY ./ ./ # build a fully standalone binary with zero dependencies RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate main.go -RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux go build -o /bin/aftermath . +RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=1 GOOS=linux GOMEMLIMIT=$GO_MEM_LIMIT go build -o /bin/aftermath . # Make a scratch container with required files and binary FROM debian:stable-slim diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index 499a4e34..ebbbc6e2 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -151,107 +151,109 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { for { select { case task := <-q.queued: - go func() { - q.workerLimiter <- struct{}{} + q.workerLimiter <- struct{}{} + q.activeTasksMx.Lock() + q.activeTasks[task.ID] = &struct{}{} + q.activeTasksMx.Unlock() + defer func() { + <-q.workerLimiter q.activeTasksMx.Lock() - q.activeTasks[task.ID] = &struct{}{} + delete(q.activeTasks, task.ID) q.activeTasksMx.Unlock() - defer func() { - <-q.workerLimiter - q.activeTasksMx.Lock() - delete(q.activeTasks, task.ID) - q.activeTasksMx.Unlock() - if onComplete != nil { - onComplete(task.ID) - } - }() - - log.Debug().Str("taskId", task.ID).Msg("worker started processing a task") - defer log.Debug().Str("taskId", task.ID).Msg("worker finished processing a task") - - defer func() { - if r := recover(); r != nil { - event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) - defer event.Msg("panic in queue worker") - - coreClient, err := q.newCoreClient() - if err != nil { - event.AnErr("core", err).Str("additional", "failed to create a core client") - return - } - task.Status = models.TaskStatusFailed - task.LogAttempt(models.TaskLog{ - Timestamp: time.Now(), - Comment: "task caused a panic in worker handler", - }) - - uctx, cancel := context.WithTimeout(ctx, q.workerTimeout) - defer cancel() - - err = coreClient.Database().UpdateTasks(uctx, task) - if err != nil { - event.AnErr("updateTasks", err).Str("additional", "failed to update a task") - } - } - }() - - coreClient, err := q.newCoreClient() - if err != nil { - log.Err(err).Msg("failed to create a new core client for a task worker") - return + if onComplete != nil { + onComplete(task.ID) } + }() - task.TriesLeft -= 1 - handler, ok := q.handlers[task.Type] - if !ok { - task.Status = models.TaskStatusFailed - task.LogAttempt(models.TaskLog{ - Error: "task missing a handler", - Comment: "task missing a handler", - Timestamp: time.Now(), - }) - - uctx, cancel := context.WithTimeout(ctx, q.workerTimeout) - defer cancel() - - err := coreClient.Database().UpdateTasks(uctx, task) - if err != nil { - log.Err(err).Msg("failed to update a task") - } - return - } + coreClient, err := q.newCoreClient() + if err != nil { + log.Err(err).Msg("failed to create a new core client for a task worker") + return + } + go q.processTask(coreClient, task) - wctx, cancel := context.WithTimeout(ctx, q.workerTimeout) - defer cancel() + case <-ctx.Done(): + return + } + } +} - err = handler.Process(wctx, coreClient, &task) - task.Status = models.TaskStatusComplete - l := models.TaskLog{ - Timestamp: time.Now(), - Comment: "task handler finished", - } - if err != nil { - l.Error = err.Error() - if task.TriesLeft > 0 { - task.Status = models.TaskStatusScheduled - task.ScheduledAfter = time.Now().Add(time.Minute * 5) - } else { - task.Status = models.TaskStatusFailed - } - } - task.LogAttempt(l) +func (q *queue) processTask(coreClient core.Client, task models.Task) { + log.Debug().Str("taskId", task.ID).Msg("worker started processing a task") + defer log.Debug().Str("taskId", task.ID).Msg("worker finished processing a task") - uctx, cancel := context.WithTimeout(ctx, q.workerTimeout) - defer cancel() + defer func() { + if r := recover(); r != nil { + event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) + defer event.Msg("panic in queue worker") - err = coreClient.Database().UpdateTasks(uctx, task) - if err != nil { - log.Err(err).Msg("failed to update a task") - } - }() + coreClient, err := q.newCoreClient() + if err != nil { + event.AnErr("core", err).Str("additional", "failed to create a core client") + return + } + task.Status = models.TaskStatusFailed + task.LogAttempt(models.TaskLog{ + Timestamp: time.Now(), + Comment: "task caused a panic in worker handler", + }) + + uctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + err = coreClient.Database().UpdateTasks(uctx, task) + if err != nil { + event.AnErr("updateTasks", err).Str("additional", "failed to update a task") + } + } + }() + + task.TriesLeft -= 1 + handler, ok := q.handlers[task.Type] + if !ok { + task.Status = models.TaskStatusFailed + task.LogAttempt(models.TaskLog{ + Error: "task missing a handler", + Comment: "task missing a handler", + Timestamp: time.Now(), + }) + + uctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() - case <-ctx.Done(): - return + err := coreClient.Database().UpdateTasks(uctx, task) + if err != nil { + log.Err(err).Msg("failed to update a task") } + return } + + wctx, cancel := context.WithTimeout(context.Background(), q.workerTimeout) + defer cancel() + + err := handler.Process(wctx, coreClient, &task) + task.Status = models.TaskStatusComplete + l := models.TaskLog{ + Timestamp: time.Now(), + Comment: "task handler finished", + } + if err != nil { + l.Error = err.Error() + if task.TriesLeft > 0 { + task.Status = models.TaskStatusScheduled + task.ScheduledAfter = time.Now().Add(time.Minute * 5) + } else { + task.Status = models.TaskStatusFailed + } + } + task.LogAttempt(l) + + uctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + err = coreClient.Database().UpdateTasks(uctx, task) + if err != nil { + log.Err(err).Msg("failed to update a task") + } + } diff --git a/cmd/frontend/components/navbar.templ b/cmd/frontend/components/navbar.templ index 09b4fa99..b3145190 100644 --- a/cmd/frontend/components/navbar.templ +++ b/cmd/frontend/components/navbar.templ @@ -31,20 +31,36 @@ templ navbar(props navbarProps) { <div class="no-animation" hx-boost="true"> <div class="navbar bg-base-100 gap-1"> <div class="navbar-start gap-1"> - @Logo("32") - <div class="flex-1"> - <a class="btn btn-ghost text-xl" href="/">Aftermath</a> - </div> + <a href="/" class="px-2"> + @Logo("32") + </a> </div> <div class="navbar-center"> <div class="form-control"> - <input type="text" placeholder="Search Players" class="input input-bordered w-24 md:w-auto"/> + <input type="text" placeholder="Search Players" class="input input-bordered w-48 md:w-auto placeholder:text-center"/> </div> </div> - <ul class="navbar-end menu menu-horizontal px-1 gap-1"> + <ul class="navbar-end menu menu-horizontal px-1 gap-1 hidden md:flex"> <li><a href="/widget" class={ logic.StringIfElse("active", "", props.path == "/widget") }>Widget</a></li> <li><a href="/login" class={ logic.StringIfElse("btn active", "btn", props.path == "/login") }>Login</a></li> </ul> + <ul class="navbar-end menu menu-horizontal px-1 gap-1 md:hidden"> + <button class="btn btn-square btn-ghost"> + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + class="inline-block h-5 w-5 stroke-current" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M4 6h16M4 12h16M4 18h16" + ></path> + </svg> + </button> + </ul> </div> </div> } diff --git a/cmd/frontend/components/navbar_templ.go b/cmd/frontend/components/navbar_templ.go index 2d384ef6..66d0cbd5 100644 --- a/cmd/frontend/components/navbar_templ.go +++ b/cmd/frontend/components/navbar_templ.go @@ -53,7 +53,7 @@ func navbar(props navbarProps) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"no-animation\" hx-boost=\"true\"><div class=\"navbar bg-base-100 gap-1\"><div class=\"navbar-start gap-1\">") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"no-animation\" hx-boost=\"true\"><div class=\"navbar bg-base-100 gap-1\"><div class=\"navbar-start gap-1\"><a href=\"/\" class=\"px-2\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -61,7 +61,7 @@ func navbar(props navbarProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex-1\"><a class=\"btn btn-ghost text-xl\" href=\"/\">Aftermath</a></div></div><div class=\"navbar-center\"><div class=\"form-control\"><input type=\"text\" placeholder=\"Search Players\" class=\"input input-bordered w-24 md:w-auto\"></div></div><ul class=\"navbar-end menu menu-horizontal px-1 gap-1\"><li>") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"navbar-center\"><div class=\"form-control\"><input type=\"text\" placeholder=\"Search Players\" class=\"input input-bordered w-48 md:w-auto placeholder:text-center\"></div></div><ul class=\"navbar-end menu menu-horizontal px-1 gap-1 hidden md:flex\"><li>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -105,7 +105,7 @@ func navbar(props navbarProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Login</a></li></ul></div></div>") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Login</a></li></ul><ul class=\"navbar-end menu menu-horizontal px-1 gap-1 md:hidden\"><button class=\"btn btn-square btn-ghost\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" class=\"inline-block h-5 w-5 stroke-current\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 12h16M4 18h16\"></path></svg></button></ul></div></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -165,7 +165,7 @@ func navMenuLink(label, href, currentPath string) templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 53, Col: 78} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/navbar.templ`, Line: 69, Col: 78} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { diff --git a/cmd/frontend/components/obs.templ b/cmd/frontend/components/obs.templ new file mode 100644 index 00000000..b124f529 --- /dev/null +++ b/cmd/frontend/components/obs.templ @@ -0,0 +1,71 @@ +package components + +templ OBSMockup(backgroundURL string) { + <div class="mockup-window bg-base-300 rounded-xl overflow-hidden w-full"> + <div class="flex flex-col p-4 pt-0 gap-2"> + <div class="grow relative aspect-video rounded-md overflow-hidden"> + if backgroundURL == "" { + <div class="absolute w-1/2 h-full bg-gray-300 left-0 top-0 z-10"></div> + <div class="absolute w-1/2 h-full bg-gray-600 right-0 top-0 z-0"></div> + } else { + <img src={ backgroundURL } class="object-cover absolute laft-0 top-0 z-0 w-full h-full"/> + } + <div class="relative z-10 p-2 flex items-center justify-center h-full"> + { children... } + </div> + </div> + <div class="grow flex flex-row gap-1 text-xs"> + <div class="grow flex flex-col gap-1 bg-base-200 p-2 rounded-md"> + <div class="bg-gray-700 rounded-full px-2 py-1 whitespace-nowrap"> + ❤︎ Aftermath + </div> + @buttonMockup() + @buttonMockup() + </div> + <div class="grow flex flex-col gap-1 bg-base-200 p-2 rounded-md"> + <div class="bg-gray-800 rounded-full px-2 py-1"> + <span class="line-clamp-1 break-all whitespace-normal"> + amth.one/widget + </span> + </div> + @buttonMockup() + @buttonMockup() + </div> + <div class="grow flex flex-col gap-2 bg-base-200 p-2 rounded-md"> + <div class="flex flex-col gap-1"> + <div class="h-1 min-w-12 w-full bg-gray-700 rounded-full"> + <div class="w-2/3 h-1 rounded-full bg-green-600"></div> + </div> + <div class="h-1 flex flex-row gap-1"> + <div class="grow rounded-full bg-blue-600"></div> + <div class="w-1 h-1 rounded-full bg-red-600"></div> + </div> + </div> + <div class="flex flex-col gap-1"> + <div class="h-1 min-w-12 w-full bg-gray-700 rounded-full"> + <div class="w-1/4 h-1 rounded-full bg-green-600"></div> + </div> + <div class="h-1 flex flex-row gap-1"> + <div class="grow rounded-full bg-blue-600"></div> + <div class="w-1 h-1 rounded-full bg-red-600"></div> + </div> + </div> + </div> + <div class="grow hidden sm:flex flex-col gap-1 bg-base-200 p-2 rounded-md text-center max-w-24"> + <div class="h-4 min-w-12 w-full bg-gray-800 rounded-full flex flex-row gap-1 items-center px-1"> + <div class="h-2 w-2 rounded-full bg-red-500"></div> + <div class="h-1 min-w-8 grow bg-gray-500 rounded-full"></div> + </div> + @buttonMockup() + @buttonMockup() + </div> + </div> + </div> + </div> +} + +templ buttonMockup() { + <div class="min-w-12 w-full bg-gray-800 rounded-full flex items-center p-1"> + <div class="h-1 min-w-8 grow bg-gray-500 rounded-full"></div> + </div> +} diff --git a/cmd/frontend/components/obs_templ.go b/cmd/frontend/components/obs_templ.go new file mode 100644 index 00000000..d3ee9522 --- /dev/null +++ b/cmd/frontend/components/obs_templ.go @@ -0,0 +1,133 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func OBSMockup(backgroundURL string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"mockup-window bg-base-300 rounded-xl overflow-hidden w-full\"><div class=\"flex flex-col p-4 pt-0 gap-2\"><div class=\"grow relative aspect-video rounded-md overflow-hidden\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if backgroundURL == "" { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"absolute w-1/2 h-full bg-gray-300 left-0 top-0 z-10\"></div><div class=\"absolute w-1/2 h-full bg-gray-600 right-0 top-0 z-0\"></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img src=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(backgroundURL) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/obs.templ`, Line: 11, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"object-cover absolute laft-0 top-0 z-0 w-full h-full\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"relative z-10 p-2 flex items-center justify-center h-full\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"grow flex flex-row gap-1 text-xs\"><div class=\"grow flex flex-col gap-1 bg-base-200 p-2 rounded-md\"><div class=\"bg-gray-700 rounded-full px-2 py-1 whitespace-nowrap\">❤︎ Aftermath</div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = buttonMockup().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = buttonMockup().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"grow flex flex-col gap-1 bg-base-200 p-2 rounded-md\"><div class=\"bg-gray-800 rounded-full px-2 py-1\"><span class=\"line-clamp-1 break-all whitespace-normal\">amth.one/widget</span></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = buttonMockup().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = buttonMockup().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"grow flex flex-col gap-2 bg-base-200 p-2 rounded-md\"><div class=\"flex flex-col gap-1\"><div class=\"h-1 min-w-12 w-full bg-gray-700 rounded-full\"><div class=\"w-2/3 h-1 rounded-full bg-green-600\"></div></div><div class=\"h-1 flex flex-row gap-1\"><div class=\"grow rounded-full bg-blue-600\"></div><div class=\"w-1 h-1 rounded-full bg-red-600\"></div></div></div><div class=\"flex flex-col gap-1\"><div class=\"h-1 min-w-12 w-full bg-gray-700 rounded-full\"><div class=\"w-1/4 h-1 rounded-full bg-green-600\"></div></div><div class=\"h-1 flex flex-row gap-1\"><div class=\"grow rounded-full bg-blue-600\"></div><div class=\"w-1 h-1 rounded-full bg-red-600\"></div></div></div></div><div class=\"grow hidden sm:flex flex-col gap-1 bg-base-200 p-2 rounded-md text-center max-w-24\"><div class=\"h-4 min-w-12 w-full bg-gray-800 rounded-full flex flex-row gap-1 items-center px-1\"><div class=\"h-2 w-2 rounded-full bg-red-500\"></div><div class=\"h-1 min-w-8 grow bg-gray-500 rounded-full\"></div></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = buttonMockup().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = buttonMockup().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></div></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func buttonMockup() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"min-w-12 w-full bg-gray-800 rounded-full flex items-center p-1\"><div class=\"h-1 min-w-8 grow bg-gray-500 rounded-full\"></div></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/handlers.go b/cmd/frontend/handlers.go index 62dfec16..eb3c3d09 100644 --- a/cmd/frontend/handlers.go +++ b/cmd/frontend/handlers.go @@ -59,6 +59,10 @@ func Handlers(core core.Client) ([]server.Handler, error) { Func: handler.Chain(core, routes.Login), }, // widget + { + Path: get("/widget"), + Func: handler.Chain(core, widget.WidgetHome), + }, { Path: get("/widget/{accountId}"), Func: handler.Chain(core, widget.ConfigureWidget), diff --git a/cmd/frontend/layouts/main.templ b/cmd/frontend/layouts/main.templ index 252ea782..dc9b6a2f 100644 --- a/cmd/frontend/layouts/main.templ +++ b/cmd/frontend/layouts/main.templ @@ -33,9 +33,11 @@ templ main(navbar templ.Component, children ...templ.Component) { @logic.EmbedMinifiedScript(tailwindConfig()) <title>{ appName } - -
    + +
    @navbar +
    +
    for _, render := range children { @render } diff --git a/cmd/frontend/layouts/main_templ.go b/cmd/frontend/layouts/main_templ.go index f38cdafb..ec95d7dc 100644 --- a/cmd/frontend/layouts/main_templ.go +++ b/cmd/frontend/layouts/main_templ.go @@ -65,7 +65,7 @@ func main(navbar templ.Component, children ...templ.Component) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -73,6 +73,10 @@ func main(navbar templ.Component, children ...templ.Component) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } for _, render := range children { templ_7745c5c3_Err = render.Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { diff --git a/cmd/frontend/public/widget-background.jpg b/cmd/frontend/public/widget-background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3c9324e1140516813d8db897e3d44121c31bdd4d GIT binary patch literal 8506 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 - Something did not work - { message } -
    +templ genericErrorPage(message string) { + @errorPage("500", message) { + Something unexpected happened while working on your request. You can reach out to our team on Aftermath Official to report this issue. + } } var ErrorNotFound handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - path := ctx.Path("pathname") + message := "/" + ctx.Path("pathname") + " does not exist" + return layouts.Main, notFoundPage(message), nil +} - // ctx.SetStatus(http.StatusNotFound) - return layouts.Main, notFoundPage(path), nil +templ notFoundPage(message string) { + @errorPage("404", message) { + Sorry, we couldn't find this page. But don't worry, you can find plenty of other things on our homepage. + } } -templ notFoundPage(path string) { -
    - 404 - Not Found - /{ path } +templ errorPage(title string, context string) { +
    +
    + { title } + + { children... } + +
    + + { context } +
    } diff --git a/cmd/frontend/routes/errors_templ.go b/cmd/frontend/routes/errors_templ.go index e6a3617b..e2c174e5 100644 --- a/cmd/frontend/routes/errors_templ.go +++ b/cmd/frontend/routes/errors_templ.go @@ -15,12 +15,10 @@ import ( var GenericError handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { message := ctx.Query("message") - - // ctx.SetStatus(http.StatusInternalServerError) - return layouts.Main, errorPage(message), nil + return layouts.Main, genericErrorPage(message), nil } -func errorPage(message string) templ.Component { +func genericErrorPage(message string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) @@ -38,20 +36,25 @@ func errorPage(message string) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Something did not work ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(message) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 18, Col: 17} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) - if templ_7745c5c3_Err != nil { + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Something unexpected happened while working on your request. You can reach out to our team on Aftermath Official to report this issue.") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + }) + templ_7745c5c3_Err = errorPage("500", message).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -60,13 +63,11 @@ func errorPage(message string) templ.Component { } var ErrorNotFound handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - path := ctx.Path("pathname") - - // ctx.SetStatus(http.StatusNotFound) - return layouts.Main, notFoundPage(path), nil + message := "/" + ctx.Path("pathname") + " does not exist" + return layouts.Main, notFoundPage(message), nil } -func notFoundPage(path string) templ.Component { +func notFoundPage(message string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) @@ -84,16 +85,81 @@ func notFoundPage(path string) templ.Component { templ_7745c5c3_Var3 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    404 - Not Found /") + templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Sorry, we couldn't find this page. But don't worry, you can find plenty of other things on our homepage.") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = errorPage("404", message).Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func errorPage(title string, context string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 33, Col: 43} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var5.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(path) + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(context) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 32, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/routes/errors.templ`, Line: 39, Col: 12} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/cmd/frontend/routes/widget/index.templ b/cmd/frontend/routes/widget/index.templ new file mode 100644 index 00000000..2180b095 --- /dev/null +++ b/cmd/frontend/routes/widget/index.templ @@ -0,0 +1,35 @@ +package widget + +import ( + "github.com/cufee/aftermath/cmd/frontend/components" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" +) + +var WidgetHome handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + + return layouts.Main, widgetHome(), nil +} + +templ widgetHome() { +
    +
    +
    + Amet cillum fugiat ut eu enim dolor nulla minim. Officia veniam esse exercitation elit consequat voluptate cillum ex. Ut aute nulla ipsum in sint enim et amet nisi irure. Nulla tempor enim ut reprehenderit excepteur ad aute eu fugiat cupidatat et eu magna officia. Aute aute quis ea anim. Ipsum aliqua officia qui id dolore ullamco ipsum ipsum esse laborum. Minim Lorem ex cupidatat elit Lorem consequat deserunt cillum. + Laboris esse do non dolor reprehenderit excepteur ea eiusmod incididunt sit eu tempor consectetur reprehenderit. Dolore est exercitation ullamco magna adipisicing dolor ea esse sit fugiat sint enim in irure. Aliquip ea ea magna irure amet dolore. Lorem anim ad enim reprehenderit officia et magna pariatur dolore ex quis sint. Quis duis aliqua do ipsum enim cillum. +
    +
    +
    + @components.OBSMockup("/assets/widget-background.jpg") { +
    + content +
    + } +
    +
    +
    +
    + // account input +
    +
    +} diff --git a/cmd/frontend/routes/widget/index_templ.go b/cmd/frontend/routes/widget/index_templ.go new file mode 100644 index 00000000..d0ccd481 --- /dev/null +++ b/cmd/frontend/routes/widget/index_templ.go @@ -0,0 +1,72 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package widget + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "github.com/cufee/aftermath/cmd/frontend/components" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" +) + +var WidgetHome handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + + return layouts.Main, widgetHome(), nil +} + +func widgetHome() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Amet cillum fugiat ut eu enim dolor nulla minim. Officia veniam esse exercitation elit consequat voluptate cillum ex. Ut aute nulla ipsum in sint enim et amet nisi irure. Nulla tempor enim ut reprehenderit excepteur ad aute eu fugiat cupidatat et eu magna officia. Aute aute quis ea anim. Ipsum aliqua officia qui id dolore ullamco ipsum ipsum esse laborum. Minim Lorem ex cupidatat elit Lorem consequat deserunt cillum. Laboris esse do non dolor reprehenderit excepteur ea eiusmod incididunt sit eu tempor consectetur reprehenderit. Dolore est exercitation ullamco magna adipisicing dolor ea esse sit fugiat sint enim in irure. Aliquip ea ea magna irure amet dolore. Lorem anim ad enim reprehenderit officia et magna pariatur dolore ex quis sint. Quis duis aliqua do ipsum enim cillum.
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    content
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = components.OBSMockup("/assets/widget-background.jpg").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/docker-compose.yaml b/docker-compose.yaml index ca895eef..6caba5f0 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -24,6 +24,8 @@ services: image: aftermath-service build: dockerfile: Dockerfile + args: + - GO_MEM_LIMIT=122MiB restart: always deploy: resources: From 1711b91f3db8a9c62d3895dc4b75cd884f987f28 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 14:29:27 -0400 Subject: [PATCH 249/341] adding map length --- .air.toml | 2 +- .air.web.toml | 2 +- cmd/core/queue/queue.go | 2 +- cmd/core/server/handlers/private/accounts.go | 4 ++-- internal/external/blitzstars/account.go | 4 ++-- internal/external/blitzstars/averages.go | 4 ++-- internal/external/blitzstars/client.go | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.air.toml b/.air.toml index 57af028c..327c2ea5 100644 --- a/.air.toml +++ b/.air.toml @@ -2,7 +2,7 @@ include_ext = ["go", "tmpl", "templ", "html"] exclude_regex = [".*_templ.go"] pre_cmd = ["templ generate"] -cmd = "GODEBUG=gctrace=1 go build -o ./tmp/main ." +cmd = "go build -gcflags '-m' -o ./tmp/main ." send_interrupt = true stop_on_error = true diff --git a/.air.web.toml b/.air.web.toml index 3b69524b..c61c6803 100644 --- a/.air.web.toml +++ b/.air.web.toml @@ -4,7 +4,7 @@ exclude_regex = [".*_templ.go"] pre_cmd = ["templ generate"] send_interrupt = true stop_on_error = true -cmd = "go build -o ./tmp/web dev_web.go" +cmd = "go build -gcflags '-m' -o ./tmp/main ." bin = "tmp/web" [proxy] diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index ebbbc6e2..7c23ca99 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -33,7 +33,7 @@ type queue struct { func New(workerLimit int, newCoreClient func() (core.Client, error)) *queue { return &queue{ newCoreClient: newCoreClient, - handlers: make(map[models.TaskType]tasks.TaskHandler), + handlers: make(map[models.TaskType]tasks.TaskHandler, 10), workerLimit: workerLimit, workerTimeout: time.Second * 60, // a single cron scheduler cycle diff --git a/cmd/core/server/handlers/private/accounts.go b/cmd/core/server/handlers/private/accounts.go index d42b260b..9bfb47f1 100644 --- a/cmd/core/server/handlers/private/accounts.go +++ b/cmd/core/server/handlers/private/accounts.go @@ -44,7 +44,7 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { existingMap[a.ID] = struct{}{} } - accountsByRealm := make(map[string][]string) + accountsByRealm := make(map[string][]string, len(accounts)) for _, a := range accounts { if _, ok := existingMap[a]; ok { continue @@ -57,7 +57,7 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { batchSize := 50 var wg sync.WaitGroup sem := semaphore.NewWeighted(5) - errors := make(map[string]error) + errors := make(map[string]error, len(accountsByRealm)) var errorsMx sync.Mutex for realm, accounts := range accountsByRealm { diff --git a/internal/external/blitzstars/account.go b/internal/external/blitzstars/account.go index 6c63ecb2..3c20d585 100644 --- a/internal/external/blitzstars/account.go +++ b/internal/external/blitzstars/account.go @@ -17,7 +17,7 @@ type TankHistoryEntry struct { Stats types.StatsFrame `json:"all"` } -func (c *client) AccountTankHistories(ctx context.Context, accountId string) (map[int][]TankHistoryEntry, error) { +func (c client) AccountTankHistories(ctx context.Context, accountId string) (map[int][]TankHistoryEntry, error) { req, err := http.NewRequest("GET", fmt.Sprintf("%s/tankhistories/for/%s", c.apiURL, accountId), nil) if err != nil { return nil, err @@ -38,7 +38,7 @@ func (c *client) AccountTankHistories(ctx context.Context, accountId string) (ma return nil, err } - var historiesMap = make(map[int][]TankHistoryEntry) + var historiesMap = make(map[int][]TankHistoryEntry, len(histories)) for _, entry := range histories { historiesMap[entry.TankID] = append(historiesMap[entry.TankID], entry) } diff --git a/internal/external/blitzstars/averages.go b/internal/external/blitzstars/averages.go index b7ddddb4..d206b4d4 100644 --- a/internal/external/blitzstars/averages.go +++ b/internal/external/blitzstars/averages.go @@ -33,7 +33,7 @@ type VehicleAverages struct { } `json:"special,omitempty"` } -func (c *client) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) { +func (c client) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) { req, err := http.NewRequest("GET", c.apiURL+"/tankaverages.json", nil) if err != nil { return nil, err @@ -55,7 +55,7 @@ func (c *client) CurrentTankAverages(ctx context.Context) (map[string]frame.Stat return nil, err } - averagesMap := make(map[string]frame.StatsFrame) + averagesMap := make(map[string]frame.StatsFrame, len(averages)) for _, average := range averages { battles := average.All.AvgBattles * float32(average.Players) diff --git a/internal/external/blitzstars/client.go b/internal/external/blitzstars/client.go index 2015050e..1b5a69c4 100644 --- a/internal/external/blitzstars/client.go +++ b/internal/external/blitzstars/client.go @@ -21,8 +21,8 @@ type client struct { requestTimeout time.Duration } -func NewClient(apiURL string, requestTimeout time.Duration) (*client, error) { - return &client{ +func NewClient(apiURL string, requestTimeout time.Duration) (client, error) { + return client{ apiURL: apiURL, requestTimeout: requestTimeout, http: http.Client{Timeout: requestTimeout}, From 8680f1893b50ca15c6803ab66c414a61bacac09c Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 14:29:34 -0400 Subject: [PATCH 250/341] added missing length --- cmd/core/server/handlers/private/accounts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/core/server/handlers/private/accounts.go b/cmd/core/server/handlers/private/accounts.go index 9bfb47f1..2281ae22 100644 --- a/cmd/core/server/handlers/private/accounts.go +++ b/cmd/core/server/handlers/private/accounts.go @@ -39,7 +39,7 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { log.Info().Int("count", len(accounts)-len(existing)).Msg("importing accounts") go func(accounts []string, existing []models.Account) { - existingMap := make(map[string]struct{}) + existingMap := make(map[string]struct{}, len(existing)) for _, a := range existing { existingMap[a.ID] = struct{}{} } From b87e7275218708994a4bcac1e51071264bf3c4a2 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 14:45:29 -0400 Subject: [PATCH 251/341] added hard mem limit --- docker-compose.yaml | 4 +++- internal/stats/render/common/v1/images.go | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 6caba5f0..9d7bcfce 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -30,7 +30,9 @@ services: deploy: resources: reservations: - memory: 128m + memory: 64m + limits: + memory: 156m networks: - dokploy-network labels: diff --git a/internal/stats/render/common/v1/images.go b/internal/stats/render/common/v1/images.go index c28f1ef7..8e5d9a01 100644 --- a/internal/stats/render/common/v1/images.go +++ b/internal/stats/render/common/v1/images.go @@ -157,6 +157,10 @@ func getDetailedSize(images []image.Image, style Style) imageSize { var totalHeight float64 maxWidth, maxHeight := 0.0, 0.0 for _, img := range images { + if img == nil { + continue + } + imgX := float64(img.Bounds().Dx()) maxWidth = max(maxWidth, imgX) From 4de2407fac84790edc98da51c2fa1768b3f51ed0 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 14:45:57 -0400 Subject: [PATCH 252/341] added error on invalid font --- internal/stats/render/common/v1/block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/stats/render/common/v1/block.go b/internal/stats/render/common/v1/block.go index 86d07cb9..3b2013b8 100644 --- a/internal/stats/render/common/v1/block.go +++ b/internal/stats/render/common/v1/block.go @@ -62,7 +62,7 @@ func NewTextContent(style Style, value string) Block { func (content contentText) Render(style Style) (image.Image, error) { if !style.Font.Valid() { - // return nil, errors.New("font not valid") + return nil, errors.New("font not valid") } size := MeasureString(content.value, style.Font) From f68c154bfe367caf6b98f5f69a509a6d62eb8dcb Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 14:51:42 -0400 Subject: [PATCH 253/341] fixed queue deadlock --- cmd/core/queue/queue.go | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index 7c23ca99..b45853cb 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -151,26 +151,23 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { for { select { case task := <-q.queued: - q.workerLimiter <- struct{}{} - q.activeTasksMx.Lock() - q.activeTasks[task.ID] = &struct{}{} - q.activeTasksMx.Unlock() - defer func() { + go func(task models.Task) { + // send to limiter channel + q.workerLimiter <- struct{}{} + q.activeTasksMx.Lock() + q.activeTasks[task.ID] = &struct{}{} + q.activeTasksMx.Unlock() + // run task handler + q.processTask(task) + // free limiter channel <-q.workerLimiter q.activeTasksMx.Lock() delete(q.activeTasks, task.ID) q.activeTasksMx.Unlock() if onComplete != nil { - onComplete(task.ID) + go onComplete(task.ID) } - }() - - coreClient, err := q.newCoreClient() - if err != nil { - log.Err(err).Msg("failed to create a new core client for a task worker") - return - } - go q.processTask(coreClient, task) + }(task) case <-ctx.Done(): return @@ -178,10 +175,16 @@ func (q *queue) startWorkers(ctx context.Context, onComplete func(id string)) { } } -func (q *queue) processTask(coreClient core.Client, task models.Task) { +func (q *queue) processTask(task models.Task) { log.Debug().Str("taskId", task.ID).Msg("worker started processing a task") defer log.Debug().Str("taskId", task.ID).Msg("worker finished processing a task") + coreClient, err := q.newCoreClient() + if err != nil { + log.Err(err).Msg("failed to create a new core client for a task worker") + return + } + defer func() { if r := recover(); r != nil { event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) @@ -231,7 +234,7 @@ func (q *queue) processTask(coreClient core.Client, task models.Task) { wctx, cancel := context.WithTimeout(context.Background(), q.workerTimeout) defer cancel() - err := handler.Process(wctx, coreClient, &task) + err = handler.Process(wctx, coreClient, &task) task.Status = models.TaskStatusComplete l := models.TaskLog{ Timestamp: time.Now(), From 1bbd147965324c46aa11278689c5a10e8a703799 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 14:59:58 -0400 Subject: [PATCH 254/341] capturing body vars --- cmd/core/queue/queue.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/core/queue/queue.go b/cmd/core/queue/queue.go index b45853cb..b8d311de 100644 --- a/cmd/core/queue/queue.go +++ b/cmd/core/queue/queue.go @@ -190,11 +190,6 @@ func (q *queue) processTask(task models.Task) { event := log.Error().Str("stack", string(debug.Stack())).Str("taskId", task.ID) defer event.Msg("panic in queue worker") - coreClient, err := q.newCoreClient() - if err != nil { - event.AnErr("core", err).Str("additional", "failed to create a core client") - return - } task.Status = models.TaskStatusFailed task.LogAttempt(models.TaskLog{ Timestamp: time.Now(), From b226f6a73ddf050944ca9f1dc04ba6f1e3ee44ac Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 15:01:36 -0400 Subject: [PATCH 255/341] removed boost from login --- cmd/frontend/components/navbar.templ | 2 +- cmd/frontend/components/navbar_templ.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/frontend/components/navbar.templ b/cmd/frontend/components/navbar.templ index b3145190..d96c1db4 100644 --- a/cmd/frontend/components/navbar.templ +++ b/cmd/frontend/components/navbar.templ @@ -42,7 +42,7 @@ templ navbar(props navbarProps) {
    -
    - // account input +
    +
    + @components.OBSMockup("/assets/widget-background.jpg") { +
    + @widget +
    + } +
    } + +script handlePreview() { + const ou = document.getElementById("widget-ou").checked ? "1" : "0" + const or = document.getElementById("widget-or").checked ? "1" : "0" + const vl = document.getElementById("widget-vl").value + const newQuery = `?or=${or}&ou=${ou}&vl=${vl}` + if (newQuery != window.location.search) { + window.location.search = newQuery + } +} diff --git a/cmd/frontend/routes/widget/index_templ.go b/cmd/frontend/routes/widget/index_templ.go index d0ccd481..cc797c88 100644 --- a/cmd/frontend/routes/widget/index_templ.go +++ b/cmd/frontend/routes/widget/index_templ.go @@ -9,17 +9,31 @@ import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" import ( + "fmt" "github.com/cufee/aftermath/cmd/frontend/components" "github.com/cufee/aftermath/cmd/frontend/handler" "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/cmd/frontend/routes/api/widget" + "strconv" ) var WidgetHome handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + widget, err := widget.MockWidget(ctx) + if err != nil { + return layouts.Main, nil, ctx.Error(err, "failed to generate a widget preview") + } - return layouts.Main, widgetHome(), nil + var withUnrated = ctx.Query("ou") != "0" + var withRating = ctx.Query("or") != "0" + var vehicles int = 3 + if v, err := strconv.Atoi(ctx.Query("vl")); err == nil && v >= 0 && v <= 10 { + vehicles = v + } + + return layouts.Main, widgetHome(widget, withRating, withUnrated, vehicles), nil } -func widgetHome() templ.Component { +func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) @@ -37,11 +51,84 @@ func widgetHome() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Amet cillum fugiat ut eu enim dolor nulla minim. Officia veniam esse exercitation elit consequat voluptate cillum ex. Ut aute nulla ipsum in sint enim et amet nisi irure. Nulla tempor enim ut reprehenderit excepteur ad aute eu fugiat cupidatat et eu magna officia. Aute aute quis ea anim. Ipsum aliqua officia qui id dolore ullamco ipsum ipsum esse laborum. Minim Lorem ex cupidatat elit Lorem consequat deserunt cillum. Laboris esse do non dolor reprehenderit excepteur ea eiusmod incididunt sit eu tempor consectetur reprehenderit. Dolore est exercitation ullamco magna adipisicing dolor ea esse sit fugiat sint enim in irure. Aliquip ea ea magna irure amet dolore. Lorem anim ad enim reprehenderit officia et magna pariatur dolore ex quis sint. Quis duis aliqua do ipsum enim cillum.
    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Aftermath Streaming Widget

    Level up your stream with a real-time stats widget!

    Rating Battles
    Regular Battles
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, handlePreview()) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -53,20 +140,44 @@ func widgetHome() templ.Component { }() } ctx = templ.InitializeContext(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    content
    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = widget.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = components.OBSMockup("/assets/widget-background.jpg").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = components.OBSMockup("/assets/widget-background.jpg").Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return templ_7745c5c3_Err }) } + +func handlePreview() templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_handlePreview_9417`, + Function: `function __templ_handlePreview_9417(){const ou = document.getElementById("widget-ou").checked ? "1" : "0" + const or = document.getElementById("widget-or").checked ? "1" : "0" + const vl = document.getElementById("widget-vl").value + const newQuery = ` + "`" + `?or=${or}&ou=${ou}&vl=${vl}` + "`" + ` + if (newQuery != window.location.search) { + window.location.search = newQuery + } +}`, + Call: templ.SafeScript(`__templ_handlePreview_9417`), + CallInline: templ.SafeScriptInline(`__templ_handlePreview_9417`), + } +} diff --git a/cmd/frontend/routes/widget/live.templ b/cmd/frontend/routes/widget/live.templ index ef76ce7b..e5829ae9 100644 --- a/cmd/frontend/routes/widget/live.templ +++ b/cmd/frontend/routes/widget/live.templ @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "golang.org/x/text/language" "slices" + "strconv" "time" ) @@ -37,7 +38,18 @@ var PersonalLiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout return nil, nil, err } - return layouts.Main, liveWidget(widget.Widget(account, cards, widget.WithAutoReload())), nil + var wopts = []widget.WidgetOption{widget.WithAutoReload()} + if v, err := strconv.Atoi(ctx.Query("vl")); err == nil && v >= 0 && v <= 10 { + wopts = append(wopts, widget.WithVehicleLimit(int(v))) + } + if v := ctx.Query("or"); v != "" { + wopts = append(wopts, widget.WithRatingOverview(v == "1")) + } + if v := ctx.Query("ou"); v != "" { + wopts = append(wopts, widget.WithUnratedOverview(v == "1")) + } + + return layouts.Main, liveWidget(widget.Widget(account, cards, wopts...)), nil } var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { diff --git a/cmd/frontend/routes/widget/live_templ.go b/cmd/frontend/routes/widget/live_templ.go index 0e1ebfb9..028ae140 100644 --- a/cmd/frontend/routes/widget/live_templ.go +++ b/cmd/frontend/routes/widget/live_templ.go @@ -18,6 +18,7 @@ import ( "github.com/pkg/errors" "golang.org/x/text/language" "slices" + "strconv" "time" ) @@ -45,7 +46,18 @@ var PersonalLiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout return nil, nil, err } - return layouts.Main, liveWidget(widget.Widget(account, cards, widget.WithAutoReload())), nil + var wopts = []widget.WidgetOption{widget.WithAutoReload()} + if v, err := strconv.Atoi(ctx.Query("vl")); err == nil && v >= 0 && v <= 10 { + wopts = append(wopts, widget.WithVehicleLimit(int(v))) + } + if v := ctx.Query("or"); v != "" { + wopts = append(wopts, widget.WithRatingOverview(v == "1")) + } + if v := ctx.Query("ou"); v != "" { + wopts = append(wopts, widget.WithUnratedOverview(v == "1")) + } + + return layouts.Main, liveWidget(widget.Widget(account, cards, wopts...)), nil } var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { diff --git a/internal/database/models/account.go b/internal/database/models/account.go index 773bfdca..221e284b 100644 --- a/internal/database/models/account.go +++ b/internal/database/models/account.go @@ -3,14 +3,14 @@ package models import "time" type Account struct { - ID string - Realm string - Nickname string + ID string `json:"id"` + Realm string `json:"realm"` + Nickname string `json:"nickname"` - Private bool - CreatedAt time.Time - LastBattleTime time.Time + Private bool `json:"private"` + CreatedAt time.Time `json:"createdAt"` + LastBattleTime time.Time `json:"lastBattleTime"` - ClanID string - ClanTag string + ClanID string `json:"clanId"` + ClanTag string `json:"clanTag"` } diff --git a/internal/stats/frame/value.go b/internal/stats/frame/value.go index b14a2148..5c5075dd 100644 --- a/internal/stats/frame/value.go +++ b/internal/stats/frame/value.go @@ -4,6 +4,8 @@ import "fmt" type ValueInt int +var _ Value = ValueInt(0) + func (value ValueInt) String() string { return fmt.Sprintf("%d", value) } @@ -14,6 +16,8 @@ func (value ValueInt) Float() float32 { type ValueFloatDecimal float32 +var _ Value = ValueFloatDecimal(0) + func (value ValueFloatDecimal) String() string { return fmt.Sprintf("%.2f", value) } @@ -24,6 +28,8 @@ func (value ValueFloatDecimal) Float() float32 { type ValueFloatPercent float32 +var _ Value = ValueFloatPercent(0) + func (value ValueFloatPercent) String() string { return fmt.Sprintf("%.2f%%", value) } @@ -34,6 +40,8 @@ func (value ValueFloatPercent) Float() float32 { type valueInvalid struct{} +var _ Value = InvalidValue + func (value valueInvalid) String() string { return "-" } @@ -54,6 +62,8 @@ var InvalidValue = valueInvalid{} type ValueSpecialRating float32 +var _ Value = ValueSpecialRating(0) + func (value ValueSpecialRating) int() int { if value > 0 { return int((value * 10) + 3000) diff --git a/internal/stats/prepare/session/v1/constants.go b/internal/stats/prepare/session/v1/constants.go index 4d872179..c64e8a86 100644 --- a/internal/stats/prepare/session/v1/constants.go +++ b/internal/stats/prepare/session/v1/constants.go @@ -31,27 +31,27 @@ type Cards struct { } type UnratedCards struct { - Overview OverviewCard - Vehicles []VehicleCard - Highlights []VehicleCard + Overview OverviewCard `json:"overview"` + Vehicles []VehicleCard `json:"vehicles"` + Highlights []VehicleCard `json:"highlights"` } type RatingCards struct { - Overview OverviewCard - Vehicles []VehicleCard + Overview OverviewCard `json:"overview"` + Vehicles []VehicleCard `json:"vehicles"` } type OverviewColumn struct { - Blocks []common.StatsBlock[BlockData] - Flavor blockFlavor + Blocks []common.StatsBlock[BlockData] `json:"blocks"` + Flavor blockFlavor `json:"flavor"` } type OverviewCard common.StatsCard[OverviewColumn, string] type VehicleCard common.StatsCard[common.StatsBlock[BlockData], string] type BlockData struct { - Session frame.Value - Career frame.Value + Session frame.Value `json:"session"` + Career frame.Value `json:"career"` } type blockFlavor string From 03a604b2af817545e7dd21047d3d27a20763f617 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 19:05:02 -0400 Subject: [PATCH 257/341] widget preview without refresh --- cmd/frontend/routes/api/widget/mock.templ | 3 +++ cmd/frontend/routes/api/widget/mock_templ.go | 3 +++ cmd/frontend/routes/widget/index.templ | 6 +++++- cmd/frontend/routes/widget/index_templ.go | 14 +++++++++----- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cmd/frontend/routes/api/widget/mock.templ b/cmd/frontend/routes/api/widget/mock.templ index 6a93e6bc..7d81c897 100644 --- a/cmd/frontend/routes/api/widget/mock.templ +++ b/cmd/frontend/routes/api/widget/mock.templ @@ -4,6 +4,7 @@ import ( "github.com/cufee/aftermath/cmd/frontend/assets" "github.com/cufee/aftermath/cmd/frontend/components/widget" "github.com/cufee/aftermath/cmd/frontend/handler" + "net/http" "strconv" ) @@ -18,6 +19,8 @@ var MockWidget handler.Partial = func(ctx *handler.Context) (templ.Component, er if v := ctx.Query("ou"); v != "" { opts = append(opts, widget.WithUnratedOverview(v == "1")) } + + ctx.SetStatus(http.StatusBadRequest) return mockWidget(widget.Widget(assets.MockWidgetData().Account, assets.MockWidgetData().Cards, opts...)), nil } diff --git a/cmd/frontend/routes/api/widget/mock_templ.go b/cmd/frontend/routes/api/widget/mock_templ.go index 6bfbf0a3..0694da22 100644 --- a/cmd/frontend/routes/api/widget/mock_templ.go +++ b/cmd/frontend/routes/api/widget/mock_templ.go @@ -12,6 +12,7 @@ import ( "github.com/cufee/aftermath/cmd/frontend/assets" "github.com/cufee/aftermath/cmd/frontend/components/widget" "github.com/cufee/aftermath/cmd/frontend/handler" + "net/http" "strconv" ) @@ -26,6 +27,8 @@ var MockWidget handler.Partial = func(ctx *handler.Context) (templ.Component, er if v := ctx.Query("ou"); v != "" { opts = append(opts, widget.WithUnratedOverview(v == "1")) } + + ctx.SetStatus(http.StatusBadRequest) return mockWidget(widget.Widget(assets.MockWidgetData().Account, assets.MockWidgetData().Cards, opts...)), nil } diff --git a/cmd/frontend/routes/widget/index.templ b/cmd/frontend/routes/widget/index.templ index 3119cab4..2bcc14b4 100644 --- a/cmd/frontend/routes/widget/index.templ +++ b/cmd/frontend/routes/widget/index.templ @@ -83,6 +83,10 @@ script handlePreview() { const vl = document.getElementById("widget-vl").value const newQuery = `?or=${or}&ou=${ou}&vl=${vl}` if (newQuery != window.location.search) { - window.location.search = newQuery + fetch("/api/widget/mock"+newQuery).then((r) => r.text()).then((html) => { + document.getElementById("mock-widget").outerHTML = html + const url = window.location.protocol + "//" + window.location.host + window.location.pathname + newQuery; + window.history?.pushState({path:url},'',url); + }).catch(e => console.log(e)) } } diff --git a/cmd/frontend/routes/widget/index_templ.go b/cmd/frontend/routes/widget/index_templ.go index cc797c88..59f4f47a 100644 --- a/cmd/frontend/routes/widget/index_templ.go +++ b/cmd/frontend/routes/widget/index_templ.go @@ -168,16 +168,20 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { func handlePreview() templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_handlePreview_9417`, - Function: `function __templ_handlePreview_9417(){const ou = document.getElementById("widget-ou").checked ? "1" : "0" + Name: `__templ_handlePreview_f919`, + Function: `function __templ_handlePreview_f919(){const ou = document.getElementById("widget-ou").checked ? "1" : "0" const or = document.getElementById("widget-or").checked ? "1" : "0" const vl = document.getElementById("widget-vl").value const newQuery = ` + "`" + `?or=${or}&ou=${ou}&vl=${vl}` + "`" + ` if (newQuery != window.location.search) { - window.location.search = newQuery + fetch("/api/widget/mock"+newQuery).then((r) => r.text()).then((html) => { + document.getElementById("mock-widget").outerHTML = html + const url = window.location.protocol + "//" + window.location.host + window.location.pathname + newQuery; + window.history?.pushState({path:url},'',url); + }).catch(e => console.log(e)) } }`, - Call: templ.SafeScript(`__templ_handlePreview_9417`), - CallInline: templ.SafeScriptInline(`__templ_handlePreview_9417`), + Call: templ.SafeScript(`__templ_handlePreview_f919`), + CallInline: templ.SafeScriptInline(`__templ_handlePreview_f919`), } } From ee7cf4f57c76a0c806170eeaeb618c74ae095ae3 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 19:05:58 -0400 Subject: [PATCH 258/341] fixed response status --- cmd/frontend/routes/api/widget/mock.templ | 2 -- cmd/frontend/routes/api/widget/mock_templ.go | 2 -- 2 files changed, 4 deletions(-) diff --git a/cmd/frontend/routes/api/widget/mock.templ b/cmd/frontend/routes/api/widget/mock.templ index 7d81c897..24ba8cd6 100644 --- a/cmd/frontend/routes/api/widget/mock.templ +++ b/cmd/frontend/routes/api/widget/mock.templ @@ -4,7 +4,6 @@ import ( "github.com/cufee/aftermath/cmd/frontend/assets" "github.com/cufee/aftermath/cmd/frontend/components/widget" "github.com/cufee/aftermath/cmd/frontend/handler" - "net/http" "strconv" ) @@ -20,7 +19,6 @@ var MockWidget handler.Partial = func(ctx *handler.Context) (templ.Component, er opts = append(opts, widget.WithUnratedOverview(v == "1")) } - ctx.SetStatus(http.StatusBadRequest) return mockWidget(widget.Widget(assets.MockWidgetData().Account, assets.MockWidgetData().Cards, opts...)), nil } diff --git a/cmd/frontend/routes/api/widget/mock_templ.go b/cmd/frontend/routes/api/widget/mock_templ.go index 0694da22..033591f5 100644 --- a/cmd/frontend/routes/api/widget/mock_templ.go +++ b/cmd/frontend/routes/api/widget/mock_templ.go @@ -12,7 +12,6 @@ import ( "github.com/cufee/aftermath/cmd/frontend/assets" "github.com/cufee/aftermath/cmd/frontend/components/widget" "github.com/cufee/aftermath/cmd/frontend/handler" - "net/http" "strconv" ) @@ -28,7 +27,6 @@ var MockWidget handler.Partial = func(ctx *handler.Context) (templ.Component, er opts = append(opts, widget.WithUnratedOverview(v == "1")) } - ctx.SetStatus(http.StatusBadRequest) return mockWidget(widget.Widget(assets.MockWidgetData().Account, assets.MockWidgetData().Cards, opts...)), nil } From ad2adb45f56657a5f5e79bdbc436336dfba26db9 Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 19:52:20 -0400 Subject: [PATCH 259/341] better widget preview experience --- cmd/frontend/components/head.templ | 32 ++++ cmd/frontend/components/head_templ.go | 78 +++++++++ cmd/frontend/components/widget/settings.templ | 33 ++++ .../components/widget/settings_templ.go | 144 +++++++++++++++ cmd/frontend/handlers.go | 6 +- cmd/frontend/layouts/main.templ | 31 +--- cmd/frontend/layouts/main_templ.go | 43 +---- cmd/frontend/layouts/style_only.templ | 31 ++++ cmd/frontend/layouts/style_only_templ.go | 62 +++++++ cmd/frontend/routes/api/widget/account.templ | 58 +++++++ .../routes/api/widget/account_templ.go | 94 ++++++++++ cmd/frontend/routes/widget/configure.templ | 76 -------- cmd/frontend/routes/widget/configure_templ.go | 97 ----------- cmd/frontend/routes/widget/index.templ | 59 +++---- cmd/frontend/routes/widget/index_templ.go | 108 +++--------- cmd/frontend/routes/widget/live.templ | 17 +- cmd/frontend/routes/widget/live_templ.go | 19 +- cmd/frontend/routes/widget/preview.templ | 89 ++++++++++ cmd/frontend/routes/widget/preview_templ.go | 164 ++++++++++++++++++ 19 files changed, 877 insertions(+), 364 deletions(-) create mode 100644 cmd/frontend/components/head.templ create mode 100644 cmd/frontend/components/head_templ.go create mode 100644 cmd/frontend/components/widget/settings.templ create mode 100644 cmd/frontend/components/widget/settings_templ.go create mode 100644 cmd/frontend/layouts/style_only.templ create mode 100644 cmd/frontend/layouts/style_only_templ.go create mode 100644 cmd/frontend/routes/api/widget/account.templ create mode 100644 cmd/frontend/routes/api/widget/account_templ.go delete mode 100644 cmd/frontend/routes/widget/configure.templ delete mode 100644 cmd/frontend/routes/widget/configure_templ.go create mode 100644 cmd/frontend/routes/widget/preview.templ create mode 100644 cmd/frontend/routes/widget/preview_templ.go diff --git a/cmd/frontend/components/head.templ b/cmd/frontend/components/head.templ new file mode 100644 index 00000000..8ecca509 --- /dev/null +++ b/cmd/frontend/components/head.templ @@ -0,0 +1,32 @@ +package components + +import "github.com/cufee/aftermath/cmd/frontend/logic" + +templ Head() { + + + + + + + + + + @logic.EmbedMinifiedScript(tailwindConfig()) + { appName } + +} + +script tailwindConfig() { + tailwind.config = { + theme: { + borderRadius: { + "sm": "10px", + "md": "15px", + "lg": "20px", + "xl": "25px", + 'full': '9999px', + } + } + } +} diff --git a/cmd/frontend/components/head_templ.go b/cmd/frontend/components/head_templ.go new file mode 100644 index 00000000..a78d7c78 --- /dev/null +++ b/cmd/frontend/components/head_templ.go @@ -0,0 +1,78 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "github.com/cufee/aftermath/cmd/frontend/logic" + +func Head() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = logic.EmbedMinifiedScript(tailwindConfig()).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(appName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/components/head.templ`, Line: 16, Col: 18} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func tailwindConfig() templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_tailwindConfig_0472`, + Function: `function __templ_tailwindConfig_0472(){tailwind.config = { + theme: { + borderRadius: { + "sm": "10px", + "md": "15px", + "lg": "20px", + "xl": "25px", + 'full': '9999px', + } + } + } +}`, + Call: templ.SafeScript(`__templ_tailwindConfig_0472`), + CallInline: templ.SafeScriptInline(`__templ_tailwindConfig_0472`), + } +} diff --git a/cmd/frontend/components/widget/settings.templ b/cmd/frontend/components/widget/settings.templ new file mode 100644 index 00000000..c19bcbc9 --- /dev/null +++ b/cmd/frontend/components/widget/settings.templ @@ -0,0 +1,33 @@ +package widget + +import "fmt" + +templ Settings(onChange templ.ComponentScript, or, ou bool, vl int) { +
    +
    + Rating Battles + +
    +
    + Regular Battles + + +
    +
    +} diff --git a/cmd/frontend/components/widget/settings_templ.go b/cmd/frontend/components/widget/settings_templ.go new file mode 100644 index 00000000..5e02fe40 --- /dev/null +++ b/cmd/frontend/components/widget/settings_templ.go @@ -0,0 +1,144 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package widget + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "fmt" + +func Settings(onChange templ.ComponentScript, or, ou bool, vl int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Rating Battles
    Regular Battles
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/handlers.go b/cmd/frontend/handlers.go index 484db49e..503c1c71 100644 --- a/cmd/frontend/handlers.go +++ b/cmd/frontend/handlers.go @@ -66,7 +66,7 @@ func Handlers(core core.Client) ([]server.Handler, error) { }, { Path: get("/widget/{accountId}"), - Func: handler.Chain(core, widget.ConfigureWidget), + Func: handler.Chain(core, widget.WidgetPreview), }, { Path: get("/widget/{accountId}/live"), @@ -98,6 +98,10 @@ func Handlers(core core.Client) ([]server.Handler, error) { Path: get("/api/widget/mock"), Func: handler.Chain(core, aWidget.MockWidget), }, + { + Path: get("/api/widget/{accountId}"), + Func: handler.Chain(core, aWidget.AccountWidget), + }, }, nil } diff --git a/cmd/frontend/layouts/main.templ b/cmd/frontend/layouts/main.templ index dc9b6a2f..cf4276c8 100644 --- a/cmd/frontend/layouts/main.templ +++ b/cmd/frontend/layouts/main.templ @@ -3,12 +3,8 @@ package layouts import ( "github.com/cufee/aftermath/cmd/frontend/components" "github.com/cufee/aftermath/cmd/frontend/handler" - "github.com/cufee/aftermath/cmd/frontend/logic" - "os" ) -var appName = os.Getenv("WEBAPP_NAME") - var Main handler.Layout = func(ctx *handler.Context, children ...templ.Component) (templ.Component, error) { navbar, err := components.Navbar(ctx) if err != nil { @@ -21,18 +17,7 @@ var Main handler.Layout = func(ctx *handler.Context, children ...templ.Component templ main(navbar templ.Component, children ...templ.Component) { - - - - - - - - - - @logic.EmbedMinifiedScript(tailwindConfig()) - { appName } - + @components.Head()
    @navbar @@ -54,17 +39,3 @@ templ main(navbar templ.Component, children ...templ.Component) { } - -script tailwindConfig() { - tailwind.config = { - theme: { - borderRadius: { - "sm": "10px", - "md": "15px", - "lg": "20px", - "xl": "25px", - 'full': '9999px', - } - } - } -} diff --git a/cmd/frontend/layouts/main_templ.go b/cmd/frontend/layouts/main_templ.go index ec95d7dc..01543a48 100644 --- a/cmd/frontend/layouts/main_templ.go +++ b/cmd/frontend/layouts/main_templ.go @@ -11,12 +11,8 @@ import templruntime "github.com/a-h/templ/runtime" import ( "github.com/cufee/aftermath/cmd/frontend/components" "github.com/cufee/aftermath/cmd/frontend/handler" - "github.com/cufee/aftermath/cmd/frontend/logic" - "os" ) -var appName = os.Getenv("WEBAPP_NAME") - var Main handler.Layout = func(ctx *handler.Context, children ...templ.Component) (templ.Component, error) { navbar, err := components.Navbar(ctx) if err != nil { @@ -44,28 +40,15 @@ func main(navbar templ.Component, children ...templ.Component) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = logic.EmbedMinifiedScript(tailwindConfig()).Render(ctx, templ_7745c5c3_Buffer) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + templ_7745c5c3_Err = components.Head().Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(appName) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/frontend/layouts/main.templ`, Line: 34, Col: 19} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -90,23 +73,3 @@ func main(navbar templ.Component, children ...templ.Component) templ.Component { return templ_7745c5c3_Err }) } - -func tailwindConfig() templ.ComponentScript { - return templ.ComponentScript{ - Name: `__templ_tailwindConfig_0472`, - Function: `function __templ_tailwindConfig_0472(){tailwind.config = { - theme: { - borderRadius: { - "sm": "10px", - "md": "15px", - "lg": "20px", - "xl": "25px", - 'full': '9999px', - } - } - } -}`, - Call: templ.SafeScript(`__templ_tailwindConfig_0472`), - CallInline: templ.SafeScriptInline(`__templ_tailwindConfig_0472`), - } -} diff --git a/cmd/frontend/layouts/style_only.templ b/cmd/frontend/layouts/style_only.templ new file mode 100644 index 00000000..d3f19c4d --- /dev/null +++ b/cmd/frontend/layouts/style_only.templ @@ -0,0 +1,31 @@ +package layouts + +import ( + "github.com/cufee/aftermath/cmd/frontend/components" + "github.com/cufee/aftermath/cmd/frontend/handler" +) + +var StyleOnly handler.Layout = func(ctx *handler.Context, children ...templ.Component) (templ.Component, error) { + return styleOnly(children...), nil +} + +templ styleOnly(children ...templ.Component) { + + + @components.Head() + + for _, render := range children { + @render + } + + + +} diff --git a/cmd/frontend/layouts/style_only_templ.go b/cmd/frontend/layouts/style_only_templ.go new file mode 100644 index 00000000..c41d6a1c --- /dev/null +++ b/cmd/frontend/layouts/style_only_templ.go @@ -0,0 +1,62 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package layouts + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "github.com/cufee/aftermath/cmd/frontend/components" + "github.com/cufee/aftermath/cmd/frontend/handler" +) + +var StyleOnly handler.Layout = func(ctx *handler.Context, children ...templ.Component) (templ.Component, error) { + return styleOnly(children...), nil +} + +func styleOnly(children ...templ.Component) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.Head().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, render := range children { + templ_7745c5c3_Err = render.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/routes/api/widget/account.templ b/cmd/frontend/routes/api/widget/account.templ new file mode 100644 index 00000000..a41df837 --- /dev/null +++ b/cmd/frontend/routes/api/widget/account.templ @@ -0,0 +1,58 @@ +package widget + +import ( + "context" + "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/client/v1" + "golang.org/x/text/language" + "net/http" + "slices" + "strconv" + "time" +) + +var AccountWidget handler.Partial = func(ctx *handler.Context) (templ.Component, error) { + accountID := ctx.Path("accountId") + if accountID == "" { + return nil, ctx.Redirect("/widget", http.StatusTemporaryRedirect) + } + + account, err := ctx.Fetch().Account(ctx.Context, accountID) + if err != nil { + return nil, ctx.Redirect("/widget", http.StatusTemporaryRedirect) + } + + var opts = []client.RequestOption{client.WithWN8()} + if ref := ctx.Query("ref"); ref != "" { + opts = append(opts, client.WithReferenceID(ref)) + } + if t := ctx.Query("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { + opts = append(opts, client.WithType(models.SnapshotType(t))) + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), opts...) + if err != nil { + return nil, err + } + + var wopts []widget.WidgetOption + if v, err := strconv.Atoi(ctx.Query("vl")); err == nil && v >= 0 && v <= 10 { + wopts = append(wopts, widget.WithVehicleLimit(int(v))) + } + if v := ctx.Query("or"); v != "" { + wopts = append(wopts, widget.WithRatingOverview(v == "1")) + } + if v := ctx.Query("ou"); v != "" { + wopts = append(wopts, widget.WithUnratedOverview(v == "1")) + } + + return accountWidget(widget.Widget(account, cards, wopts...)), nil +} + +templ accountWidget(widget templ.Component) { +
    + @widget +
    +} diff --git a/cmd/frontend/routes/api/widget/account_templ.go b/cmd/frontend/routes/api/widget/account_templ.go new file mode 100644 index 00000000..1648bee9 --- /dev/null +++ b/cmd/frontend/routes/api/widget/account_templ.go @@ -0,0 +1,94 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package widget + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "context" + "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/internal/database/models" + "github.com/cufee/aftermath/internal/stats/client/v1" + "golang.org/x/text/language" + "net/http" + "slices" + "strconv" + "time" +) + +var AccountWidget handler.Partial = func(ctx *handler.Context) (templ.Component, error) { + accountID := ctx.Path("accountId") + if accountID == "" { + return nil, ctx.Redirect("/widget", http.StatusTemporaryRedirect) + } + + account, err := ctx.Fetch().Account(ctx.Context, accountID) + if err != nil { + return nil, ctx.Redirect("/widget", http.StatusTemporaryRedirect) + } + + var opts = []client.RequestOption{client.WithWN8()} + if ref := ctx.Query("ref"); ref != "" { + opts = append(opts, client.WithReferenceID(ref)) + } + if t := ctx.Query("type"); t != "" && slices.Contains(models.SnapshotType("").Values(), t) { + opts = append(opts, client.WithType(models.SnapshotType(t))) + } + + cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), opts...) + if err != nil { + return nil, err + } + + var wopts []widget.WidgetOption + if v, err := strconv.Atoi(ctx.Query("vl")); err == nil && v >= 0 && v <= 10 { + wopts = append(wopts, widget.WithVehicleLimit(int(v))) + } + if v := ctx.Query("or"); v != "" { + wopts = append(wopts, widget.WithRatingOverview(v == "1")) + } + if v := ctx.Query("ou"); v != "" { + wopts = append(wopts, widget.WithUnratedOverview(v == "1")) + } + + return accountWidget(widget.Widget(account, cards, wopts...)), nil +} + +func accountWidget(widget templ.Component) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = widget.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/frontend/routes/widget/configure.templ b/cmd/frontend/routes/widget/configure.templ deleted file mode 100644 index d3b012c5..00000000 --- a/cmd/frontend/routes/widget/configure.templ +++ /dev/null @@ -1,76 +0,0 @@ -package widget - -import ( - "context" - "fmt" - "github.com/cufee/aftermath/cmd/frontend/components/widget" - "github.com/cufee/aftermath/cmd/frontend/handler" - "github.com/cufee/aftermath/cmd/frontend/layouts" - "github.com/cufee/aftermath/internal/stats/client/v1" - "github.com/pkg/errors" - "golang.org/x/text/language" - "time" -) - -var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - accountID := ctx.Path("accountId") - if accountID == "" { - return nil, nil, errors.New("invalid account id") - } - - account, err := ctx.Fetch().Account(ctx.Context, accountID) - if err != nil { - return nil, nil, errors.New("invalid account id") - } - - cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), client.WithWN8()) - if err != nil { - return nil, nil, err - } - - return layouts.Main, configureWidgetPage(widget.Widget(account, cards)), nil -} - -templ configureWidgetPage(widget templ.Component) { -
    -
    -
    -
    -
    - Regular Battles - - -
    -
    - Rating Battles - -
    -
    - -
    -
    -
    -
    - @widget -
    -
    -
    -} diff --git a/cmd/frontend/routes/widget/configure_templ.go b/cmd/frontend/routes/widget/configure_templ.go deleted file mode 100644 index 6331a92e..00000000 --- a/cmd/frontend/routes/widget/configure_templ.go +++ /dev/null @@ -1,97 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.2.731 -package widget - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "context" - "fmt" - "github.com/cufee/aftermath/cmd/frontend/components/widget" - "github.com/cufee/aftermath/cmd/frontend/handler" - "github.com/cufee/aftermath/cmd/frontend/layouts" - "github.com/cufee/aftermath/internal/stats/client/v1" - "github.com/pkg/errors" - "golang.org/x/text/language" - "time" -) - -var ConfigureWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { - accountID := ctx.Path("accountId") - if accountID == "" { - return nil, nil, errors.New("invalid account id") - } - - account, err := ctx.Fetch().Account(ctx.Context, accountID) - if err != nil { - return nil, nil, errors.New("invalid account id") - } - - cards, _, err := ctx.Client.Stats(language.English).SessionCards(context.Background(), account.ID, time.Now(), client.WithWN8()) - if err != nil { - return nil, nil, err - } - - return layouts.Main, configureWidgetPage(widget.Widget(account, cards)), nil -} - -func configureWidgetPage(widget templ.Component) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Regular Battles
    Rating Battles
    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = widget.Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return templ_7745c5c3_Err - }) -} diff --git a/cmd/frontend/routes/widget/index.templ b/cmd/frontend/routes/widget/index.templ index 2bcc14b4..666f5d68 100644 --- a/cmd/frontend/routes/widget/index.templ +++ b/cmd/frontend/routes/widget/index.templ @@ -1,8 +1,8 @@ package widget import ( - "fmt" "github.com/cufee/aftermath/cmd/frontend/components" + cwidget "github.com/cufee/aftermath/cmd/frontend/components/widget" "github.com/cufee/aftermath/cmd/frontend/handler" "github.com/cufee/aftermath/cmd/frontend/layouts" "github.com/cufee/aftermath/cmd/frontend/routes/api/widget" @@ -28,7 +28,7 @@ var WidgetHome handler.Page = func(ctx *handler.Context) (handler.Layout, templ. templ widgetHome(widget templ.Component, or, ou bool, vl int) {
    -
    +
    Aftermath Streaming Widget
    @@ -36,39 +36,13 @@ templ widgetHome(widget templ.Component, or, ou bool, vl int) { Level up your stream with a real-time stats widget!

    -
    -
    - Rating Battles - -
    -
    - Regular Battles - - -
    - -
    + @cwidget.Settings(handlePreviewOnHome(), or, ou, vl) +
    @components.OBSMockup("/assets/widget-background.jpg") { -
    +
    @widget
    } @@ -77,16 +51,29 @@ templ widgetHome(widget templ.Component, or, ou bool, vl int) {
    } -script handlePreview() { - const ou = document.getElementById("widget-ou").checked ? "1" : "0" - const or = document.getElementById("widget-or").checked ? "1" : "0" - const vl = document.getElementById("widget-vl").value +script handlePreviewOnHome() { + const ouEl = document.getElementById("widget-settings-ou") + const orEl = document.getElementById("widget-settings-or") + const vlEl = document.getElementById("widget-settings-vl") + + const ou = ouEl.checked ? "1" : "0" + const or = orEl.checked ? "1" : "0" + const vl = vlEl.value const newQuery = `?or=${or}&ou=${ou}&vl=${vl}` if (newQuery != window.location.search) { + ouEl.disabled = true + orEl.disabled = true + vlEl.disabled = true fetch("/api/widget/mock"+newQuery).then((r) => r.text()).then((html) => { document.getElementById("mock-widget").outerHTML = html const url = window.location.protocol + "//" + window.location.host + window.location.pathname + newQuery; window.history?.pushState({path:url},'',url); - }).catch(e => console.log(e)) + }).catch(e => console.log(e)).finally(() => { + setTimeout(() => { + ouEl.disabled = false + orEl.disabled = false + vlEl.disabled = false + }, 250) + }) } } diff --git a/cmd/frontend/routes/widget/index_templ.go b/cmd/frontend/routes/widget/index_templ.go index 59f4f47a..1db963b6 100644 --- a/cmd/frontend/routes/widget/index_templ.go +++ b/cmd/frontend/routes/widget/index_templ.go @@ -9,8 +9,8 @@ import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" import ( - "fmt" "github.com/cufee/aftermath/cmd/frontend/components" + cwidget "github.com/cufee/aftermath/cmd/frontend/components/widget" "github.com/cufee/aftermath/cmd/frontend/handler" "github.com/cufee/aftermath/cmd/frontend/layouts" "github.com/cufee/aftermath/cmd/frontend/routes/api/widget" @@ -51,84 +51,19 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Aftermath Streaming Widget

    Level up your stream with a real-time stats widget!

    Rating Battles
    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -140,7 +75,7 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { }() } ctx = templ.InitializeContext(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -154,7 +89,7 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = components.OBSMockup("/assets/widget-background.jpg").Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = components.OBSMockup("/assets/widget-background.jpg").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -166,22 +101,35 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { }) } -func handlePreview() templ.ComponentScript { +func handlePreviewOnHome() templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_handlePreview_f919`, - Function: `function __templ_handlePreview_f919(){const ou = document.getElementById("widget-ou").checked ? "1" : "0" - const or = document.getElementById("widget-or").checked ? "1" : "0" - const vl = document.getElementById("widget-vl").value + Name: `__templ_handlePreviewOnHome_85e3`, + Function: `function __templ_handlePreviewOnHome_85e3(){const ouEl = document.getElementById("widget-settings-ou") + const orEl = document.getElementById("widget-settings-or") + const vlEl = document.getElementById("widget-settings-vl") + + const ou = ouEl.checked ? "1" : "0" + const or = orEl.checked ? "1" : "0" + const vl = vlEl.value const newQuery = ` + "`" + `?or=${or}&ou=${ou}&vl=${vl}` + "`" + ` if (newQuery != window.location.search) { + ouEl.disabled = true + orEl.disabled = true + vlEl.disabled = true fetch("/api/widget/mock"+newQuery).then((r) => r.text()).then((html) => { document.getElementById("mock-widget").outerHTML = html const url = window.location.protocol + "//" + window.location.host + window.location.pathname + newQuery; window.history?.pushState({path:url},'',url); - }).catch(e => console.log(e)) + }).catch(e => console.log(e)).finally(() => { + setTimeout(() => { + ouEl.disabled = false + orEl.disabled = false + vlEl.disabled = false + }, 250) + }) } }`, - Call: templ.SafeScript(`__templ_handlePreview_f919`), - CallInline: templ.SafeScriptInline(`__templ_handlePreview_f919`), + Call: templ.SafeScript(`__templ_handlePreviewOnHome_85e3`), + CallInline: templ.SafeScriptInline(`__templ_handlePreviewOnHome_85e3`), } } diff --git a/cmd/frontend/routes/widget/live.templ b/cmd/frontend/routes/widget/live.templ index e5829ae9..3defc533 100644 --- a/cmd/frontend/routes/widget/live.templ +++ b/cmd/frontend/routes/widget/live.templ @@ -76,11 +76,24 @@ var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ. return nil, nil, err } - return layouts.Main, liveWidget(widget.Widget(account, cards, widget.WithAutoReload())), nil + var wopts = []widget.WidgetOption{widget.WithAutoReload()} + if v, err := strconv.Atoi(ctx.Query("vl")); err == nil && v >= 0 && v <= 10 { + wopts = append(wopts, widget.WithVehicleLimit(int(v))) + } + if v := ctx.Query("or"); v != "" { + wopts = append(wopts, widget.WithRatingOverview(v == "1")) + } + if v := ctx.Query("ou"); v != "" { + wopts = append(wopts, widget.WithUnratedOverview(v == "1")) + } + + return layouts.StyleOnly, liveWidget(widget.Widget(account, cards, wopts...)), nil } templ liveWidget(widget templ.Component) { - @widget +
    + @widget +
    diff --git a/cmd/frontend/routes/widget/live_templ.go b/cmd/frontend/routes/widget/live_templ.go index 028ae140..85bfe6c1 100644 --- a/cmd/frontend/routes/widget/live_templ.go +++ b/cmd/frontend/routes/widget/live_templ.go @@ -84,7 +84,18 @@ var LiveWidget handler.Page = func(ctx *handler.Context) (handler.Layout, templ. return nil, nil, err } - return layouts.Main, liveWidget(widget.Widget(account, cards, widget.WithAutoReload())), nil + var wopts = []widget.WidgetOption{widget.WithAutoReload()} + if v, err := strconv.Atoi(ctx.Query("vl")); err == nil && v >= 0 && v <= 10 { + wopts = append(wopts, widget.WithVehicleLimit(int(v))) + } + if v := ctx.Query("or"); v != "" { + wopts = append(wopts, widget.WithRatingOverview(v == "1")) + } + if v := ctx.Query("ou"); v != "" { + wopts = append(wopts, widget.WithUnratedOverview(v == "1")) + } + + return layouts.StyleOnly, liveWidget(widget.Widget(account, cards, wopts...)), nil } func liveWidget(widget templ.Component) templ.Component { @@ -105,11 +116,15 @@ func liveWidget(widget templ.Component) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } templ_7745c5c3_Err = widget.Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/cmd/frontend/routes/widget/preview.templ b/cmd/frontend/routes/widget/preview.templ new file mode 100644 index 00000000..382e2d26 --- /dev/null +++ b/cmd/frontend/routes/widget/preview.templ @@ -0,0 +1,89 @@ +package widget + +import ( + "github.com/cufee/aftermath/cmd/frontend/components" + cwidget "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/cmd/frontend/routes/api/widget" + "strconv" +) + +var WidgetPreview handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + widget, err := widget.AccountWidget(ctx) + if err != nil { + return layouts.Main, nil, ctx.Error(err, "failed to generate a widget preview") + } + if widget == nil { + return nil, nil, nil + } + + var withUnrated = ctx.Query("ou") != "0" + var withRating = ctx.Query("or") != "0" + var vehicles int = 3 + if v, err := strconv.Atoi(ctx.Query("vl")); err == nil && v >= 0 && v <= 10 { + vehicles = v + } + + return layouts.Main, widgetPreview(ctx.Path("accountId"), widget, withRating, withUnrated, vehicles), nil +} + +templ widgetPreview(accountID string, widget templ.Component, or, ou bool, vl int) { +
    +
    +
    +
    + Aftermath Streaming Widget +
    +

    + Level up your stream with a real-time stats widget! +

    +
    + @cwidget.Settings(handlePreview(accountID), or, ou, vl) + +
    +
    +
    + @components.OBSMockup("/assets/widget-background.jpg") { +
    + @widget +
    + } +
    +
    +
    +} + +script copyButtonAction() { + const url = window.location.protocol + "//" + window.location.host + window.location.pathname + "/live" + window.location.search + navigator.clipboard.writeText(url); +} + +script handlePreview(id string) { + const ouEl = document.getElementById("widget-settings-ou") + const orEl = document.getElementById("widget-settings-or") + const vlEl = document.getElementById("widget-settings-vl") + const button = document.getElementById("copy-widget-link") + + const ou = ouEl.checked ? "1" : "0" + const or = orEl.checked ? "1" : "0" + const vl = vlEl.value + const newQuery = `?or=${or}&ou=${ou}&vl=${vl}` + if (newQuery != window.location.search) { + ouEl.disabled = true + orEl.disabled = true + vlEl.disabled = true + button.disabled = true + + fetch("/api/widget/"+id+newQuery).then((r) => r.text()).then((html) => { + document.getElementById("account-widget").outerHTML = html + const url = window.location.protocol + "//" + window.location.host + window.location.pathname + newQuery; + window.history?.pushState({path:url},'',url); + }).catch(e => console.log(e)).finally(() => { + ouEl.disabled = false + orEl.disabled = false + vlEl.disabled = false + button.disabled = false + }) + } +} diff --git a/cmd/frontend/routes/widget/preview_templ.go b/cmd/frontend/routes/widget/preview_templ.go new file mode 100644 index 00000000..7bebfffb --- /dev/null +++ b/cmd/frontend/routes/widget/preview_templ.go @@ -0,0 +1,164 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.731 +package widget + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "github.com/cufee/aftermath/cmd/frontend/components" + cwidget "github.com/cufee/aftermath/cmd/frontend/components/widget" + "github.com/cufee/aftermath/cmd/frontend/handler" + "github.com/cufee/aftermath/cmd/frontend/layouts" + "github.com/cufee/aftermath/cmd/frontend/routes/api/widget" + "strconv" +) + +var WidgetPreview handler.Page = func(ctx *handler.Context) (handler.Layout, templ.Component, error) { + widget, err := widget.AccountWidget(ctx) + if err != nil { + return layouts.Main, nil, ctx.Error(err, "failed to generate a widget preview") + } + if widget == nil { + return nil, nil, nil + } + + var withUnrated = ctx.Query("ou") != "0" + var withRating = ctx.Query("or") != "0" + var vehicles int = 3 + if v, err := strconv.Atoi(ctx.Query("vl")); err == nil && v >= 0 && v <= 10 { + vehicles = v + } + + return layouts.Main, widgetPreview(ctx.Path("accountId"), widget, withRating, withUnrated, vehicles), nil +} + +func widgetPreview(accountID string, widget templ.Component, or, ou bool, vl int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    Aftermath Streaming Widget

    Level up your stream with a real-time stats widget!

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = cwidget.Settings(handlePreview(accountID), or, ou, vl).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyButtonAction()) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = widget.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = components.OBSMockup("/assets/widget-background.jpg").Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func copyButtonAction() templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_copyButtonAction_1df6`, + Function: `function __templ_copyButtonAction_1df6(){const url = window.location.protocol + "//" + window.location.host + window.location.pathname + "/live" + window.location.search + navigator.clipboard.writeText(url); +}`, + Call: templ.SafeScript(`__templ_copyButtonAction_1df6`), + CallInline: templ.SafeScriptInline(`__templ_copyButtonAction_1df6`), + } +} + +func handlePreview(id string) templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_handlePreview_7995`, + Function: `function __templ_handlePreview_7995(id){const ouEl = document.getElementById("widget-settings-ou") + const orEl = document.getElementById("widget-settings-or") + const vlEl = document.getElementById("widget-settings-vl") + const button = document.getElementById("copy-widget-link") + + const ou = ouEl.checked ? "1" : "0" + const or = orEl.checked ? "1" : "0" + const vl = vlEl.value + const newQuery = ` + "`" + `?or=${or}&ou=${ou}&vl=${vl}` + "`" + ` + if (newQuery != window.location.search) { + ouEl.disabled = true + orEl.disabled = true + vlEl.disabled = true + button.disabled = true + + fetch("/api/widget/"+id+newQuery).then((r) => r.text()).then((html) => { + document.getElementById("account-widget").outerHTML = html + const url = window.location.protocol + "//" + window.location.host + window.location.pathname + newQuery; + window.history?.pushState({path:url},'',url); + }).catch(e => console.log(e)).finally(() => { + ouEl.disabled = false + orEl.disabled = false + vlEl.disabled = false + button.disabled = false + }) + } +}`, + Call: templ.SafeScript(`__templ_handlePreview_7995`, id), + CallInline: templ.SafeScriptInline(`__templ_handlePreview_7995`, id), + } +} From e4dc803a120b9d838773fa9f767f4cbcc5839cdb Mon Sep 17 00:00:00 2001 From: Vovko Date: Thu, 4 Jul 2024 19:53:21 -0400 Subject: [PATCH 260/341] slightly longer timeout --- cmd/frontend/routes/widget/index.templ | 2 +- cmd/frontend/routes/widget/index_templ.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/frontend/routes/widget/index.templ b/cmd/frontend/routes/widget/index.templ index 666f5d68..d79ed062 100644 --- a/cmd/frontend/routes/widget/index.templ +++ b/cmd/frontend/routes/widget/index.templ @@ -73,7 +73,7 @@ script handlePreviewOnHome() { ouEl.disabled = false orEl.disabled = false vlEl.disabled = false - }, 250) + }, 500) }) } } diff --git a/cmd/frontend/routes/widget/index_templ.go b/cmd/frontend/routes/widget/index_templ.go index 1db963b6..c1c65f9c 100644 --- a/cmd/frontend/routes/widget/index_templ.go +++ b/cmd/frontend/routes/widget/index_templ.go @@ -103,8 +103,8 @@ func widgetHome(widget templ.Component, or, ou bool, vl int) templ.Component { func handlePreviewOnHome() templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_handlePreviewOnHome_85e3`, - Function: `function __templ_handlePreviewOnHome_85e3(){const ouEl = document.getElementById("widget-settings-ou") + Name: `__templ_handlePreviewOnHome_66f0`, + Function: `function __templ_handlePreviewOnHome_66f0(){const ouEl = document.getElementById("widget-settings-ou") const orEl = document.getElementById("widget-settings-or") const vlEl = document.getElementById("widget-settings-vl") @@ -125,11 +125,11 @@ func handlePreviewOnHome() templ.ComponentScript { ouEl.disabled = false orEl.disabled = false vlEl.disabled = false - }, 250) + }, 500) }) } }`, - Call: templ.SafeScript(`__templ_handlePreviewOnHome_85e3`), - CallInline: templ.SafeScriptInline(`__templ_handlePreviewOnHome_85e3`), + Call: templ.SafeScript(`__templ_handlePreviewOnHome_66f0`), + CallInline: templ.SafeScriptInline(`__templ_handlePreviewOnHome_66f0`), } } From 150cf4fc93a942facd35807f630db6dd7409e7ec Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 5 Jul 2024 11:09:04 -0400 Subject: [PATCH 261/341] generating website logos --- cmd/frontend/assets/gen.go | 28 ++++++++++++++++++++++++ cmd/frontend/public/icon-128.png | Bin 2378 -> 2261 bytes cmd/frontend/public/icon-16.png | Bin 0 -> 349 bytes cmd/frontend/public/icon-256.png | Bin 4905 -> 4671 bytes cmd/frontend/public/icon-32.png | Bin 692 -> 583 bytes cmd/frontend/public/icon-512.png | Bin 10321 -> 10453 bytes cmd/frontend/public/icon-64.png | Bin 1235 -> 1146 bytes internal/stats/render/common/v1/logo.go | 2 +- 8 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 cmd/frontend/public/icon-16.png diff --git a/cmd/frontend/assets/gen.go b/cmd/frontend/assets/gen.go index 5b233b97..f69bcc74 100644 --- a/cmd/frontend/assets/gen.go +++ b/cmd/frontend/assets/gen.go @@ -3,17 +3,20 @@ package main import ( + "fmt" "image/png" "os" "path/filepath" "github.com/cufee/aftermath/cmd/frontend/assets" "github.com/cufee/aftermath/internal/stats/render/common/v1" + "github.com/disintegration/imaging" "github.com/rs/zerolog/log" ) func main() { generateWN8Icons() + generateLogoOptions() } var wn8Tiers = []int{0, 1, 301, 451, 651, 901, 1201, 1601, 2001, 2451, 2901} @@ -54,3 +57,28 @@ func generateWN8Icons() { } } } + +func generateLogoOptions() { + log.Debug().Msg("generating logo options") + + for _, size := range []int{16, 32, 64, 128, 256, 512} { + filename := fmt.Sprintf("icon-%d.png", size) + + opts := common.DefaultLogoOptions() + opts.Gap *= 10 + opts.Jump *= 10 + opts.LineStep *= 10 + opts.LineWidth *= 10 + + img := common.AftermathLogo(common.ColorAftermathRed, opts) + f, err := os.Create(filepath.Join("../public", filename)) + if err != nil { + panic(err) + } + err = png.Encode(f, imaging.Fit(img, size, size, imaging.Linear)) + if err != nil { + panic(err) + } + f.Close() + } +} diff --git a/cmd/frontend/public/icon-128.png b/cmd/frontend/public/icon-128.png index 7c8566d8f547ac7ce318a8c074353e01ea63e21e..0ead61004b181ab32db55bac4aeb02fa428137bb 100644 GIT binary patch literal 2261 zcmbtWc{tRK7XHl`48HKm&d{}!<%%ZD7-JtITXsI8EEOM(ozYArD*F<$lQAVJl-(#9 zJHy;a;y2eWdqQY@{=Cb7_x^F-^SsadJm-(|p7WfOY;(oz45uh3003vqvBq|%nQ$6$ zaJExis}d;(0C=IfvBA~I!nLBn$L z)dwGxL=94^^zkfuov>g6%bf$6oUvc*W9T%L0~WJnuwo4}L5DaQj*0CX+%O5Tl?@BbXz@e;;*x!Pi?|M=i+lbEvQPu+Bqrg?n{|4zJ3&Cu zHog2(6Fua7F#gH!q0QZG=y251Wio5}cP2Zr5yy|P81HP7;?u23zp7z`%Z_p2wNEW; z$aVz+<}WP{`U@da}FVv9QGZ@Qf1!&j(piB@W zMqC%Zbs-=U39BB(x_bdR81xS(z>@_HQ`W98rKdnT@AaFG6t()8^M=5JZxJ?u&B2xKYkRwt0qpk0bM2!E3eWIJF zbP^US(^Wg@68X{{ruZRcz8A0g!Q!XQknU?!!cCqDAtP} zREI}hzRYrEylq6N0CwOsoT?{#!=V%loWQm$Q}2}xN^-%g`4#vGzbNkr(jNT@fwQpA zPD<0^Ls5wMkl=wN#FLfDVL38|Qt5obqJL#zwUpkxwj^eT8?&F~MyaZi2V*O!<0}I~ zs5yVjeeFR*TUj6(X3oC3M-L}Sd+oFs4$zh({P6}wfQD89L-L{#UY_pGBE0^u>sOe` z1?mM$`7sp1>+^*4=6h|Pj(WCxx+!nCctwxMh4qmYD{tzT)e*oSz4kWV`Uw51lM*{F zaFSnio3VTAbL)jOC}^B!+*NZ#+I0!pchcCMx(eIXR~c%JfFjd8zgv5z3?u9vZUNuJ;=`>q1+iHTa0~i{jU{88+>K zK|M9<)Mt;qvKnXWz0K#Aw7qvO`LsTx#2|`)&HqZ|>KB3;@e=R~*7NLjgwZ$SL0S4z zPd$hAx=XNUIt^^3c%O5ZJZ3#+8syuG!y#Dt5W{%1nm*oak?%dCKWj%LJ)6nz)%|P^ z4rYxzhhaaFoyPt)VJ)al7j2g|msf?V=02jU=b^tUezkOipc=0QPe3JEYBN73kHjD4 zlbE~>-1+bYsr7^Y8<{vT+A9SWA1Nw`0?8*mw7fy)S8ZtIO=)lg2x7?{n%a#9^(ZlA zcUnnrordF0OEN-Hw`9Nc9uZCIk)A4sx3}pVA(~X*lfZ>F#6Ql1$lk!gH^{L2h}pcw zh7f65YVUGJrP@YjhYP)m@}|N1Yhpv%NP07T_Z4=Nob}yI{<$5^ZBa4WCrAfvwVTjY z@UDM!cD)=uT{WYGKjzEV4`#%6s+wWcSZcTFItwTH*~#gs#{sU%xWwzlJ}5s1N5DAp zYN5fX{l7+?pl)ssEd*T(D zwuy9|x00NDgO^w^u!(-Oe3$$7`h`IRv;%@-G^v>iKkJdjnH(ETmD(3~kA}MuJiA-g zQ}<*>X@%+WM|@klD_bG?sLvd+Y?Da%S-IEgKC^OxukELN-KUfqJ_`|B5h-mG_IhW- zR4Fv!eR_B$?ywKPifv&IZ?ZxyBx&$;%B``D~8qYhG^dZ%kj(ms~kzo(U z#yv)2#SpAa$I!1xk;;ENQhl7G11eNZ+xM!3Haw&LMm7n(JNPIA$4$!qXnV3HQKs{Zo#gB!e*8ThLH z&V9GmO6!&)PPy@pFh7-&7lrNhi<$@Pln7nni?V&$$i=)B4bFmU<+SNNA_9Q1%IU*DR$Rfhe`$lr~z}c+C^Tl1vU#iI(V+@~b1y c(+}n3gzC+B`Da42&M5)RO|BT%8hYLP3tu-RLjV8( literal 2378 zcmb7GX*d*$8lEwWrNNBQ7-G`Yh~XsaWEqSmjAY2zn=vF@DT8Cn(kyZ8rYSA5OeslH z$y&;gE{?L*h{$pxj_t~1Fk?CHbAH_CKKI9c&X4c=etggO<9na?eZMc=#o1m_0i^%{ z02E0M#8caqxgD``(Cys$&><86kVlY+1WH`NT%li>YTIsRQ-eiVSdR5SPnNMAQNtKh zJw^9x{SaMkpO3sB_uXATm?|oht})WjdCyL!K{03fE#{gTjNbDM1b% z_s4elaV9**l2^WdMPGupDaq$#5w!x!Fn5*EP8hk6L?kbPsU~n*35TTN#Ygh&bgESV z`PRS|D^a*;pdL6{uywO)?Vf}B+#@?j&EiMcyBBdlJkFCJ7?4P@V@!d`yJ`> z?OJJP7~jq}v(8O+gr8S49hR*^5K%`JH>m6!<<2X9$W{*ksq>ISkno<8+0M&gqvqe8 zJ5meTUmED5A#UoApfL@29a|Nh%C9}q$3p~?!MFUpT9?%MU5PK3CU3A%@D}`aGP_++ zV9zNSFA*P}a&-u#Xvff;iw+As(Y>Vs89L8Y0ihoC9~v6<nC{oMICi;YZ0SU|rxbTNYKM=?>Ev;Q8?a2j3J)pi7^{jB zPd>Mva-`r&Gcc7hNST(u0nGov`7kXW*oTPNI9*11BLofz_X;x1E77iP0~A?d&B2?T zu3Y_b`Q7w z%+2H+s&#;HtGggoXl+oC(?B(>^%XC>Tki8ucF2GZha*PJ8LHqDHVa3q=r_oJMeJ)N z_4I1GC%V=F`_3?>Pj-oSS!~ z&6_lzfnq$~m^Z0e>1$cUOd&-Oof7CosD?kD7zZgk!7T8@A#i$>IAcM26733cd6B}r z>Q!pjS_kNENctHgcYqstY66*Yo#37RLXn<^{QFe<52Dzo@j#yYfw(bk2SZN|{;kGS z8mD$kF8Fe_e|eezxoukX7sXe9H!LSNK(z#Gs}ilP^y-ux@Y3u^Ludma^VV=xI) zD`X`O8%lX{V+!1Y`L1Y#%SpGreKgz}D@ens`>&QZ)}O8UxTS80qGPC`G@u+>Z0lmLOD*S0Q2xEbjWox*Gr)n4$=;0W_ zSieTxz|X4DVXpi_z?phAD?=K=O(DMq2wU5B18k+tX@jaHFt4Qf?9^Om@TF7Ey4!jv zmGO$Xl-z@DmNEQjk5$6KTP``8) zF?4~CPO$A;r>21q=`(t)b5)Gya0FZ>+XhwfK`hj(%p(^-=DTc20 zgoYCLGo_D)ixah{VVr2BIL~tFu=(7;q<=K72*y6DHQ9ndjLJy-AhaKPXx|)9!Nmhz zP2GsxMjcxqnb& zICe$fm9M!)EOe!0`+7WBC*xkeb8EP5y;5G%a&2A9W>G|dOTR4KmR$nFbo3AFD4N~- zt?Q~bS4tVYVP~Vvmdqzh$;19`XKw71R&7DOtl3kEscssLgUPhKLTc()k%m2?NH!V9 zVOiQJ1X?orU&{mCv8J19UP=L043AfPFy}_#OU5qGZ&p#cYw8$nN;V^256m2Iq2iBC za_fIL*%Sl;rZve)8=~fw%&eFi-{o*w)%$l62cm!J3YJE+ZzRkWM*zVuqYi$CF5z>3 ziss~{?4ANCe}W$z+Jpp#Zwh;FHUB80wv|_^#h+)+%?hqAxI9i?0k%f)HK2Ysott{e zo|N6oTS^nkgx8b({!P9Q$S5r0003bNklNYed0YAXZJDT@zk7)(PHFoHc3nKRB{*l$5D8nXC@B;00960_gg=A=>A>U00000NkvXXu0mjf8}gje literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/icon-256.png b/cmd/frontend/public/icon-256.png index ac3ed4fa79383f352d421b8423dcbd720ac0154b..60feffa2fbf05f6fca3bf1438e00f164c38b836f 100644 GIT binary patch literal 4671 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 literal 4905 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 AdH?_b diff --git a/cmd/frontend/public/icon-32.png b/cmd/frontend/public/icon-32.png index 09440087a38dd5fa71c6c56c6fca09dee349acc6..cf84b0338966cc7934059b46e4efe500588eafbd 100644 GIT binary patch delta 571 zcmV-B0>u5a1;+%C7k?iJ1^@s6hE~sv0006GNklL>_SP#3|eV^u2RkGAQP+k6Ah zHlg9BZaEP0Zti*Ko_Fr|d-FzQqSQEtCo#NMXfEgI)N&1A3V$c#GnFfnoj}}M45~9o z0{eoYfuVuEs<+fg)t>tP0$$)#7f$**gNt~LuSI~RH-FpXze zz~Nj2aqmKCO@DVI^fGV>Gq{4u0Srur#!P7TZJ=J6AM~ZAwnK9lAs1iqr*fTzK@7aa zt;%&4vfHrGT%(-FeET{N{U%Ut#~*05P28z+S}`ZM8~bVC8}8yV9#;0giuBuktYR_l z+1o&=k+$JHPGO01btkFNY?T_1@iaYYp}9*a9Wtfa=YJGS*lLd(|8PpPiFqj6@-yD1 z1Ww=q&XSai^iuJAo8OLgy|X|k`d)}Fsd6@AT(&ifbi&?X+u284O$H`#H2U;(6y{>Q zey!j`KT-C;dpyFi(5%H8^;mPFR}H+wb!>JL$ix@ij9UBEt?Nc2qOBOG1v_zP{|R*v zwX)uMh%~5)&6rMWwrUac8>C)-wbwomhO!lFti_rue**vj|Njrxe<%CCg+>4X002ov JPDHLkV1m0;6*K?< delta 681 zcmV;a0#^OU1hfT^7k?cH1^@s6%bsqB0007eNklabEZGWL#82y@uC$|6ynt>w~ z<6Hu~`~rNbKK^S0HV6Ng00mKc>g_X&ZPNrme;2gghyIQ#wvO1=wIu@7`E~;rKs&2= zbUGW|nt?ZC2E>n-`BkX=q|cTJ*nDgYNc}5T`gVbL33L{w9&MbqCs2-^2iXsE_k^+U zxdedM26_Wz27ijrH!h&K4`cwe2u-hmH#H-q6P4>h>VO8Q&(PhGZ-ZNF9JTS!dth6@ znkH1Px4Pz=&(L(~P9tasW$_9H`;}xaVnY}r?kW|YJUk>4_bEHeOMMoekk0uS%FaL zE4zg1I@w4fi+~WYQ}AAprz?S1&C%TP*eBd5={j$LhVCd}&yvE>1WaCpp_glYZ7#A+ zINz0Znkptx&IHhY6c$UsT++P!5&Xx%XONOagb$ah5mnk0DS?iI6l0DPiJwJp3pDP5 z*V7)HJbwk=6DSNrp)po&lq%f*0!x=+YEZbfke;ndG3Glp2tThl4$}~E+BZs}DI;+?87%ZHFk(xVLb{Lm& zmhM#aRg~ob!cXAO0`FmdF;-r+&UxY9mQm|5t0Mm*?EC;6wAjA_00960E_lS=Dp@Wy P00000NkvXXu0mjffrdN^ diff --git a/cmd/frontend/public/icon-512.png b/cmd/frontend/public/icon-512.png index d09fa52fbf5bde2e762c895d9747b700ed7e730e..39d71f287f3f54302b70ec0a6ab7f93821a4d5ff 100644 GIT binary patch literal 10453 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 Date: Fri, 5 Jul 2024 11:36:18 -0400 Subject: [PATCH 262/341] generating og images --- cmd/frontend/assets/bg-default.jpg | Bin 0 -> 36041 bytes cmd/frontend/assets/gen.go | 81 ++++++++++++++++++++++++ cmd/frontend/components/navbar.templ | 2 +- cmd/frontend/components/navbar_templ.go | 2 +- cmd/frontend/public/og-widget.jpg | Bin 40334 -> 16098 bytes cmd/frontend/public/og.jpg | Bin 40334 -> 16098 bytes cmd/frontend/public/og.png | Bin 14493 -> 0 bytes 7 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 cmd/frontend/assets/bg-default.jpg delete mode 100644 cmd/frontend/public/og.png diff --git a/cmd/frontend/assets/bg-default.jpg b/cmd/frontend/assets/bg-default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d752d09c77a0818bb8bd2f54bb9fe3233942504a GIT binary patch literal 36041 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 literal 0 HcmV?d00001 diff --git a/cmd/frontend/assets/gen.go b/cmd/frontend/assets/gen.go index f69bcc74..53476f65 100644 --- a/cmd/frontend/assets/gen.go +++ b/cmd/frontend/assets/gen.go @@ -11,12 +11,14 @@ import ( "github.com/cufee/aftermath/cmd/frontend/assets" "github.com/cufee/aftermath/internal/stats/render/common/v1" "github.com/disintegration/imaging" + "github.com/fogleman/gg" "github.com/rs/zerolog/log" ) func main() { generateWN8Icons() generateLogoOptions() + generateOGImages() } var wn8Tiers = []int{0, 1, 301, 451, 651, 901, 1201, 1601, 2001, 2451, 2901} @@ -82,3 +84,82 @@ func generateLogoOptions() { f.Close() } } + +func generateOGImages() { + log.Debug().Msg("generating og images") + + imageWidth := 512 + imageHeight := imageWidth * 2 / 3 + logoSize := imageHeight * 2 / 3 + borderWidth := 2 + + { + filename := "og-widget.jpg" + opts := common.DefaultLogoOptions() + opts.Gap *= 10 + opts.Jump *= 10 + opts.LineStep *= 10 + opts.LineWidth *= 10 + + logo := common.AftermathLogo(common.ColorAftermathRed, opts) + ctx := gg.NewContext(imageWidth, imageHeight) + + bg, err := imaging.Open("./bg-default.jpg") + if err != nil { + panic(err) + } + + ctx.DrawImage(imaging.Blur(imaging.Fill(bg, imageWidth, imageHeight, imaging.Center, imaging.Lanczos), 30), 0, 0) + ctx.DrawRoundedRectangle(float64(borderWidth), float64(borderWidth), float64(imageWidth-borderWidth*2), float64(imageHeight-borderWidth*2), 20) + ctx.SetColor(common.DefaultCardColorNoAlpha) + ctx.Fill() + + ctx.DrawImageAnchored(imaging.Fit(logo, logoSize, logoSize, imaging.Linear), imageWidth/2, imageHeight/2, 0.5, 0.5) + + f, err := os.Create(filepath.Join("../public", filename)) + if err != nil { + panic(err) + } + + err = imaging.Encode(f, ctx.Image(), imaging.JPEG) + if err != nil { + panic(err) + } + f.Close() + } + + { + filename := "og.jpg" + opts := common.DefaultLogoOptions() + opts.Gap *= 10 + opts.Jump *= 10 + opts.LineStep *= 10 + opts.LineWidth *= 10 + + logo := common.AftermathLogo(common.ColorAftermathRed, opts) + ctx := gg.NewContext(imageWidth, imageHeight) + + bg, err := imaging.Open("./bg-default.jpg") + if err != nil { + panic(err) + } + + ctx.DrawImage(imaging.Blur(imaging.Fill(bg, imageWidth, imageHeight, imaging.Center, imaging.Lanczos), 30), 0, 0) + ctx.DrawRoundedRectangle(float64(borderWidth), float64(borderWidth), float64(imageWidth-borderWidth*2), float64(imageHeight-borderWidth*2), 20) + ctx.SetColor(common.DefaultCardColorNoAlpha) + ctx.Fill() + + ctx.DrawImageAnchored(imaging.Fit(logo, logoSize, logoSize, imaging.Linear), imageWidth/2, imageHeight/2, 0.5, 0.5) + + f, err := os.Create(filepath.Join("../public", filename)) + if err != nil { + panic(err) + } + + err = imaging.Encode(f, ctx.Image(), imaging.JPEG) + if err != nil { + panic(err) + } + f.Close() + } +} diff --git a/cmd/frontend/components/navbar.templ b/cmd/frontend/components/navbar.templ index d96c1db4..a2502d76 100644 --- a/cmd/frontend/components/navbar.templ +++ b/cmd/frontend/components/navbar.templ @@ -31,7 +31,7 @@ templ navbar(props navbarProps) {

    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 LNkvXXu0mjfTChFp literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/rating/calibration.png b/cmd/frontend/public/rating/calibration.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb133a82817b856c40187e40ae193d01fb45ef0 GIT binary patch literal 2231 zcmdT``#;nBAOGxPb4XTkmI^zCI-+r;zAmvOHcm(#gjk0!!pw0S&6tpDVTmu2OFKAW ze0|+E8-WHW)ITHDo~_N?VX-*&0}Ztm|Mu}KWdGPZ zWL5tzV+g-X%g|mgJ+r<%$rzqdUUgdkq6$Jg!}r#8EPUAc6`CV>_@BJX!-ws*!a*9KOR{kL9$MFHh|mi?e~oaOu#cb!1X-_+4~`|w0|(PbeR%P z+ZX0+b^!nL=I1iOHZ;sPluQq3OXl|S4q+M;p-G&H*hyaLsRjLrknrs|?+EYiy`S2! zKeyOY6mq)~<)$B&0EZ?vKO%jk!L}8Js!B%`B zPI)Y( z7O0wQUEgyaJnq|HAl(83!19Cx?HZVR!G;FNNI2l2yInc1?$-Z$+3OdlJ_-!(65hNK zOKcqdD}lVvEVJNV1dG~>)3=mIUFm;4?`7cBSFou1Lj6i@j3@u1SR2+f^&_Ui-3at~ z!lXXI>EAs|=3CTk)yVh-5T05y_Vv+JHW2XV1<2uQW1~e#FfQoKeoUi#my%>Ca0w|h zOg#mAUqz9=jf`fPml+`19o)t(K)K87KM)+-u+S<}L$YS^*D~r~(Q1}mBb2zUGopjH zA}XKx+};iuv=d-Na>Fyq%(q|fjl>ZoVOZdKVDgr$^Eev~_K+4mGU_A%xXH!IgQ9-w_m|5y_hc!c z=1RLHS%!?BKJ?-=WVh*QHrm9l^{C6CalE^1kw1N|l|tziJQsckjC*6ZX~*400q@t+ z>oba|nNlNd5ga_6P{Cu%WvSDg`AO6qde>D5^pNiZMnnTOjehQvZC!AFM7oT2xovIU zHS3I9G9i#7uzNz;=D4K9ZYqbGKp(DyB~1*jW=B*|W9XAJuh+M74`)iG9W(IqD(vqiS7HyJ&rdMN;}k z!&P4$nmsPbWQI>85rQ}0Z_Q615fzKOprnTXmPLc>dPL&q5Hd!%jpq!}oJvWjax34A zXHrU-_EShX!Mh&BJa2@CFTL$>q9v$SRZ_$3n@WKTmWxyuim4uP$x=Gbqc(rDE`2TT zB@I{F-+tLpczikqCJ4UGL?4%nZIXuKCP^$6iLp2lLZUF4{<_`NS}F1IvCsJAXiJHO zih)9p*0*cV*Fyf#RM@YIRyGmVv6bPO4Da2BLUQN=qSQpP|99^AKI~8p^_GgnMMG2Z zWKbA>O{ga_0I$(6hOeOJdFT0a;zZ!#+s2>F{Wfj86{9^ZiCjvqBN-Afk+x_P_N_;H ziBLV20@#e^LFRcy(U6iR#`jnLT+rUMhXxRHX{^EM7J~cx_Gn_R`t*{Iji#>mCnRyR z*J7UL#&4B&CR}WB=I=YxBgJnOsL@|9%2*XMQk;PZojquk^${hJXsb)w0|2Dw#5r!f zp5q1*)eY;*q&$kXQ^cW)^3)`E89GyF#aW`ImkoDPn>Ro!19nG^{qkTLg|9o>rX=80!Il)RR4oMUUywQ!fHElJypT3=1l=NrK0tTCg) z%qzxQmr@STer!Wfw);}alxp#MX=edoUmOCl_XrVV2>qvmG+y=x+1`E)sg9Y?88nkM N0O7oYHOq>4^Zy4%*~|a{ literal 0 HcmV?d00001 diff --git a/cmd/frontend/public/rating/diamond.png b/cmd/frontend/public/rating/diamond.png new file mode 100644 index 0000000000000000000000000000000000000000..8ae642921cf8723d44e57f9d9c65dd14c87b3402 GIT binary patch literal 58867 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