feat: Admin model extension and superadmin bootstrap script - #53
Merged
codebestia merged 1 commit intoAug 21, 2026
Merged
Conversation
…cript Admin had no way to get a row into it. There is deliberately no admin self-registration endpoint — unlike merchants, who provision themselves on first wallet sign-in — so the first admin has to come from a terminal script, and every later admin is created by one that already exists. Extends Admin with `name` and a nullable self-referential `createdBy`, null only for the script-bootstrapped admin. `active` and `isSuperAdmin` were already added by the admin refresh-token migration, so they are unchanged here. scripts/create-superadmin.ts validates the address with StrKey.isValidEd25519PublicKey — the same check createChallengeController uses — and refuses an address that already has an Admin row rather than overwriting or duplicating it. Both refusals exit non-zero without touching the database. It is CLI-only by design: no HTTP route reaches this code. The `name` column is added with a temporary default that is then dropped, so the migration cannot fail on the NOT NULL for a dev database that had an Admin row hand-inserted. The end state matches the schema exactly. Also widens the format/format:check globs to cover the new scripts/ directory, so it does not escape the formatting the rest of the source is held to.
codebestia
approved these changes
Aug 21, 2026
codebestia
left a comment
Contributor
There was a problem hiding this comment.
LGTM!
Thank you for your contribution.
5 tasks
Contributor
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adminhad no way to get a row into it. There is deliberately no admin self-registration endpoint — unlike merchants, who provision themselves on first wallet sign-in — so the first admin has to come from a terminal script, and every admin after that is created by one that already exists. This adds the model fields that make that shape expressible and the script that bootstraps the first row.Since every other admin issue depends on this model's final shape, the field set is the thing to scrutinise here.
Schema
One correction to the issue's premise:
activeandisSuperAdminalready existed. They were added by the20260821000000_add_admin_refresh_tokenmigration, so this PR only adds the two fields that were genuinely missing:name String createdBy String? // self-relation FK → Admin.id, null only for the bootstrap admincreatedByis a nullable self-relation (AdminCreatedBy),ON DELETE SET NULL— Prisma's default for an optional relation. Null means "no creator", which is true of exactly one row: the one the script makes.The migration adds
namewith a temporaryDEFAULT ''and then drops the default. Nothing can have written anAdminrow before now, so the table is empty in practice, but this keeps the migration from failing on theNOT NULLfor anyone with a hand-inserted dev row. The end state matches the schema exactly — no default survives.Script
scripts/create-superadmin.ts, wired up asnpm run admin:create-superadmin:StrKey.isValidEd25519PublicKey— the same checkcreateChallengeControlleruses, so an address the network would reject is rejected here too (including a well-formed one with a bad checksum)Adminrow. Re-running with the same address is an operator mistake worth surfacing, never an overwrite and never a silently swallowed no-opisSuperAdmin: true,active: true,createdBy: null--flag=valueand--flag value, since operators type these by handparseArgsandcreateSuperadminare exported so the guard rails are testable;main()runs only when the file is executed directlyCLI-only by design. No HTTP route reaches this code — I grepped
src/routes/andsrc/controllers/to confirm none exists.Testing
npm test— 391 tests / 45 suites passing (+11 new).prisma validateclean,tsc --noEmitclean, eslint 0 errors, prettier clean.Verified end-to-end from the terminal:
tests/unit/create-superadmin.test.tscovers argument parsing in both spellings, invalid and bad-checksum addresses, missing/blank name, a--nameflag swallowed by the next flag, the exactcreatepayload, and duplicate refusal (both when the existing admin is and is not already a superadmin).Also in this PR
Widened the
format/format:checknpm globs fromsrc/**to{src,scripts}/**, so the new top-level directory does not escape the formatting the rest of the source is held to. I left.github/workflows/alone — its prettier step hardcodessrc/**, so CI still will not checkscripts/. Worth a follow-up if you want that enforced.Acceptance criteria
Adminmodel updated (name,createdBy;active/isSuperAdminalready present)prisma migrate devruns cleanly — needs a live database, see abovenpm run admin:create-superadmin -- --address=<valid G...> --name="Jane Doe"creates a row withisSuperAdmin: true,active: true— unit-tested, needs a live database to confirmCloses #39