fix: apply 8 persona review corrections to Sprint 6 plan (v3)

This commit is contained in:
Patrick Plate
2026-06-12 22:06:08 +02:00
parent 8391dbb2cd
commit 4fa068092f
+26 -10
View File
@@ -2,7 +2,7 @@
**Date:** 2026-06-12 **Date:** 2026-06-12
**Author:** Patrick Plate / Lumen (Planner) **Author:** Patrick Plate / Lumen (Planner)
**Status:** Draft v2 **Status:** Draft v3 (post-review)
**Base Branch:** `main` **Base Branch:** `main`
**Sprint Branch:** `sprint/6-production` **Sprint Branch:** `sprint/6-production`
**Sprint Goal:** Production readiness — deploy, DSGVO compliance, Stripe payments, immutable audit log, grow calendar, notifications, launch **Sprint Goal:** Production readiness — deploy, DSGVO compliance, Stripe payments, immutable audit log, grow calendar, notifications, launch
@@ -143,6 +143,7 @@ server {
add_header X-Content-Type-Options nosniff always; add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always; add_header X-Frame-Options DENY always;
add_header Referrer-Policy strict-origin-when-cross-origin always; add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' js.stripe.com; frame-src js.stripe.com hooks.stripe.com; img-src 'self' data: *.stripe.com;" always;
# Rate limiting # Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s; limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
@@ -267,8 +268,10 @@ WEEKLY_RETENTION=28
TIMESTAMP=$(date +%Y%m%d_%H%M%S) TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DAY_OF_WEEK=$(date +%u) DAY_OF_WEEK=$(date +%u)
# GPG-encrypted backup (DSGVO-compliant)
docker exec cannamanage-db pg_dump -U ${DB_USER} -d ${DB_NAME} \ docker exec cannamanage-db pg_dump -U ${DB_USER} -d ${DB_NAME} \
| gzip > "${BACKUP_DIR}/cannamanage_daily_${TIMESTAMP}.sql.gz" | gpg --encrypt --recipient cannamanage-backup \
> "${BACKUP_DIR}/cannamanage_daily_${TIMESTAMP}.sql.gpg"
# Weekly backup on Sundays # Weekly backup on Sundays
if [ "$DAY_OF_WEEK" -eq 7 ]; then if [ "$DAY_OF_WEEK" -eq 7 ]; then
@@ -462,9 +465,10 @@ public ResponseEntity<Void> requestErasure(@AuthenticationPrincipal UserDetails
| Tier | Price | Features | Stripe Price ID | | Tier | Price | Features | Stripe Price ID |
|------|-------|----------|-----------------| |------|-------|----------|-----------------|
| **Free Trial** | €0 (14 days) | Full features, 1 club, max 7 members | — (trial period on Starter) | | **Free Trial** | €0 (3 months) | Full features, 1 club, ≤30 members | — (trial period on Starter) |
| **Starter** | €29/month | 1 club, up to 50 members, email support | `price_starter_monthly` | | **Starter** | €19/month | 1 club, ≤30 members, email support | `price_starter_monthly` |
| **Pro** | €79/month | 1 club, unlimited members, priority support, API access | `price_pro_monthly` | | **Pro** | €49/month | 1 club, ≤100 members, priority support, API access | `price_pro_monthly` |
| **Enterprise** | Custom | Multiple clubs, unlimited members, dedicated support, SLA | Contact sales |
**Backend — StripeService:** **Backend — StripeService:**
@@ -508,7 +512,7 @@ public class StripeService {
.setSuccessUrl(successUrl + "?session_id={CHECKOUT_SESSION_ID}") .setSuccessUrl(successUrl + "?session_id={CHECKOUT_SESSION_ID}")
.setCancelUrl(cancelUrl) .setCancelUrl(cancelUrl)
.setSubscriptionData(SessionCreateParams.SubscriptionData.builder() .setSubscriptionData(SessionCreateParams.SubscriptionData.builder()
.setTrialPeriodDays(14L) .setTrialPeriodDays(90L) // 3-month free trial
.build()) .build())
.build(); .build();
return Session.create(params); return Session.create(params);
@@ -919,13 +923,13 @@ public class NotificationService {
## 4. Phase Details ## 4. Phase Details
### Phase 1: Production Deployment (Hetzner VPS) ### Phase 1: Production Deployment (IONOS VPS)
**Effort:** 3 days **Effort:** 3 days
| Step | Task | Files | | Step | Task | Files |
|------|------|-------| |------|------|-------|
| 1.1 | Provision Hetzner VPS (CX31), install Docker + Docker Compose + Nginx + Certbot | Server setup (manual) | | 1.1 | Configure IONOS VPS (existing plate-software.de), install Docker + Docker Compose + Nginx + Certbot | Server setup (manual) |
| 1.2 | Register domain, configure DNS A/AAAA records | DNS config (manual) | | 1.2 | Register domain, configure DNS A/AAAA records | DNS config (manual) |
| 1.3 | Create `docker-compose.prod.yml` with env vars, volumes, restart policies | `docker-compose.prod.yml` | | 1.3 | Create `docker-compose.prod.yml` with env vars, volumes, restart policies | `docker-compose.prod.yml` |
| 1.4 | Create `.env.prod.example` with all required env vars documented | `.env.prod.example` | | 1.4 | Create `.env.prod.example` with all required env vars documented | `.env.prod.example` |
@@ -1005,7 +1009,7 @@ public class NotificationService {
- [ ] Club owner can subscribe via SEPA Lastschrift (direct debit) - [ ] Club owner can subscribe via SEPA Lastschrift (direct debit)
- [ ] Club owner can subscribe via PayPal or Card as alternatives - [ ] Club owner can subscribe via PayPal or Card as alternatives
- [ ] 14-day free trial starts automatically for new clubs - [ ] 3-month free trial starts automatically for new clubs
- [ ] After trial expiry without payment → club access locked with "Please subscribe" message - [ ] After trial expiry without payment → club access locked with "Please subscribe" message
- [ ] `invoice.paid` webhook extends access, stores payment in audit log - [ ] `invoice.paid` webhook extends access, stores payment in audit log
- [ ] `invoice.payment_failed` webhook shows warning banner to club owner - [ ] `invoice.payment_failed` webhook shows warning banner to club owner
@@ -1015,6 +1019,8 @@ public class NotificationService {
- [ ] Webhook endpoint validates Stripe signature (rejects spoofed events) - [ ] Webhook endpoint validates Stripe signature (rejects spoofed events)
- [ ] All payment events logged in audit log - [ ] All payment events logged in audit log
**Voraussetzung:** Auftragsverarbeitungsvertrag (AVV) mit Stripe abschließen (DSGVO Art. 28). Stripe bietet standardisiertes DPA unter stripe.com/de/legal/dpa.
--- ---
### Phase 4: Immutable Audit Log ### Phase 4: Immutable Audit Log
@@ -1046,6 +1052,16 @@ public class NotificationService {
- [ ] Search works across description field (full-text) - [ ] Search works across description field (full-text)
- [ ] Pagination works (default 50 per page) - [ ] Pagination works (default 50 per page)
**Database-level protection:**
```sql
REVOKE DELETE ON audit_events FROM cannamanage_app;
```
Only the DBA role can delete audit records — the application user has INSERT + SELECT only.
**Aufbewahrungsfrist:** 10 Jahre (KCanG-konform). Automatische Löschung nach 10 Jahren via Scheduled Task.
--- ---
### Phase 5: Grow Calendar (FULL) ### Phase 5: Grow Calendar (FULL)
@@ -1166,7 +1182,7 @@ public class NotificationService {
| R1 | Stripe integration complexity (SEPA mandates, webhook reliability) | Medium | High | Start with test mode, use Stripe CLI for local webhook testing, extensive logging | | R1 | Stripe integration complexity (SEPA mandates, webhook reliability) | Medium | High | Start with test mode, use Stripe CLI for local webhook testing, extensive logging |
| R2 | DSGVO compliance gaps (incomplete data export, retention conflicts) | Medium | High | Legal review of Datenschutzerklärung before launch, document what data is retained and why | | R2 | DSGVO compliance gaps (incomplete data export, retention conflicts) | Medium | High | Legal review of Datenschutzerklärung before launch, document what data is retained and why |
| R3 | Production deployment issues (SSL, DNS propagation, Docker networking) | Low | Medium | Test with staging subdomain first (`staging.cannamanage.de`), blue-green deploy | | R3 | Production deployment issues (SSL, DNS propagation, Docker networking) | Low | Medium | Test with staging subdomain first (`staging.cannamanage.de`), blue-green deploy |
| R4 | WebSocket scalability on single VPS | Low | Low | Hetzner CX31 handles 1000+ concurrent WS connections easily for MVP; scale later if needed | | R4 | WebSocket scalability on single VPS | Low | Low | IONOS VPS handles 1000+ concurrent WS connections easily for MVP; scale later if needed |
| R5 | Stripe webhook delivery failures (lost events) | Low | High | Implement idempotent webhook handler + periodic reconciliation job (check subscription status every 6h) | | R5 | Stripe webhook delivery failures (lost events) | Low | High | Implement idempotent webhook handler + periodic reconciliation job (check subscription status every 6h) |
| R6 | Backup corruption / restore failure | Low | Critical | Test restore procedure monthly, verify backup integrity with checksum | | R6 | Backup corruption / restore failure | Low | Critical | Test restore procedure monthly, verify backup integrity with checksum |
| R7 | FullCalendar bundle size (large JS library) | Low | Low | Dynamic import, lazy-load only on `/grow` route | | R7 | FullCalendar bundle size (large JS library) | Low | Low | Dynamic import, lazy-load only on `/grow` route |