feature/quota-and-member-limits #2

Merged
manohar merged 2 commits from feature/quota-and-member-limits into main 2026-05-27 18:05:21 +00:00

2 commits

Author SHA1 Message Date
0c7f37fd12 feat(quota): storage quota + family-member limits for free tier
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>
2026-05-27 23:21:11 +05:30
b6814579c6 feat(pwa): add Serwist service worker, manifest, icons, install prompt
- Wrap next.config.ts with @serwist/next (webpack mode, disabled in dev)
- Service worker: NetworkOnly for /api/*, offline fallback → /~offline
- Web app manifest via Next.js metadata API (app/manifest.ts)
- PNG icon set generated with sharp (192, 512, maskable-512, apple-180)
- iOS meta tags: appleWebApp, themeColor viewport export
- Middleware: pwaAssets early-return so /sw.js never gets a 302→login
- Offline fallback page at /~offline (static, no auth dependency)
- InstallPrompt component: beforeinstallprompt (Android) + iOS Share sheet instructions
- Logout (menu/page.tsx): purge all SW caches on signout (shared-device safety)
- Fix invite/[token]/page.tsx params type for Next.js 16 (use(params))
- Build script: next build --webpack (Serwist requires webpack, not Turbopack)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:20:48 +05:30