- Add ffmpeg-based media asset generation (poster, thumbnail, clip) for previews and videos
- Add GET /media/thumbnails/:filename serving route
- Add filter=mine to feed endpoint for user's own published streams
- Feed response now includes posterUrl, thumbnailUrl, clipUrl
- Deleting an ENDED plan with preview preserves it as a Video record
- Add sourcePlanId to Video schema
- Prisma: Device model (Quest/Phone, online status, battery, storage,
game/streaming/cortex state), Video model with likes, VideoLike
- Signaling WebSocket for SDP/ICE relay and device presence
- Device routes: list, status, delete
- Content routes: video CRUD with range-support streaming
- SignalingManager service for device socket registry and heartbeat
- Prepare endpoint wraps each destination in try/catch; partial success
if at least one destination is ready (e.g., Twitch works when YouTube
is rate-limited)
- Echo sent Twitch messages back to app WebSocket (IRC doesn't echo
your own PRIVMSGs)
- Start YouTube and Twitch chat clients in parallel via Promise.allSettled
- Fix Twitch auth failure detection (Login unsuccessful + Login
authentication failed)
- Add Twitch IRC debug logging
- Add POST /providers/accounts/custom-rtmp endpoint for saved RTMP servers
- Encrypt rtmpUrl/streamKey in accessTokenEnc/refreshTokenEnc fields
- Decrypt and return rtmpUrl/streamKey in GET /providers/accounts for CUSTOM_RTMP
- Skip token revocation on DELETE for CUSTOM_RTMP accounts
- Decrypt CUSTOM_RTMP credentials into CUSTOM destinations on plan create/update
- Handle CUSTOM destinations in prepare lifecycle (already READY, skip provider auth)
- Add debug logging for plan operations and user upsert
- Add executionMode and gameId columns to StreamPlan schema
- Add migration for new columns
- Support executionMode/gameId in plan create and update endpoints
- Add version field to health check response from package.json
- Change LinkedAccount unique constraint to (userId, serviceId, accountId)
- Add linkedAccountId to StreamDestination for per-account targeting
- OAuth callbacks upsert by accountId so different accounts create new rows
- Delete endpoint changed to /providers/accounts/:id
- getDecryptedToken resolves tokens by linkedAccountId instead of serviceId
- /start transition wrapped in try-catch (enableAutoStart compatibility)
- /end always attempts YouTube complete transition regardless of plan status
- autoDetectEndedPlans loads tokens per-destination
When the Data Use Checkup hasn't granted numeric ID access, the SDK
returns oculusId (string username) instead. This makes nonce optional,
skips nonce verification for non-numeric userIds, and uses oculusId as
the metaId when numeric ID is unavailable.