streamby-core 0.28.5 → 0.29.0

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.
@@ -0,0 +1,158 @@
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.renderFarmRouter = renderFarmRouter;
7
+ const express_1 = require("express");
8
+ const manager_1 = require("../../models/manager");
9
+ const auth_1 = require("../../utils/auth");
10
+ const jobQueue_1 = require("../../services/jobQueue");
11
+ const vfxProcessor_1 = require("../../services/vfxProcessor");
12
+ const crypto_1 = __importDefault(require("crypto"));
13
+ function renderFarmRouter(_config) {
14
+ const router = (0, express_1.Router)();
15
+ const Project = (0, manager_1.getModel)('projects');
16
+ // ─── Render Farm Connections ─────────────────────────────────────────────────
17
+ router.get('/projects/:projectId/render-farm-connections', async (req, res) => {
18
+ try {
19
+ const auth = req.auth;
20
+ const { projectId } = req.params;
21
+ const project = await Project.findOne({ _id: projectId });
22
+ if (!project)
23
+ return res.status(404).json({ message: 'Project not found' });
24
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
25
+ return res.status(403).json({ message: 'Unauthorized' });
26
+ res.json({ connections: project.renderFarmConnections ?? [] });
27
+ }
28
+ catch (err) {
29
+ res.status(500).json({ message: err.message });
30
+ }
31
+ });
32
+ router.post('/projects/:projectId/render-farm-connections', async (req, res) => {
33
+ try {
34
+ const auth = req.auth;
35
+ const { projectId } = req.params;
36
+ const { name, provider, apiUrl, credentialId, description } = req.body;
37
+ if (!name || !provider || !apiUrl) {
38
+ return res.status(400).json({ message: 'name, provider and apiUrl are required' });
39
+ }
40
+ const project = await Project.findOne({ _id: projectId });
41
+ if (!project)
42
+ return res.status(404).json({ message: 'Project not found' });
43
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
44
+ return res.status(403).json({ message: 'Unauthorized' });
45
+ const connection = {
46
+ id: crypto_1.default.randomUUID(),
47
+ name,
48
+ provider,
49
+ apiUrl,
50
+ credentialId,
51
+ description,
52
+ projectId,
53
+ createdAt: new Date(),
54
+ };
55
+ await Project.update({ _id: projectId }, { $push: { renderFarmConnections: connection } });
56
+ res.status(201).json({ connection });
57
+ }
58
+ catch (err) {
59
+ res.status(500).json({ message: err.message });
60
+ }
61
+ });
62
+ router.delete('/projects/:projectId/render-farm-connections/:connectionId', async (req, res) => {
63
+ try {
64
+ const auth = req.auth;
65
+ const { projectId, connectionId } = req.params;
66
+ const project = await Project.findOne({ _id: projectId });
67
+ if (!project)
68
+ return res.status(404).json({ message: 'Project not found' });
69
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
70
+ return res.status(403).json({ message: 'Unauthorized' });
71
+ await Project.update({ _id: projectId }, { $pull: { renderFarmConnections: { id: connectionId } } });
72
+ res.json({ message: 'Connection deleted' });
73
+ }
74
+ catch (err) {
75
+ res.status(500).json({ message: err.message });
76
+ }
77
+ });
78
+ // ─── VFX Jobs ────────────────────────────────────────────────────────────────
79
+ router.post('/projects/:projectId/jobs/render', async (req, res) => {
80
+ try {
81
+ const auth = req.auth;
82
+ const { projectId } = req.params;
83
+ const { fileId, renderer, renderFarmConnectionId, frameRange, resolution, samples, outputFormat } = req.body;
84
+ if (!fileId)
85
+ return res.status(400).json({ message: 'fileId is required' });
86
+ const project = await Project.findOne({ _id: projectId });
87
+ if (!project)
88
+ return res.status(404).json({ message: 'Project not found' });
89
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
90
+ return res.status(403).json({ message: 'Unauthorized' });
91
+ const job = (0, jobQueue_1.createJob)('render', auth.userId, projectId, { fileId, renderer, renderFarmConnectionId, frameRange, resolution, samples, outputFormat });
92
+ setImmediate(() => (0, vfxProcessor_1.runRenderJob)(job.jobId, fileId, projectId, { renderer, renderFarmConnectionId, frameRange, resolution, samples, outputFormat }));
93
+ res.status(202).json({ jobId: job.jobId, status: job.status });
94
+ }
95
+ catch (err) {
96
+ res.status(500).json({ message: err.message });
97
+ }
98
+ });
99
+ router.post('/projects/:projectId/jobs/format-convert', async (req, res) => {
100
+ try {
101
+ const auth = req.auth;
102
+ const { projectId } = req.params;
103
+ const { fileId, inputFormat, outputFormat, applyTransforms, embedTextures } = req.body;
104
+ if (!fileId)
105
+ return res.status(400).json({ message: 'fileId is required' });
106
+ const project = await Project.findOne({ _id: projectId });
107
+ if (!project)
108
+ return res.status(404).json({ message: 'Project not found' });
109
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
110
+ return res.status(403).json({ message: 'Unauthorized' });
111
+ const job = (0, jobQueue_1.createJob)('format-convert', auth.userId, projectId, { fileId, inputFormat, outputFormat, applyTransforms, embedTextures });
112
+ setImmediate(() => (0, vfxProcessor_1.runFormatConvertJob)(job.jobId, fileId, projectId, { inputFormat, outputFormat, applyTransforms, embedTextures }));
113
+ res.status(202).json({ jobId: job.jobId, status: job.status });
114
+ }
115
+ catch (err) {
116
+ res.status(500).json({ message: err.message });
117
+ }
118
+ });
119
+ router.post('/projects/:projectId/jobs/lod', async (req, res) => {
120
+ try {
121
+ const auth = req.auth;
122
+ const { projectId } = req.params;
123
+ const { fileId, levels, reductionRatios, algorithm, outputFormat } = req.body;
124
+ if (!fileId)
125
+ return res.status(400).json({ message: 'fileId is required' });
126
+ const project = await Project.findOne({ _id: projectId });
127
+ if (!project)
128
+ return res.status(404).json({ message: 'Project not found' });
129
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
130
+ return res.status(403).json({ message: 'Unauthorized' });
131
+ const job = (0, jobQueue_1.createJob)('lod', auth.userId, projectId, { fileId, levels, reductionRatios, algorithm, outputFormat });
132
+ setImmediate(() => (0, vfxProcessor_1.runLodJob)(job.jobId, fileId, projectId, { levels, reductionRatios, algorithm, outputFormat }));
133
+ res.status(202).json({ jobId: job.jobId, status: job.status });
134
+ }
135
+ catch (err) {
136
+ res.status(500).json({ message: err.message });
137
+ }
138
+ });
139
+ // ─── Asset Dependency Graph ──────────────────────────────────────────────────
140
+ router.get('/projects/:projectId/assets/:fileId/dependency-graph', async (req, res) => {
141
+ try {
142
+ const auth = req.auth;
143
+ const { projectId, fileId } = req.params;
144
+ const maxDepth = Number(req.query.maxDepth ?? 5);
145
+ const project = await Project.findOne({ _id: projectId });
146
+ if (!project)
147
+ return res.status(404).json({ message: 'Project not found' });
148
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
149
+ return res.status(403).json({ message: 'Unauthorized' });
150
+ const graph = await (0, vfxProcessor_1.resolveAssetDependencyGraph)(projectId, fileId, maxDepth);
151
+ res.json({ graph });
152
+ }
153
+ catch (err) {
154
+ res.status(500).json({ message: err.message });
155
+ }
156
+ });
157
+ return router;
158
+ }
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig } from '../../types';
3
+ export declare function reviewRouter(_config: StreamByConfig): Router;
@@ -0,0 +1,240 @@
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.reviewRouter = reviewRouter;
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 broadcastReviewUpdate(review, project) {
20
+ const members = project.members ?? [];
21
+ for (const m of members) {
22
+ (0, wsHub_1.emitToUser)(m.userId, { type: 'reviewEvent', data: review });
23
+ }
24
+ }
25
+ function reviewRouter(_config) {
26
+ const router = (0, express_1.Router)();
27
+ const Project = (0, manager_1.getModel)('projects');
28
+ // GET /projects/:projectId/reviews
29
+ router.get('/projects/:projectId/reviews', async (req, res) => {
30
+ try {
31
+ const auth = req.auth;
32
+ const { projectId } = req.params;
33
+ const project = await Project.findOne({ _id: projectId });
34
+ if (!project)
35
+ return res.status(404).json({ message: 'Project not found' });
36
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
37
+ return res.status(403).json({ message: 'Unauthorized' });
38
+ const db = getDb();
39
+ if (!db)
40
+ return res.status(500).json({ message: 'Database not available' });
41
+ const reviews = await db.collection('review_sessions')
42
+ .find({ projectId })
43
+ .sort({ createdAt: -1 })
44
+ .toArray();
45
+ res.json({ reviews });
46
+ }
47
+ catch (err) {
48
+ res.status(500).json({ message: err.message });
49
+ }
50
+ });
51
+ // POST /projects/:projectId/reviews
52
+ router.post('/projects/:projectId/reviews', async (req, res) => {
53
+ try {
54
+ const auth = req.auth;
55
+ const { projectId } = req.params;
56
+ const { assetId, assetVersionId, requiredApprovers, deadline } = req.body;
57
+ if (!assetId || !assetVersionId) {
58
+ return res.status(400).json({ message: 'assetId and assetVersionId are required' });
59
+ }
60
+ const project = await Project.findOne({ _id: projectId });
61
+ if (!project)
62
+ return res.status(404).json({ message: 'Project not found' });
63
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
64
+ return res.status(403).json({ message: 'Unauthorized' });
65
+ const db = getDb();
66
+ if (!db)
67
+ return res.status(500).json({ message: 'Database not available' });
68
+ const now = new Date();
69
+ const review = {
70
+ id: crypto_1.default.randomUUID(),
71
+ projectId,
72
+ assetId,
73
+ assetVersionId,
74
+ status: 'open',
75
+ requiredApprovers: requiredApprovers ?? 1,
76
+ approvals: [],
77
+ deadline: deadline ? new Date(deadline) : undefined,
78
+ createdAt: now,
79
+ updatedAt: now,
80
+ };
81
+ await db.collection('review_sessions').insertOne({ ...review });
82
+ broadcastReviewUpdate(review, project);
83
+ res.status(201).json({ review });
84
+ }
85
+ catch (err) {
86
+ res.status(500).json({ message: err.message });
87
+ }
88
+ });
89
+ // GET /projects/:projectId/reviews/:reviewId
90
+ router.get('/projects/:projectId/reviews/:reviewId', async (req, res) => {
91
+ try {
92
+ const auth = req.auth;
93
+ const { projectId, reviewId } = req.params;
94
+ const project = await Project.findOne({ _id: projectId });
95
+ if (!project)
96
+ return res.status(404).json({ message: 'Project not found' });
97
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
98
+ return res.status(403).json({ message: 'Unauthorized' });
99
+ const db = getDb();
100
+ if (!db)
101
+ return res.status(500).json({ message: 'Database not available' });
102
+ const review = await db.collection('review_sessions').findOne({ id: reviewId, projectId });
103
+ if (!review)
104
+ return res.status(404).json({ message: 'Review not found' });
105
+ res.json({ review });
106
+ }
107
+ catch (err) {
108
+ res.status(500).json({ message: err.message });
109
+ }
110
+ });
111
+ // POST /projects/:projectId/reviews/:reviewId/decision
112
+ router.post('/projects/:projectId/reviews/:reviewId/decision', async (req, res) => {
113
+ try {
114
+ const auth = req.auth;
115
+ const { projectId, reviewId } = req.params;
116
+ const { decision, comment } = req.body;
117
+ if (decision !== 'approve' && decision !== 'reject') {
118
+ return res.status(400).json({ message: 'decision must be approve or reject' });
119
+ }
120
+ const project = await Project.findOne({ _id: projectId });
121
+ if (!project)
122
+ return res.status(404).json({ message: 'Project not found' });
123
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
124
+ return res.status(403).json({ message: 'Unauthorized' });
125
+ const db = getDb();
126
+ if (!db)
127
+ return res.status(500).json({ message: 'Database not available' });
128
+ const col = db.collection('review_sessions');
129
+ const review = await col.findOne({ id: reviewId, projectId });
130
+ if (!review)
131
+ return res.status(404).json({ message: 'Review not found' });
132
+ if (review.status !== 'open')
133
+ return res.status(409).json({ message: `Review is already ${review.status}` });
134
+ // Remove any prior decision from this user, then add the new one
135
+ const approvals = (review.approvals ?? []).filter(a => a.userId !== auth.userId);
136
+ approvals.push({
137
+ userId: auth.userId,
138
+ username: auth.username ?? auth.userId,
139
+ decision,
140
+ comment,
141
+ at: new Date(),
142
+ });
143
+ const approveCount = approvals.filter(a => a.decision === 'approve').length;
144
+ const hasRejection = approvals.some(a => a.decision === 'reject');
145
+ let status = 'open';
146
+ if (hasRejection)
147
+ status = 'rejected';
148
+ else if (approveCount >= review.requiredApprovers)
149
+ status = 'approved';
150
+ const updatedAt = new Date();
151
+ await col.updateOne({ id: reviewId, projectId }, { $set: { approvals, status, updatedAt } });
152
+ const updated = { ...review, approvals, status, updatedAt };
153
+ broadcastReviewUpdate(updated, project);
154
+ res.json({ review: updated });
155
+ }
156
+ catch (err) {
157
+ res.status(500).json({ message: err.message });
158
+ }
159
+ });
160
+ // ─── Annotations ─────────────────────────────────────────────────────────────
161
+ router.get('/projects/:projectId/assets/:assetId/versions/:versionId/annotations', async (req, res) => {
162
+ try {
163
+ const auth = req.auth;
164
+ const { projectId, assetId, versionId } = req.params;
165
+ const project = await Project.findOne({ _id: projectId });
166
+ if (!project)
167
+ return res.status(404).json({ message: 'Project not found' });
168
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
169
+ return res.status(403).json({ message: 'Unauthorized' });
170
+ const db = getDb();
171
+ if (!db)
172
+ return res.status(500).json({ message: 'Database not available' });
173
+ const annotations = await db.collection('annotations')
174
+ .find({ assetId, assetVersionId: versionId, projectId })
175
+ .sort({ createdAt: 1 })
176
+ .toArray();
177
+ res.json({ annotations });
178
+ }
179
+ catch (err) {
180
+ res.status(500).json({ message: err.message });
181
+ }
182
+ });
183
+ router.post('/projects/:projectId/assets/:assetId/versions/:versionId/annotations', async (req, res) => {
184
+ try {
185
+ const auth = req.auth;
186
+ const { projectId, assetId, versionId } = req.params;
187
+ const { type, timecode, position3d, regionRect, text } = req.body;
188
+ if (!type || !text)
189
+ return res.status(400).json({ message: 'type and text are required' });
190
+ const project = await Project.findOne({ _id: projectId });
191
+ if (!project)
192
+ return res.status(404).json({ message: 'Project not found' });
193
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
194
+ return res.status(403).json({ message: 'Unauthorized' });
195
+ const db = getDb();
196
+ if (!db)
197
+ return res.status(500).json({ message: 'Database not available' });
198
+ const annotation = {
199
+ id: crypto_1.default.randomUUID(),
200
+ assetId,
201
+ assetVersionId: versionId,
202
+ projectId,
203
+ authorId: auth.userId,
204
+ authorUsername: auth.username ?? auth.userId,
205
+ type,
206
+ timecode,
207
+ position3d,
208
+ regionRect,
209
+ text,
210
+ resolved: false,
211
+ createdAt: new Date(),
212
+ };
213
+ await db.collection('annotations').insertOne({ ...annotation });
214
+ res.status(201).json({ annotation });
215
+ }
216
+ catch (err) {
217
+ res.status(500).json({ message: err.message });
218
+ }
219
+ });
220
+ router.patch('/projects/:projectId/assets/:assetId/versions/:versionId/annotations/:annotationId/resolve', async (req, res) => {
221
+ try {
222
+ const auth = req.auth;
223
+ const { projectId, assetId, versionId, annotationId } = req.params;
224
+ const project = await Project.findOne({ _id: projectId });
225
+ if (!project)
226
+ return res.status(404).json({ message: 'Project not found' });
227
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
228
+ return res.status(403).json({ message: 'Unauthorized' });
229
+ const db = getDb();
230
+ if (!db)
231
+ return res.status(500).json({ message: 'Database not available' });
232
+ await db.collection('annotations').updateOne({ id: annotationId, assetId, assetVersionId: versionId, projectId }, { $set: { resolved: true, resolvedBy: auth.userId } });
233
+ res.json({ message: 'Annotation resolved' });
234
+ }
235
+ catch (err) {
236
+ res.status(500).json({ message: err.message });
237
+ }
238
+ });
239
+ return router;
240
+ }
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig } from '../../types';
3
+ export declare function rightsRouter(_config: StreamByConfig): Router;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rightsRouter = rightsRouter;
4
+ const express_1 = require("express");
5
+ const auth_1 = require("../../utils/auth");
6
+ const connectionManager_1 = require("../../adapters/database/connectionManager");
7
+ const manager_1 = require("../../models/manager");
8
+ function getDb() {
9
+ const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
10
+ if (!ids.length)
11
+ return null;
12
+ return (0, connectionManager_1.getConnection)(ids[0]).client.db();
13
+ }
14
+ function rightsRouter(_config) {
15
+ const router = (0, express_1.Router)();
16
+ const Project = (0, manager_1.getModel)('projects');
17
+ // GET /projects/:projectId/assets/:assetId/rights
18
+ router.get('/projects/:projectId/assets/:assetId/rights', async (req, res) => {
19
+ try {
20
+ const auth = req.auth;
21
+ const { projectId, assetId } = req.params;
22
+ const project = await Project.findOne({ _id: projectId });
23
+ if (!project)
24
+ return res.status(404).json({ message: 'Project not found' });
25
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
26
+ return res.status(403).json({ message: 'Unauthorized' });
27
+ const db = getDb();
28
+ if (!db)
29
+ return res.status(500).json({ message: 'Database not available' });
30
+ const rights = await db.collection('asset_rights').findOne({ assetId, projectId });
31
+ res.json({ rights: rights ?? null });
32
+ }
33
+ catch (err) {
34
+ res.status(500).json({ message: err.message });
35
+ }
36
+ });
37
+ // PUT /projects/:projectId/assets/:assetId/rights (upsert)
38
+ router.put('/projects/:projectId/assets/:assetId/rights', async (req, res) => {
39
+ try {
40
+ const auth = req.auth;
41
+ const { projectId, assetId } = req.params;
42
+ const project = await Project.findOne({ _id: projectId });
43
+ if (!project)
44
+ return res.status(404).json({ message: 'Project not found' });
45
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
46
+ return res.status(403).json({ message: 'Unauthorized' });
47
+ const db = getDb();
48
+ if (!db)
49
+ return res.status(500).json({ message: 'Database not available' });
50
+ const { rightsHolder, licenseType, licenseUrl, territory, expiresAt, usageRestrictions, notes } = req.body;
51
+ const now = new Date();
52
+ const payload = {
53
+ assetId,
54
+ projectId,
55
+ licenseType: licenseType ?? 'unknown',
56
+ updatedBy: auth.userId,
57
+ updatedAt: now,
58
+ };
59
+ if (rightsHolder !== undefined)
60
+ payload.rightsHolder = rightsHolder;
61
+ if (licenseUrl !== undefined)
62
+ payload.licenseUrl = licenseUrl;
63
+ if (territory !== undefined)
64
+ payload.territory = territory;
65
+ if (expiresAt !== undefined)
66
+ payload.expiresAt = expiresAt ? new Date(expiresAt) : null;
67
+ if (usageRestrictions !== undefined)
68
+ payload.usageRestrictions = usageRestrictions;
69
+ if (notes !== undefined)
70
+ payload.notes = notes;
71
+ await db.collection('asset_rights').updateOne({ assetId, projectId }, { $set: payload }, { upsert: true });
72
+ const rights = await db.collection('asset_rights').findOne({ assetId, projectId });
73
+ res.json({ rights });
74
+ }
75
+ catch (err) {
76
+ res.status(500).json({ message: err.message });
77
+ }
78
+ });
79
+ return router;
80
+ }
@@ -45,11 +45,11 @@ class Model {
45
45
  // Handle members filter for SQL projects
46
46
  const userId = processedFilter.members.$elemMatch.userId;
47
47
  const schema = this.schema || 'streamby'; // Default to 'streamby' if schema not provided
48
- const query = `
49
- SELECT p.*, pm."userId" as memberUserId, pm.role as memberRole, pm.status as memberStatus, pm.archived as memberArchived
50
- FROM "${schema}"."projects" p
51
- JOIN "${schema}"."project_members" pm ON p.id = pm."projectId"
52
- WHERE pm."userId" = $1
48
+ const query = `
49
+ SELECT p.*, pm."userId" as memberUserId, pm.role as memberRole, pm.status as memberStatus, pm.archived as memberArchived
50
+ FROM "${schema}"."projects" p
51
+ JOIN "${schema}"."project_members" pm ON p.id = pm."projectId"
52
+ WHERE pm."userId" = $1
53
53
  `;
54
54
  const result = await connection.query(query, [userId]);
55
55
  sqlResults = result.rows.map(row => ({
@@ -219,10 +219,10 @@ class Model {
219
219
  else {
220
220
  const matchKey = value.id !== undefined ? 'id' : Object.keys(value)[0];
221
221
  const matchVal = String(value[matchKey]);
222
- await connection.query(`UPDATE ${tbl} SET "${field}" = (
223
- SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb)
224
- FROM jsonb_array_elements("${field}") elem
225
- WHERE elem->>'${matchKey}' != $1
222
+ await connection.query(`UPDATE ${tbl} SET "${field}" = (
223
+ SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb)
224
+ FROM jsonb_array_elements("${field}") elem
225
+ WHERE elem->>'${matchKey}' != $1
226
226
  ) WHERE id = $2`, [matchVal, id]);
227
227
  }
228
228
  }
@@ -0,0 +1,25 @@
1
+ import { PipelineSuggestion } from '../types';
2
+ export declare function runTranscriptionJob(jobId: string, fileId: string, projectId: string, options: {
3
+ model?: string;
4
+ sourceLanguage?: string;
5
+ outputFormats?: string[];
6
+ provider?: string;
7
+ credentialId?: string;
8
+ }): Promise<void>;
9
+ export declare function runUpscaleJob(jobId: string, fileId: string, projectId: string, options: {
10
+ scale?: number;
11
+ model?: string;
12
+ mode?: 'images' | 'video';
13
+ credentialId?: string;
14
+ }): Promise<void>;
15
+ export declare function runGenerateAssetJob(jobId: string, projectId: string, options: {
16
+ assetType?: 'mesh' | 'texture' | 'audio';
17
+ provider?: string;
18
+ prompt: string;
19
+ seed?: number;
20
+ credentialId?: string;
21
+ }): Promise<void>;
22
+ export declare function buildPipelineSuggestion(nodeSchema: {
23
+ nodes: any[];
24
+ edges: any[];
25
+ }, availableNodeTypes: string[]): Promise<PipelineSuggestion>;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runTranscriptionJob = runTranscriptionJob;
4
+ exports.runUpscaleJob = runUpscaleJob;
5
+ exports.runGenerateAssetJob = runGenerateAssetJob;
6
+ exports.buildPipelineSuggestion = buildPipelineSuggestion;
7
+ const connectionManager_1 = require("../adapters/database/connectionManager");
8
+ const jobQueue_1 = require("./jobQueue");
9
+ function getNosqlDb() {
10
+ const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
11
+ if (!ids.length)
12
+ return null;
13
+ return (0, connectionManager_1.getConnection)(ids[0]).client.db();
14
+ }
15
+ async function runTranscriptionJob(jobId, fileId, projectId, options) {
16
+ (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
17
+ try {
18
+ const db = getNosqlDb();
19
+ if (!db)
20
+ throw new Error('Storage database not available');
21
+ const fileDoc = await db.collection('storage_files').findOne({ fileId, projectId });
22
+ if (!fileDoc)
23
+ throw new Error(`File ${fileId} not found`);
24
+ if (!fileDoc.contentType?.startsWith('video/') && !fileDoc.contentType?.startsWith('audio/')) {
25
+ throw new Error('Transcription only supports video and audio files');
26
+ }
27
+ (0, jobQueue_1.updateJob)(jobId, { stage: 'queued-provider', progress: 20 });
28
+ await db.collection('media_metadata').updateOne({ assetId: fileId, projectId }, {
29
+ $set: {
30
+ assetId: fileId,
31
+ projectId,
32
+ pendingTranscription: {
33
+ model: options.model ?? 'whisper-1',
34
+ sourceLanguage: options.sourceLanguage ?? 'auto',
35
+ outputFormats: options.outputFormats ?? ['srt', 'vtt'],
36
+ provider: options.provider ?? 'openai',
37
+ credentialId: options.credentialId,
38
+ requestedAt: new Date(),
39
+ },
40
+ customTags: {},
41
+ extractedAt: new Date(),
42
+ },
43
+ }, { upsert: true });
44
+ (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
45
+ }
46
+ catch (err) {
47
+ (0, jobQueue_1.failJob)(jobId, err.message);
48
+ }
49
+ }
50
+ async function runUpscaleJob(jobId, fileId, projectId, options) {
51
+ (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
52
+ try {
53
+ const db = getNosqlDb();
54
+ if (!db)
55
+ throw new Error('Storage database not available');
56
+ const fileDoc = await db.collection('storage_files').findOne({ fileId, projectId });
57
+ if (!fileDoc)
58
+ throw new Error(`File ${fileId} not found`);
59
+ (0, jobQueue_1.updateJob)(jobId, { stage: 'queued-upscale', progress: 20 });
60
+ await db.collection('media_metadata').updateOne({ assetId: fileId, projectId }, {
61
+ $set: {
62
+ assetId: fileId,
63
+ projectId,
64
+ pendingUpscale: {
65
+ scale: options.scale ?? 4,
66
+ model: options.model ?? 'real-esrgan',
67
+ mode: options.mode ?? 'images',
68
+ credentialId: options.credentialId,
69
+ requestedAt: new Date(),
70
+ },
71
+ customTags: {},
72
+ extractedAt: new Date(),
73
+ },
74
+ }, { upsert: true });
75
+ (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
76
+ }
77
+ catch (err) {
78
+ (0, jobQueue_1.failJob)(jobId, err.message);
79
+ }
80
+ }
81
+ async function runGenerateAssetJob(jobId, projectId, options) {
82
+ (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'queued-generation', progress: 10 });
83
+ try {
84
+ const db = getNosqlDb();
85
+ if (!db)
86
+ throw new Error('Storage database not available');
87
+ await db.collection('generative_jobs').insertOne({
88
+ jobId,
89
+ projectId,
90
+ assetType: options.assetType ?? 'mesh',
91
+ provider: options.provider ?? 'meshy',
92
+ prompt: options.prompt,
93
+ seed: options.seed,
94
+ credentialId: options.credentialId,
95
+ status: 'pending',
96
+ requestedAt: new Date(),
97
+ });
98
+ (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 20 });
99
+ }
100
+ catch (err) {
101
+ (0, jobQueue_1.failJob)(jobId, err.message);
102
+ }
103
+ }
104
+ async function buildPipelineSuggestion(nodeSchema, availableNodeTypes) {
105
+ const presentTypes = new Set(nodeSchema.nodes.map((n) => n.type));
106
+ // Heuristic rules — replaced by LLM call in the host environment
107
+ const suggestions = [];
108
+ const edgeSuggestions = [];
109
+ const hasVideo = nodeSchema.nodes.some((n) => ['ingestNode', 'transcodeNode'].includes(n.type));
110
+ if (hasVideo && !presentTypes.has('captionNode') && availableNodeTypes.includes('captionNode')) {
111
+ suggestions.push({ type: 'captionNode', label: 'Captions', position: { x: 600, y: 100 } });
112
+ }
113
+ if (hasVideo && !presentTypes.has('thumbnailNode') && availableNodeTypes.includes('thumbnailNode')) {
114
+ suggestions.push({ type: 'thumbnailNode', label: 'Thumbnail', position: { x: 600, y: 250 } });
115
+ }
116
+ if (!presentTypes.has('qcCheckNode') && availableNodeTypes.includes('qcCheckNode')) {
117
+ suggestions.push({ type: 'qcCheckNode', label: 'QC Check', position: { x: 400, y: -150 } });
118
+ }
119
+ const rationale = suggestions.length > 0
120
+ ? `Detected ${suggestions.map(s => s.label).join(', ')} as missing steps for a complete pipeline.`
121
+ : 'Pipeline looks complete for the current asset types.';
122
+ return { rationale, nodesToAdd: suggestions, edgesToAdd: edgeSuggestions };
123
+ }