streamby-core 0.29.9 → 0.29.11

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.
@@ -81,6 +81,7 @@ const ensureTablesExist = async (pool) => {
81
81
  ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "storageConnections" JSONB DEFAULT '[]';
82
82
  ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS workflow JSONB;
83
83
  ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS pipelines JSONB DEFAULT '[]';
84
+ ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "builtinBackfilledAt" TIMESTAMP WITH TIME ZONE;
84
85
  `);
85
86
  // Ensure dbType exists with a non-null default
86
87
  await pool.query(`
@@ -40,24 +40,41 @@ const manager_1 = require("../../models/manager");
40
40
  const auth_1 = require("../../utils/auth");
41
41
  const connectionManager_1 = require("../../adapters/database/connectionManager");
42
42
  const builtinAccess_1 = require("../../utils/builtinAccess");
43
+ const userIntegration_1 = require("../../services/userIntegration");
43
44
  const dbConnection_1 = require("../../services/dbConnection");
44
45
  const VALID_DB_TYPES = ['postgresql', 'mongodb'];
45
- function resolveBuiltinConnection(connId, config) {
46
- const db = (config.databases ?? []).find(d => d.id === connId);
46
+ // `builtinId` is config.databases[].id (e.g. 'mongo'), NOT the DbConnection row's own id —
47
+ // those are different values since TCORE-69's backfill gives each row its own UUID.
48
+ function resolveBuiltinConnection(builtinId, config) {
49
+ const db = (config.databases ?? []).find(d => d.id === builtinId);
47
50
  if (!db)
48
- return { error: `Database '${connId}' not found in config`, status: 404 };
51
+ return { error: `Database '${builtinId}' not found in config`, status: 404 };
49
52
  try {
50
- const { client, type } = (0, connectionManager_1.getConnection)(connId);
53
+ const { client, type } = (0, connectionManager_1.getConnection)(builtinId);
51
54
  return { client: client, dbType: type };
52
55
  }
53
56
  catch (e) {
54
57
  return { error: e.message, status: 503 };
55
58
  }
56
59
  }
60
+ // Finds the connection row + classifies it. A row is builtin iff conn.source === 'builtin'
61
+ // (persisted at creation/backfill time) — NOT by comparing the row's own id against
62
+ // config.databases, which no longer holds since builtin rows get their own generated id.
63
+ function findDbConnection(project, connId) {
64
+ return project.dbConnections?.find((c) => c.id === connId);
65
+ }
57
66
  async function getDecryptedConnectionString(project, connId) {
58
67
  const conn = project.dbConnections?.find((c) => c.id === connId);
59
68
  if (!conn)
60
69
  return { error: 'DB connection not found', status: 404 };
70
+ if (conn.source === 'integration') {
71
+ if (!conn.integrationId)
72
+ return { error: 'Connection is missing its integrationId', status: 500 };
73
+ const credential = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId);
74
+ if (!credential)
75
+ return { error: 'Integration not found', status: 400 };
76
+ return { connectionString: credential, conn };
77
+ }
61
78
  const { decrypt, isEncryptionKeySet } = await Promise.resolve().then(() => __importStar(require('../../utils/encryption')));
62
79
  if (!isEncryptionKeySet())
63
80
  return { error: 'Encryption key is not set', status: 500 };
@@ -91,9 +108,15 @@ function dbConnectionRouter(config) {
91
108
  if (auth.role !== 'admin' && auth.role !== 'editor') {
92
109
  return res.status(403).json({ message: 'Permission denied' });
93
110
  }
94
- const { name, dbType, credentialId, description } = req.body;
95
- if (!name || !dbType || !credentialId) {
96
- return res.status(400).json({ message: 'name, dbType, and credentialId are required' });
111
+ const { name, dbType, credentialId, integrationId, description } = req.body;
112
+ if (!name || !dbType) {
113
+ return res.status(400).json({ message: 'name and dbType are required' });
114
+ }
115
+ if (!credentialId && !integrationId) {
116
+ return res.status(400).json({ message: 'Either credentialId or integrationId is required' });
117
+ }
118
+ if (credentialId && integrationId) {
119
+ return res.status(400).json({ message: 'Provide only one of credentialId or integrationId' });
97
120
  }
98
121
  if (!VALID_DB_TYPES.includes(dbType)) {
99
122
  return res.status(400).json({ message: `dbType must be one of: ${VALID_DB_TYPES.join(', ')}` });
@@ -103,16 +126,28 @@ function dbConnectionRouter(config) {
103
126
  return res.status(404).json({ message: 'Project not found' });
104
127
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
105
128
  return res.status(403).json({ message: 'Unauthorized project access' });
106
- const credExists = project.credentials?.some((c) => c.id === credentialId);
107
- if (!credExists)
108
- return res.status(400).json({ message: 'Credential not found in project' });
129
+ if (integrationId) {
130
+ const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
131
+ const integration = await UserIntegrationModel.findOne({ id: integrationId, userId: auth.userId });
132
+ if (!integration)
133
+ return res.status(404).json({ message: 'Integration not found' });
134
+ if (integration.kind !== 'database')
135
+ return res.status(400).json({ message: 'Integration is not a database integration' });
136
+ }
137
+ else {
138
+ const credExists = project.credentials?.some((c) => c.id === credentialId);
139
+ if (!credExists)
140
+ return res.status(400).json({ message: 'Credential not found in project' });
141
+ }
109
142
  const connection = {
110
143
  id: new mongodb_1.ObjectId().toHexString(),
111
144
  name,
112
145
  dbType,
113
- credentialId,
146
+ credentialId: credentialId ?? '',
114
147
  projectId: req.params.id,
115
148
  createdAt: new Date(),
149
+ source: integrationId ? 'integration' : 'manual',
150
+ ...(integrationId && { integrationId }),
116
151
  ...(description !== undefined && { description }),
117
152
  };
118
153
  await Project.update({ _id: req.params.id }, { $push: { dbConnections: connection } });
@@ -125,9 +160,6 @@ function dbConnectionRouter(config) {
125
160
  // ─── Delete DB connection ─────────────────────────────────────────────────
126
161
  router.delete('/projects/:id/connections/db/:connId', async (req, res) => {
127
162
  try {
128
- if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
129
- return res.status(403).json({ message: 'Cannot delete a built-in database connection' });
130
- }
131
163
  const auth = req.auth;
132
164
  if (auth.role !== 'admin' && auth.role !== 'editor') {
133
165
  return res.status(403).json({ message: 'Permission denied' });
@@ -137,9 +169,12 @@ function dbConnectionRouter(config) {
137
169
  return res.status(404).json({ message: 'Project not found' });
138
170
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
139
171
  return res.status(403).json({ message: 'Unauthorized project access' });
140
- const exists = project.dbConnections?.some((c) => c.id === req.params.connId);
141
- if (!exists)
172
+ const conn = findDbConnection(project, req.params.connId);
173
+ if (!conn)
142
174
  return res.status(404).json({ message: 'DB connection not found' });
175
+ if (conn.source === 'builtin') {
176
+ return res.status(403).json({ message: 'Cannot delete a built-in database connection' });
177
+ }
143
178
  await Project.update({ _id: req.params.id }, { $pull: { dbConnections: { id: req.params.connId } } });
144
179
  return res.status(200).json({ message: 'DB connection deleted successfully' });
145
180
  }
@@ -156,11 +191,15 @@ function dbConnectionRouter(config) {
156
191
  return res.status(404).json({ message: 'Project not found' });
157
192
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
158
193
  return res.status(403).json({ message: 'Unauthorized project access' });
159
- if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
160
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
194
+ const conn = findDbConnection(project, req.params.connId);
195
+ if (!conn)
196
+ return res.status(404).json({ message: 'DB connection not found' });
197
+ if (conn.source === 'builtin') {
198
+ const builtinId = conn.integrationId;
199
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
161
200
  return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
162
201
  }
163
- const internal = resolveBuiltinConnection(req.params.connId, config);
202
+ const internal = resolveBuiltinConnection(builtinId, config);
164
203
  if ('error' in internal)
165
204
  return res.status(internal.status).json({ message: internal.error });
166
205
  const tables = await (0, dbConnection_1.listTablesInternal)(internal.client, internal.dbType, req.params.id);
@@ -191,11 +230,15 @@ function dbConnectionRouter(config) {
191
230
  const schema = req.body;
192
231
  if (!schema.tableName)
193
232
  return res.status(400).json({ message: 'tableName is required' });
194
- if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
195
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
233
+ const conn = findDbConnection(project, req.params.connId);
234
+ if (!conn)
235
+ return res.status(404).json({ message: 'DB connection not found' });
236
+ if (conn.source === 'builtin') {
237
+ const builtinId = conn.integrationId;
238
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
196
239
  return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
197
240
  }
198
- const internal = resolveBuiltinConnection(req.params.connId, config);
241
+ const internal = resolveBuiltinConnection(builtinId, config);
199
242
  if ('error' in internal)
200
243
  return res.status(internal.status).json({ message: internal.error });
201
244
  if (internal.dbType === 'sql' && (!Array.isArray(schema.columns) || schema.columns.length === 0)) {
@@ -229,11 +272,15 @@ function dbConnectionRouter(config) {
229
272
  return res.status(404).json({ message: 'Project not found' });
230
273
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
231
274
  return res.status(403).json({ message: 'Unauthorized project access' });
232
- if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
233
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
275
+ const conn = findDbConnection(project, req.params.connId);
276
+ if (!conn)
277
+ return res.status(404).json({ message: 'DB connection not found' });
278
+ if (conn.source === 'builtin') {
279
+ const builtinId = conn.integrationId;
280
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
234
281
  return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
235
282
  }
236
- const internal = resolveBuiltinConnection(req.params.connId, config);
283
+ const internal = resolveBuiltinConnection(builtinId, config);
237
284
  if ('error' in internal)
238
285
  return res.status(internal.status).json({ message: internal.error });
239
286
  await (0, dbConnection_1.deleteTableOrCollectionInternal)(internal.client, internal.dbType, req.params.tableName, req.params.id);
@@ -260,11 +307,15 @@ function dbConnectionRouter(config) {
260
307
  return res.status(403).json({ message: 'Unauthorized project access' });
261
308
  const limit = Math.min(parseInt(String(req.query.limit ?? 50), 10), 500);
262
309
  const offset = Math.max(parseInt(String(req.query.offset ?? 0), 10), 0);
263
- if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
264
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
310
+ const conn = findDbConnection(project, req.params.connId);
311
+ if (!conn)
312
+ return res.status(404).json({ message: 'DB connection not found' });
313
+ if (conn.source === 'builtin') {
314
+ const builtinId = conn.integrationId;
315
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
265
316
  return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
266
317
  }
267
- const internal = resolveBuiltinConnection(req.params.connId, config);
318
+ const internal = resolveBuiltinConnection(builtinId, config);
268
319
  if ('error' in internal)
269
320
  return res.status(internal.status).json({ message: internal.error });
270
321
  const records = await (0, dbConnection_1.queryRecordsInternal)(internal.client, internal.dbType, req.params.tableName, limit, offset, req.params.id);
@@ -296,11 +347,15 @@ function dbConnectionRouter(config) {
296
347
  if (!record || typeof record !== 'object' || Array.isArray(record)) {
297
348
  return res.status(400).json({ message: 'Request body must be a JSON object' });
298
349
  }
299
- if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
300
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
350
+ const conn = findDbConnection(project, req.params.connId);
351
+ if (!conn)
352
+ return res.status(404).json({ message: 'DB connection not found' });
353
+ if (conn.source === 'builtin') {
354
+ const builtinId = conn.integrationId;
355
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
301
356
  return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
302
357
  }
303
- const internal = resolveBuiltinConnection(req.params.connId, config);
358
+ const internal = resolveBuiltinConnection(builtinId, config);
304
359
  if ('error' in internal)
305
360
  return res.status(internal.status).json({ message: internal.error });
306
361
  const inserted = await (0, dbConnection_1.insertRecordInternal)(internal.client, internal.dbType, req.params.tableName, record, req.params.id);
@@ -331,11 +386,15 @@ function dbConnectionRouter(config) {
331
386
  if (!updates || typeof updates !== 'object' || Array.isArray(updates)) {
332
387
  return res.status(400).json({ message: 'Request body must be a JSON object' });
333
388
  }
334
- if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
335
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
389
+ const conn = findDbConnection(project, req.params.connId);
390
+ if (!conn)
391
+ return res.status(404).json({ message: 'DB connection not found' });
392
+ if (conn.source === 'builtin') {
393
+ const builtinId = conn.integrationId;
394
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
336
395
  return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
337
396
  }
338
- const internal = resolveBuiltinConnection(req.params.connId, config);
397
+ const internal = resolveBuiltinConnection(builtinId, config);
339
398
  if ('error' in internal)
340
399
  return res.status(internal.status).json({ message: internal.error });
341
400
  const updated = await (0, dbConnection_1.updateRecordInternal)(internal.client, internal.dbType, req.params.tableName, req.params.recordId, updates, req.params.id);
@@ -362,11 +421,15 @@ function dbConnectionRouter(config) {
362
421
  return res.status(404).json({ message: 'Project not found' });
363
422
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
364
423
  return res.status(403).json({ message: 'Unauthorized project access' });
365
- if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
366
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
424
+ const conn = findDbConnection(project, req.params.connId);
425
+ if (!conn)
426
+ return res.status(404).json({ message: 'DB connection not found' });
427
+ if (conn.source === 'builtin') {
428
+ const builtinId = conn.integrationId;
429
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
367
430
  return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
368
431
  }
369
- const internal = resolveBuiltinConnection(req.params.connId, config);
432
+ const internal = resolveBuiltinConnection(builtinId, config);
370
433
  if ('error' in internal)
371
434
  return res.status(internal.status).json({ message: internal.error });
372
435
  await (0, dbConnection_1.deleteRecordInternal)(internal.client, internal.dbType, req.params.tableName, req.params.recordId, req.params.id);
@@ -1,17 +1,13 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.projectRouter = projectRouter;
7
4
  const express_1 = require("express");
8
- const crypto_1 = __importDefault(require("crypto"));
9
5
  const manager_1 = require("../../models/manager");
10
6
  const auth_1 = require("../../utils/auth");
11
7
  const connectionManager_1 = require("../../adapters/database/connectionManager");
12
8
  const sql_1 = require("../../adapters/database/sql");
13
9
  const sanitize_1 = require("../../utils/sanitize");
14
- const builtinAccess_1 = require("../../utils/builtinAccess");
10
+ const projectIntegration_1 = require("../../services/projectIntegration");
15
11
  function projectRouter(config) {
16
12
  const router = (0, express_1.Router)();
17
13
  const Project = (0, manager_1.getModel)('projects');
@@ -98,47 +94,14 @@ function projectRouter(config) {
98
94
  const dbConnections = [];
99
95
  const storageConnections = [];
100
96
  if (Array.isArray(integrationIds)) {
101
- const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
102
97
  for (const id of integrationIds) {
103
- const now = new Date();
104
- if ((0, builtinAccess_1.isBuiltinDb)(id, config)) {
105
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, id, config, 'database'))) {
106
- return res.status(403).json({ message: `Access to built-in database '${id}' is not permitted` });
107
- }
108
- const db = config.databases.find(d => d.id === id);
109
- dbConnections.push({
110
- id: crypto_1.default.randomUUID(), name: id, dbType: db.type === 'sql' ? 'postgresql' : 'mongodb',
111
- credentialId: '', projectId: '', createdAt: now, isBuiltin: true, integrationId: id, source: 'builtin',
112
- });
113
- }
114
- else if ((0, builtinAccess_1.isBuiltinStorageId)(id, config)) {
115
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, id, config, 'storage'))) {
116
- return res.status(403).json({ message: `Access to built-in storage '${id}' is not permitted` });
117
- }
118
- const provider = config.storageProviders.find(p => p.id === id);
119
- storageConnections.push({
120
- id: crypto_1.default.randomUUID(), name: id, type: provider.type,
121
- credentialId: '', projectId: '', createdAt: now, isBuiltin: true, integrationId: id, source: 'builtin',
122
- });
123
- }
124
- else {
125
- const integration = await UserIntegrationModel.findOne({ id, userId: auth.userId });
126
- if (!integration) {
127
- return res.status(403).json({ message: `Integration '${id}' is not valid or does not belong to you` });
128
- }
129
- if (integration.kind === 'database') {
130
- dbConnections.push({
131
- id: crypto_1.default.randomUUID(), name: integration.name, dbType: integration.provider,
132
- credentialId: '', projectId: '', createdAt: now, isBuiltin: false, integrationId: id, source: 'integration',
133
- });
134
- }
135
- else {
136
- storageConnections.push({
137
- id: crypto_1.default.randomUUID(), name: integration.name, type: integration.provider,
138
- credentialId: '', projectId: '', createdAt: now, isBuiltin: false, integrationId: id, source: 'integration',
139
- });
140
- }
141
- }
98
+ const result = await (0, projectIntegration_1.classifyIntegrationId)(id, auth, config, '');
99
+ if ('error' in result)
100
+ return res.status(result.status).json({ message: result.error });
101
+ if (result.kind === 'database')
102
+ dbConnections.push(result.connection);
103
+ else
104
+ storageConnections.push(result.connection);
142
105
  }
143
106
  }
144
107
  let newProject = await Project.create({
@@ -430,5 +393,68 @@ function projectRouter(config) {
430
393
  res.status(500).json({ message: 'Failed to update project origins', details: err.message });
431
394
  }
432
395
  });
396
+ // ─── Add an integration to an existing project ───────────────────────────
397
+ router.post('/projects/:id/integrations', async (req, res) => {
398
+ try {
399
+ const auth = req.auth;
400
+ if (auth.role !== 'admin' && auth.role !== 'editor') {
401
+ return res.status(403).json({ message: 'Permission denied' });
402
+ }
403
+ const projectId = req.params.id;
404
+ const { integrationId } = req.body;
405
+ if (!integrationId)
406
+ return res.status(400).json({ message: 'integrationId is required' });
407
+ const project = await Project.findOne({ _id: projectId });
408
+ if (!project)
409
+ return res.status(404).json({ message: 'Project not found' });
410
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
411
+ return res.status(403).json({ message: 'Unauthorized project access' });
412
+ const alreadyConnected = [...(project.dbConnections ?? []), ...(project.storageConnections ?? [])]
413
+ .some((c) => c.integrationId === integrationId);
414
+ if (alreadyConnected)
415
+ return res.status(400).json({ message: 'This integration is already connected to the project' });
416
+ const result = await (0, projectIntegration_1.classifyIntegrationId)(integrationId, auth, config, projectId);
417
+ if ('error' in result)
418
+ return res.status(result.status).json({ message: result.error });
419
+ const updated = result.kind === 'database'
420
+ ? await Project.update({ _id: projectId }, { $push: { dbConnections: result.connection } })
421
+ : await Project.update({ _id: projectId }, { $push: { storageConnections: result.connection } });
422
+ if (!updated)
423
+ return res.status(404).json({ message: 'Project not found or not updated' });
424
+ res.status(201).json({ data: result.connection, project: (0, sanitize_1.sanitizeProject)(updated) });
425
+ }
426
+ catch (err) {
427
+ res.status(500).json({ message: 'Failed to add integration', details: err.message });
428
+ }
429
+ });
430
+ // ─── Remove an integration from a project ────────────────────────────────
431
+ router.delete('/projects/:id/integrations/:integrationId', async (req, res) => {
432
+ try {
433
+ const auth = req.auth;
434
+ if (auth.role !== 'admin' && auth.role !== 'editor') {
435
+ return res.status(403).json({ message: 'Permission denied' });
436
+ }
437
+ const projectId = req.params.id;
438
+ const { integrationId } = req.params;
439
+ const project = await Project.findOne({ _id: projectId });
440
+ if (!project)
441
+ return res.status(404).json({ message: 'Project not found' });
442
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
443
+ return res.status(403).json({ message: 'Unauthorized project access' });
444
+ const dbConn = (project.dbConnections ?? []).find((c) => c.integrationId === integrationId);
445
+ const storageConn = (project.storageConnections ?? []).find((c) => c.integrationId === integrationId);
446
+ if (!dbConn && !storageConn)
447
+ return res.status(404).json({ message: 'Integration not connected to this project' });
448
+ const updated = dbConn
449
+ ? await Project.update({ _id: projectId }, { $pull: { dbConnections: { id: dbConn.id } } })
450
+ : await Project.update({ _id: projectId }, { $pull: { storageConnections: { id: storageConn.id } } });
451
+ if (!updated)
452
+ return res.status(404).json({ message: 'Project not found or not updated' });
453
+ res.status(200).json({ message: 'Integration removed', project: (0, sanitize_1.sanitizeProject)(updated) });
454
+ }
455
+ catch (err) {
456
+ res.status(500).json({ message: 'Failed to remove integration', details: err.message });
457
+ }
458
+ });
433
459
  return router;
434
460
  }
@@ -14,6 +14,7 @@ const connectionManager_1 = require("../../adapters/database/connectionManager")
14
14
  const auth_1 = require("../../utils/auth");
15
15
  const encryption_1 = require("../../utils/encryption");
16
16
  const builtinAccess_1 = require("../../utils/builtinAccess");
17
+ const userIntegration_1 = require("../../services/userIntegration");
17
18
  const mongodb_1 = require("mongodb");
18
19
  const crypto_1 = __importDefault(require("crypto"));
19
20
  const VALID_CATEGORIES = ['images', 'audios', 'videos', '3d-models'];
@@ -51,26 +52,47 @@ function storageRouter(config) {
51
52
  const router = (0, express_1.Router)();
52
53
  const adapter = config.adapter || (0, storage_1.createStorageProvider)(config.storageProviders);
53
54
  const Project = (0, manager_1.getModel)('projects');
54
- function connFilter(projectId, connId, extra) {
55
- const isBuiltin = (0, builtinAccess_1.isBuiltinStorageId)(connId, config);
55
+ // A row is builtin iff its own `source === 'builtin'` (set at backfill/creation time) —
56
+ // NOT by comparing connId (the row's own generated id) against config.storageProviders,
57
+ // which no longer holds since builtin rows get their own id distinct from provider.id.
58
+ function findStorageConn(project, connId) {
59
+ return project.storageConnections?.find((c) => c.id === connId);
60
+ }
61
+ function connFilter(projectId, connId, project, extra) {
62
+ const conn = findStorageConn(project, connId);
63
+ const isBuiltin = conn?.source === 'builtin';
56
64
  const connMatch = isBuiltin
57
65
  ? { $or: [{ storageConnectionId: connId }, { storageConnectionId: { $exists: false } }] }
58
66
  : { storageConnectionId: connId };
59
67
  return { projectId, ...connMatch, ...(extra ?? {}) };
60
68
  }
61
69
  async function resolveConnAdapter(connId, project, auth) {
62
- if ((0, builtinAccess_1.isBuiltinStorageId)(connId, config)) {
63
- if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, connId, config, 'storage'))) {
70
+ const conn = findStorageConn(project, connId);
71
+ if (!conn)
72
+ return { error: 'Storage connection not found', status: 404 };
73
+ if (conn.source === 'builtin') {
74
+ const builtinId = conn.integrationId;
75
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'storage'))) {
64
76
  return { error: 'Access to this built-in storage is not permitted', status: 403 };
65
77
  }
66
- const provider = config.storageProviders.find(p => p.id === connId);
78
+ const provider = config.storageProviders.find(p => p.id === builtinId);
67
79
  if (!provider)
68
80
  return { error: 'Storage provider not found', status: 404 };
69
81
  return provider.type === 's3' ? new s3_1.S3Adapter(provider.config) : adapter;
70
82
  }
71
- const conn = project.storageConnections?.find((c) => c.id === connId);
72
- if (!conn)
73
- return { error: 'Storage connection not found', status: 404 };
83
+ if (conn.source === 'integration') {
84
+ if (!conn.integrationId)
85
+ return { error: 'Connection is missing its integrationId', status: 500 };
86
+ try {
87
+ const s3Config = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId);
88
+ if (!s3Config)
89
+ return { error: 'Integration not found', status: 400 };
90
+ return new s3_1.S3Adapter(s3Config);
91
+ }
92
+ catch (e) {
93
+ return { error: `Failed to initialize storage adapter: ${e.message}`, status: 500 };
94
+ }
95
+ }
74
96
  if (!(0, encryption_1.isEncryptionKeySet)())
75
97
  return { error: 'Encryption key not set', status: 500 };
76
98
  const credential = project.credentials?.find((c) => c.id === conn.credentialId);
@@ -354,7 +376,7 @@ function storageRouter(config) {
354
376
  const collection = getRawFilesCollection();
355
377
  if (!collection)
356
378
  return res.status(500).json({ message: 'Database not available' });
357
- const files = await collection.find(connFilter(projectId, connId)).sort({ createdAt: -1 }).limit(12).toArray();
379
+ const files = await collection.find(connFilter(projectId, connId, project)).sort({ createdAt: -1 }).limit(12).toArray();
358
380
  const data = await Promise.all(files.map(f => resolveFileUrl(f, connAdapter)));
359
381
  res.json({ data });
360
382
  }
@@ -380,7 +402,7 @@ function storageRouter(config) {
380
402
  const collection = getRawFilesCollection();
381
403
  if (!collection)
382
404
  return res.status(500).json({ message: 'Database not available' });
383
- const files = await collection.find(connFilter(projectId, connId, { category })).sort({ createdAt: -1 }).toArray();
405
+ const files = await collection.find(connFilter(projectId, connId, project, { category })).sort({ createdAt: -1 }).toArray();
384
406
  const data = await Promise.all(files.map(f => resolveFileUrl(f, connAdapter)));
385
407
  res.json({ data });
386
408
  }
@@ -404,7 +426,7 @@ function storageRouter(config) {
404
426
  const collection = getRawFilesCollection();
405
427
  if (!collection)
406
428
  return res.status(500).json({ message: 'Database not available' });
407
- const fileDoc = await collection.findOne(connFilter(projectId, connId, { fileId }));
429
+ const fileDoc = await collection.findOne(connFilter(projectId, connId, project, { fileId }));
408
430
  if (!fileDoc)
409
431
  return res.status(404).json({ message: 'File not found' });
410
432
  if (!validateContentType(contentType, fileName, fileDoc.category)) {
@@ -417,7 +439,7 @@ function storageRouter(config) {
417
439
  return res.status(501).json({ message: 'Storage adapter does not support presigned uploads' });
418
440
  }
419
441
  const url = await connAdapter.getPresignedUploadUrl(fileDoc.storageKey, contentType);
420
- await collection.updateOne(connFilter(projectId, connId, { fileId }), { $set: { contentType, updatedAt: new Date() } });
442
+ await collection.updateOne(connFilter(projectId, connId, project, { fileId }), { $set: { contentType, updatedAt: new Date() } });
421
443
  res.json({ url, storageKey: fileDoc.storageKey, fileId });
422
444
  }
423
445
  catch (err) {
@@ -440,7 +462,7 @@ function storageRouter(config) {
440
462
  const collection = getRawFilesCollection();
441
463
  if (!collection)
442
464
  return res.status(500).json({ message: 'Database not available' });
443
- const result = await collection.findOneAndUpdate(connFilter(projectId, connId, { fileId }), { $set: { displayName } }, { returnDocument: 'after' });
465
+ const result = await collection.findOneAndUpdate(connFilter(projectId, connId, project, { fileId }), { $set: { displayName } }, { returnDocument: 'after' });
444
466
  if (!result)
445
467
  return res.status(404).json({ message: 'File not found' });
446
468
  res.json({ message: 'File renamed successfully', file: result });
@@ -464,12 +486,12 @@ function storageRouter(config) {
464
486
  const collection = getRawFilesCollection();
465
487
  if (!collection)
466
488
  return res.status(500).json({ message: 'Database not available' });
467
- const fileDoc = await collection.findOne(connFilter(projectId, connId, { fileId }));
489
+ const fileDoc = await collection.findOne(connFilter(projectId, connId, project, { fileId }));
468
490
  if (!fileDoc)
469
491
  return res.status(404).json({ message: 'File not found' });
470
492
  if (connAdapter.deleteFile)
471
493
  await connAdapter.deleteFile(fileDoc.storageKey);
472
- await collection.deleteOne(connFilter(projectId, connId, { fileId }));
494
+ await collection.deleteOne(connFilter(projectId, connId, project, { fileId }));
473
495
  res.json({ message: 'File deleted successfully' });
474
496
  }
475
497
  catch (err) {
@@ -494,7 +516,7 @@ function storageRouter(config) {
494
516
  ? { parentId: null }
495
517
  : { parentId };
496
518
  const folders = await collection
497
- .find(connFilter(projectId, connId, parentFilter))
519
+ .find(connFilter(projectId, connId, project, parentFilter))
498
520
  .sort({ name: 1 })
499
521
  .toArray();
500
522
  res.json({
@@ -525,7 +547,7 @@ function storageRouter(config) {
525
547
  const collection = getRawFoldersCollection();
526
548
  if (!collection)
527
549
  return res.status(500).json({ message: 'Database not available' });
528
- const f = await collection.findOne(connFilter(projectId, connId, { folderId }));
550
+ const f = await collection.findOne(connFilter(projectId, connId, project, { folderId }));
529
551
  if (!f)
530
552
  return res.status(404).json({ message: 'Folder not found' });
531
553
  res.json({
@@ -557,8 +579,9 @@ function storageRouter(config) {
557
579
  return res.status(404).json({ message: 'Project not found' });
558
580
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
559
581
  return res.status(403).json({ message: 'Unauthorized access' });
560
- const isBuiltin = (0, builtinAccess_1.isBuiltinStorageId)(connId, config);
561
- if (isBuiltin && !(await (0, builtinAccess_1.assertBuiltinAccess)(auth, connId, config, 'storage'))) {
582
+ const conn = findStorageConn(project, connId);
583
+ const isBuiltin = conn?.source === 'builtin';
584
+ if (isBuiltin && !(await (0, builtinAccess_1.assertBuiltinAccess)(auth, conn.integrationId, config, 'storage'))) {
562
585
  return res.status(403).json({ message: 'Access to this built-in storage is not permitted' });
563
586
  }
564
587
  const collection = getRawFoldersCollection();
@@ -610,7 +633,7 @@ function storageRouter(config) {
610
633
  const collection = getRawFoldersCollection();
611
634
  if (!collection)
612
635
  return res.status(500).json({ message: 'Database not available' });
613
- const result = await collection.findOneAndUpdate(connFilter(projectId, connId, { folderId }), { $set: { name: name.trim(), updatedAt: new Date() } }, { returnDocument: 'after' });
636
+ const result = await collection.findOneAndUpdate(connFilter(projectId, connId, project, { folderId }), { $set: { name: name.trim(), updatedAt: new Date() } }, { returnDocument: 'after' });
614
637
  if (!result)
615
638
  return res.status(404).json({ message: 'Folder not found' });
616
639
  res.json({
@@ -644,7 +667,7 @@ function storageRouter(config) {
644
667
  const collection = getRawFoldersCollection();
645
668
  if (!collection)
646
669
  return res.status(500).json({ message: 'Database not available' });
647
- const result = await collection.findOneAndUpdate(connFilter(projectId, connId, { folderId }), { $set: { parentId: newParentId ?? null, updatedAt: new Date() } }, { returnDocument: 'after' });
670
+ const result = await collection.findOneAndUpdate(connFilter(projectId, connId, project, { folderId }), { $set: { parentId: newParentId ?? null, updatedAt: new Date() } }, { returnDocument: 'after' });
648
671
  if (!result)
649
672
  return res.status(404).json({ message: 'Folder not found' });
650
673
  res.json({
@@ -674,15 +697,15 @@ function storageRouter(config) {
674
697
  const foldersCollection = getRawFoldersCollection();
675
698
  if (!foldersCollection)
676
699
  return res.status(500).json({ message: 'Database not available' });
677
- const folderDoc = await foldersCollection.findOne(connFilter(projectId, connId, { folderId }));
700
+ const folderDoc = await foldersCollection.findOne(connFilter(projectId, connId, project, { folderId }));
678
701
  if (!folderDoc)
679
702
  return res.status(404).json({ message: 'Folder not found' });
680
703
  const filesCollection = getRawFilesCollection();
681
704
  if (filesCollection) {
682
- await filesCollection.updateMany(connFilter(projectId, connId, { folderId }), { $set: { folderId: folderDoc.parentId ?? null } });
705
+ await filesCollection.updateMany(connFilter(projectId, connId, project, { folderId }), { $set: { folderId: folderDoc.parentId ?? null } });
683
706
  }
684
- await foldersCollection.updateMany(connFilter(projectId, connId, { parentId: folderId }), { $set: { parentId: folderDoc.parentId ?? null } });
685
- await foldersCollection.deleteOne(connFilter(projectId, connId, { folderId }));
707
+ await foldersCollection.updateMany(connFilter(projectId, connId, project, { parentId: folderId }), { $set: { parentId: folderDoc.parentId ?? null } });
708
+ await foldersCollection.deleteOne(connFilter(projectId, connId, project, { folderId }));
686
709
  res.json({ message: 'Folder deleted successfully' });
687
710
  }
688
711
  catch (err) {
@@ -702,7 +725,7 @@ function storageRouter(config) {
702
725
  if (folderId != null) {
703
726
  const foldersCollection = getRawFoldersCollection();
704
727
  if (foldersCollection) {
705
- const folderDoc = await foldersCollection.findOne(connFilter(projectId, connId, { folderId }));
728
+ const folderDoc = await foldersCollection.findOne(connFilter(projectId, connId, project, { folderId }));
706
729
  if (!folderDoc)
707
730
  return res.status(404).json({ message: 'Folder not found' });
708
731
  }
@@ -710,7 +733,7 @@ function storageRouter(config) {
710
733
  const filesCollection = getRawFilesCollection();
711
734
  if (!filesCollection)
712
735
  return res.status(500).json({ message: 'Database not available' });
713
- const result = await filesCollection.findOneAndUpdate(connFilter(projectId, connId, { fileId }), { $set: { folderId: folderId ?? null } }, { returnDocument: 'after' });
736
+ const result = await filesCollection.findOneAndUpdate(connFilter(projectId, connId, project, { fileId }), { $set: { folderId: folderId ?? null } }, { returnDocument: 'after' });
714
737
  if (!result)
715
738
  return res.status(404).json({ message: 'File not found' });
716
739
  res.json({ message: 'File moved successfully', file: result });
@@ -41,9 +41,15 @@ function storageConnectionRouter(config) {
41
41
  if (auth.role !== 'admin' && auth.role !== 'editor') {
42
42
  return res.status(403).json({ message: 'Permission denied' });
43
43
  }
44
- const { name, type, credentialId, description } = req.body;
45
- if (!name || !type || !credentialId) {
46
- return res.status(400).json({ message: 'name, type, and credentialId are required' });
44
+ const { name, type, credentialId, integrationId, description } = req.body;
45
+ if (!name || !type) {
46
+ return res.status(400).json({ message: 'name and type are required' });
47
+ }
48
+ if (!credentialId && !integrationId) {
49
+ return res.status(400).json({ message: 'Either credentialId or integrationId is required' });
50
+ }
51
+ if (credentialId && integrationId) {
52
+ return res.status(400).json({ message: 'Provide only one of credentialId or integrationId' });
47
53
  }
48
54
  if (!VALID_STORAGE_TYPES.includes(type)) {
49
55
  return res.status(400).json({ message: `type must be one of: ${VALID_STORAGE_TYPES.join(', ')}` });
@@ -53,16 +59,28 @@ function storageConnectionRouter(config) {
53
59
  return res.status(404).json({ message: 'Project not found' });
54
60
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
55
61
  return res.status(403).json({ message: 'Unauthorized project access' });
56
- const credExists = project.credentials?.some((c) => c.id === credentialId);
57
- if (!credExists)
58
- return res.status(400).json({ message: 'Credential not found in project' });
62
+ if (integrationId) {
63
+ const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
64
+ const integration = await UserIntegrationModel.findOne({ id: integrationId, userId: auth.userId });
65
+ if (!integration)
66
+ return res.status(404).json({ message: 'Integration not found' });
67
+ if (integration.kind !== 'storage')
68
+ return res.status(400).json({ message: 'Integration is not a storage integration' });
69
+ }
70
+ else {
71
+ const credExists = project.credentials?.some((c) => c.id === credentialId);
72
+ if (!credExists)
73
+ return res.status(400).json({ message: 'Credential not found in project' });
74
+ }
59
75
  const connection = {
60
76
  id: new mongodb_1.ObjectId().toHexString(),
61
77
  name,
62
78
  type,
63
- credentialId,
79
+ credentialId: credentialId ?? '',
64
80
  projectId: req.params.id,
65
81
  createdAt: new Date(),
82
+ source: integrationId ? 'integration' : 'manual',
83
+ ...(integrationId && { integrationId }),
66
84
  ...(description !== undefined && { description }),
67
85
  };
68
86
  await Project.update({ _id: req.params.id }, { $push: { storageConnections: connection } });
@@ -76,9 +94,6 @@ function storageConnectionRouter(config) {
76
94
  router.delete('/projects/:id/connections/storage/:connId', async (req, res) => {
77
95
  try {
78
96
  const { connId } = req.params;
79
- if ((0, builtinAccess_1.isBuiltinStorageId)(connId, config)) {
80
- return res.status(403).json({ message: 'Cannot delete a built-in storage connection' });
81
- }
82
97
  const auth = req.auth;
83
98
  if (auth.role !== 'admin' && auth.role !== 'editor') {
84
99
  return res.status(403).json({ message: 'Permission denied' });
@@ -88,9 +103,12 @@ function storageConnectionRouter(config) {
88
103
  return res.status(404).json({ message: 'Project not found' });
89
104
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
90
105
  return res.status(403).json({ message: 'Unauthorized project access' });
91
- const exists = project.storageConnections?.some((c) => c.id === connId);
92
- if (!exists)
106
+ const conn = project.storageConnections?.find((c) => c.id === connId);
107
+ if (!conn)
93
108
  return res.status(404).json({ message: 'Storage connection not found' });
109
+ if (conn.source === 'builtin') {
110
+ return res.status(403).json({ message: 'Cannot delete a built-in storage connection' });
111
+ }
94
112
  await Project.update({ _id: req.params.id }, { $pull: { storageConnections: { id: connId } } });
95
113
  return res.status(200).json({ message: 'Storage connection deleted successfully' });
96
114
  }
@@ -22,11 +22,11 @@ function userIntegrationRouter(config) {
22
22
  // requiredPlan is not populated here — canUseBuiltin only returns a boolean today,
23
23
  // so there's no plan-level detail to surface without a hook-signature change.
24
24
  const available = await (0, builtinAccess_1.assertBuiltinAccess)(auth, db.id, config, 'database');
25
- pool.push({ id: db.id, kind: 'database', name: db.id, provider: db.type, source: 'builtin', available });
25
+ pool.push({ id: db.id, kind: 'database', name: builtinAccess_1.BUILTIN_DB_DISPLAY[db.type] ?? db.type, provider: db.type, source: 'builtin', available });
26
26
  }
27
27
  for (const provider of config.storageProviders ?? []) {
28
28
  const available = await (0, builtinAccess_1.assertBuiltinAccess)(auth, provider.id, config, 'storage');
29
- pool.push({ id: provider.id, kind: 'storage', name: provider.id, provider: provider.type, source: 'builtin', available });
29
+ pool.push({ id: provider.id, kind: 'storage', name: builtinAccess_1.BUILTIN_STORAGE_DISPLAY[provider.type] ?? provider.type, provider: provider.type, source: 'builtin', available });
30
30
  }
31
31
  res.json({ data: pool });
32
32
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.backfillBuiltinConnections = backfillBuiltinConnections;
7
7
  const crypto_1 = __importDefault(require("crypto"));
8
8
  const manager_1 = require("../models/manager");
9
+ const builtinAccess_1 = require("../utils/builtinAccess");
9
10
  // Pre-BYOC, every project appeared connected to every configured builtin because
10
11
  // storageConnection.ts/dbConnection.ts SYNTHESIZED builtins at read time instead of
11
12
  // persisting them (see storageConnection.ts's old GET handler). Now that connections must
@@ -26,7 +27,7 @@ async function backfillBuiltinConnections(config) {
26
27
  const now = new Date();
27
28
  const dbConnections = (config.databases ?? []).map(db => ({
28
29
  id: crypto_1.default.randomUUID(),
29
- name: db.id,
30
+ name: builtinAccess_1.BUILTIN_DB_DISPLAY[db.type] ?? db.type,
30
31
  dbType: db.type === 'sql' ? 'postgresql' : 'mongodb',
31
32
  credentialId: '',
32
33
  projectId: project.id,
@@ -37,7 +38,7 @@ async function backfillBuiltinConnections(config) {
37
38
  }));
38
39
  const storageConnections = (config.storageProviders ?? []).map(provider => ({
39
40
  id: crypto_1.default.randomUUID(),
40
- name: provider.id,
41
+ name: builtinAccess_1.BUILTIN_STORAGE_DISPLAY[provider.type] ?? provider.type,
41
42
  type: provider.type,
42
43
  credentialId: '',
43
44
  projectId: project.id,
@@ -0,0 +1,13 @@
1
+ import { Auth, DbConnection, StorageConnection, StreamByConfig } from '../types';
2
+ type ClassifyResult = {
3
+ kind: 'database';
4
+ connection: DbConnection;
5
+ } | {
6
+ kind: 'storage';
7
+ connection: StorageConnection;
8
+ } | {
9
+ error: string;
10
+ status: number;
11
+ };
12
+ export declare function classifyIntegrationId(id: string, auth: Auth, config: StreamByConfig, projectId: string): Promise<ClassifyResult>;
13
+ export {};
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.classifyIntegrationId = classifyIntegrationId;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const manager_1 = require("../models/manager");
9
+ const builtinAccess_1 = require("../utils/builtinAccess");
10
+ // Classifies a client-supplied integrationId (builtin db, builtin storage, or a user's own
11
+ // BYOC integration) and revalidates access/ownership server-side — never trust the client.
12
+ // Shared by POST /projects/create (bulk, at project creation) and
13
+ // POST /projects/:id/integrations (single, added to an existing project).
14
+ async function classifyIntegrationId(id, auth, config, projectId) {
15
+ const now = new Date();
16
+ if ((0, builtinAccess_1.isBuiltinDb)(id, config)) {
17
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, id, config, 'database'))) {
18
+ return { error: `Access to built-in database '${id}' is not permitted`, status: 403 };
19
+ }
20
+ const db = config.databases.find(d => d.id === id);
21
+ return {
22
+ kind: 'database',
23
+ connection: {
24
+ id: crypto_1.default.randomUUID(), name: builtinAccess_1.BUILTIN_DB_DISPLAY[db.type] ?? db.type, dbType: db.type === 'sql' ? 'postgresql' : 'mongodb',
25
+ credentialId: '', projectId, createdAt: now, isBuiltin: true, integrationId: id, source: 'builtin',
26
+ },
27
+ };
28
+ }
29
+ if ((0, builtinAccess_1.isBuiltinStorageId)(id, config)) {
30
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, id, config, 'storage'))) {
31
+ return { error: `Access to built-in storage '${id}' is not permitted`, status: 403 };
32
+ }
33
+ const provider = config.storageProviders.find(p => p.id === id);
34
+ return {
35
+ kind: 'storage',
36
+ connection: {
37
+ id: crypto_1.default.randomUUID(), name: builtinAccess_1.BUILTIN_STORAGE_DISPLAY[provider.type] ?? provider.type, type: provider.type,
38
+ credentialId: '', projectId, createdAt: now, isBuiltin: true, integrationId: id, source: 'builtin',
39
+ },
40
+ };
41
+ }
42
+ const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
43
+ const integration = await UserIntegrationModel.findOne({ id, userId: auth.userId });
44
+ if (!integration) {
45
+ return { error: `Integration '${id}' is not valid or does not belong to you`, status: 403 };
46
+ }
47
+ if (integration.kind === 'database') {
48
+ return {
49
+ kind: 'database',
50
+ connection: {
51
+ id: crypto_1.default.randomUUID(), name: integration.name, dbType: integration.provider,
52
+ credentialId: '', projectId, createdAt: now, isBuiltin: false, integrationId: id, source: 'integration',
53
+ },
54
+ };
55
+ }
56
+ return {
57
+ kind: 'storage',
58
+ connection: {
59
+ id: crypto_1.default.randomUUID(), name: integration.name, type: integration.provider,
60
+ credentialId: '', projectId, createdAt: now, isBuiltin: false, integrationId: id, source: 'integration',
61
+ },
62
+ };
63
+ }
@@ -14,3 +14,4 @@ export declare function updateUserIntegration(userId: string, integrationId: str
14
14
  }): Promise<UserIntegration | null>;
15
15
  export declare function deleteUserIntegration(userId: string, integrationId: string): Promise<boolean>;
16
16
  export declare function getDecryptedIntegrationCredential(userId: string, integrationId: string): Promise<unknown | null>;
17
+ export declare function getDecryptedIntegrationCredentialById(integrationId: string): Promise<unknown | null>;
@@ -8,6 +8,7 @@ exports.createUserIntegration = createUserIntegration;
8
8
  exports.updateUserIntegration = updateUserIntegration;
9
9
  exports.deleteUserIntegration = deleteUserIntegration;
10
10
  exports.getDecryptedIntegrationCredential = getDecryptedIntegrationCredential;
11
+ exports.getDecryptedIntegrationCredentialById = getDecryptedIntegrationCredentialById;
11
12
  const crypto_1 = __importDefault(require("crypto"));
12
13
  const manager_1 = require("../models/manager");
13
14
  const encryption_1 = require("../utils/encryption");
@@ -63,6 +64,18 @@ async function deleteUserIntegration(userId, integrationId) {
63
64
  async function getDecryptedIntegrationCredential(userId, integrationId) {
64
65
  const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
65
66
  const integration = await UserIntegrationModel.findOne({ id: integrationId, userId });
67
+ return decryptIntegration(integration);
68
+ }
69
+ // Used when resolving a project connection's `integrationId` at connection-use time — the
70
+ // project already validated ownership when the connection was created (see project.ts /
71
+ // dbConnection.ts POST), so there's no userId to filter by here, only the integrationId
72
+ // stored on the connection itself.
73
+ async function getDecryptedIntegrationCredentialById(integrationId) {
74
+ const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
75
+ const integration = await UserIntegrationModel.findOne({ id: integrationId });
76
+ return decryptIntegration(integration);
77
+ }
78
+ async function decryptIntegration(integration) {
66
79
  if (!integration)
67
80
  return null;
68
81
  if (!(0, encryption_1.isEncryptionKeySet)()) {
@@ -18,7 +18,7 @@ export interface StorageConnection {
18
18
  description?: string;
19
19
  isBuiltin?: boolean;
20
20
  integrationId?: string;
21
- source?: 'builtin' | 'integration';
21
+ source?: 'builtin' | 'integration' | 'manual';
22
22
  available?: boolean;
23
23
  }
24
24
  export type StorageProvider = {
@@ -159,7 +159,7 @@ export interface DbConnection {
159
159
  description?: string;
160
160
  isBuiltin?: boolean;
161
161
  integrationId?: string;
162
- source?: 'builtin' | 'integration';
162
+ source?: 'builtin' | 'integration' | 'manual';
163
163
  }
164
164
  export type IntegrationKind = 'database' | 'storage';
165
165
  export interface UserIntegration {
@@ -2,3 +2,5 @@ import { Auth, StreamByConfig } from '../types';
2
2
  export declare function assertBuiltinAccess(auth: Auth, builtinId: string, config: StreamByConfig, kind: 'database' | 'storage'): Promise<boolean>;
3
3
  export declare function isBuiltinStorageId(id: string, config: StreamByConfig): boolean;
4
4
  export declare function isBuiltinDb(id: string, config: StreamByConfig): boolean;
5
+ export declare const BUILTIN_STORAGE_DISPLAY: Record<string, string>;
6
+ export declare const BUILTIN_DB_DISPLAY: Record<string, string>;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BUILTIN_DB_DISPLAY = exports.BUILTIN_STORAGE_DISPLAY = void 0;
3
4
  exports.assertBuiltinAccess = assertBuiltinAccess;
4
5
  exports.isBuiltinStorageId = isBuiltinStorageId;
5
6
  exports.isBuiltinDb = isBuiltinDb;
@@ -17,3 +18,15 @@ function isBuiltinStorageId(id, config) {
17
18
  function isBuiltinDb(id, config) {
18
19
  return (config.databases ?? []).some(db => db.id === id);
19
20
  }
21
+ // Display names for builtin connections — never the raw config id/type. The frontend pairs
22
+ // this with the StreamBy icon to visually mark a connection as built-in vs BYOC.
23
+ exports.BUILTIN_STORAGE_DISPLAY = {
24
+ s3: 'AWS S3',
25
+ gcs: 'Google Cloud Storage',
26
+ r2: 'Cloudflare R2',
27
+ azure: 'Azure Blob Storage',
28
+ };
29
+ exports.BUILTIN_DB_DISPLAY = {
30
+ sql: 'PostgreSQL',
31
+ nosql: 'MongoDB',
32
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamby-core",
3
- "version": "0.29.9",
3
+ "version": "0.29.11",
4
4
  "description": "StreamBy middleware framework for media storage management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",