397 lines
14 KiB
Markdown
397 lines
14 KiB
Markdown
# FamChore v2 — Architecture & Developer Reference
|
|
|
|
## Overview
|
|
|
|
Multi-tenant chore tracking SaaS. Families ("fams") are isolated tenant groups. Fam admins use email/password. Members join via invite code + device token (no password). Super admin (you) can see everything.
|
|
|
|
**Demo reference:** Current prototype at `/home/threejjjs/development/famchore/`
|
|
|
|
---
|
|
|
|
## 1. Stack
|
|
|
|
| Component | Role | Deploy | Port |
|
|
|---|---|---|---|
|
|
| SvelteKit | SSR frontend, all UI | Coolify Docker (chores.app.com) | :3000 |
|
|
| Hono proxy | Stripe, CRON, webhooks | Same container as SvelteKit, proxied via `/api/*` | :3001 (internal) |
|
|
| PocketBase | DB, auth, realtime, storage, Admin UI | Coolify Docker (pb.chores.app.com) | :8090 |
|
|
|
|
### Deployment Topology
|
|
|
|
```
|
|
chores.app.com ────┬──► SvelteKit (:3000)
|
|
│ └── /api/* ──► Hono proxy (:3001)
|
|
│
|
|
pb.chores.app.com ──► PocketBase (:8090)
|
|
│ Admin UI at /_
|
|
│ Volume: /pb_data (persistence + backups)
|
|
│
|
|
stripe.com ─────────► Hono /api/stripe/webhook
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Auth Model
|
|
|
|
### 2.1 Roles & Methods
|
|
|
|
| Role | Auth | Session | Scope | PB Entity |
|
|
|---|---|---|---|---|
|
|
| **Super admin** (you) | PB email + password | 24hr JWT | All collections, all fams | PB `users` (set via seed) |
|
|
| **Fam admin** | PB email + password | 24hr JWT | Their fam only | PB `users` |
|
|
| **Member** | Invite code + device token | localStorage, no expiry | Own data only | `members` collection |
|
|
|
|
### 2.2 Member Auth Flow
|
|
|
|
1. Fam admin generates invite code → stored on `fams.inviteCode`
|
|
2. Admin shares as text (`chores.app.com/join/XYZ123`) or QR code
|
|
3. Member opens link, enters name, browser generates `crypto.randomUUID()` as device token
|
|
4. PB API creates `members` record with SHA-256 hashed token
|
|
5. Device token stored in `localStorage`, sent as `X-Device-Token` header
|
|
6. If token lost → admin regenerates invite code, member re-registers
|
|
7. Admin can revoke access by deleting member record
|
|
|
|
### 2.3 PB Auth Rules (row-level security)
|
|
|
|
Every collection has `famId` field. PB rule pattern:
|
|
|
|
```
|
|
famId = @request.auth.famId
|
|
```
|
|
|
|
Super admin bypasses via admin API (PB superuser credentials).
|
|
|
|
---
|
|
|
|
## 3. Data Model
|
|
|
|
All collections live in PocketBase. Every tenant-scoped collection includes `famId` (relation to `fams`).
|
|
|
|
### `fams`
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `id` | auto | PB default |
|
|
| `name` | text | Display name |
|
|
| `slug` | text | URL segment, unique |
|
|
| `inviteCode` | text | Short alphanumeric, regeneratable |
|
|
| `stripeCustomerId` | text? | Set after first donation |
|
|
| `featureFlags` | json | `{ "monthlyBonus": true }` |
|
|
| `created` | auto | |
|
|
|
|
### `members`
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `famId` | relation→fams | |
|
|
| `name` | text | |
|
|
| `color` | text | Hex |
|
|
| `deviceToken` | text | SHA-256 hash of raw token |
|
|
| `deviceTokenHint` | text | First 8 chars of raw token (for admin display) |
|
|
| `pointsThreshold` | number? | Override fam default |
|
|
| `weeklyBonus` | number? | Override fam default |
|
|
| `created` | auto | |
|
|
|
|
### `chore_templates`
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `famId` | relation→fams | |
|
|
| `name` | text | |
|
|
| `description` | text | |
|
|
| `defaultFrequency` | select | `daily` or `weekly` |
|
|
| `defaultType` | select | `points` or `money` |
|
|
| `defaultValue` | number | |
|
|
|
|
### `assigned_chores`
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `famId` | relation→fams | |
|
|
| `memberId` | relation→members | |
|
|
| `templateId` | relation→chore_templates | |
|
|
| `frequency` | select | |
|
|
| `type` | select | |
|
|
| `value` | number | |
|
|
| `customName` | text? | |
|
|
|
|
### `completions`
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `famId` | relation→fams | |
|
|
| `memberId` | relation→members | |
|
|
| `assignedChoreId` | relation→assigned_chores | |
|
|
| `date` | date | ISO date |
|
|
| `completedAt` | auto | |
|
|
|
|
### `rewards`
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `famId` | relation→fams | |
|
|
| `memberId` | relation→members | |
|
|
| `source` | select | `weekly_bonus`, `monthly_bonus`, `custom` |
|
|
| `label` | text | |
|
|
| `value` | number | |
|
|
| `weekStart` | date? | |
|
|
| `month` | text? | "2026-06" |
|
|
| `claimed` | bool | |
|
|
| `claimedAt` | auto? | |
|
|
|
|
### `weekly_history`
|
|
| Field | Type |
|
|
|---|---|
|
|
| `famId` | relation→fams |
|
|
| `memberId` | relation→members |
|
|
| `weekStart` | date |
|
|
| `pointsEarned` | number |
|
|
| `moneyEarned` | number |
|
|
| `choresCompleted` | number |
|
|
| `bonusEarned` | number |
|
|
|
|
### `monthly_bonuses`
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `famId` | relation→fams | |
|
|
| `month` | text | "2026-06" |
|
|
| `prizeType` | select | `cash`, `string` |
|
|
| `prizeValue` | text | |
|
|
| `winnerMemberId` | relation→members? | Nullable until computed |
|
|
| `pointsScored` | number? | |
|
|
| `claimed` | bool | |
|
|
|
|
### `settings` (singleton per fam)
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `famId` | relation→fams | Unique |
|
|
| `pointsThreshold` | number | Default: 100 |
|
|
| `weeklyBonus` | number | Default: 2 (e.g. £2) |
|
|
| `webhookUrl` | text? | N8N/notification URL |
|
|
|
|
---
|
|
|
|
## 4. Routes
|
|
|
|
### SvelteKit
|
|
|
|
```
|
|
/ Landing page (SaaS marketing)
|
|
/join/:code Member invite code + name entry
|
|
|
|
/{fam} Fam dashboard (weekly overview)
|
|
/{fam}/admin Admin panel
|
|
/{fam}/admin/chores Chore template CRUD + assignment grid
|
|
/{fam}/admin/rewards Reward management, claim history
|
|
/{fam}/admin/settings Thresholds, webhook, invite code, features
|
|
|
|
/{fam}/:username Member kanban
|
|
?token=<deviceToken> Auto-auth via query param (from QR/share)
|
|
```
|
|
|
|
### Hono proxy (`/api/*`)
|
|
|
|
```
|
|
/api/stripe/create-checkout Create Stripe Checkout Session
|
|
/api/stripe/webhook Stripe event webhook
|
|
/api/weekly-cron Coolify CRON target
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Data Flow
|
|
|
|
### 5.1 Chore Toggle
|
|
|
|
```
|
|
User clicks checkbox
|
|
→ Browser → PB SDK (direct, auth= device token or admin JWT)
|
|
→ PB inserts/deletes completion
|
|
→ PB realtime SSE push to all subscribers
|
|
→ UI updates (kanban card, progress bar, money counter)
|
|
→ If weekly threshold crossed:
|
|
→ SvelteKit server handler creates Reward (weekly_bonus)
|
|
```
|
|
|
|
### 5.2 Weekly CRON
|
|
|
|
Triggered by Coolify CRON job → `GET /api/weekly-cron`:
|
|
|
|
```
|
|
Hono receives request
|
|
→ Queries all fams
|
|
→ For each fam:
|
|
→ Compute weekly summaries per member (completions tally)
|
|
→ Upsert weekly_history records
|
|
→ Evaluate monthly bonus (end-of-month)
|
|
→ If webhookUrl set on fam settings:
|
|
→ POST summary to webhook (pluggable — WhatsApp later)
|
|
→ Returns 200
|
|
```
|
|
|
|
### 5.3 Stripe Donation
|
|
|
|
```
|
|
User clicks "Donate" on landing or modal
|
|
→ Hono /api/stripe/create-checkout
|
|
→ Creates Stripe Checkout Session
|
|
→ Returns session.url → redirect user to Stripe
|
|
→ Stripe redirects back to app
|
|
→ Stripe webhook → Hono /api/stripe/webhook
|
|
→ Updates fam.stripeCustomerId
|
|
→ Sets fam.featureFlags.donated = true (or similar)
|
|
```
|
|
|
|
### 5.4 Admin CRUD
|
|
|
|
All admin operations go through PB admin API (Hono proxy or `+page.server.ts`). This ensures:
|
|
- Server-side validation of famId
|
|
- Audit trail option
|
|
- Consistent error handling
|
|
|
|
### 5.5 Family Chat
|
|
|
|
Real-time right-slideout chat panel (TopNav chat icon, slideout on desktop / full-screen on mobile).
|
|
|
|
- **Writes go through the Hono proxy** (`POST /api/chat/:famId/messages`, `POST /api/chat/:famId/typing`) — the proxy resolves the actor via the `session` cookie / `x-session-*` headers (admin) or `x-device-token` headers (member). `GET /api/chat/me` returns the actor's identity (`{id, type, name, color}`) for both roles.
|
|
- **Reads use the anonymous PB SDK client-side** (`chatStore` in `frontend/src/lib/stores/chat.svelte.ts`), the same pattern as `famStore`. Collections `messages` and `chat_typing` have public `listRule`/`viewRule` (empty string); browser `.subscribe('*')` gives realtime SSE sync for both roles.
|
|
- **Collections:** `messages` (famId, authorType admin/member, authorId, authorName, authorColor, content, `createdAt`), `chat_typing` (transient per-actor presence: famId, actorId, actorType, authorName, authorColor, typing).
|
|
- **History** = last 14 days. Filter uses the custom `createdAt` field — the auto `created`/`updated` fields are NOT filterable in this PocketBase version (400), so a custom date field is set by the proxy at create time.
|
|
- **`createdAt` vs `created`:** all sorting, optimistic-temp messages, and the timestamp label use `createdAt`. The PB auto `created` field is not returned on records, so referencing it throws (`localeCompare` of undefined).
|
|
- **UI** (`Chat.svelte`): @mention tokens, typing indicators, unread badge, optimistic send with reconcile. The panel starts closed (no auto-open on mount) and is toggled via `chatStore.toggle()`.
|
|
|
|
---
|
|
|
|
## 6. Project Structure
|
|
|
|
```
|
|
/chores
|
|
/src SvelteKit app
|
|
/lib
|
|
/components Shared UI components
|
|
/stores Svelte stores (auth, current fam)
|
|
/pb PB SDK client helpers
|
|
/routes SvelteKit file-based routing
|
|
/hooks.server.ts Auth hooks, PB client init
|
|
/proxy Hono proxy
|
|
/src
|
|
/routes Stripe webhook, CRON handler
|
|
/services PB admin client, notification service
|
|
package.json
|
|
/seed JSON dump files for PB collections
|
|
/pb PB collection schema definitions
|
|
package.json Workspace root
|
|
Dockerfile.frontend SvelteKit + Hono build
|
|
Dockerfile.backend PocketBase (custom, or use official image)
|
|
coolify.json Coolify deployment config (optional)
|
|
AGENTS.md AI reference (this file's sibling)
|
|
ARCHITECTURE.md This document
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Environment Variables
|
|
|
|
### Frontend + Hono container
|
|
```
|
|
PUBLIC_PB_URL=https://pb.chores.app.com
|
|
PB_ADMIN_EMAIL=admin@chores.app
|
|
PB_ADMIN_PASSWORD=<super-admin-password>
|
|
STRIPE_SECRET_KEY=sk_live_xxx
|
|
STRIPE_WEBHOOK_SECRET=whsec_xxx
|
|
DONATION_MODAL_INTERVAL=30
|
|
```
|
|
|
|
### PocketBase container
|
|
```
|
|
PB_SUPERUSER_EMAIL=you@email.com
|
|
PB_SUPERUSER_PASSWORD=<your-password>
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Implementation Phases
|
|
|
|
### Phase 0 — Infrastructure (2-4 hrs)
|
|
- [ ] Deploy PocketBase on Coolify (`pb.chores.app.com`)
|
|
- Official `pocketbase/pocketbase` image
|
|
- Mount `/pb_data` volume
|
|
- Set super admin env vars
|
|
- Test: visit `pb.chores.app.com/_/`, login, data persists after restart
|
|
- [ ] Scaffold monorepo with SvelteKit + Hono
|
|
- [ ] Create Dockerfiles
|
|
- [ ] Deploy to Coolify (`chores.app.com`)
|
|
- [ ] Cloudflare DNS for both
|
|
|
|
### Phase 1 — Auth (4-6 hrs)
|
|
- [ ] Create PB collections: `fams`, `members`, `settings`
|
|
- [ ] Super admin seed script
|
|
- [ ] Fam signup → PB user + fam record
|
|
- [ ] Fam admin login (24hr JWT)
|
|
- [ ] Invite code generation
|
|
- [ ] Member join page (`/join/:code`)
|
|
- [ ] Device token auth
|
|
- [ ] Route guards
|
|
- [ ] Seed data (1 fam + 3 members + chores matching demo)
|
|
|
|
### Phase 2 — Core Chore Tracking (6-8 hrs)
|
|
- [ ] PB collections: `chore_templates`, `assigned_chores`, `completions`, `weekly_history`
|
|
- [ ] Admin chore CRUD
|
|
- [ ] Admin chore assignment grid
|
|
- [ ] Member kanban (3 columns: Daily Pending, Weekly Pending, Completed)
|
|
- [ ] Completion toggle
|
|
- [ ] Realtime updates (PB SSE)
|
|
- [ ] Weekly progress + chart
|
|
- [ ] Admin dashboard
|
|
|
|
### Phase 3 — Rewards + Claims (3-4 hrs)
|
|
- [ ] PB collection: `rewards`
|
|
- [ ] Auto-create weekly bonus reward on threshold
|
|
- [ ] Claim section with visual feedback
|
|
- [ ] Admin reward CRUD
|
|
- [ ] Monthly bonus (set prize → compute winner → create reward)
|
|
- [ ] Pluggable notification interface in CRON handler
|
|
|
|
### Phase 4 — SaaS (4-6 hrs)
|
|
- [ ] Landing page (hero, features, CTA)
|
|
- [ ] Stripe one-time checkout (Hono)
|
|
- [ ] Donation modal (triggered after N admin page loads)
|
|
- [ ] Feature flags (checked in routes via settings)
|
|
- [ ] Super admin dashboard (stats, all-fams view, feature toggles)
|
|
|
|
### Phase 5 — Polish (ongoing)
|
|
- [ ] QR invite code
|
|
- [ ] PB backup scheduler
|
|
- [ ] Error/loading/empty states
|
|
- [ ] Responsive mobile layout
|
|
- [ ] Accessibility
|
|
|
|
---
|
|
|
|
## 9. Key Conventions
|
|
|
|
- **`famId` on every query** — PB auth rules enforce `famId = @request.auth.famId`
|
|
- **PB SDK client-side for members** — Browser talks to PB directly for toggles, reads. PB auth rules handle security.
|
|
- **PB admin API server-side for admins** — Hono proxy or SvelteKit server handlers for admin CRUD.
|
|
- **Device tokens as SHA-256** — Never store or log raw tokens.
|
|
- **JSON dump for seed data** — Portable, version-controllable, restorable via PB backup CLI.
|
|
- **All collection schema files in `/pb/`** — Tracked in git, used for CI/CD schema migration.
|
|
- **Notifications via pluggable interface** — Hono CRON handler has `NotificationService` interface; WhatsApp is one implementation (deferred).
|
|
|
|
---
|
|
|
|
## 10. Open / Deferred
|
|
|
|
- **WhatsApp notifications** — Will be implemented as a `NotificationService` plugin for the weekly CRON handler. N8N or Twilio, TBD.
|
|
- **Subscription payments** — Currently one-time donation only. Subscription model can be added later via Stripe webhooks.
|
|
- **Member re-auth on new device** — Current design requires admin to regenerate invite code. Could add "re-issue link" feature in admin panel.
|
|
- **Multi-language** — Not yet scoped. All text in English for now.
|
|
|
|
---
|
|
|
|
## 11. Reference: Current Prototype
|
|
|
|
The existing HonoJS prototype at `/home/threejjjs/development/famchore/` contains the reference logic for:
|
|
- Weekly bonus calculation (`src/services.ts` → `checkWeeklyBonus`)
|
|
- Monthly bonus evaluation
|
|
- Reward claim flow
|
|
- Chore toggle event delegation
|
|
- Chart/stat formatting
|
|
- Date/timezone helpers
|
|
|
|
Refer to `src/types.ts` for the original type definitions, and `src/views/` for the Alpine.js template structure that maps to the new SvelteKit components.
|