Map design IDs to Gitea #1-#37, add tasks 80-88, stub ADRs 0004-0006, and point STATUS at the P1 batch order.
33 KiB
33 KiB
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
- Module Isolation: Modules only import from
_coreandlib/- never from sibling modules - Generic Core Services: Sharing, reminders, activity log, search work against any registered entity type
- Household Scoping: All entity data scoped to household_id; permissions gated by household membership
- Server Actions: All mutations use Next.js server actions; route handlers only for webhooks/SSE/share viewer
- Type Safety: TypeScript strict mode; Drizzle schema inference; Zod validation for all configs
- Extensibility: Adding new features requires only creating a new module - no core changes needed
- PWA First: Installable PWA with web(push) notifications; no native apps required
- 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)