From 0ef650d94afd4e7081769ec0f9b56b1ca141ce97 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 15 Apr 2026 18:09:26 +0200 Subject: [PATCH] chore(dev): run mana-credits locally and gift sync to dev users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two halves of the same "why is sync inactive in dev" fix: - package.json: new dev:credits script and mana-credits added to the dev:mana:servers concurrently group. The service was never started by pnpm dev:mana:all, so the frontend's GET /api/v1/sync/status failed, syncBilling.load() caught the error and defaulted to inactive — while mana-sync (Go) was actually fail-open on the billing check, making the UI indicator lie about the backend state. - scripts/dev/setup-dev-user.sh: after the existing email-verify + tier-lift UPDATE, upsert a row into credits.sync_subscriptions with is_gifted=true. Mirrors what POST /api/v1/admin/sync/:id/gift would do, so every new dev user gets Cloud Sync from the first login without a separate admin call. The credits schema lives inside mana_platform, so no new database needed — just a second statement in the same psql heredoc. Existing dev users (tills95, tilljkb, rajiehq) were backfilled manually with the same INSERT … ON CONFLICT DO UPDATE once; future runs of setup-dev-user.sh stay idempotent. Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 3 ++- scripts/dev/setup-dev-user.sh | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index da40d382e..b8f9ed836 100644 --- a/package.json +++ b/package.json @@ -221,6 +221,7 @@ "cf:projects:create": "echo 'Creating Cloudflare Pages projects...' && npx wrangler pages project create chat-landing --production-branch=main && npx wrangler pages project create picture-landing --production-branch=main && npx wrangler pages project create mana-landing --production-branch=main && npx wrangler pages project create cards-landing --production-branch=main && npx wrangler pages project create quotes-landing --production-branch=main", "dev:search": "cd services/mana-search && go run ./cmd/server", "dev:crawler": "cd services/mana-crawler && DATABASE_URL=postgresql://mana:devpassword@localhost:5432/mana_platform go run ./cmd/server", + "dev:credits": "cd services/mana-credits && bun run --watch src/index.ts", "dev:notify": "cd services/mana-notify && go run ./cmd/server", "questions:dev": "turbo run dev --filter=questions...", "dev:questions:web": "pnpm --filter @questions/web dev", @@ -256,7 +257,7 @@ "dev:manavoxel:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:manavoxel:web\"", "dev:media": "cd services/mana-media/apps/api && bun run --hot src/index.ts", "dev:geocoding": "cd services/mana-geocoding && bun run --watch src/index.ts", - "dev:mana:servers": "concurrently -n auth,sync,api,media,crawler -c blue,magenta,yellow,green,cyan \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:api\" \"pnpm dev:media\" \"pnpm dev:crawler\"" + "dev:mana:servers": "concurrently -n auth,sync,api,media,crawler,credits -c blue,magenta,yellow,green,cyan,red \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:api\" \"pnpm dev:media\" \"pnpm dev:crawler\" \"pnpm dev:credits\"" }, "devDependencies": { "@mana/eslint-config": "workspace:*", diff --git a/scripts/dev/setup-dev-user.sh b/scripts/dev/setup-dev-user.sh index 05991ec89..d0698577d 100755 --- a/scripts/dev/setup-dev-user.sh +++ b/scripts/dev/setup-dev-user.sh @@ -103,12 +103,22 @@ create_user() { ;; esac - # Step 2: idempotent SQL — verify email + lift tier. + # Step 2: idempotent SQL — verify email, lift tier, gift sync. # Quoting note: the table is auth.users (Better Auth schema), columns # are email_verified + access_tier. The access_tier enum lives in the # public schema (Drizzle's pgEnum default), so the cast is just # `::access_tier`, NOT `auth.access_tier`. We bind email + tier as # psql vars to dodge any quoting weirdness. + # + # The sync_subscriptions upsert makes Cloud Sync work out of the box + # for dev accounts. `is_gifted = true` means the recurring-billing + # cron in mana-credits skips the row and sync stays on indefinitely — + # same effect as if an admin had called POST /api/v1/admin/sync/:id/gift. + # Without this, the sync-billing status endpoint returns active=false + # and the UI shows "Lokal" even though the Go mana-sync would fail-open + # (which makes the inactive indicator a dev-only lie). The sync schema + # lives inside mana_platform (credits.sync_subscriptions) — it's only + # a separate *service*, not a separate DB. PGPASSWORD="${DB_PASS}" psql -q \ -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d "${DB_NAME}" \ -v ON_ERROR_STOP=1 \ @@ -120,6 +130,20 @@ create_user() { access_tier = :'tier'::access_tier, updated_at = NOW() WHERE email = :'email'; + + INSERT INTO credits.sync_subscriptions + (user_id, active, billing_interval, amount_charged, + activated_at, is_gifted, gifted_by, gifted_at, + created_at, updated_at) + SELECT id, true, 'monthly', 0, + NOW(), true, 'setup-dev-user.sh', NOW(), + NOW(), NOW() + FROM auth.users + WHERE email = :'email' + ON CONFLICT (user_id) DO UPDATE + SET active = true, + is_gifted = true, + updated_at = NOW(); SQL # Verify final state and report