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

Open
vikingowl wants to merge 0 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 branch is already included in the target branch. There is nothing to merge.
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