streamby-core 0.37.0 → 0.38.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,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deliverableRouter = deliverableRouter;
4
+ const express_1 = require("express");
5
+ const manager_1 = require("../../models/manager");
6
+ const auth_1 = require("../../utils/auth");
7
+ const nosqlDb_1 = require("../../utils/nosqlDb");
8
+ const assetVersions_1 = require("../../services/assetVersions");
9
+ const deliverables_1 = require("../../services/deliverables");
10
+ const MAX_NOTE = 1000;
11
+ // A project's deliverables (see services/deliverables): marked, listed and removed. Removing
12
+ // one never deletes its asset.
13
+ function deliverableRouter(_config) {
14
+ const router = (0, express_1.Router)();
15
+ const Project = (0, manager_1.getModel)('projects');
16
+ const exportsOf = (project) => project.exports ?? [];
17
+ // What a list shows of each: its asset's name and kind, its version's number, and the name
18
+ // of the shot or export it's for.
19
+ const describe = async (project, deliverables) => {
20
+ const db = (0, nosqlDb_1.nosqlDb)();
21
+ const assetIds = [...new Set(deliverables.map(d => d.assetId))];
22
+ const shotIds = [...new Set(deliverables.map(d => d.shotId).filter(Boolean))];
23
+ const versionIds = [...new Set(deliverables.map(d => d.versionId).filter(Boolean))];
24
+ const [files, shots, versions] = db
25
+ ? await Promise.all([
26
+ db.collection('storage_files').find({ projectId: project._id?.toString() ?? project.id, fileId: { $in: assetIds } }).toArray(),
27
+ shotIds.length ? db.collection('production_shots').find({ id: { $in: shotIds } }).toArray() : [],
28
+ versionIds.length ? db.collection('asset_versions').find({ versionId: { $in: versionIds } }).toArray() : [],
29
+ ])
30
+ : [[], [], []];
31
+ return deliverables.map(d => {
32
+ const file = files.find((f) => f.fileId === d.assetId);
33
+ const version = versions.find((v) => v.versionId === d.versionId);
34
+ return {
35
+ ...d,
36
+ asset: file ? { displayName: file.displayName, category: file.category, contentType: file.contentType ?? null } : null,
37
+ version: version ? version.version : null,
38
+ shotName: d.shotId ? shots.find((s) => s.id === d.shotId)?.name ?? null : null,
39
+ exportName: d.exportId ? exportsOf(project).find((e) => e.id?.toString() === d.exportId)?.name ?? null : null,
40
+ };
41
+ });
42
+ };
43
+ router.get('/projects/:projectId/deliverables', async (req, res) => {
44
+ try {
45
+ const auth = req.auth;
46
+ const { projectId } = req.params;
47
+ const { shotId, exportId } = req.query;
48
+ const project = await Project.findOne({ _id: projectId });
49
+ if (!project)
50
+ return res.status(404).json({ message: 'Project not found' });
51
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
52
+ return res.status(403).json({ message: 'Unauthorized' });
53
+ const deliverables = await (0, deliverables_1.listDeliverables)(projectId, { shotId, exportId });
54
+ res.json({ deliverables: await describe(project, deliverables) });
55
+ }
56
+ catch (err) {
57
+ res.status(500).json({ message: 'Failed to list deliverables', details: err.message });
58
+ }
59
+ });
60
+ router.post('/projects/:projectId/deliverables', async (req, res) => {
61
+ try {
62
+ const auth = req.auth;
63
+ const { projectId } = req.params;
64
+ const { assetId, versionId, shotId, exportId, note } = req.body ?? {};
65
+ if (typeof assetId !== 'string' || !assetId)
66
+ return res.status(400).json({ message: 'assetId is required' });
67
+ if (versionId !== undefined && versionId !== null && typeof versionId !== 'string') {
68
+ return res.status(400).json({ message: 'versionId must be a string' });
69
+ }
70
+ const forShot = typeof shotId === 'string' && shotId !== '';
71
+ const forExport = typeof exportId === 'string' && exportId !== '';
72
+ if (forShot === forExport)
73
+ return res.status(400).json({ message: 'A deliverable is for a shot (shotId) or for an export (exportId), one of them' });
74
+ if (note !== undefined && note !== null && (typeof note !== 'string' || note.length > MAX_NOTE)) {
75
+ return res.status(400).json({ message: `note must be a string of at most ${MAX_NOTE} characters` });
76
+ }
77
+ const project = await Project.findOne({ _id: projectId });
78
+ if (!project)
79
+ return res.status(404).json({ message: 'Project not found' });
80
+ if (!(0, auth_1.canEditProject)(project, auth.userId))
81
+ return res.status(403).json({ message: 'Unauthorized' });
82
+ const db = (0, nosqlDb_1.nosqlDb)();
83
+ if (!db)
84
+ return res.status(500).json({ message: 'Database not available' });
85
+ if (!(await db.collection('storage_files').findOne({ fileId: assetId, projectId }))) {
86
+ return res.status(404).json({ message: 'Asset not found' });
87
+ }
88
+ if (versionId && !(await (0, assetVersions_1.findVersion)(projectId, assetId, versionId))) {
89
+ return res.status(404).json({ message: 'Version not found' });
90
+ }
91
+ if (forShot && !(await db.collection('production_shots').findOne({ id: shotId, projectId }))) {
92
+ return res.status(404).json({ message: 'Shot not found' });
93
+ }
94
+ if (forExport && !exportsOf(project).some((e) => e.id?.toString() === exportId)) {
95
+ return res.status(404).json({ message: 'Export not found' });
96
+ }
97
+ const deliverable = await (0, deliverables_1.createDeliverable)({
98
+ projectId,
99
+ assetId,
100
+ versionId: versionId || null,
101
+ shotId: forShot ? shotId : null,
102
+ exportId: forExport ? exportId : null,
103
+ note: note || null,
104
+ createdBy: auth.userId,
105
+ });
106
+ const [described] = await describe(project, [deliverable]);
107
+ res.status(201).json({ deliverable: described });
108
+ }
109
+ catch (err) {
110
+ res.status(500).json({ message: 'Failed to mark the deliverable', details: err.message });
111
+ }
112
+ });
113
+ router.delete('/projects/:projectId/deliverables/:deliverableId', async (req, res) => {
114
+ try {
115
+ const auth = req.auth;
116
+ const { projectId, deliverableId } = req.params;
117
+ const project = await Project.findOne({ _id: projectId });
118
+ if (!project)
119
+ return res.status(404).json({ message: 'Project not found' });
120
+ if (!(0, auth_1.canEditProject)(project, auth.userId))
121
+ return res.status(403).json({ message: 'Unauthorized' });
122
+ if (!(await (0, deliverables_1.deleteDeliverable)(projectId, deliverableId)))
123
+ return res.status(404).json({ message: 'Deliverable not found' });
124
+ res.json({ message: 'Deliverable removed' });
125
+ }
126
+ catch (err) {
127
+ res.status(500).json({ message: 'Failed to remove the deliverable', details: err.message });
128
+ }
129
+ });
130
+ return router;
131
+ }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.exportRouter = exportRouter;
4
4
  const express_1 = require("express");
5
+ const deliverables_1 = require("../../services/deliverables");
5
6
  const manager_1 = require("../../models/manager");
6
7
  const auth_1 = require("../../utils/auth");
7
8
  const export_1 = require("../../services/export");
@@ -292,6 +293,7 @@ function exportRouter(config) {
292
293
  return res.status(404).json({ message: 'Export not found in this project' });
293
294
  }
294
295
  await (0, export_1.deleteExport)(config, projectId, exportId, project.dbType);
296
+ await (0, deliverables_1.deleteDeliverablesOf)(projectId, { exportId });
295
297
  res.status(200).json({ message: 'Export deleted successfully' });
296
298
  }
297
299
  catch (err) {
@@ -9,12 +9,16 @@ const auth_1 = require("../../utils/auth");
9
9
  const connectionManager_1 = require("../../adapters/database/connectionManager");
10
10
  const manager_1 = require("../../models/manager");
11
11
  const crypto_1 = __importDefault(require("crypto"));
12
+ const deliverables_1 = require("../../services/deliverables");
13
+ const productionFields_1 = require("../../services/productionFields");
12
14
  function getDb() {
13
15
  const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
14
16
  if (!ids.length)
15
17
  return null;
16
18
  return (0, connectionManager_1.getConnection)(ids[0]).client.db();
17
19
  }
20
+ const SHOT_FIELDS = ['name', 'description', 'status', 'assignees', 'assetId', 'exportId', 'dueDate', 'order'];
21
+ const TASK_FIELDS = ['name', 'status', 'priority', 'assignee', 'dueDate', 'notes'];
18
22
  function productionRouter(_config) {
19
23
  const router = (0, express_1.Router)();
20
24
  const Project = (0, manager_1.getModel)('projects');
@@ -45,14 +49,15 @@ function productionRouter(_config) {
45
49
  try {
46
50
  const auth = req.auth;
47
51
  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
52
  const project = await Project.findOne({ _id: projectId });
52
53
  if (!project)
53
54
  return res.status(404).json({ message: 'Project not found' });
54
55
  if (!(0, auth_1.canEditProject)(project, auth.userId))
55
56
  return res.status(403).json({ message: 'Unauthorized' });
57
+ const checked = (0, productionFields_1.checkProductionFields)(req.body, ['name', 'description', 'order'], project, { creating: true });
58
+ if ('error' in checked)
59
+ return res.status(400).json({ message: checked.error });
60
+ const { values } = checked;
56
61
  const db = getDb();
57
62
  if (!db)
58
63
  return res.status(500).json({ message: 'Database not available' });
@@ -60,9 +65,9 @@ function productionRouter(_config) {
60
65
  const sequence = {
61
66
  id: crypto_1.default.randomUUID(),
62
67
  projectId,
63
- name,
64
- description: description ?? null,
65
- order: order ?? 0,
68
+ name: values.name,
69
+ description: values.description ?? null,
70
+ order: values.order ?? 0,
66
71
  createdBy: auth.userId,
67
72
  createdAt: now,
68
73
  updatedAt: now,
@@ -86,14 +91,10 @@ function productionRouter(_config) {
86
91
  const db = getDb();
87
92
  if (!db)
88
93
  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;
94
+ const checked = (0, productionFields_1.checkProductionFields)(req.body, ['name', 'description', 'order'], project);
95
+ if ('error' in checked)
96
+ return res.status(400).json({ message: checked.error });
97
+ const $set = { ...checked.values, updatedAt: new Date() };
97
98
  const result = await db.collection('production_sequences').findOneAndUpdate({ id: seqId, projectId }, { $set }, { returnDocument: 'after' });
98
99
  if (!result)
99
100
  return res.status(404).json({ message: 'Sequence not found' });
@@ -115,8 +116,15 @@ function productionRouter(_config) {
115
116
  const db = getDb();
116
117
  if (!db)
117
118
  return res.status(500).json({ message: 'Database not available' });
118
- await db.collection('production_sequences').deleteOne({ id: seqId, projectId });
119
+ // With its shots, and their tasks and deliverables: nothing is left behind.
120
+ const shotIds = (await db.collection('production_shots').find({ sequenceId: seqId, projectId }).toArray()).map(s => s.id);
121
+ const { deletedCount } = await db.collection('production_sequences').deleteOne({ id: seqId, projectId });
122
+ if (!deletedCount)
123
+ return res.status(404).json({ message: 'Sequence not found' });
119
124
  await db.collection('production_shots').deleteMany({ sequenceId: seqId, projectId });
125
+ if (shotIds.length)
126
+ await db.collection('production_tasks').deleteMany({ projectId, shotId: { $in: shotIds } });
127
+ await (0, deliverables_1.deleteDeliverablesOf)(projectId, { shotIds });
120
128
  res.json({ message: 'Sequence deleted' });
121
129
  }
122
130
  catch (err) {
@@ -150,30 +158,34 @@ function productionRouter(_config) {
150
158
  try {
151
159
  const auth = req.auth;
152
160
  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
161
  const project = await Project.findOne({ _id: projectId });
157
162
  if (!project)
158
163
  return res.status(404).json({ message: 'Project not found' });
159
164
  if (!(0, auth_1.canEditProject)(project, auth.userId))
160
165
  return res.status(403).json({ message: 'Unauthorized' });
166
+ const checked = (0, productionFields_1.checkProductionFields)(req.body, SHOT_FIELDS, project, { creating: true });
167
+ if ('error' in checked)
168
+ return res.status(400).json({ message: checked.error });
169
+ const { values } = checked;
161
170
  const db = getDb();
162
171
  if (!db)
163
172
  return res.status(500).json({ message: 'Database not available' });
173
+ if (!(await db.collection('production_sequences').findOne({ id: seqId, projectId }))) {
174
+ return res.status(404).json({ message: 'Sequence not found' });
175
+ }
164
176
  const now = new Date();
165
177
  const shot = {
166
178
  id: crypto_1.default.randomUUID(),
167
179
  sequenceId: seqId,
168
180
  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,
181
+ name: values.name,
182
+ description: values.description ?? null,
183
+ order: values.order ?? 0,
184
+ status: values.status ?? 'todo',
185
+ assignedTo: values.assignedTo ?? [],
186
+ assetId: values.assetId ?? null,
187
+ exportId: values.exportId ?? null,
188
+ dueDate: values.dueDate ?? null,
177
189
  createdBy: auth.userId,
178
190
  createdAt: now,
179
191
  updatedAt: now,
@@ -197,24 +209,10 @@ function productionRouter(_config) {
197
209
  const db = getDb();
198
210
  if (!db)
199
211
  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;
212
+ const checked = (0, productionFields_1.checkProductionFields)(req.body, SHOT_FIELDS, project);
213
+ if ('error' in checked)
214
+ return res.status(400).json({ message: checked.error });
215
+ const $set = { ...checked.values, updatedAt: new Date() };
218
216
  const result = await db.collection('production_shots').findOneAndUpdate({ id: shotId, projectId }, { $set }, { returnDocument: 'after' });
219
217
  if (!result)
220
218
  return res.status(404).json({ message: 'Shot not found' });
@@ -238,6 +236,7 @@ function productionRouter(_config) {
238
236
  return res.status(500).json({ message: 'Database not available' });
239
237
  await db.collection('production_shots').deleteOne({ id: shotId, projectId });
240
238
  await db.collection('production_tasks').deleteMany({ shotId, projectId });
239
+ await (0, deliverables_1.deleteDeliverablesOf)(projectId, { shotIds: [shotId] });
241
240
  res.json({ message: 'Shot deleted' });
242
241
  }
243
242
  catch (err) {
@@ -271,28 +270,32 @@ function productionRouter(_config) {
271
270
  try {
272
271
  const auth = req.auth;
273
272
  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
273
  const project = await Project.findOne({ _id: projectId });
278
274
  if (!project)
279
275
  return res.status(404).json({ message: 'Project not found' });
280
276
  if (!(0, auth_1.canEditProject)(project, auth.userId))
281
277
  return res.status(403).json({ message: 'Unauthorized' });
278
+ const checked = (0, productionFields_1.checkProductionFields)(req.body, TASK_FIELDS, project, { creating: true });
279
+ if ('error' in checked)
280
+ return res.status(400).json({ message: checked.error });
281
+ const { values } = checked;
282
282
  const db = getDb();
283
283
  if (!db)
284
284
  return res.status(500).json({ message: 'Database not available' });
285
+ if (!(await db.collection('production_shots').findOne({ id: shotId, projectId }))) {
286
+ return res.status(404).json({ message: 'Shot not found' });
287
+ }
285
288
  const now = new Date();
286
289
  const task = {
287
290
  id: crypto_1.default.randomUUID(),
288
291
  shotId,
289
292
  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,
293
+ name: values.name,
294
+ status: values.status ?? 'todo',
295
+ priority: values.priority ?? 'medium',
296
+ assignedTo: values.assignedTo ?? null,
297
+ dueDate: values.dueDate ?? null,
298
+ notes: values.notes ?? null,
296
299
  createdBy: auth.userId,
297
300
  createdAt: now,
298
301
  updatedAt: now,
@@ -316,20 +319,10 @@ function productionRouter(_config) {
316
319
  const db = getDb();
317
320
  if (!db)
318
321
  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;
322
+ const checked = (0, productionFields_1.checkProductionFields)(req.body, TASK_FIELDS, project);
323
+ if ('error' in checked)
324
+ return res.status(400).json({ message: checked.error });
325
+ const $set = { ...checked.values, updatedAt: new Date() };
333
326
  const result = await db.collection('production_tasks').findOneAndUpdate({ id: taskId, shotId, projectId }, { $set }, { returnDocument: 'after' });
334
327
  if (!result)
335
328
  return res.status(404).json({ message: 'Task not found' });
@@ -10,6 +10,8 @@ const sanitize_1 = require("../../utils/sanitize");
10
10
  const projectIntegration_1 = require("../../services/projectIntegration");
11
11
  const auth_2 = require("../../services/auth");
12
12
  const apiTokens_1 = require("../../services/apiTokens");
13
+ const nosqlDb_1 = require("../../utils/nosqlDb");
14
+ const production_1 = require("../../services/production");
13
15
  function projectRouter(config) {
14
16
  const router = (0, express_1.Router)();
15
17
  const Project = (0, manager_1.getModel)('projects');
@@ -204,7 +206,10 @@ function projectRouter(config) {
204
206
  useCredentials: e.useCredentials,
205
207
  }));
206
208
  const sanitized = (0, sanitize_1.sanitizeProject)({ ...project, id: project._id || project.id, _id: undefined, connections: project.connections || [], exports });
207
- res.json({ project: sanitized, message: 'Project fetched successfully' });
209
+ // Whether it has production data, for its Production section to show even without a
210
+ // production node in its workflow.
211
+ const hasProductionData = !!(await (0, nosqlDb_1.nosqlDb)()?.collection('production_sequences').findOne({ projectId }));
212
+ res.json({ project: { ...sanitized, hasProductionData }, message: 'Project fetched successfully' });
208
213
  }
209
214
  catch (err) {
210
215
  res.status(500).json({ message: 'Failed to fetch project', details: err.message });
@@ -257,8 +262,9 @@ function projectRouter(config) {
257
262
  return res.status(403).json({ message: 'Only a project admin can delete it' });
258
263
  }
259
264
  await Project.useDbType(project.dbType).delete({ _id: projectId });
260
- // Its tokens have nothing left to reach.
265
+ // Its tokens have nothing left to reach, and its production data nothing to belong to.
261
266
  await (0, apiTokens_1.deleteProjectApiTokens)(String(projectId));
267
+ await (0, production_1.deleteProductionData)(String(projectId));
262
268
  const allProjects = await Project.find({});
263
269
  const projects = allProjects
264
270
  .filter(project => {