Generated by `prisma migrate dev --name encrypt_profile_fields`.
ALTER TABLE "profiles" sets weight_start_kg / weight_current_kg /
weight_goal_kg / target_date to TEXT. Existing rows keep their decimal
representation as plaintext text (e.g. "76.00") and prisma-field-encryption
re-encrypts them on the next write — both ciphertext (v1.aesgcm256.<keyId>.
<iv>.<ciphertext>) and legacy plaintext are decrypted transparently on read,
so no app-level fallback was needed.
Verified end-to-end:
- pnpm test:e2e — all 4 specs pass, including profile signup → submit →
BMR 1583 / TDEE 1899 round-trip
- raw SQL on profiles shows ciphertext for new rows; older rows stay in clear
text until their next update
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>
Generated by `prisma migrate dev --name init` against the local Postgres.
Creates the 6 enums and the 3 tables with their unique indexes and
foreign keys (profiles.user_id and preferences.user_id with ON DELETE CASCADE).
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>