Feature A — Storage quota (1 GiB per family):
- src/lib/quota.ts: enforcement library with pure functions (fully unit-tested)
and DB-bound helpers; isPaidFamily() is the single payment abstraction gate
- src/lib/format-bytes.ts: extracted formatBytes() — safe for client imports
- POST /api/upload: quota check before presigned URL issuance (HTTP 402 + reason code)
- POST /api/memories/[id]/confirm: HeadObject reconciles actual R2 size; deletes
over-quota objects and marks row failed rather than silently exceeding limit
- GET /api/storage-usage: storage info endpoint for UI meter
- src/components/StorageMeter.tsx: meter bar + StorageQuotaBanner + MemberLimitBanner
- memories/page.tsx: quota banner, FAB disabled (⊘) when exceeded, compact meter in header
- settings/page.tsx: always-visible StorageMeter + MemberLimitBanner in invite section
Feature B — Member limit (2 per family, free tier):
- invites/route.ts: replaced ad-hoc inline check with checkMemberLimit() from quota lib
Structured 403 response: { reason, currentCount, limit }
- Freeze rule: paid→free downgrade leaves all members intact; only new invites blocked
Migration:
- drizzle/0007_subscription_status.sql: ADD COLUMN subscription_status varchar(20)
- debug-migration/route.ts: step added for hot-apply without full redeploy
- src/db/schema/family.ts: subscriptionStatus field added to Drizzle schema
Tests: 44 unit tests in src/__tests__/quota.test.ts, all passing
- Pure function tests (no DB): isPaidFamily, wouldExceedQuota, isAtMemberLimit, formatBytes
- DB-bound tests (mocked @/db): getFamilyStorageUsage, checkStorageQuota,
checkMemberLimit, getStorageInfo, tenant isolation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| manual | ||
| meta | ||
| 0000_baseline_prod_2026_05_19.sql | ||
| 0001_wardrobe_tables.sql | ||
| 0002_outfits_table.sql | ||
| 0003_circles.sql | ||
| 0004_circle_invite_email.sql | ||
| 0005_email_verification.sql | ||
| 0006_family_invites_missing_cols.sql | ||
| 0007_subscription_status.sql | ||
| README.md | ||
Tia — Database Migrations
This folder is source code and is committed to git. It is consumed by the
deploy pipeline (pnpm db:migrate, run on container start — see Dockerfile).
Baseline reset — 2026-05-19
The project's first 16 migrations (0000–0015) plus a manual/ folder were
hand-rolled SQL applied directly via the Dokploy database terminal. They were
never run through Drizzle's migrator, so:
- prod had no
__drizzle_migrationstracking table; - the
drizzle/folder was gitignored, so migration SQL never reached the server; schema.tshad drifted well behind the real production schema.
To fix this we performed a Path A baseline reset:
pg_dumpbackup of prod taken and stored off-server.drizzle-kit pullintrospected the live prod schema (35 tables).src/db/schema/*.tswas rewritten to match prod exactly.- Legacy migrations were archived to
_archived_pre_baseline_2026-05-19/(also retained in git history). - A single fresh baseline —
0000_baseline_prod_2026_05_19.sql— was generated and verified column-for-column against the introspected prod schema. - Prod's
drizzle.__drizzle_migrationstable was created and seeded with one row marking0000_baseline_prod_2026_05_19as already applied, so the migrator treats prod as up-to-date and runs nothing on the next deploy.
Normal workflow from here
# 1. Edit src/db/schema/*.ts
# 2. Generate a migration from the diff:
pnpm db:generate # writes drizzle/000N_<name>.sql
# 3. Review the generated SQL by eye.
# 4. Apply locally against the dev DB:
pnpm db:migrate
# 5. Commit schema + migration together, then push.
# Dokploy redeploys; the migrator applies it in prod on container start.
Hard rules
- Never edit a migration file after it has been pushed. Fix-forward with a new migration instead.
- Never run schema-changing SQL directly against prod. It becomes drift.
- The
drizzle/folder must stay out of.gitignore.
RLS policies
Five log tables (feeds, diapers_logs, sleeps, vaccinations, growth)
plus children / family_members carry row-level-security policies in prod.
These are not modelled in the pgTable definitions and are managed
separately in the database. Drizzle migrations will not recreate them — keep
that in mind if you ever rebuild the DB from scratch.