streamby-core 0.27.0 → 0.27.2
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/dist/adapters/database/connectionManager.js +4 -0
- package/dist/adapters/database/sql.js +1 -1
- package/dist/middleware/createRouter.js +2 -0
- package/dist/middleware/routes/dbConnection.js +89 -0
- package/dist/middleware/routes/export.js +33 -11
- package/dist/middleware/routes/storage.js +179 -0
- package/dist/middleware/routes/storageConnection.d.ts +3 -0
- package/dist/middleware/routes/storageConnection.js +108 -0
- package/dist/models/Model.js +7 -4
- package/dist/services/dbConnection.d.ts +7 -0
- package/dist/services/dbConnection.js +156 -0
- package/dist/services/export.d.ts +1 -1
- package/dist/services/export.js +7 -4
- package/dist/services/pipeline.js +21 -9
- package/dist/types/index.d.ts +13 -1
- package/package.json +1 -1
|
@@ -89,6 +89,7 @@ const ensureTablesExist = async (pool) => {
|
|
|
89
89
|
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
90
90
|
"userId" VARCHAR(255) NOT NULL,
|
|
91
91
|
role VARCHAR(255) NOT NULL DEFAULT 'member',
|
|
92
|
+
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
92
93
|
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
93
94
|
"archivedBy" VARCHAR(255),
|
|
94
95
|
"archivedAt" TIMESTAMP WITH TIME ZONE,
|
|
@@ -96,6 +97,9 @@ const ensureTablesExist = async (pool) => {
|
|
|
96
97
|
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
97
98
|
UNIQUE ("projectId", "userId")
|
|
98
99
|
);
|
|
100
|
+
`);
|
|
101
|
+
await pool.query(`
|
|
102
|
+
ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
|
|
99
103
|
`);
|
|
100
104
|
console.log('✅ "project_members" table ensured to exist.');
|
|
101
105
|
// Migrate projectId FK column from UUID to TEXT if table predates this schema version
|
|
@@ -88,7 +88,7 @@ exports.sqlAdapter = {
|
|
|
88
88
|
delete: async (connection, tableName, filter, schema) => {
|
|
89
89
|
const keys = Object.keys(filter);
|
|
90
90
|
const values = Object.values(filter);
|
|
91
|
-
const where = keys.map((key, i) => `"${key}" =
|
|
91
|
+
const where = keys.map((key, i) => `"${key}" = $${i + 1}`).join(' AND ');
|
|
92
92
|
const fullTableName = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
|
|
93
93
|
const result = await connection.query(`DELETE FROM ${fullTableName} WHERE ${where}`, values);
|
|
94
94
|
return result.rowCount || 0;
|
|
@@ -21,6 +21,7 @@ const database_1 = require("./routes/database");
|
|
|
21
21
|
const credential_1 = require("./routes/credential");
|
|
22
22
|
const connection_1 = require("./routes/connection");
|
|
23
23
|
const dbConnection_1 = require("./routes/dbConnection");
|
|
24
|
+
const storageConnection_1 = require("./routes/storageConnection");
|
|
24
25
|
const notification_1 = require("./routes/notification");
|
|
25
26
|
function createStreamByRouter(config) {
|
|
26
27
|
const router = express_1.default.Router();
|
|
@@ -57,6 +58,7 @@ function createStreamByRouter(config) {
|
|
|
57
58
|
router.use((0, credential_1.credentialRouter)(config));
|
|
58
59
|
router.use((0, connection_1.connectionRouter)(config));
|
|
59
60
|
router.use((0, dbConnection_1.dbConnectionRouter)(config));
|
|
61
|
+
router.use((0, storageConnection_1.storageConnectionRouter)(config));
|
|
60
62
|
router.use((0, notification_1.notificationRouter)(config));
|
|
61
63
|
return router;
|
|
62
64
|
}
|
|
@@ -213,6 +213,35 @@ function dbConnectionRouter(config) {
|
|
|
213
213
|
res.status(500).json({ message: 'Failed to create table/collection', details: err.message });
|
|
214
214
|
}
|
|
215
215
|
});
|
|
216
|
+
// ─── Delete table / collection ────────────────────────────────────────────
|
|
217
|
+
router.delete('/projects/:id/connections/db/:connId/tables/:tableName', async (req, res) => {
|
|
218
|
+
try {
|
|
219
|
+
const auth = req.auth;
|
|
220
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
221
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
222
|
+
}
|
|
223
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
224
|
+
if (!project)
|
|
225
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
226
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
227
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
228
|
+
if (isBuiltinDb(req.params.connId, config)) {
|
|
229
|
+
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
230
|
+
if ('error' in internal)
|
|
231
|
+
return res.status(internal.status).json({ message: internal.error });
|
|
232
|
+
await (0, dbConnection_1.deleteTableOrCollectionInternal)(internal.client, internal.dbType, req.params.tableName, req.params.id);
|
|
233
|
+
return res.status(200).json({ message: 'Table/collection deleted' });
|
|
234
|
+
}
|
|
235
|
+
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
236
|
+
if ('error' in resolved)
|
|
237
|
+
return res.status(resolved.status).json({ message: resolved.error });
|
|
238
|
+
await (0, dbConnection_1.deleteTableOrCollection)(resolved.connectionString, resolved.conn.dbType, req.params.tableName);
|
|
239
|
+
return res.status(200).json({ message: 'Table/collection deleted' });
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
res.status(500).json({ message: 'Failed to delete table/collection', details: err.message });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
216
245
|
// ─── Query records ────────────────────────────────────────────────────────
|
|
217
246
|
router.get('/projects/:id/connections/db/:connId/tables/:tableName', async (req, res) => {
|
|
218
247
|
try {
|
|
@@ -274,5 +303,65 @@ function dbConnectionRouter(config) {
|
|
|
274
303
|
res.status(500).json({ message: 'Failed to insert record', details: err.message });
|
|
275
304
|
}
|
|
276
305
|
});
|
|
306
|
+
// ─── Update record ────────────────────────────────────────────────────────
|
|
307
|
+
router.put('/projects/:id/connections/db/:connId/tables/:tableName/:recordId', async (req, res) => {
|
|
308
|
+
try {
|
|
309
|
+
const auth = req.auth;
|
|
310
|
+
if (auth.role !== 'admin' && auth.role !== 'editor')
|
|
311
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
312
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
313
|
+
if (!project)
|
|
314
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
315
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
316
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
317
|
+
const updates = req.body;
|
|
318
|
+
if (!updates || typeof updates !== 'object' || Array.isArray(updates)) {
|
|
319
|
+
return res.status(400).json({ message: 'Request body must be a JSON object' });
|
|
320
|
+
}
|
|
321
|
+
if (isBuiltinDb(req.params.connId, config)) {
|
|
322
|
+
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
323
|
+
if ('error' in internal)
|
|
324
|
+
return res.status(internal.status).json({ message: internal.error });
|
|
325
|
+
const updated = await (0, dbConnection_1.updateRecordInternal)(internal.client, internal.dbType, req.params.tableName, req.params.recordId, updates, req.params.id);
|
|
326
|
+
return res.status(200).json({ data: updated });
|
|
327
|
+
}
|
|
328
|
+
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
329
|
+
if ('error' in resolved)
|
|
330
|
+
return res.status(resolved.status).json({ message: resolved.error });
|
|
331
|
+
const updated = await (0, dbConnection_1.updateRecord)(resolved.connectionString, resolved.conn.dbType, req.params.tableName, req.params.recordId, updates);
|
|
332
|
+
return res.status(200).json({ data: updated });
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
res.status(500).json({ message: 'Failed to update record', details: err.message });
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
// ─── Delete record ────────────────────────────────────────────────────────
|
|
339
|
+
router.delete('/projects/:id/connections/db/:connId/tables/:tableName/:recordId', async (req, res) => {
|
|
340
|
+
try {
|
|
341
|
+
const auth = req.auth;
|
|
342
|
+
if (auth.role !== 'admin' && auth.role !== 'editor')
|
|
343
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
344
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
345
|
+
if (!project)
|
|
346
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
347
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
348
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
349
|
+
if (isBuiltinDb(req.params.connId, config)) {
|
|
350
|
+
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
351
|
+
if ('error' in internal)
|
|
352
|
+
return res.status(internal.status).json({ message: internal.error });
|
|
353
|
+
await (0, dbConnection_1.deleteRecordInternal)(internal.client, internal.dbType, req.params.tableName, req.params.recordId, req.params.id);
|
|
354
|
+
return res.status(200).json({ message: 'Record deleted' });
|
|
355
|
+
}
|
|
356
|
+
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
357
|
+
if ('error' in resolved)
|
|
358
|
+
return res.status(resolved.status).json({ message: resolved.error });
|
|
359
|
+
await (0, dbConnection_1.deleteRecord)(resolved.connectionString, resolved.conn.dbType, req.params.tableName, req.params.recordId);
|
|
360
|
+
return res.status(200).json({ message: 'Record deleted' });
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
res.status(500).json({ message: 'Failed to delete record', details: err.message });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
277
366
|
return router;
|
|
278
367
|
}
|
|
@@ -168,8 +168,11 @@ function exportRouter(config) {
|
|
|
168
168
|
let responseData;
|
|
169
169
|
responseData = {
|
|
170
170
|
...data,
|
|
171
|
+
id: exportId,
|
|
171
172
|
allowedOrigin: exportMetadata.allowedOrigin,
|
|
172
173
|
private: exportMetadata.private,
|
|
174
|
+
devMode: exportMetadata.devMode,
|
|
175
|
+
devPorts: exportMetadata.devPorts,
|
|
173
176
|
};
|
|
174
177
|
res.json({
|
|
175
178
|
data: responseData,
|
|
@@ -215,7 +218,7 @@ function exportRouter(config) {
|
|
|
215
218
|
return res.status(401).json({ message: 'Unauthorized' });
|
|
216
219
|
}
|
|
217
220
|
const { id: projectId, export_id: exportId } = req.params;
|
|
218
|
-
const { name, description, isPrivate, allowedOrigin, useConnections, useCredentials, nodeSchema } = req.body;
|
|
221
|
+
const { name, description, isPrivate, allowedOrigin, devMode, devPorts, useConnections, useCredentials, nodeSchema } = req.body;
|
|
219
222
|
const project = await Project.findOne({ _id: projectId });
|
|
220
223
|
if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
221
224
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
@@ -228,7 +231,7 @@ function exportRouter(config) {
|
|
|
228
231
|
const resolvedNodeSchema = nodeSchema ?? exportMetadata.nodeSchema;
|
|
229
232
|
const resolvedUseConnections = useConnections ?? exportMetadata.useConnections;
|
|
230
233
|
const exportType = resolvedUseConnections ? 'externalApi' : 'json';
|
|
231
|
-
const result = await (0, export_1.updateExport)(config, projectId, exportId, description ?? exportMetadata.description, resolvedName, project.dbType, exportType, isPrivate ?? exportMetadata.private, allowedOrigin ?? exportMetadata.allowedOrigin, resolvedNodeSchema, resolvedUseConnections, useCredentials ?? exportMetadata.useCredentials);
|
|
234
|
+
const result = await (0, export_1.updateExport)(config, projectId, exportId, description ?? exportMetadata.description, resolvedName, project.dbType, exportType, isPrivate ?? exportMetadata.private, allowedOrigin ?? exportMetadata.allowedOrigin, devMode ?? exportMetadata.devMode, devPorts ?? exportMetadata.devPorts, resolvedNodeSchema, resolvedUseConnections, useCredentials ?? exportMetadata.useCredentials);
|
|
232
235
|
res.status(200).json({ data: result, message: result.message });
|
|
233
236
|
}
|
|
234
237
|
catch (err) {
|
|
@@ -264,7 +267,7 @@ function exportRouter(config) {
|
|
|
264
267
|
if (!project) {
|
|
265
268
|
return res.status(404).json({ message: 'Project not found' });
|
|
266
269
|
}
|
|
267
|
-
const exportMetadata = project.exports
|
|
270
|
+
const exportMetadata = project.exports?.find((e) => e.name?.toLowerCase().trim() === exportName.toLowerCase().trim());
|
|
268
271
|
if (!exportMetadata) {
|
|
269
272
|
return res.status(404).json({ message: 'Export not found in this project' });
|
|
270
273
|
}
|
|
@@ -284,14 +287,33 @@ function exportRouter(config) {
|
|
|
284
287
|
}
|
|
285
288
|
}
|
|
286
289
|
}
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
+
// Dev mode: auto-allow localhost requests on configured ports.
|
|
291
|
+
let devModeAllowed = false;
|
|
292
|
+
if (exportMetadata.devMode && origin) {
|
|
293
|
+
const match = origin.match(/^https?:\/\/localhost:(\d+)$/);
|
|
294
|
+
if (match) {
|
|
295
|
+
const port = parseInt(match[1]);
|
|
296
|
+
const allowedPorts = exportMetadata.devPorts?.length
|
|
297
|
+
? exportMetadata.devPorts
|
|
298
|
+
: [3000, 5173, 8080, 4200];
|
|
299
|
+
if (allowedPorts.includes(port)) {
|
|
300
|
+
devModeAllowed = true;
|
|
301
|
+
res.header('Access-Control-Allow-Origin', origin);
|
|
302
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
303
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
290
306
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
307
|
+
if (!devModeAllowed) {
|
|
308
|
+
// If there's no origin header, deny access unless the effective scope is public.
|
|
309
|
+
if (!origin && !(effectiveAllowedOrigins && effectiveAllowedOrigins.includes('*'))) {
|
|
310
|
+
return res.status(403).json({ message: 'Origin header required' });
|
|
311
|
+
}
|
|
312
|
+
// Check for public access ('*') or if the request's origin is in the effective list.
|
|
313
|
+
const isAllowed = effectiveAllowedOrigins && (effectiveAllowedOrigins.includes('*') || (origin && effectiveAllowedOrigins.includes(origin)));
|
|
314
|
+
if (!isAllowed) {
|
|
315
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
316
|
+
}
|
|
295
317
|
}
|
|
296
318
|
let data;
|
|
297
319
|
if (exportMetadata.nodeSchema) {
|
|
@@ -350,7 +372,7 @@ function exportRouter(config) {
|
|
|
350
372
|
return res.status(501).json({ message: 'SQL public exports not yet implemented' });
|
|
351
373
|
}
|
|
352
374
|
} // end legacy else block
|
|
353
|
-
if (
|
|
375
|
+
if (data === null || data === undefined) {
|
|
354
376
|
return res.status(404).json({ message: 'Export data not found' });
|
|
355
377
|
}
|
|
356
378
|
res.json(data);
|
|
@@ -8,9 +8,11 @@ const express_1 = require("express");
|
|
|
8
8
|
const file_1 = require("../../services/file");
|
|
9
9
|
const presign_1 = require("../../services/presign");
|
|
10
10
|
const storage_1 = require("../../providers/storage");
|
|
11
|
+
const s3_1 = require("../../adapters/storage/s3");
|
|
11
12
|
const manager_1 = require("../../models/manager");
|
|
12
13
|
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
13
14
|
const auth_1 = require("../../utils/auth");
|
|
15
|
+
const encryption_1 = require("../../utils/encryption");
|
|
14
16
|
const mongodb_1 = require("mongodb");
|
|
15
17
|
const crypto_1 = __importDefault(require("crypto"));
|
|
16
18
|
const VALID_CATEGORIES = ['images', 'audios', 'videos', '3d-models'];
|
|
@@ -36,6 +38,32 @@ function getRawFilesCollection() {
|
|
|
36
38
|
return null;
|
|
37
39
|
return (0, connectionManager_1.getConnection)(activeId).client.db().collection('storage_files');
|
|
38
40
|
}
|
|
41
|
+
function connFilter(projectId, connId, extra) {
|
|
42
|
+
const isBuiltin = connId === 'builtin' || connId.startsWith('builtin-');
|
|
43
|
+
const connMatch = isBuiltin
|
|
44
|
+
? { $or: [{ storageConnectionId: connId }, { storageConnectionId: { $exists: false } }] }
|
|
45
|
+
: { storageConnectionId: connId };
|
|
46
|
+
return { projectId, ...connMatch, ...(extra ?? {}) };
|
|
47
|
+
}
|
|
48
|
+
async function resolveConnAdapter(connId, project, globalAdapter) {
|
|
49
|
+
if (connId === 'builtin' || connId.startsWith('builtin-'))
|
|
50
|
+
return globalAdapter;
|
|
51
|
+
const conn = project.storageConnections?.find((c) => c.id === connId);
|
|
52
|
+
if (!conn)
|
|
53
|
+
return { error: 'Storage connection not found', status: 404 };
|
|
54
|
+
if (!(0, encryption_1.isEncryptionKeySet)())
|
|
55
|
+
return { error: 'Encryption key not set', status: 500 };
|
|
56
|
+
const credential = project.credentials?.find((c) => c.id === conn.credentialId);
|
|
57
|
+
if (!credential)
|
|
58
|
+
return { error: 'Credential not found in project', status: 400 };
|
|
59
|
+
try {
|
|
60
|
+
const s3Config = JSON.parse((0, encryption_1.decrypt)(credential.encryptedValue));
|
|
61
|
+
return new s3_1.S3Adapter(s3Config);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
return { error: `Failed to initialize storage adapter: ${e.message}`, status: 500 };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
39
67
|
function storageRouter(config) {
|
|
40
68
|
const router = (0, express_1.Router)();
|
|
41
69
|
const adapter = config.adapter || (0, storage_1.createStorageProvider)(config.storageProviders);
|
|
@@ -212,6 +240,157 @@ function storageRouter(config) {
|
|
|
212
240
|
res.status(500).json({ message: 'Failed to list files', details: err.message });
|
|
213
241
|
}
|
|
214
242
|
});
|
|
243
|
+
// ─── Per-connection file routes ───────────────────────────────────────────
|
|
244
|
+
router.get('/projects/:projectId/connections/storage/:connId/upload-url', async (req, res) => {
|
|
245
|
+
try {
|
|
246
|
+
const auth = req.auth;
|
|
247
|
+
const { projectId, connId } = req.params;
|
|
248
|
+
const { fileName, contentType, category } = req.query;
|
|
249
|
+
if (!category || !VALID_CATEGORIES.includes(category)) {
|
|
250
|
+
return res.status(400).json({ message: `Invalid category. Must be one of: ${VALID_CATEGORIES.join(', ')}` });
|
|
251
|
+
}
|
|
252
|
+
if (!fileName || !contentType) {
|
|
253
|
+
return res.status(400).json({ message: 'Query params fileName and contentType are required' });
|
|
254
|
+
}
|
|
255
|
+
if (!validateContentType(contentType, fileName, category)) {
|
|
256
|
+
return res.status(400).json({ message: `File does not match category ${category}` });
|
|
257
|
+
}
|
|
258
|
+
const project = await Project.findOne({ _id: projectId });
|
|
259
|
+
if (!project)
|
|
260
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
261
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
262
|
+
return res.status(403).json({ message: 'Unauthorized access' });
|
|
263
|
+
const connAdapter = await resolveConnAdapter(connId, project, adapter);
|
|
264
|
+
if ('error' in connAdapter)
|
|
265
|
+
return res.status(connAdapter.status).json({ message: connAdapter.error });
|
|
266
|
+
if (!connAdapter.getPresignedUploadUrl)
|
|
267
|
+
return res.status(501).json({ message: 'Storage adapter does not support presigned uploads' });
|
|
268
|
+
const ext = fileName.includes('.') ? `.${fileName.split('.').pop().toLowerCase()}` : '';
|
|
269
|
+
const fileId = crypto_1.default.randomUUID();
|
|
270
|
+
const storageKey = `${projectId}/${category}/${fileId}${ext}`;
|
|
271
|
+
const collection = getRawFilesCollection();
|
|
272
|
+
if (collection) {
|
|
273
|
+
await collection.insertOne({
|
|
274
|
+
_id: new mongodb_1.ObjectId(),
|
|
275
|
+
fileId,
|
|
276
|
+
projectId,
|
|
277
|
+
storageConnectionId: connId,
|
|
278
|
+
category,
|
|
279
|
+
storageKey,
|
|
280
|
+
displayName: fileName,
|
|
281
|
+
contentType,
|
|
282
|
+
uploadedBy: auth.userId,
|
|
283
|
+
createdAt: new Date(),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const url = await connAdapter.getPresignedUploadUrl(storageKey, contentType);
|
|
287
|
+
res.json({ url, fileId, storageKey, displayName: fileName });
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
res.status(500).json({ message: 'Failed to generate upload URL', details: err.message });
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
router.get('/projects/:projectId/connections/storage/:connId/files/lasts', async (req, res) => {
|
|
294
|
+
try {
|
|
295
|
+
const auth = req.auth;
|
|
296
|
+
const { projectId, connId } = req.params;
|
|
297
|
+
const project = await Project.findOne({ _id: projectId });
|
|
298
|
+
if (!project)
|
|
299
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
300
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
301
|
+
return res.status(403).json({ message: 'Unauthorized access' });
|
|
302
|
+
const connAdapter = await resolveConnAdapter(connId, project, adapter);
|
|
303
|
+
if ('error' in connAdapter)
|
|
304
|
+
return res.status(connAdapter.status).json({ message: connAdapter.error });
|
|
305
|
+
const collection = getRawFilesCollection();
|
|
306
|
+
if (!collection)
|
|
307
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
308
|
+
const files = await collection.find(connFilter(projectId, connId)).sort({ createdAt: -1 }).limit(12).toArray();
|
|
309
|
+
const data = await Promise.all(files.map(f => resolveFileUrl(f, connAdapter)));
|
|
310
|
+
res.json({ data });
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
res.status(500).json({ message: 'Failed to fetch latest files', details: err.message });
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
router.get('/projects/:projectId/connections/storage/:connId/files/:category', async (req, res) => {
|
|
317
|
+
try {
|
|
318
|
+
const auth = req.auth;
|
|
319
|
+
const { projectId, connId, category } = req.params;
|
|
320
|
+
if (!VALID_CATEGORIES.includes(category)) {
|
|
321
|
+
return res.status(400).json({ message: `Invalid category. Must be one of: ${VALID_CATEGORIES.join(', ')}` });
|
|
322
|
+
}
|
|
323
|
+
const project = await Project.findOne({ _id: projectId });
|
|
324
|
+
if (!project)
|
|
325
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
326
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
327
|
+
return res.status(403).json({ message: 'Unauthorized access' });
|
|
328
|
+
const connAdapter = await resolveConnAdapter(connId, project, adapter);
|
|
329
|
+
if ('error' in connAdapter)
|
|
330
|
+
return res.status(connAdapter.status).json({ message: connAdapter.error });
|
|
331
|
+
const collection = getRawFilesCollection();
|
|
332
|
+
if (!collection)
|
|
333
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
334
|
+
const files = await collection.find(connFilter(projectId, connId, { category })).sort({ createdAt: -1 }).toArray();
|
|
335
|
+
const data = await Promise.all(files.map(f => resolveFileUrl(f, connAdapter)));
|
|
336
|
+
res.json({ data });
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
res.status(500).json({ message: 'Failed to list files', details: err.message });
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
router.patch('/projects/:projectId/connections/storage/:connId/files/:fileId', async (req, res) => {
|
|
343
|
+
try {
|
|
344
|
+
const auth = req.auth;
|
|
345
|
+
const { projectId, connId, fileId } = req.params;
|
|
346
|
+
const { displayName } = req.body;
|
|
347
|
+
if (!displayName || typeof displayName !== 'string') {
|
|
348
|
+
return res.status(400).json({ message: 'displayName is required' });
|
|
349
|
+
}
|
|
350
|
+
const project = await Project.findOne({ _id: projectId });
|
|
351
|
+
if (!project)
|
|
352
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
353
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
354
|
+
return res.status(403).json({ message: 'Unauthorized access' });
|
|
355
|
+
const collection = getRawFilesCollection();
|
|
356
|
+
if (!collection)
|
|
357
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
358
|
+
const result = await collection.findOneAndUpdate(connFilter(projectId, connId, { fileId }), { $set: { displayName } }, { returnDocument: 'after' });
|
|
359
|
+
if (!result)
|
|
360
|
+
return res.status(404).json({ message: 'File not found' });
|
|
361
|
+
res.json({ message: 'File renamed successfully', file: result });
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
res.status(500).json({ message: 'Failed to rename file', details: err.message });
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
router.delete('/projects/:projectId/connections/storage/:connId/files/:fileId', async (req, res) => {
|
|
368
|
+
try {
|
|
369
|
+
const auth = req.auth;
|
|
370
|
+
const { projectId, connId, fileId } = req.params;
|
|
371
|
+
const project = await Project.findOne({ _id: projectId });
|
|
372
|
+
if (!project)
|
|
373
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
374
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
375
|
+
return res.status(403).json({ message: 'Unauthorized access' });
|
|
376
|
+
const connAdapter = await resolveConnAdapter(connId, project, adapter);
|
|
377
|
+
if ('error' in connAdapter)
|
|
378
|
+
return res.status(connAdapter.status).json({ message: connAdapter.error });
|
|
379
|
+
const collection = getRawFilesCollection();
|
|
380
|
+
if (!collection)
|
|
381
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
382
|
+
const fileDoc = await collection.findOne(connFilter(projectId, connId, { fileId }));
|
|
383
|
+
if (!fileDoc)
|
|
384
|
+
return res.status(404).json({ message: 'File not found' });
|
|
385
|
+
if (connAdapter.deleteFile)
|
|
386
|
+
await connAdapter.deleteFile(fileDoc.storageKey);
|
|
387
|
+
await collection.deleteOne(connFilter(projectId, connId, { fileId }));
|
|
388
|
+
res.json({ message: 'File deleted successfully' });
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
res.status(500).json({ message: 'Failed to delete file', details: err.message });
|
|
392
|
+
}
|
|
393
|
+
});
|
|
215
394
|
router.get('/storages', async (_req, res) => {
|
|
216
395
|
try {
|
|
217
396
|
const STORAGE_DISPLAY = {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.storageConnectionRouter = storageConnectionRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const mongodb_1 = require("mongodb");
|
|
6
|
+
const manager_1 = require("../../models/manager");
|
|
7
|
+
const auth_1 = require("../../utils/auth");
|
|
8
|
+
const VALID_STORAGE_TYPES = ['s3', 'gcs', 'r2', 'azure'];
|
|
9
|
+
const STORAGE_DISPLAY = {
|
|
10
|
+
s3: 'AWS S3',
|
|
11
|
+
gcs: 'Google Cloud Storage',
|
|
12
|
+
r2: 'Cloudflare R2',
|
|
13
|
+
azure: 'Azure Blob Storage',
|
|
14
|
+
};
|
|
15
|
+
function storageConnectionRouter(config) {
|
|
16
|
+
const router = (0, express_1.Router)();
|
|
17
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
18
|
+
// ─── List storage connections ─────────────────────────────────────────────
|
|
19
|
+
router.get('/projects/:id/connections/storage', async (req, res) => {
|
|
20
|
+
try {
|
|
21
|
+
const auth = req.auth;
|
|
22
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
23
|
+
if (!project)
|
|
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 project access' });
|
|
27
|
+
const builtinConnections = (config.storageProviders || []).map((provider, i) => ({
|
|
28
|
+
id: i === 0 ? 'builtin' : `builtin-${i}`,
|
|
29
|
+
name: STORAGE_DISPLAY[provider.type] || provider.type,
|
|
30
|
+
type: provider.type,
|
|
31
|
+
credentialId: '',
|
|
32
|
+
projectId: req.params.id,
|
|
33
|
+
createdAt: new Date(0),
|
|
34
|
+
isBuiltin: true,
|
|
35
|
+
}));
|
|
36
|
+
const projectConnections = project.storageConnections || [];
|
|
37
|
+
return res.status(200).json({ data: [...builtinConnections, ...projectConnections] });
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
res.status(500).json({ message: 'Failed to fetch storage connections', details: err.message });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// ─── Add storage connection ───────────────────────────────────────────────
|
|
44
|
+
router.post('/projects/:id/connections/storage', async (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
const auth = req.auth;
|
|
47
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
48
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
49
|
+
}
|
|
50
|
+
const { name, type, credentialId, description } = req.body;
|
|
51
|
+
if (!name || !type || !credentialId) {
|
|
52
|
+
return res.status(400).json({ message: 'name, type, and credentialId are required' });
|
|
53
|
+
}
|
|
54
|
+
if (!VALID_STORAGE_TYPES.includes(type)) {
|
|
55
|
+
return res.status(400).json({ message: `type must be one of: ${VALID_STORAGE_TYPES.join(', ')}` });
|
|
56
|
+
}
|
|
57
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
58
|
+
if (!project)
|
|
59
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
60
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
61
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
62
|
+
const credExists = project.credentials?.some((c) => c.id === credentialId);
|
|
63
|
+
if (!credExists)
|
|
64
|
+
return res.status(400).json({ message: 'Credential not found in project' });
|
|
65
|
+
const connection = {
|
|
66
|
+
id: new mongodb_1.ObjectId().toHexString(),
|
|
67
|
+
name,
|
|
68
|
+
type,
|
|
69
|
+
credentialId,
|
|
70
|
+
projectId: req.params.id,
|
|
71
|
+
createdAt: new Date(),
|
|
72
|
+
...(description !== undefined && { description }),
|
|
73
|
+
};
|
|
74
|
+
await Project.update({ _id: req.params.id }, { $push: { storageConnections: connection } });
|
|
75
|
+
return res.status(201).json({ data: connection });
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
res.status(500).json({ message: 'Failed to add storage connection', details: err.message });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// ─── Delete storage connection ────────────────────────────────────────────
|
|
82
|
+
router.delete('/projects/:id/connections/storage/:connId', async (req, res) => {
|
|
83
|
+
try {
|
|
84
|
+
const { connId } = req.params;
|
|
85
|
+
if (connId === 'builtin' || connId.startsWith('builtin-')) {
|
|
86
|
+
return res.status(403).json({ message: 'Cannot delete a built-in storage connection' });
|
|
87
|
+
}
|
|
88
|
+
const auth = req.auth;
|
|
89
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
90
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
91
|
+
}
|
|
92
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
93
|
+
if (!project)
|
|
94
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
95
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
96
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
97
|
+
const exists = project.storageConnections?.some((c) => c.id === connId);
|
|
98
|
+
if (!exists)
|
|
99
|
+
return res.status(404).json({ message: 'Storage connection not found' });
|
|
100
|
+
await Project.update({ _id: req.params.id }, { $pull: { storageConnections: { id: connId } } });
|
|
101
|
+
return res.status(200).json({ message: 'Storage connection deleted successfully' });
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
res.status(500).json({ message: 'Failed to delete storage connection', details: err.message });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return router;
|
|
108
|
+
}
|
package/dist/models/Model.js
CHANGED
|
@@ -46,7 +46,7 @@ class Model {
|
|
|
46
46
|
const userId = processedFilter.members.$elemMatch.userId;
|
|
47
47
|
const schema = this.schema || 'streamby'; // Default to 'streamby' if schema not provided
|
|
48
48
|
const query = `
|
|
49
|
-
SELECT p.*, pm."userId" as memberUserId, pm.archived as memberArchived
|
|
49
|
+
SELECT p.*, pm."userId" as memberUserId, pm.role as memberRole, pm.status as memberStatus, pm.archived as memberArchived
|
|
50
50
|
FROM "${schema}"."projects" p
|
|
51
51
|
JOIN "${schema}"."project_members" pm ON p.id = pm."projectId"
|
|
52
52
|
WHERE pm."userId" = $1
|
|
@@ -56,8 +56,9 @@ class Model {
|
|
|
56
56
|
...row,
|
|
57
57
|
members: [{
|
|
58
58
|
userId: row.memberUserId,
|
|
59
|
+
role: row.memberRole,
|
|
60
|
+
status: row.memberStatus,
|
|
59
61
|
archived: row.memberArchived,
|
|
60
|
-
// Add other member fields if necessary
|
|
61
62
|
}]
|
|
62
63
|
}));
|
|
63
64
|
}
|
|
@@ -72,7 +73,8 @@ class Model {
|
|
|
72
73
|
const projectMembers = await sql_1.sqlAdapter.find(connection, 'project_members', { projectId: transformedItem.id }, this.schema);
|
|
73
74
|
transformedItem.members = projectMembers.map((member) => ({
|
|
74
75
|
userId: member.userId,
|
|
75
|
-
role: member.role,
|
|
76
|
+
role: member.role,
|
|
77
|
+
status: member.status,
|
|
76
78
|
archived: member.archived,
|
|
77
79
|
}));
|
|
78
80
|
}
|
|
@@ -122,7 +124,8 @@ class Model {
|
|
|
122
124
|
const projectMembers = await sql_1.sqlAdapter.find(connection, 'project_members', { projectId: transformedResult.id }, this.schema);
|
|
123
125
|
transformedResult.members = projectMembers.map((member) => ({
|
|
124
126
|
userId: member.userId,
|
|
125
|
-
role: member.role,
|
|
127
|
+
role: member.role,
|
|
128
|
+
status: member.status,
|
|
126
129
|
archived: member.archived,
|
|
127
130
|
}));
|
|
128
131
|
}
|
|
@@ -5,7 +5,14 @@ export declare function listTablesOrCollections(connectionString: string, dbType
|
|
|
5
5
|
export declare function createTableOrCollection(connectionString: string, dbType: ExternalDbType, schema: CreateTableSchema): Promise<void>;
|
|
6
6
|
export declare function queryRecords(connectionString: string, dbType: ExternalDbType, tableName: string, limit?: number, offset?: number): Promise<any[]>;
|
|
7
7
|
export declare function insertRecord(connectionString: string, dbType: ExternalDbType, tableName: string, record: Record<string, unknown>): Promise<any>;
|
|
8
|
+
export declare function updateRecord(connectionString: string, dbType: ExternalDbType, tableName: string, recordId: string, updates: Record<string, unknown>): Promise<any | null>;
|
|
9
|
+
export declare function deleteRecord(connectionString: string, dbType: ExternalDbType, tableName: string, recordId: string): Promise<boolean>;
|
|
8
10
|
export declare function listTablesInternal(client: Pool | MongoClient, dbType: 'sql' | 'nosql', projectId?: string): Promise<string[]>;
|
|
9
11
|
export declare function queryRecordsInternal(client: Pool | MongoClient, dbType: 'sql' | 'nosql', tableName: string, limit?: number, offset?: number, projectId?: string): Promise<any[]>;
|
|
12
|
+
export declare function queryRecordByIdInternal(client: Pool | MongoClient, dbType: 'sql' | 'nosql', tableName: string, recordId: string, projectId?: string): Promise<any | null>;
|
|
10
13
|
export declare function createTableOrCollectionInternal(client: Pool | MongoClient, dbType: 'sql' | 'nosql', schema: CreateTableSchema, projectId?: string): Promise<void>;
|
|
11
14
|
export declare function insertRecordInternal(client: Pool | MongoClient, dbType: 'sql' | 'nosql', tableName: string, record: Record<string, unknown>, projectId?: string): Promise<any>;
|
|
15
|
+
export declare function updateRecordInternal(client: Pool | MongoClient, dbType: 'sql' | 'nosql', tableName: string, recordId: string, updates: Record<string, unknown>, projectId?: string): Promise<any | null>;
|
|
16
|
+
export declare function deleteRecordInternal(client: Pool | MongoClient, dbType: 'sql' | 'nosql', tableName: string, recordId: string, projectId?: string): Promise<boolean>;
|
|
17
|
+
export declare function deleteTableOrCollection(connectionString: string, dbType: ExternalDbType, tableName: string): Promise<void>;
|
|
18
|
+
export declare function deleteTableOrCollectionInternal(client: Pool | MongoClient, dbType: 'sql' | 'nosql', tableName: string, projectId?: string): Promise<void>;
|
|
@@ -1,13 +1,53 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.listTablesOrCollections = listTablesOrCollections;
|
|
4
37
|
exports.createTableOrCollection = createTableOrCollection;
|
|
5
38
|
exports.queryRecords = queryRecords;
|
|
6
39
|
exports.insertRecord = insertRecord;
|
|
40
|
+
exports.updateRecord = updateRecord;
|
|
41
|
+
exports.deleteRecord = deleteRecord;
|
|
7
42
|
exports.listTablesInternal = listTablesInternal;
|
|
8
43
|
exports.queryRecordsInternal = queryRecordsInternal;
|
|
44
|
+
exports.queryRecordByIdInternal = queryRecordByIdInternal;
|
|
9
45
|
exports.createTableOrCollectionInternal = createTableOrCollectionInternal;
|
|
10
46
|
exports.insertRecordInternal = insertRecordInternal;
|
|
47
|
+
exports.updateRecordInternal = updateRecordInternal;
|
|
48
|
+
exports.deleteRecordInternal = deleteRecordInternal;
|
|
49
|
+
exports.deleteTableOrCollection = deleteTableOrCollection;
|
|
50
|
+
exports.deleteTableOrCollectionInternal = deleteTableOrCollectionInternal;
|
|
11
51
|
const pg_1 = require("pg");
|
|
12
52
|
const mongodb_1 = require("mongodb");
|
|
13
53
|
// ─── Ephemeral connection helpers ─────────────────────────────────────────────
|
|
@@ -86,6 +126,50 @@ async function insertRecord(connectionString, dbType, tableName, record) {
|
|
|
86
126
|
return { ...record, _id: result.insertedId };
|
|
87
127
|
});
|
|
88
128
|
}
|
|
129
|
+
async function updateRecord(connectionString, dbType, tableName, recordId, updates) {
|
|
130
|
+
if (dbType === 'postgresql') {
|
|
131
|
+
return withPostgres(connectionString, async (client) => {
|
|
132
|
+
const keys = Object.keys(updates).filter(k => k !== 'id');
|
|
133
|
+
if (!keys.length)
|
|
134
|
+
return null;
|
|
135
|
+
const sets = keys.map((k, i) => `"${k}" = $${i + 1}`).join(', ');
|
|
136
|
+
const result = await client.query(`UPDATE "${tableName}" SET ${sets} WHERE id = $${keys.length + 1} RETURNING *`, [...keys.map(k => updates[k]), recordId]);
|
|
137
|
+
return result.rows[0] ?? null;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return withMongo(connectionString, async (db) => {
|
|
141
|
+
const { ObjectId } = await Promise.resolve().then(() => __importStar(require('mongodb')));
|
|
142
|
+
const { _id, ...fields } = updates;
|
|
143
|
+
let filter;
|
|
144
|
+
try {
|
|
145
|
+
filter = { _id: new ObjectId(recordId) };
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
filter = { _id: recordId };
|
|
149
|
+
}
|
|
150
|
+
return db.collection(tableName).findOneAndUpdate(filter, { $set: fields }, { returnDocument: 'after' });
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async function deleteRecord(connectionString, dbType, tableName, recordId) {
|
|
154
|
+
if (dbType === 'postgresql') {
|
|
155
|
+
return withPostgres(connectionString, async (client) => {
|
|
156
|
+
const result = await client.query(`DELETE FROM "${tableName}" WHERE id = $1`, [recordId]);
|
|
157
|
+
return (result.rowCount ?? 0) > 0;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return withMongo(connectionString, async (db) => {
|
|
161
|
+
const { ObjectId } = await Promise.resolve().then(() => __importStar(require('mongodb')));
|
|
162
|
+
let filter;
|
|
163
|
+
try {
|
|
164
|
+
filter = { _id: new ObjectId(recordId) };
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
filter = { _id: recordId };
|
|
168
|
+
}
|
|
169
|
+
const result = await db.collection(tableName).deleteOne(filter);
|
|
170
|
+
return result.deletedCount > 0;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
89
173
|
// ─── Internal (pool-based) variants ──────────────────────────────────────────
|
|
90
174
|
// These operate on already-connected Pool / MongoClient from connectionManager.
|
|
91
175
|
function quoteSqlTable(tableName) {
|
|
@@ -120,6 +204,23 @@ async function queryRecordsInternal(client, dbType, tableName, limit = 50, offse
|
|
|
120
204
|
}
|
|
121
205
|
return client.db().collection(fullName).find().limit(limit).skip(offset).toArray();
|
|
122
206
|
}
|
|
207
|
+
async function queryRecordByIdInternal(client, dbType, tableName, recordId, projectId) {
|
|
208
|
+
const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
|
|
209
|
+
if (dbType === 'sql') {
|
|
210
|
+
const result = await client.query(`SELECT * FROM ${quoteSqlTable(fullName)} WHERE id = $1 LIMIT 1`, [recordId]);
|
|
211
|
+
return result.rows[0] ?? null;
|
|
212
|
+
}
|
|
213
|
+
const db = client.db();
|
|
214
|
+
let doc = await db.collection(fullName).findOne({ _id: recordId });
|
|
215
|
+
if (!doc) {
|
|
216
|
+
try {
|
|
217
|
+
const { ObjectId } = await Promise.resolve().then(() => __importStar(require('mongodb')));
|
|
218
|
+
doc = await db.collection(fullName).findOne({ _id: new ObjectId(recordId) });
|
|
219
|
+
}
|
|
220
|
+
catch { /* invalid ObjectId format — not found */ }
|
|
221
|
+
}
|
|
222
|
+
return doc;
|
|
223
|
+
}
|
|
123
224
|
async function createTableOrCollectionInternal(client, dbType, schema, projectId) {
|
|
124
225
|
const fullName = projectId ? `db_${projectId}_${schema.tableName}` : schema.tableName;
|
|
125
226
|
if (dbType === 'sql') {
|
|
@@ -146,3 +247,58 @@ async function insertRecordInternal(client, dbType, tableName, record, projectId
|
|
|
146
247
|
const result = await client.db().collection(fullName).insertOne(record);
|
|
147
248
|
return { ...record, _id: result.insertedId };
|
|
148
249
|
}
|
|
250
|
+
async function updateRecordInternal(client, dbType, tableName, recordId, updates, projectId) {
|
|
251
|
+
const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
|
|
252
|
+
if (dbType === 'sql') {
|
|
253
|
+
const keys = Object.keys(updates).filter(k => k !== 'id');
|
|
254
|
+
if (!keys.length)
|
|
255
|
+
return null;
|
|
256
|
+
const sets = keys.map((k, i) => `"${k}" = $${i + 1}`).join(', ');
|
|
257
|
+
const result = await client.query(`UPDATE ${quoteSqlTable(fullName)} SET ${sets} WHERE id = $${keys.length + 1} RETURNING *`, [...keys.map(k => updates[k]), recordId]);
|
|
258
|
+
return result.rows[0] ?? null;
|
|
259
|
+
}
|
|
260
|
+
const { ObjectId } = await Promise.resolve().then(() => __importStar(require('mongodb')));
|
|
261
|
+
const { _id, ...fields } = updates;
|
|
262
|
+
let filter;
|
|
263
|
+
try {
|
|
264
|
+
filter = { _id: new ObjectId(recordId) };
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
filter = { _id: recordId };
|
|
268
|
+
}
|
|
269
|
+
return client.db().collection(fullName).findOneAndUpdate(filter, { $set: fields }, { returnDocument: 'after' });
|
|
270
|
+
}
|
|
271
|
+
async function deleteRecordInternal(client, dbType, tableName, recordId, projectId) {
|
|
272
|
+
const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
|
|
273
|
+
if (dbType === 'sql') {
|
|
274
|
+
const result = await client.query(`DELETE FROM ${quoteSqlTable(fullName)} WHERE id = $1`, [recordId]);
|
|
275
|
+
return (result.rowCount ?? 0) > 0;
|
|
276
|
+
}
|
|
277
|
+
const { ObjectId } = await Promise.resolve().then(() => __importStar(require('mongodb')));
|
|
278
|
+
let filter;
|
|
279
|
+
try {
|
|
280
|
+
filter = { _id: new ObjectId(recordId) };
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
filter = { _id: recordId };
|
|
284
|
+
}
|
|
285
|
+
const result = await client.db().collection(fullName).deleteOne(filter);
|
|
286
|
+
return result.deletedCount > 0;
|
|
287
|
+
}
|
|
288
|
+
async function deleteTableOrCollection(connectionString, dbType, tableName) {
|
|
289
|
+
if (dbType === 'postgresql') {
|
|
290
|
+
await withPostgres(connectionString, client => client.query(`DROP TABLE IF EXISTS "${tableName}"`).then(() => undefined));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
await withMongo(connectionString, async (db) => {
|
|
294
|
+
await db.dropCollection(tableName);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
async function deleteTableOrCollectionInternal(client, dbType, tableName, projectId) {
|
|
298
|
+
const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
|
|
299
|
+
if (dbType === 'sql') {
|
|
300
|
+
await client.query(`DROP TABLE IF EXISTS ${quoteSqlTable(fullName)}`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
await client.db().dropCollection(fullName);
|
|
304
|
+
}
|
|
@@ -4,7 +4,7 @@ interface ExportResult {
|
|
|
4
4
|
message: string;
|
|
5
5
|
}
|
|
6
6
|
export declare function createExport(config: StreamByConfig, projectId: string, description: string, exportName: string, dbType: DatabaseType, exportType: 'json' | 'externalApi', isPrivate?: boolean, allowedOrigin?: string[], nodeSchema?: NodeSchema, useConnections?: boolean, useCredentials?: boolean, storageDbId?: string): Promise<ExportResult>;
|
|
7
|
-
export declare function updateExport(config: StreamByConfig, projectId: string, exportId: string, description: string, exportName: string, dbType: DatabaseType, exportType: 'json' | 'externalApi', isPrivate?: boolean, allowedOrigin?: string[], nodeSchema?: NodeSchema, useConnections?: boolean, useCredentials?: boolean): Promise<ExportResult>;
|
|
7
|
+
export declare function updateExport(config: StreamByConfig, projectId: string, exportId: string, description: string, exportName: string, dbType: DatabaseType, exportType: 'json' | 'externalApi', isPrivate?: boolean, allowedOrigin?: string[], devMode?: boolean, devPorts?: number[], nodeSchema?: NodeSchema, useConnections?: boolean, useCredentials?: boolean): Promise<ExportResult>;
|
|
8
8
|
export declare function deleteExport(config: StreamByConfig, projectId: string, exportId: string, dbType: DatabaseType, exportName: string): Promise<{
|
|
9
9
|
message: string;
|
|
10
10
|
}>;
|
package/dist/services/export.js
CHANGED
|
@@ -48,10 +48,13 @@ async function createExport(config, projectId, description, exportName, dbType,
|
|
|
48
48
|
storageDbId: targetDb.id,
|
|
49
49
|
};
|
|
50
50
|
const ProjectModel = isSqlProject ? (0, manager_1.getModel)('projects', 'sql') : (0, manager_1.getModel)('projects', 'nosql');
|
|
51
|
-
await ProjectModel.update({ _id: projectId }, { $push: { exports: exportEntry } });
|
|
51
|
+
const updated = await ProjectModel.update({ _id: projectId }, { $push: { exports: exportEntry } });
|
|
52
|
+
if (!updated) {
|
|
53
|
+
throw new Error(`Failed to save export entry to project ${projectId}. The project may not exist or the database update failed.`);
|
|
54
|
+
}
|
|
52
55
|
return { exportId, message: `${exportType} export created successfully` };
|
|
53
56
|
}
|
|
54
|
-
async function updateExport(config, projectId, exportId, description, exportName, dbType, exportType, isPrivate, allowedOrigin, nodeSchema, useConnections, useCredentials) {
|
|
57
|
+
async function updateExport(config, projectId, exportId, description, exportName, dbType, exportType, isPrivate, allowedOrigin, devMode, devPorts, nodeSchema, useConnections, useCredentials) {
|
|
55
58
|
// Resolve the project to find where its raw export data lives
|
|
56
59
|
const Project = (0, manager_1.getModel)('projects');
|
|
57
60
|
const project = await Project.findOne({ _id: projectId });
|
|
@@ -78,13 +81,13 @@ async function updateExport(config, projectId, exportId, description, exportName
|
|
|
78
81
|
const SqlProject = (0, manager_1.getModel)('projects', 'sql');
|
|
79
82
|
const currentExports = project?.exports ?? [];
|
|
80
83
|
const updatedExports = currentExports.map((e) => String(e.id) === exportId
|
|
81
|
-
? { ...e, name: exportName, private: isPrivate, allowedOrigin, nodeSchema, useConnections, useCredentials, description }
|
|
84
|
+
? { ...e, name: exportName, private: isPrivate, allowedOrigin, devMode, devPorts, nodeSchema, useConnections, useCredentials, description }
|
|
82
85
|
: e);
|
|
83
86
|
await SqlProject.update({ _id: projectId }, { exports: updatedExports });
|
|
84
87
|
}
|
|
85
88
|
else {
|
|
86
89
|
const NoSQLProject = (0, manager_1.getModel)('projects', 'nosql');
|
|
87
|
-
await NoSQLProject.update({ _id: new mongodb_1.ObjectId(projectId), 'exports.id': { $in: [new mongodb_1.ObjectId(exportId), exportId] } }, { $set: { 'exports.$.name': exportName, 'exports.$.private': isPrivate, 'exports.$.allowedOrigin': allowedOrigin, 'exports.$.nodeSchema': nodeSchema, 'exports.$.useConnections': useConnections, 'exports.$.useCredentials': useCredentials, 'exports.$.description': description } });
|
|
90
|
+
await NoSQLProject.update({ _id: new mongodb_1.ObjectId(projectId), 'exports.id': { $in: [new mongodb_1.ObjectId(exportId), exportId] } }, { $set: { 'exports.$.name': exportName, 'exports.$.private': isPrivate, 'exports.$.allowedOrigin': allowedOrigin, 'exports.$.devMode': devMode, 'exports.$.devPorts': devPorts, 'exports.$.nodeSchema': nodeSchema, 'exports.$.useConnections': useConnections, 'exports.$.useCredentials': useCredentials, 'exports.$.description': description } });
|
|
88
91
|
}
|
|
89
92
|
return { exportId, message: `${exportType} export updated successfully` };
|
|
90
93
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.executePipeline = executePipeline;
|
|
4
4
|
const connectionManager_1 = require("../adapters/database/connectionManager");
|
|
5
5
|
const encryption_1 = require("../utils/encryption");
|
|
6
|
+
const dbConnection_1 = require("./dbConnection");
|
|
6
7
|
function applyFilterConfig(payload, config) {
|
|
7
8
|
const isArr = Array.isArray(payload);
|
|
8
9
|
let r = isArr ? [...payload] : payload;
|
|
@@ -85,16 +86,27 @@ async function executePipeline(nodeSchema, project, config) {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
else if (node.type === 'dataSourceNode') {
|
|
88
|
-
const
|
|
89
|
-
if (!
|
|
89
|
+
const tableName = (node.data?.tableName || node.data?.subtitle || node.data?.label);
|
|
90
|
+
if (!tableName)
|
|
90
91
|
continue;
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
const connectionId = node.data?.connectionId;
|
|
93
|
+
const recordId = node.data?.recordId;
|
|
94
|
+
const fetchData = async (client, type) => recordId
|
|
95
|
+
? (0, dbConnection_1.queryRecordByIdInternal)(client, type, tableName, recordId, project.id)
|
|
96
|
+
: (0, dbConnection_1.queryRecordsInternal)(client, type, tableName, 500, 0, project.id);
|
|
97
|
+
if (connectionId) {
|
|
98
|
+
const { client, type } = (0, connectionManager_1.getConnection)(connectionId);
|
|
99
|
+
dataResults.push(await fetchData(client, type));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// legacy fallback for nodes saved before connectionId was introduced
|
|
103
|
+
const targetDb = config.databases?.find((db) => db.type === project.dbType && db.main)
|
|
104
|
+
?? config.databases?.find((db) => db.type === project.dbType);
|
|
105
|
+
if (!targetDb)
|
|
106
|
+
throw new Error(`No database connection for type ${project.dbType}`);
|
|
107
|
+
const { client, type } = (0, connectionManager_1.getConnection)(targetDb.id);
|
|
108
|
+
dataResults.push(await fetchData(client, type));
|
|
109
|
+
}
|
|
98
110
|
}
|
|
99
111
|
else if (node.type === 'apiConnectionNode') {
|
|
100
112
|
const apiConnection = project.apiConnections?.find((c) => c.id === node.data?.connectionId);
|
package/dist/types/index.d.ts
CHANGED
|
@@ -6,7 +6,18 @@ export interface S3Config {
|
|
|
6
6
|
accessKeyId: string;
|
|
7
7
|
secretAccessKey: string;
|
|
8
8
|
}
|
|
9
|
-
export type StorageProviderType = 's3';
|
|
9
|
+
export type StorageProviderType = 's3' | 'gcs' | 'r2' | 'azure';
|
|
10
|
+
export type StorageConnectionType = 's3' | 'gcs' | 'r2' | 'azure';
|
|
11
|
+
export interface StorageConnection {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
type: StorageConnectionType;
|
|
15
|
+
credentialId: string;
|
|
16
|
+
projectId: string;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
description?: string;
|
|
19
|
+
isBuiltin?: boolean;
|
|
20
|
+
}
|
|
10
21
|
export type StorageProvider = {
|
|
11
22
|
type: StorageProviderType;
|
|
12
23
|
config: S3Config;
|
|
@@ -60,6 +71,7 @@ export interface ProjectInfo {
|
|
|
60
71
|
credentials?: Credential[];
|
|
61
72
|
apiConnections?: ApiConnection[];
|
|
62
73
|
dbConnections?: DbConnection[];
|
|
74
|
+
storageConnections?: StorageConnection[];
|
|
63
75
|
}
|
|
64
76
|
export interface Credential {
|
|
65
77
|
id: string;
|