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.
- 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 +21 -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/production.d.ts +3 -0
- package/dist/middleware/routes/production.js +362 -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/middleware/routes/rights.d.ts +3 -0
- package/dist/middleware/routes/rights.js +80 -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,362 @@
|
|
|
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.productionRouter = productionRouter;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const auth_1 = require("../../utils/auth");
|
|
9
|
+
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
10
|
+
const manager_1 = require("../../models/manager");
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
function getDb() {
|
|
13
|
+
const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
|
|
14
|
+
if (!ids.length)
|
|
15
|
+
return null;
|
|
16
|
+
return (0, connectionManager_1.getConnection)(ids[0]).client.db();
|
|
17
|
+
}
|
|
18
|
+
function productionRouter(_config) {
|
|
19
|
+
const router = (0, express_1.Router)();
|
|
20
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
21
|
+
// ── Sequences ──────────────────────────────────────────────────────────────
|
|
22
|
+
router.get('/projects/:projectId/production/sequences', async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const auth = req.auth;
|
|
25
|
+
const { projectId } = req.params;
|
|
26
|
+
const project = await Project.findOne({ _id: projectId });
|
|
27
|
+
if (!project)
|
|
28
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
29
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
30
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
31
|
+
const db = getDb();
|
|
32
|
+
if (!db)
|
|
33
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
34
|
+
const sequences = await db.collection('production_sequences')
|
|
35
|
+
.find({ projectId })
|
|
36
|
+
.sort({ order: 1, createdAt: 1 })
|
|
37
|
+
.toArray();
|
|
38
|
+
res.json({ sequences });
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
res.status(500).json({ message: err.message });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
router.post('/projects/:projectId/production/sequences', async (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
const auth = req.auth;
|
|
47
|
+
const { projectId } = req.params;
|
|
48
|
+
const { name, description, order } = req.body;
|
|
49
|
+
if (!name)
|
|
50
|
+
return res.status(400).json({ message: 'name is required' });
|
|
51
|
+
const project = await Project.findOne({ _id: projectId });
|
|
52
|
+
if (!project)
|
|
53
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
54
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
55
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
56
|
+
const db = getDb();
|
|
57
|
+
if (!db)
|
|
58
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
59
|
+
const now = new Date();
|
|
60
|
+
const sequence = {
|
|
61
|
+
id: crypto_1.default.randomUUID(),
|
|
62
|
+
projectId,
|
|
63
|
+
name,
|
|
64
|
+
description: description ?? null,
|
|
65
|
+
order: order ?? 0,
|
|
66
|
+
createdBy: auth.userId,
|
|
67
|
+
createdAt: now,
|
|
68
|
+
updatedAt: now,
|
|
69
|
+
};
|
|
70
|
+
await db.collection('production_sequences').insertOne({ ...sequence });
|
|
71
|
+
res.status(201).json({ sequence });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
res.status(500).json({ message: err.message });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
router.patch('/projects/:projectId/production/sequences/:seqId', async (req, res) => {
|
|
78
|
+
try {
|
|
79
|
+
const auth = req.auth;
|
|
80
|
+
const { projectId, seqId } = req.params;
|
|
81
|
+
const project = await Project.findOne({ _id: projectId });
|
|
82
|
+
if (!project)
|
|
83
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
84
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
85
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
86
|
+
const db = getDb();
|
|
87
|
+
if (!db)
|
|
88
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
89
|
+
const { name, description, order } = req.body;
|
|
90
|
+
const $set = { updatedAt: new Date() };
|
|
91
|
+
if (name !== undefined)
|
|
92
|
+
$set.name = name;
|
|
93
|
+
if (description !== undefined)
|
|
94
|
+
$set.description = description;
|
|
95
|
+
if (order !== undefined)
|
|
96
|
+
$set.order = order;
|
|
97
|
+
const result = await db.collection('production_sequences').findOneAndUpdate({ id: seqId, projectId }, { $set }, { returnDocument: 'after' });
|
|
98
|
+
if (!result)
|
|
99
|
+
return res.status(404).json({ message: 'Sequence not found' });
|
|
100
|
+
res.json({ sequence: result });
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
res.status(500).json({ message: err.message });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
router.delete('/projects/:projectId/production/sequences/:seqId', async (req, res) => {
|
|
107
|
+
try {
|
|
108
|
+
const auth = req.auth;
|
|
109
|
+
const { projectId, seqId } = req.params;
|
|
110
|
+
const project = await Project.findOne({ _id: projectId });
|
|
111
|
+
if (!project)
|
|
112
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
113
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
114
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
115
|
+
const db = getDb();
|
|
116
|
+
if (!db)
|
|
117
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
118
|
+
await db.collection('production_sequences').deleteOne({ id: seqId, projectId });
|
|
119
|
+
await db.collection('production_shots').deleteMany({ sequenceId: seqId, projectId });
|
|
120
|
+
res.json({ message: 'Sequence deleted' });
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
res.status(500).json({ message: err.message });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// ── Shots ──────────────────────────────────────────────────────────────────
|
|
127
|
+
router.get('/projects/:projectId/production/sequences/:seqId/shots', async (req, res) => {
|
|
128
|
+
try {
|
|
129
|
+
const auth = req.auth;
|
|
130
|
+
const { projectId, seqId } = req.params;
|
|
131
|
+
const project = await Project.findOne({ _id: projectId });
|
|
132
|
+
if (!project)
|
|
133
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
134
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
135
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
136
|
+
const db = getDb();
|
|
137
|
+
if (!db)
|
|
138
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
139
|
+
const shots = await db.collection('production_shots')
|
|
140
|
+
.find({ sequenceId: seqId, projectId })
|
|
141
|
+
.sort({ order: 1, createdAt: 1 })
|
|
142
|
+
.toArray();
|
|
143
|
+
res.json({ shots });
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
res.status(500).json({ message: err.message });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
router.post('/projects/:projectId/production/sequences/:seqId/shots', async (req, res) => {
|
|
150
|
+
try {
|
|
151
|
+
const auth = req.auth;
|
|
152
|
+
const { projectId, seqId } = req.params;
|
|
153
|
+
const { name, description, status, assignedTo, assetId, exportId, dueDate, order } = req.body;
|
|
154
|
+
if (!name)
|
|
155
|
+
return res.status(400).json({ message: 'name is required' });
|
|
156
|
+
const project = await Project.findOne({ _id: projectId });
|
|
157
|
+
if (!project)
|
|
158
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
159
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
160
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
161
|
+
const db = getDb();
|
|
162
|
+
if (!db)
|
|
163
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
164
|
+
const now = new Date();
|
|
165
|
+
const shot = {
|
|
166
|
+
id: crypto_1.default.randomUUID(),
|
|
167
|
+
sequenceId: seqId,
|
|
168
|
+
projectId,
|
|
169
|
+
name,
|
|
170
|
+
description: description ?? null,
|
|
171
|
+
order: order ?? 0,
|
|
172
|
+
status: status ?? 'todo',
|
|
173
|
+
assignedTo: assignedTo ?? [],
|
|
174
|
+
assetId: assetId ?? null,
|
|
175
|
+
exportId: exportId ?? null,
|
|
176
|
+
dueDate: dueDate ? new Date(dueDate) : null,
|
|
177
|
+
createdBy: auth.userId,
|
|
178
|
+
createdAt: now,
|
|
179
|
+
updatedAt: now,
|
|
180
|
+
};
|
|
181
|
+
await db.collection('production_shots').insertOne({ ...shot });
|
|
182
|
+
res.status(201).json({ shot });
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
res.status(500).json({ message: err.message });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
router.patch('/projects/:projectId/production/shots/:shotId', async (req, res) => {
|
|
189
|
+
try {
|
|
190
|
+
const auth = req.auth;
|
|
191
|
+
const { projectId, shotId } = req.params;
|
|
192
|
+
const project = await Project.findOne({ _id: projectId });
|
|
193
|
+
if (!project)
|
|
194
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
195
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
196
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
197
|
+
const db = getDb();
|
|
198
|
+
if (!db)
|
|
199
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
200
|
+
const { name, description, status, assignedTo, assetId, exportId, dueDate, order } = req.body;
|
|
201
|
+
const $set = { updatedAt: new Date() };
|
|
202
|
+
if (name !== undefined)
|
|
203
|
+
$set.name = name;
|
|
204
|
+
if (description !== undefined)
|
|
205
|
+
$set.description = description;
|
|
206
|
+
if (status !== undefined)
|
|
207
|
+
$set.status = status;
|
|
208
|
+
if (assignedTo !== undefined)
|
|
209
|
+
$set.assignedTo = assignedTo;
|
|
210
|
+
if (assetId !== undefined)
|
|
211
|
+
$set.assetId = assetId;
|
|
212
|
+
if (exportId !== undefined)
|
|
213
|
+
$set.exportId = exportId;
|
|
214
|
+
if (dueDate !== undefined)
|
|
215
|
+
$set.dueDate = dueDate ? new Date(dueDate) : null;
|
|
216
|
+
if (order !== undefined)
|
|
217
|
+
$set.order = order;
|
|
218
|
+
const result = await db.collection('production_shots').findOneAndUpdate({ id: shotId, projectId }, { $set }, { returnDocument: 'after' });
|
|
219
|
+
if (!result)
|
|
220
|
+
return res.status(404).json({ message: 'Shot not found' });
|
|
221
|
+
res.json({ shot: result });
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
res.status(500).json({ message: err.message });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
router.delete('/projects/:projectId/production/shots/:shotId', async (req, res) => {
|
|
228
|
+
try {
|
|
229
|
+
const auth = req.auth;
|
|
230
|
+
const { projectId, shotId } = req.params;
|
|
231
|
+
const project = await Project.findOne({ _id: projectId });
|
|
232
|
+
if (!project)
|
|
233
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
234
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
235
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
236
|
+
const db = getDb();
|
|
237
|
+
if (!db)
|
|
238
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
239
|
+
await db.collection('production_shots').deleteOne({ id: shotId, projectId });
|
|
240
|
+
await db.collection('production_tasks').deleteMany({ shotId, projectId });
|
|
241
|
+
res.json({ message: 'Shot deleted' });
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
res.status(500).json({ message: err.message });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
// ── Tasks ──────────────────────────────────────────────────────────────────
|
|
248
|
+
router.get('/projects/:projectId/production/shots/:shotId/tasks', async (req, res) => {
|
|
249
|
+
try {
|
|
250
|
+
const auth = req.auth;
|
|
251
|
+
const { projectId, shotId } = req.params;
|
|
252
|
+
const project = await Project.findOne({ _id: projectId });
|
|
253
|
+
if (!project)
|
|
254
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
255
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
256
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
257
|
+
const db = getDb();
|
|
258
|
+
if (!db)
|
|
259
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
260
|
+
const tasks = await db.collection('production_tasks')
|
|
261
|
+
.find({ shotId, projectId })
|
|
262
|
+
.sort({ createdAt: 1 })
|
|
263
|
+
.toArray();
|
|
264
|
+
res.json({ tasks });
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
res.status(500).json({ message: err.message });
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
router.post('/projects/:projectId/production/shots/:shotId/tasks', async (req, res) => {
|
|
271
|
+
try {
|
|
272
|
+
const auth = req.auth;
|
|
273
|
+
const { projectId, shotId } = req.params;
|
|
274
|
+
const { name, status, priority, assignedTo, dueDate, notes } = req.body;
|
|
275
|
+
if (!name)
|
|
276
|
+
return res.status(400).json({ message: 'name is required' });
|
|
277
|
+
const project = await Project.findOne({ _id: projectId });
|
|
278
|
+
if (!project)
|
|
279
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
280
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
281
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
282
|
+
const db = getDb();
|
|
283
|
+
if (!db)
|
|
284
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
285
|
+
const now = new Date();
|
|
286
|
+
const task = {
|
|
287
|
+
id: crypto_1.default.randomUUID(),
|
|
288
|
+
shotId,
|
|
289
|
+
projectId,
|
|
290
|
+
name,
|
|
291
|
+
status: status ?? 'todo',
|
|
292
|
+
priority: priority ?? 'medium',
|
|
293
|
+
assignedTo: assignedTo ?? null,
|
|
294
|
+
dueDate: dueDate ? new Date(dueDate) : null,
|
|
295
|
+
notes: notes ?? null,
|
|
296
|
+
createdBy: auth.userId,
|
|
297
|
+
createdAt: now,
|
|
298
|
+
updatedAt: now,
|
|
299
|
+
};
|
|
300
|
+
await db.collection('production_tasks').insertOne({ ...task });
|
|
301
|
+
res.status(201).json({ task });
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
res.status(500).json({ message: err.message });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
router.patch('/projects/:projectId/production/shots/:shotId/tasks/:taskId', async (req, res) => {
|
|
308
|
+
try {
|
|
309
|
+
const auth = req.auth;
|
|
310
|
+
const { projectId, shotId, taskId } = req.params;
|
|
311
|
+
const project = await Project.findOne({ _id: projectId });
|
|
312
|
+
if (!project)
|
|
313
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
314
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
315
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
316
|
+
const db = getDb();
|
|
317
|
+
if (!db)
|
|
318
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
319
|
+
const { name, status, priority, assignedTo, dueDate, notes } = req.body;
|
|
320
|
+
const $set = { updatedAt: new Date() };
|
|
321
|
+
if (name !== undefined)
|
|
322
|
+
$set.name = name;
|
|
323
|
+
if (status !== undefined)
|
|
324
|
+
$set.status = status;
|
|
325
|
+
if (priority !== undefined)
|
|
326
|
+
$set.priority = priority;
|
|
327
|
+
if (assignedTo !== undefined)
|
|
328
|
+
$set.assignedTo = assignedTo;
|
|
329
|
+
if (dueDate !== undefined)
|
|
330
|
+
$set.dueDate = dueDate ? new Date(dueDate) : null;
|
|
331
|
+
if (notes !== undefined)
|
|
332
|
+
$set.notes = notes;
|
|
333
|
+
const result = await db.collection('production_tasks').findOneAndUpdate({ id: taskId, shotId, projectId }, { $set }, { returnDocument: 'after' });
|
|
334
|
+
if (!result)
|
|
335
|
+
return res.status(404).json({ message: 'Task not found' });
|
|
336
|
+
res.json({ task: result });
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
res.status(500).json({ message: err.message });
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
router.delete('/projects/:projectId/production/shots/:shotId/tasks/:taskId', async (req, res) => {
|
|
343
|
+
try {
|
|
344
|
+
const auth = req.auth;
|
|
345
|
+
const { projectId, shotId, taskId } = req.params;
|
|
346
|
+
const project = await Project.findOne({ _id: projectId });
|
|
347
|
+
if (!project)
|
|
348
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
349
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
350
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
351
|
+
const db = getDb();
|
|
352
|
+
if (!db)
|
|
353
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
354
|
+
await db.collection('production_tasks').deleteOne({ id: taskId, shotId, projectId });
|
|
355
|
+
res.json({ message: 'Task deleted' });
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
res.status(500).json({ message: err.message });
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
return router;
|
|
362
|
+
}
|