streamby-core 0.31.0 → 0.32.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.
- package/README.md +283 -283
- package/dist/adapters/database/connectionManager.js +83 -78
- package/dist/adapters/database/sql.js +1 -1
- package/dist/middleware/routes/dbConnection.js +46 -210
- package/dist/middleware/routes/storage.js +2 -42
- package/dist/models/Model.js +9 -9
- package/dist/services/connectionResolver.d.ts +19 -0
- package/dist/services/connectionResolver.js +132 -0
- package/dist/services/executor.d.ts +5 -0
- package/dist/services/executor.js +44 -15
- package/dist/types/index.d.ts +12 -0
- package/dist/types/nodePersistence.d.ts +3 -0
- package/dist/types/nodePersistence.js +15 -0
- package/package.json +63 -63
- package/dist/utils/crypto.d.ts +0 -3
- package/dist/utils/crypto.js +0 -32
|
@@ -43,103 +43,108 @@ const getConnection = (id) => {
|
|
|
43
43
|
exports.getConnection = getConnection;
|
|
44
44
|
const ensureTablesExist = async (pool) => {
|
|
45
45
|
try {
|
|
46
|
-
await pool.query(`
|
|
47
|
-
CREATE SCHEMA IF NOT EXISTS streamby;
|
|
48
|
-
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
46
|
+
await pool.query(`
|
|
47
|
+
CREATE SCHEMA IF NOT EXISTS streamby;
|
|
48
|
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
49
49
|
`);
|
|
50
50
|
// Migrate id column from UUID to TEXT if it was created with the old schema
|
|
51
|
-
await pool.query(`
|
|
52
|
-
DO $$ BEGIN
|
|
53
|
-
ALTER TABLE streamby.projects ALTER COLUMN id TYPE TEXT USING id::text;
|
|
54
|
-
ALTER TABLE streamby.projects ALTER COLUMN id SET DEFAULT uuid_generate_v4()::text;
|
|
55
|
-
EXCEPTION WHEN OTHERS THEN NULL; END $$;
|
|
51
|
+
await pool.query(`
|
|
52
|
+
DO $$ BEGIN
|
|
53
|
+
ALTER TABLE streamby.projects ALTER COLUMN id TYPE TEXT USING id::text;
|
|
54
|
+
ALTER TABLE streamby.projects ALTER COLUMN id SET DEFAULT uuid_generate_v4()::text;
|
|
55
|
+
EXCEPTION WHEN OTHERS THEN NULL; END $$;
|
|
56
56
|
`);
|
|
57
|
-
await pool.query(`
|
|
58
|
-
CREATE TABLE IF NOT EXISTS streamby.projects (
|
|
59
|
-
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
60
|
-
name VARCHAR(255) NOT NULL,
|
|
61
|
-
description TEXT,
|
|
62
|
-
image TEXT,
|
|
63
|
-
"allowedOrigin" JSONB DEFAULT '[]',
|
|
64
|
-
exports JSONB DEFAULT '[]',
|
|
65
|
-
connections JSONB DEFAULT '[]',
|
|
66
|
-
"dbConnections" JSONB DEFAULT '[]',
|
|
67
|
-
"dbType" VARCHAR(50) NOT NULL DEFAULT 'sql',
|
|
68
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
69
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
70
|
-
);
|
|
57
|
+
await pool.query(`
|
|
58
|
+
CREATE TABLE IF NOT EXISTS streamby.projects (
|
|
59
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
60
|
+
name VARCHAR(255) NOT NULL,
|
|
61
|
+
description TEXT,
|
|
62
|
+
image TEXT,
|
|
63
|
+
"allowedOrigin" JSONB DEFAULT '[]',
|
|
64
|
+
exports JSONB DEFAULT '[]',
|
|
65
|
+
connections JSONB DEFAULT '[]',
|
|
66
|
+
"dbConnections" JSONB DEFAULT '[]',
|
|
67
|
+
"dbType" VARCHAR(50) NOT NULL DEFAULT 'sql',
|
|
68
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
69
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
70
|
+
);
|
|
71
71
|
`);
|
|
72
72
|
console.log('✅ "projects" table ensured to exist.');
|
|
73
73
|
// Add JSONB columns to existing tables that predate this schema version
|
|
74
|
-
await pool.query(`
|
|
75
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "allowedOrigin" JSONB DEFAULT '[]';
|
|
76
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS exports JSONB DEFAULT '[]';
|
|
77
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS connections JSONB DEFAULT '[]';
|
|
78
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbConnections" JSONB DEFAULT '[]';
|
|
79
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "storageConnections" JSONB DEFAULT '[]';
|
|
80
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS workflow JSONB;
|
|
81
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS pipelines JSONB DEFAULT '[]';
|
|
82
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "builtinBackfilledAt" TIMESTAMP WITH TIME ZONE;
|
|
74
|
+
await pool.query(`
|
|
75
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "allowedOrigin" JSONB DEFAULT '[]';
|
|
76
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS exports JSONB DEFAULT '[]';
|
|
77
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS connections JSONB DEFAULT '[]';
|
|
78
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbConnections" JSONB DEFAULT '[]';
|
|
79
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "storageConnections" JSONB DEFAULT '[]';
|
|
80
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS workflow JSONB;
|
|
81
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS pipelines JSONB DEFAULT '[]';
|
|
82
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "builtinBackfilledAt" TIMESTAMP WITH TIME ZONE;
|
|
83
|
+
-- Reverted: a project has no notion of a "default" database/storage connection —
|
|
84
|
+
-- each dataSourceNode/ingestNode always declares its own connection explicitly.
|
|
85
|
+
ALTER TABLE streamby.projects DROP COLUMN IF EXISTS "defaultDatabaseId";
|
|
86
|
+
ALTER TABLE streamby.projects DROP COLUMN IF EXISTS "defaultStorageId";
|
|
87
|
+
ALTER TABLE streamby.projects DROP COLUMN IF EXISTS "defaultConnectionsBackfilledAt";
|
|
83
88
|
`);
|
|
84
89
|
// Ensure dbType exists with a non-null default
|
|
85
|
-
await pool.query(`
|
|
86
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql';
|
|
90
|
+
await pool.query(`
|
|
91
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql';
|
|
87
92
|
`);
|
|
88
|
-
await pool.query(`
|
|
89
|
-
CREATE TABLE IF NOT EXISTS streamby.project_members (
|
|
90
|
-
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
91
|
-
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
92
|
-
"userId" VARCHAR(255) NOT NULL,
|
|
93
|
-
role VARCHAR(255) NOT NULL DEFAULT 'member',
|
|
94
|
-
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
95
|
-
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
96
|
-
"archivedBy" VARCHAR(255),
|
|
97
|
-
"archivedAt" TIMESTAMP WITH TIME ZONE,
|
|
98
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
99
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
100
|
-
UNIQUE ("projectId", "userId")
|
|
101
|
-
);
|
|
93
|
+
await pool.query(`
|
|
94
|
+
CREATE TABLE IF NOT EXISTS streamby.project_members (
|
|
95
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
96
|
+
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
97
|
+
"userId" VARCHAR(255) NOT NULL,
|
|
98
|
+
role VARCHAR(255) NOT NULL DEFAULT 'member',
|
|
99
|
+
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
100
|
+
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
101
|
+
"archivedBy" VARCHAR(255),
|
|
102
|
+
"archivedAt" TIMESTAMP WITH TIME ZONE,
|
|
103
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
104
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
105
|
+
UNIQUE ("projectId", "userId")
|
|
106
|
+
);
|
|
102
107
|
`);
|
|
103
|
-
await pool.query(`
|
|
104
|
-
ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
|
|
108
|
+
await pool.query(`
|
|
109
|
+
ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
|
|
105
110
|
`);
|
|
106
111
|
console.log('✅ "project_members" table ensured to exist.');
|
|
107
112
|
// Migrate projectId FK column from UUID to TEXT if table predates this schema version
|
|
108
|
-
await pool.query(`
|
|
109
|
-
DO $$ BEGIN
|
|
110
|
-
ALTER TABLE streamby.project_members DROP CONSTRAINT IF EXISTS project_members_projectId_fkey;
|
|
111
|
-
ALTER TABLE streamby.project_members ALTER COLUMN "projectId" TYPE TEXT;
|
|
112
|
-
ALTER TABLE streamby.project_members ADD CONSTRAINT project_members_projectid_fkey
|
|
113
|
-
FOREIGN KEY ("projectId") REFERENCES streamby.projects(id) ON DELETE CASCADE;
|
|
114
|
-
EXCEPTION WHEN OTHERS THEN NULL; END $$;
|
|
113
|
+
await pool.query(`
|
|
114
|
+
DO $$ BEGIN
|
|
115
|
+
ALTER TABLE streamby.project_members DROP CONSTRAINT IF EXISTS project_members_projectId_fkey;
|
|
116
|
+
ALTER TABLE streamby.project_members ALTER COLUMN "projectId" TYPE TEXT;
|
|
117
|
+
ALTER TABLE streamby.project_members ADD CONSTRAINT project_members_projectid_fkey
|
|
118
|
+
FOREIGN KEY ("projectId") REFERENCES streamby.projects(id) ON DELETE CASCADE;
|
|
119
|
+
EXCEPTION WHEN OTHERS THEN NULL; END $$;
|
|
115
120
|
`);
|
|
116
|
-
await pool.query(`
|
|
117
|
-
CREATE TABLE IF NOT EXISTS streamby.exports (
|
|
118
|
-
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
119
|
-
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
120
|
-
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
121
|
-
"filePath" TEXT,
|
|
122
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
123
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
124
|
-
);
|
|
121
|
+
await pool.query(`
|
|
122
|
+
CREATE TABLE IF NOT EXISTS streamby.exports (
|
|
123
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
124
|
+
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
125
|
+
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
126
|
+
"filePath" TEXT,
|
|
127
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
128
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
129
|
+
);
|
|
125
130
|
`);
|
|
126
131
|
console.log('✅ "exports" table ensured to exist.');
|
|
127
132
|
// user_integrations is registered against the main db only (see createRouter.ts), but
|
|
128
133
|
// this function runs per-SQL-connection like projects/exports above — harmless on
|
|
129
134
|
// non-main connections since the model never queries them there.
|
|
130
|
-
await pool.query(`
|
|
131
|
-
CREATE TABLE IF NOT EXISTS streamby.user_integrations (
|
|
132
|
-
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
133
|
-
"userId" VARCHAR(255) NOT NULL,
|
|
134
|
-
kind VARCHAR(20) NOT NULL,
|
|
135
|
-
provider VARCHAR(20) NOT NULL,
|
|
136
|
-
name VARCHAR(255) NOT NULL,
|
|
137
|
-
description TEXT,
|
|
138
|
-
"encryptedCredential" TEXT NOT NULL,
|
|
139
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
140
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
141
|
-
);
|
|
142
|
-
CREATE INDEX IF NOT EXISTS user_integrations_userid_idx ON streamby.user_integrations ("userId");
|
|
135
|
+
await pool.query(`
|
|
136
|
+
CREATE TABLE IF NOT EXISTS streamby.user_integrations (
|
|
137
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
138
|
+
"userId" VARCHAR(255) NOT NULL,
|
|
139
|
+
kind VARCHAR(20) NOT NULL,
|
|
140
|
+
provider VARCHAR(20) NOT NULL,
|
|
141
|
+
name VARCHAR(255) NOT NULL,
|
|
142
|
+
description TEXT,
|
|
143
|
+
"encryptedCredential" TEXT NOT NULL,
|
|
144
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
145
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
146
|
+
);
|
|
147
|
+
CREATE INDEX IF NOT EXISTS user_integrations_userid_idx ON streamby.user_integrations ("userId");
|
|
143
148
|
`);
|
|
144
149
|
console.log('✅ "user_integrations" table ensured to exist.');
|
|
145
150
|
}
|
|
@@ -10,7 +10,7 @@ function serializeForPg(v) {
|
|
|
10
10
|
}
|
|
11
11
|
const createSQLExportTable = async (connection, projectId, exportName, nodeSchema) => {
|
|
12
12
|
const exportId = (0, crypto_1.randomUUID)();
|
|
13
|
-
await connection.query(`INSERT INTO "exports" (id, "projectId", node_schema, name, method, created_at, updated_at)
|
|
13
|
+
await connection.query(`INSERT INTO "exports" (id, "projectId", node_schema, name, method, created_at, updated_at)
|
|
14
14
|
VALUES ($1, $2, $3, $4, 'GET', NOW(), NOW())`, [exportId, projectId, nodeSchema ? JSON.stringify(nodeSchema) : null, exportName]);
|
|
15
15
|
return { exportId };
|
|
16
16
|
};
|
|
@@ -1,88 +1,31 @@
|
|
|
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
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.dbConnectionRouter = dbConnectionRouter;
|
|
37
4
|
const express_1 = require("express");
|
|
38
5
|
const mongodb_1 = require("mongodb");
|
|
39
6
|
const manager_1 = require("../../models/manager");
|
|
40
7
|
const auth_1 = require("../../utils/auth");
|
|
41
|
-
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
42
|
-
const builtinAccess_1 = require("../../utils/builtinAccess");
|
|
43
|
-
const userIntegration_1 = require("../../services/userIntegration");
|
|
44
8
|
const sanitize_1 = require("../../utils/sanitize");
|
|
45
9
|
const encryption_1 = require("../../utils/encryption");
|
|
10
|
+
const connectionResolver_1 = require("../../services/connectionResolver");
|
|
46
11
|
const dbConnection_1 = require("../../services/dbConnection");
|
|
47
12
|
const VALID_DB_TYPES = ['postgresql', 'mongodb'];
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return
|
|
13
|
+
// Resolves the client for `connId`, runs `fn`, and writes the response — releasing
|
|
14
|
+
// ephemeral (non-builtin) clients afterward. Builtin clients are pooled and owned by
|
|
15
|
+
// connectionManager, never closed here. Responds with the resolve error directly if
|
|
16
|
+
// resolution fails, so callers never have to distinguish an error shape from fn's result.
|
|
17
|
+
async function withResolvedDbClient(project, connId, config, auth, res, fn) {
|
|
18
|
+
const resolved = await (0, connectionResolver_1.resolveDbConnectionClient)(project, connId, config, auth);
|
|
19
|
+
if ('error' in resolved) {
|
|
20
|
+
res.status(resolved.status).json({ message: resolved.error });
|
|
21
|
+
return;
|
|
57
22
|
}
|
|
58
|
-
|
|
59
|
-
|
|
23
|
+
try {
|
|
24
|
+
await fn(resolved);
|
|
60
25
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// (persisted at creation/backfill time) — NOT by comparing the row's own id against
|
|
64
|
-
// config.databases, which no longer holds since builtin rows get their own generated id.
|
|
65
|
-
function findDbConnection(project, connId) {
|
|
66
|
-
return project.dbConnections?.find((c) => c.id === connId);
|
|
67
|
-
}
|
|
68
|
-
async function getDecryptedConnectionString(project, connId) {
|
|
69
|
-
const conn = project.dbConnections?.find((c) => c.id === connId);
|
|
70
|
-
if (!conn)
|
|
71
|
-
return { error: 'DB connection not found', status: 404 };
|
|
72
|
-
if (conn.source === 'integration') {
|
|
73
|
-
if (!conn.integrationId)
|
|
74
|
-
return { error: 'Connection is missing its integrationId', status: 500 };
|
|
75
|
-
const credential = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId);
|
|
76
|
-
if (!credential)
|
|
77
|
-
return { error: 'Integration not found', status: 400 };
|
|
78
|
-
return { connectionString: credential, conn };
|
|
26
|
+
finally {
|
|
27
|
+
await (0, connectionResolver_1.releaseDbClient)(resolved);
|
|
79
28
|
}
|
|
80
|
-
const { decrypt, isEncryptionKeySet } = await Promise.resolve().then(() => __importStar(require('../../utils/encryption')));
|
|
81
|
-
if (!isEncryptionKeySet())
|
|
82
|
-
return { error: 'Encryption key is not set', status: 500 };
|
|
83
|
-
if (!conn.encryptedCredential)
|
|
84
|
-
return { error: 'Connection has no stored credential', status: 400 };
|
|
85
|
-
return { connectionString: decrypt(conn.encryptedCredential), conn };
|
|
86
29
|
}
|
|
87
30
|
function dbConnectionRouter(config) {
|
|
88
31
|
const router = (0, express_1.Router)();
|
|
@@ -171,7 +114,7 @@ function dbConnectionRouter(config) {
|
|
|
171
114
|
return res.status(404).json({ message: 'Project not found' });
|
|
172
115
|
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
173
116
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
174
|
-
const conn = findDbConnection(project, req.params.connId);
|
|
117
|
+
const conn = (0, connectionResolver_1.findDbConnection)(project, req.params.connId);
|
|
175
118
|
if (!conn)
|
|
176
119
|
return res.status(404).json({ message: 'DB connection not found' });
|
|
177
120
|
if (conn.source === 'builtin') {
|
|
@@ -193,25 +136,10 @@ function dbConnectionRouter(config) {
|
|
|
193
136
|
return res.status(404).json({ message: 'Project not found' });
|
|
194
137
|
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
195
138
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const builtinId = conn.integrationId;
|
|
201
|
-
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
|
|
202
|
-
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
203
|
-
}
|
|
204
|
-
const internal = resolveBuiltinConnection(builtinId, config);
|
|
205
|
-
if ('error' in internal)
|
|
206
|
-
return res.status(internal.status).json({ message: internal.error });
|
|
207
|
-
const tables = await (0, dbConnection_1.listTablesInternal)(internal.client, internal.dbType, req.params.id);
|
|
208
|
-
return res.status(200).json({ data: tables });
|
|
209
|
-
}
|
|
210
|
-
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
211
|
-
if ('error' in resolved)
|
|
212
|
-
return res.status(resolved.status).json({ message: resolved.error });
|
|
213
|
-
const tables = await (0, dbConnection_1.listTablesOrCollections)(resolved.connectionString, resolved.conn.dbType);
|
|
214
|
-
return res.status(200).json({ data: tables });
|
|
139
|
+
await withResolvedDbClient(project, req.params.connId, config, auth, res, async (resolved) => {
|
|
140
|
+
const tables = await (0, dbConnection_1.listTablesInternal)(resolved.client, resolved.type, req.params.id);
|
|
141
|
+
res.status(200).json({ data: tables });
|
|
142
|
+
});
|
|
215
143
|
}
|
|
216
144
|
catch (err) {
|
|
217
145
|
res.status(500).json({ message: 'Failed to list tables', details: err.message });
|
|
@@ -232,31 +160,14 @@ function dbConnectionRouter(config) {
|
|
|
232
160
|
const schema = req.body;
|
|
233
161
|
if (!schema.tableName)
|
|
234
162
|
return res.status(400).json({ message: 'tableName is required' });
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const builtinId = conn.integrationId;
|
|
240
|
-
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
|
|
241
|
-
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
242
|
-
}
|
|
243
|
-
const internal = resolveBuiltinConnection(builtinId, config);
|
|
244
|
-
if ('error' in internal)
|
|
245
|
-
return res.status(internal.status).json({ message: internal.error });
|
|
246
|
-
if (internal.dbType === 'sql' && (!Array.isArray(schema.columns) || schema.columns.length === 0)) {
|
|
247
|
-
return res.status(400).json({ message: 'columns are required for SQL tables' });
|
|
163
|
+
await withResolvedDbClient(project, req.params.connId, config, auth, res, async (resolved) => {
|
|
164
|
+
if (resolved.type === 'sql' && (!Array.isArray(schema.columns) || schema.columns.length === 0)) {
|
|
165
|
+
res.status(400).json({ message: 'columns are required for SQL tables' });
|
|
166
|
+
return;
|
|
248
167
|
}
|
|
249
|
-
await (0, dbConnection_1.createTableOrCollectionInternal)(
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
253
|
-
if ('error' in resolved)
|
|
254
|
-
return res.status(resolved.status).json({ message: resolved.error });
|
|
255
|
-
if (resolved.conn.dbType === 'postgresql' && (!Array.isArray(schema.columns) || schema.columns.length === 0)) {
|
|
256
|
-
return res.status(400).json({ message: 'columns are required for PostgreSQL tables' });
|
|
257
|
-
}
|
|
258
|
-
await (0, dbConnection_1.createTableOrCollection)(resolved.connectionString, resolved.conn.dbType, schema);
|
|
259
|
-
return res.status(201).json({ message: `${resolved.conn.dbType === 'postgresql' ? 'Table' : 'Collection'} created successfully` });
|
|
168
|
+
await (0, dbConnection_1.createTableOrCollectionInternal)(resolved.client, resolved.type, schema, req.params.id);
|
|
169
|
+
res.status(201).json({ message: `${resolved.type === 'sql' ? 'Table' : 'Collection'} created successfully` });
|
|
170
|
+
});
|
|
260
171
|
}
|
|
261
172
|
catch (err) {
|
|
262
173
|
res.status(500).json({ message: 'Failed to create table/collection', details: err.message });
|
|
@@ -274,25 +185,10 @@ function dbConnectionRouter(config) {
|
|
|
274
185
|
return res.status(404).json({ message: 'Project not found' });
|
|
275
186
|
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
276
187
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const builtinId = conn.integrationId;
|
|
282
|
-
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
|
|
283
|
-
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
284
|
-
}
|
|
285
|
-
const internal = resolveBuiltinConnection(builtinId, config);
|
|
286
|
-
if ('error' in internal)
|
|
287
|
-
return res.status(internal.status).json({ message: internal.error });
|
|
288
|
-
await (0, dbConnection_1.deleteTableOrCollectionInternal)(internal.client, internal.dbType, req.params.tableName, req.params.id);
|
|
289
|
-
return res.status(200).json({ message: 'Table/collection deleted' });
|
|
290
|
-
}
|
|
291
|
-
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
292
|
-
if ('error' in resolved)
|
|
293
|
-
return res.status(resolved.status).json({ message: resolved.error });
|
|
294
|
-
await (0, dbConnection_1.deleteTableOrCollection)(resolved.connectionString, resolved.conn.dbType, req.params.tableName);
|
|
295
|
-
return res.status(200).json({ message: 'Table/collection deleted' });
|
|
188
|
+
await withResolvedDbClient(project, req.params.connId, config, auth, res, async (resolved) => {
|
|
189
|
+
await (0, dbConnection_1.deleteTableOrCollectionInternal)(resolved.client, resolved.type, req.params.tableName, req.params.id);
|
|
190
|
+
res.status(200).json({ message: 'Table/collection deleted' });
|
|
191
|
+
});
|
|
296
192
|
}
|
|
297
193
|
catch (err) {
|
|
298
194
|
res.status(500).json({ message: 'Failed to delete table/collection', details: err.message });
|
|
@@ -309,25 +205,10 @@ function dbConnectionRouter(config) {
|
|
|
309
205
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
310
206
|
const limit = Math.min(parseInt(String(req.query.limit ?? 50), 10), 500);
|
|
311
207
|
const offset = Math.max(parseInt(String(req.query.offset ?? 0), 10), 0);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const builtinId = conn.integrationId;
|
|
317
|
-
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
|
|
318
|
-
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
319
|
-
}
|
|
320
|
-
const internal = resolveBuiltinConnection(builtinId, config);
|
|
321
|
-
if ('error' in internal)
|
|
322
|
-
return res.status(internal.status).json({ message: internal.error });
|
|
323
|
-
const records = await (0, dbConnection_1.queryRecordsInternal)(internal.client, internal.dbType, req.params.tableName, limit, offset, req.params.id);
|
|
324
|
-
return res.status(200).json({ data: records });
|
|
325
|
-
}
|
|
326
|
-
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
327
|
-
if ('error' in resolved)
|
|
328
|
-
return res.status(resolved.status).json({ message: resolved.error });
|
|
329
|
-
const records = await (0, dbConnection_1.queryRecords)(resolved.connectionString, resolved.conn.dbType, req.params.tableName, limit, offset);
|
|
330
|
-
return res.status(200).json({ data: records });
|
|
208
|
+
await withResolvedDbClient(project, req.params.connId, config, auth, res, async (resolved) => {
|
|
209
|
+
const records = await (0, dbConnection_1.queryRecordsInternal)(resolved.client, resolved.type, req.params.tableName, limit, offset, req.params.id);
|
|
210
|
+
res.status(200).json({ data: records });
|
|
211
|
+
});
|
|
331
212
|
}
|
|
332
213
|
catch (err) {
|
|
333
214
|
res.status(500).json({ message: 'Failed to query records', details: err.message });
|
|
@@ -349,25 +230,10 @@ function dbConnectionRouter(config) {
|
|
|
349
230
|
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
350
231
|
return res.status(400).json({ message: 'Request body must be a JSON object' });
|
|
351
232
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const builtinId = conn.integrationId;
|
|
357
|
-
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
|
|
358
|
-
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
359
|
-
}
|
|
360
|
-
const internal = resolveBuiltinConnection(builtinId, config);
|
|
361
|
-
if ('error' in internal)
|
|
362
|
-
return res.status(internal.status).json({ message: internal.error });
|
|
363
|
-
const inserted = await (0, dbConnection_1.insertRecordInternal)(internal.client, internal.dbType, req.params.tableName, record, req.params.id);
|
|
364
|
-
return res.status(201).json({ data: inserted });
|
|
365
|
-
}
|
|
366
|
-
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
367
|
-
if ('error' in resolved)
|
|
368
|
-
return res.status(resolved.status).json({ message: resolved.error });
|
|
369
|
-
const inserted = await (0, dbConnection_1.insertRecord)(resolved.connectionString, resolved.conn.dbType, req.params.tableName, record);
|
|
370
|
-
return res.status(201).json({ data: inserted });
|
|
233
|
+
await withResolvedDbClient(project, req.params.connId, config, auth, res, async (resolved) => {
|
|
234
|
+
const inserted = await (0, dbConnection_1.insertRecordInternal)(resolved.client, resolved.type, req.params.tableName, record, req.params.id);
|
|
235
|
+
res.status(201).json({ data: inserted });
|
|
236
|
+
});
|
|
371
237
|
}
|
|
372
238
|
catch (err) {
|
|
373
239
|
res.status(500).json({ message: 'Failed to insert record', details: err.message });
|
|
@@ -388,25 +254,10 @@ function dbConnectionRouter(config) {
|
|
|
388
254
|
if (!updates || typeof updates !== 'object' || Array.isArray(updates)) {
|
|
389
255
|
return res.status(400).json({ message: 'Request body must be a JSON object' });
|
|
390
256
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const builtinId = conn.integrationId;
|
|
396
|
-
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
|
|
397
|
-
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
398
|
-
}
|
|
399
|
-
const internal = resolveBuiltinConnection(builtinId, config);
|
|
400
|
-
if ('error' in internal)
|
|
401
|
-
return res.status(internal.status).json({ message: internal.error });
|
|
402
|
-
const updated = await (0, dbConnection_1.updateRecordInternal)(internal.client, internal.dbType, req.params.tableName, req.params.recordId, updates, req.params.id);
|
|
403
|
-
return res.status(200).json({ data: updated });
|
|
404
|
-
}
|
|
405
|
-
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
406
|
-
if ('error' in resolved)
|
|
407
|
-
return res.status(resolved.status).json({ message: resolved.error });
|
|
408
|
-
const updated = await (0, dbConnection_1.updateRecord)(resolved.connectionString, resolved.conn.dbType, req.params.tableName, req.params.recordId, updates);
|
|
409
|
-
return res.status(200).json({ data: updated });
|
|
257
|
+
await withResolvedDbClient(project, req.params.connId, config, auth, res, async (resolved) => {
|
|
258
|
+
const updated = await (0, dbConnection_1.updateRecordInternal)(resolved.client, resolved.type, req.params.tableName, req.params.recordId, updates, req.params.id);
|
|
259
|
+
res.status(200).json({ data: updated });
|
|
260
|
+
});
|
|
410
261
|
}
|
|
411
262
|
catch (err) {
|
|
412
263
|
res.status(500).json({ message: 'Failed to update record', details: err.message });
|
|
@@ -423,25 +274,10 @@ function dbConnectionRouter(config) {
|
|
|
423
274
|
return res.status(404).json({ message: 'Project not found' });
|
|
424
275
|
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
425
276
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const builtinId = conn.integrationId;
|
|
431
|
-
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
|
|
432
|
-
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
433
|
-
}
|
|
434
|
-
const internal = resolveBuiltinConnection(builtinId, config);
|
|
435
|
-
if ('error' in internal)
|
|
436
|
-
return res.status(internal.status).json({ message: internal.error });
|
|
437
|
-
await (0, dbConnection_1.deleteRecordInternal)(internal.client, internal.dbType, req.params.tableName, req.params.recordId, req.params.id);
|
|
438
|
-
return res.status(200).json({ message: 'Record deleted' });
|
|
439
|
-
}
|
|
440
|
-
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
441
|
-
if ('error' in resolved)
|
|
442
|
-
return res.status(resolved.status).json({ message: resolved.error });
|
|
443
|
-
await (0, dbConnection_1.deleteRecord)(resolved.connectionString, resolved.conn.dbType, req.params.tableName, req.params.recordId);
|
|
444
|
-
return res.status(200).json({ message: 'Record deleted' });
|
|
277
|
+
await withResolvedDbClient(project, req.params.connId, config, auth, res, async (resolved) => {
|
|
278
|
+
await (0, dbConnection_1.deleteRecordInternal)(resolved.client, resolved.type, req.params.tableName, req.params.recordId, req.params.id);
|
|
279
|
+
res.status(200).json({ message: 'Record deleted' });
|
|
280
|
+
});
|
|
445
281
|
}
|
|
446
282
|
catch (err) {
|
|
447
283
|
res.status(500).json({ message: 'Failed to delete record', details: err.message });
|
|
@@ -8,13 +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");
|
|
12
11
|
const manager_1 = require("../../models/manager");
|
|
13
12
|
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
14
13
|
const auth_1 = require("../../utils/auth");
|
|
15
|
-
const encryption_1 = require("../../utils/encryption");
|
|
16
14
|
const builtinAccess_1 = require("../../utils/builtinAccess");
|
|
17
|
-
const
|
|
15
|
+
const connectionResolver_1 = require("../../services/connectionResolver");
|
|
18
16
|
const mongodb_1 = require("mongodb");
|
|
19
17
|
const crypto_1 = __importDefault(require("crypto"));
|
|
20
18
|
const VALID_CATEGORIES = ['images', 'audios', 'videos', '3d-models'];
|
|
@@ -69,45 +67,7 @@ function storageRouter(config) {
|
|
|
69
67
|
: { storageConnectionId: connId };
|
|
70
68
|
return { projectId, ...connMatch, ...(extra ?? {}) };
|
|
71
69
|
}
|
|
72
|
-
|
|
73
|
-
const conn = findStorageConn(project, connId);
|
|
74
|
-
if (!conn)
|
|
75
|
-
return { error: 'Storage connection not found', status: 404 };
|
|
76
|
-
if (conn.source === 'builtin') {
|
|
77
|
-
const builtinId = conn.integrationId;
|
|
78
|
-
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'storage'))) {
|
|
79
|
-
return { error: 'Access to this built-in storage is not permitted', status: 403 };
|
|
80
|
-
}
|
|
81
|
-
const provider = config.storageProviders.find(p => p.id === builtinId);
|
|
82
|
-
if (!provider)
|
|
83
|
-
return { error: 'Storage provider not found', status: 404 };
|
|
84
|
-
return provider.type === 's3' ? new s3_1.S3Adapter(provider.config) : adapter;
|
|
85
|
-
}
|
|
86
|
-
if (conn.source === 'integration') {
|
|
87
|
-
if (!conn.integrationId)
|
|
88
|
-
return { error: 'Connection is missing its integrationId', status: 500 };
|
|
89
|
-
try {
|
|
90
|
-
const s3Config = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId);
|
|
91
|
-
if (!s3Config)
|
|
92
|
-
return { error: 'Integration not found', status: 400 };
|
|
93
|
-
return new s3_1.S3Adapter(s3Config);
|
|
94
|
-
}
|
|
95
|
-
catch (e) {
|
|
96
|
-
return { error: `Failed to initialize storage adapter: ${e.message}`, status: 500 };
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (!(0, encryption_1.isEncryptionKeySet)())
|
|
100
|
-
return { error: 'Encryption key not set', status: 500 };
|
|
101
|
-
if (!conn.encryptedCredential)
|
|
102
|
-
return { error: 'Connection has no stored credential', status: 400 };
|
|
103
|
-
try {
|
|
104
|
-
const s3Config = JSON.parse((0, encryption_1.decrypt)(conn.encryptedCredential));
|
|
105
|
-
return new s3_1.S3Adapter(s3Config);
|
|
106
|
-
}
|
|
107
|
-
catch (e) {
|
|
108
|
-
return { error: `Failed to initialize storage adapter: ${e.message}`, status: 500 };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
70
|
+
const resolveConnAdapter = (connId, project, auth) => (0, connectionResolver_1.resolveStorageAdapter)(project, connId, { ...config, adapter }, auth);
|
|
111
71
|
router.get('/upload-project-image-url/:id', async (req, res) => {
|
|
112
72
|
try {
|
|
113
73
|
const auth = req.auth;
|
package/dist/models/Model.js
CHANGED
|
@@ -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
|
}
|