streamby-core 0.28.3 → 0.28.5
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.
|
@@ -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
|
|
@@ -148,6 +156,39 @@ function storageRouter(config) {
|
|
|
148
156
|
res.status(500).json({ message: 'Failed to generate upload URL', details: err.message });
|
|
149
157
|
}
|
|
150
158
|
});
|
|
159
|
+
router.get('/projects/:projectId/storage/files/:fileId/replace-url', async (req, res) => {
|
|
160
|
+
try {
|
|
161
|
+
const auth = req.auth;
|
|
162
|
+
const { projectId, fileId } = req.params;
|
|
163
|
+
const { contentType, fileName } = req.query;
|
|
164
|
+
if (!contentType || !fileName) {
|
|
165
|
+
return res.status(400).json({ message: 'Query params contentType and fileName are required' });
|
|
166
|
+
}
|
|
167
|
+
const project = await Project.findOne({ _id: projectId });
|
|
168
|
+
if (!project)
|
|
169
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
170
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
171
|
+
return res.status(403).json({ message: 'Unauthorized access' });
|
|
172
|
+
const collection = getRawFilesCollection();
|
|
173
|
+
if (!collection)
|
|
174
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
175
|
+
const fileDoc = await collection.findOne({ fileId, projectId });
|
|
176
|
+
if (!fileDoc)
|
|
177
|
+
return res.status(404).json({ message: 'File not found' });
|
|
178
|
+
if (!validateContentType(contentType, fileName, fileDoc.category)) {
|
|
179
|
+
return res.status(400).json({ message: `File does not match category ${fileDoc.category}` });
|
|
180
|
+
}
|
|
181
|
+
if (!adapter.getPresignedUploadUrl) {
|
|
182
|
+
return res.status(501).json({ message: 'Storage adapter does not support presigned uploads' });
|
|
183
|
+
}
|
|
184
|
+
const url = await adapter.getPresignedUploadUrl(fileDoc.storageKey, contentType);
|
|
185
|
+
await collection.updateOne({ fileId, projectId }, { $set: { contentType, updatedAt: new Date() } });
|
|
186
|
+
res.json({ url, storageKey: fileDoc.storageKey, fileId });
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
res.status(500).json({ message: 'Failed to generate replace URL', details: err.message });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
151
192
|
router.patch('/projects/:projectId/storage/files/:fileId', async (req, res) => {
|
|
152
193
|
try {
|
|
153
194
|
const auth = req.auth;
|
|
@@ -339,6 +380,42 @@ function storageRouter(config) {
|
|
|
339
380
|
res.status(500).json({ message: 'Failed to list files', details: err.message });
|
|
340
381
|
}
|
|
341
382
|
});
|
|
383
|
+
router.get('/projects/:projectId/connections/storage/:connId/files/:fileId/replace-url', async (req, res) => {
|
|
384
|
+
try {
|
|
385
|
+
const auth = req.auth;
|
|
386
|
+
const { projectId, connId, fileId } = req.params;
|
|
387
|
+
const { contentType, fileName } = req.query;
|
|
388
|
+
if (!contentType || !fileName) {
|
|
389
|
+
return res.status(400).json({ message: 'Query params contentType and fileName are required' });
|
|
390
|
+
}
|
|
391
|
+
const project = await Project.findOne({ _id: projectId });
|
|
392
|
+
if (!project)
|
|
393
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
394
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
395
|
+
return res.status(403).json({ message: 'Unauthorized access' });
|
|
396
|
+
const collection = getRawFilesCollection();
|
|
397
|
+
if (!collection)
|
|
398
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
399
|
+
const fileDoc = await collection.findOne(connFilter(projectId, connId, { fileId }));
|
|
400
|
+
if (!fileDoc)
|
|
401
|
+
return res.status(404).json({ message: 'File not found' });
|
|
402
|
+
if (!validateContentType(contentType, fileName, fileDoc.category)) {
|
|
403
|
+
return res.status(400).json({ message: `File does not match category ${fileDoc.category}` });
|
|
404
|
+
}
|
|
405
|
+
const connAdapter = await resolveConnAdapter(connId, project, adapter);
|
|
406
|
+
if ('error' in connAdapter)
|
|
407
|
+
return res.status(connAdapter.status).json({ message: connAdapter.error });
|
|
408
|
+
if (!connAdapter.getPresignedUploadUrl) {
|
|
409
|
+
return res.status(501).json({ message: 'Storage adapter does not support presigned uploads' });
|
|
410
|
+
}
|
|
411
|
+
const url = await connAdapter.getPresignedUploadUrl(fileDoc.storageKey, contentType);
|
|
412
|
+
await collection.updateOne(connFilter(projectId, connId, { fileId }), { $set: { contentType, updatedAt: new Date() } });
|
|
413
|
+
res.json({ url, storageKey: fileDoc.storageKey, fileId });
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
res.status(500).json({ message: 'Failed to generate replace URL', details: err.message });
|
|
417
|
+
}
|
|
418
|
+
});
|
|
342
419
|
router.patch('/projects/:projectId/connections/storage/:connId/files/:fileId', async (req, res) => {
|
|
343
420
|
try {
|
|
344
421
|
const auth = req.auth;
|
|
@@ -391,6 +468,246 @@ function storageRouter(config) {
|
|
|
391
468
|
res.status(500).json({ message: 'Failed to delete file', details: err.message });
|
|
392
469
|
}
|
|
393
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
|
+
});
|
|
394
711
|
router.get('/storages', async (_req, res) => {
|
|
395
712
|
try {
|
|
396
713
|
const STORAGE_DISPLAY = {
|
|
@@ -427,6 +744,7 @@ async function resolveFileUrl(file, adapter) {
|
|
|
427
744
|
contentType: file.contentType,
|
|
428
745
|
uploadedBy: file.uploadedBy,
|
|
429
746
|
createdAt: file.createdAt,
|
|
747
|
+
folderId: file.folderId ?? null,
|
|
430
748
|
url,
|
|
431
749
|
};
|
|
432
750
|
}
|