6.3 KiB
6.3 KiB
08 — Implementation Plan (Phases 2–10)
Each phase ends with: run tests, run lint, build, fix all errors, verify
docker compose up -d still works end-to-end for whatever exists so far.
No phase starts until the previous one is green.
Phase 2 — Bootstrap project & Docker Compose
- npm/pnpm workspace scaffold (
apps/server,apps/web,packages/shared) - Fastify skeleton with the two listeners (8080/8081),
/health,/ready - Vite + React + Tailwind skeleton, dark theme tokens, empty shell page
- Drizzle configured against Postgres;
docker-compose.yml(dev) brings upnotizarr(hot-reload) +notizarr-postgres Dockerfile(multi-stage: build web → build server → copy web dist into server's static dir → slim runtime image), non-root user, healthcheck script- Exit criteria:
docker compose up -dsucceeds,notizarr-postgresis healthy,GET :8080/healthreturns 200, empty React shell loads.
Phase 3 — Database + backend API skeleton
- All tables from
03-database-model.mdas Drizzle schema + migrations - CRUD-level modules for
topics,notifications(insert, list, get) with no auth yet (stubbed), so the data layer can be exercised - OpenAPI schema generation wired up,
/docsserving it - Unit tests for schema constraints (unique slugs, FK cascades, retention math)
- Exit criteria: migrations run clean on a fresh DB; can insert/list a topic and a notification via the API without auth.
Phase 4 — Authentication + API tokens
- OIDC module (
openid-client): login/callback/logout, PKCE, state/nonce validation, session cookie issuance, bootstrap-first-admin logic - Proxy-auth module: trusted-CIDR check, header parsing, session creation
AUTH_MODE=disabled(local dev convenience: single implicit admin user)- API token module: create/list/revoke/rotate, hashing, scoping (topics/source/CIDR/expiry), last-used tracking
- CSRF protection (Origin check + custom header) on listener A mutations
- Tests: each auth mode, token scope enforcement, revoked/expired token rejection, CSRF rejection on cross-origin mutation attempts
- Exit criteria: MVP criteria #5–7 (auth works, admin can create a topic, admin can create a scoped token) pass manually and via tests.
Phase 5 — Notification ingestion + history
- Listener B: canonical publish endpoint,
/api/v1/topics/:slugalias, ntfy-compat endpoint, webhook endpoint - Rate limiter (per-token, per-IP),
INGESTION_ALLOWED_CIDRSenforcement - Deduplication logic, occurrence_count increment
- SSE stream on listener A (
notification.created/updatedevents) - Read/unread + starred state, unread-count aggregation per topic
- Retention cleanup interval job
- Tests: ingestion auth/scoping, rate limiting (burst + sustained), dedup window behavior, SSE fan-out, retention deletion
- Exit criteria: MVP criteria #8–10 (LAN client can publish, appears instantly via SSE, persisted) pass.
Phase 6 — Frontend UI
- Navigation shells: mobile bottom nav (Inbox/Topics/Search/Starred/ Settings), desktop sidebar + stream + optional detail panel
- Topic list (unread/total/latest, bell state), topic settings screen (push ON/OFF toggle)
- Notification list (cards per spec), notification detail
(
/topics/:slug/messages/:id), search + filters - Empty states, loading skeletons, dark-first theme, responsive breakpoints
- Manual + automated visual smoke checks in the Browser tool (mobile and desktop viewport)
- Exit criteria: MVP criteria #22 (polished on iPhone and desktop) — judged by hand in the browser tool at this stage, refined further in Phase 7/8.
Phase 7 — PWA + Web Push
vite-plugin-pwawithinjectManifest, manifest.webmanifest, icon set (incl. maskable + apple-touch-icon), safe-area CSS- Service worker: precache, runtime caching,
push/notificationclickhandlers - VAPID generation/storage in
system_settings,web-pushsend integration wired into the Phase 5 fan-out step (previously a no-op stub) - Subscribe/unsubscribe flow, devices screen, diagnostics screen
- Manual iPhone test pass (install to Home Screen, enable notifications, trigger a real push, confirm deep link opens the right message)
- Exit criteria: MVP criteria #11–18 (PWA installable, push permission, subscription stored, push ON sends / OFF doesn't, arrives on iPhone, tapping opens exact notification) pass.
Phase 8 — Admin UI + rate limiting polish + diagnostics
- Admin dashboard widgets (
04-api-design.md's/admin/stats/dashboard) - Noisy-source detection thresholds + admin warnings
- Tokens/Users/Devices/Push/Security/System admin screens
- Audit log viewer
- Settings screens (user + admin) per
01-architecture.md - Exit criteria: full admin IA from the spec is navigable and functional; rate-limit rejections are visible end-to-end (trigger one, see it flagged).
Phase 9 — Migration helpers + ntfy compatibility hardening
docs/migration-ntfy.md: side-by-side ntfy → Notizarr endpoint mappingdocs/home-assistant.md:rest_commandexample replacingntfy.publish(as given in the spec), plus one example per currently-used service (Seerr, Uptime Kuma, generic webhook)- Compatibility test matrix: header-based, JSON-based, query-param-based ntfy-style requests all produce equivalent stored notifications
- Exit criteria: MVP criteria #8 re-verified using an ntfy-style request shape, not just the canonical API.
Phase 10 — Testing + hardening + documentation
- Fill any remaining gaps in the automated test list from the original spec (stale push subscription cleanup, device removal, authorization matrix across roles, retention cleanup edge cases)
- Security pass: CSP, HSTS, frame-ancestors, request size limits, dependency audit
- Top-level
README.md: install, configure.env, deploy on Unraid, backup/restore, troubleshooting (linking the diagnostics screen) - Full MVP acceptance checklist run end-to-end, including container restart persistence checks (#19–21)
- Exit criteria: all 22 MVP acceptance criteria pass.
Cross-cutting, done incrementally rather than as a separate phase
- Structured logging + secret redaction: introduced in Phase 2, extended as new modules land
- Metrics: introduced alongside the ingestion endpoints in Phase 5
- Lint/typecheck/test/build as CI-equivalent local commands: set up in Phase 2, run at the end of every subsequent phase