Files
cannamanage/docs/sprint-7/cannamanage-sprint7-analysis.md
Patrick Plate 706a6e257b feat(sprint7): Phase 1 — notifications enhancement + push infrastructure
Phase 1 (Notification Enhancement):
- Extended NotificationType enum (ADMIN_MESSAGE, INFO_BOARD_POST, FORUM_REPLY, FORUM_MENTION)
- Extended StaffPermission enum (SEND_NOTIFICATIONS, MANAGE_INFO_BOARD, MODERATE_FORUM)
- Extended AuditEventType with Sprint 7 events
- Flyway V11: notification_sends + notification_send_recipients tables
- NotificationSend + NotificationSendRecipient entities
- NotificationSendRepository + NotificationSendRecipientRepository
- Extended NotificationService with sendBroadcast() and sendToSelected()
- NotificationComposeController (POST /compose, GET /sends)
- ComposeNotificationRequest DTO

Phase 1B (Push Infrastructure):
- Flyway V12: device_tokens + notification_preferences tables
- DeviceToken entity + DevicePlatform enum
- NotificationPreference entity + NotificationChannel enum
- DeviceTokenRepository + NotificationPreferenceRepository
- DeviceRegistrationService (register/unregister/list devices, max 10 per user)
- NotificationPreferenceService (get/create defaults, update, IN_APP always on)
- NotificationDispatchService (multi-channel fan-out: WebSocket, Web Push, FCM, Email)
- WebPushSender (VAPID-based, simplified for MVP)
- FcmPushSender (graceful degradation if not configured)
- PushPayload DTO
- DeviceRegistrationController (POST/GET/DELETE /devices, GET /vapid-key)
- NotificationPreferenceController (GET/PUT /preferences)
- ConsentType extended (NOTIFICATION_PUSH, NOTIFICATION_EMAIL)
- TargetType enum (ALL, SELECTED)

Frontend:
- Updated sw.js with push event handler + notification click handler
- push-subscription.ts (subscribeToPush, unsubscribe, permission helpers)
- notification-compose.ts service (compose, sends, devices, preferences APIs)
- i18n keys (de.json + en.json) for compose, preferences, push, devices

Configuration:
- application-docker.properties: VAPID + FCM push config properties
- MemberRepository: added findAllActiveUserIds() for broadcast
2026-06-13 19:25:19 +02:00

806 lines
39 KiB
Markdown

# Sprint 7 Feature Analysis — Communication & Community
**Date:** 2026-06-13
**Author:** Patrick Plate / Lumen (Architect)
**Status:** Draft v1
**Sprint Goal:** Transform CannaManage from a compliance tool into a community platform — the communication layer that keeps members engaged and clubs organized.
---
## Executive Summary
Sprint 7 introduces four communication features that turn CannaManage from "backend compliance software" into a living club platform. The recommended Sprint 7 scope is:
1. **Club Info Board** (Schwarzes Brett) — ship it. Core feature, medium effort, high engagement value.
2. **Club-Internal Forum** — ship it (MVP). Threaded discussions within a club. High member engagement, competitive differentiator.
3. **Club-to-Member Notifications** (admin-composed) — ship it. Builds on existing Sprint 6 infrastructure. Low effort, high value.
4. **Cross-Club Community Forum** — defer to Sprint 8+. Legal complexity (KCanG), moderation overhead, and "nice to have" status make it wrong for this sprint.
**Strategic rationale:** No competitor (420cloud, Hanf-App, Cannanas) offers in-app community features. Clubs currently use Telegram/Signal/WhatsApp groups, which are unmoderated, unarchived, and disconnected from the platform. Owning the communication layer creates massive switching costs — once a club's discussions live in CannaManage, they can't leave without losing history.
---
## 1. Club Info Board (Schwarzes Brett)
### 1.1 Problem Statement
Club admins need a one-to-many broadcast channel: announcements, rule changes, event notices, operational updates ("We're closed next Thursday"), new strain arrivals. Today this happens in Telegram groups where messages get buried, new members miss old announcements, and there's no archive.
The Info Board is **not** a conversation — it's a pinboard. Staff posts, members read. Think: physical Schwarzes Brett in the club room, digitized.
### 1.2 How It Differs from Notifications
| Aspect | Notifications | Info Board |
|--------|--------------|-----------|
| Trigger | System-generated events (quota warning, batch recalled) | Human-authored by admin/staff |
| Persistence | Transient — read and dismiss | Permanent — archived, searchable |
| Visibility | Individual user inbox | All club members see all posts |
| Rich content | Short text + link | Rich text, images, attachments |
| Categories | NotificationType enum | Admin-defined categories |
| Interaction | Mark as read | Pin, archive, comment (optional) |
### 1.3 User Stories
| # | As a... | I want to... | So that... | Priority |
|---|---------|-------------|-----------|----------|
| IB-01 | Club Admin | Post an announcement with title, body, and category | All members see it on their dashboard | P0 |
| IB-02 | Club Admin | Pin an important post to the top | Critical info (rules, hours) stays visible | P0 |
| IB-03 | Club Admin | Attach images or PDFs to a post | I can share event flyers or updated rules docs | P1 |
| IB-04 | Club Admin | Archive old posts (not delete) | The board stays clean but history is preserved | P1 |
| IB-05 | Club Admin | Assign categories (Events, Rules, General, Strain News) | Members can filter what interests them | P1 |
| IB-06 | Staff (with permission) | Post announcements | The admin doesn't have to do everything | P0 |
| IB-07 | Member | See announcements on my portal dashboard | I don't miss important club news | P0 |
| IB-08 | Member | Filter posts by category | I only see what's relevant to me | P2 |
| IB-09 | Member | Get notified when a new post appears | I'm alerted even if I don't check the board | P1 |
### 1.4 Data Model
```sql
-- V11: Info Board
CREATE TABLE info_board_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
color VARCHAR(7), -- hex color for UI badge
sort_order INTEGER NOT NULL DEFAULT 0,
tenant_id UUID NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE TABLE info_board_posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL, -- markdown or sanitized HTML
category_id UUID REFERENCES info_board_categories(id),
author_id UUID NOT NULL, -- staff or admin user
pinned BOOLEAN NOT NULL DEFAULT false,
archived BOOLEAN NOT NULL DEFAULT false,
published_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
tenant_id UUID NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE TABLE info_board_attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
post_id UUID NOT NULL REFERENCES info_board_posts(id) ON DELETE CASCADE,
filename VARCHAR(255) NOT NULL,
content_type VARCHAR(100) NOT NULL,
size_bytes BIGINT NOT NULL,
storage_path VARCHAR(500) NOT NULL,
tenant_id UUID NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_info_board_posts_tenant ON info_board_posts(tenant_id, archived, pinned DESC, published_at DESC);
CREATE INDEX idx_info_board_posts_category ON info_board_posts(category_id);
```
**Key design decisions:**
- Markdown body (rendered on frontend) — simple, secure, no XSS risk from raw HTML
- `pinned` as boolean — pinned posts sort first, then by `published_at DESC`
- Soft-archive (not delete) — compliance friendliness, audit trail integrity
- Attachments stored on filesystem (or S3-compatible) — not in DB
### 1.5 UI Mockup Description
**Admin/Staff view (Dashboard → Info Board):**
- Top: "New Post" button (opens rich text editor)
- Category filter tabs (All | Events | Rules | General | Strain News)
- Post list: card layout with title, excerpt, author, date, pinned badge
- Each card: actions dropdown (Edit, Pin/Unpin, Archive)
- Attachment upload: drag-and-drop zone in the editor
**Portal/Member view (Portal Dashboard → Announcements section):**
- Latest 3-5 posts shown as cards on the portal dashboard
- "View All" link → dedicated announcements page
- Category color badges
- Pinned posts have a 📌 indicator and always show first
- Unread indicator (new posts since last visit)
### 1.6 Integration Points
| Integration | How |
|------------|-----|
| Notifications (existing) | New post → auto-generate `INFO_BOARD_POST` notification for all club members |
| WebSocket (existing) | Push new-post event in real-time to connected members |
| Audit Log (existing) | Log post creation, edits, archives as `INFO_BOARD_CREATED` / `INFO_BOARD_EDITED` / `INFO_BOARD_ARCHIVED` |
| StaffPermission | New permission: `MANAGE_INFO_BOARD` — controls who can post/edit/archive |
### 1.7 Plan Tier Mapping
| Tier | Info Board Access |
|------|------------------|
| Starter | ✅ Basic (3 categories max, no attachments) |
| Pro | ✅ Full (unlimited categories, attachments up to 10 MB) |
| Enterprise | ✅ Full + scheduled posts, auto-archive after N days |
---
## 2. Club-Internal Forum (Vereinsinterne Diskussion)
### 2.1 Problem Statement
Members want to talk to each other — not just passively receive announcements. Today they use Telegram/WhatsApp groups that are:
- Unmoderated (spam, off-topic, conflicts)
- Ephemeral (messages get buried, no searchable archive)
- Disconnected from the club platform (no link to member profiles, no awareness of club context)
- A privacy leak (phone numbers exposed to all members)
A club-internal forum gives members a place to discuss within the platform — moderated, archived, searchable, and integrated with member identity.
### 2.2 How It Differs from Info Board
| Aspect | Info Board | Forum |
|--------|-----------|-------|
| Direction | One-to-many (staff → members) | Many-to-many (members ↔ members) |
| Who posts | Admin/Staff only | All members |
| Structure | Flat list of posts | Threaded: Topics → Replies |
| Moderation | N/A (staff controls everything) | Staff can moderate, lock, delete |
| Purpose | Official communication | Community discussion |
### 2.3 User Stories
| # | As a... | I want to... | So that... | Priority |
|---|---------|-------------|-----------|----------|
| FO-01 | Member | Create a new discussion topic | I can ask questions or start conversations | P0 |
| FO-02 | Member | Reply to an existing topic | I can participate in discussions | P0 |
| FO-03 | Member | Browse topics by category | I find relevant discussions | P0 |
| FO-04 | Member | See who posted (display name + avatar) | I know who I'm talking to | P0 |
| FO-05 | Member | Edit my own posts (within 30 min) | I can fix typos | P1 |
| FO-06 | Member | Report inappropriate posts | I can flag content for moderators | P1 |
| FO-07 | Staff/Admin | Create and manage forum categories | Discussions are organized | P0 |
| FO-08 | Staff/Admin | Delete posts / lock topics | I can moderate inappropriate content | P0 |
| FO-09 | Staff/Admin | Pin important topics | Key discussions stay visible | P1 |
| FO-10 | Staff/Admin | See reported posts in a moderation queue | I can act on reports efficiently | P1 |
| FO-11 | Member | Get notified when someone replies to my topic | I don't miss responses | P1 |
| FO-12 | Member | Upload images in posts | I can share photos (grow results, events) | P2 |
| FO-13 | Member | React to posts (thumbs up, leaf emoji) | I can show appreciation without cluttering | P2 |
### 2.4 Data Model
```sql
-- V12: Club-Internal Forum
CREATE TABLE forum_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
description VARCHAR(500),
icon VARCHAR(50), -- emoji or icon name
sort_order INTEGER NOT NULL DEFAULT 0,
tenant_id UUID NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE TABLE forum_topics (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL, -- first post content (markdown)
category_id UUID NOT NULL REFERENCES forum_categories(id),
author_id UUID NOT NULL, -- member or staff user
pinned BOOLEAN NOT NULL DEFAULT false,
locked BOOLEAN NOT NULL DEFAULT false,
reply_count INTEGER NOT NULL DEFAULT 0,
last_reply_at TIMESTAMP WITH TIME ZONE,
last_reply_by UUID,
tenant_id UUID NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE TABLE forum_replies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
topic_id UUID NOT NULL REFERENCES forum_topics(id) ON DELETE CASCADE,
body TEXT NOT NULL, -- markdown
author_id UUID NOT NULL,
edited_at TIMESTAMP WITH TIME ZONE,
tenant_id UUID NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE TABLE forum_reports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
reporter_id UUID NOT NULL,
target_type VARCHAR(10) NOT NULL, -- 'TOPIC' or 'REPLY'
target_id UUID NOT NULL,
reason VARCHAR(500) NOT NULL,
resolved BOOLEAN NOT NULL DEFAULT false,
resolved_by UUID,
resolved_at TIMESTAMP WITH TIME ZONE,
tenant_id UUID NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE TABLE forum_reactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
target_type VARCHAR(10) NOT NULL, -- 'TOPIC' or 'REPLY'
target_id UUID NOT NULL,
reaction VARCHAR(20) NOT NULL, -- 'thumbsup', 'leaf', 'fire', 'heart'
tenant_id UUID NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
UNIQUE(user_id, target_type, target_id, reaction)
);
CREATE INDEX idx_forum_topics_tenant ON forum_topics(tenant_id, category_id, pinned DESC, last_reply_at DESC);
CREATE INDEX idx_forum_replies_topic ON forum_replies(topic_id, created_at);
CREATE INDEX idx_forum_reports_unresolved ON forum_reports(tenant_id, resolved, created_at);
```
**Key design decisions:**
- Flat replies (not nested/threaded) — simpler UX, easier to follow, works on mobile
- `reply_count` + `last_reply_at` denormalized on topics — avoids expensive JOINs for listing
- Edit window: 30 minutes (set in application logic, not DB)
- Reactions: limited set (4-5 emojis) — not free-form to keep it simple
- Reports go to a moderation queue — staff sees flagged content
### 2.5 Moderation Features
| Feature | How It Works |
|---------|-------------|
| Delete post/reply | Staff removes content, replaced with "[Beitrag entfernt]" placeholder |
| Lock topic | No new replies allowed. Existing content preserved. |
| Pin topic | Pinned topics always appear at the top of category view |
| Report queue | Members flag → staff sees queue → resolve (delete, warn, or dismiss) |
| Auto-lock inactive | Topics with no replies for 90 days auto-lock (configurable) |
| Word filter | Optional blocklist (admin-configured) — posts with blocked words held for review |
**New StaffPermission:** `MODERATE_FORUM` — controls access to moderation queue and lock/delete actions.
### 2.6 UI Mockup Description
**Forum main page (Dashboard → Forum):**
- Category cards with name, description, topic count, last activity
- "New Topic" button (per category or global)
- Search bar (full-text search across topics and replies)
**Category view:**
- Topic list: title, author, reply count, last reply date
- Pinned topics at top with badge
- Locked topics with lock icon
- Pagination (20 topics per page)
**Topic view:**
- Original post at top
- Replies below in chronological order
- Reply editor at bottom (markdown with preview)
- Reaction buttons on each post
- "Report" link on each post (subtle, doesn't clutter)
- Lock/Delete/Pin controls for staff (contextual)
**Portal/Member view:**
- Same forum, just within the portal layout
- Members see their own posts highlighted
- "My Topics" and "Subscribed" quick filters
### 2.7 Plan Tier Mapping
| Tier | Forum Access |
|------|-------------|
| Starter | ❌ Not included (upgrade incentive) |
| Pro | ✅ Full forum (5 categories max) |
| Enterprise | ✅ Full forum (unlimited categories, custom word filter, advanced moderation) |
---
## 3. Cross-Club Community Forum
### 3.1 Problem Statement
Members from different clubs may want to exchange knowledge — growing tips, legal questions, strain reviews, event coordination. Currently there's no platform-level community; people use Reddit, Telegram, or CannaConnection forums.
A cross-club forum would make CannaManage a "social network for CSC members" — a powerful retention mechanism. But it also introduces significant complexity: moderation at scale, privacy concerns, and legal implications under KCanG.
### 3.2 User Stories
| # | As a... | I want to... | So that... | Priority |
|---|---------|-------------|-----------|----------|
| CC-01 | Member | Browse community topics from all clubs | I can learn from other clubs' experiences | P1 |
| CC-02 | Member | Post in the community forum | I can share knowledge with the wider community | P1 |
| CC-03 | Member | Choose to appear as "Member of [ClubName]" or anonymous | I control my privacy | P0 |
| CC-04 | Platform Admin | Moderate cross-club content | The community stays safe and legal | P0 |
| CC-05 | Platform Admin | Define community-wide categories | Content is organized | P0 |
| CC-06 | Club Admin | Opt their club in/out of community features | They control member exposure | P1 |
### 3.3 Data Model (Multi-Tenant Considerations)
The cross-club forum breaks the fundamental `tenant_id` isolation pattern. Every other entity in CannaManage is scoped to a single club. A cross-club entity needs either:
**Option A: Separate schema (no tenant_id)**
```sql
-- community_topics, community_replies — NOT extending AbstractTenantEntity
-- Use a separate 'platform_user_id' that maps to the user across clubs
```
**Option B: Platform-level tenant (tenant_id = NULL or platform UUID)**
```sql
-- A special "platform" tenant that all users can access
-- More complex query logic: WHERE tenant_id = :current OR tenant_id = :platform
```
**Recommendation:** Option A — separate schema. The cross-club forum is architecturally a different bounded context. Mixing it with tenant-scoped entities creates query complexity and potential data leakage bugs.
### 3.4 Legal & Privacy Analysis (KCanG Implications)
| Concern | Analysis | Risk Level |
|---------|----------|-----------|
| **KCanG §4 — Advertising prohibition** | CSCs may not advertise cannabis or their club publicly. A cross-club forum where clubs share their strains could be interpreted as advertising. | 🟡 Medium |
| **KCanG §18 — Data handling** | Member data must be handled with care. Cross-club exposure of member names (even display names) to non-club-members may violate the principle of data minimization. | 🟡 Medium |
| **DSGVO Art. 5(1)(c) — Data minimization** | Exposing member identity across clubs goes beyond what's necessary for club management. Must offer genuine anonymity option. | 🔴 High |
| **KCanG §5 — Membership restriction** | Members can only belong to one CSC. If the forum reveals multi-club participation, it could expose legal violations. | 🟡 Medium |
| **Platform liability** | If the platform hosts inter-club communication, it becomes a publisher/moderator of cannabis-related content. Illegal content (sales, purchases) is a risk. | 🔴 High |
| **Age verification** | KCanG requires age verification for membership. A community forum accessible to "all members" must ensure all participants are verified. | 🟢 Low (all members already verified) |
**Legal recommendation:** If built, the cross-club forum MUST:
1. Default to anonymous posting (no club name, no real name)
2. Prohibit any form of transaction/trade discussion
3. Include clear terms of use that forbid advertising
4. Have active platform-level moderation
5. Allow clubs to opt-out entirely
6. Allow individual members to opt-out
### 3.5 Moderation at Platform Level
| Challenge | Solution |
|-----------|----------|
| Scale | Platform admin team or community moderators (elevated non-admin users) |
| Illegal content | Automated keyword filtering + user reports + manual review |
| Cross-club disputes | Platform terms of service, not individual club rules |
| Reporting to authorities | If illegal activity is detected, platform has Mitwirkungspflicht |
| Content liability | Clear Terms of Service, notice-and-takedown procedure |
### 3.6 Recommendation: Defer to Sprint 8+
**Reasons to defer:**
1. **Legal complexity** — needs actual legal review (not just developer analysis)
2. **Architectural break** — violates the clean tenant isolation model; needs careful design
3. **Moderation overhead** — requires platform-level moderation tooling that doesn't exist yet
4. **Low priority** — club-internal communication is far more valuable than inter-club
5. **Competitive non-urgency** — no competitor has this either; no market pressure
6. **MVP path** — clubs can still use Telegram for inter-club chat; CannaManage solves the intra-club problem first
**When to revisit:** After 50+ clubs are on the platform and actively request it. Validate demand before building.
### 3.7 Plan Tier Mapping (if eventually built)
| Tier | Community Forum |
|------|----------------|
| Starter | ❌ Not included |
| Pro | ✅ Read-only access |
| Enterprise | ✅ Full access (post + moderate) |
---
## 4. Club Notifications to Members (Benachrichtigungen)
### 4.1 Existing Infrastructure (What We Already Have)
Sprint 6 delivered a solid notification foundation:
| Component | Status | Details |
|-----------|--------|---------|
| `notifications` table | ✅ Built | UUID PK, user_id, type, title, message, link, read flag, tenant_id |
| `Notification` entity | ✅ Built | JPA entity extending `AbstractTenantEntity` |
| `NotificationType` enum | ✅ Built | 4 system types: `QUOTA_WARNING`, `BATCH_RECALLED`, `DISTRIBUTION_RECORDED`, `SUBSCRIPTION_EXPIRING` |
| REST API (`/notifications`) | ✅ Built | GET list, PUT mark-read, PUT mark-all-read |
| Frontend service | ✅ Built | `getNotifications()`, `markNotificationAsRead()`, `markAllNotificationsAsRead()` |
| WebSocket (SockJS/STOMP) | ✅ Built | Real-time push to connected clients (Nginx configured for `/ws/` upgrade) |
| PWA + Service Worker | ✅ Built | Manifest, offline page, install prompt (Sprint 6 Phase 6) |
| Bell icon in nav | ✅ Built | Unread count badge in dashboard header |
**What works today:** System-generated notifications (triggered by backend events) get stored in the DB, pushed via WebSocket to the frontend, and shown in the bell dropdown. Members can mark them as read.
### 4.2 Gap Analysis (What's Missing)
| Gap | Description | Effort |
|-----|------------|--------|
| **Admin compose UI** | No interface for admins to write and send custom notifications | Medium |
| **Targeting** | Current notifications are individual (`user_id`). No way to send to "all members" or "members in group X" | Medium |
| **Custom notification types** | Only 4 system types. Need `ADMIN_MESSAGE`, `INFO_BOARD_POST`, `FORUM_REPLY` | Low |
| **Email delivery channel** | Notifications are in-app only. No email fallback for offline users | Medium |
| **Push notifications (Web Push API)** | PWA exists but Web Push subscription/delivery not implemented | Medium |
| **Scheduling** | Can't schedule a notification for future delivery | Low |
| **Templates** | No template system for recurring notifications | Low |
| **Read receipts** | Admin can't see who read the notification | Low |
| **Member groups/segments** | No concept of member groups for targeted sending | Medium |
### 4.3 User Stories
| # | As a... | I want to... | So that... | Priority |
|---|---------|-------------|-----------|----------|
| NT-01 | Club Admin | Compose a custom notification with title + message | I can reach members directly | P0 |
| NT-02 | Club Admin | Send to all members at once | I don't have to select each one individually | P0 |
| NT-03 | Club Admin | Send to specific members (multi-select) | I can target a subset | P1 |
| NT-04 | Club Admin | Preview before sending | I catch mistakes before broadcasting | P0 |
| NT-05 | Club Admin | See delivery stats (sent/read counts) | I know if members are getting the message | P1 |
| NT-06 | Member | Receive notifications in-app (real-time) | I see messages immediately when online | P0 |
| NT-07 | Member | Receive email for important notifications (opt-in) | I don't miss things when offline | P1 |
| NT-08 | Member | Control which notification types I receive | I'm not overwhelmed | P2 |
| NT-09 | Club Admin | Schedule a notification for later | I can prepare communications in advance | P2 |
### 4.4 Admin UI for Composing Notifications
**Page: Dashboard → Notifications → Compose**
```
┌─────────────────────────────────────────────────┐
│ 📣 Neue Benachrichtigung │
├─────────────────────────────────────────────────┤
│ │
│ Empfänger: [● Alle Mitglieder ▼] │
│ ○ Ausgewählte Mitglieder │
│ [Search + multi-select chips] │
│ │
│ Titel: [____________________________] │
│ │
│ Nachricht: │
│ ┌─────────────────────────────────────────┐ │
│ │ (Markdown editor with preview toggle) │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Link (optional): [________________________] │
│ │
│ □ Auch per E-Mail senden │
│ │
│ [Vorschau] [Senden] │
│ │
└─────────────────────────────────────────────────┘
```
**Notification History page:**
- Table of sent notifications with: title, recipients (count), sent date, read rate
- Click to see per-recipient read status
- Resend option for unread recipients
### 4.5 Delivery Channels
| Channel | Implementation | Priority |
|---------|---------------|----------|
| **In-app (WebSocket)** | Already works. Extend to handle `ADMIN_MESSAGE` type. | P0 (exists) |
| **In-app (polling fallback)** | Already works via REST GET `/notifications`. | P0 (exists) |
| **Email** | Use existing `EmailService`. New template: `notification-email.txt`. Opt-in per member. | P1 |
| **Web Push** | Register subscription via Push API. Backend stores endpoint + keys. Send via `web-push` library. | P2 (Sprint 8) |
### 4.6 Targeting (All, Groups, Individuals)
**Phase 1 (Sprint 7):**
- Send to ALL members (broadcast)
- Send to selected individuals (multi-select picker)
**Phase 2 (Sprint 8+):**
- Member groups/segments (e.g., "Board Members", "Active Growers", "New Members < 30 days")
- Tag-based targeting
- Dynamic segments (members who haven't visited in 30 days)
### 4.7 Data Model Changes
```sql
-- Extend NotificationType enum (code change only, no migration needed)
-- Add: ADMIN_MESSAGE, INFO_BOARD_POST, FORUM_REPLY, FORUM_MENTION
-- V13: Notification send log (for admin-composed notifications)
CREATE TABLE notification_sends (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
link VARCHAR(500),
author_id UUID NOT NULL,
target_type VARCHAR(20) NOT NULL, -- 'ALL', 'SELECTED'
target_count INTEGER NOT NULL,
read_count INTEGER NOT NULL DEFAULT 0,
sent_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
tenant_id UUID NOT NULL
);
CREATE TABLE notification_send_recipients (
send_id UUID NOT NULL REFERENCES notification_sends(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
notification_id UUID REFERENCES notifications(id), -- links to the actual notification created
PRIMARY KEY (send_id, user_id)
);
CREATE INDEX idx_notification_sends_tenant ON notification_sends(tenant_id, sent_at DESC);
```
---
## 5. Prioritization & Sprint Plan
### 5.1 Feature Priority Matrix
| Feature | User Value | Competitive Edge | Build Effort | Risk | Sprint 7? |
|---------|-----------|-----------------|-------------|------|-----------|
| Club Notifications (admin compose) | 🟢 High | 🟢 Medium | 🟢 Low (builds on existing) | 🟢 Low | ✅ Yes |
| Club Info Board | 🟢 High | 🟢 High (no competitor has it) | 🟡 Medium | 🟢 Low | ✅ Yes |
| Club-Internal Forum | 🟢 High | 🟢 High (no competitor has it) | 🟡 Medium-High | 🟡 Medium (moderation) | ✅ Yes (MVP) |
| Cross-Club Forum | 🟡 Medium | 🟡 Medium | 🔴 High | 🔴 High (legal, arch) | ❌ Defer |
### 5.2 Recommended Sprint 7 Scope
```
Phase 1: Admin Notifications (builds on existing infra)
Phase 2: Info Board (Schwarzes Brett)
Phase 3: Forum MVP (categories, topics, replies, basic moderation)
Phase 4: Integration (notifications ↔ info board ↔ forum)
Phase 5: Polish & testing
```
#### Phase 1 — Admin Notifications
| Step | Description |
|------|-------------|
| 1.1 | Extend `NotificationType` enum: `ADMIN_MESSAGE`, `INFO_BOARD_POST`, `FORUM_REPLY` |
| 1.2 | Add `notification_sends` table (Flyway V13) |
| 1.3 | Backend: `NotificationComposeService` — create notification for multiple recipients |
| 1.4 | Backend: `POST /api/v1/notifications/compose` endpoint |
| 1.5 | Frontend: Compose notification page (admin area) |
| 1.6 | Frontend: Notification history page with read stats |
| 1.7 | Email delivery: notification email template + opt-in flag on member |
| 1.8 | New `StaffPermission`: `SEND_NOTIFICATIONS` |
#### Phase 2 — Info Board
| Step | Description |
|------|-------------|
| 2.1 | `info_board_categories` + `info_board_posts` + `info_board_attachments` tables (Flyway V14) |
| 2.2 | Backend: `InfoBoardService` — CRUD for categories and posts |
| 2.3 | Backend: REST endpoints `POST/GET/PUT /api/v1/info-board/posts`, `GET /api/v1/info-board/categories` |
| 2.4 | Backend: File upload endpoint for attachments |
| 2.5 | Frontend (admin): Info Board management page (create, edit, pin, archive) |
| 2.6 | Frontend (portal): Announcements section on member dashboard + dedicated page |
| 2.7 | Integration: New post → auto-notify all members (uses Phase 1 infra) |
| 2.8 | New `StaffPermission`: `MANAGE_INFO_BOARD` |
#### Phase 3 — Forum MVP
| Step | Description |
|------|-------------|
| 3.1 | Forum tables (Flyway V15): categories, topics, replies, reports, reactions |
| 3.2 | Backend: `ForumService` — topics CRUD, replies CRUD, moderation actions |
| 3.3 | Backend: REST endpoints for forum (topics, replies, categories, reports) |
| 3.4 | Frontend: Forum page (category list → topic list → topic detail with replies) |
| 3.5 | Frontend: New topic / reply editors (markdown) |
| 3.6 | Frontend: Moderation panel (report queue, lock/delete actions) |
| 3.7 | Integration: Reply notification → auto-notify topic author |
| 3.8 | New `StaffPermission`: `MODERATE_FORUM` |
#### Phase 4 — Integration & Polish
| Step | Description |
|------|-------------|
| 4.1 | WebSocket events for new info board posts and forum replies |
| 4.2 | Audit log events for info board and forum actions |
| 4.3 | Portal navigation update (add Forum, Announcements) |
| 4.4 | Dashboard widget: "Latest Announcements" + "Recent Forum Activity" |
| 4.5 | Email notifications for forum replies (opt-in) |
#### Phase 5 — Testing
| Step | Description |
|------|-------------|
| 5.1 | Unit tests for all new services |
| 5.2 | Integration tests for forum + info board + notifications |
| 5.3 | E2E tests (Playwright): post announcement, create topic, reply |
| 5.4 | Tenant isolation verification (no cross-club data leaks) |
| 5.5 | Permission checks (unauthorized staff can't post/moderate) |
### 5.3 Deferred to Sprint 8+
| Feature | Reason | When |
|---------|--------|------|
| Cross-Club Community Forum | Legal review needed, architecture break | Sprint 9+ (if validated) |
| Web Push Notifications | PWA push subscription, VAPID keys | Sprint 8 |
| Member groups / segments | Low urgency, "all members" is sufficient for MVP | Sprint 8 |
| Scheduled notifications | Nice-to-have, not blocking | Sprint 8 |
| Forum image uploads | Moderation complexity, storage cost | Sprint 8 |
| Forum search (full-text) | PostgreSQL FTS setup, medium effort | Sprint 8 |
| Notification preferences (per-type opt-in/out) | UX complexity | Sprint 8 |
| Rich text editor (WYSIWYG vs markdown) | Markdown is sufficient for MVP | Sprint 9 |
### 5.4 Dependencies Between Features
```mermaid
graph TD
A[Phase 1: Admin Notifications] --> B[Phase 2: Info Board]
A --> C[Phase 3: Forum MVP]
B --> D[Phase 4: Integration]
C --> D
D --> E[Phase 5: Testing]
subgraph Existing Infrastructure
N[Notification entity + table]
W[WebSocket STOMP]
P[StaffPermission system]
AU[AuditService]
end
N --> A
W --> A
P --> A
P --> B
P --> C
AU --> D
```
**Critical path:** Phase 1 must come first because Phases 2 and 3 depend on it for their notification integration.
---
## 6. Technical Architecture
### 6.1 New Entities & Migrations
| Migration | Tables | Entity Classes |
|-----------|--------|---------------|
| V11 | `info_board_categories`, `info_board_posts`, `info_board_attachments` | `InfoBoardCategory`, `InfoBoardPost`, `InfoBoardAttachment` |
| V12 | `forum_categories`, `forum_topics`, `forum_replies`, `forum_reports`, `forum_reactions` | `ForumCategory`, `ForumTopic`, `ForumReply`, `ForumReport`, `ForumReaction` |
| V13 | `notification_sends`, `notification_send_recipients` | `NotificationSend`, `NotificationSendRecipient` |
**All new entities extend `AbstractTenantEntity`** — tenant isolation by default.
### 6.2 API Endpoints
```
# Admin Notifications
POST /api/v1/notifications/compose — Send notification to targets
GET /api/v1/notifications/sends — List sent notifications (admin)
GET /api/v1/notifications/sends/{id} — Get send details + per-recipient status
# Info Board
GET /api/v1/info-board/categories — List categories
POST /api/v1/info-board/categories — Create category (admin)
PUT /api/v1/info-board/categories/{id} — Update category (admin)
GET /api/v1/info-board/posts — List posts (paginated, filterable)
POST /api/v1/info-board/posts — Create post (staff with permission)
PUT /api/v1/info-board/posts/{id} — Update post
PUT /api/v1/info-board/posts/{id}/pin — Toggle pin
PUT /api/v1/info-board/posts/{id}/archive — Archive post
POST /api/v1/info-board/posts/{id}/attachments — Upload attachment
GET /api/v1/info-board/posts/{id}/attachments/{aid} — Download attachment
# Forum
GET /api/v1/forum/categories — List forum categories
POST /api/v1/forum/categories — Create category (admin)
GET /api/v1/forum/topics — List topics (paginated, by category)
POST /api/v1/forum/topics — Create topic (any member)
GET /api/v1/forum/topics/{id} — Get topic with replies (paginated)
PUT /api/v1/forum/topics/{id} — Edit topic (author, within 30 min)
PUT /api/v1/forum/topics/{id}/pin — Pin topic (moderator)
PUT /api/v1/forum/topics/{id}/lock — Lock topic (moderator)
DELETE /api/v1/forum/topics/{id} — Delete topic (moderator)
POST /api/v1/forum/topics/{id}/replies — Post reply (any member)
PUT /api/v1/forum/replies/{id} — Edit reply (author, within 30 min)
DELETE /api/v1/forum/replies/{id} — Delete reply (moderator)
POST /api/v1/forum/reports — Report a post/reply
GET /api/v1/forum/reports — List reports (moderator)
PUT /api/v1/forum/reports/{id}/resolve — Resolve report (moderator)
POST /api/v1/forum/reactions — Add reaction
DELETE /api/v1/forum/reactions/{id} — Remove reaction
```
### 6.3 Frontend Pages/Components
**Admin Dashboard additions:**
- `/settings/info-board` — Info Board management (categories + post editor)
- `/settings/notifications/compose` — Compose notification
- `/settings/notifications/history` — Sent notifications log
- `/forum` — Forum with admin moderation controls
**Portal additions:**
- `/portal/announcements` — All info board posts
- `/portal/forum` — Forum access for members
- Portal dashboard: "Announcements" widget + "Forum Activity" widget
**Shared components:**
- `MarkdownEditor` — textarea with preview toggle (reusable for info board + forum)
- `CategoryBadge` — colored badge component
- `ReactionBar` — reaction emoji buttons with counts
- `ModerationActions` — dropdown with delete/lock/pin (shown to staff)
### 6.4 WebSocket Integration
Extend the existing STOMP broker with new message types:
```
/topic/club/{tenantId}/notifications — existing (extend with new types)
/topic/club/{tenantId}/info-board — new post published
/topic/club/{tenantId}/forum — new topic or reply in subscribed topic
```
**Frontend subscription (React hook):**
```typescript
// useForumSubscription(topicId) → real-time new replies
// useInfoBoardSubscription() → real-time new posts
// useNotificationSubscription() → existing, extended with ADMIN_MESSAGE
```
### 6.5 Search & Performance Considerations
| Concern | Solution |
|---------|----------|
| Forum topic listing performance | Denormalized `reply_count` + `last_reply_at` on topic table. Paginate at 20 per page. |
| Info board post search | PostgreSQL `ILIKE` on title + body for MVP. Full-text search (tsvector) in Sprint 8 if needed. |
| Notification fan-out (send to 500 members) | Batch INSERT using `JdbcTemplate.batchUpdate()`. Don't create 500 individual transactions. |
| WebSocket scaling | Single server is fine for MVP (< 1000 concurrent connections). Redis pub/sub for horizontal scaling if needed later. |
| Attachment storage | Local filesystem for MVP (`/data/attachments/{tenant_id}/{post_id}/`). S3-compatible object storage for production scale. |
| Forum reaction counting | `COUNT(*)` with GROUP BY on reactions table. Cache in application if >1000 reactions per post (unlikely in Sprint 7). |
---
## 7. New StaffPermissions Summary
```java
public enum StaffPermission {
RECORD_DISTRIBUTION,
VIEW_MEMBER_LIST,
VIEW_MEMBER_QUOTA,
ADD_MEMBER,
VIEW_STOCK,
RECORD_STOCK_IN,
VIEW_COMPLIANCE_REPORT,
MANAGE_GROW_CALENDAR,
// Sprint 7 additions:
SEND_NOTIFICATIONS, // Compose + send admin notifications
MANAGE_INFO_BOARD, // Create/edit/pin/archive info board posts
MODERATE_FORUM // Delete posts, lock topics, resolve reports
}
```
---
## 8. GDPR/KCanG Compliance Checklist
| Requirement | How We Address It |
|-------------|------------------|
| DSGVO Art. 5 — Data minimization | Forum posts only show display name (never real name or email). No data shared cross-club. |
| DSGVO Art. 17 — Right to erasure | Member deletion cascades to their forum posts (anonymized, not removed: "Ehemaliges Mitglied") |
| DSGVO Art. 15 — Data export | Export includes forum posts and info board interactions in data package |
| KCanG §4 — No advertising | Forum rules explicitly prohibit strain advertising. Moderation enforces this. |
| KCanG §18 — Data handling | All communication data scoped to tenant_id. No cross-club exposure. Club admin controls who can participate. |
| Audit compliance | Info board posts and moderation actions logged in immutable audit trail |
---
## 9. Risk Assessment
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| Moderation burden overwhelms club admins | Medium | Medium | Start with simple report queue. Auto-lock inactive topics. Word filter optional. |
| Low member engagement with forum | Medium | Low | Info Board is one-way (guaranteed content). Forum is additive. Don't over-invest. |
| Markdown too technical for non-technical admins | Low | Medium | Provide a toolbar (bold/italic/link buttons) over the textarea. Add preview. |
| Attachment storage fills disk | Low | Medium | Set max file size (10 MB). Set per-club storage quota (100 MB Starter, 1 GB Pro). |
| Notification spam (admin sends too often) | Low | Medium | Show "last sent" timestamp. Optional daily digest mode in Sprint 8. |
| Tenant isolation bug (cross-club data leak) | Low | 🔴 Critical | Integration test specifically for tenant isolation on all new entities. AbstractTenantEntity pattern prevents most issues. |
---
## 10. Success Metrics
| Metric | Target (3 months post-launch) | How to Measure |
|--------|------------------------------|---------------|
| Info Board posts created | 5+ per club per month | Count `info_board_posts` per tenant per month |
| Forum topics created | 10+ per club per month | Count `forum_topics` per tenant per month |
| Admin notifications sent | 3+ per club per month | Count `notification_sends` per tenant per month |
| Member forum participation rate | >30% of members posted at least once | Distinct `author_id` / total members |
| Notification read rate | >70% within 48 hours | `read_count / target_count` on notification_sends |
| Moderation queue response time | <24 hours | Time between report creation and resolution |