streamby-core 0.28.4 → 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.
@@ -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
+ }
@@ -38,6 +38,14 @@ function getRawFilesCollection() {
38
38
  return null;
39
39
  return (0, connectionManager_1.getConnection)(activeId).client.db().collection('storage_files');
40
40
  }
41
+ function getRawFoldersCollection() {
42
+ const model = (0, manager_1.getModel)('storage_files', 'nosql');
43
+ const connectionIds = model.getConnectionIds();
44
+ const activeId = connectionIds.find((id) => (0, connectionManager_1.getConnectedIds)().includes(id) && (0, connectionManager_1.getConnection)(id).type === 'nosql');
45
+ if (!activeId)
46
+ return null;
47
+ return (0, connectionManager_1.getConnection)(activeId).client.db().collection('storage_folders');
48
+ }
41
49
  function connFilter(projectId, connId, extra) {
42
50
  const isBuiltin = connId === 'builtin' || connId.startsWith('builtin-');
43
51
  const connMatch = isBuiltin
@@ -460,6 +468,246 @@ function storageRouter(config) {
460
468
  res.status(500).json({ message: 'Failed to delete file', details: err.message });
461
469
  }
462
470
  });
471
+ // ─── Folder routes (per-connection) ─────────────────────────────────────
472
+ router.get('/projects/:projectId/connections/storage/:connId/folders', async (req, res) => {
473
+ try {
474
+ const auth = req.auth;
475
+ const { projectId, connId } = req.params;
476
+ const { parentId } = req.query;
477
+ const project = await Project.findOne({ _id: projectId });
478
+ if (!project)
479
+ return res.status(404).json({ message: 'Project not found' });
480
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
481
+ return res.status(403).json({ message: 'Unauthorized access' });
482
+ const collection = getRawFoldersCollection();
483
+ if (!collection)
484
+ return res.status(500).json({ message: 'Database not available' });
485
+ const parentFilter = (parentId === undefined || parentId === 'null')
486
+ ? { parentId: null }
487
+ : { parentId };
488
+ const folders = await collection
489
+ .find(connFilter(projectId, connId, parentFilter))
490
+ .sort({ name: 1 })
491
+ .toArray();
492
+ res.json({
493
+ data: folders.map(f => ({
494
+ id: f.folderId,
495
+ name: f.name,
496
+ parentId: f.parentId ?? null,
497
+ projectId: f.projectId,
498
+ storageConnectionId: f.storageConnectionId,
499
+ createdBy: f.createdBy,
500
+ createdAt: f.createdAt,
501
+ })),
502
+ });
503
+ }
504
+ catch (err) {
505
+ res.status(500).json({ message: 'Failed to list folders', details: err.message });
506
+ }
507
+ });
508
+ router.get('/projects/:projectId/connections/storage/:connId/folders/:folderId', async (req, res) => {
509
+ try {
510
+ const auth = req.auth;
511
+ const { projectId, connId, folderId } = req.params;
512
+ const project = await Project.findOne({ _id: projectId });
513
+ if (!project)
514
+ return res.status(404).json({ message: 'Project not found' });
515
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
516
+ return res.status(403).json({ message: 'Unauthorized access' });
517
+ const collection = getRawFoldersCollection();
518
+ if (!collection)
519
+ return res.status(500).json({ message: 'Database not available' });
520
+ const f = await collection.findOne(connFilter(projectId, connId, { folderId }));
521
+ if (!f)
522
+ return res.status(404).json({ message: 'Folder not found' });
523
+ res.json({
524
+ folder: {
525
+ id: f.folderId,
526
+ name: f.name,
527
+ parentId: f.parentId ?? null,
528
+ projectId: f.projectId,
529
+ storageConnectionId: f.storageConnectionId,
530
+ createdBy: f.createdBy,
531
+ createdAt: f.createdAt,
532
+ },
533
+ });
534
+ }
535
+ catch (err) {
536
+ res.status(500).json({ message: 'Failed to get folder', details: err.message });
537
+ }
538
+ });
539
+ router.post('/projects/:projectId/connections/storage/:connId/folders', async (req, res) => {
540
+ try {
541
+ const auth = req.auth;
542
+ const { projectId, connId } = req.params;
543
+ const { name, parentId } = req.body;
544
+ if (!name || typeof name !== 'string' || !name.trim()) {
545
+ return res.status(400).json({ message: 'Folder name is required' });
546
+ }
547
+ const project = await Project.findOne({ _id: projectId });
548
+ if (!project)
549
+ return res.status(404).json({ message: 'Project not found' });
550
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
551
+ return res.status(403).json({ message: 'Unauthorized access' });
552
+ const collection = getRawFoldersCollection();
553
+ if (!collection)
554
+ return res.status(500).json({ message: 'Database not available' });
555
+ const folderId = crypto_1.default.randomUUID();
556
+ const isBuiltin = connId === 'builtin' || connId.startsWith('builtin-');
557
+ const folderDoc = {
558
+ _id: new mongodb_1.ObjectId(),
559
+ folderId,
560
+ projectId,
561
+ name: name.trim(),
562
+ parentId: parentId ?? null,
563
+ createdBy: auth.userId,
564
+ createdAt: new Date(),
565
+ };
566
+ if (!isBuiltin)
567
+ folderDoc.storageConnectionId = connId;
568
+ await collection.insertOne(folderDoc);
569
+ res.status(201).json({
570
+ message: 'Folder created successfully',
571
+ folder: {
572
+ id: folderId,
573
+ name: folderDoc.name,
574
+ parentId: folderDoc.parentId,
575
+ projectId,
576
+ storageConnectionId: isBuiltin ? undefined : connId,
577
+ createdBy: auth.userId,
578
+ createdAt: folderDoc.createdAt,
579
+ },
580
+ });
581
+ }
582
+ catch (err) {
583
+ res.status(500).json({ message: 'Failed to create folder', details: err.message });
584
+ }
585
+ });
586
+ router.patch('/projects/:projectId/connections/storage/:connId/folders/:folderId', async (req, res) => {
587
+ try {
588
+ const auth = req.auth;
589
+ const { projectId, connId, folderId } = req.params;
590
+ const { name } = req.body;
591
+ if (!name || typeof name !== 'string' || !name.trim()) {
592
+ return res.status(400).json({ message: 'Folder name is required' });
593
+ }
594
+ const project = await Project.findOne({ _id: projectId });
595
+ if (!project)
596
+ return res.status(404).json({ message: 'Project not found' });
597
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
598
+ return res.status(403).json({ message: 'Unauthorized access' });
599
+ const collection = getRawFoldersCollection();
600
+ if (!collection)
601
+ return res.status(500).json({ message: 'Database not available' });
602
+ const result = await collection.findOneAndUpdate(connFilter(projectId, connId, { folderId }), { $set: { name: name.trim(), updatedAt: new Date() } }, { returnDocument: 'after' });
603
+ if (!result)
604
+ return res.status(404).json({ message: 'Folder not found' });
605
+ res.json({
606
+ message: 'Folder renamed successfully',
607
+ folder: {
608
+ id: result.folderId,
609
+ name: result.name,
610
+ parentId: result.parentId ?? null,
611
+ projectId: result.projectId,
612
+ createdAt: result.createdAt,
613
+ },
614
+ });
615
+ }
616
+ catch (err) {
617
+ res.status(500).json({ message: 'Failed to rename folder', details: err.message });
618
+ }
619
+ });
620
+ router.patch('/projects/:projectId/connections/storage/:connId/folders/:folderId/move', async (req, res) => {
621
+ try {
622
+ const auth = req.auth;
623
+ const { projectId, connId, folderId } = req.params;
624
+ const { newParentId } = req.body;
625
+ if (newParentId === folderId) {
626
+ return res.status(400).json({ message: 'A folder cannot be its own parent' });
627
+ }
628
+ const project = await Project.findOne({ _id: projectId });
629
+ if (!project)
630
+ return res.status(404).json({ message: 'Project not found' });
631
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
632
+ return res.status(403).json({ message: 'Unauthorized access' });
633
+ const collection = getRawFoldersCollection();
634
+ if (!collection)
635
+ return res.status(500).json({ message: 'Database not available' });
636
+ const result = await collection.findOneAndUpdate(connFilter(projectId, connId, { folderId }), { $set: { parentId: newParentId ?? null, updatedAt: new Date() } }, { returnDocument: 'after' });
637
+ if (!result)
638
+ return res.status(404).json({ message: 'Folder not found' });
639
+ res.json({
640
+ message: 'Folder moved successfully',
641
+ folder: {
642
+ id: result.folderId,
643
+ name: result.name,
644
+ parentId: result.parentId ?? null,
645
+ projectId: result.projectId,
646
+ createdAt: result.createdAt,
647
+ },
648
+ });
649
+ }
650
+ catch (err) {
651
+ res.status(500).json({ message: 'Failed to move folder', details: err.message });
652
+ }
653
+ });
654
+ router.delete('/projects/:projectId/connections/storage/:connId/folders/:folderId', async (req, res) => {
655
+ try {
656
+ const auth = req.auth;
657
+ const { projectId, connId, folderId } = req.params;
658
+ const project = await Project.findOne({ _id: projectId });
659
+ if (!project)
660
+ return res.status(404).json({ message: 'Project not found' });
661
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
662
+ return res.status(403).json({ message: 'Unauthorized access' });
663
+ const foldersCollection = getRawFoldersCollection();
664
+ if (!foldersCollection)
665
+ return res.status(500).json({ message: 'Database not available' });
666
+ const folderDoc = await foldersCollection.findOne(connFilter(projectId, connId, { folderId }));
667
+ if (!folderDoc)
668
+ return res.status(404).json({ message: 'Folder not found' });
669
+ const filesCollection = getRawFilesCollection();
670
+ if (filesCollection) {
671
+ await filesCollection.updateMany(connFilter(projectId, connId, { folderId }), { $set: { folderId: folderDoc.parentId ?? null } });
672
+ }
673
+ await foldersCollection.updateMany(connFilter(projectId, connId, { parentId: folderId }), { $set: { parentId: folderDoc.parentId ?? null } });
674
+ await foldersCollection.deleteOne(connFilter(projectId, connId, { folderId }));
675
+ res.json({ message: 'Folder deleted successfully' });
676
+ }
677
+ catch (err) {
678
+ res.status(500).json({ message: 'Failed to delete folder', details: err.message });
679
+ }
680
+ });
681
+ router.patch('/projects/:projectId/connections/storage/:connId/files/:fileId/move', async (req, res) => {
682
+ try {
683
+ const auth = req.auth;
684
+ const { projectId, connId, fileId } = req.params;
685
+ const { folderId } = req.body;
686
+ const project = await Project.findOne({ _id: projectId });
687
+ if (!project)
688
+ return res.status(404).json({ message: 'Project not found' });
689
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
690
+ return res.status(403).json({ message: 'Unauthorized access' });
691
+ if (folderId != null) {
692
+ const foldersCollection = getRawFoldersCollection();
693
+ if (foldersCollection) {
694
+ const folderDoc = await foldersCollection.findOne(connFilter(projectId, connId, { folderId }));
695
+ if (!folderDoc)
696
+ return res.status(404).json({ message: 'Folder not found' });
697
+ }
698
+ }
699
+ const filesCollection = getRawFilesCollection();
700
+ if (!filesCollection)
701
+ return res.status(500).json({ message: 'Database not available' });
702
+ const result = await filesCollection.findOneAndUpdate(connFilter(projectId, connId, { fileId }), { $set: { folderId: folderId ?? null } }, { returnDocument: 'after' });
703
+ if (!result)
704
+ return res.status(404).json({ message: 'File not found' });
705
+ res.json({ message: 'File moved successfully', file: result });
706
+ }
707
+ catch (err) {
708
+ res.status(500).json({ message: 'Failed to move file', details: err.message });
709
+ }
710
+ });
463
711
  router.get('/storages', async (_req, res) => {
464
712
  try {
465
713
  const STORAGE_DISPLAY = {
@@ -496,6 +744,7 @@ async function resolveFileUrl(file, adapter) {
496
744
  contentType: file.contentType,
497
745
  uploadedBy: file.uploadedBy,
498
746
  createdAt: file.createdAt,
747
+ folderId: file.folderId ?? null,
499
748
  url,
500
749
  };
501
750
  }