Content & Streaming Domain Architecture
Content & Streaming Domain Architecture
Key Takeaways
- Three distinct services — Content (articles/resources), Media (video/photo/blogs), and Webinar (live events via Zoom) — with significant overlap in video handling via Mux.
- Mux is the video backbone — Used by both content and media services for video upload, transcoding, and playback. Content also handles Mux livestream webhooks. Media manages the full Mux asset lifecycle.
- Webinar uses Zoom, NOT Jitsi/100ms — Despite Jitsi and 100ms SDKs in the frontend, the webinar backend integrates exclusively with Zoom REST API + Google Calendar. Jitsi is a dormant Docker container; 100ms is frontend-only.
- Broadcast is confirmed inactive (H1 L2) — No broadcast service in production. peeq-mux-livestream (Gen 1) used Mux Spaces (now deprecated). Frontend broadcast code is dead.
- Content service runs Java 24 — Newer than all other Gen 2 services (Java 21), suggesting active development or experimentation. Uses vips-ffm for photo processing and Spring Content for filesystem abstraction.
Migration Decision Question
What is the video/content migration path, and is broadcast functionality needed?
Migration Verdict
Verdict: Consolidate Complexity: L Key Constraint: Mux integration spans content + media services; NFS/Spring Content filesystem storage is tightly coupled to GKE infrastructure Dependencies: Mux API keys, GCS bucket access, NFS provisioner, Zoom API credentials
Services Inventory
| Service | Gen | Deployed? | Stack | Purpose |
|---|---|---|---|---|
| content | 2 | Yes (4/4 tenants) | Java 24 / SB 3.5.4 | Articles, resources, SEU/PDU tracking, Mux webhooks |
| media | 2 | Yes (4/4 tenants) | Java 21 / SB 3.5.4 | Video/photo management, blogs, news, Mux assets |
| webinar | 2 | Yes (4/4 tenants) | Java 21 / SB 3.5.4 | Live webinars via Zoom, Google Calendar |
| peeq-mux-livestream | 1 | No | Node.js / Mux Spaces | Gen 1 livestream (Mux Spaces deprecated) |
| mux-sync | Utility | No (ETL) | Python / dlt | Mux data sync to Snowflake |
| peeq-jitsi-meet | 1 | No | Docker | Jitsi video container (inactive) |
Not in Production
| Service | Status | Notes |
|---|---|---|
| broadcast | Never deployed | H1 verified — no ArgoCD app in any tenant |
| peeq-mux-livestream | Retired | Mux Spaces API deprecated; replaced by content service Mux integration |
| peeq-jitsi-meet | Inactive | Docker container config only; webinar uses Zoom instead |
| conference | Never deployed | No ArgoCD app; frontend ConferenceGateway is dead code |
API Surface
Content Service (18 GraphQL + 10 REST)
GraphQL Mutations (7): createContent, updateContent,
deleteContent, rateContent, toggleContentLock, createCategory,
updateCategory GraphQL Queries (11): getContent,
getContents, getContentsByCategory, getCategories, getContentRatings,
searchContent, getContentStats, getSeuPduCredits, plus admin queries
REST (10): - POST /multipart — File upload
(articles, resources) - POST /mux/webhook — Mux video event
callbacks - GET /content/{id}/download — File download -
GET /content/{id}/thumbnail — Thumbnail generation - Photo
processing endpoints (resize, crop via vips-ffm) - SEU/PDU certificate
generation
Media Service (20+ GraphQL + 12 REST)
GraphQL Mutations (8): createMedia, updateMedia,
deleteMedia, createBlog, updateBlog, createNews, updateNews,
featureMedia GraphQL Queries (12+): getMedia,
getMediaPaged, getBlogs, getBlogsPaged, getNews, getNewsPaged,
getFeaturedMedia, search queries, admin queries REST
(12): - POST /multipart — Video/image upload -
POST /mux/upload — Direct Mux upload URL generation -
GET /mux/asset/{id} — Mux asset status -
POST /mux/webhook — Mux webhook handler -
GET /thumbnail/{id} — Video thumbnail from Mux - Blog/news
image upload endpoints - GCS signed URL generation
Webinar Service (40+ GraphQL + 2 REST)
GraphQL Mutations (15+): createWebinar,
updateWebinar, deleteWebinar, registerForWebinar, cancelRegistration,
startWebinar, endWebinar, createWebinarSeries, createRecurringWebinar,
linkZoomMeeting, syncZoomRegistrants, plus admin mutations
GraphQL Queries (25+): getWebinar, getWebinars,
getWebinarsPaged, getUpcomingWebinars, getWebinarRegistrations,
getWebinarAttendees, getWebinarRecordings, getZoomMeetingDetails,
getGoogleCalendarEvents, analytics queries, plus admin queries
REST (2): - POST /zoom/webhook — Zoom
event callbacks - GET /webinar/{id}/ical — iCalendar
export
Who Calls This Domain? Who Does It Call?
graph LR
subgraph "Content & Streaming Domain"
CON[Content]
MED[Media]
WEB[Webinar]
end
subgraph "Callers"
FE1[peeq-mono]
FE2[frontends]
end
subgraph "Called Services"
MUX[Mux API]
GCS[Google Cloud Storage]
ZOOM[Zoom REST API]
GCAL[Google Calendar API]
NFS[NFS Storage]
TAGS[Tags Service]
CEL[Celebrity Service]
INV[Inventory Service]
KC[Keycloak]
end
FE1 -->|GraphQL| CON & MED & WEB
FE2 -->|GraphQL| CON & MED & WEB
CON -->|REST| MUX
CON -->|Filesystem| NFS
CON -->|GraphQL| TAGS
CON -->|Admin API| KC
MED -->|REST| MUX
MED -->|REST| GCS
MED -->|GraphQL| TAGS
MED -->|Admin API| KC
WEB -->|REST| ZOOM
WEB -->|REST| GCAL
WEB -->|GraphQL| CEL & INV
WEB -->|Admin API| KC
CON -.->|RabbitMQ| MED
MED -.->|RabbitMQ| CON
WEB -.->|RabbitMQ| CON & MED
API Backward Compatibility Constraints
- GraphQL schemas for content, media, and webinar are called by both peeq-mono and frontends — any schema change breaks both
- Mux webhook URLs are registered in Mux dashboard — must update if service URLs change
- Zoom webhook URLs registered in Zoom Marketplace app — must update if webinar service URL changes
- File upload multipart endpoints have specific content-type expectations shared by both frontends
- NFS mount paths are hardcoded in Helm chart values — infrastructure coupling
Data Model
Content Schema (12 Flyway migrations)
erDiagram
contents {
uuid id PK
uuid creator_id FK "Keycloak UUID"
string title
text description
string content_type "article|resource|video"
string status "draft|published|archived"
string media_url
string thumbnail_url
string mux_asset_id
string mux_playback_id
int seu_credits
int pdu_credits
date created_on
date updated_on
boolean is_locked
}
categories {
uuid id PK
string name
string description
int display_order
}
content_categories {
uuid content_id FK
uuid category_id FK
}
content_ratings {
uuid id PK
uuid content_id FK
uuid user_id FK
int rating
date created_on
}
content_files {
uuid id PK
uuid content_id FK
string file_path "NFS path"
string file_type
long file_size
date uploaded_on
}
content_tags {
uuid content_id FK
uuid tag_id FK
}
seu_pdu_completions {
uuid id PK
uuid content_id FK
uuid user_id FK
date completed_on
string certificate_url
}
contents ||--o{ content_categories : categorized
contents ||--o{ content_ratings : rated
contents ||--o{ content_files : has
contents ||--o{ content_tags : tagged
contents ||--o{ seu_pdu_completions : tracks
categories ||--o{ content_categories : contains
Media Schema (26 Flyway migrations — 13 tables)
erDiagram
media {
uuid id PK
uuid creator_id FK "Keycloak UUID"
string title
text description
string media_type "video|image|audio"
string status
string mux_asset_id
string mux_playback_id
string mux_upload_id
string gcs_url
long duration_seconds
date created_on
boolean is_featured
}
media_thumbnails {
uuid id PK
uuid media_id FK
string url
string source "mux|custom"
}
blogs {
uuid id PK
uuid author_id FK
string title
text body
string slug UK
string status
string featured_image_url
date published_on
date created_on
}
news {
uuid id PK
uuid author_id FK
string title
text body
string slug UK
string featured_image_url
date published_on
}
media_tags {
uuid media_id FK
uuid tag_id FK
}
media ||--o{ media_thumbnails : has
media ||--o{ media_tags : tagged
Webinar Schema (24 Flyway migrations — 24 tables)
erDiagram
webinars {
uuid id PK
uuid host_id FK "Celebrity Keycloak UUID"
string title
text description
string status "scheduled|live|completed|cancelled"
string zoom_meeting_id
string zoom_join_url
string zoom_start_url
date start_time
date end_time
int max_attendees
string recurrence_type
uuid series_id FK
date created_on
}
webinar_registrations {
uuid id PK
uuid webinar_id FK
uuid user_id FK
string zoom_registrant_id
string status "registered|attended|cancelled"
date registered_on
}
webinar_series {
uuid id PK
string title
text description
uuid host_id FK
}
webinar_recordings {
uuid id PK
uuid webinar_id FK
string zoom_recording_id
string download_url
string playback_url
long duration_seconds
date recorded_on
}
webinar_calendar_events {
uuid id PK
uuid webinar_id FK
string google_event_id
string calendar_id
}
webinar_attendees {
uuid id PK
uuid webinar_id FK
uuid user_id FK
date joined_at
date left_at
int duration_minutes
}
webinars ||--o{ webinar_registrations : has
webinars ||--o{ webinar_recordings : has
webinars ||--o{ webinar_calendar_events : syncs
webinars ||--o{ webinar_attendees : tracks
webinar_series ||--o{ webinars : contains
Data Migration Specifics
- Schema transformation: Content and media schemas are additive (nullable new columns across migrations). Webinar has the most complex schema (24 tables, 24 migrations) with Zoom-specific fields.
- Foreign key dependencies: All creator/host/user IDs are Keycloak UUIDs. Content→Tags and Media→Tags use external tag service. Webinar→Celebrity for host profiles.
- Data volume: Unknown (no DB access). Media has 26 migrations suggesting heavy evolution. Webinar has 24 tables — most complex schema in this domain.
- Stateful data: Mux asset IDs (permanent — migration must preserve), Zoom meeting IDs (time-bound), NFS file paths (infrastructure-coupled).
- External ID coupling: Mux asset IDs, Mux playback IDs, Zoom meeting IDs, Google Calendar event IDs — all external references that must be preserved or re-mapped.
In-Flight State
- Mux uploads: Active video uploads have Mux upload IDs. Incomplete uploads would fail if service is interrupted.
- Mux webhooks: Video processing callbacks arrive asynchronously (minutes after upload). Webhook endpoint must remain available during migration.
- Zoom meetings: Scheduled webinars with registered attendees have active Zoom meeting IDs. Cannot delete/recreate without losing registrations.
- Google Calendar events: Synced calendar events reference webinar IDs. Re-sync needed if webinar IDs change.
- NFS files: Content files stored on NFS volume. Must migrate or maintain NFS mount during transition.
- RabbitMQ: Content/media/webinar publish events consumed by notification, SSE, and each other. Queue drain needed during migration.
External Integrations
| Integration | Service | Purpose | Migration Impact |
|---|---|---|---|
| Mux | Content + Media | Video upload, transcoding, playback, livestream | API key migration; asset IDs are permanent references |
| Google Cloud Storage | Media | Image/video blob storage | Bucket access; signed URL generation must continue |
| Zoom | Webinar | Meeting creation, registration, recordings | OAuth app migration; meeting IDs are time-bound |
| Google Calendar | Webinar | Event sync for webinar scheduling | Service account migration |
| NFS Provisioner | Content | Filesystem storage for articles/resources | Infrastructure coupling; consider S3/GCS migration |
| vips-ffm | Content | Photo processing (resize, crop, format) | Native library dependency; requires libvips on container |
| FFmpeg | Media | Video processing, thumbnail extraction | Native binary dependency; requires ffmpeg on container |
| Tags Service | Content + Media | Tag lookup for search/categorization | Internal dependency — migrates with tags domain |
| Snowflake | mux-sync (ETL) | Analytics data warehouse | dlt pipeline config |
Inter-Service Communication
Synchronous (REST/GraphQL)
| From | To | Protocol | Purpose |
|---|---|---|---|
| Content | Tags | GraphQL | Tag lookup for content categorization |
| Content | Keycloak | Admin API | Creator profile retrieval |
| Media | Tags | GraphQL | Tag lookup for media categorization |
| Media | Keycloak | Admin API | Creator profile retrieval |
| Media | GCS | REST | Signed URL generation, blob upload/download |
| Media | Mux | REST | Asset management, upload URLs, playback IDs |
| Webinar | Celebrity | GraphQL | Host profile lookup |
| Webinar | Inventory | GraphQL | Event listing integration |
| Webinar | Zoom | REST | Meeting CRUD, registration, recordings |
| Webinar | Google Calendar | REST | Event sync |
| Webinar | Keycloak | Admin API | User profile retrieval |
Asynchronous (RabbitMQ)
| Publisher | Message | Consumer | Exchange |
|---|---|---|---|
| Content | ContentCreated | Media?, Notifications? | content |
| Content | ContentUpdated | Media?, Notifications? | content |
| Content | ContentDeleted | Media? | content |
| Media | MediaCreated | Content?, Notifications? | media |
| Media | MediaUpdated | Notifications? | media |
| Webinar | WebinarCreated | Notifications, SSE | webinar |
| Webinar | WebinarStarted | Notifications, SSE | webinar |
| Webinar | WebinarEnded | Notifications | webinar |
| Webinar | RegistrationCreated | Notifications, Email? | webinar |
Note: Exact consumer mappings need validation —
message classes use core-lib MessageSender but consumer bindings are
configured at runtime via @MessageHandler annotations.
Exchange names match service names by convention.
Gen 1 → Gen 2 Comparison
| Aspect | Gen 1 | Gen 2 | Gap |
|---|---|---|---|
| Content | peeq-content (Java 11/SB 2.6) | content (Java 24/SB 3.5) | Gen 2 adds SEU/PDU tracking, categories, vips-ffm photo processing |
| Media | (part of peeq-content) | media (Java 21/SB 3.5) | Gen 2 splits media into dedicated service with blogs/news |
| Livestream | peeq-mux-livestream (Node.js) | content Mux webhooks | Gen 2 replaces standalone Node service with webhook handler in content |
| Webinar | (none — Jitsi was planned) | webinar (Java 21/SB 3.5) | Gen 2 uses Zoom API instead of self-hosted Jitsi |
| Video conferencing | peeq-jitsi-meet (Docker) | Not used | Jitsi abandoned in favor of Zoom |
| Broadcast | broadcast (never deployed) | None | Feature never reached production |
| GraphQL | SPQR (io.leangen) | Spring Native GraphQL | Standard migration |
| Persistence | javax.persistence | jakarta.persistence | Jakarta namespace |
Key insight: Gen 2 significantly expanded the content domain — splitting media out, adding webinar (new capability), upgrading to Zoom from planned Jitsi, and adding professional development (SEU/PDU) tracking. Content service running Java 24 suggests it’s the most actively developed service.
Broadcast Status (H1 Evidence)
Broadcast is confirmed inactive and should be retired.
Evidence chain: 1. ArgoCD: No broadcast app in any of 4 production tenants (Session 0, L2) 2. Helm charts: broadcast chart exists but is not referenced in any ArgoCD Application 3. Frontend: BroadcastGateway/BroadcastService exist in both peeq-mono and frontends — dead code 4. Gen 1: peeq-mux-livestream used Mux Spaces API, which Mux has deprecated 5. Gen 2: No broadcast service exists. Content service handles Mux video but not live broadcast.
Recommendation: Remove BroadcastGateway/BroadcastService from both frontends. Archive peeq-mux-livestream. No broadcast capability needs to be built for Gen 3 unless business requirements change.
Modernization Implications
Consolidation Opportunity: Content + Media
Content and media services have significant overlap: - Both integrate with Mux for video - Both integrate with Tags for categorization - Both use Keycloak for creator identity - Both handle file uploads (content via NFS, media via GCS)
Recommended: Consolidate into a single Content & Media Service in Gen 3. The content service’s SEU/PDU tracking and media service’s blog/news functionality are distinct enough to be separate modules within one service. Key benefit: single Mux integration point.
Webinar: Keep Separate
Webinar has distinct characteristics: - Different external integrations (Zoom, Google Calendar) - Complex scheduling/registration domain - 24-table schema (most complex in this domain) - Different lifecycle (scheduled events vs on-demand content)
Recommended: Keep webinar as a separate Gen 3 service. Consolidating it with content/media would create an over-sized service with too many responsibilities.
Infrastructure Migration: NFS → Object Storage
Content service uses NFS (Spring Content filesystem) for file storage. This creates tight coupling to GKE infrastructure: - NFS provisioner must run in cluster - Files are not replicated across zones - Scaling is limited by NFS throughput
Recommended: Migrate from NFS to GCS (already used by media service). This aligns storage patterns and improves resilience.
Video Platform Rationalization
Current state: Mux for VOD + livestream, Zoom for webinars, Phenix RTS and 100ms in frontend SDKs.
Recommended: Standardize on Mux for all video-on-demand and Zoom for all interactive sessions. Remove Phenix RTS and 100ms frontend SDKs unless there’s a product requirement for real-time sub-second streaming (needs business validation).
Jitsi Retirement
peeq-jitsi-meet is a Docker container that was never integrated with the Gen 2 webinar service. Webinar uses Zoom exclusively.
Recommended: Archive peeq-jitsi-meet. No Jitsi capability needed for Gen 3.
Dead Code Cleanup
Remove before migration: - BroadcastGateway / BroadcastService (both frontends) - ConferenceGateway / ConferenceService (both frontends) - StreamGateway (peeq-mono) - peeq-mux-livestream repo (archive) - peeq-jitsi-meet repo (archive)
Last updated: 2026-01-30 — Session 3 Review by: 2026-07-30 Staleness risk: Medium — Mux/Zoom API versions and content features evolve with development