Obsidian_vault/30 - Projects/Tia/Tia - Architecture.md
2026-06-07 15:00:01 +00:00

57 lines
3.1 KiB
Markdown

---
type: project-doc
parent: "[[Tia]]"
status: verified
tags: [tia, architecture, engineering]
---
# 🏗️ Tia — Architecture
> Verified from CLAUDE.md + memory files.
## Stack
Next.js 16 (App Router, Turbopack) · PostgreSQL 16 + **pgvector** (pgvector/pgvector:pg18 on prod) · **Drizzle ORM** · **LiteLLM** gateway → **MiniMax** (minimax-2.7) · **Cloudflare R2** · **Resend** · Tailwind v4 · **Dokploy** (auto-migrate on deploy).
## Route groups ([[Decision Log#TD-005]])
- `(marketing)/``/`, `/pricing`, `/privacy`, `/terms`**static, no DB/auth imports**
- `(app)/` → authenticated app with ThemeProvider + FamilyProvider + BottomNav; dashboard at `/home`
## Auth
- User: `/login``POST /api/auth/signin` (bcrypt) → session row → httpOnly `tia_session`; routes use `requireFamily()`
- Admin: `/admin-login``tia_admin_session`; admin layout is a server component calling `verifyAdminSession()`
## Data & context
- `FamilyProvider` resolves family/child from session: `{ familyId, child, children, tier, memberCount, updateChildImage }`
- Quota logic in `src/lib/quota.ts` (pure fns + queries); usage always derived via `SUM` ([[Decision Log#TD-001]])
## Migrations (the big gotcha) ([[Decision Log#TD-003]])
- `migrate.mjs` runs before `server.js`; reads all SQL in `drizzle/`, applies any not in `__drizzle_migrations`
- **`when` must be `Date.now()`** (> 1779539431897) or Drizzle silently skips it
- Hot-apply via `POST /api/debug-migration` (header `x-run-migration: yes`), idempotent steps
## Media (R2) ([[Decision Log#TD-004]])
- 3-step upload: init → server-proxied `PUT /api/upload` → save URL
- **Never** direct cross-origin PUT; **never** raw `*.r2.dev` in `<img>` — use `/api/img?key=` proxy
## Observability (admin)
`error_events` table + `logError()` + `POST /api/errors`; `/admin/{health,errors,audit,ai}` over `audit_log`, `ai_usage` (p95 via `percentile_cont`).
## Related
[[Tia]] · [[Tia - Decisions]] · [[Self-Hosting]] · [[Engineering Overview]]
---
## Auth evolution (important nuance)
Two phases appear across the history:
1. **DB sessions** — bcrypt + `sessions` table + httpOnly `tia_session` cookie; `requireFamily()`
2. **NextAuth v5** — Google OAuth (public) + magic link (invite-only); used by the marketing-era build
Hardening sprint **H1** explicitly "replaced broken password hash." Treat auth as **migrating/hardened** — confirm the current source of truth.
## DB hardening (sprint H2)
- Non-superuser role **`tia_app`** (LOGIN, only SELECT/INSERT/UPDATE/DELETE — no DROP/CREATE/ALTER ROLE)
- Separate `DATABASE_URL_SUPERUSER` for migrations only
- Per-request **session context** `app.current_family_id` set in a transaction (`src/db/scoped.ts`) → foundation for **Row-Level Security (RLS)**
## Memories vision pipeline (sprint G2)
Photo is mandatory; a **vision model does the metadata heavy-lifting**: photo → vision caption/tags → `memories.vision_embedding vector(1536)` (pgvector, ivfflat index `memories_embedding_idx`). Requires the `pgvector/pgvector:pg18` image ([[Deployment Checklist]]).
## Hosting
**Hetzner** server via **Dokploy**; `output: 'standalone'`; PWA shell installable.