streamby-core 0.29.7 → 0.29.9

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.
@@ -13,6 +13,7 @@ const manager_1 = require("../../models/manager");
13
13
  const connectionManager_1 = require("../../adapters/database/connectionManager");
14
14
  const auth_1 = require("../../utils/auth");
15
15
  const encryption_1 = require("../../utils/encryption");
16
+ const builtinAccess_1 = require("../../utils/builtinAccess");
16
17
  const mongodb_1 = require("mongodb");
17
18
  const crypto_1 = __importDefault(require("crypto"));
18
19
  const VALID_CATEGORIES = ['images', 'audios', 'videos', '3d-models'];
@@ -46,36 +47,43 @@ function getRawFoldersCollection() {
46
47
  return null;
47
48
  return (0, connectionManager_1.getConnection)(activeId).client.db().collection('storage_folders');
48
49
  }
49
- function connFilter(projectId, connId, extra) {
50
- const isBuiltin = connId === 'builtin' || connId.startsWith('builtin-');
51
- const connMatch = isBuiltin
52
- ? { $or: [{ storageConnectionId: connId }, { storageConnectionId: { $exists: false } }] }
53
- : { storageConnectionId: connId };
54
- return { projectId, ...connMatch, ...(extra ?? {}) };
55
- }
56
- async function resolveConnAdapter(connId, project, globalAdapter) {
57
- if (connId === 'builtin' || connId.startsWith('builtin-'))
58
- return globalAdapter;
59
- const conn = project.storageConnections?.find((c) => c.id === connId);
60
- if (!conn)
61
- return { error: 'Storage connection not found', status: 404 };
62
- if (!(0, encryption_1.isEncryptionKeySet)())
63
- return { error: 'Encryption key not set', status: 500 };
64
- const credential = project.credentials?.find((c) => c.id === conn.credentialId);
65
- if (!credential)
66
- return { error: 'Credential not found in project', status: 400 };
67
- try {
68
- const s3Config = JSON.parse((0, encryption_1.decrypt)(credential.encryptedValue));
69
- return new s3_1.S3Adapter(s3Config);
70
- }
71
- catch (e) {
72
- return { error: `Failed to initialize storage adapter: ${e.message}`, status: 500 };
73
- }
74
- }
75
50
  function storageRouter(config) {
76
51
  const router = (0, express_1.Router)();
77
52
  const adapter = config.adapter || (0, storage_1.createStorageProvider)(config.storageProviders);
78
53
  const Project = (0, manager_1.getModel)('projects');
54
+ function connFilter(projectId, connId, extra) {
55
+ const isBuiltin = (0, builtinAccess_1.isBuiltinStorageId)(connId, config);
56
+ const connMatch = isBuiltin
57
+ ? { $or: [{ storageConnectionId: connId }, { storageConnectionId: { $exists: false } }] }
58
+ : { storageConnectionId: connId };
59
+ return { projectId, ...connMatch, ...(extra ?? {}) };
60
+ }
61
+ 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'))) {
64
+ return { error: 'Access to this built-in storage is not permitted', status: 403 };
65
+ }
66
+ const provider = config.storageProviders.find(p => p.id === connId);
67
+ if (!provider)
68
+ return { error: 'Storage provider not found', status: 404 };
69
+ return provider.type === 's3' ? new s3_1.S3Adapter(provider.config) : adapter;
70
+ }
71
+ const conn = project.storageConnections?.find((c) => c.id === connId);
72
+ if (!conn)
73
+ return { error: 'Storage connection not found', status: 404 };
74
+ if (!(0, encryption_1.isEncryptionKeySet)())
75
+ return { error: 'Encryption key not set', status: 500 };
76
+ const credential = project.credentials?.find((c) => c.id === conn.credentialId);
77
+ if (!credential)
78
+ return { error: 'Credential not found in project', status: 400 };
79
+ try {
80
+ const s3Config = JSON.parse((0, encryption_1.decrypt)(credential.encryptedValue));
81
+ return new s3_1.S3Adapter(s3Config);
82
+ }
83
+ catch (e) {
84
+ return { error: `Failed to initialize storage adapter: ${e.message}`, status: 500 };
85
+ }
86
+ }
79
87
  router.get('/upload-project-image-url/:id', async (req, res) => {
80
88
  try {
81
89
  const auth = req.auth;
@@ -301,7 +309,7 @@ function storageRouter(config) {
301
309
  return res.status(404).json({ message: 'Project not found' });
302
310
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
303
311
  return res.status(403).json({ message: 'Unauthorized access' });
304
- const connAdapter = await resolveConnAdapter(connId, project, adapter);
312
+ const connAdapter = await resolveConnAdapter(connId, project, auth);
305
313
  if ('error' in connAdapter)
306
314
  return res.status(connAdapter.status).json({ message: connAdapter.error });
307
315
  if (!connAdapter.getPresignedUploadUrl)
@@ -340,7 +348,7 @@ function storageRouter(config) {
340
348
  return res.status(404).json({ message: 'Project not found' });
341
349
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
342
350
  return res.status(403).json({ message: 'Unauthorized access' });
343
- const connAdapter = await resolveConnAdapter(connId, project, adapter);
351
+ const connAdapter = await resolveConnAdapter(connId, project, auth);
344
352
  if ('error' in connAdapter)
345
353
  return res.status(connAdapter.status).json({ message: connAdapter.error });
346
354
  const collection = getRawFilesCollection();
@@ -366,7 +374,7 @@ function storageRouter(config) {
366
374
  return res.status(404).json({ message: 'Project not found' });
367
375
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
368
376
  return res.status(403).json({ message: 'Unauthorized access' });
369
- const connAdapter = await resolveConnAdapter(connId, project, adapter);
377
+ const connAdapter = await resolveConnAdapter(connId, project, auth);
370
378
  if ('error' in connAdapter)
371
379
  return res.status(connAdapter.status).json({ message: connAdapter.error });
372
380
  const collection = getRawFilesCollection();
@@ -402,7 +410,7 @@ function storageRouter(config) {
402
410
  if (!validateContentType(contentType, fileName, fileDoc.category)) {
403
411
  return res.status(400).json({ message: `File does not match category ${fileDoc.category}` });
404
412
  }
405
- const connAdapter = await resolveConnAdapter(connId, project, adapter);
413
+ const connAdapter = await resolveConnAdapter(connId, project, auth);
406
414
  if ('error' in connAdapter)
407
415
  return res.status(connAdapter.status).json({ message: connAdapter.error });
408
416
  if (!connAdapter.getPresignedUploadUrl) {
@@ -450,7 +458,7 @@ function storageRouter(config) {
450
458
  return res.status(404).json({ message: 'Project not found' });
451
459
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
452
460
  return res.status(403).json({ message: 'Unauthorized access' });
453
- const connAdapter = await resolveConnAdapter(connId, project, adapter);
461
+ const connAdapter = await resolveConnAdapter(connId, project, auth);
454
462
  if ('error' in connAdapter)
455
463
  return res.status(connAdapter.status).json({ message: connAdapter.error });
456
464
  const collection = getRawFilesCollection();
@@ -549,11 +557,14 @@ function storageRouter(config) {
549
557
  return res.status(404).json({ message: 'Project not found' });
550
558
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
551
559
  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'))) {
562
+ return res.status(403).json({ message: 'Access to this built-in storage is not permitted' });
563
+ }
552
564
  const collection = getRawFoldersCollection();
553
565
  if (!collection)
554
566
  return res.status(500).json({ message: 'Database not available' });
555
567
  const folderId = crypto_1.default.randomUUID();
556
- const isBuiltin = connId === 'builtin' || connId.startsWith('builtin-');
557
568
  const folderDoc = {
558
569
  _id: new mongodb_1.ObjectId(),
559
570
  folderId,
@@ -717,6 +728,7 @@ function storageRouter(config) {
717
728
  r2: { value: 'cloudflare-r2', name: 'Cloudflare R2' },
718
729
  };
719
730
  const storages = (config.storageProviders || []).map(provider => ({
731
+ id: provider.id,
720
732
  ...STORAGE_DISPLAY[provider.type],
721
733
  type: provider.type,
722
734
  }));
@@ -5,13 +5,8 @@ const express_1 = require("express");
5
5
  const mongodb_1 = require("mongodb");
6
6
  const manager_1 = require("../../models/manager");
7
7
  const auth_1 = require("../../utils/auth");
8
+ const builtinAccess_1 = require("../../utils/builtinAccess");
8
9
  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
10
  function storageConnectionRouter(config) {
16
11
  const router = (0, express_1.Router)();
17
12
  const Project = (0, manager_1.getModel)('projects');
@@ -24,17 +19,16 @@ function storageConnectionRouter(config) {
24
19
  return res.status(404).json({ message: 'Project not found' });
25
20
  if (!(0, auth_1.isProjectMember)(project, auth.userId))
26
21
  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,
22
+ const connections = project.storageConnections || [];
23
+ // Recompute availability live for builtins — never trust a stored flag, per
24
+ // "validate on every use" (TCORE-69). BYOC (source: 'integration') is always available.
25
+ const data = await Promise.all(connections.map(async (conn) => {
26
+ if (conn.source !== 'builtin')
27
+ return conn;
28
+ const available = await (0, builtinAccess_1.assertBuiltinAccess)(auth, conn.integrationId ?? conn.id, config, 'storage');
29
+ return { ...conn, isBuiltin: true, available };
35
30
  }));
36
- const projectConnections = project.storageConnections || [];
37
- return res.status(200).json({ data: [...builtinConnections, ...projectConnections] });
31
+ return res.status(200).json({ data });
38
32
  }
39
33
  catch (err) {
40
34
  res.status(500).json({ message: 'Failed to fetch storage connections', details: err.message });
@@ -82,7 +76,7 @@ function storageConnectionRouter(config) {
82
76
  router.delete('/projects/:id/connections/storage/:connId', async (req, res) => {
83
77
  try {
84
78
  const { connId } = req.params;
85
- if (connId === 'builtin' || connId.startsWith('builtin-')) {
79
+ if ((0, builtinAccess_1.isBuiltinStorageId)(connId, config)) {
86
80
  return res.status(403).json({ message: 'Cannot delete a built-in storage connection' });
87
81
  }
88
82
  const auth = req.auth;
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig } from '../../types';
3
+ export declare function userIntegrationRouter(config: StreamByConfig): Router;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.userIntegrationRouter = userIntegrationRouter;
4
+ const express_1 = require("express");
5
+ const builtinAccess_1 = require("../../utils/builtinAccess");
6
+ const userIntegration_1 = require("../../services/userIntegration");
7
+ const sanitize_1 = require("../../utils/sanitize");
8
+ const VALID_KINDS = ['database', 'storage'];
9
+ const VALID_DB_PROVIDERS = ['postgresql', 'mongodb'];
10
+ const VALID_STORAGE_PROVIDERS = ['s3', 'gcs', 'r2', 'azure'];
11
+ function userIntegrationRouter(config) {
12
+ const router = (0, express_1.Router)();
13
+ // ─── Combined pool: account integrations + built-ins the user can access ─────
14
+ router.get('/user/integrations', async (req, res) => {
15
+ try {
16
+ const auth = req.auth;
17
+ const owned = await (0, userIntegration_1.listUserIntegrations)(auth.userId);
18
+ const pool = owned.map(i => ({
19
+ id: i.id, kind: i.kind, name: i.name, provider: i.provider, source: 'integration', available: true,
20
+ }));
21
+ for (const db of config.databases ?? []) {
22
+ // requiredPlan is not populated here — canUseBuiltin only returns a boolean today,
23
+ // so there's no plan-level detail to surface without a hook-signature change.
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 });
26
+ }
27
+ for (const provider of config.storageProviders ?? []) {
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 });
30
+ }
31
+ res.json({ data: pool });
32
+ }
33
+ catch (err) {
34
+ res.status(500).json({ message: 'Failed to list integrations', details: err.message });
35
+ }
36
+ });
37
+ // ─── Create ────────────────────────────────────────────────────────────────
38
+ router.post('/user/integrations', async (req, res) => {
39
+ try {
40
+ const auth = req.auth;
41
+ const { kind, provider, name, description, credential } = req.body;
42
+ if (!kind || !VALID_KINDS.includes(kind)) {
43
+ return res.status(400).json({ message: `kind must be one of: ${VALID_KINDS.join(', ')}` });
44
+ }
45
+ const validProviders = kind === 'database' ? VALID_DB_PROVIDERS : VALID_STORAGE_PROVIDERS;
46
+ if (!provider || !validProviders.includes(provider)) {
47
+ return res.status(400).json({ message: `provider must be one of: ${validProviders.join(', ')}` });
48
+ }
49
+ if (!name)
50
+ return res.status(400).json({ message: 'name is required' });
51
+ if (credential === undefined || credential === null) {
52
+ return res.status(400).json({ message: 'credential is required' });
53
+ }
54
+ const integration = await (0, userIntegration_1.createUserIntegration)(auth.userId, {
55
+ kind, provider, name, description, credentialValue: credential,
56
+ });
57
+ res.status(201).json({ data: (0, sanitize_1.sanitizeUserIntegration)(integration) });
58
+ }
59
+ catch (err) {
60
+ res.status(500).json({ message: 'Failed to create integration', details: err.message });
61
+ }
62
+ });
63
+ // ─── Update ────────────────────────────────────────────────────────────────
64
+ router.patch('/user/integrations/:integrationId', async (req, res) => {
65
+ try {
66
+ const auth = req.auth;
67
+ const { name, description, credential } = req.body;
68
+ const updated = await (0, userIntegration_1.updateUserIntegration)(auth.userId, req.params.integrationId, {
69
+ name, description, credentialValue: credential,
70
+ });
71
+ if (!updated)
72
+ return res.status(404).json({ message: 'Integration not found' });
73
+ res.json({ data: (0, sanitize_1.sanitizeUserIntegration)(updated) });
74
+ }
75
+ catch (err) {
76
+ res.status(500).json({ message: 'Failed to update integration', details: err.message });
77
+ }
78
+ });
79
+ // ─── Delete ────────────────────────────────────────────────────────────────
80
+ router.delete('/user/integrations/:integrationId', async (req, res) => {
81
+ try {
82
+ const auth = req.auth;
83
+ const deleted = await (0, userIntegration_1.deleteUserIntegration)(auth.userId, req.params.integrationId);
84
+ if (!deleted)
85
+ return res.status(404).json({ message: 'Integration not found' });
86
+ res.json({ message: 'Integration deleted' });
87
+ }
88
+ catch (err) {
89
+ res.status(500).json({ message: 'Failed to delete integration', details: err.message });
90
+ }
91
+ });
92
+ return router;
93
+ }
@@ -45,11 +45,11 @@ class Model {
45
45
  // Handle members filter for SQL projects
46
46
  const userId = processedFilter.members.$elemMatch.userId;
47
47
  const schema = this.schema || 'streamby'; // Default to 'streamby' if schema not provided
48
- const query = `
49
- SELECT p.*, pm."userId" as memberUserId, pm.role as memberRole, pm.status as memberStatus, pm.archived as memberArchived
50
- FROM "${schema}"."projects" p
51
- JOIN "${schema}"."project_members" pm ON p.id = pm."projectId"
52
- WHERE pm."userId" = $1
48
+ const query = `
49
+ SELECT p.*, pm."userId" as memberUserId, pm.role as memberRole, pm.status as memberStatus, pm.archived as memberArchived
50
+ FROM "${schema}"."projects" p
51
+ JOIN "${schema}"."project_members" pm ON p.id = pm."projectId"
52
+ WHERE pm."userId" = $1
53
53
  `;
54
54
  const result = await connection.query(query, [userId]);
55
55
  sqlResults = result.rows.map(row => ({
@@ -219,10 +219,10 @@ class Model {
219
219
  else {
220
220
  const matchKey = value.id !== undefined ? 'id' : Object.keys(value)[0];
221
221
  const matchVal = String(value[matchKey]);
222
- await connection.query(`UPDATE ${tbl} SET "${field}" = (
223
- SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb)
224
- FROM jsonb_array_elements("${field}") elem
225
- WHERE elem->>'${matchKey}' != $1
222
+ await connection.query(`UPDATE ${tbl} SET "${field}" = (
223
+ SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb)
224
+ FROM jsonb_array_elements("${field}") elem
225
+ WHERE elem->>'${matchKey}' != $1
226
226
  ) WHERE id = $2`, [matchVal, id]);
227
227
  }
228
228
  }
@@ -0,0 +1,2 @@
1
+ import { StreamByConfig } from '../types';
2
+ export declare function backfillBuiltinConnections(config: StreamByConfig): Promise<void>;
@@ -0,0 +1,56 @@
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.backfillBuiltinConnections = backfillBuiltinConnections;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const manager_1 = require("../models/manager");
9
+ // Pre-BYOC, every project appeared connected to every configured builtin because
10
+ // storageConnection.ts/dbConnection.ts SYNTHESIZED builtins at read time instead of
11
+ // persisting them (see storageConnection.ts's old GET handler). Now that connections must
12
+ // be real rows on the project, this seeds those rows once for projects that predate the
13
+ // change — so existing projects don't silently lose access on deploy.
14
+ //
15
+ // Guarded by ProjectInfo.builtinBackfilledAt: runs once per project, ever. An admin who
16
+ // later disconnects a builtin on purpose won't have it silently reappear on the next boot.
17
+ async function backfillBuiltinConnections(config) {
18
+ if (!config.databases?.length && !config.storageProviders?.length)
19
+ return;
20
+ const Project = (0, manager_1.getModel)('projects');
21
+ const allProjects = await Project.find({});
22
+ const pending = allProjects.filter(p => !p.builtinBackfilledAt);
23
+ if (!pending.length)
24
+ return;
25
+ for (const project of pending) {
26
+ const now = new Date();
27
+ const dbConnections = (config.databases ?? []).map(db => ({
28
+ id: crypto_1.default.randomUUID(),
29
+ name: db.id,
30
+ dbType: db.type === 'sql' ? 'postgresql' : 'mongodb',
31
+ credentialId: '',
32
+ projectId: project.id,
33
+ createdAt: now,
34
+ isBuiltin: true,
35
+ integrationId: db.id,
36
+ source: 'builtin',
37
+ }));
38
+ const storageConnections = (config.storageProviders ?? []).map(provider => ({
39
+ id: crypto_1.default.randomUUID(),
40
+ name: provider.id,
41
+ type: provider.type,
42
+ credentialId: '',
43
+ projectId: project.id,
44
+ createdAt: now,
45
+ isBuiltin: true,
46
+ integrationId: provider.id,
47
+ source: 'builtin',
48
+ }));
49
+ await Project.update({ _id: project.id }, {
50
+ dbConnections: [...(project.dbConnections ?? []), ...dbConnections],
51
+ storageConnections: [...(project.storageConnections ?? []), ...storageConnections],
52
+ builtinBackfilledAt: now,
53
+ });
54
+ }
55
+ console.log(`✅ Backfilled built-in connections for ${pending.length} project(s).`);
56
+ }
@@ -0,0 +1,16 @@
1
+ import { IntegrationKind, UserIntegration } from '../types';
2
+ export declare function listUserIntegrations(userId: string): Promise<UserIntegration[]>;
3
+ export declare function createUserIntegration(userId: string, input: {
4
+ kind: IntegrationKind;
5
+ provider: string;
6
+ name: string;
7
+ description?: string;
8
+ credentialValue: unknown;
9
+ }): Promise<UserIntegration>;
10
+ export declare function updateUserIntegration(userId: string, integrationId: string, updates: {
11
+ name?: string;
12
+ description?: string;
13
+ credentialValue?: unknown;
14
+ }): Promise<UserIntegration | null>;
15
+ export declare function deleteUserIntegration(userId: string, integrationId: string): Promise<boolean>;
16
+ export declare function getDecryptedIntegrationCredential(userId: string, integrationId: string): Promise<unknown | null>;
@@ -0,0 +1,73 @@
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.listUserIntegrations = listUserIntegrations;
7
+ exports.createUserIntegration = createUserIntegration;
8
+ exports.updateUserIntegration = updateUserIntegration;
9
+ exports.deleteUserIntegration = deleteUserIntegration;
10
+ exports.getDecryptedIntegrationCredential = getDecryptedIntegrationCredential;
11
+ const crypto_1 = __importDefault(require("crypto"));
12
+ const manager_1 = require("../models/manager");
13
+ const encryption_1 = require("../utils/encryption");
14
+ function encryptCredential(kind, value) {
15
+ return kind === 'storage' ? (0, encryption_1.encrypt)(JSON.stringify(value)) : (0, encryption_1.encrypt)(String(value));
16
+ }
17
+ async function listUserIntegrations(userId) {
18
+ const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
19
+ return UserIntegrationModel.find({ userId });
20
+ }
21
+ async function createUserIntegration(userId, input) {
22
+ if (!(0, encryption_1.isEncryptionKeySet)()) {
23
+ throw new Error('Encryption key is not set. Cannot create integration.');
24
+ }
25
+ const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
26
+ const now = new Date();
27
+ const integration = {
28
+ id: crypto_1.default.randomUUID(),
29
+ userId,
30
+ kind: input.kind,
31
+ provider: input.provider,
32
+ name: input.name,
33
+ description: input.description,
34
+ encryptedCredential: encryptCredential(input.kind, input.credentialValue),
35
+ createdAt: now,
36
+ updatedAt: now,
37
+ };
38
+ return UserIntegrationModel.create(integration);
39
+ }
40
+ async function updateUserIntegration(userId, integrationId, updates) {
41
+ const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
42
+ const existing = await UserIntegrationModel.findOne({ id: integrationId, userId });
43
+ if (!existing)
44
+ return null;
45
+ const $set = { updatedAt: new Date() };
46
+ if (updates.name !== undefined)
47
+ $set.name = updates.name;
48
+ if (updates.description !== undefined)
49
+ $set.description = updates.description;
50
+ if (updates.credentialValue !== undefined) {
51
+ if (!(0, encryption_1.isEncryptionKeySet)()) {
52
+ throw new Error('Encryption key is not set. Cannot update integration.');
53
+ }
54
+ $set.encryptedCredential = encryptCredential(existing.kind, updates.credentialValue);
55
+ }
56
+ return UserIntegrationModel.update({ id: integrationId, userId }, $set);
57
+ }
58
+ async function deleteUserIntegration(userId, integrationId) {
59
+ const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
60
+ const deletedCount = await UserIntegrationModel.delete({ id: integrationId, userId });
61
+ return deletedCount > 0;
62
+ }
63
+ async function getDecryptedIntegrationCredential(userId, integrationId) {
64
+ const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
65
+ const integration = await UserIntegrationModel.findOne({ id: integrationId, userId });
66
+ if (!integration)
67
+ return null;
68
+ if (!(0, encryption_1.isEncryptionKeySet)()) {
69
+ throw new Error('Encryption key is not set. Cannot decrypt integration credential.');
70
+ }
71
+ const decrypted = (0, encryption_1.decrypt)(integration.encryptedCredential);
72
+ return integration.kind === 'storage' ? JSON.parse(decrypted) : decrypted;
73
+ }
@@ -17,8 +17,12 @@ export interface StorageConnection {
17
17
  createdAt: Date;
18
18
  description?: string;
19
19
  isBuiltin?: boolean;
20
+ integrationId?: string;
21
+ source?: 'builtin' | 'integration';
22
+ available?: boolean;
20
23
  }
21
24
  export type StorageProvider = {
25
+ id: string;
22
26
  type: StorageProviderType;
23
27
  config: S3Config;
24
28
  };
@@ -73,6 +77,8 @@ export interface ProjectInfo {
73
77
  apiConnections?: ApiConnection[];
74
78
  dbConnections?: DbConnection[];
75
79
  storageConnections?: StorageConnection[];
80
+ pipelines?: PipelineRef[];
81
+ builtinBackfilledAt?: Date;
76
82
  }
77
83
  export interface Credential {
78
84
  id: string;
@@ -106,10 +112,7 @@ export interface DatabaseCredential {
106
112
  main?: boolean;
107
113
  }
108
114
  export interface StreamByConfig {
109
- storageProviders: {
110
- type: StorageProviderType;
111
- config: S3Config;
112
- }[];
115
+ storageProviders: StorageProvider[];
113
116
  authProvider: AuthProvider;
114
117
  databases?: DatabaseCredential[];
115
118
  adapter?: StorageAdapter;
@@ -117,6 +120,7 @@ export interface StreamByConfig {
117
120
  websocket?: {
118
121
  server: WebSocketServer;
119
122
  };
123
+ canUseBuiltin?: (auth: Auth, builtinId: string, kind: 'database' | 'storage') => boolean | Promise<boolean>;
120
124
  }
121
125
  export interface Notification {
122
126
  _id?: string;
@@ -153,6 +157,21 @@ export interface DbConnection {
153
157
  projectId: string;
154
158
  createdAt: Date;
155
159
  description?: string;
160
+ isBuiltin?: boolean;
161
+ integrationId?: string;
162
+ source?: 'builtin' | 'integration';
163
+ }
164
+ export type IntegrationKind = 'database' | 'storage';
165
+ export interface UserIntegration {
166
+ id: string;
167
+ userId: string;
168
+ kind: IntegrationKind;
169
+ provider: ExternalDbType | StorageProviderType;
170
+ name: string;
171
+ description?: string;
172
+ encryptedCredential: string;
173
+ createdAt: Date;
174
+ updatedAt: Date;
156
175
  }
157
176
  export interface ColumnDefinition {
158
177
  name: string;
@@ -168,6 +187,23 @@ export interface NodeSchema {
168
187
  nodes: any[];
169
188
  edges: any[];
170
189
  }
190
+ export interface Pipeline {
191
+ _id?: string;
192
+ id: string;
193
+ projectId: string;
194
+ name: string;
195
+ description?: string | null;
196
+ order: number;
197
+ nodeSchema: NodeSchema | null;
198
+ createdBy: string;
199
+ createdAt: Date;
200
+ updatedAt: Date;
201
+ }
202
+ export interface PipelineRef {
203
+ id: string;
204
+ name: string;
205
+ order: number;
206
+ }
171
207
  export interface Export {
172
208
  _id?: string;
173
209
  id: string;
@@ -0,0 +1,4 @@
1
+ import { Auth, StreamByConfig } from '../types';
2
+ export declare function assertBuiltinAccess(auth: Auth, builtinId: string, config: StreamByConfig, kind: 'database' | 'storage'): Promise<boolean>;
3
+ export declare function isBuiltinStorageId(id: string, config: StreamByConfig): boolean;
4
+ export declare function isBuiltinDb(id: string, config: StreamByConfig): boolean;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertBuiltinAccess = assertBuiltinAccess;
4
+ exports.isBuiltinStorageId = isBuiltinStorageId;
5
+ exports.isBuiltinDb = isBuiltinDb;
6
+ // Gates access to a built-in database/storage provider. Delegates to config.canUseBuiltin
7
+ // when the mounting app implements it (e.g. Nhexa-API checking user_subscriptions);
8
+ // defaults to allow when absent, preserving pre-BYOC behavior for deploys without gating.
9
+ async function assertBuiltinAccess(auth, builtinId, config, kind) {
10
+ if (!config.canUseBuiltin)
11
+ return true;
12
+ return config.canUseBuiltin(auth, builtinId, kind);
13
+ }
14
+ function isBuiltinStorageId(id, config) {
15
+ return (config.storageProviders ?? []).some(p => p.id === id);
16
+ }
17
+ function isBuiltinDb(id, config) {
18
+ return (config.databases ?? []).some(db => db.id === id);
19
+ }
@@ -1 +1,2 @@
1
1
  export declare function sanitizeProject(project: any): any;
2
+ export declare function sanitizeUserIntegration(integration: any): any;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sanitizeProject = sanitizeProject;
4
+ exports.sanitizeUserIntegration = sanitizeUserIntegration;
4
5
  function sanitizeProject(project) {
5
6
  if (!project)
6
7
  return project;
@@ -9,3 +10,9 @@ function sanitizeProject(project) {
9
10
  credentials: project.credentials?.map(({ id, key }) => ({ id, key })) ?? [],
10
11
  };
11
12
  }
13
+ function sanitizeUserIntegration(integration) {
14
+ if (!integration)
15
+ return integration;
16
+ const { encryptedCredential, ...rest } = integration;
17
+ return rest;
18
+ }