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.
- package/README.md +283 -283
- package/dist/adapters/database/connectionManager.js +63 -63
- package/dist/adapters/database/sql.js +1 -1
- package/dist/middleware/createRouter.js +17 -0
- package/dist/middleware/routes/ai.d.ts +3 -0
- package/dist/middleware/routes/ai.js +105 -0
- package/dist/middleware/routes/distribution.d.ts +3 -0
- package/dist/middleware/routes/distribution.js +192 -0
- package/dist/middleware/routes/media.d.ts +5 -0
- package/dist/middleware/routes/media.js +189 -0
- package/dist/middleware/routes/renderFarm.d.ts +3 -0
- package/dist/middleware/routes/renderFarm.js +158 -0
- package/dist/middleware/routes/review.d.ts +3 -0
- package/dist/middleware/routes/review.js +240 -0
- package/dist/models/Model.js +9 -9
- package/dist/services/aiProcessor.d.ts +25 -0
- package/dist/services/aiProcessor.js +123 -0
- package/dist/services/jobQueue.d.ts +5 -0
- package/dist/services/jobQueue.js +54 -0
- package/dist/services/mediaProcessor.d.ts +19 -0
- package/dist/services/mediaProcessor.js +201 -0
- package/dist/services/pipeline.js +153 -8
- package/dist/services/vfxProcessor.d.ts +24 -0
- package/dist/services/vfxProcessor.js +139 -0
- package/dist/types/index.d.ts +188 -0
- package/package.json +63 -63
- package/dist/utils/crypto.d.ts +0 -3
- package/dist/utils/crypto.js +0 -32
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mediaRouter = mediaRouter;
|
|
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 mediaProcessor_1 = require("../../services/mediaProcessor");
|
|
9
|
+
const storage_1 = require("../../providers/storage");
|
|
10
|
+
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
11
|
+
function getMetadataCollection() {
|
|
12
|
+
const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
|
|
13
|
+
if (!ids.length)
|
|
14
|
+
return null;
|
|
15
|
+
return (0, connectionManager_1.getConnection)(ids[0]).client.db().collection('media_metadata');
|
|
16
|
+
}
|
|
17
|
+
function getVersionsCollection() {
|
|
18
|
+
const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
|
|
19
|
+
if (!ids.length)
|
|
20
|
+
return null;
|
|
21
|
+
return (0, connectionManager_1.getConnection)(ids[0]).client.db().collection('asset_versions');
|
|
22
|
+
}
|
|
23
|
+
function mediaRouter(config) {
|
|
24
|
+
const router = (0, express_1.Router)();
|
|
25
|
+
const adapter = config.adapter || (0, storage_1.createStorageProvider)(config.storageProviders);
|
|
26
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
27
|
+
// GET /jobs/:jobId — poll job status
|
|
28
|
+
router.get('/jobs/:jobId', (req, res) => {
|
|
29
|
+
const job = (0, jobQueue_1.getJob)(req.params.jobId);
|
|
30
|
+
if (!job)
|
|
31
|
+
return res.status(404).json({ message: 'Job not found' });
|
|
32
|
+
res.json({ job });
|
|
33
|
+
});
|
|
34
|
+
// POST /projects/:projectId/jobs/ingest — queue an ingest job for an already-uploaded file
|
|
35
|
+
router.post('/projects/:projectId/jobs/ingest', async (req, res) => {
|
|
36
|
+
try {
|
|
37
|
+
const auth = req.auth;
|
|
38
|
+
const { projectId } = req.params;
|
|
39
|
+
const { fileId } = req.body;
|
|
40
|
+
if (!fileId)
|
|
41
|
+
return res.status(400).json({ message: 'fileId is required' });
|
|
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 job = (0, jobQueue_1.createJob)('ingest', auth.userId, projectId, { fileId });
|
|
48
|
+
// Run async — response returns immediately with jobId
|
|
49
|
+
setImmediate(() => (0, mediaProcessor_1.runIngestJob)(job.jobId, fileId, projectId));
|
|
50
|
+
res.status(202).json({ jobId: job.jobId, status: job.status });
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
res.status(500).json({ message: err.message });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// POST /projects/:projectId/jobs/transcode
|
|
57
|
+
router.post('/projects/:projectId/jobs/transcode', async (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
const auth = req.auth;
|
|
60
|
+
const { projectId } = req.params;
|
|
61
|
+
const { fileId, codec, resolution, outputFormat, bitrate, audioCodec } = req.body;
|
|
62
|
+
if (!fileId)
|
|
63
|
+
return res.status(400).json({ message: 'fileId is required' });
|
|
64
|
+
const project = await Project.findOne({ _id: projectId });
|
|
65
|
+
if (!project)
|
|
66
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
67
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
68
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
69
|
+
const job = (0, jobQueue_1.createJob)('transcode', auth.userId, projectId, {
|
|
70
|
+
fileId, codec, resolution, outputFormat, bitrate, audioCodec,
|
|
71
|
+
});
|
|
72
|
+
setImmediate(() => (0, mediaProcessor_1.runTranscodeJob)(job.jobId, fileId, projectId, { codec, resolution, outputFormat, bitrate, audioCodec }, adapter));
|
|
73
|
+
res.status(202).json({ jobId: job.jobId, status: job.status });
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
res.status(500).json({ message: err.message });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// POST /projects/:projectId/jobs/thumbnail
|
|
80
|
+
router.post('/projects/:projectId/jobs/thumbnail', async (req, res) => {
|
|
81
|
+
try {
|
|
82
|
+
const auth = req.auth;
|
|
83
|
+
const { projectId } = req.params;
|
|
84
|
+
const { fileId, timecode, resolution, strategy } = req.body;
|
|
85
|
+
if (!fileId)
|
|
86
|
+
return res.status(400).json({ message: 'fileId is required' });
|
|
87
|
+
const project = await Project.findOne({ _id: projectId });
|
|
88
|
+
if (!project)
|
|
89
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
90
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
91
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
92
|
+
const job = (0, jobQueue_1.createJob)('thumbnail', auth.userId, projectId, { fileId, timecode, resolution, strategy });
|
|
93
|
+
setImmediate(() => (0, mediaProcessor_1.runThumbnailJob)(job.jobId, fileId, projectId, { timecode, resolution, strategy }));
|
|
94
|
+
res.status(202).json({ jobId: job.jobId, status: job.status });
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
res.status(500).json({ message: err.message });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// POST /projects/:projectId/jobs/caption
|
|
101
|
+
router.post('/projects/:projectId/jobs/caption', async (req, res) => {
|
|
102
|
+
try {
|
|
103
|
+
const auth = req.auth;
|
|
104
|
+
const { projectId } = req.params;
|
|
105
|
+
const { fileId, sourceLanguage, outputFormat, provider } = req.body;
|
|
106
|
+
if (!fileId)
|
|
107
|
+
return res.status(400).json({ message: 'fileId is required' });
|
|
108
|
+
const project = await Project.findOne({ _id: projectId });
|
|
109
|
+
if (!project)
|
|
110
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
111
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
112
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
113
|
+
const job = (0, jobQueue_1.createJob)('caption', auth.userId, projectId, { fileId, sourceLanguage, outputFormat, provider });
|
|
114
|
+
setImmediate(() => (0, mediaProcessor_1.runCaptionJob)(job.jobId, fileId, projectId, { sourceLanguage, outputFormat, provider }));
|
|
115
|
+
res.status(202).json({ jobId: job.jobId, status: job.status });
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
res.status(500).json({ message: err.message });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
// GET /projects/:projectId/assets/:fileId/metadata
|
|
122
|
+
router.get('/projects/:projectId/assets/:fileId/metadata', async (req, res) => {
|
|
123
|
+
try {
|
|
124
|
+
const auth = req.auth;
|
|
125
|
+
const { projectId, fileId } = req.params;
|
|
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 col = getMetadataCollection();
|
|
132
|
+
if (!col)
|
|
133
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
134
|
+
const meta = await col.findOne({ assetId: fileId, projectId });
|
|
135
|
+
res.json({ metadata: meta ?? null });
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
res.status(500).json({ message: err.message });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
// PATCH /projects/:projectId/assets/:fileId/metadata — update custom tags
|
|
142
|
+
router.patch('/projects/:projectId/assets/:fileId/metadata', async (req, res) => {
|
|
143
|
+
try {
|
|
144
|
+
const auth = req.auth;
|
|
145
|
+
const { projectId, fileId } = req.params;
|
|
146
|
+
const { customTags } = req.body;
|
|
147
|
+
if (!customTags || typeof customTags !== 'object') {
|
|
148
|
+
return res.status(400).json({ message: 'customTags object is required' });
|
|
149
|
+
}
|
|
150
|
+
const project = await Project.findOne({ _id: projectId });
|
|
151
|
+
if (!project)
|
|
152
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
153
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
154
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
155
|
+
const col = getMetadataCollection();
|
|
156
|
+
if (!col)
|
|
157
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
158
|
+
await col.updateOne({ assetId: fileId, projectId }, { $set: { customTags, updatedAt: new Date() } }, { upsert: true });
|
|
159
|
+
res.json({ message: 'Metadata updated' });
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
res.status(500).json({ message: err.message });
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
// GET /projects/:projectId/assets/:fileId/versions
|
|
166
|
+
router.get('/projects/:projectId/assets/:fileId/versions', async (req, res) => {
|
|
167
|
+
try {
|
|
168
|
+
const auth = req.auth;
|
|
169
|
+
const { projectId, fileId } = req.params;
|
|
170
|
+
const project = await Project.findOne({ _id: projectId });
|
|
171
|
+
if (!project)
|
|
172
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
173
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
174
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
175
|
+
const col = getVersionsCollection();
|
|
176
|
+
if (!col)
|
|
177
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
178
|
+
const versions = await col
|
|
179
|
+
.find({ assetId: fileId, projectId })
|
|
180
|
+
.sort({ version: -1 })
|
|
181
|
+
.toArray();
|
|
182
|
+
res.json({ versions });
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
res.status(500).json({ message: err.message });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
return router;
|
|
189
|
+
}
|
|
@@ -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,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
|
+
}
|
package/dist/models/Model.js
CHANGED
|
@@ -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>;
|