streamby-core 0.26.9 → 0.27.0
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 +67 -44
- package/dist/adapters/database/sql.js +21 -21
- package/dist/middleware/createRouter.js +2 -0
- package/dist/middleware/routes/credential.js +4 -3
- package/dist/middleware/routes/dbConnection.d.ts +3 -0
- package/dist/middleware/routes/dbConnection.js +278 -0
- package/dist/middleware/routes/export.js +4 -2
- package/dist/middleware/routes/project.js +11 -9
- package/dist/models/Model.d.ts +1 -0
- package/dist/models/Model.js +29 -6
- package/dist/services/dbConnection.d.ts +11 -0
- package/dist/services/dbConnection.js +148 -0
- package/dist/services/export.d.ts +1 -1
- package/dist/services/export.js +70 -22
- package/dist/services/pipeline.js +59 -1
- package/dist/types/index.d.ts +24 -2
- package/dist/utils/crypto.d.ts +3 -0
- package/dist/utils/crypto.js +32 -0
- package/dist/utils/sanitize.d.ts +1 -0
- package/dist/utils/sanitize.js +11 -0
- package/package.json +63 -63
|
@@ -43,58 +43,81 @@ 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";
|
|
49
|
-
|
|
50
|
-
id UUID
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
await pool.query(`
|
|
47
|
+
CREATE SCHEMA IF NOT EXISTS streamby;
|
|
48
|
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
49
|
+
`);
|
|
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 $$;
|
|
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
72
|
`);
|
|
58
73
|
console.log('✅ "projects" table ensured to exist.');
|
|
59
|
-
//
|
|
60
|
-
await pool.query(`
|
|
61
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "
|
|
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" 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 '[]';
|
|
62
81
|
`);
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
// Ensure dbType exists with a non-null default
|
|
83
|
+
await pool.query(`
|
|
84
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql';
|
|
65
85
|
`);
|
|
66
|
-
await pool.query(`
|
|
67
|
-
|
|
86
|
+
await pool.query(`
|
|
87
|
+
CREATE TABLE IF NOT EXISTS streamby.project_members (
|
|
88
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
89
|
+
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
90
|
+
"userId" VARCHAR(255) NOT NULL,
|
|
91
|
+
role VARCHAR(255) NOT NULL DEFAULT 'member',
|
|
92
|
+
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
93
|
+
"archivedBy" VARCHAR(255),
|
|
94
|
+
"archivedAt" TIMESTAMP WITH TIME ZONE,
|
|
95
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
96
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
97
|
+
UNIQUE ("projectId", "userId")
|
|
98
|
+
);
|
|
68
99
|
`);
|
|
69
|
-
|
|
70
|
-
|
|
100
|
+
console.log('✅ "project_members" table ensured to exist.');
|
|
101
|
+
// Migrate projectId FK column from UUID to TEXT if table predates this schema version
|
|
102
|
+
await pool.query(`
|
|
103
|
+
DO $$ BEGIN
|
|
104
|
+
ALTER TABLE streamby.project_members DROP CONSTRAINT IF EXISTS project_members_projectId_fkey;
|
|
105
|
+
ALTER TABLE streamby.project_members ALTER COLUMN "projectId" TYPE TEXT;
|
|
106
|
+
ALTER TABLE streamby.project_members ADD CONSTRAINT project_members_projectid_fkey
|
|
107
|
+
FOREIGN KEY ("projectId") REFERENCES streamby.projects(id) ON DELETE CASCADE;
|
|
108
|
+
EXCEPTION WHEN OTHERS THEN NULL; END $$;
|
|
71
109
|
`);
|
|
72
|
-
await pool.query(`
|
|
73
|
-
CREATE TABLE IF NOT EXISTS streamby.exports (
|
|
74
|
-
id
|
|
75
|
-
"projectId"
|
|
76
|
-
status
|
|
77
|
-
"filePath"
|
|
78
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
79
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
80
|
-
);
|
|
110
|
+
await pool.query(`
|
|
111
|
+
CREATE TABLE IF NOT EXISTS streamby.exports (
|
|
112
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
113
|
+
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
114
|
+
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
115
|
+
"filePath" TEXT,
|
|
116
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
117
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
118
|
+
);
|
|
81
119
|
`);
|
|
82
120
|
console.log('✅ "exports" table ensured to exist.');
|
|
83
|
-
await pool.query(`
|
|
84
|
-
CREATE TABLE IF NOT EXISTS streamby.project_members (
|
|
85
|
-
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
86
|
-
"projectId" UUID NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
87
|
-
"userId" VARCHAR(255) NOT NULL,
|
|
88
|
-
role VARCHAR(255) NOT NULL DEFAULT 'member',
|
|
89
|
-
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
90
|
-
"archivedBy" VARCHAR(255),
|
|
91
|
-
"archivedAt" TIMESTAMP WITH TIME ZONE,
|
|
92
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
93
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
94
|
-
UNIQUE ("projectId", "userId")
|
|
95
|
-
);
|
|
96
|
-
`);
|
|
97
|
-
console.log('✅ "project_members" table ensured to exist.');
|
|
98
121
|
}
|
|
99
122
|
catch (error) {
|
|
100
123
|
console.error('❌ Error ensuring tables exist:', error);
|
|
@@ -6,13 +6,13 @@ const createSQLExportTable = async (connection, projectId, exportName, fields) =
|
|
|
6
6
|
const slug = exportName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
7
7
|
const tableName = `export_${projectId}_${slug}`;
|
|
8
8
|
const columns = fields.map(field => `"${field.name}" ${field.type}`).join(',');
|
|
9
|
-
const createTableQuery = `
|
|
10
|
-
CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
11
|
-
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
12
|
-
"projectId" UUID NOT NULL,
|
|
13
|
-
__metadata JSONB NOT NULL,
|
|
14
|
-
${columns ? `, ${columns}` : ''}
|
|
15
|
-
);
|
|
9
|
+
const createTableQuery = `
|
|
10
|
+
CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
11
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
12
|
+
"projectId" UUID NOT NULL,
|
|
13
|
+
__metadata JSONB NOT NULL,
|
|
14
|
+
${columns ? `, ${columns}` : ''}
|
|
15
|
+
);
|
|
16
16
|
`;
|
|
17
17
|
await connection.query(createTableQuery);
|
|
18
18
|
console.log(`✅ Table '${tableName}' created with metadata.`);
|
|
@@ -30,15 +30,15 @@ exports.createSQLExportTable = createSQLExportTable;
|
|
|
30
30
|
const createSQLRawExportTable = async (connection, projectId, exportName, nodeSchema) => {
|
|
31
31
|
const tableName = projectId;
|
|
32
32
|
const exportId = (0, crypto_1.randomUUID)();
|
|
33
|
-
const createTableQuery = `
|
|
34
|
-
CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
35
|
-
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
36
|
-
node_schema JSONB,
|
|
37
|
-
name TEXT,
|
|
38
|
-
method TEXT,
|
|
39
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
40
|
-
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
41
|
-
);
|
|
33
|
+
const createTableQuery = `
|
|
34
|
+
CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
35
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
36
|
+
node_schema JSONB,
|
|
37
|
+
name TEXT,
|
|
38
|
+
method TEXT,
|
|
39
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
40
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
41
|
+
);
|
|
42
42
|
`;
|
|
43
43
|
await connection.query(createTableQuery);
|
|
44
44
|
await connection.query(`INSERT INTO "${tableName}" (id, node_schema, name, method) VALUES ($1, $2, $3, $4)`, [exportId, nodeSchema ? JSON.stringify(nodeSchema) : null, exportName, 'GET']);
|
|
@@ -53,7 +53,7 @@ exports.sqlAdapter = {
|
|
|
53
53
|
const fullTableName = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
|
|
54
54
|
let query = `SELECT * FROM ${fullTableName}`;
|
|
55
55
|
if (keys.length > 0) {
|
|
56
|
-
const where = keys.map((key, i) => `"${key}" =
|
|
56
|
+
const where = keys.map((key, i) => `"${key}" = $${i + 1}`).join(' AND ');
|
|
57
57
|
query += ` WHERE ${where}`;
|
|
58
58
|
}
|
|
59
59
|
const result = await connection.query(query, values);
|
|
@@ -62,7 +62,7 @@ exports.sqlAdapter = {
|
|
|
62
62
|
findOne: async (connection, tableName, filter, schema) => {
|
|
63
63
|
const keys = Object.keys(filter);
|
|
64
64
|
const values = Object.values(filter);
|
|
65
|
-
const where = keys.map((key, i) => `"${key}" =
|
|
65
|
+
const where = keys.map((key, i) => `"${key}" = $${i + 1}`).join(' AND ');
|
|
66
66
|
const fullTableName = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
|
|
67
67
|
const result = await connection.query(`SELECT * FROM ${fullTableName} WHERE ${where} LIMIT 1`, values);
|
|
68
68
|
return result.rows[0] || null;
|
|
@@ -70,7 +70,7 @@ exports.sqlAdapter = {
|
|
|
70
70
|
create: async (connection, tableName, data, schema) => {
|
|
71
71
|
const keys = Object.keys(data).map(key => `"${key}"`).join(', ');
|
|
72
72
|
const values = Object.values(data);
|
|
73
|
-
const placeholders = values.map((_, i) =>
|
|
73
|
+
const placeholders = values.map((_, i) => `$${i + 1}`).join(', ');
|
|
74
74
|
const fullTableName = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
|
|
75
75
|
const result = await connection.query(`INSERT INTO ${fullTableName} (${keys}) VALUES (${placeholders}) RETURNING *`, values);
|
|
76
76
|
return result.rows[0];
|
|
@@ -79,8 +79,8 @@ exports.sqlAdapter = {
|
|
|
79
79
|
const dataKeys = Object.keys(data);
|
|
80
80
|
const filterKeys = Object.keys(filter);
|
|
81
81
|
const values = [...Object.values(data), ...Object.values(filter)];
|
|
82
|
-
const setClause = dataKeys.map((key, i) => `"${key}" =
|
|
83
|
-
const whereClause = filterKeys.map((key, i) => `"${key}" =
|
|
82
|
+
const setClause = dataKeys.map((key, i) => `"${key}" = $${i + 1}`).join(', ');
|
|
83
|
+
const whereClause = filterKeys.map((key, i) => `"${key}" = $${dataKeys.length + i + 1}`).join(' AND ');
|
|
84
84
|
const fullTableName = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
|
|
85
85
|
const result = await connection.query(`UPDATE ${fullTableName} SET ${setClause} WHERE ${whereClause} RETURNING *`, values);
|
|
86
86
|
return result.rows[0] || null;
|
|
@@ -20,6 +20,7 @@ const project_1 = require("./routes/project");
|
|
|
20
20
|
const database_1 = require("./routes/database");
|
|
21
21
|
const credential_1 = require("./routes/credential");
|
|
22
22
|
const connection_1 = require("./routes/connection");
|
|
23
|
+
const dbConnection_1 = require("./routes/dbConnection");
|
|
23
24
|
const notification_1 = require("./routes/notification");
|
|
24
25
|
function createStreamByRouter(config) {
|
|
25
26
|
const router = express_1.default.Router();
|
|
@@ -55,6 +56,7 @@ function createStreamByRouter(config) {
|
|
|
55
56
|
router.use((0, database_1.databaseRouter)(config));
|
|
56
57
|
router.use((0, credential_1.credentialRouter)(config));
|
|
57
58
|
router.use((0, connection_1.connectionRouter)(config));
|
|
59
|
+
router.use((0, dbConnection_1.dbConnectionRouter)(config));
|
|
58
60
|
router.use((0, notification_1.notificationRouter)(config));
|
|
59
61
|
return router;
|
|
60
62
|
}
|
|
@@ -4,6 +4,7 @@ exports.credentialRouter = credentialRouter;
|
|
|
4
4
|
const express_1 = require("express");
|
|
5
5
|
const manager_1 = require("../../models/manager");
|
|
6
6
|
const auth_1 = require("../../utils/auth");
|
|
7
|
+
const sanitize_1 = require("../../utils/sanitize");
|
|
7
8
|
const credential_1 = require("../../services/credential");
|
|
8
9
|
function credentialRouter(config) {
|
|
9
10
|
const router = (0, express_1.Router)();
|
|
@@ -25,7 +26,7 @@ function credentialRouter(config) {
|
|
|
25
26
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
26
27
|
}
|
|
27
28
|
const updatedProject = await (0, credential_1.addCredential)(config, projectId, { id, key, value });
|
|
28
|
-
res.status(201).json({ message: 'Credential added successfully', project: updatedProject });
|
|
29
|
+
res.status(201).json({ message: 'Credential added successfully', project: (0, sanitize_1.sanitizeProject)(updatedProject) });
|
|
29
30
|
}
|
|
30
31
|
catch (err) {
|
|
31
32
|
res.status(500).json({ message: 'Failed to add credential', details: err.message });
|
|
@@ -49,7 +50,7 @@ function credentialRouter(config) {
|
|
|
49
50
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
50
51
|
}
|
|
51
52
|
const updatedProject = await (0, credential_1.updateCredential)(config, projectId, credentialId, { key, value });
|
|
52
|
-
res.status(200).json({ message: 'Credential updated successfully', project: updatedProject });
|
|
53
|
+
res.status(200).json({ message: 'Credential updated successfully', project: (0, sanitize_1.sanitizeProject)(updatedProject) });
|
|
53
54
|
}
|
|
54
55
|
catch (err) {
|
|
55
56
|
res.status(500).json({ message: 'Failed to update credential', details: err.message });
|
|
@@ -69,7 +70,7 @@ function credentialRouter(config) {
|
|
|
69
70
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
70
71
|
}
|
|
71
72
|
const updatedProject = await (0, credential_1.deleteCredential)(config, projectId, credentialId);
|
|
72
|
-
res.status(200).json({ message: 'Credential deleted successfully', project: updatedProject });
|
|
73
|
+
res.status(200).json({ message: 'Credential deleted successfully', project: (0, sanitize_1.sanitizeProject)(updatedProject) });
|
|
73
74
|
}
|
|
74
75
|
catch (err) {
|
|
75
76
|
res.status(500).json({ message: 'Failed to delete credential', details: err.message });
|
|
@@ -0,0 +1,278 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.dbConnectionRouter = dbConnectionRouter;
|
|
37
|
+
const express_1 = require("express");
|
|
38
|
+
const mongodb_1 = require("mongodb");
|
|
39
|
+
const manager_1 = require("../../models/manager");
|
|
40
|
+
const auth_1 = require("../../utils/auth");
|
|
41
|
+
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
42
|
+
const dbConnection_1 = require("../../services/dbConnection");
|
|
43
|
+
const VALID_DB_TYPES = ['postgresql', 'mongodb'];
|
|
44
|
+
function isBuiltinDb(connId, config) {
|
|
45
|
+
return (config.databases ?? []).some(db => db.id === connId);
|
|
46
|
+
}
|
|
47
|
+
function resolveBuiltinConnection(connId, config) {
|
|
48
|
+
const db = (config.databases ?? []).find(d => d.id === connId);
|
|
49
|
+
if (!db)
|
|
50
|
+
return { error: `Database '${connId}' not found in config`, status: 404 };
|
|
51
|
+
try {
|
|
52
|
+
const { client, type } = (0, connectionManager_1.getConnection)(connId);
|
|
53
|
+
return { client: client, dbType: type };
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
return { error: e.message, status: 503 };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function getDecryptedConnectionString(project, connId) {
|
|
60
|
+
const conn = project.dbConnections?.find((c) => c.id === connId);
|
|
61
|
+
if (!conn)
|
|
62
|
+
return { error: 'DB connection not found', status: 404 };
|
|
63
|
+
const { decrypt, isEncryptionKeySet } = await Promise.resolve().then(() => __importStar(require('../../utils/encryption')));
|
|
64
|
+
if (!isEncryptionKeySet())
|
|
65
|
+
return { error: 'Encryption key is not set', status: 500 };
|
|
66
|
+
const credential = project.credentials?.find((c) => c.id === conn.credentialId);
|
|
67
|
+
if (!credential)
|
|
68
|
+
return { error: 'Credential not found in project', status: 400 };
|
|
69
|
+
return { connectionString: decrypt(credential.encryptedValue), conn };
|
|
70
|
+
}
|
|
71
|
+
function dbConnectionRouter(config) {
|
|
72
|
+
const router = (0, express_1.Router)();
|
|
73
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
74
|
+
// ─── List DB connections ──────────────────────────────────────────────────
|
|
75
|
+
router.get('/projects/:id/connections/db', async (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
const auth = req.auth;
|
|
78
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
79
|
+
if (!project)
|
|
80
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
81
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
82
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
83
|
+
return res.status(200).json({ data: project.dbConnections || [] });
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
res.status(500).json({ message: 'Failed to fetch DB connections', details: err.message });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// ─── Add DB connection ────────────────────────────────────────────────────
|
|
90
|
+
router.post('/projects/:id/connections/db', async (req, res) => {
|
|
91
|
+
try {
|
|
92
|
+
const auth = req.auth;
|
|
93
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
94
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
95
|
+
}
|
|
96
|
+
const { name, dbType, credentialId, description } = req.body;
|
|
97
|
+
if (!name || !dbType || !credentialId) {
|
|
98
|
+
return res.status(400).json({ message: 'name, dbType, and credentialId are required' });
|
|
99
|
+
}
|
|
100
|
+
if (!VALID_DB_TYPES.includes(dbType)) {
|
|
101
|
+
return res.status(400).json({ message: `dbType must be one of: ${VALID_DB_TYPES.join(', ')}` });
|
|
102
|
+
}
|
|
103
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
104
|
+
if (!project)
|
|
105
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
106
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
107
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
108
|
+
const credExists = project.credentials?.some((c) => c.id === credentialId);
|
|
109
|
+
if (!credExists)
|
|
110
|
+
return res.status(400).json({ message: 'Credential not found in project' });
|
|
111
|
+
const connection = {
|
|
112
|
+
id: new mongodb_1.ObjectId().toHexString(),
|
|
113
|
+
name,
|
|
114
|
+
dbType,
|
|
115
|
+
credentialId,
|
|
116
|
+
projectId: req.params.id,
|
|
117
|
+
createdAt: new Date(),
|
|
118
|
+
...(description !== undefined && { description }),
|
|
119
|
+
};
|
|
120
|
+
await Project.update({ _id: req.params.id }, { $push: { dbConnections: connection } });
|
|
121
|
+
return res.status(201).json({ data: connection });
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
res.status(500).json({ message: 'Failed to add DB connection', details: err.message });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
// ─── Delete DB connection ─────────────────────────────────────────────────
|
|
128
|
+
router.delete('/projects/:id/connections/db/:connId', async (req, res) => {
|
|
129
|
+
try {
|
|
130
|
+
if (isBuiltinDb(req.params.connId, config)) {
|
|
131
|
+
return res.status(403).json({ message: 'Cannot delete a built-in database connection' });
|
|
132
|
+
}
|
|
133
|
+
const auth = req.auth;
|
|
134
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
135
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
136
|
+
}
|
|
137
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
138
|
+
if (!project)
|
|
139
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
140
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
141
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
142
|
+
const exists = project.dbConnections?.some((c) => c.id === req.params.connId);
|
|
143
|
+
if (!exists)
|
|
144
|
+
return res.status(404).json({ message: 'DB connection not found' });
|
|
145
|
+
await Project.update({ _id: req.params.id }, { $pull: { dbConnections: { id: req.params.connId } } });
|
|
146
|
+
return res.status(200).json({ message: 'DB connection deleted successfully' });
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
res.status(500).json({ message: 'Failed to delete DB connection', details: err.message });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// ─── List tables / collections ────────────────────────────────────────────
|
|
153
|
+
router.get('/projects/:id/connections/db/:connId/tables', async (req, res) => {
|
|
154
|
+
try {
|
|
155
|
+
const auth = req.auth;
|
|
156
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
157
|
+
if (!project)
|
|
158
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
159
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
160
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
161
|
+
if (isBuiltinDb(req.params.connId, config)) {
|
|
162
|
+
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
163
|
+
if ('error' in internal)
|
|
164
|
+
return res.status(internal.status).json({ message: internal.error });
|
|
165
|
+
const tables = await (0, dbConnection_1.listTablesInternal)(internal.client, internal.dbType, req.params.id);
|
|
166
|
+
return res.status(200).json({ data: tables });
|
|
167
|
+
}
|
|
168
|
+
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
169
|
+
if ('error' in resolved)
|
|
170
|
+
return res.status(resolved.status).json({ message: resolved.error });
|
|
171
|
+
const tables = await (0, dbConnection_1.listTablesOrCollections)(resolved.connectionString, resolved.conn.dbType);
|
|
172
|
+
return res.status(200).json({ data: tables });
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
res.status(500).json({ message: 'Failed to list tables', details: err.message });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
// ─── Create table / collection ────────────────────────────────────────────
|
|
179
|
+
router.post('/projects/:id/connections/db/:connId/tables', async (req, res) => {
|
|
180
|
+
try {
|
|
181
|
+
const auth = req.auth;
|
|
182
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
183
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
184
|
+
}
|
|
185
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
186
|
+
if (!project)
|
|
187
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
188
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
189
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
190
|
+
const schema = req.body;
|
|
191
|
+
if (!schema.tableName)
|
|
192
|
+
return res.status(400).json({ message: 'tableName is required' });
|
|
193
|
+
if (isBuiltinDb(req.params.connId, config)) {
|
|
194
|
+
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
195
|
+
if ('error' in internal)
|
|
196
|
+
return res.status(internal.status).json({ message: internal.error });
|
|
197
|
+
if (internal.dbType === 'sql' && (!Array.isArray(schema.columns) || schema.columns.length === 0)) {
|
|
198
|
+
return res.status(400).json({ message: 'columns are required for SQL tables' });
|
|
199
|
+
}
|
|
200
|
+
await (0, dbConnection_1.createTableOrCollectionInternal)(internal.client, internal.dbType, schema, req.params.id);
|
|
201
|
+
return res.status(201).json({ message: `${internal.dbType === 'sql' ? 'Table' : 'Collection'} created successfully` });
|
|
202
|
+
}
|
|
203
|
+
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
204
|
+
if ('error' in resolved)
|
|
205
|
+
return res.status(resolved.status).json({ message: resolved.error });
|
|
206
|
+
if (resolved.conn.dbType === 'postgresql' && (!Array.isArray(schema.columns) || schema.columns.length === 0)) {
|
|
207
|
+
return res.status(400).json({ message: 'columns are required for PostgreSQL tables' });
|
|
208
|
+
}
|
|
209
|
+
await (0, dbConnection_1.createTableOrCollection)(resolved.connectionString, resolved.conn.dbType, schema);
|
|
210
|
+
return res.status(201).json({ message: `${resolved.conn.dbType === 'postgresql' ? 'Table' : 'Collection'} created successfully` });
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
res.status(500).json({ message: 'Failed to create table/collection', details: err.message });
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
// ─── Query records ────────────────────────────────────────────────────────
|
|
217
|
+
router.get('/projects/:id/connections/db/:connId/tables/:tableName', async (req, res) => {
|
|
218
|
+
try {
|
|
219
|
+
const auth = req.auth;
|
|
220
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
221
|
+
if (!project)
|
|
222
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
223
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
224
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
225
|
+
const limit = Math.min(parseInt(String(req.query.limit ?? 50), 10), 500);
|
|
226
|
+
const offset = Math.max(parseInt(String(req.query.offset ?? 0), 10), 0);
|
|
227
|
+
if (isBuiltinDb(req.params.connId, config)) {
|
|
228
|
+
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
229
|
+
if ('error' in internal)
|
|
230
|
+
return res.status(internal.status).json({ message: internal.error });
|
|
231
|
+
const records = await (0, dbConnection_1.queryRecordsInternal)(internal.client, internal.dbType, req.params.tableName, limit, offset, req.params.id);
|
|
232
|
+
return res.status(200).json({ data: records });
|
|
233
|
+
}
|
|
234
|
+
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
235
|
+
if ('error' in resolved)
|
|
236
|
+
return res.status(resolved.status).json({ message: resolved.error });
|
|
237
|
+
const records = await (0, dbConnection_1.queryRecords)(resolved.connectionString, resolved.conn.dbType, req.params.tableName, limit, offset);
|
|
238
|
+
return res.status(200).json({ data: records });
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
res.status(500).json({ message: 'Failed to query records', details: err.message });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
// ─── Insert record ────────────────────────────────────────────────────────
|
|
245
|
+
router.post('/projects/:id/connections/db/:connId/tables/:tableName', async (req, res) => {
|
|
246
|
+
try {
|
|
247
|
+
const auth = req.auth;
|
|
248
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
249
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
250
|
+
}
|
|
251
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
252
|
+
if (!project)
|
|
253
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
254
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
255
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
256
|
+
const record = req.body;
|
|
257
|
+
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
258
|
+
return res.status(400).json({ message: 'Request body must be a JSON object' });
|
|
259
|
+
}
|
|
260
|
+
if (isBuiltinDb(req.params.connId, config)) {
|
|
261
|
+
const internal = resolveBuiltinConnection(req.params.connId, config);
|
|
262
|
+
if ('error' in internal)
|
|
263
|
+
return res.status(internal.status).json({ message: internal.error });
|
|
264
|
+
const inserted = await (0, dbConnection_1.insertRecordInternal)(internal.client, internal.dbType, req.params.tableName, record, req.params.id);
|
|
265
|
+
return res.status(201).json({ data: inserted });
|
|
266
|
+
}
|
|
267
|
+
const resolved = await getDecryptedConnectionString(project, req.params.connId);
|
|
268
|
+
if ('error' in resolved)
|
|
269
|
+
return res.status(resolved.status).json({ message: resolved.error });
|
|
270
|
+
const inserted = await (0, dbConnection_1.insertRecord)(resolved.connectionString, resolved.conn.dbType, req.params.tableName, record);
|
|
271
|
+
return res.status(201).json({ data: inserted });
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
res.status(500).json({ message: 'Failed to insert record', details: err.message });
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
return router;
|
|
278
|
+
}
|
|
@@ -189,7 +189,7 @@ function exportRouter(config) {
|
|
|
189
189
|
return res.status(401).json({ message: 'Unauthorized' });
|
|
190
190
|
}
|
|
191
191
|
const projectId = req.params.id;
|
|
192
|
-
const { name, description, isPrivate, allowedOrigin, useConnections, useCredentials, nodeSchema } = req.body;
|
|
192
|
+
const { name, description, isPrivate, allowedOrigin, useConnections, useCredentials, nodeSchema, storageDbId } = req.body;
|
|
193
193
|
if (!name) {
|
|
194
194
|
return res.status(400).json({ message: 'Missing export name' });
|
|
195
195
|
}
|
|
@@ -199,7 +199,9 @@ function exportRouter(config) {
|
|
|
199
199
|
if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
200
200
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
201
201
|
}
|
|
202
|
-
const
|
|
202
|
+
const mainDb = config.databases?.find(db => db.main) ?? config.databases?.[0];
|
|
203
|
+
const dbType = mainDb?.type ?? 'nosql';
|
|
204
|
+
const result = await (0, export_1.createExport)(config, projectId, description, name, dbType, exportType, isPrivate, allowedOrigin, resolvedNodeSchema, useConnections, useCredentials, storageDbId);
|
|
203
205
|
res.status(201).json({ data: result, message: result.message });
|
|
204
206
|
}
|
|
205
207
|
catch (err) {
|
|
@@ -6,6 +6,7 @@ const manager_1 = require("../../models/manager");
|
|
|
6
6
|
const auth_1 = require("../../utils/auth");
|
|
7
7
|
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
8
8
|
const sql_1 = require("../../adapters/database/sql");
|
|
9
|
+
const sanitize_1 = require("../../utils/sanitize");
|
|
9
10
|
function projectRouter(config) {
|
|
10
11
|
const router = (0, express_1.Router)();
|
|
11
12
|
const Project = (0, manager_1.getModel)('projects');
|
|
@@ -49,19 +50,19 @@ function projectRouter(config) {
|
|
|
49
50
|
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
50
51
|
return res.status(403).json({ message: 'Permission denied' });
|
|
51
52
|
}
|
|
52
|
-
const { name, description,
|
|
53
|
-
const mainDb = config.databases?.find(db => db.main);
|
|
53
|
+
const { name, description, image, allowedOrigin } = req.body;
|
|
54
|
+
const mainDb = config.databases?.find(db => db.main) ?? config.databases?.[0];
|
|
54
55
|
if (!mainDb) {
|
|
55
|
-
return res.status(500).json({ message: '
|
|
56
|
+
return res.status(500).json({ message: 'No database configured' });
|
|
56
57
|
}
|
|
57
|
-
const
|
|
58
|
-
const User = (0, manager_1.getModel)('users',
|
|
58
|
+
const dbType = mainDb.type;
|
|
59
|
+
const User = (0, manager_1.getModel)('users', dbType);
|
|
59
60
|
const user = await User.findOne({ _id: auth.userId });
|
|
60
61
|
if (!user) {
|
|
61
62
|
return res.status(404).json({ message: 'User not found' });
|
|
62
63
|
}
|
|
63
64
|
const newProject = await Project.create({
|
|
64
|
-
dbType
|
|
65
|
+
dbType,
|
|
65
66
|
name,
|
|
66
67
|
description: description || '',
|
|
67
68
|
image: image || '',
|
|
@@ -136,7 +137,8 @@ function projectRouter(config) {
|
|
|
136
137
|
useConnections: e.useConnections,
|
|
137
138
|
useCredentials: e.useCredentials,
|
|
138
139
|
}));
|
|
139
|
-
|
|
140
|
+
const sanitized = (0, sanitize_1.sanitizeProject)({ ...project, id: project._id || project.id, _id: undefined, apiConnections: project.apiConnections || [], exports });
|
|
141
|
+
res.json({ project: sanitized, message: 'Project fetched successfully' });
|
|
140
142
|
}
|
|
141
143
|
catch (err) {
|
|
142
144
|
res.status(500).json({ message: 'Failed to fetch project', details: err.message });
|
|
@@ -304,7 +306,7 @@ function projectRouter(config) {
|
|
|
304
306
|
if (!updatedProject) {
|
|
305
307
|
return res.status(404).json({ message: 'Project not found or not updated' });
|
|
306
308
|
}
|
|
307
|
-
res.status(200).json({ success: true, project: { ...updatedProject, id: updatedProject._id || updatedProject.id, _id: undefined }, message: 'Project image updated successfully' });
|
|
309
|
+
res.status(200).json({ success: true, project: (0, sanitize_1.sanitizeProject)({ ...updatedProject, id: updatedProject._id || updatedProject.id, _id: undefined }), message: 'Project image updated successfully' });
|
|
308
310
|
}
|
|
309
311
|
catch (err) {
|
|
310
312
|
res.status(500).json({ message: 'Failed to update project image', details: err.message });
|
|
@@ -329,7 +331,7 @@ function projectRouter(config) {
|
|
|
329
331
|
if (!updated) {
|
|
330
332
|
return res.status(404).json({ message: 'Project not found or not updated' });
|
|
331
333
|
}
|
|
332
|
-
res.status(200).json({ success: true, project: updated, message: 'Project origins updated successfully' });
|
|
334
|
+
res.status(200).json({ success: true, project: (0, sanitize_1.sanitizeProject)(updated), message: 'Project origins updated successfully' });
|
|
333
335
|
}
|
|
334
336
|
catch (err) {
|
|
335
337
|
res.status(500).json({ message: 'Failed to update project origins', details: err.message });
|
package/dist/models/Model.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare class Model<T extends Document> {
|
|
|
11
11
|
private transformResult;
|
|
12
12
|
findOne(filter: any): Promise<T | null>;
|
|
13
13
|
create(data: T): Promise<T>;
|
|
14
|
+
private updateSqlJsonbArray;
|
|
14
15
|
update(filter: any, data: Partial<T>): Promise<T | null>;
|
|
15
16
|
delete(filter: any): Promise<number>;
|
|
16
17
|
}
|