Schema changes (require a single migration to apply):
- User gains disclaimerAcceptedAt — null until the user accepts the medical
disclaimer, blocks /profile until set (PRD §14.3)
- New WeightEntry (PRD §5.2) — weight/measurements/notes encrypted at rest,
energy/sleep/hunger left in the clear so we can produce aggregate analytics
- New FastEvent (PRD §5.3) — symptoms/notes encrypted, protocol/status/timer
in the clear; indexed on (userId, startedAt)
- New Ingredient / Recipe / RecipeIngredient (PRD §5.1) — italian keto recipe
database with macros and exclusion groups, ready for the matchmaking algo
- New MealPlan / MealSlot — generated weekly plan with selected recipe and
alternatives per (day, meal)
- New enums: FastStatus, MealCategory, Difficulty, MealPlanStatus
Web — disclaimer flow:
- /welcome page (server component, requires auth) lists the 4 PRD-mandated
points and surfaces the 3 mandatory checkboxes; submit triggers a server
action that stamps disclaimerAcceptedAt and redirects to /profile
- /profile redirects to /welcome whenever disclaimerAcceptedAt is null,
so the disclaimer becomes a hard prerequisite for any sensitive page
- New Italian copy under Welcome.* in messages/it.json
- @ketopath/db added to apps/web dependencies (was indirect via @ketopath/auth)
@ketopath/db re-exports the new models and enums.
Run the migration before testing: `pnpm db:migrate --name onboarding_v1_recipes`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire prisma-field-encryption AES-256-GCM extension on the shared Prisma
client and annotate the four sensitive columns on Profile with @encrypted:
- weightStartKg / weightCurrentKg / weightGoalKg (Decimal → String)
- targetDate (DateTime @db.Date → String, ISO YYYY-MM-DD)
Other Profile fields stay in clear text per ADR 0002 (age, gender,
heightCm, activityLevel) — they're needed for plan generation and
aggregate analytics, and are not strongly identifying on their own.
apps/api profile.routes.ts:
- serialize() now reads the columns as strings and parses them back to
numbers for BMR/TDEE; targetDate is already an ISO string from the DB
- the upsert stringifies numeric inputs and slices the date to YYYY-MM-DD
Env wiring:
- packages/db, apps/api, apps/web .env.example all document
PRISMA_FIELD_ENCRYPTION_KEY (k1.aesgcm256.<base64url>) — must match
across every process that hits the DB
- key generation snippet documented inline
Migration is intentionally NOT in this commit: needs to be created against
a live Postgres instance and applied. The fields change Decimal/Date → text
so prisma migrate dev will require a USING cast — see the follow-up commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- User extended with emailVerified, name, image (Better Auth fields)
- Session: signed session tokens with expiry, ip and user agent
- Account: credential rows for email+password (provider 'credential') and
OAuth providers (e.g. Google) — password hash stored on the credential row
- Verification: single-use tokens for email verification, password reset,
and magic links
- Re-export new types from @ketopath/db
Migration 20260429104721_add_auth_tables run against local Postgres.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Initial schema covers only the 3 base entities from PRD §8 — additional
models (MealPlan, Recipe, WeightEntry, FastEvent, ShoppingList, etc.)
will be added alongside their respective features.
- Postgres datasource via DATABASE_URL
- Enums: Role, Gender, ActivityLevel, Phase, CookingTime, FastingProtocol
- Singleton PrismaClient export from @ketopath/db
- Root scripts: db:generate / db:migrate / db:studio / db:seed / db:format
- Seed stub at prisma/seed.ts
- Ignore .claude/settings.local.json (per-user CLI permissions)
The first migration must be run by the developer:
pnpm db:migrate --name init
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>