streamby-core 0.28.5 → 0.28.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,83 +43,83 @@ const getConnection = (id) => {
43
43
  exports.getConnection = getConnection;
44
44
  const ensureTablesExist = async (pool) => {
45
45
  try {
46
- await pool.query(`
47
- CREATE SCHEMA IF NOT EXISTS streamby;
48
- CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
46
+ await pool.query(`
47
+ CREATE SCHEMA IF NOT EXISTS streamby;
48
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
49
49
  `);
50
50
  // Migrate id column from UUID to TEXT if it was created with the old schema
51
- await pool.query(`
52
- DO $$ BEGIN
53
- ALTER TABLE streamby.projects ALTER COLUMN id TYPE TEXT USING id::text;
54
- ALTER TABLE streamby.projects ALTER COLUMN id SET DEFAULT uuid_generate_v4()::text;
55
- EXCEPTION WHEN OTHERS THEN NULL; END $$;
51
+ await pool.query(`
52
+ DO $$ BEGIN
53
+ ALTER TABLE streamby.projects ALTER COLUMN id TYPE TEXT USING id::text;
54
+ ALTER TABLE streamby.projects ALTER COLUMN id SET DEFAULT uuid_generate_v4()::text;
55
+ EXCEPTION WHEN OTHERS THEN NULL; END $$;
56
56
  `);
57
- await pool.query(`
58
- CREATE TABLE IF NOT EXISTS streamby.projects (
59
- id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
60
- name VARCHAR(255) NOT NULL,
61
- description TEXT,
62
- image TEXT,
63
- "allowedOrigin" JSONB DEFAULT '[]',
64
- exports JSONB DEFAULT '[]',
65
- credentials JSONB DEFAULT '[]',
66
- "apiConnections" JSONB DEFAULT '[]',
67
- "dbConnections" JSONB DEFAULT '[]',
68
- "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql',
69
- "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
70
- "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
71
- );
57
+ await pool.query(`
58
+ CREATE TABLE IF NOT EXISTS streamby.projects (
59
+ id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
60
+ name VARCHAR(255) NOT NULL,
61
+ description TEXT,
62
+ image TEXT,
63
+ "allowedOrigin" JSONB DEFAULT '[]',
64
+ exports JSONB DEFAULT '[]',
65
+ credentials JSONB DEFAULT '[]',
66
+ "apiConnections" JSONB DEFAULT '[]',
67
+ "dbConnections" JSONB DEFAULT '[]',
68
+ "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql',
69
+ "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
70
+ "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
71
+ );
72
72
  `);
73
73
  console.log('✅ "projects" table ensured to exist.');
74
74
  // Add JSONB columns to existing tables that predate this schema version
75
- await pool.query(`
76
- ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "allowedOrigin" JSONB DEFAULT '[]';
77
- ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS exports JSONB DEFAULT '[]';
78
- ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS credentials JSONB DEFAULT '[]';
79
- ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "apiConnections" JSONB DEFAULT '[]';
80
- ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbConnections" JSONB DEFAULT '[]';
75
+ await pool.query(`
76
+ ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "allowedOrigin" JSONB DEFAULT '[]';
77
+ ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS exports JSONB DEFAULT '[]';
78
+ ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS credentials JSONB DEFAULT '[]';
79
+ ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "apiConnections" JSONB DEFAULT '[]';
80
+ ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbConnections" JSONB DEFAULT '[]';
81
81
  `);
82
82
  // Ensure dbType exists with a non-null default
83
- await pool.query(`
84
- ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql';
83
+ await pool.query(`
84
+ ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql';
85
85
  `);
86
- await pool.query(`
87
- CREATE TABLE IF NOT EXISTS streamby.project_members (
88
- id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
89
- "projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
90
- "userId" VARCHAR(255) NOT NULL,
91
- role VARCHAR(255) NOT NULL DEFAULT 'member',
92
- status VARCHAR(50) NOT NULL DEFAULT 'active',
93
- archived BOOLEAN NOT NULL DEFAULT FALSE,
94
- "archivedBy" VARCHAR(255),
95
- "archivedAt" TIMESTAMP WITH TIME ZONE,
96
- "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
97
- "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
98
- UNIQUE ("projectId", "userId")
99
- );
86
+ await pool.query(`
87
+ CREATE TABLE IF NOT EXISTS streamby.project_members (
88
+ id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
89
+ "projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
90
+ "userId" VARCHAR(255) NOT NULL,
91
+ role VARCHAR(255) NOT NULL DEFAULT 'member',
92
+ status VARCHAR(50) NOT NULL DEFAULT 'active',
93
+ archived BOOLEAN NOT NULL DEFAULT FALSE,
94
+ "archivedBy" VARCHAR(255),
95
+ "archivedAt" TIMESTAMP WITH TIME ZONE,
96
+ "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
97
+ "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
98
+ UNIQUE ("projectId", "userId")
99
+ );
100
100
  `);
101
- await pool.query(`
102
- ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
101
+ await pool.query(`
102
+ ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
103
103
  `);
104
104
  console.log('✅ "project_members" table ensured to exist.');
105
105
  // Migrate projectId FK column from UUID to TEXT if table predates this schema version
106
- await pool.query(`
107
- DO $$ BEGIN
108
- ALTER TABLE streamby.project_members DROP CONSTRAINT IF EXISTS project_members_projectId_fkey;
109
- ALTER TABLE streamby.project_members ALTER COLUMN "projectId" TYPE TEXT;
110
- ALTER TABLE streamby.project_members ADD CONSTRAINT project_members_projectid_fkey
111
- FOREIGN KEY ("projectId") REFERENCES streamby.projects(id) ON DELETE CASCADE;
112
- EXCEPTION WHEN OTHERS THEN NULL; END $$;
106
+ await pool.query(`
107
+ DO $$ BEGIN
108
+ ALTER TABLE streamby.project_members DROP CONSTRAINT IF EXISTS project_members_projectId_fkey;
109
+ ALTER TABLE streamby.project_members ALTER COLUMN "projectId" TYPE TEXT;
110
+ ALTER TABLE streamby.project_members ADD CONSTRAINT project_members_projectid_fkey
111
+ FOREIGN KEY ("projectId") REFERENCES streamby.projects(id) ON DELETE CASCADE;
112
+ EXCEPTION WHEN OTHERS THEN NULL; END $$;
113
113
  `);
114
- await pool.query(`
115
- CREATE TABLE IF NOT EXISTS streamby.exports (
116
- id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
117
- "projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
118
- status VARCHAR(50) NOT NULL DEFAULT 'pending',
119
- "filePath" TEXT,
120
- "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
121
- "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
122
- );
114
+ await pool.query(`
115
+ CREATE TABLE IF NOT EXISTS streamby.exports (
116
+ id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
117
+ "projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
118
+ status VARCHAR(50) NOT NULL DEFAULT 'pending',
119
+ "filePath" TEXT,
120
+ "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
121
+ "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
122
+ );
123
123
  `);
124
124
  console.log('✅ "exports" table ensured to exist.');
125
125
  }
@@ -10,7 +10,7 @@ function serializeForPg(v) {
10
10
  }
11
11
  const createSQLExportTable = async (connection, projectId, exportName, nodeSchema) => {
12
12
  const exportId = (0, crypto_1.randomUUID)();
13
- await connection.query(`INSERT INTO "exports" (id, "projectId", node_schema, name, method, created_at, updated_at)
13
+ await connection.query(`INSERT INTO "exports" (id, "projectId", node_schema, name, method, created_at, updated_at)
14
14
  VALUES ($1, $2, $3, $4, 'GET', NOW(), NOW())`, [exportId, projectId, nodeSchema ? JSON.stringify(nodeSchema) : null, exportName]);
15
15
  return { exportId };
16
16
  };
@@ -24,6 +24,11 @@ const dbConnection_1 = require("./routes/dbConnection");
24
24
  const storageConnection_1 = require("./routes/storageConnection");
25
25
  const notification_1 = require("./routes/notification");
26
26
  const subscription_1 = require("./routes/subscription");
27
+ const media_1 = require("./routes/media");
28
+ const renderFarm_1 = require("./routes/renderFarm");
29
+ const review_1 = require("./routes/review");
30
+ const distribution_1 = require("./routes/distribution");
31
+ const ai_1 = require("./routes/ai");
27
32
  function createStreamByRouter(config) {
28
33
  const router = express_1.default.Router();
29
34
  if (config.encrypt)
@@ -39,6 +44,13 @@ function createStreamByRouter(config) {
39
44
  if (nosqlDbIds.length > 0) {
40
45
  (0, manager_1.registerModel)('notifications', nosqlDbIds, 'notifications', streambySchema);
41
46
  (0, manager_1.registerModel)('storage_files', nosqlDbIds, 'storage_files');
47
+ (0, manager_1.registerModel)('media_metadata', nosqlDbIds, 'media_metadata');
48
+ (0, manager_1.registerModel)('asset_versions', nosqlDbIds, 'asset_versions');
49
+ (0, manager_1.registerModel)('asset_dependency_graphs', nosqlDbIds, 'asset_dependency_graphs');
50
+ (0, manager_1.registerModel)('review_sessions', nosqlDbIds, 'review_sessions');
51
+ (0, manager_1.registerModel)('annotations', nosqlDbIds, 'annotations');
52
+ (0, manager_1.registerModel)('qc_reports', nosqlDbIds, 'qc_reports');
53
+ (0, manager_1.registerModel)('generative_jobs', nosqlDbIds, 'generative_jobs');
42
54
  }
43
55
  const mainDb = config.databases.find(db => db.main);
44
56
  if (mainDb) {
@@ -64,5 +76,10 @@ function createStreamByRouter(config) {
64
76
  router.use((0, storageConnection_1.storageConnectionRouter)(config));
65
77
  router.use((0, notification_1.notificationRouter)(config));
66
78
  router.use((0, subscription_1.subscriptionRouter)(config));
79
+ router.use((0, media_1.mediaRouter)(config));
80
+ router.use((0, renderFarm_1.renderFarmRouter)(config));
81
+ router.use((0, review_1.reviewRouter)(config));
82
+ router.use((0, distribution_1.distributionRouter)(config));
83
+ router.use((0, ai_1.aiRouter)(config));
67
84
  return router;
68
85
  }
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig } from '../../types';
3
+ export declare function aiRouter(_config: StreamByConfig): Router;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.aiRouter = aiRouter;
4
+ const express_1 = require("express");
5
+ const manager_1 = require("../../models/manager");
6
+ const auth_1 = require("../../utils/auth");
7
+ const jobQueue_1 = require("../../services/jobQueue");
8
+ const aiProcessor_1 = require("../../services/aiProcessor");
9
+ const AVAILABLE_NODE_TYPES = [
10
+ 'dataSourceNode', 'jsonInputNode', 'apiConnectionNode',
11
+ 'processNode', 'filterNode',
12
+ 'ingestNode', 'transcodeNode', 'captionNode', 'thumbnailNode',
13
+ 'renderJobNode', 'formatConvertNode', 'lodNode', 'assetDependencyNode',
14
+ 'reviewGateNode', 'annotationNode',
15
+ 'qcCheckNode', 'deliverableNode', 'distributionNode',
16
+ 'transcriptionNode', 'upscaleNode', 'proceduralAssetNode', 'pipelineSuggestNode',
17
+ ];
18
+ function aiRouter(_config) {
19
+ const router = (0, express_1.Router)();
20
+ const Project = (0, manager_1.getModel)('projects');
21
+ // POST /projects/:projectId/jobs/transcription
22
+ router.post('/projects/:projectId/jobs/transcription', async (req, res) => {
23
+ try {
24
+ const auth = req.auth;
25
+ const { projectId } = req.params;
26
+ const { fileId, model, sourceLanguage, outputFormats, provider, credentialId } = req.body;
27
+ if (!fileId)
28
+ return res.status(400).json({ message: 'fileId is required' });
29
+ const project = await Project.findOne({ _id: projectId });
30
+ if (!project)
31
+ return res.status(404).json({ message: 'Project not found' });
32
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
33
+ return res.status(403).json({ message: 'Unauthorized' });
34
+ const job = (0, jobQueue_1.createJob)('transcription', auth.userId, projectId, { fileId, model, sourceLanguage, outputFormats, provider, credentialId });
35
+ setImmediate(() => (0, aiProcessor_1.runTranscriptionJob)(job.jobId, fileId, projectId, { model, sourceLanguage, outputFormats, provider, credentialId }));
36
+ res.status(202).json({ jobId: job.jobId, status: job.status });
37
+ }
38
+ catch (err) {
39
+ res.status(500).json({ message: err.message });
40
+ }
41
+ });
42
+ // POST /projects/:projectId/jobs/upscale
43
+ router.post('/projects/:projectId/jobs/upscale', async (req, res) => {
44
+ try {
45
+ const auth = req.auth;
46
+ const { projectId } = req.params;
47
+ const { fileId, scale, model, mode, credentialId } = req.body;
48
+ if (!fileId)
49
+ return res.status(400).json({ message: 'fileId is required' });
50
+ const project = await Project.findOne({ _id: projectId });
51
+ if (!project)
52
+ return res.status(404).json({ message: 'Project not found' });
53
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
54
+ return res.status(403).json({ message: 'Unauthorized' });
55
+ const job = (0, jobQueue_1.createJob)('upscale', auth.userId, projectId, { fileId, scale, model, mode, credentialId });
56
+ setImmediate(() => (0, aiProcessor_1.runUpscaleJob)(job.jobId, fileId, projectId, { scale, model, mode, credentialId }));
57
+ res.status(202).json({ jobId: job.jobId, status: job.status });
58
+ }
59
+ catch (err) {
60
+ res.status(500).json({ message: err.message });
61
+ }
62
+ });
63
+ // POST /projects/:projectId/jobs/generate-asset
64
+ router.post('/projects/:projectId/jobs/generate-asset', async (req, res) => {
65
+ try {
66
+ const auth = req.auth;
67
+ const { projectId } = req.params;
68
+ const { assetType, provider, prompt, seed, credentialId } = req.body;
69
+ if (!prompt)
70
+ return res.status(400).json({ message: 'prompt is required' });
71
+ const project = await Project.findOne({ _id: projectId });
72
+ if (!project)
73
+ return res.status(404).json({ message: 'Project not found' });
74
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
75
+ return res.status(403).json({ message: 'Unauthorized' });
76
+ const job = (0, jobQueue_1.createJob)('generate-asset', auth.userId, projectId, { assetType, provider, prompt, seed, credentialId });
77
+ setImmediate(() => (0, aiProcessor_1.runGenerateAssetJob)(job.jobId, projectId, { assetType, provider, prompt, seed, credentialId }));
78
+ res.status(202).json({ jobId: job.jobId, status: job.status });
79
+ }
80
+ catch (err) {
81
+ res.status(500).json({ message: err.message });
82
+ }
83
+ });
84
+ // POST /projects/:projectId/pipeline-suggest
85
+ router.post('/projects/:projectId/pipeline-suggest', async (req, res) => {
86
+ try {
87
+ const auth = req.auth;
88
+ const { projectId } = req.params;
89
+ const { nodeSchema } = req.body;
90
+ if (!nodeSchema?.nodes)
91
+ return res.status(400).json({ message: 'nodeSchema is required' });
92
+ const project = await Project.findOne({ _id: projectId });
93
+ if (!project)
94
+ return res.status(404).json({ message: 'Project not found' });
95
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
96
+ return res.status(403).json({ message: 'Unauthorized' });
97
+ const suggestion = await (0, aiProcessor_1.buildPipelineSuggestion)(nodeSchema, AVAILABLE_NODE_TYPES);
98
+ res.json({ suggestion });
99
+ }
100
+ catch (err) {
101
+ res.status(500).json({ message: err.message });
102
+ }
103
+ });
104
+ return router;
105
+ }
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig } from '../../types';
3
+ export declare function distributionRouter(_config: StreamByConfig): Router;
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.distributionRouter = distributionRouter;
7
+ const express_1 = require("express");
8
+ const manager_1 = require("../../models/manager");
9
+ const auth_1 = require("../../utils/auth");
10
+ const wsHub_1 = require("../../services/wsHub");
11
+ const connectionManager_1 = require("../../adapters/database/connectionManager");
12
+ const crypto_1 = __importDefault(require("crypto"));
13
+ function getDb() {
14
+ const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
15
+ if (!ids.length)
16
+ return null;
17
+ return (0, connectionManager_1.getConnection)(ids[0]).client.db();
18
+ }
19
+ function broadcastDeliveryUpdate(userId, payload) {
20
+ (0, wsHub_1.emitToUser)(userId, { type: 'jobEvent', data: payload });
21
+ }
22
+ function distributionRouter(_config) {
23
+ const router = (0, express_1.Router)();
24
+ const Project = (0, manager_1.getModel)('projects');
25
+ const Export = (0, manager_1.getModel)('exports');
26
+ // ─── Distribution Connections ─────────────────────────────────────────────────
27
+ router.get('/projects/:projectId/distribution-connections', async (req, res) => {
28
+ try {
29
+ const auth = req.auth;
30
+ const { projectId } = req.params;
31
+ const project = await Project.findOne({ _id: projectId });
32
+ if (!project)
33
+ return res.status(404).json({ message: 'Project not found' });
34
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
35
+ return res.status(403).json({ message: 'Unauthorized' });
36
+ res.json({ connections: project.distributionConnections ?? [] });
37
+ }
38
+ catch (err) {
39
+ res.status(500).json({ message: err.message });
40
+ }
41
+ });
42
+ router.post('/projects/:projectId/distribution-connections', async (req, res) => {
43
+ try {
44
+ const auth = req.auth;
45
+ const { projectId } = req.params;
46
+ const { name, target, credentialId, config, description } = req.body;
47
+ if (!name || !target)
48
+ return res.status(400).json({ message: 'name and target are required' });
49
+ const project = await Project.findOne({ _id: projectId });
50
+ if (!project)
51
+ return res.status(404).json({ message: 'Project not found' });
52
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
53
+ return res.status(403).json({ message: 'Unauthorized' });
54
+ const connection = {
55
+ id: crypto_1.default.randomUUID(),
56
+ name,
57
+ target,
58
+ credentialId,
59
+ config: config ?? {},
60
+ description,
61
+ projectId,
62
+ createdAt: new Date(),
63
+ };
64
+ await Project.update({ _id: projectId }, { $push: { distributionConnections: connection } });
65
+ res.status(201).json({ connection });
66
+ }
67
+ catch (err) {
68
+ res.status(500).json({ message: err.message });
69
+ }
70
+ });
71
+ router.delete('/projects/:projectId/distribution-connections/:connectionId', async (req, res) => {
72
+ try {
73
+ const auth = req.auth;
74
+ const { projectId, connectionId } = req.params;
75
+ const project = await Project.findOne({ _id: projectId });
76
+ if (!project)
77
+ return res.status(404).json({ message: 'Project not found' });
78
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
79
+ return res.status(403).json({ message: 'Unauthorized' });
80
+ await Project.update({ _id: projectId }, { $pull: { distributionConnections: { id: connectionId } } });
81
+ res.json({ message: 'Connection deleted' });
82
+ }
83
+ catch (err) {
84
+ res.status(500).json({ message: err.message });
85
+ }
86
+ });
87
+ // ─── Publish Deliverable ──────────────────────────────────────────────────────
88
+ router.post('/projects/:projectId/deliverables/:exportId/publish', async (req, res) => {
89
+ try {
90
+ const auth = req.auth;
91
+ const { projectId, exportId } = req.params;
92
+ const { targets } = req.body;
93
+ if (!targets?.length)
94
+ return res.status(400).json({ message: 'targets array is required' });
95
+ const project = await Project.findOne({ _id: projectId });
96
+ if (!project)
97
+ return res.status(404).json({ message: 'Project not found' });
98
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
99
+ return res.status(403).json({ message: 'Unauthorized' });
100
+ const exportDoc = await Export.findOne({ id: exportId, projectId });
101
+ if (!exportDoc)
102
+ return res.status(404).json({ message: 'Deliverable not found' });
103
+ const connections = project.distributionConnections ?? [];
104
+ const deliveryTargets = targets.map(t => {
105
+ const conn = connections.find(c => c.id === t.connectionId);
106
+ return {
107
+ connectionId: t.connectionId,
108
+ target: conn?.target ?? 'customWebhook',
109
+ channel: t.channel,
110
+ status: 'pending',
111
+ };
112
+ });
113
+ const now = new Date();
114
+ await Export.update({ id: exportId, projectId }, {
115
+ $set: {
116
+ deliverableTargets: deliveryTargets,
117
+ publishedAt: now,
118
+ publishedBy: auth.userId,
119
+ updatedAt: now,
120
+ },
121
+ });
122
+ // Dispatch async — actual upload/API call happens in the host worker
123
+ setImmediate(() => {
124
+ for (const dt of deliveryTargets) {
125
+ broadcastDeliveryUpdate(auth.userId, {
126
+ jobId: crypto_1.default.randomUUID(),
127
+ jobType: 'distribute',
128
+ stage: 'queued',
129
+ progress: 5,
130
+ assetId: exportId,
131
+ message: `Queued publish to ${dt.target}`,
132
+ });
133
+ }
134
+ });
135
+ res.status(202).json({ deliverableTargets: deliveryTargets, publishedAt: now });
136
+ }
137
+ catch (err) {
138
+ res.status(500).json({ message: err.message });
139
+ }
140
+ });
141
+ // ─── QC Reports ───────────────────────────────────────────────────────────────
142
+ router.get('/projects/:projectId/assets/:assetId/qc-report', async (req, res) => {
143
+ try {
144
+ const auth = req.auth;
145
+ const { projectId, assetId } = req.params;
146
+ const project = await Project.findOne({ _id: projectId });
147
+ if (!project)
148
+ return res.status(404).json({ message: 'Project not found' });
149
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
150
+ return res.status(403).json({ message: 'Unauthorized' });
151
+ const db = getDb();
152
+ if (!db)
153
+ return res.status(500).json({ message: 'Database not available' });
154
+ const report = await db.collection('qc_reports').findOne({ assetId, projectId }, { sort: { generatedAt: -1 } });
155
+ res.json({ report: report ?? null });
156
+ }
157
+ catch (err) {
158
+ res.status(500).json({ message: err.message });
159
+ }
160
+ });
161
+ router.post('/projects/:projectId/assets/:assetId/qc-report', async (req, res) => {
162
+ try {
163
+ const auth = req.auth;
164
+ const { projectId, assetId } = req.params;
165
+ const { checks } = req.body;
166
+ if (!Array.isArray(checks))
167
+ return res.status(400).json({ message: 'checks array is required' });
168
+ const project = await Project.findOne({ _id: projectId });
169
+ if (!project)
170
+ return res.status(404).json({ message: 'Project not found' });
171
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
172
+ return res.status(403).json({ message: 'Unauthorized' });
173
+ const db = getDb();
174
+ if (!db)
175
+ return res.status(500).json({ message: 'Database not available' });
176
+ const overallPassed = checks.every((c) => c.passed === true);
177
+ const report = {
178
+ assetId,
179
+ projectId,
180
+ checks,
181
+ overallPassed,
182
+ generatedAt: new Date(),
183
+ };
184
+ await db.collection('qc_reports').insertOne({ ...report });
185
+ res.status(201).json({ report });
186
+ }
187
+ catch (err) {
188
+ res.status(500).json({ message: err.message });
189
+ }
190
+ });
191
+ return router;
192
+ }
@@ -0,0 +1,5 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig, StorageAdapter } from '../../types';
3
+ export declare function mediaRouter(config: StreamByConfig & {
4
+ adapter?: StorageAdapter;
5
+ }): Router;