Files
famapp/ARCHITECTURE.md
T
ginnoir 76f68548b2 docs: complete Phase 9 backlog triage (issues map, briefs, ADRs)
Map design IDs to Gitea #1-#37, add tasks 80-88, stub ADRs 0004-0006,
and point STATUS at the P1 batch order.
2026-07-04 01:23:30 -05:00

345 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# famapp Architecture Diagram
## System Overview
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL LAYER │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Browser │ │ Mobile PWA │ │ Share Link │ │
│ │ (Next.js) │ │ (Installed) │ │ (Public) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └────────────────────────┴────────────────────────┘ │
│ │ │
│ HTTPS (Caddy Reverse Proxy) │
│ │ │
│ fam.ginnoir.com → :3000 │
│ auth.ginnoir.com → :9000 │
└──────────────────────────────────┼───────────────────────────────────────────┘
┌──────────────────────────────────┼───────────────────────────────────────────┐
│ APPLICATION LAYER │
├──────────────────────────────────┼───────────────────────────────────────────┤
│ │ │
│ ┌────────────────────────┴────────────────────────┐ │
│ │ Next.js 15 App Router │ │
│ │ (Node 24 LTS Container) │ │
│ └────────────────────────┬────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────┼───────────────────────────────┐ │
│ │ │ │ │
│ │ ┌───────────────────────────┐│ ┌───────────────────────────┐│ │
│ │ │ Middleware ││ │ Auth (NextAuth) ││ │
│ │ │ - Rate limiting ││ │ - OIDC (Authentik) ││ │
│ │ │ - Session check ││ │ - Session management ││ │
│ │ └───────────┬───────────────┘│ └───────────┬───────────────┘│ │
│ │ │ │ │ │ │
│ │ ┌───────────┴───────────────┐│ ┌───────────┴───────────────┐│ │
│ │ │ Routes (app/) ││ │ Module Registry ││ │
│ │ │ / → /d/{slug} ││ │ - Entity types ││ │
│ │ │ /d/[slug] (dashboard) ││ │ - Dashboard widgets ││ │
│ │ │ /calendar ││ │ - Quick-add actions ││ │
│ │ │ /lists ││ │ - Share handlers ││ │
│ │ │ /notes ││ │ - Search adapters ││ │
│ │ │ /garden ││ │ - Reminder handlers ││ │
│ │ │ /s/[token] (share) ││ │ - Activity renderers ││ │
│ │ │ /settings ││ │ - Toggle hooks ││ │
│ │ │ /api/* ││ └───────────┬───────────────┘│ │
│ │ └───────────┬───────────────┘ │ │ │
│ │ │ │ │ │
│ │ ┌───────────┴───────────────┬────────────────┴───────────────┐│ │
│ │ │ │ ││ │
│ │ │ ┌───────────────────────┴──────────────────────────────┐││ │
│ │ │ │ MODULE SYSTEM (src/modules/) │││ │
│ │ │ │ │││ │
│ │ │ │ ┌────────────────────────────────────────────────┐ │││ │
│ │ │ │ │ _core/ │ │││ │
│ │ │ │ │ - schema.ts (users, households, activity_log, │ │││ │
│ │ │ │ │ share_links, dashboards, reminders, push) │ │││ │
│ │ │ │ │ - registry.ts (module registration) │ │││ │
│ │ │ │ │ - share.ts (share-link service) │ │││ │
│ │ │ │ │ - push.ts (web push notifications) │ │││ │
│ │ │ │ │ - reminders.ts (reminder scheduling) │ │││ │
│ │ │ │ │ - activity.ts (activity logging) │ │││ │
│ │ │ │ │ - themes.ts (theme configuration) │ │││ │
│ │ │ │ └────────────────────────────────────────────────┘ │││ │
│ │ │ │ │││ │
│ │ │ │ ┌────────────────────────────────────────────────┐ │││ │
│ │ │ │ │ calendar/ │ │││ │
│ │ │ │ │ - schema.ts (calendars, calendar_events) │ │││ │
│ │ │ │ │ - server/ (queries, mutations, share-queries) │ │││ │
│ │ │ │ │ - components/ (shared-view, calendar views) │ │││ │
│ │ │ │ │ - manifest.tsx (widgets: upcoming, month) │ │││ │
│ │ │ │ └────────────────────────────────────────────────┘ │││ │
│ │ │ │ │││ │
│ │ │ │ ┌────────────────────────────────────────────────┐ │││ │
│ │ │ │ │ lists/ │ │││ │
│ │ │ │ │ - schema.ts (lists, list_items) │ │││ │
│ │ │ │ │ - server/ (queries, mutations, share-queries) │ │││ │
│ │ │ │ │ - components/ (shared-view, list-widget) │ │││ │
│ │ │ │ │ - manifest.tsx (widget: list items) │ │││ │
│ │ │ │ └────────────────────────────────────────────────┘ │││ │
│ │ │ │ │││ │
│ │ │ │ ┌────────────────────────────────────────────────┐ │││ │
│ │ │ │ │ notes/ │ │││ │
│ │ │ │ │ - schema.ts (notes) │ │││ │
│ │ │ │ │ - server/ (queries, mutations, share-queries) │ │││ │
│ │ │ │ │ - components/ (shared-view) │ │││ │
│ │ │ │ │ - manifest.tsx (widget: notes) │ │││ │
│ │ │ │ └────────────────────────────────────────────────┘ │││ │
│ │ │ │ │││ │
│ │ │ │ ┌────────────────────────────────────────────────┐ │││ │
│ │ │ │ │ garden/ │ │││ │
│ │ │ │ │ - schema.ts (plants, containers, care_logs) │ │││ │
│ │ │ │ │ - server/ (queries, mutations, share-queries) │ │││ │
│ │ │ │ │ - components/ (plant-widget, shared-view) │ │││ │
│ │ │ │ │ - manifest.tsx (widgets: care-due, overview) │ │││ │
│ │ │ │ └────────────────────────────────────────────────┘ │││ │
│ │ │ │ │││ │
│ │ │ │ ┌────────────────────────────────────────────────┐ │││ │
│ │ │ │ │ bangs/ │ │││ │
│ │ │ │ │ - schema.ts (bang_events) │ │││ │
│ │ │ │ │ - server/ (queries) │ │││ │
│ │ │ │ │ - components/ (bang-widget) │ │││ │
│ │ │ │ │ - manifest.tsx (widget: bang counter) │ │││ │
│ │ │ │ └────────────────────────────────────────────────┘ │││ │
│ │ │ │ │││ │
│ │ │ └────────────────────────────────────────────────────┘││ │
│ │ │ ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Shared Libraries (src/lib/) │ │
│ │ - db.ts (Drizzle client) │ │
│ │ - auth.ts (NextAuth configuration) │ │
│ │ - session.ts (session helpers) │ │
│ │ - dashboard.ts (dashboard helpers) │ │
│ │ - logger.ts (pino logging) │ │
│ │ - minio.ts (S3 client for uploads) │ │
│ │ - rate-limit.ts (rate limiting) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────┬─────────────────────────────────────┘
┌───────────────────────────────────────┼─────────────────────────────────────┐
│ DATA LAYER │
├───────────────────────────────────────┼─────────────────────────────────────┤
│ │ │
│ ┌─────────────────────────────┴──────────────────────────┐ │
│ │ Postgres 16 (famapp-db) │ │
│ │ (Dedicated Container) │ │
│ └─────────────────────────────┬──────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────┼────────────────────────────┐ │
│ │ │ │ │
│ │ Core Tables │ Module Tables │ │
│ │ ┌──────────────────────────────┐ │ ┌──────────────────────┐ │ │
│ │ │ users │ │ │ calendars │ │ │
│ │ │ accounts │ │ │ calendar_events │ │ │
│ │ │ sessions │ │ │ lists │ │ │
│ │ │ verification_tokens │ │ │ list_items │ │ │
│ │ │ households │ │ │ notes │ │ │
│ │ │ household_members │ │ │ garden_plants │ │ │
│ │ │ activity_log │ │ │ garden_containers │ │ │
│ │ │ share_links │ │ │ garden_care_logs │ │ │
│ │ │ dashboards │ │ │ bang_events │ │ │
│ │ │ reminders │ │ └──────────────────────┘ │ │
│ │ │ push_subscriptions │ │ │ │
│ │ │ notifications │ │ │ │
│ │ └──────────────────────────────┘ │ │ │
│ │ │ │ │
│ └────────────────────────────────────┴────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ AUTHENTICATION LAYER │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Authentik (auth.ginnoir.com) │ │
│ │ (Separate Docker Stack) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ authentik- │ │ authentik- │ │ authentik- │ │ │
│ │ │ server │ │ worker │ │ redis │ │ │
│ │ │ (:9000) │ │ │ │ │ │ │
│ │ └──────┬───────┘ └──────────────┘ └──────┬───────┘ │ │
│ │ │ │ │ │
│ │ └────────────────┬─────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────────────────────┴──────────────────┐ │ │
│ │ │ authentik-db (Postgres 16) │ │ │
│ │ │ (Dedicated Container) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │
│ │ - OIDC Provider for famapp │ │
│ │ - Forward-auth for *arr apps (future) │ │
│ │ - User management │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Module System Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ MODULE REGISTRATION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
1. Module Registration (src/modules/index.ts)
┌─────────────────────────────────────────────────────────────────────┐
│ import { registerModule } from "./_core/registry"; │
│ import coreManifest from "./_core/manifest"; │
│ import calendarManifest from "./calendar/manifest"; │
│ import listsManifest from "./lists/manifest"; │
│ import notesManifest from "./notes/manifest"; │
│ import gardenManifest from "./garden/manifest"; │
│ import bangsManifest from "./bangs/manifest"; │
│ │
│ registerModule(coreManifest); │
│ registerModule(calendarManifest); │
│ registerModule(listsManifest); │
│ registerModule(notesManifest); │
│ registerModule(gardenManifest); │
│ registerModule(bangsManifest); │
└─────────────────────────────────────────────────────────────────────┘
2. Registry Storage (src/modules/_core/registry.ts)
┌─────────────────────────────────────────────────────────────────────┐
│ const modules = new Map<string, ModuleManifest>(); │
│ const entityTypes = new Map<string, EntityTypeRegistration>(); │
│ const widgets = new Map<string, DashboardWidget>(); │
│ │
│ registerModule() stores: │
│ - Module metadata (id, name, nav) │
│ - Entity types (with share/reminder/search handlers) │
│ - Dashboard widgets (with render functions) │
│ - Quick-add actions │
└─────────────────────────────────────────────────────────────────────┘
3. Module Manifest Structure (src/modules/_core/module.ts)
┌─────────────────────────────────────────────────────────────────────┐
│ ModuleManifest { │
│ id: string; // "calendar", "lists", etc. │
│ name: string; // "Calendar", "Lists", etc. │
│ nav?: { // Navigation entry │
│ href: string; // "/calendar" │
│ label: string; // "Calendar" │
│ icon?: string; // "calendar" │
│ }; │
│ entities: EntityTypeRegistration[]; // Entity type declarations │
│ dashboardWidgets?: DashboardWidget[]; // Widget declarations │
│ quickAdds?: QuickAddAction[]; // Quick-add actions │
│ } │
└─────────────────────────────────────────────────────────────────────┘
4. Entity Type Registration
┌─────────────────────────────────────────────────────────────────────┐
│ EntityTypeRegistration { │
│ type: string; // "calendar.event", "lists.item", etc. │
│ label: { singular, plural }; │
│ share?: { │
│ canShare: boolean; │
│ defaultCapabilities?: string[]; │
│ }; │
│ reminder?: { canRemind: boolean }; │
│ search?: { search: (query, householdId) => SearchResult[] }; │
│ resolveUrl: (id) => string; │
│ canShareEntity?: (id, ctx) => Promise<boolean>; │
│ loadForShare?: (id, ctx) => Promise<unknown>; │
│ renderSharedView?: (data, capabilities, token) => ReactNode; │
│ renderActivity?: (entry) => string; │
│ } │
└─────────────────────────────────────────────────────────────────────┘
5. Dashboard Widget Registration
┌─────────────────────────────────────────────────────────────────────┐
│ DashboardWidget { │
│ id: string; // "calendar.upcoming", "lists.list", etc│
│ title: string; │
│ description: string; │
│ category?: string; // "Calendar", "Lists", etc. │
│ defaultSize: { w, h }; │
│ minSize?: { w, h }; │
│ maxSize?: { w, h }; │
│ defaultPriority: number; // For default dashboard ordering │
│ configSchema: ZodType; // Zod schema for widget config │
│ defaultConfig: unknown; │
│ resolveConfigOptions?: (ctx) => Promise<unknown>; │
│ render: (props) => ReactNode; // Server component render │
│ } │
└─────────────────────────────────────────────────────────────────────┘
```
## Data Flow Patterns
### Share Link Flow
```
User creates share link
→ Module's canShareEntity() checks permissions
→ Core share.ts creates share_links record
→ Returns token: /s/<token>
Public viewer accesses /s/<token>
→ Middleware rate-limits by IP:token prefix
→ Share viewer loads share_links record
→ Module's loadForShare() loads entity data
→ Module's renderSharedView() renders public view
→ Share-link grants scoped read/write per capabilities
```
### Activity Logging Flow
```
Any entity mutation (server action)
→ Core activity.ts logs activity_log entry
→ Includes: entityType, entityId, actorId, action, payload
→ Dashboard activity widget queries activity_log
→ Module's renderActivity() formats entry for display
```
### Reminder Flow
```
Entity with remind_at field (note, event)
→ Module schedules reminder via core reminders.ts
→ reminders table stores: entityType, entityId, fireAt, channel
→ Background worker (planned) fires reminders
→ notify() fans out to: web push, in-app, optional ntfy
```
### Dashboard Widget Flow
```
User configures dashboard layout
→ dashboards.layout stores: [{ widgetId, config, x, y, w, h }]
→ Dashboard page loads layout
→ For each widget: calls module's render() with config
→ Widget component queries data via module's server/ queries
→ Renders widget in grid layout
```
## Key Design Principles
1. **Module Isolation**: Modules only import from `_core` and `lib/` - never from sibling modules
2. **Generic Core Services**: Sharing, reminders, activity log, search work against any registered entity type
3. **Household Scoping**: All entity data scoped to household_id; permissions gated by household membership
4. **Server Actions**: All mutations use Next.js server actions; route handlers only for webhooks/SSE/share viewer
5. **Type Safety**: TypeScript strict mode; Drizzle schema inference; Zod validation for all configs
6. **Extensibility**: Adding new features requires only creating a new module - no core changes needed
7. **PWA First**: Installable PWA with web(push) notifications; no native apps required
8. **Privacy-First Sharing**: Share links are temporary, scoped, and revocable
## Future (Phase 9)
Placeholders only — details land with tasks 8588:
- **`journal/` module** — per-user journal (task 86, Gitea #19)
- **Versioned HTTP API** (`/api/v1/…`) with token auth alongside OIDC session (task 87, Gitea #15)
- **LLM agent chat** — OpenAI-compatible tools over the API (task 88)