- Pond model with pond-level trash columns (ADR 0013) and settings jsonb
holding only deviations from the defaults (sidebar sort, font slots per
ADR 0016); migration 20260705090100_ponds
- shared: pond schemas/views and slugify (German transliteration,
URL-safe, length-capped); deterministic -2/-3 suffixes for collisions
- InterimAccessService: single place answering pond access questions
until the real role model lands in M5
- POST/GET /ponds, GET /ponds/:slug, PATCH/DELETE /ponds/:id, Site-Admin
trash + restore; personal pond auto-created on e-mail verification and
for active seed fixtures; personal ponds cannot be trashed
- e2e pack covering verify-flow pond creation, slug suffixes, rename,
foreign-pond 404s, trash/restore; slugify unit tests
Closes#21
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpQz6ypHJsLfMf4S6fyQEB
- compose: pass APP_BASE_URL and SMTP_* through to the api container so
stages can use a real relay (defaults still match the dev Mailpit
overlay); document the new keys in .env.example and stages.md
- seed: FIXTURE_ADMIN_PASSWORD / FIXTURE_USER_PASSWORD env overrides so
shared stages get non-public fixture passwords; credential is re-hashed
on every run so re-seeding applies a changed password
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpQz6ypHJsLfMf4S6fyQEB
The seed script now provisions the documented fixture matrix
(fixture-admin / fixture-user / fixture-pending, idempotent upserts,
rate-limit reset for disposable databases). A six-test Playwright pack
drives the real UI against a full local stack with Mailpit: complete
signup→mail→verify→first-login journey, wrong-password error, guarded
route redirect honoring ?next (race between the login page and the
anonymous guard fixed by teaching the guard about ?next), menu logout,
site-admin gating, and a profile rename reflected in the top bar. The
pack self-skips without E2E_MAILPIT_URL, so the CD smoke stage (now
pinned to smoke.spec.ts) stays untouched; a new CI job boots api +
web dev server against postgres/mailpit service containers and runs
the pack on every PR and push. Also fixed: the web api client choked
on empty 201 bodies. e2e/README.md documents targets and fixtures.
Closes#20
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The web app grows its account surface: login (with next-redirect,
unverified-hint + resend), signup (react-hook-form + shared Zod
schemas, field-level api errors, closed-registration state fed by the
new public GET /auth/registration), e-mail verification, forgot/reset
password; a settings page with profile (locale applies immediately),
password change, and active-session management; a Site-Admin page for
instance name, default locale, and registration mode. AuthProvider
holds /auth/me, applies the profile locale, and backs route guards
(RequireAuth/RequireAnonymous/RequireSiteAdmin); the top bar gains a
user menu. All strings ship in the new auth/settings namespaces (de+
en); the exception filter now preserves handler-specific error codes.
Verified live: signup → Mailpit → verify → login → profile through
the Vite proxy.
Closes#16Closes#17Closes#18Closes#19
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Server halves of #17/#18/#19: PATCH /users/me and change-password
(verifies the current password, logs out every other session),
GET/DELETE /users/me/sessions with current-session flag and protection
against revoking oneself; InstanceSettingsService as a typed, cached,
Zod-validated registry over instance_settings (schema-default fallback
for invalid stored values, audit-logged writes) consumed by the signup
flow; /admin/settings behind the new SiteAdminGuard with strict
unknown-key rejection. SessionsService moves to its own module to keep
Auth/Users acyclic. Three new e2e suites bring the api to 42 tests.
Part of #17, #18, #19
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AuthModule implements the M1 core as one coherent unit:
Signup (#13): POST signup/verify-email/resend-verification with shared
Zod validation (field-level error details), double opt-in via hashed
single-use tokens (24h, superseding reissue), registration_mode
enforcement, and per-IP rate limits.
Sessions (#14): opaque 32-byte cookie tokens stored as SHA-256 row
ids, sliding 30-day expiry (refresh at most hourly), global AuthGuard
with @Public() opt-out attaching the user to every request, CSRF
origin check on mutating requests, per-account login backoff (5/15min,
reset on success), generic 401 for wrong-vs-unknown credentials,
logout with immediate invalidation, GET /auth/me.
Reset (#15): forgot-password without account enumeration, one-hour
single-use tokens, reset destroys all existing sessions.
A 14-case supertest e2e suite drives every flow against the test
database, reading verification/reset links from the mail outbox.
Closes#13Closes#14Closes#15
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MailService renders transactional mails (verify-email, reset-password)
from the new de/en `mails` i18n namespace — text plus minimal HTML
with escaped interpolation — and enqueues them into mail_outbox.
MailWorker delivers pending rows every 15s through an injectable
transport (nodemailer; faked in tests) with quadratic backoff and a
permanent FAILED state after five attempts, logged as a warning.
SMTP_* and APP_BASE_URL join the environment schema with defaults
matching the new Mailpit container in the dev overlay.
Closes#12
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RateLimitService implements fixed-window counters as one atomic
PostgreSQL upsert (race-safe under concurrency, proven by test), with
opportunistic sweeping of expired windows and an explicit reset for
successful-login scenarios. The global RateLimitGuard applies
@RateLimit({scope, limit, windowSeconds}) per client IP and answers
429 with Retry-After; main.ts trusts the single Caddy hop so req.ip
is the real client.
Closes#11
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prisma models per data-model.md: users (status enum, site-admin flag),
user_identities (password provider now, OIDC later — subject is the
stable user id), sessions (hashed ids), auth_tokens (hashed, single-
use), plus rate_limits and mail_outbox for the upcoming M1 stories.
UsersService creates accounts transactionally with Argon2id-hashed
password identities (OWASP parameters, rehash detection) and maps
uniqueness violations to field-level conflicts. Database-backed suites
run when TEST_DATABASE_URL is set — locally against the dev db, in CI
via a new postgres service container; shared auth schemas (username,
password policy incl. common-password blocklist) ship with tests.
Closes#10
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Multi-stage images: web (workspace build baked into unprivileged
nginx with SPA fallback, asset caching, /healthz) and api (pnpm deploy
bundle with the prisma CLI for migrate-on-start, non-root, node-based
healthcheck). deploy/compose/docker-compose.yml defines the stage
stack (web, api, db) with frontend/internal networks, localhost-only
published ports for the host reverse proxy, log rotation, and named
volumes; .env.example documents every variable. compose.dev.yml layers
hot-reloading dev containers (or database-only usage) over the same
definition. Verified locally: full stack healthy, SPA fallback, readyz
green after automatic migration, db not reachable from outside.
Closes#6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Translation resources live in packages/shared/i18n/<lang>/<ns>.json
(common, errors) and ship with de and en. The web app initializes
react-i18next with bundled resources (?lng= wins, then the browser
language); all shell components use useTranslation and the temporary
t() stub is gone. The api localizes its uniform error bodies via a
minimal i18next instance negotiated from Accept-Language. `pnpm
i18n:check` fails CI when any key is missing in any language, backed
by tested helpers in @dorfteich/shared.
Closes#5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
apps/api gains Prisma (instance_settings as the first model) with the
initial migration applied automatically at startup via prisma migrate
deploy, a lazy-connecting PrismaService, and GET /api/v1/readyz
reporting named checks (database reachable, migrations applied) with
200/503. DATABASE_URL joins the validated environment schema;
MIGRATE_ON_START=false skips deploys for tests and tooling. An
idempotent seed script and a Compose dev overlay with PostgreSQL
(host port 5434 — 5433 is taken locally) complete the loop.
Closes#3
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
apps/api boots a NestJS application with: Zod-validated environment
configuration (schema in @dorfteich/shared, fails fast listing every
invalid variable), structured pino request logging via nestjs-pino
(pretty in development, JSON otherwise, auth headers redacted), a
global exception filter producing the uniform ApiErrorBody shape, and
GET /api/v1/healthz. Vitest runs Nest through SWC for decorator
metadata; supertest covers healthz and the 404 error shape.
Closes#2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pnpm workspace with apps/web, apps/api, apps/collab, and
packages/shared; strict TypeScript base config, repo-wide ESLint (flat)
+ Prettier, Vitest per package, and root scripts lint/typecheck/test/
build. @dorfteich/shared ships a first health-response helper consumed
by apps/api to prove workspace linking. Existing markdown docs are
reformatted once by the new Prettier setup.
Closes#1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>