# 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(); │ │ const entityTypes = new Map(); │ │ const widgets = new Map(); │ │ │ │ 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; │ │ loadForShare?: (id, ctx) => Promise; │ │ 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; │ │ 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/ Public viewer accesses /s/ → 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)