streamby-core 0.27.0 → 0.27.1

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.
@@ -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}" = ${i + 1}`).join(' AND ');
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) {
@@ -284,14 +287,33 @@ function exportRouter(config) {
284
287
  }
285
288
  }
286
289
  }
287
- // If there's no origin header, deny access unless the effective scope is public.
288
- if (!origin && !(effectiveAllowedOrigins && effectiveAllowedOrigins.includes('*'))) {
289
- return res.status(403).json({ message: 'Origin header required' });
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
- // Check for public access ('*') or if the request's origin is in the effective list.
292
- const isAllowed = effectiveAllowedOrigins && (effectiveAllowedOrigins.includes('*') || (origin && effectiveAllowedOrigins.includes(origin)));
293
- if (!isAllowed) {
294
- return res.status(403).json({ message: 'Unauthorized' });
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) {
@@ -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,3 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig } from '../../types';
3
+ export declare function storageConnectionRouter(config: StreamByConfig): Router;
@@ -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
+ }
@@ -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, // Assuming role is also stored in project_members
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, // Assuming role is also stored in project_members
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
  }>;
@@ -51,7 +51,7 @@ async function createExport(config, projectId, description, exportName, dbType,
51
51
  await ProjectModel.update({ _id: projectId }, { $push: { exports: exportEntry } });
52
52
  return { exportId, message: `${exportType} export created successfully` };
53
53
  }
54
- async function updateExport(config, projectId, exportId, description, exportName, dbType, exportType, isPrivate, allowedOrigin, nodeSchema, useConnections, useCredentials) {
54
+ async function updateExport(config, projectId, exportId, description, exportName, dbType, exportType, isPrivate, allowedOrigin, devMode, devPorts, nodeSchema, useConnections, useCredentials) {
55
55
  // Resolve the project to find where its raw export data lives
56
56
  const Project = (0, manager_1.getModel)('projects');
57
57
  const project = await Project.findOne({ _id: projectId });
@@ -78,13 +78,13 @@ async function updateExport(config, projectId, exportId, description, exportName
78
78
  const SqlProject = (0, manager_1.getModel)('projects', 'sql');
79
79
  const currentExports = project?.exports ?? [];
80
80
  const updatedExports = currentExports.map((e) => String(e.id) === exportId
81
- ? { ...e, name: exportName, private: isPrivate, allowedOrigin, nodeSchema, useConnections, useCredentials, description }
81
+ ? { ...e, name: exportName, private: isPrivate, allowedOrigin, devMode, devPorts, nodeSchema, useConnections, useCredentials, description }
82
82
  : e);
83
83
  await SqlProject.update({ _id: projectId }, { exports: updatedExports });
84
84
  }
85
85
  else {
86
86
  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 } });
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.$.devMode': devMode, 'exports.$.devPorts': devPorts, 'exports.$.nodeSchema': nodeSchema, 'exports.$.useConnections': useConnections, 'exports.$.useCredentials': useCredentials, 'exports.$.description': description } });
88
88
  }
89
89
  return { exportId, message: `${exportType} export updated successfully` };
90
90
  }
@@ -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 collectionName = node.data?.subtitle || node.data?.label;
89
- if (!collectionName)
89
+ const tableName = (node.data?.tableName || node.data?.subtitle || node.data?.label);
90
+ if (!tableName)
90
91
  continue;
91
- const targetDb = config.databases?.find(db => db.type === project.dbType && db.main)
92
- ?? config.databases?.find(db => db.type === project.dbType);
93
- if (!targetDb)
94
- throw new Error(`No database connection for type ${project.dbType}`);
95
- const connection = (0, connectionManager_1.getConnection)(targetDb.id);
96
- const db = connection.client.db();
97
- dataResults.push(await db.collection(collectionName).find({}).toArray());
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);
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamby-core",
3
- "version": "0.27.00",
3
+ "version": "0.27.1",
4
4
  "description": "StreamBy middleware framework for media storage management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",