chore: Remove production Docker infrastructure and reorganize docs
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled

- Remove production Docker Compose files (docker-compose.production.yml, docker-compose.prod-simple.yml)
- Remove production Dockerfiles (backend and frontend)
- Move implementation docs to docs/implementation-docs/ directory
- Remove test scripts (test-embeddings.js, test-voice-*.js/sh)
- Update ecosystem.config.js with production environment variables (CORS, JWT secrets, database config)
- Add database connection pooling configuration
- Update CORS configuration for production domains (parentflowapp.com)
- Fix frontend dev server port configuration (3005)
- Add PWA web push implementation plan documentation
- Simplify health check endpoints (remove MongoDB/Redis specific checks)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-06 21:03:11 +00:00
parent a6b3ad67fb
commit 8ae42ffc75
28 changed files with 547 additions and 1536 deletions

View File

@@ -0,0 +1,291 @@
# PWA Web Push (Local, Apprise) — MVP Implementation Plan
**Goal:** Ship a fully local/browser-push MVP (no Firebase). Frontend collects Web Push subscriptions (VAPID); backend stores and routes; a local dispatcher sends notifications via **Apprise** (`vapid://`). Optional Kafka for decoupling.
---
## Phase 0 — Foundations & Decisions (1 day)
**Outcomes**
- PWA target browsers: Chrome/Edge/Firefox (desktop/mobile), Safari iOS 16.4+ (installed PWA).
- Tech choices:
- Frontend: existing web app + Service Worker.
- Backend: **FastAPI (Python)** or **Node/Express** (pick one).
- Dispatcher: Python + Apprise.
- Storage: Postgres (or SQLite for dev).
- Messaging (optional but recommended): Kafka (local), else direct HTTP call.
- Domain + TLS (required for Push): HTTPS everywhere.
**Deliverables**
- `.env.example` (VAPID\_PRIVATE\_KEY\_PATH, VAPID\_PUBLIC\_KEY, DB\_URL, KAFKA\_BROKERS, APPRISE\_STORAGE\_PATH).
- VAPID keypair generated.
```bash
# Example: generate VAPID keys (node-web-push)
npx web-push generate-vapid-keys
# save PUBLIC / PRIVATE into secure storage or PEM files
```
---
## Phase 1 — PWA Frontend (Service Worker & Subscription) (0.51 day)
**Tasks**
- Register Service Worker: `sw.js`.
- Permission flow: `Notification.requestPermission()` + feature checks.
- Subscribe user: `registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: VAPID_PUBLIC })`.
- Send `subscription` JSON to backend; handle revoke/refresh.
**Minimal code (TypeScript/JS)**
```js
// app-push.ts
export async function ensurePushSubscription(vapidPublicKey) {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) return null;
const reg = await navigator.serviceWorker.register('/sw.js');
const perm = await Notification.requestPermission();
if (perm !== 'granted') return null;
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
});
// POST to backend
await fetch('/api/push/subscriptions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(sub) });
return sub;
}
```
**Service Worker**
```js
// sw.js
self.addEventListener('push', event => {
const data = event.data ? event.data.json() : {};
event.waitUntil(self.registration.showNotification(data.title || 'Notification', {
body: data.body,
icon: data.icon,
badge: data.badge,
data: data.data,
tag: data.tag, // collapseId equivalent
requireInteraction: !!data.requireInteraction,
}));
});
self.addEventListener('notificationclick', event => {
event.notification.close();
const url = event.notification.data?.url || '/';
event.waitUntil(clients.openWindow(url));
});
```
---
## Phase 2 — Backend Subscription API & Storage (0.5 day)
**Tables (Postgres)**
```sql
create table push_subscriptions (
id uuid primary key default gen_random_uuid(),
user_id uuid not null,
endpoint text not null unique,
p256dh text not null,
auth text not null,
ua text,
created_at timestamptz default now(),
updated_at timestamptz default now(),
last_status int,
active boolean default true
);
create index on push_subscriptions(user_id);
```
**HTTP API**
- `POST /api/push/subscriptions` — upsert subscription (by `endpoint`).
- `DELETE /api/push/subscriptions/:id` — deactivate.
- `GET /api/push/subscriptions/me` — list current users subs.
**Validation**: ensure `endpoint`, `keys.p256dh`, `keys.auth` present.
---
## Phase 3 — Message Contract & Routing (0.5 day)
**Unified message (Kafka or HTTP)**
```json
{
"user_id": "uuid",
"channels": ["webpush"],
"webpush": {
"title": "ParentFlow",
"body": "Reminder: feeding due",
"icon": "/icons/app.png",
"badge": "/icons/badge.png",
"tag": "timeline-123",
"data": { "url": "/timeline/123" }
},
"dedupe_key": "timeline-123",
"ttl_seconds": 3600,
"priority": "normal"
}
```
**Routing**
- App publishes message per user/segment → Kafka topic `notify.events` (or POST `/api/push/send`).
- Dispatcher consumes and fans out to each active subscription for that user.
---
## Phase 4 — Local Dispatcher (Python + Apprise) (1 day)
**Responsibilities**
- Consume messages (Kafka) or receive HTTP.
- Load active subscriptions for `user_id`.
- For each subscription → invoke Apprise `vapid://` with per-subscription `subfile` data.
- Update delivery result (`last_status`, deactivate on 404/410).
**Apprise usage (per send)**
```python
import apprise, json, tempfile
ap = apprise.Apprise()
# write subscription JSON to a temp file (or pass inline as data URI)
subfile = tempfile.NamedTemporaryFile(delete=False)
subfile.write(json.dumps(subscription).encode()); subfile.flush()
ap.add(
f"vapid://{MAILTO_IDENT}/{subscription['endpoint']}?"
f"keyfile={VAPID_PRIVATE_KEY_PATH}&subfile={subfile.name}"
)
ap.notify(title=msg['webpush']['title'], body=msg['webpush']['body'])
```
**Failures handling**
- HTTP 404/410 → mark `active=false`.
- 429/5xx → exponential backoff (retry queue with max attempts).
**Performance**
- Batch fan-out with worker pool (e.g., `concurrent.futures.ThreadPoolExecutor`).
- Keep Apprise in-memory; enable persistent storage `AUTO` for token caching.
---
## Phase 5 — Admin & Lifecycle (0.5 day)
- Subscription pruning cron: deactivate stale (`updated_at < now()-90d`) or failed endpoints.
- Unsubscribe endpoint (user action) → delete/deactivate.
- Privacy: per-user export & hard delete subscriptions on request.
---
## Phase 6 — Observability (0.5 day)
- Structured logs (JSON) for send attempts with `endpoint_hash` only (no PII).
- Metrics: sends, success rate, failures by code, active subs, opt-in rate.
- Dashboards: Grafana/Prometheus (optional) or simple SQL views.
---
## Phase 7 — Security & Compliance (0.5 day)
- Store VAPID private key on disk with strict permissions or in a local vault.
- HTTPS only; set `Strict-Transport-Security`.
- CSRF for subscription endpoints; auth required.
- Rate limit `/api/push/subscriptions` + `/api/push/send`.
- Content rules: cap payload size (<4KB), sanitize URLs.
---
## Phase 8 — iOS/Safari Specifics (notes)
- Web Push works for **installed** PWA only (Add to Home Screen).
- Permission must be user-gesture initiated.
- Background delivery may be throttled; design for non-guaranteed delivery.
---
## Phase 9 — Testing & Load (0.51 day)
- Unit: subscription CRUD, dispatcher send mock.
- E2E: subscribe → send → receive across browsers.
- Load: N users × M subs; verify throughput and backoff.
---
## Phase 10 — Rollout & Feature Flags (0.5 day)
- Feature flag `webpush_enabled` per user/tenant.
- Gradual rollout: 5% → 25% → 100%.
- Fallback channel (email/Telegram via Apprise) if webpush not available.
---
## Upgrade Path — Firebase/OneSignal (when needed)
- Abstract `Notifier` with drivers: `webpush_vapid`, `fcm`, `onesignal`.
- Mirror message schema; add provider-specific fields.
- Migration: dual-write for 12 weeks, compare delivery metrics, then switch.
---
## Acceptance Criteria
- Users can opt-in, receive a test notification within 3s median on desktop Chrome.
- Subscriptions are persisted and deduplicated by `endpoint`.
- Dead endpoints are auto-pruned on 404/410 within 24h.
- No VAPID private keys leak in logs; payload ≤ 4KB; HTTPS enforced.
---
## Quick Reference (Snippets)
**VAPID env**
```ini
VAPID_PUBLIC_KEY=...
VAPID_PRIVATE_KEY_PATH=/secrets/vapid_private_key.pem
MAILTO_IDENT=push@yourdomain.com
```
**HTTP publish (no Kafka) — example contract**
```http
POST /api/push/send
Content-Type: application/json
{ "user_id": "...", "webpush": { "title": "Hi", "body": "", "data": {"url":"/"} } }
```
**Kafka topics (optional)**
- `notify.events` (ingress)
- `notify.retry` (backoff)
- `notify.deadletter`
---
## Risks & Mitigations
- **Browser variability** → test matrix; graceful degradation.
- **Quota / payload limits** → compact payloads; use `tag` to collapse duplicates.
- **No delivery guarantees** → show in-app inbox as source of truth.
---
## Done Means
- End-to-end working on Chrome desktop + Android Chrome.
- At least 1 iOS PWA device validated.
- Metrics panel shows ≥95% success on active endpoints over 48h.