streamby-core 0.38.0 → 0.39.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/CHANGELOG.md +12 -0
- package/LICENSE +661 -661
- package/README.md +573 -573
- package/dist/adapters/database/connectionManager.js +124 -124
- package/dist/adapters/database/sql.js +1 -1
- package/dist/middleware/routes/apiConnection.d.ts +3 -0
- package/dist/middleware/routes/apiConnection.js +100 -0
- package/dist/middleware/routes/credential.d.ts +3 -0
- package/dist/middleware/routes/credential.js +80 -0
- package/dist/middleware/routes/export.js +16 -0
- package/dist/middleware/routes/project.js +31 -9
- package/dist/models/Model.js +9 -9
- package/dist/services/apiConnection.d.ts +18 -0
- package/dist/services/apiConnection.js +65 -0
- package/dist/services/apiTokenPermissions.js +2 -2
- package/dist/services/credential.d.ts +8 -0
- package/dist/services/credential.js +70 -0
- package/dist/services/pipeline.d.ts +2 -0
- package/dist/services/pipeline.js +292 -0
- 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 +14 -0
- package/package.json +1 -1
|
@@ -43,157 +43,157 @@ 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;
|
|
83
|
-
-- Written when a project is made (routes/project) and by its connection routes.
|
|
84
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS public BOOLEAN DEFAULT TRUE;
|
|
85
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS category TEXT;
|
|
86
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "integrationConnections" JSONB DEFAULT '[]';
|
|
87
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "distributionConnections" JSONB DEFAULT '[]';
|
|
88
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "renderFarmConnections" JSONB DEFAULT '[]';
|
|
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
|
+
-- Written when a project is made (routes/project) and by its connection routes.
|
|
84
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS public BOOLEAN DEFAULT TRUE;
|
|
85
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS category TEXT;
|
|
86
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "integrationConnections" JSONB DEFAULT '[]';
|
|
87
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "distributionConnections" JSONB DEFAULT '[]';
|
|
88
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "renderFarmConnections" JSONB DEFAULT '[]';
|
|
89
89
|
`);
|
|
90
90
|
// Ensure dbType exists with a non-null default
|
|
91
|
-
await pool.query(`
|
|
92
|
-
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql';
|
|
91
|
+
await pool.query(`
|
|
92
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "dbType" VARCHAR(50) NOT NULL DEFAULT 'sql';
|
|
93
93
|
`);
|
|
94
|
-
await pool.query(`
|
|
95
|
-
CREATE TABLE IF NOT EXISTS streamby.project_members (
|
|
96
|
-
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
97
|
-
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
98
|
-
"userId" VARCHAR(255) NOT NULL,
|
|
99
|
-
role VARCHAR(255) NOT NULL DEFAULT 'member',
|
|
100
|
-
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
101
|
-
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
102
|
-
"archivedBy" VARCHAR(255),
|
|
103
|
-
"archivedAt" TIMESTAMP WITH TIME ZONE,
|
|
104
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
105
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
106
|
-
UNIQUE ("projectId", "userId")
|
|
107
|
-
);
|
|
94
|
+
await pool.query(`
|
|
95
|
+
CREATE TABLE IF NOT EXISTS streamby.project_members (
|
|
96
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
97
|
+
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
98
|
+
"userId" VARCHAR(255) NOT NULL,
|
|
99
|
+
role VARCHAR(255) NOT NULL DEFAULT 'member',
|
|
100
|
+
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
101
|
+
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
102
|
+
"archivedBy" VARCHAR(255),
|
|
103
|
+
"archivedAt" TIMESTAMP WITH TIME ZONE,
|
|
104
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
105
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
106
|
+
UNIQUE ("projectId", "userId")
|
|
107
|
+
);
|
|
108
108
|
`);
|
|
109
|
-
await pool.query(`
|
|
110
|
-
ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
|
|
109
|
+
await pool.query(`
|
|
110
|
+
ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
|
|
111
111
|
`);
|
|
112
112
|
console.log('✅ "project_members" table ensured to exist.');
|
|
113
113
|
// Migrate projectId FK column from UUID to TEXT if table predates this schema version
|
|
114
|
-
await pool.query(`
|
|
115
|
-
DO $$ BEGIN
|
|
116
|
-
ALTER TABLE streamby.project_members DROP CONSTRAINT IF EXISTS project_members_projectId_fkey;
|
|
117
|
-
ALTER TABLE streamby.project_members ALTER COLUMN "projectId" TYPE TEXT;
|
|
118
|
-
ALTER TABLE streamby.project_members ADD CONSTRAINT project_members_projectid_fkey
|
|
119
|
-
FOREIGN KEY ("projectId") REFERENCES streamby.projects(id) ON DELETE CASCADE;
|
|
120
|
-
EXCEPTION WHEN OTHERS THEN NULL; END $$;
|
|
114
|
+
await pool.query(`
|
|
115
|
+
DO $$ BEGIN
|
|
116
|
+
ALTER TABLE streamby.project_members DROP CONSTRAINT IF EXISTS project_members_projectId_fkey;
|
|
117
|
+
ALTER TABLE streamby.project_members ALTER COLUMN "projectId" TYPE TEXT;
|
|
118
|
+
ALTER TABLE streamby.project_members ADD CONSTRAINT project_members_projectid_fkey
|
|
119
|
+
FOREIGN KEY ("projectId") REFERENCES streamby.projects(id) ON DELETE CASCADE;
|
|
120
|
+
EXCEPTION WHEN OTHERS THEN NULL; END $$;
|
|
121
121
|
`);
|
|
122
|
-
await pool.query(`
|
|
123
|
-
CREATE TABLE IF NOT EXISTS streamby.exports (
|
|
124
|
-
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
125
|
-
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
126
|
-
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
127
|
-
"filePath" TEXT,
|
|
128
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
129
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
130
|
-
);
|
|
122
|
+
await pool.query(`
|
|
123
|
+
CREATE TABLE IF NOT EXISTS streamby.exports (
|
|
124
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
125
|
+
"projectId" TEXT NOT NULL REFERENCES streamby.projects(id) ON DELETE CASCADE,
|
|
126
|
+
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
127
|
+
"filePath" TEXT,
|
|
128
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
129
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
130
|
+
);
|
|
131
131
|
`);
|
|
132
132
|
console.log('✅ "exports" table ensured to exist.');
|
|
133
133
|
// user_integrations is registered against the main db only (see createRouter.ts), but
|
|
134
134
|
// this function runs per-SQL-connection like projects/exports above — harmless on
|
|
135
135
|
// non-main connections since the model never queries them there.
|
|
136
|
-
await pool.query(`
|
|
137
|
-
CREATE TABLE IF NOT EXISTS streamby.user_integrations (
|
|
138
|
-
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
139
|
-
"userId" VARCHAR(255) NOT NULL,
|
|
140
|
-
kind VARCHAR(20) NOT NULL,
|
|
141
|
-
provider VARCHAR(20) NOT NULL,
|
|
142
|
-
name VARCHAR(255) NOT NULL,
|
|
143
|
-
description TEXT,
|
|
144
|
-
"encryptedCredential" TEXT NOT NULL,
|
|
145
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
146
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
147
|
-
);
|
|
148
|
-
CREATE INDEX IF NOT EXISTS user_integrations_userid_idx ON streamby.user_integrations ("userId");
|
|
136
|
+
await pool.query(`
|
|
137
|
+
CREATE TABLE IF NOT EXISTS streamby.user_integrations (
|
|
138
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
139
|
+
"userId" VARCHAR(255) NOT NULL,
|
|
140
|
+
kind VARCHAR(20) NOT NULL,
|
|
141
|
+
provider VARCHAR(20) NOT NULL,
|
|
142
|
+
name VARCHAR(255) NOT NULL,
|
|
143
|
+
description TEXT,
|
|
144
|
+
"encryptedCredential" TEXT NOT NULL,
|
|
145
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
146
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
147
|
+
);
|
|
148
|
+
CREATE INDEX IF NOT EXISTS user_integrations_userid_idx ON streamby.user_integrations ("userId");
|
|
149
149
|
`);
|
|
150
150
|
console.log('✅ "user_integrations" table ensured to exist.');
|
|
151
151
|
// API tokens: only their hash is kept (see services/apiTokens). Main db only, like
|
|
152
152
|
// user_integrations above. A table from before tokens had a kind and permissions gets the
|
|
153
153
|
// new columns; its old tokens no longer work (they have no kind) and aren't listed.
|
|
154
|
-
await pool.query(`
|
|
155
|
-
CREATE TABLE IF NOT EXISTS streamby.api_tokens (
|
|
156
|
-
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
157
|
-
kind VARCHAR(10),
|
|
158
|
-
"userId" VARCHAR(255) NOT NULL,
|
|
159
|
-
"createdByName" VARCHAR(255),
|
|
160
|
-
name VARCHAR(255) NOT NULL,
|
|
161
|
-
prefix VARCHAR(32) NOT NULL,
|
|
162
|
-
hash CHAR(64) NOT NULL UNIQUE,
|
|
163
|
-
permissions JSONB NOT NULL DEFAULT '[]',
|
|
164
|
-
"projectIds" JSONB NOT NULL,
|
|
165
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
166
|
-
"expiresAt" TIMESTAMP WITH TIME ZONE,
|
|
167
|
-
"lastUsedAt" TIMESTAMP WITH TIME ZONE
|
|
168
|
-
);
|
|
169
|
-
ALTER TABLE streamby.api_tokens ADD COLUMN IF NOT EXISTS kind VARCHAR(10);
|
|
170
|
-
ALTER TABLE streamby.api_tokens ADD COLUMN IF NOT EXISTS "createdByName" VARCHAR(255);
|
|
171
|
-
ALTER TABLE streamby.api_tokens ADD COLUMN IF NOT EXISTS permissions JSONB NOT NULL DEFAULT '[]';
|
|
172
|
-
DO $$ BEGIN
|
|
173
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns
|
|
174
|
-
WHERE table_schema = 'streamby' AND table_name = 'api_tokens' AND column_name = 'access') THEN
|
|
175
|
-
ALTER TABLE streamby.api_tokens ALTER COLUMN access DROP NOT NULL;
|
|
176
|
-
END IF;
|
|
177
|
-
END $$;
|
|
178
|
-
CREATE INDEX IF NOT EXISTS api_tokens_userid_idx ON streamby.api_tokens ("userId");
|
|
179
|
-
CREATE INDEX IF NOT EXISTS api_tokens_kind_idx ON streamby.api_tokens (kind);
|
|
154
|
+
await pool.query(`
|
|
155
|
+
CREATE TABLE IF NOT EXISTS streamby.api_tokens (
|
|
156
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
157
|
+
kind VARCHAR(10),
|
|
158
|
+
"userId" VARCHAR(255) NOT NULL,
|
|
159
|
+
"createdByName" VARCHAR(255),
|
|
160
|
+
name VARCHAR(255) NOT NULL,
|
|
161
|
+
prefix VARCHAR(32) NOT NULL,
|
|
162
|
+
hash CHAR(64) NOT NULL UNIQUE,
|
|
163
|
+
permissions JSONB NOT NULL DEFAULT '[]',
|
|
164
|
+
"projectIds" JSONB NOT NULL,
|
|
165
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
166
|
+
"expiresAt" TIMESTAMP WITH TIME ZONE,
|
|
167
|
+
"lastUsedAt" TIMESTAMP WITH TIME ZONE
|
|
168
|
+
);
|
|
169
|
+
ALTER TABLE streamby.api_tokens ADD COLUMN IF NOT EXISTS kind VARCHAR(10);
|
|
170
|
+
ALTER TABLE streamby.api_tokens ADD COLUMN IF NOT EXISTS "createdByName" VARCHAR(255);
|
|
171
|
+
ALTER TABLE streamby.api_tokens ADD COLUMN IF NOT EXISTS permissions JSONB NOT NULL DEFAULT '[]';
|
|
172
|
+
DO $$ BEGIN
|
|
173
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns
|
|
174
|
+
WHERE table_schema = 'streamby' AND table_name = 'api_tokens' AND column_name = 'access') THEN
|
|
175
|
+
ALTER TABLE streamby.api_tokens ALTER COLUMN access DROP NOT NULL;
|
|
176
|
+
END IF;
|
|
177
|
+
END $$;
|
|
178
|
+
CREATE INDEX IF NOT EXISTS api_tokens_userid_idx ON streamby.api_tokens ("userId");
|
|
179
|
+
CREATE INDEX IF NOT EXISTS api_tokens_kind_idx ON streamby.api_tokens (kind);
|
|
180
180
|
`);
|
|
181
181
|
console.log('✅ "api_tokens" table ensured to exist.');
|
|
182
182
|
// The MCP server's audit log: every tool call (see mcp/audit). Main db only.
|
|
183
|
-
await pool.query(`
|
|
184
|
-
CREATE TABLE IF NOT EXISTS streamby.mcp_audit (
|
|
185
|
-
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
186
|
-
"userId" VARCHAR(255) NOT NULL,
|
|
187
|
-
"tokenId" VARCHAR(255) NOT NULL,
|
|
188
|
-
"projectId" VARCHAR(255),
|
|
189
|
-
tool VARCHAR(100) NOT NULL,
|
|
190
|
-
ok BOOLEAN NOT NULL,
|
|
191
|
-
status INTEGER,
|
|
192
|
-
error TEXT,
|
|
193
|
-
"durationMs" INTEGER NOT NULL,
|
|
194
|
-
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
195
|
-
);
|
|
196
|
-
CREATE INDEX IF NOT EXISTS mcp_audit_token_idx ON streamby.mcp_audit ("tokenId", "createdAt");
|
|
183
|
+
await pool.query(`
|
|
184
|
+
CREATE TABLE IF NOT EXISTS streamby.mcp_audit (
|
|
185
|
+
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::text,
|
|
186
|
+
"userId" VARCHAR(255) NOT NULL,
|
|
187
|
+
"tokenId" VARCHAR(255) NOT NULL,
|
|
188
|
+
"projectId" VARCHAR(255),
|
|
189
|
+
tool VARCHAR(100) NOT NULL,
|
|
190
|
+
ok BOOLEAN NOT NULL,
|
|
191
|
+
status INTEGER,
|
|
192
|
+
error TEXT,
|
|
193
|
+
"durationMs" INTEGER NOT NULL,
|
|
194
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
195
|
+
);
|
|
196
|
+
CREATE INDEX IF NOT EXISTS mcp_audit_token_idx ON streamby.mcp_audit ("tokenId", "createdAt");
|
|
197
197
|
`);
|
|
198
198
|
console.log('✅ "mcp_audit" table ensured to exist.');
|
|
199
199
|
}
|
|
@@ -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
|
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apiConnectionRouter = apiConnectionRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const manager_1 = require("../../models/manager");
|
|
6
|
+
const auth_1 = require("../../utils/auth");
|
|
7
|
+
const apiConnection_1 = require("../../services/apiConnection");
|
|
8
|
+
const VALID_METHODS = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'];
|
|
9
|
+
function apiConnectionRouter(config) {
|
|
10
|
+
const router = (0, express_1.Router)();
|
|
11
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
12
|
+
// List API connections for a project
|
|
13
|
+
router.get('/projects/:id/api/connections', async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const auth = req.auth;
|
|
16
|
+
const projectId = req.params.id;
|
|
17
|
+
const project = await Project.findOne({ _id: projectId });
|
|
18
|
+
if (!project) {
|
|
19
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
20
|
+
}
|
|
21
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
22
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
23
|
+
}
|
|
24
|
+
return res.status(200).json({ data: project.apiConnections || [] });
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
res.status(500).json({ message: 'Failed to fetch API connections', details: err.message });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
// Add a new API connection to a project
|
|
31
|
+
router.post('/projects/:id/api/connections', async (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const auth = req.auth;
|
|
34
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
35
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
36
|
+
}
|
|
37
|
+
const projectId = req.params.id;
|
|
38
|
+
const { name, baseUrl, method, description, credentialId, prefix } = req.body;
|
|
39
|
+
if (!name || !baseUrl) {
|
|
40
|
+
return res.status(400).json({ message: 'name and baseUrl are required' });
|
|
41
|
+
}
|
|
42
|
+
if (!VALID_METHODS.includes(method)) {
|
|
43
|
+
return res.status(400).json({ message: `method must be one of: ${VALID_METHODS.join(', ')}` });
|
|
44
|
+
}
|
|
45
|
+
const project = await Project.findOne({ _id: projectId });
|
|
46
|
+
if (!project) {
|
|
47
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
48
|
+
}
|
|
49
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
50
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
51
|
+
}
|
|
52
|
+
if (credentialId) {
|
|
53
|
+
const credExists = project.credentials?.some((c) => c.id === credentialId);
|
|
54
|
+
if (!credExists) {
|
|
55
|
+
return res.status(400).json({ message: 'Credential not found in project' });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const connection = await (0, apiConnection_1.addApiConnection)(config, projectId, {
|
|
59
|
+
name,
|
|
60
|
+
baseUrl,
|
|
61
|
+
method,
|
|
62
|
+
prefix,
|
|
63
|
+
...(description !== undefined && { description }),
|
|
64
|
+
...(credentialId !== undefined && { credentialId }),
|
|
65
|
+
});
|
|
66
|
+
return res.status(201).json({ data: connection });
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
res.status(500).json({ message: 'Failed to add API connection', details: err.message });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// Delete an API connection from a project
|
|
73
|
+
router.delete('/projects/:id/api/connections/:connectionId', async (req, res) => {
|
|
74
|
+
try {
|
|
75
|
+
const auth = req.auth;
|
|
76
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
77
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
78
|
+
}
|
|
79
|
+
const projectId = req.params.id;
|
|
80
|
+
const { connectionId } = req.params;
|
|
81
|
+
const project = await Project.findOne({ _id: projectId });
|
|
82
|
+
if (!project) {
|
|
83
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
84
|
+
}
|
|
85
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
86
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
87
|
+
}
|
|
88
|
+
const exists = project.apiConnections?.some((c) => c.id === connectionId);
|
|
89
|
+
if (!exists) {
|
|
90
|
+
return res.status(404).json({ message: 'API connection not found' });
|
|
91
|
+
}
|
|
92
|
+
await (0, apiConnection_1.deleteApiConnection)(config, projectId, connectionId);
|
|
93
|
+
return res.status(200).json({ message: 'API connection deleted successfully.' });
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
res.status(500).json({ message: 'Failed to delete API connection', details: err.message });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return router;
|
|
100
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.credentialRouter = credentialRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const manager_1 = require("../../models/manager");
|
|
6
|
+
const auth_1 = require("../../utils/auth");
|
|
7
|
+
const sanitize_1 = require("../../utils/sanitize");
|
|
8
|
+
const credential_1 = require("../../services/credential");
|
|
9
|
+
function credentialRouter(config) {
|
|
10
|
+
const router = (0, express_1.Router)();
|
|
11
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
12
|
+
// Add a new credential to a project
|
|
13
|
+
router.post('/projects/:id/credentials', async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const auth = req.auth;
|
|
16
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
17
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
18
|
+
}
|
|
19
|
+
const projectId = req.params.id;
|
|
20
|
+
const { id, key, value } = req.body;
|
|
21
|
+
if (!key || !value) {
|
|
22
|
+
return res.status(400).json({ message: 'Missing credential key or value' });
|
|
23
|
+
}
|
|
24
|
+
const project = await Project.findOne({ _id: projectId });
|
|
25
|
+
if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
26
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
27
|
+
}
|
|
28
|
+
const updatedProject = await (0, credential_1.addCredential)(config, projectId, { id, key, value });
|
|
29
|
+
res.status(201).json({ message: 'Credential added successfully', project: (0, sanitize_1.sanitizeProject)(updatedProject) });
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
res.status(500).json({ message: 'Failed to add credential', details: err.message });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
// Update an existing credential in a project
|
|
36
|
+
router.patch('/projects/:id/credentials/:credentialId', async (req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
const auth = req.auth;
|
|
39
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
40
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
41
|
+
}
|
|
42
|
+
const projectId = req.params.id;
|
|
43
|
+
const credentialId = req.params.credentialId;
|
|
44
|
+
const { key, value } = req.body;
|
|
45
|
+
if (!key && !value) {
|
|
46
|
+
return res.status(400).json({ message: 'No update fields provided' });
|
|
47
|
+
}
|
|
48
|
+
const project = await Project.findOne({ _id: projectId });
|
|
49
|
+
if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
50
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
51
|
+
}
|
|
52
|
+
const updatedProject = await (0, credential_1.updateCredential)(config, projectId, credentialId, { key, value });
|
|
53
|
+
res.status(200).json({ message: 'Credential updated successfully', project: (0, sanitize_1.sanitizeProject)(updatedProject) });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
res.status(500).json({ message: 'Failed to update credential', details: err.message });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// Delete a credential from a project
|
|
60
|
+
router.delete('/projects/:id/credentials/:credentialId', async (req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
const auth = req.auth;
|
|
63
|
+
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
64
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
65
|
+
}
|
|
66
|
+
const projectId = req.params.id;
|
|
67
|
+
const credentialId = req.params.credentialId;
|
|
68
|
+
const project = await Project.findOne({ _id: projectId });
|
|
69
|
+
if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
70
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
71
|
+
}
|
|
72
|
+
const updatedProject = await (0, credential_1.deleteCredential)(config, projectId, credentialId);
|
|
73
|
+
res.status(200).json({ message: 'Credential deleted successfully', project: (0, sanitize_1.sanitizeProject)(updatedProject) });
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
res.status(500).json({ message: 'Failed to delete credential', details: err.message });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return router;
|
|
80
|
+
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.exportRouter = exportRouter;
|
|
4
4
|
const express_1 = require("express");
|
|
5
5
|
const deliverables_1 = require("../../services/deliverables");
|
|
6
|
+
const sanitize_1 = require("../../utils/sanitize");
|
|
6
7
|
const manager_1 = require("../../models/manager");
|
|
7
8
|
const auth_1 = require("../../utils/auth");
|
|
8
9
|
const export_1 = require("../../services/export");
|
|
@@ -61,6 +62,21 @@ const DEFAULT_NODE_SCHEMA = {
|
|
|
61
62
|
function exportRouter(config) {
|
|
62
63
|
const router = (0, express_1.Router)();
|
|
63
64
|
const Project = (0, manager_1.getModel)('projects');
|
|
65
|
+
// ─── A project's exports, on their own ────────────────────────────────────
|
|
66
|
+
router.get('/projects/:id/exports', async (req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
const auth = req.auth;
|
|
69
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
70
|
+
if (!project)
|
|
71
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
72
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
73
|
+
return res.status(403).json({ message: 'Unauthorized access' });
|
|
74
|
+
return res.status(200).json({ data: (0, sanitize_1.exportSummaries)(project.exports) });
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
res.status(500).json({ message: 'Failed to fetch exports', details: err.message });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
64
80
|
router.get('/projects/:id/exports/:export_id', async (req, res) => {
|
|
65
81
|
try {
|
|
66
82
|
const auth = await config.authProvider(req);
|
|
@@ -196,15 +196,7 @@ function projectRouter(config) {
|
|
|
196
196
|
if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
197
197
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
198
198
|
}
|
|
199
|
-
const exports = (project.exports
|
|
200
|
-
id: e.id,
|
|
201
|
-
name: e.name,
|
|
202
|
-
description: e.description,
|
|
203
|
-
method: e.method,
|
|
204
|
-
private: e.private,
|
|
205
|
-
useConnections: e.useConnections,
|
|
206
|
-
useCredentials: e.useCredentials,
|
|
207
|
-
}));
|
|
199
|
+
const exports = (0, sanitize_1.exportSummaries)(project.exports);
|
|
208
200
|
const sanitized = (0, sanitize_1.sanitizeProject)({ ...project, id: project._id || project.id, _id: undefined, connections: project.connections || [], exports });
|
|
209
201
|
// Whether it has production data, for its Production section to show even without a
|
|
210
202
|
// production node in its workflow.
|
|
@@ -381,6 +373,21 @@ function projectRouter(config) {
|
|
|
381
373
|
res.status(500).json({ message: 'Failed to update project image', details: err.message });
|
|
382
374
|
}
|
|
383
375
|
});
|
|
376
|
+
// ─── A project's allowed origins, on their own ────────────────────────────
|
|
377
|
+
router.get('/projects/:id/origins', async (req, res) => {
|
|
378
|
+
try {
|
|
379
|
+
const auth = req.auth;
|
|
380
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
381
|
+
if (!project)
|
|
382
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
383
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
384
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
385
|
+
return res.status(200).json({ data: project.allowedOrigin || [] });
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
res.status(500).json({ message: 'Failed to fetch project origins', details: err.message });
|
|
389
|
+
}
|
|
390
|
+
});
|
|
384
391
|
router.patch('/projects/:id/origins', async (req, res) => {
|
|
385
392
|
try {
|
|
386
393
|
const auth = req.auth;
|
|
@@ -403,6 +410,21 @@ function projectRouter(config) {
|
|
|
403
410
|
res.status(500).json({ message: 'Failed to update project origins', details: err.message });
|
|
404
411
|
}
|
|
405
412
|
});
|
|
413
|
+
// ─── A project's integrations, on their own ───────────────────────────────
|
|
414
|
+
router.get('/projects/:id/integrations', async (req, res) => {
|
|
415
|
+
try {
|
|
416
|
+
const auth = req.auth;
|
|
417
|
+
const project = await Project.findOne({ _id: req.params.id });
|
|
418
|
+
if (!project)
|
|
419
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
420
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
421
|
+
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
422
|
+
return res.status(200).json({ data: (0, sanitize_1.sanitizeIntegrationConnections)(project.integrationConnections) });
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
res.status(500).json({ message: 'Failed to fetch project integrations', details: err.message });
|
|
426
|
+
}
|
|
427
|
+
});
|
|
406
428
|
// ─── Add an integration to an existing project ───────────────────────────
|
|
407
429
|
router.post('/projects/:id/integrations', async (req, res) => {
|
|
408
430
|
try {
|
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
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StreamByConfig, ApiConnection, ApiConnectionMethod } from '../types';
|
|
2
|
+
export declare function addApiConnection(config: StreamByConfig, projectId: string, data: {
|
|
3
|
+
name: string;
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
method: ApiConnectionMethod;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
credentialId?: string;
|
|
9
|
+
}): Promise<ApiConnection>;
|
|
10
|
+
export declare function updateApiConnection(config: StreamByConfig, projectId: string, connectionId: string, data: {
|
|
11
|
+
name?: string;
|
|
12
|
+
apiUrl?: string;
|
|
13
|
+
method?: ApiConnectionMethod;
|
|
14
|
+
prefix?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
credentialId?: string;
|
|
17
|
+
}): Promise<ApiConnection>;
|
|
18
|
+
export declare function deleteApiConnection(config: StreamByConfig, projectId: string, connectionId: string): Promise<void>;
|