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.
- package/README.md +283 -283
- package/dist/adapters/database/connectionManager.js +84 -65
- package/dist/adapters/database/sql.js +1 -1
- package/dist/middleware/createRouter.js +10 -0
- package/dist/middleware/routes/dbConnection.js +30 -11
- package/dist/middleware/routes/pipeline.d.ts +3 -0
- package/dist/middleware/routes/pipeline.js +171 -0
- package/dist/middleware/routes/project.js +66 -2
- package/dist/middleware/routes/storage.js +44 -32
- package/dist/middleware/routes/storageConnection.js +11 -17
- package/dist/middleware/routes/userIntegration.d.ts +3 -0
- package/dist/middleware/routes/userIntegration.js +93 -0
- package/dist/models/Model.js +9 -9
- package/dist/services/backfillBuiltinConnections.d.ts +2 -0
- package/dist/services/backfillBuiltinConnections.js +56 -0
- package/dist/services/userIntegration.d.ts +16 -0
- package/dist/services/userIntegration.js +73 -0
- package/dist/types/index.d.ts +40 -4
- package/dist/utils/builtinAccess.d.ts +4 -0
- package/dist/utils/builtinAccess.js +19 -0
- package/dist/utils/sanitize.d.ts +1 -0
- package/dist/utils/sanitize.js +7 -0
- package/package.json +63 -63
- package/dist/utils/crypto.d.ts +0 -3
- package/dist/utils/crypto.js +0 -32
|
@@ -43,87 +43,106 @@ 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
|
-
credentials JSONB DEFAULT '[]',
|
|
66
|
-
"apiConnections" JSONB DEFAULT '[]',
|
|
67
|
-
"dbConnections" JSONB DEFAULT '[]',
|
|
68
|
-
"dbType" VARCHAR(50) NOT NULL DEFAULT 'sql',
|
|
69
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
70
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
71
|
-
);
|
|
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
|
+
credentials JSONB DEFAULT '[]',
|
|
66
|
+
"apiConnections" JSONB DEFAULT '[]',
|
|
67
|
+
"dbConnections" JSONB DEFAULT '[]',
|
|
68
|
+
"dbType" VARCHAR(50) NOT NULL DEFAULT 'sql',
|
|
69
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
70
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
71
|
+
);
|
|
72
72
|
`);
|
|
73
73
|
console.log('✅ "projects" table ensured to exist.');
|
|
74
74
|
// Add JSONB columns to existing tables that predate this schema version
|
|
75
|
-
await pool.query(`
|
|
76
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "allowedOrigin"
|
|
77
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS exports
|
|
78
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS credentials
|
|
79
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "apiConnections"
|
|
80
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbConnections"
|
|
81
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS
|
|
82
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS
|
|
75
|
+
await pool.query(`
|
|
76
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "allowedOrigin" JSONB DEFAULT '[]';
|
|
77
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS exports JSONB DEFAULT '[]';
|
|
78
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS credentials JSONB DEFAULT '[]';
|
|
79
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "apiConnections" JSONB DEFAULT '[]';
|
|
80
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbConnections" JSONB DEFAULT '[]';
|
|
81
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "storageConnections" JSONB DEFAULT '[]';
|
|
82
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS workflow JSONB;
|
|
83
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS pipelines JSONB DEFAULT '[]';
|
|
83
84
|
`);
|
|
84
85
|
// 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';
|
|
86
|
+
await pool.query(`
|
|
87
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql';
|
|
87
88
|
`);
|
|
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
|
-
);
|
|
89
|
+
await pool.query(`
|
|
90
|
+
CREATE TABLE IF NOT EXISTS streamby.project_members (
|
|
91
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
92
|
+
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
93
|
+
"userId" VARCHAR(255) NOT NULL,
|
|
94
|
+
role VARCHAR(255) NOT NULL DEFAULT 'member',
|
|
95
|
+
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
96
|
+
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
97
|
+
"archivedBy" VARCHAR(255),
|
|
98
|
+
"archivedAt" TIMESTAMP WITH TIME ZONE,
|
|
99
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
100
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
101
|
+
UNIQUE ("projectId", "userId")
|
|
102
|
+
);
|
|
102
103
|
`);
|
|
103
|
-
await pool.query(`
|
|
104
|
-
ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
|
|
104
|
+
await pool.query(`
|
|
105
|
+
ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
|
|
105
106
|
`);
|
|
106
107
|
console.log('✅ "project_members" table ensured to exist.');
|
|
107
108
|
// 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 $$;
|
|
109
|
+
await pool.query(`
|
|
110
|
+
DO $$ BEGIN
|
|
111
|
+
ALTER TABLE streamby.project_members DROP CONSTRAINT IF EXISTS project_members_projectId_fkey;
|
|
112
|
+
ALTER TABLE streamby.project_members ALTER COLUMN "projectId" TYPE TEXT;
|
|
113
|
+
ALTER TABLE streamby.project_members ADD CONSTRAINT project_members_projectid_fkey
|
|
114
|
+
FOREIGN KEY ("projectId") REFERENCES streamby.projects(id) ON DELETE CASCADE;
|
|
115
|
+
EXCEPTION WHEN OTHERS THEN NULL; END $$;
|
|
115
116
|
`);
|
|
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
|
-
);
|
|
117
|
+
await pool.query(`
|
|
118
|
+
CREATE TABLE IF NOT EXISTS streamby.exports (
|
|
119
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
120
|
+
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
121
|
+
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
122
|
+
"filePath" TEXT,
|
|
123
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
124
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
125
|
+
);
|
|
125
126
|
`);
|
|
126
127
|
console.log('✅ "exports" table ensured to exist.');
|
|
128
|
+
// user_integrations is registered against the main db only (see createRouter.ts), but
|
|
129
|
+
// this function runs per-SQL-connection like projects/exports above — harmless on
|
|
130
|
+
// non-main connections since the model never queries them there.
|
|
131
|
+
await pool.query(`
|
|
132
|
+
CREATE TABLE IF NOT EXISTS streamby.user_integrations (
|
|
133
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
134
|
+
"userId" VARCHAR(255) NOT NULL,
|
|
135
|
+
kind VARCHAR(20) NOT NULL,
|
|
136
|
+
provider VARCHAR(20) NOT NULL,
|
|
137
|
+
name VARCHAR(255) NOT NULL,
|
|
138
|
+
description TEXT,
|
|
139
|
+
"encryptedCredential" TEXT NOT NULL,
|
|
140
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
141
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
142
|
+
);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS user_integrations_userid_idx ON streamby.user_integrations ("userId");
|
|
144
|
+
`);
|
|
145
|
+
console.log('✅ "user_integrations" table ensured to exist.');
|
|
127
146
|
}
|
|
128
147
|
catch (error) {
|
|
129
148
|
console.error('❌ Error ensuring tables exist:', error);
|
|
@@ -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
|
};
|
|
@@ -8,6 +8,7 @@ const express_1 = __importDefault(require("express"));
|
|
|
8
8
|
const manager_1 = require("../models/manager");
|
|
9
9
|
const storage_1 = require("../providers/storage");
|
|
10
10
|
const connectionManager_1 = require("../adapters/database/connectionManager");
|
|
11
|
+
const backfillBuiltinConnections_1 = require("../services/backfillBuiltinConnections");
|
|
11
12
|
const wsHub_1 = require("../services/wsHub");
|
|
12
13
|
const auth_1 = require("../services/auth");
|
|
13
14
|
const encryption_1 = require("../utils/encryption");
|
|
@@ -22,6 +23,7 @@ const credential_1 = require("./routes/credential");
|
|
|
22
23
|
const connection_1 = require("./routes/connection");
|
|
23
24
|
const dbConnection_1 = require("./routes/dbConnection");
|
|
24
25
|
const storageConnection_1 = require("./routes/storageConnection");
|
|
26
|
+
const userIntegration_1 = require("./routes/userIntegration");
|
|
25
27
|
const notification_1 = require("./routes/notification");
|
|
26
28
|
const subscription_1 = require("./routes/subscription");
|
|
27
29
|
const media_1 = require("./routes/media");
|
|
@@ -32,6 +34,7 @@ const ai_1 = require("./routes/ai");
|
|
|
32
34
|
const production_1 = require("./routes/production");
|
|
33
35
|
const rights_1 = require("./routes/rights");
|
|
34
36
|
const workflow_1 = require("./routes/workflow");
|
|
37
|
+
const pipeline_1 = require("./routes/pipeline");
|
|
35
38
|
function createStreamByRouter(config) {
|
|
36
39
|
const router = express_1.default.Router();
|
|
37
40
|
if (config.encrypt)
|
|
@@ -45,6 +48,10 @@ function createStreamByRouter(config) {
|
|
|
45
48
|
const readyPromise = (0, connectionManager_1.initConnections)(config.databases || [])
|
|
46
49
|
.catch((err) => {
|
|
47
50
|
console.error('❌ StreamBy: failed to initialize database connections:', err);
|
|
51
|
+
})
|
|
52
|
+
.then(() => (0, backfillBuiltinConnections_1.backfillBuiltinConnections)(config))
|
|
53
|
+
.catch((err) => {
|
|
54
|
+
console.error('❌ StreamBy: failed to backfill built-in connections:', err);
|
|
48
55
|
})
|
|
49
56
|
.finally(() => {
|
|
50
57
|
ready = true;
|
|
@@ -76,6 +83,7 @@ function createStreamByRouter(config) {
|
|
|
76
83
|
if (mainDb) {
|
|
77
84
|
(0, manager_1.registerModel)('users', [mainDb.id], 'users', 'accounts');
|
|
78
85
|
(0, manager_1.registerModel)('user_subscriptions', [mainDb.id], 'user_subscriptions', 'streamby');
|
|
86
|
+
(0, manager_1.registerModel)('user_integrations', [mainDb.id], 'user_integrations', streambySchema);
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
if (config.websocket?.server) {
|
|
@@ -94,6 +102,7 @@ function createStreamByRouter(config) {
|
|
|
94
102
|
router.use((0, connection_1.connectionRouter)(config));
|
|
95
103
|
router.use((0, dbConnection_1.dbConnectionRouter)(config));
|
|
96
104
|
router.use((0, storageConnection_1.storageConnectionRouter)(config));
|
|
105
|
+
router.use((0, userIntegration_1.userIntegrationRouter)(config));
|
|
97
106
|
router.use((0, notification_1.notificationRouter)(config));
|
|
98
107
|
router.use((0, subscription_1.subscriptionRouter)(config));
|
|
99
108
|
router.use((0, media_1.mediaRouter)(config));
|
|
@@ -104,5 +113,6 @@ function createStreamByRouter(config) {
|
|
|
104
113
|
router.use((0, production_1.productionRouter)(config));
|
|
105
114
|
router.use((0, rights_1.rightsRouter)(config));
|
|
106
115
|
router.use((0, workflow_1.workflowRouter)(config));
|
|
116
|
+
router.use((0, pipeline_1.pipelineRouter)(config));
|
|
107
117
|
return router;
|
|
108
118
|
}
|
|
@@ -39,11 +39,9 @@ const mongodb_1 = require("mongodb");
|
|
|
39
39
|
const manager_1 = require("../../models/manager");
|
|
40
40
|
const auth_1 = require("../../utils/auth");
|
|
41
41
|
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
42
|
+
const builtinAccess_1 = require("../../utils/builtinAccess");
|
|
42
43
|
const dbConnection_1 = require("../../services/dbConnection");
|
|
43
44
|
const VALID_DB_TYPES = ['postgresql', 'mongodb'];
|
|
44
|
-
function isBuiltinDb(connId, config) {
|
|
45
|
-
return (config.databases ?? []).some(db => db.id === connId);
|
|
46
|
-
}
|
|
47
45
|
function resolveBuiltinConnection(connId, config) {
|
|
48
46
|
const db = (config.databases ?? []).find(d => d.id === connId);
|
|
49
47
|
if (!db)
|
|
@@ -127,7 +125,7 @@ function dbConnectionRouter(config) {
|
|
|
127
125
|
// ─── Delete DB connection ─────────────────────────────────────────────────
|
|
128
126
|
router.delete('/projects/:id/connections/db/:connId', async (req, res) => {
|
|
129
127
|
try {
|
|
130
|
-
if (isBuiltinDb(req.params.connId, config)) {
|
|
128
|
+
if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
|
|
131
129
|
return res.status(403).json({ message: 'Cannot delete a built-in database connection' });
|
|
132
130
|
}
|
|
133
131
|
const auth = req.auth;
|
|
@@ -158,7 +156,10 @@ function dbConnectionRouter(config) {
|
|
|
158
156
|
return res.status(404).json({ message: 'Project not found' });
|
|
159
157
|
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
160
158
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
161
|
-
if (isBuiltinDb(req.params.connId, config)) {
|
|
159
|
+
if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
|
|
160
|
+
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
|
|
161
|
+
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
162
|
+
}
|
|
162
163
|
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
163
164
|
if ('error' in internal)
|
|
164
165
|
return res.status(internal.status).json({ message: internal.error });
|
|
@@ -190,7 +191,10 @@ function dbConnectionRouter(config) {
|
|
|
190
191
|
const schema = req.body;
|
|
191
192
|
if (!schema.tableName)
|
|
192
193
|
return res.status(400).json({ message: 'tableName is required' });
|
|
193
|
-
if (isBuiltinDb(req.params.connId, config)) {
|
|
194
|
+
if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
|
|
195
|
+
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
|
|
196
|
+
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
197
|
+
}
|
|
194
198
|
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
195
199
|
if ('error' in internal)
|
|
196
200
|
return res.status(internal.status).json({ message: internal.error });
|
|
@@ -225,7 +229,10 @@ function dbConnectionRouter(config) {
|
|
|
225
229
|
return res.status(404).json({ message: 'Project not found' });
|
|
226
230
|
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
227
231
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
228
|
-
if (isBuiltinDb(req.params.connId, config)) {
|
|
232
|
+
if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
|
|
233
|
+
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
|
|
234
|
+
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
235
|
+
}
|
|
229
236
|
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
230
237
|
if ('error' in internal)
|
|
231
238
|
return res.status(internal.status).json({ message: internal.error });
|
|
@@ -253,7 +260,10 @@ function dbConnectionRouter(config) {
|
|
|
253
260
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
254
261
|
const limit = Math.min(parseInt(String(req.query.limit ?? 50), 10), 500);
|
|
255
262
|
const offset = Math.max(parseInt(String(req.query.offset ?? 0), 10), 0);
|
|
256
|
-
if (isBuiltinDb(req.params.connId, config)) {
|
|
263
|
+
if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
|
|
264
|
+
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
|
|
265
|
+
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
266
|
+
}
|
|
257
267
|
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
258
268
|
if ('error' in internal)
|
|
259
269
|
return res.status(internal.status).json({ message: internal.error });
|
|
@@ -286,7 +296,10 @@ function dbConnectionRouter(config) {
|
|
|
286
296
|
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
287
297
|
return res.status(400).json({ message: 'Request body must be a JSON object' });
|
|
288
298
|
}
|
|
289
|
-
if (isBuiltinDb(req.params.connId, config)) {
|
|
299
|
+
if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
|
|
300
|
+
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
|
|
301
|
+
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
302
|
+
}
|
|
290
303
|
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
291
304
|
if ('error' in internal)
|
|
292
305
|
return res.status(internal.status).json({ message: internal.error });
|
|
@@ -318,7 +331,10 @@ function dbConnectionRouter(config) {
|
|
|
318
331
|
if (!updates || typeof updates !== 'object' || Array.isArray(updates)) {
|
|
319
332
|
return res.status(400).json({ message: 'Request body must be a JSON object' });
|
|
320
333
|
}
|
|
321
|
-
if (isBuiltinDb(req.params.connId, config)) {
|
|
334
|
+
if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
|
|
335
|
+
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
|
|
336
|
+
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
337
|
+
}
|
|
322
338
|
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
323
339
|
if ('error' in internal)
|
|
324
340
|
return res.status(internal.status).json({ message: internal.error });
|
|
@@ -346,7 +362,10 @@ function dbConnectionRouter(config) {
|
|
|
346
362
|
return res.status(404).json({ message: 'Project not found' });
|
|
347
363
|
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
348
364
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
349
|
-
if (isBuiltinDb(req.params.connId, config)) {
|
|
365
|
+
if ((0, builtinAccess_1.isBuiltinDb)(req.params.connId, config)) {
|
|
366
|
+
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, req.params.connId, config, 'database'))) {
|
|
367
|
+
return res.status(403).json({ message: 'Access to this built-in database is not permitted' });
|
|
368
|
+
}
|
|
350
369
|
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
351
370
|
if ('error' in internal)
|
|
352
371
|
return res.status(internal.status).json({ message: internal.error });
|
|
@@ -0,0 +1,171 @@
|
|
|
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.pipelineRouter = pipelineRouter;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const auth_1 = require("../../utils/auth");
|
|
9
|
+
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
10
|
+
const manager_1 = require("../../models/manager");
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
function getDb() {
|
|
13
|
+
const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
|
|
14
|
+
if (!ids.length)
|
|
15
|
+
return null;
|
|
16
|
+
return (0, connectionManager_1.getConnection)(ids[0]).client.db();
|
|
17
|
+
}
|
|
18
|
+
// A Pipeline is a sub-workflow scoped to a project. The `pipelines` collection is the
|
|
19
|
+
// source of truth (holds the full nodeSchema); ProjectInfo.pipelines mirrors lightweight
|
|
20
|
+
// refs so the sidebar can list them without loading every schema.
|
|
21
|
+
function pipelineRouter(_config) {
|
|
22
|
+
const router = (0, express_1.Router)();
|
|
23
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
24
|
+
const toRef = (p) => ({ id: p.id, name: p.name, order: p.order });
|
|
25
|
+
// Rewrite ProjectInfo.pipelines from the current collection state, sorted by order.
|
|
26
|
+
const syncProjectRefs = async (projectId) => {
|
|
27
|
+
const db = getDb();
|
|
28
|
+
if (!db)
|
|
29
|
+
return;
|
|
30
|
+
const all = await db.collection('pipelines')
|
|
31
|
+
.find({ projectId })
|
|
32
|
+
.project({ _id: 0, id: 1, name: 1, order: 1 })
|
|
33
|
+
.sort({ order: 1, createdAt: 1 })
|
|
34
|
+
.toArray();
|
|
35
|
+
const refs = all.map(p => toRef(p));
|
|
36
|
+
await Project.update({ _id: projectId }, { pipelines: refs });
|
|
37
|
+
};
|
|
38
|
+
router.get('/projects/:projectId/pipelines', async (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const auth = req.auth;
|
|
41
|
+
const { projectId } = req.params;
|
|
42
|
+
const project = await Project.findOne({ _id: projectId });
|
|
43
|
+
if (!project)
|
|
44
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
45
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
46
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
47
|
+
const db = getDb();
|
|
48
|
+
if (!db)
|
|
49
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
50
|
+
const pipelines = await db.collection('pipelines')
|
|
51
|
+
.find({ projectId })
|
|
52
|
+
.sort({ order: 1, createdAt: 1 })
|
|
53
|
+
.toArray();
|
|
54
|
+
res.json({ pipelines });
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
res.status(500).json({ message: err.message });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
router.post('/projects/:projectId/pipelines', async (req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
const auth = req.auth;
|
|
63
|
+
const { projectId } = req.params;
|
|
64
|
+
const { name, description, order } = req.body;
|
|
65
|
+
if (!name)
|
|
66
|
+
return res.status(400).json({ message: 'name is required' });
|
|
67
|
+
const project = await Project.findOne({ _id: projectId });
|
|
68
|
+
if (!project)
|
|
69
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
70
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
71
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
72
|
+
const db = getDb();
|
|
73
|
+
if (!db)
|
|
74
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
75
|
+
const now = new Date();
|
|
76
|
+
const pipeline = {
|
|
77
|
+
id: crypto_1.default.randomUUID(),
|
|
78
|
+
projectId,
|
|
79
|
+
name,
|
|
80
|
+
description: description ?? null,
|
|
81
|
+
order: order ?? (project.pipelines?.length ?? 0),
|
|
82
|
+
nodeSchema: null,
|
|
83
|
+
createdBy: auth.userId,
|
|
84
|
+
createdAt: now,
|
|
85
|
+
updatedAt: now,
|
|
86
|
+
};
|
|
87
|
+
await db.collection('pipelines').insertOne({ ...pipeline });
|
|
88
|
+
await syncProjectRefs(projectId);
|
|
89
|
+
res.status(201).json({ pipeline });
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
res.status(500).json({ message: err.message });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
router.get('/projects/:projectId/pipelines/:pipelineId', async (req, res) => {
|
|
96
|
+
try {
|
|
97
|
+
const auth = req.auth;
|
|
98
|
+
const { projectId, pipelineId } = req.params;
|
|
99
|
+
const project = await Project.findOne({ _id: projectId });
|
|
100
|
+
if (!project)
|
|
101
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
102
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
103
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
104
|
+
const db = getDb();
|
|
105
|
+
if (!db)
|
|
106
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
107
|
+
const pipeline = await db.collection('pipelines').findOne({ id: pipelineId, projectId });
|
|
108
|
+
if (!pipeline)
|
|
109
|
+
return res.status(404).json({ message: 'Pipeline not found' });
|
|
110
|
+
res.json({ pipeline });
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
res.status(500).json({ message: err.message });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
router.patch('/projects/:projectId/pipelines/:pipelineId', async (req, res) => {
|
|
117
|
+
try {
|
|
118
|
+
const auth = req.auth;
|
|
119
|
+
const { projectId, pipelineId } = req.params;
|
|
120
|
+
const project = await Project.findOne({ _id: projectId });
|
|
121
|
+
if (!project)
|
|
122
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
123
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
124
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
125
|
+
const db = getDb();
|
|
126
|
+
if (!db)
|
|
127
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
128
|
+
const { name, description, nodeSchema, order } = req.body;
|
|
129
|
+
const $set = { updatedAt: new Date() };
|
|
130
|
+
if (name !== undefined)
|
|
131
|
+
$set.name = name;
|
|
132
|
+
if (description !== undefined)
|
|
133
|
+
$set.description = description;
|
|
134
|
+
if (nodeSchema !== undefined)
|
|
135
|
+
$set.nodeSchema = nodeSchema;
|
|
136
|
+
if (order !== undefined)
|
|
137
|
+
$set.order = order;
|
|
138
|
+
const result = await db.collection('pipelines').findOneAndUpdate({ id: pipelineId, projectId }, { $set }, { returnDocument: 'after' });
|
|
139
|
+
if (!result)
|
|
140
|
+
return res.status(404).json({ message: 'Pipeline not found' });
|
|
141
|
+
// Only name/order affect the lightweight refs.
|
|
142
|
+
if (name !== undefined || order !== undefined)
|
|
143
|
+
await syncProjectRefs(projectId);
|
|
144
|
+
res.json({ pipeline: result });
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
res.status(500).json({ message: err.message });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
router.delete('/projects/:projectId/pipelines/:pipelineId', async (req, res) => {
|
|
151
|
+
try {
|
|
152
|
+
const auth = req.auth;
|
|
153
|
+
const { projectId, pipelineId } = req.params;
|
|
154
|
+
const project = await Project.findOne({ _id: projectId });
|
|
155
|
+
if (!project)
|
|
156
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
157
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
158
|
+
return res.status(403).json({ message: 'Unauthorized' });
|
|
159
|
+
const db = getDb();
|
|
160
|
+
if (!db)
|
|
161
|
+
return res.status(500).json({ message: 'Database not available' });
|
|
162
|
+
await db.collection('pipelines').deleteOne({ id: pipelineId, projectId });
|
|
163
|
+
await syncProjectRefs(projectId);
|
|
164
|
+
res.json({ message: 'Pipeline deleted' });
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
res.status(500).json({ message: err.message });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return router;
|
|
171
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.projectRouter = projectRouter;
|
|
4
7
|
const express_1 = require("express");
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
5
9
|
const manager_1 = require("../../models/manager");
|
|
6
10
|
const auth_1 = require("../../utils/auth");
|
|
7
11
|
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
8
12
|
const sql_1 = require("../../adapters/database/sql");
|
|
9
13
|
const sanitize_1 = require("../../utils/sanitize");
|
|
14
|
+
const builtinAccess_1 = require("../../utils/builtinAccess");
|
|
10
15
|
function projectRouter(config) {
|
|
11
16
|
const router = (0, express_1.Router)();
|
|
12
17
|
const Project = (0, manager_1.getModel)('projects');
|
|
@@ -74,7 +79,7 @@ function projectRouter(config) {
|
|
|
74
79
|
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
75
80
|
return res.status(403).json({ message: 'Permission denied' });
|
|
76
81
|
}
|
|
77
|
-
const { name, description, image, allowedOrigin, public: isPublic = true, category } = req.body;
|
|
82
|
+
const { name, description, image, allowedOrigin, public: isPublic = true, category, integrationIds } = req.body;
|
|
78
83
|
if (isPublic === false && req.subscription === 'freemium') {
|
|
79
84
|
return res.status(403).json({ error: 'Private projects require a subscriber plan' });
|
|
80
85
|
}
|
|
@@ -88,7 +93,55 @@ function projectRouter(config) {
|
|
|
88
93
|
if (!user) {
|
|
89
94
|
return res.status(404).json({ message: 'User not found' });
|
|
90
95
|
}
|
|
91
|
-
|
|
96
|
+
// Classify + revalidate every integrationId server-side — never trust a client-supplied
|
|
97
|
+
// builtin id. Any invalid id rejects the whole request (no project is created).
|
|
98
|
+
const dbConnections = [];
|
|
99
|
+
const storageConnections = [];
|
|
100
|
+
if (Array.isArray(integrationIds)) {
|
|
101
|
+
const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
|
|
102
|
+
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
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let newProject = await Project.create({
|
|
92
145
|
dbType,
|
|
93
146
|
name,
|
|
94
147
|
description: description || '',
|
|
@@ -97,7 +150,18 @@ function projectRouter(config) {
|
|
|
97
150
|
public: isPublic,
|
|
98
151
|
category: category || null,
|
|
99
152
|
members: [{ userId: auth.userId, username: user.username, role: "admin", status: "active", archived: false }],
|
|
153
|
+
dbConnections: [],
|
|
154
|
+
storageConnections: [],
|
|
100
155
|
});
|
|
156
|
+
const newProjectId = newProject._id || newProject.id;
|
|
157
|
+
if (dbConnections.length || storageConnections.length) {
|
|
158
|
+
const stamped = await Project.update({ _id: newProjectId }, {
|
|
159
|
+
dbConnections: dbConnections.map(c => ({ ...c, projectId: newProjectId })),
|
|
160
|
+
storageConnections: storageConnections.map(c => ({ ...c, projectId: newProjectId })),
|
|
161
|
+
});
|
|
162
|
+
if (stamped)
|
|
163
|
+
newProject = stamped;
|
|
164
|
+
}
|
|
101
165
|
const allProjects = await Project.find({});
|
|
102
166
|
const projects = allProjects
|
|
103
167
|
.filter(project => {
|