T-HUB/apps/api/src/live/live.module.ts
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

58 lines
2.7 KiB
TypeScript

import { Module } from "@nestjs/common";
import { withTenant } from "../db/client.js";
import { LiveDestinationController } from "./live-destination.controller.js";
import { LiveDestinationRepository, LIVE_DESTINATION_DEPENDENCIES, type TenantTransaction } from "./live-destination.repository.js";
import { GoLiveService, GO_LIVE_DEPENDENCIES } from "./go-live.service.js";
import { StreamOutputController } from "./stream-output.controller.js";
import { createPublishingRegistry } from "@tactical-media-broadcast-hub/connector-runtime";
import { pool } from "../db/client.js";
/** Registers connector-level live source resolvers from the connector registry. */
function registerSourceResolvers(goLive: GoLiveService): void {
// Registry pattern: the provider set comes from the publishing registry —
// connector packages (twitch et al.) own provider behaviour, so every
// connector implementing resolveLiveSource registers under its own manifest
// id; generic-hls resolves through the built-in prober. No provider names
// branch here.
if (process.env.DATABASE_URL === undefined) return;
try {
const registry = createPublishingRegistry(pool);
for (const connector of registry.list()) {
const { id } = connector.manifest();
if (connector.resolveLiveSource === undefined) continue;
const resolveLiveSource = connector.resolveLiveSource.bind(connector);
goLive.registerSourceResolver(id, async (request) => resolveLiveSource(
{
id: `${id}:live-source`, workspaceId: "live-source", provider: id,
channelId: request.channelId ?? request.externalUrl ?? "",
credentialId: { kind: "credential-ref", id: `${id}:live-source` },
fields: {}, createdAt: new Date(),
},
request,
));
}
} catch { /* No connectors configured in this process: only generic-hls stays registered. */ }
}
@Module({
controllers: [LiveDestinationController, StreamOutputController],
providers: [
{ provide: "STREAM_OUTPUT_TRANSACTION", useValue: withTenant satisfies TenantTransaction },
{
provide: LiveDestinationRepository,
useFactory: (dependencies?: unknown) => new LiveDestinationRepository(dependencies as never),
inject: [{ token: LIVE_DESTINATION_DEPENDENCIES, optional: true }],
},
{
provide: GoLiveService,
useFactory: (destinations: LiveDestinationRepository, dependencies?: unknown) => {
const service = new GoLiveService(destinations, dependencies as never);
registerSourceResolvers(service);
return service;
},
inject: [LiveDestinationRepository, { token: GO_LIVE_DEPENDENCIES, optional: true }],
},
],
exports: [LiveDestinationRepository, GoLiveService],
})
export class LiveModule {}