fix: apply 8 persona review corrections to Sprint 6 plan (v3)
This commit is contained in:
@@ -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 |
|
||||||
|
|||||||
Reference in New Issue
Block a user