Map design IDs to Gitea #1-#37, add tasks 80-88, stub ADRs 0004-0006, and point STATUS at the P1 batch order.
345 lines
33 KiB
Markdown
345 lines
33 KiB
Markdown
# 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 85–88:
|
||
|
||
- **`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)
|
||
|