streamby-core 0.42.0 → 0.43.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/CHANGELOG.md +61 -0
- package/README.md +92 -4
- package/dist/adapters/database/connectionManager.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -1
- package/dist/mcp/tools.js +25 -11
- package/dist/mcp/workflowNodes.js +9 -3
- package/dist/middleware/createRouter.js +2 -0
- package/dist/middleware/routes/connection.js +5 -2
- package/dist/middleware/routes/dbConnection.js +29 -5
- package/dist/middleware/routes/document.js +11 -6
- package/dist/middleware/routes/export.d.ts +28 -0
- package/dist/middleware/routes/export.js +8 -4
- package/dist/middleware/routes/pipeline.js +5 -2
- package/dist/middleware/routes/project.js +14 -12
- package/dist/middleware/routes/projectResources.d.ts +8 -0
- package/dist/middleware/routes/projectResources.js +51 -0
- package/dist/middleware/routes/rights.js +72 -52
- package/dist/middleware/routes/storage.js +35 -2
- package/dist/middleware/routes/storageConnection.js +38 -15
- package/dist/middleware/routes/userIntegration.js +42 -1
- package/dist/middleware/routes/workflow.js +120 -37
- package/dist/models/Model.js +12 -1
- package/dist/services/apiTokenPermissions.js +2 -0
- package/dist/services/creditsSheets.d.ts +39 -0
- package/dist/services/creditsSheets.js +129 -0
- package/dist/services/dependents.d.ts +11 -0
- package/dist/services/dependents.js +81 -0
- package/dist/services/production.d.ts +1 -1
- package/dist/services/production.js +2 -2
- package/dist/services/projectIntegration.d.ts +14 -0
- package/dist/services/projectIntegration.js +32 -0
- package/dist/services/rights.d.ts +44 -0
- package/dist/services/rights.js +123 -0
- package/dist/services/workflowResources.d.ts +90 -0
- package/dist/services/workflowResources.js +288 -0
- package/dist/types/index.d.ts +6 -1
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.pipelineRouter = pipelineRouter;
|
|
7
7
|
const express_1 = require("express");
|
|
8
|
+
const workflowResources_1 = require("../../services/workflowResources");
|
|
8
9
|
const auth_1 = require("../../utils/auth");
|
|
9
10
|
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
10
11
|
const manager_1 = require("../../models/manager");
|
|
@@ -86,7 +87,8 @@ function pipelineRouter(_config) {
|
|
|
86
87
|
};
|
|
87
88
|
await db.collection('pipelines').insertOne({ ...pipeline });
|
|
88
89
|
await syncProjectRefs(projectId);
|
|
89
|
-
|
|
90
|
+
await (0, workflowResources_1.addToWorkflow)(projectId, 'pipeline', pipeline);
|
|
91
|
+
res.status(201).json({ pipeline, pipelines: (await Project.findOne({ _id: projectId }))?.pipelines ?? [] });
|
|
90
92
|
}
|
|
91
93
|
catch (err) {
|
|
92
94
|
res.status(500).json({ message: err.message });
|
|
@@ -161,7 +163,8 @@ function pipelineRouter(_config) {
|
|
|
161
163
|
return res.status(500).json({ message: 'Database not available' });
|
|
162
164
|
await db.collection('pipelines').deleteOne({ id: pipelineId, projectId });
|
|
163
165
|
await syncProjectRefs(projectId);
|
|
164
|
-
|
|
166
|
+
await (0, workflowResources_1.removeFromWorkflow)(projectId, 'pipeline', pipelineId);
|
|
167
|
+
res.json({ message: 'Pipeline deleted', pipelines: (await Project.findOne({ _id: projectId }))?.pipelines ?? [] });
|
|
165
168
|
}
|
|
166
169
|
catch (err) {
|
|
167
170
|
res.status(500).json({ message: err.message });
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.projectRouter = projectRouter;
|
|
4
4
|
const express_1 = require("express");
|
|
5
|
+
const workflowResources_1 = require("../../services/workflowResources");
|
|
5
6
|
const manager_1 = require("../../models/manager");
|
|
6
7
|
const auth_1 = require("../../utils/auth");
|
|
7
8
|
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
8
9
|
const sql_1 = require("../../adapters/database/sql");
|
|
9
10
|
const sanitize_1 = require("../../utils/sanitize");
|
|
10
11
|
const projectIntegration_1 = require("../../services/projectIntegration");
|
|
12
|
+
const projectResources_1 = require("./projectResources");
|
|
11
13
|
const auth_2 = require("../../services/auth");
|
|
12
14
|
const apiTokens_1 = require("../../services/apiTokens");
|
|
13
15
|
const nosqlDb_1 = require("../../utils/nosqlDb");
|
|
@@ -445,18 +447,11 @@ function projectRouter(config) {
|
|
|
445
447
|
return res.status(404).json({ message: 'Project not found' });
|
|
446
448
|
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
447
449
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
448
|
-
const
|
|
449
|
-
.some((c) => c.integrationId === integrationId);
|
|
450
|
-
if (alreadyConnected)
|
|
451
|
-
return res.status(400).json({ message: 'This integration is already connected to the project' });
|
|
452
|
-
const result = await (0, projectIntegration_1.classifyIntegrationId)(integrationId, auth, config, projectId);
|
|
450
|
+
const result = await (0, projectIntegration_1.connectIntegration)(project, integrationId, auth, config);
|
|
453
451
|
if ('error' in result)
|
|
454
452
|
return res.status(result.status).json({ message: result.error });
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
: result.kind === 'storage'
|
|
458
|
-
? await Project.update({ _id: projectId }, { $push: { storageConnections: result.connection } })
|
|
459
|
-
: await Project.update({ _id: projectId }, { $push: { integrationConnections: result.connection } });
|
|
453
|
+
await (0, workflowResources_1.addToWorkflow)(projectId, result.kind === 'database' ? 'db' : result.kind, result.connection);
|
|
454
|
+
const updated = await Project.findOne({ _id: projectId });
|
|
460
455
|
if (!updated)
|
|
461
456
|
return res.status(404).json({ message: 'Project not found or not updated' });
|
|
462
457
|
res.status(201).json({ data: result.connection, project: (0, sanitize_1.sanitizeProject)(updated) });
|
|
@@ -481,13 +476,20 @@ function projectRouter(config) {
|
|
|
481
476
|
const integrationConn = (project.integrationConnections ?? []).find((c) => c.integrationId === integrationId);
|
|
482
477
|
if (!dbConn && !storageConn && !integrationConn)
|
|
483
478
|
return res.status(404).json({ message: 'Integration not connected to this project' });
|
|
484
|
-
const
|
|
479
|
+
const removed = dbConn
|
|
485
480
|
? await Project.update({ _id: projectId }, { $pull: { dbConnections: { id: dbConn.id } } })
|
|
486
481
|
: storageConn
|
|
487
482
|
? await Project.update({ _id: projectId }, { $pull: { storageConnections: { id: storageConn.id } } })
|
|
488
483
|
: await Project.update({ _id: projectId }, { $pull: { integrationConnections: { id: integrationConn.id } } });
|
|
489
|
-
if (!
|
|
484
|
+
if (!removed)
|
|
490
485
|
return res.status(404).json({ message: 'Project not found or not updated' });
|
|
486
|
+
// A built-in disconnected keeps its id, to come back as it was (services/projectIntegration).
|
|
487
|
+
const builtin = [dbConn, storageConn].find(c => c?.source === 'builtin');
|
|
488
|
+
if (builtin)
|
|
489
|
+
await (0, projectResources_1.rememberDisconnectedBuiltin)(projectId, dbConn ? 'db' : 'storage', builtin);
|
|
490
|
+
const gone = dbConn ?? storageConn ?? integrationConn;
|
|
491
|
+
await (0, workflowResources_1.removeFromWorkflow)(projectId, dbConn ? 'db' : storageConn ? 'storage' : 'service', gone.id);
|
|
492
|
+
const updated = await Project.findOne({ _id: projectId });
|
|
491
493
|
res.status(200).json({ message: 'Integration removed', project: (0, sanitize_1.sanitizeProject)(updated) });
|
|
492
494
|
}
|
|
493
495
|
catch (err) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { StreamByConfig } from '../../types';
|
|
3
|
+
export declare function projectResourcesRouter(_config: StreamByConfig): Router;
|
|
4
|
+
/** A built-in disconnected: its id kept, to connect it again as it was (services/projectIntegration). */
|
|
5
|
+
export declare function rememberDisconnectedBuiltin(projectId: string, kind: 'db' | 'storage', conn: {
|
|
6
|
+
id: string;
|
|
7
|
+
integrationId?: string;
|
|
8
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projectResourcesRouter = projectResourcesRouter;
|
|
4
|
+
exports.rememberDisconnectedBuiltin = rememberDisconnectedBuiltin;
|
|
5
|
+
const express_1 = require("express");
|
|
6
|
+
const manager_1 = require("../../models/manager");
|
|
7
|
+
const auth_1 = require("../../utils/auth");
|
|
8
|
+
const dependents_1 = require("../../services/dependents");
|
|
9
|
+
// A project's resources as a whole: what depends on one, asked before removing it.
|
|
10
|
+
function projectResourcesRouter(_config) {
|
|
11
|
+
const router = (0, express_1.Router)();
|
|
12
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
13
|
+
async function projectFor(req, res, edit = false) {
|
|
14
|
+
const auth = req.auth;
|
|
15
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
16
|
+
if (!project) {
|
|
17
|
+
res.status(404).json({ message: 'Project not found' });
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if (!(edit ? auth_1.canEditProject : auth_1.isProjectMember)(project, auth.userId)) {
|
|
21
|
+
res.status(403).json({ message: 'Unauthorized project access' });
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return { project, auth };
|
|
25
|
+
}
|
|
26
|
+
router.get('/projects/:id/dependents/:kind/:resourceId', async (req, res) => {
|
|
27
|
+
try {
|
|
28
|
+
const found = await projectFor(req, res);
|
|
29
|
+
if (!found)
|
|
30
|
+
return;
|
|
31
|
+
const kind = req.params.kind;
|
|
32
|
+
if (!dependents_1.RESOURCE_KINDS.includes(kind))
|
|
33
|
+
return res.status(400).json({ message: `kind is one of: ${dependents_1.RESOURCE_KINDS.join(', ')}` });
|
|
34
|
+
if (!(0, dependents_1.findResource)(found.project, kind, req.params.resourceId))
|
|
35
|
+
return res.status(404).json({ message: 'Not found in this project' });
|
|
36
|
+
res.json({ dependents: await (0, dependents_1.dependentsOf)(found.project, kind, req.params.resourceId) });
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
res.status(500).json({ message: err.message });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return router;
|
|
43
|
+
}
|
|
44
|
+
/** A built-in disconnected: its id kept, to connect it again as it was (services/projectIntegration). */
|
|
45
|
+
async function rememberDisconnectedBuiltin(projectId, kind, conn) {
|
|
46
|
+
if (!conn.integrationId)
|
|
47
|
+
return;
|
|
48
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
49
|
+
await Project.update({ _id: projectId }, { $pull: { disconnectedBuiltins: { integrationId: conn.integrationId } } });
|
|
50
|
+
await Project.update({ _id: projectId }, { $push: { disconnectedBuiltins: { kind, integrationId: conn.integrationId, connectionId: conn.id } } });
|
|
51
|
+
}
|
|
@@ -3,74 +3,94 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.rightsRouter = rightsRouter;
|
|
4
4
|
const express_1 = require("express");
|
|
5
5
|
const auth_1 = require("../../utils/auth");
|
|
6
|
-
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
7
6
|
const manager_1 = require("../../models/manager");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return (0, connectionManager_1.getConnection)(ids[0]).client.db();
|
|
13
|
-
}
|
|
7
|
+
const nosqlDb_1 = require("../../utils/nosqlDb");
|
|
8
|
+
const rights_1 = require("../../services/rights");
|
|
9
|
+
const documents_1 = require("../../services/documents");
|
|
10
|
+
const creditsSheets_1 = require("../../services/creditsSheets");
|
|
14
11
|
function rightsRouter(_config) {
|
|
15
12
|
const router = (0, express_1.Router)();
|
|
16
13
|
const Project = (0, manager_1.getModel)('projects');
|
|
17
|
-
//
|
|
14
|
+
// The project, if the caller may see it (or, with `edit`, change it); otherwise the answer.
|
|
15
|
+
async function projectFor(req, res, edit = false) {
|
|
16
|
+
const auth = req.auth;
|
|
17
|
+
const project = await Project.findOne({ _id: req.params.projectId });
|
|
18
|
+
if (!project) {
|
|
19
|
+
res.status(404).json({ message: 'Project not found' });
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (!(edit ? auth_1.canEditProject : auth_1.isProjectMember)(project, auth.userId)) {
|
|
23
|
+
res.status(403).json({ message: 'Unauthorized' });
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return { project, auth, projectId: String(req.params.projectId) };
|
|
27
|
+
}
|
|
18
28
|
router.get('/projects/:projectId/assets/:assetId/rights', async (req, res) => {
|
|
19
29
|
try {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!
|
|
24
|
-
return res.status(404).json({ message: 'Project not found' });
|
|
25
|
-
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
26
|
-
return res.status(403).json({ message: 'Unauthorized' });
|
|
27
|
-
const db = getDb();
|
|
28
|
-
if (!db)
|
|
30
|
+
const found = await projectFor(req, res);
|
|
31
|
+
if (!found)
|
|
32
|
+
return;
|
|
33
|
+
if (!(0, nosqlDb_1.nosqlDb)())
|
|
29
34
|
return res.status(500).json({ message: 'Database not available' });
|
|
30
|
-
|
|
31
|
-
res.json({ rights: rights ?? null });
|
|
35
|
+
res.json({ rights: await (0, rights_1.getRights)(found.projectId, req.params.assetId) });
|
|
32
36
|
}
|
|
33
37
|
catch (err) {
|
|
34
38
|
res.status(500).json({ message: err.message });
|
|
35
39
|
}
|
|
36
40
|
});
|
|
37
|
-
//
|
|
41
|
+
// Sets the fields given of the asset's license metadata (who may edit the asset may).
|
|
38
42
|
router.put('/projects/:projectId/assets/:assetId/rights', async (req, res) => {
|
|
39
43
|
try {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return res.status(404).json({ message: 'Project not found' });
|
|
45
|
-
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
46
|
-
return res.status(403).json({ message: 'Unauthorized' });
|
|
47
|
-
const db = getDb();
|
|
44
|
+
const found = await projectFor(req, res, true);
|
|
45
|
+
if (!found)
|
|
46
|
+
return;
|
|
47
|
+
const db = (0, nosqlDb_1.nosqlDb)();
|
|
48
48
|
if (!db)
|
|
49
49
|
return res.status(500).json({ message: 'Database not available' });
|
|
50
|
-
const {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await
|
|
72
|
-
|
|
73
|
-
|
|
50
|
+
const { assetId } = req.params;
|
|
51
|
+
if (!(await db.collection('storage_files').findOne({ projectId: found.projectId, fileId: assetId }))) {
|
|
52
|
+
return res.status(404).json({ message: 'Asset not found' });
|
|
53
|
+
}
|
|
54
|
+
const checked = (0, rights_1.checkRights)(req.body);
|
|
55
|
+
if ('error' in checked)
|
|
56
|
+
return res.status(400).json({ message: checked.error });
|
|
57
|
+
res.json({ rights: await (0, rights_1.saveRights)(found.project, assetId, checked.values, found.auth.userId) });
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
res.status(500).json({ message: err.message });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// The project's credits sheets, each with its status (as its node shows it).
|
|
64
|
+
router.get('/projects/:projectId/credits-sheets', async (req, res) => {
|
|
65
|
+
try {
|
|
66
|
+
const found = await projectFor(req, res);
|
|
67
|
+
if (!found)
|
|
68
|
+
return;
|
|
69
|
+
const store = (0, documents_1.documentStore)();
|
|
70
|
+
const sheets = (await store.list(found.projectId)).filter(d => d.kind === 'credits');
|
|
71
|
+
const read = await Promise.all(sheets.map(async (summary) => {
|
|
72
|
+
const document = await store.get(found.projectId, summary.id);
|
|
73
|
+
if (!document)
|
|
74
|
+
return null;
|
|
75
|
+
const { rows, status } = await (0, creditsSheets_1.readSheet)(found.project, document);
|
|
76
|
+
return { id: document.id, title: document.title, status, rows: rows.length, incomplete: rows.filter(r => r.missing.length).length };
|
|
77
|
+
}));
|
|
78
|
+
res.json({ sheets: read.filter(Boolean) });
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
res.status(500).json({ message: err.message });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// A credits sheet: its rows, made now from its sources and its assets' metadata, and its status.
|
|
85
|
+
router.get('/projects/:projectId/credits-sheets/:docId', async (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
const found = await projectFor(req, res);
|
|
88
|
+
if (!found)
|
|
89
|
+
return;
|
|
90
|
+
const document = await (0, documents_1.documentStore)().get(found.projectId, req.params.docId);
|
|
91
|
+
if (!document || document.kind !== 'credits')
|
|
92
|
+
return res.status(404).json({ message: 'Credits sheet not found' });
|
|
93
|
+
res.json(await (0, creditsSheets_1.readSheet)(found.project, document));
|
|
74
94
|
}
|
|
75
95
|
catch (err) {
|
|
76
96
|
res.status(500).json({ message: err.message });
|
|
@@ -18,6 +18,8 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
18
18
|
const signedUrls_1 = require("../../services/signedUrls");
|
|
19
19
|
const assetVersions_1 = require("../../services/assetVersions");
|
|
20
20
|
const deliverables_1 = require("../../services/deliverables");
|
|
21
|
+
const rights_1 = require("../../services/rights");
|
|
22
|
+
const auth_2 = require("../../services/auth");
|
|
21
23
|
const assetCategories_1 = require("../../utils/assetCategories");
|
|
22
24
|
function getRawFilesCollection() {
|
|
23
25
|
const model = (0, manager_1.getModel)('storage_files', 'nosql');
|
|
@@ -116,6 +118,9 @@ function storageRouter(config) {
|
|
|
116
118
|
return res.status(404).json({ message: 'Project not found' });
|
|
117
119
|
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
118
120
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
121
|
+
const rights = uploadRights(req, res);
|
|
122
|
+
if (rights === false)
|
|
123
|
+
return;
|
|
119
124
|
if (!adapter.getPresignedUploadUrl) {
|
|
120
125
|
return res.status(501).json({ message: 'Storage adapter does not support presigned uploads' });
|
|
121
126
|
}
|
|
@@ -137,6 +142,8 @@ function storageRouter(config) {
|
|
|
137
142
|
};
|
|
138
143
|
await collection.insertOne(fileDoc);
|
|
139
144
|
await (0, assetVersions_1.recordFirstVersion)(fileDoc);
|
|
145
|
+
if (rights)
|
|
146
|
+
await (0, rights_1.saveRights)(project, fileId, rights, auth.userId);
|
|
140
147
|
}
|
|
141
148
|
const expiresIn = (0, signedUrls_1.signedUrlTtl)(auth, config, 'upload');
|
|
142
149
|
const url = await adapter.getPresignedUploadUrl(storageKey, contentType, expiresIn);
|
|
@@ -233,6 +240,7 @@ function storageRouter(config) {
|
|
|
233
240
|
return res.status(404).json({ message: 'File not found' });
|
|
234
241
|
await deleteAsset(fileDoc, adapter);
|
|
235
242
|
await collection.deleteOne({ fileId, projectId });
|
|
243
|
+
(0, rights_1.broadcastRights)(project, { action: 'deleted', assetIds: [fileId] });
|
|
236
244
|
res.json({ message: 'File deleted successfully' });
|
|
237
245
|
}
|
|
238
246
|
catch (err) {
|
|
@@ -350,6 +358,9 @@ function storageRouter(config) {
|
|
|
350
358
|
return res.status(404).json({ message: 'Project not found' });
|
|
351
359
|
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
352
360
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
361
|
+
const rights = uploadRights(req, res);
|
|
362
|
+
if (rights === false)
|
|
363
|
+
return;
|
|
353
364
|
const connAdapter = await resolveConnAdapter(connId, project, auth);
|
|
354
365
|
if ('error' in connAdapter)
|
|
355
366
|
return res.status(connAdapter.status).json({ message: connAdapter.error });
|
|
@@ -374,6 +385,8 @@ function storageRouter(config) {
|
|
|
374
385
|
};
|
|
375
386
|
await collection.insertOne(fileDoc);
|
|
376
387
|
await (0, assetVersions_1.recordFirstVersion)(fileDoc);
|
|
388
|
+
if (rights)
|
|
389
|
+
await (0, rights_1.saveRights)(project, fileId, rights, auth.userId);
|
|
377
390
|
}
|
|
378
391
|
const expiresIn = (0, signedUrls_1.signedUrlTtl)(auth, config, 'upload');
|
|
379
392
|
const url = await connAdapter.getPresignedUploadUrl(storageKey, contentType, expiresIn);
|
|
@@ -525,6 +538,7 @@ function storageRouter(config) {
|
|
|
525
538
|
return res.status(404).json({ message: 'File not found' });
|
|
526
539
|
await deleteAsset(fileDoc, connAdapter);
|
|
527
540
|
await collection.deleteOne(connFilter(projectId, connId, project, { fileId }));
|
|
541
|
+
(0, rights_1.broadcastRights)(project, { action: 'deleted', assetIds: [fileId] });
|
|
528
542
|
res.json({ message: 'File deleted successfully' });
|
|
529
543
|
}
|
|
530
544
|
catch (err) {
|
|
@@ -818,8 +832,26 @@ async function resolveFileUrl(file, adapter, expiresIn) {
|
|
|
818
832
|
}
|
|
819
833
|
// When a signed URL stops working, for a client to know (only when it's short-lived).
|
|
820
834
|
const expiry = (expiresIn) => expiresIn ? { expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString() } : {};
|
|
821
|
-
//
|
|
822
|
-
//
|
|
835
|
+
// The license metadata given with an upload (licenseType, sourceUrl, creatorName, creatorUrl,
|
|
836
|
+
// comments as query params), checked: null if none, false if it was refused (and answered).
|
|
837
|
+
// An API token sets it only with rights:write.
|
|
838
|
+
function uploadRights(req, res) {
|
|
839
|
+
const checked = (0, rights_1.rightsFromQuery)(req.query);
|
|
840
|
+
if (!checked)
|
|
841
|
+
return null;
|
|
842
|
+
const auth = req.auth;
|
|
843
|
+
if (auth.apiToken && !(0, auth_2.tokenAllows)(auth.apiToken, 'rights:write')) {
|
|
844
|
+
res.status(403).json({ error: "This API token doesn't have the 'rights:write' permission" });
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
if ('error' in checked) {
|
|
848
|
+
res.status(400).json({ message: checked.error });
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
return checked.values;
|
|
852
|
+
}
|
|
853
|
+
// An asset's files (every version), its versions, its deliverables and its license metadata.
|
|
854
|
+
// Its record is the caller's to delete.
|
|
823
855
|
async function deleteAsset(fileDoc, adapter) {
|
|
824
856
|
const keys = await (0, assetVersions_1.versionStorageKeys)(fileDoc.projectId, fileDoc.fileId, fileDoc.storageKey);
|
|
825
857
|
if (adapter.deleteFile)
|
|
@@ -827,4 +859,5 @@ async function deleteAsset(fileDoc, adapter) {
|
|
|
827
859
|
await adapter.deleteFile(key);
|
|
828
860
|
await (0, assetVersions_1.deleteVersions)(fileDoc.projectId, fileDoc.fileId);
|
|
829
861
|
await (0, deliverables_1.deleteDeliverablesOf)(fileDoc.projectId, { assetId: fileDoc.fileId });
|
|
862
|
+
await (0, rights_1.deleteRightsOf)(fileDoc.projectId, fileDoc.fileId);
|
|
830
863
|
}
|
|
@@ -2,16 +2,29 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.storageConnectionRouter = storageConnectionRouter;
|
|
4
4
|
const express_1 = require("express");
|
|
5
|
+
const workflowResources_1 = require("../../services/workflowResources");
|
|
5
6
|
const mongodb_1 = require("mongodb");
|
|
6
7
|
const manager_1 = require("../../models/manager");
|
|
7
8
|
const auth_1 = require("../../utils/auth");
|
|
8
9
|
const builtinAccess_1 = require("../../utils/builtinAccess");
|
|
9
10
|
const sanitize_1 = require("../../utils/sanitize");
|
|
10
11
|
const encryption_1 = require("../../utils/encryption");
|
|
12
|
+
const projectResources_1 = require("./projectResources");
|
|
13
|
+
const projectIntegration_1 = require("../../services/projectIntegration");
|
|
11
14
|
const VALID_STORAGE_TYPES = ['s3', 'gcs', 'r2', 'azure'];
|
|
12
15
|
function storageConnectionRouter(config) {
|
|
13
16
|
const router = (0, express_1.Router)();
|
|
14
17
|
const Project = (0, manager_1.getModel)('projects');
|
|
18
|
+
// The project's storage connections as they're answered, by the list and after each change.
|
|
19
|
+
// Availability is recomputed live for builtins — never trust a stored flag, per "validate on
|
|
20
|
+
// every use" (TCORE-69). BYOC (source: 'integration') is always available.
|
|
21
|
+
const listOf = (project, auth) => Promise.all((project.storageConnections || []).map(async (conn) => {
|
|
22
|
+
if (conn.source !== 'builtin')
|
|
23
|
+
return (0, sanitize_1.sanitizeConnection)(conn);
|
|
24
|
+
const available = await (0, builtinAccess_1.assertBuiltinAccess)(auth, conn.integrationId ?? conn.id, config, 'storage');
|
|
25
|
+
return (0, sanitize_1.sanitizeConnection)({ ...conn, isBuiltin: true, available });
|
|
26
|
+
}));
|
|
27
|
+
const listNow = async (projectId, auth) => listOf(await Project.findOne({ _id: projectId }), auth);
|
|
15
28
|
// ─── List storage connections ─────────────────────────────────────────────
|
|
16
29
|
router.get('/projects/:id/connections/storage', async (req, res) => {
|
|
17
30
|
try {
|
|
@@ -21,16 +34,7 @@ function storageConnectionRouter(config) {
|
|
|
21
34
|
return res.status(404).json({ message: 'Project not found' });
|
|
22
35
|
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
23
36
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
24
|
-
|
|
25
|
-
// Recompute availability live for builtins — never trust a stored flag, per
|
|
26
|
-
// "validate on every use" (TCORE-69). BYOC (source: 'integration') is always available.
|
|
27
|
-
const data = await Promise.all(connections.map(async (conn) => {
|
|
28
|
-
if (conn.source !== 'builtin')
|
|
29
|
-
return (0, sanitize_1.sanitizeConnection)(conn);
|
|
30
|
-
const available = await (0, builtinAccess_1.assertBuiltinAccess)(auth, conn.integrationId ?? conn.id, config, 'storage');
|
|
31
|
-
return (0, sanitize_1.sanitizeConnection)({ ...conn, isBuiltin: true, available });
|
|
32
|
-
}));
|
|
33
|
-
return res.status(200).json({ data });
|
|
37
|
+
return res.status(200).json({ data: await listOf(project, auth) });
|
|
34
38
|
}
|
|
35
39
|
catch (err) {
|
|
36
40
|
res.status(500).json({ message: 'Failed to fetch storage connections', details: err.message });
|
|
@@ -41,6 +45,22 @@ function storageConnectionRouter(config) {
|
|
|
41
45
|
try {
|
|
42
46
|
const auth = req.auth;
|
|
43
47
|
const { name, type, credential, integrationId, description } = req.body;
|
|
48
|
+
if (credential && integrationId) {
|
|
49
|
+
return res.status(400).json({ message: 'Provide only one of credential or integrationId' });
|
|
50
|
+
}
|
|
51
|
+
// An integration (the user's, or a built-in) is a named, typed storage already.
|
|
52
|
+
if (integrationId) {
|
|
53
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
54
|
+
if (!project)
|
|
55
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
56
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
57
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
58
|
+
const result = await (0, projectIntegration_1.connectIntegration)(project, integrationId, auth, config, { kind: 'storage', description });
|
|
59
|
+
if ('error' in result)
|
|
60
|
+
return res.status(result.status).json({ message: result.error });
|
|
61
|
+
await (0, workflowResources_1.addToWorkflow)(req.params.id, 'storage', result.connection);
|
|
62
|
+
return res.status(201).json({ data: (0, sanitize_1.sanitizeConnection)(result.connection), storageConnections: await listNow(req.params.id, auth) });
|
|
63
|
+
}
|
|
44
64
|
if (!name || !type) {
|
|
45
65
|
return res.status(400).json({ message: 'name and type are required' });
|
|
46
66
|
}
|
|
@@ -84,7 +104,8 @@ function storageConnectionRouter(config) {
|
|
|
84
104
|
...(description !== undefined && { description }),
|
|
85
105
|
};
|
|
86
106
|
await Project.update({ _id: req.params.id }, { $push: { storageConnections: connection } });
|
|
87
|
-
|
|
107
|
+
await (0, workflowResources_1.addToWorkflow)(req.params.id, 'storage', connection);
|
|
108
|
+
return res.status(201).json({ data: (0, sanitize_1.sanitizeConnection)(connection), storageConnections: await listNow(req.params.id, auth) });
|
|
88
109
|
}
|
|
89
110
|
catch (err) {
|
|
90
111
|
res.status(500).json({ message: 'Failed to add storage connection', details: err.message });
|
|
@@ -103,11 +124,13 @@ function storageConnectionRouter(config) {
|
|
|
103
124
|
const conn = project.storageConnections?.find((c) => c.id === connId);
|
|
104
125
|
if (!conn)
|
|
105
126
|
return res.status(404).json({ message: 'Storage connection not found' });
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
127
|
+
// A built-in is disconnected, not deleted: its files stay in it, and it can be connected
|
|
128
|
+
// again with them (routes/projectResources).
|
|
109
129
|
await Project.update({ _id: req.params.id }, { $pull: { storageConnections: { id: connId } } });
|
|
110
|
-
|
|
130
|
+
if (conn.source === 'builtin')
|
|
131
|
+
await (0, projectResources_1.rememberDisconnectedBuiltin)(req.params.id, 'storage', conn);
|
|
132
|
+
await (0, workflowResources_1.removeFromWorkflow)(req.params.id, 'storage', connId);
|
|
133
|
+
return res.status(200).json({ message: 'Storage connection deleted successfully', storageConnections: await listNow(req.params.id, auth) });
|
|
111
134
|
}
|
|
112
135
|
catch (err) {
|
|
113
136
|
res.status(500).json({ message: 'Failed to delete storage connection', details: err.message });
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.userIntegrationRouter = userIntegrationRouter;
|
|
4
4
|
const express_1 = require("express");
|
|
5
|
+
const workflowResources_1 = require("../../services/workflowResources");
|
|
5
6
|
const builtinAccess_1 = require("../../utils/builtinAccess");
|
|
6
7
|
const userIntegration_1 = require("../../services/userIntegration");
|
|
7
8
|
const sanitize_1 = require("../../utils/sanitize");
|
|
9
|
+
const manager_1 = require("../../models/manager");
|
|
8
10
|
const VALID_KINDS = ['database', 'storage', 'service'];
|
|
9
11
|
const VALID_DB_PROVIDERS = ['postgresql', 'mongodb'];
|
|
10
12
|
const VALID_STORAGE_PROVIDERS = ['s3', 'gcs', 'r2', 'azure'];
|
|
@@ -91,6 +93,29 @@ function userIntegrationRouter(config) {
|
|
|
91
93
|
res.status(500).json({ message: 'Failed to update integration', details: err.message });
|
|
92
94
|
}
|
|
93
95
|
});
|
|
96
|
+
// The projects' connections that use one of the user's integrations: a connection can't
|
|
97
|
+
// work without it, so deleting it disconnects them.
|
|
98
|
+
const LISTS = [['dbConnections', 'db'], ['storageConnections', 'storage'], ['integrationConnections', 'service']];
|
|
99
|
+
async function connectionsUsing(integrationId) {
|
|
100
|
+
// Only the projects using it, asked of the database (not every project, read and filtered).
|
|
101
|
+
const projects = await (0, manager_1.getModel)('projects').find({ connectedIntegration: integrationId });
|
|
102
|
+
return projects.flatMap(project => LISTS.flatMap(([list, kind]) => (project[list] ?? [])
|
|
103
|
+
.filter(c => c.integrationId === integrationId && c.source !== 'builtin')
|
|
104
|
+
.map(c => ({ projectId: String(project._id ?? project.id), projectName: project.name, kind, list, connectionId: c.id, name: c.name }))));
|
|
105
|
+
}
|
|
106
|
+
router.get('/user/integrations/:integrationId/dependents', async (req, res) => {
|
|
107
|
+
try {
|
|
108
|
+
const auth = req.auth;
|
|
109
|
+
const integration = await (0, manager_1.getModel)('user_integrations').findOne({ id: req.params.integrationId, userId: auth.userId });
|
|
110
|
+
if (!integration)
|
|
111
|
+
return res.status(404).json({ message: 'Integration not found' });
|
|
112
|
+
const found = await connectionsUsing(req.params.integrationId);
|
|
113
|
+
res.json({ dependents: found.map(({ list: _list, ...d }) => d) });
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
res.status(500).json({ message: err.message });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
94
119
|
// ─── Delete ────────────────────────────────────────────────────────────────
|
|
95
120
|
router.delete('/user/integrations/:integrationId', async (req, res) => {
|
|
96
121
|
try {
|
|
@@ -98,7 +123,23 @@ function userIntegrationRouter(config) {
|
|
|
98
123
|
const deleted = await (0, userIntegration_1.deleteUserIntegration)(auth.userId, req.params.integrationId);
|
|
99
124
|
if (!deleted)
|
|
100
125
|
return res.status(404).json({ message: 'Integration not found' });
|
|
101
|
-
|
|
126
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
127
|
+
const disconnected = await connectionsUsing(req.params.integrationId);
|
|
128
|
+
for (const c of disconnected) {
|
|
129
|
+
await Project.update({ _id: c.projectId }, { $pull: { [c.list]: { id: c.connectionId } } });
|
|
130
|
+
await (0, workflowResources_1.removeFromWorkflow)(c.projectId, c.kind, c.connectionId);
|
|
131
|
+
}
|
|
132
|
+
// Each project it was disconnected from, with its connections as they are now.
|
|
133
|
+
const affected = await Promise.all([...new Set(disconnected.map(c => c.projectId))].map(async (projectId) => {
|
|
134
|
+
const project = await Project.findOne({ _id: projectId });
|
|
135
|
+
return {
|
|
136
|
+
projectId,
|
|
137
|
+
dbConnections: (0, sanitize_1.sanitizeConnections)(project?.dbConnections),
|
|
138
|
+
storageConnections: (0, sanitize_1.sanitizeConnections)(project?.storageConnections),
|
|
139
|
+
integrationConnections: (0, sanitize_1.sanitizeConnections)(project?.integrationConnections),
|
|
140
|
+
};
|
|
141
|
+
}));
|
|
142
|
+
res.json({ message: 'Integration deleted', disconnected: disconnected.length, affected });
|
|
102
143
|
}
|
|
103
144
|
catch (err) {
|
|
104
145
|
res.status(500).json({ message: 'Failed to delete integration', details: err.message });
|