feat(backend): Phase 4a PR1 — user status/roles + admin approval queue #4

Open
vikingowl wants to merge 8 commits from feat/burgund-backend into main
Owner

Summary

  • Migration 000034: adds users.status (pending|active|suspended with CHECK), users.approved_at, and a users.role CHECK constraint covering all 6 planned roles. Existing rows default to status='active'.
  • Role/status constants in user/roles.go — single source of truth for the string values used across Go code.
  • User model + repository extended with Status, ApprovedAt fields; all RETURNING/Scan clauses updated.
  • Admin approval queue — three new endpoints guarded by RequireAuth + RequireRole("admin"):
    • GET /api/v1/admin/users/pending — list users awaiting approval
    • POST /api/v1/admin/users/:id/approve — set active, revoke all sessions
    • POST /api/v1/admin/users/:id/reject — set suspended, revoke all sessions
  • Session revocation on approval/reject — sessions are invalidated before status update so the cached user_role in Valkey doesn't outlive the decision.
  • SessionRevoker interface defined in the user package to avoid an import cycle with the auth package (auth.Repository satisfies it at the wiring layer).
  • Login gate for suspended accountsauth.Service.Login now returns "account suspended" before creating a session.
  • Iron Law tests — 4 PoC tests covering all auth boundaries (401/403) plus approve/reject happy paths with session revocation assertion.

Depends on

PRs #2 and #3 (Phase 2 + 3 web changes) should merge before this lands, though there are no code conflicts — this branch only touches backend/.

Test plan

  • go test ./... passes (all 27 test files green)
  • go build ./... succeeds
  • Migration runs against a local Postgres: atlas migrate apply (or golang-migrate up)
  • POST /api/v1/admin/users/:id/approve with a non-admin token returns 403
  • POST /api/v1/admin/users/:id/approve with no token returns 401
  • Approving a pending user revokes their sessions (can verify via SELECT revoked_at FROM sessions WHERE user_id = :id)
  • Suspended user login attempt returns 400 "account suspended"
## Summary - **Migration 000034**: adds `users.status` (pending|active|suspended with CHECK), `users.approved_at`, and a `users.role` CHECK constraint covering all 6 planned roles. Existing rows default to `status='active'`. - **Role/status constants** in `user/roles.go` — single source of truth for the string values used across Go code. - **User model + repository** extended with `Status`, `ApprovedAt` fields; all `RETURNING`/`Scan` clauses updated. - **Admin approval queue** — three new endpoints guarded by `RequireAuth + RequireRole("admin")`: - `GET /api/v1/admin/users/pending` — list users awaiting approval - `POST /api/v1/admin/users/:id/approve` — set active, revoke all sessions - `POST /api/v1/admin/users/:id/reject` — set suspended, revoke all sessions - **Session revocation on approval/reject** — sessions are invalidated before status update so the cached `user_role` in Valkey doesn't outlive the decision. - **`SessionRevoker` interface** defined in the user package to avoid an import cycle with the auth package (`auth.Repository` satisfies it at the wiring layer). - **Login gate for suspended accounts** — `auth.Service.Login` now returns `"account suspended"` before creating a session. - **Iron Law tests** — 4 PoC tests covering all auth boundaries (401/403) plus approve/reject happy paths with session revocation assertion. ## Depends on PRs #2 and #3 (Phase 2 + 3 web changes) should merge before this lands, though there are no code conflicts — this branch only touches `backend/`. ## Test plan - [ ] `go test ./...` passes (all 27 test files green) - [ ] `go build ./...` succeeds - [ ] Migration runs against a local Postgres: `atlas migrate apply` (or `golang-migrate up`) - [ ] `POST /api/v1/admin/users/:id/approve` with a non-admin token returns 403 - [ ] `POST /api/v1/admin/users/:id/approve` with no token returns 401 - [ ] Approving a pending user revokes their sessions (can verify via `SELECT revoked_at FROM sessions WHERE user_id = :id`) - [ ] Suspended user login attempt returns 400 "account suspended"
vikingowl added 5 commits 2026-05-10 17:25:09 +02:00
Establishes the Burgund visual identity system: 11-token color palette
(sealing-wax burgundy #9a1e2c light / halbton rosé #d86268 dark), editorial
typography (Cormorant Garamond display, EB Garamond serif, JetBrains Mono
caps), and all atoms (MarktvogtMark, Caps, Tag, Rule, Heraldry). Rewrites
Header, Footer, MobileNav, all ui/ components, and MarketCard to Burgund
tokens. Self-hosts 12 woff2 variable font subsets. Adds design-system.md
reference doc. Also switches pre-commit hook prettier step to --write.
Updates static/favicon.svg (was the old forest-green MedievalSharp signet),
regenerates favicon.ico, favicon-32.png, apple-touch-icon.png. Fixes
site.webmanifest theme_color and background_color to Burgund parchment.
Swaps app.html font preloads to EB Garamond + Cormorant Garamond and
theme-color meta tags to Burgund bg values.
feat(web): implement Burgund Phase 2 — public surfaces
ci/someci/pr/web Pipeline was successful
ci/someci/push/web Pipeline was successful
ci/someci/push/backend Pipeline was successful
ci/someci/pr/backend Pipeline was successful
5e24be03af
feat(web): implement Burgund Phase 3 — profile, security, auth component cleanup
ci/someci/push/web Pipeline was successful
ci/someci/pr/web Pipeline was successful
ci/someci/pr/backend Pipeline was successful
911439ebd8
feat(backend): Phase 4a PR1 — user status/roles + admin approval queue
ci/someci/pr/web Pipeline was successful
ci/someci/push/backend Pipeline was successful
ci/someci/pr/backend Pipeline was successful
b5748121dd
- Migration 000034: add users.status (pending|active|suspended) with
  CHECK, users.approved_at, and users.role CHECK constraint covering
  all 6 planned roles (gast, user, veranstalter, haendler, lager, admin).
  Existing rows default to status='active' to preserve behaviour.

- Role and status constants in user/roles.go (single source of truth).

- User model extended with Status and ApprovedAt; all repository
  RETURNING/Scan clauses updated accordingly.

- Repository interface extended with ListByStatus and SetStatus.

- Admin approval queue: GET /admin/users/pending, POST /admin/users/:id/approve,
  POST /admin/users/:id/reject. Guarded by RequireAuth + RequireRole("admin").
  Approve/reject revokes all target user sessions before updating status
  (session cache carries stale role data; revocation forces re-login).

- SessionRevoker interface defined in user package to avoid import cycle
  with auth package. auth.Repository satisfies it at the wiring layer.

- Login now gates suspended accounts (returns "account suspended" error
  so new sessions cannot be created after rejection).

- Security tests (Iron Law): failing PoC tests written for all three
  admin endpoints verifying 401 for unauthenticated, 403 for non-admin,
  and correct status transitions + session revocation for admin.
vikingowl added 1 commit 2026-05-10 17:32:42 +02:00
feat(backend): Phase 4a PR2 — groups, group_members, group_profiles
ci/someci/pr/web Pipeline was successful
ci/someci/push/backend Pipeline was successful
ci/someci/pr/backend Pipeline was successful
a37e79ec16
- Migration 000035: three new tables.
  groups: id, name, kind (haendler|kuenstler|lager), created_by.
  group_members: id, group_id, user_id, role (admin|member) + UNIQUE
  constraint; indexed on user_id and group_id for fast membership lookup.
  group_profiles: one-to-one with groups — description, categories (TEXT[]),
  avatar_url, website_url; upserted so the profile always exists after group
  creation.

- internal/domain/group package: model, repository (pgx), service, handler,
  routes. Public routes: GET /groups/:id, GET /groups/:id/members.
  Auth-gated routes: POST /groups, PATCH /groups/:id/profile,
  POST /groups/:id/members, DELETE /groups/:id/members/:userId,
  GET /users/me/groups.

- Authorization is group-scoped (not platform-role): UpdateProfile and
  AddMember require group admin role; RemoveMember allows self-remove or
  admin. Removing the last admin is blocked (ErrCannotRemoveLastAdmin).
  Creator is automatically added as admin on group creation.

- Security tests (Iron Law): 8 PoC tests covering 401 for unauthenticated
  requests on all auth-gated endpoints, 403 for non-admin on profile/member
  write endpoints, self-remove happy path, last-admin guard, create happy
  path (creator becomes admin), and public endpoint accessibility.
vikingowl added 1 commit 2026-05-10 17:42:49 +02:00
feat(backend): Phase 4a PR3 — applications + application_status_log
ci/someci/pr/web Pipeline was successful
ci/someci/push/backend Pipeline was successful
ci/someci/pr/backend Pipeline was successful
b62271eeb6
vikingowl added 1 commit 2026-05-10 17:55:03 +02:00
feat(backend): Phase 4a PR4 — lagerleben articles + camps API; wire frontend loaders
ci/someci/push/web Pipeline was successful
ci/someci/pr/web Pipeline was successful
ci/someci/push/backend Pipeline was successful
ci/someci/pr/backend Pipeline was successful
808f07800e
Some checks are pending
ci/someci/push/web Pipeline was successful
ci/someci/pr/web Pipeline was successful
ci/someci/push/backend Pipeline was successful
ci/someci/pr/backend Pipeline was successful
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/burgund-backend:feat/burgund-backend
git checkout feat/burgund-backend
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vikingowl/marktvogt.de#4