ops: live SMS creds on .13; seed-admin cmd in make db-up (admin survives DB resets)

This commit is contained in:
2026-08-10 10:07:29 +10:00
parent e1044f8c32
commit 17150c405d
5 changed files with 101 additions and 3 deletions

View File

@@ -4,10 +4,10 @@ SESSION_SECRET ?= dev-secret-change-me # dev only — set a real secret in pro
export DATABASE_URL export DATABASE_URL
export SESSION_SECRET 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) db-up: ## apply schema + ensure the admin user exists
cd frontend && go run ./cmd/migrate -schema ../db/schema.sql cd frontend && go run ./cmd/migrate -schema ../db/schema.sql && go run ./cmd/seed-admin
seed: ## insert test tag codes TEST000001..TEST000025 seed: ## insert test tag codes TEST000001..TEST000025
cd frontend && go run ./cmd/seed cd frontend && go run ./cmd/seed

View File

@@ -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)
}

View File

@@ -36,6 +36,7 @@ type Querier interface {
SetScanPhone(ctx context.Context, arg SetScanPhoneParams) error SetScanPhone(ctx context.Context, arg SetScanPhoneParams) error
SetTagStatus(ctx context.Context, arg SetTagStatusParams) error SetTagStatus(ctx context.Context, arg SetTagStatusParams) error
UpdateTagDetails(ctx context.Context, arg UpdateTagDetailsParams) (Tag, error) UpdateTagDetails(ctx context.Context, arg UpdateTagDetailsParams) (Tag, error)
UpsertAdmin(ctx context.Context, arg UpsertAdminParams) (User, error)
} }
var _ Querier = (*Queries)(nil) var _ Querier = (*Queries)(nil)

View File

@@ -46,6 +46,12 @@ UPDATE tags SET status=$2, updated_at=now() WHERE id=$1;
INSERT INTO tags (tag_code) VALUES ($1) INSERT INTO tags (tag_code) VALUES ($1)
RETURNING *; 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 -- name: InsertScan :one
INSERT INTO scans (tag_id, lat, lng, location_shared, scanner_phone, fingerprint, ip) INSERT INTO scans (tag_id, lat, lng, location_shared, scanner_phone, fingerprint, ip)
VALUES ($1, $2, $3, $4, $5, $6, $7) VALUES ($1, $2, $3, $4, $5, $6, $7)

View File

@@ -631,3 +631,33 @@ func (q *Queries) UpdateTagDetails(ctx context.Context, arg UpdateTagDetailsPara
) )
return i, err 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
}