Architecture

Content & Streaming Domain Architecture

Last updated: 2026-02-01 | Architecture

Content & Streaming Domain Architecture

Key Takeaways

  1. Three distinct services — Content (articles/resources), Media (video/photo/blogs), and Webinar (live events via Zoom) — with significant overlap in video handling via Mux.
  2. 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.
  3. 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.
  4. 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.
  5. 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

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

In-Flight State

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