From 17150c405d25555fb9aacdb5af55098135677206 Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Mon, 10 Aug 2026 10:07:29 +1000 Subject: [PATCH] ops: live SMS creds on .13; seed-admin cmd in make db-up (admin survives DB resets) --- Makefile | 6 +-- frontend/cmd/seed-admin/main.go | 61 +++++++++++++++++++++++++++++ frontend/internal/db/querier.go | 1 + frontend/internal/db/queries.sql | 6 +++ frontend/internal/db/queries.sql.go | 30 ++++++++++++++ 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 frontend/cmd/seed-admin/main.go diff --git a/Makefile b/Makefile index 2c87e23..612962c 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,10 @@ SESSION_SECRET ?= dev-secret-change-me # dev only — set a real secret in pro export DATABASE_URL export SESSION_SECRET -.PHONY: db-up seed seed-registry run build generate psql +.PHONY: db-up seed seed-registry seed-admin run build generate psql -db-up: ## apply db/schema.sql (embedded migrator, no psql needed) - cd frontend && go run ./cmd/migrate -schema ../db/schema.sql +db-up: ## apply schema + ensure the admin user exists + cd frontend && go run ./cmd/migrate -schema ../db/schema.sql && go run ./cmd/seed-admin seed: ## insert test tag codes TEST000001..TEST000025 cd frontend && go run ./cmd/seed diff --git a/frontend/cmd/seed-admin/main.go b/frontend/cmd/seed-admin/main.go new file mode 100644 index 0000000..be1c2d0 --- /dev/null +++ b/frontend/cmd/seed-admin/main.go @@ -0,0 +1,61 @@ +// Command seed-admin creates or refreshes the admin user so it survives DB +// resets (test suites drop the users table). Runs as part of `make db-up`. +// Env: ADMIN_EMAIL (default admin@where-woof.com), ADMIN_PASSWORD (default +// dev-only "AdminPass123!"), ADMIN_NAME (default "Admin"). +package main + +import ( + "context" + "fmt" + "os" + + "github.com/jackc/pgx/v5/pgtype" + "github.com/jackc/pgx/v5/pgxpool" + + "wherewoof/frontend/internal/auth" + "wherewoof/frontend/internal/db" +) + +func main() { + dsn := os.Getenv("DATABASE_URL") + if dsn == "" { + fmt.Fprintln(os.Stderr, "error: DATABASE_URL not set") + os.Exit(1) + } + email := os.Getenv("ADMIN_EMAIL") + if email == "" { + email = "admin@where-woof.com" + } + password := os.Getenv("ADMIN_PASSWORD") + if password == "" { + password = "AdminPass123!" // dev default; override in production env + } + name := os.Getenv("ADMIN_NAME") + if name == "" { + name = "Admin" + } + + hash, err := auth.HashPassword(password) + if err != nil { + fmt.Fprintln(os.Stderr, "hash:", err) + os.Exit(1) + } + + ctx := context.Background() + pool, err := pgxpool.New(ctx, dsn) + if err != nil { + fmt.Fprintln(os.Stderr, "connect:", err) + os.Exit(1) + } + defer pool.Close() + + if _, err := db.New(pool).UpsertAdmin(ctx, db.UpsertAdminParams{ + Email: email, + PasswordHash: hash, + Name: pgtype.Text{String: name, Valid: name != ""}, + }); err != nil { + fmt.Fprintln(os.Stderr, "upsert admin:", err) + os.Exit(1) + } + fmt.Println("admin ready:", email) +} diff --git a/frontend/internal/db/querier.go b/frontend/internal/db/querier.go index 634ab6d..9168151 100644 --- a/frontend/internal/db/querier.go +++ b/frontend/internal/db/querier.go @@ -36,6 +36,7 @@ type Querier interface { SetScanPhone(ctx context.Context, arg SetScanPhoneParams) error SetTagStatus(ctx context.Context, arg SetTagStatusParams) error UpdateTagDetails(ctx context.Context, arg UpdateTagDetailsParams) (Tag, error) + UpsertAdmin(ctx context.Context, arg UpsertAdminParams) (User, error) } var _ Querier = (*Queries)(nil) diff --git a/frontend/internal/db/queries.sql b/frontend/internal/db/queries.sql index 95be2b3..aeadda4 100644 --- a/frontend/internal/db/queries.sql +++ b/frontend/internal/db/queries.sql @@ -46,6 +46,12 @@ UPDATE tags SET status=$2, updated_at=now() WHERE id=$1; INSERT INTO tags (tag_code) VALUES ($1) RETURNING *; +-- name: UpsertAdmin :one +INSERT INTO users (email, password_hash, name, is_admin) +VALUES ($1, $2, $3, true) +ON CONFLICT (email) DO UPDATE SET password_hash = EXCLUDED.password_hash, name = EXCLUDED.name, is_admin = true +RETURNING *; + -- name: InsertScan :one INSERT INTO scans (tag_id, lat, lng, location_shared, scanner_phone, fingerprint, ip) VALUES ($1, $2, $3, $4, $5, $6, $7) diff --git a/frontend/internal/db/queries.sql.go b/frontend/internal/db/queries.sql.go index 060ac35..948e374 100644 --- a/frontend/internal/db/queries.sql.go +++ b/frontend/internal/db/queries.sql.go @@ -631,3 +631,33 @@ func (q *Queries) UpdateTagDetails(ctx context.Context, arg UpdateTagDetailsPara ) return i, err } + +const upsertAdmin = `-- name: UpsertAdmin :one +INSERT INTO users (email, password_hash, name, is_admin) +VALUES ($1, $2, $3, true) +ON CONFLICT (email) DO UPDATE SET password_hash = EXCLUDED.password_hash, name = EXCLUDED.name, is_admin = true +RETURNING id, email, password_hash, name, phone, created_at, is_admin, remember_token, paused +` + +type UpsertAdminParams struct { + Email string `json:"email"` + PasswordHash string `json:"password_hash"` + Name pgtype.Text `json:"name"` +} + +func (q *Queries) UpsertAdmin(ctx context.Context, arg UpsertAdminParams) (User, error) { + row := q.db.QueryRow(ctx, upsertAdmin, arg.Email, arg.PasswordHash, arg.Name) + var i User + err := row.Scan( + &i.ID, + &i.Email, + &i.PasswordHash, + &i.Name, + &i.Phone, + &i.CreatedAt, + &i.IsAdmin, + &i.RememberToken, + &i.Paused, + ) + return i, err +}