streamby-core 0.41.0 → 0.43.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +143 -8
  3. package/dist/adapters/database/connectionManager.js +33 -0
  4. package/dist/index.d.ts +5 -1
  5. package/dist/index.js +5 -1
  6. package/dist/mcp/tools.js +93 -11
  7. package/dist/mcp/workflowNodes.js +9 -3
  8. package/dist/middleware/createRouter.js +6 -0
  9. package/dist/middleware/routes/connection.js +5 -2
  10. package/dist/middleware/routes/dbConnection.js +29 -5
  11. package/dist/middleware/routes/document.d.ts +3 -0
  12. package/dist/middleware/routes/document.js +348 -0
  13. package/dist/middleware/routes/export.d.ts +28 -0
  14. package/dist/middleware/routes/export.js +8 -4
  15. package/dist/middleware/routes/pipeline.js +5 -2
  16. package/dist/middleware/routes/project.js +17 -12
  17. package/dist/middleware/routes/projectResources.d.ts +8 -0
  18. package/dist/middleware/routes/projectResources.js +51 -0
  19. package/dist/middleware/routes/rights.js +72 -52
  20. package/dist/middleware/routes/storage.js +35 -2
  21. package/dist/middleware/routes/storageConnection.js +38 -15
  22. package/dist/middleware/routes/userIntegration.js +42 -1
  23. package/dist/middleware/routes/workflow.js +120 -37
  24. package/dist/models/Model.js +12 -1
  25. package/dist/services/apiTokenPermissions.d.ts +1 -1
  26. package/dist/services/apiTokenPermissions.js +10 -0
  27. package/dist/services/auth.d.ts +2 -0
  28. package/dist/services/auth.js +17 -1
  29. package/dist/services/creditsSheets.d.ts +39 -0
  30. package/dist/services/creditsSheets.js +129 -0
  31. package/dist/services/dependents.d.ts +11 -0
  32. package/dist/services/dependents.js +81 -0
  33. package/dist/services/documents.d.ts +66 -0
  34. package/dist/services/documents.js +347 -0
  35. package/dist/services/executor.js +8 -0
  36. package/dist/services/oauthTokens.js +2 -2
  37. package/dist/services/production.d.ts +1 -1
  38. package/dist/services/production.js +2 -2
  39. package/dist/services/projectIntegration.d.ts +14 -0
  40. package/dist/services/projectIntegration.js +32 -0
  41. package/dist/services/rights.d.ts +44 -0
  42. package/dist/services/rights.js +123 -0
  43. package/dist/services/workflowResources.d.ts +90 -0
  44. package/dist/services/workflowResources.js +288 -0
  45. package/dist/types/index.d.ts +45 -0
  46. package/package.json +1 -1
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dbConnectionRouter = dbConnectionRouter;
4
4
  const express_1 = require("express");
5
+ const workflowResources_1 = require("../../services/workflowResources");
6
+ const projectResources_1 = require("./projectResources");
7
+ const projectIntegration_1 = require("../../services/projectIntegration");
5
8
  const mongodb_1 = require("mongodb");
6
9
  const manager_1 = require("../../models/manager");
7
10
  const auth_1 = require("../../utils/auth");
@@ -45,11 +48,29 @@ function dbConnectionRouter(config) {
45
48
  res.status(500).json({ message: 'Failed to fetch DB connections', details: err.message });
46
49
  }
47
50
  });
51
+ // The project's database connections as they're answered, after each change too.
52
+ const listNow = async (projectId) => (0, sanitize_1.sanitizeConnections)((await Project.findOne({ _id: projectId }))?.dbConnections);
48
53
  // ─── Add DB connection ────────────────────────────────────────────────────
49
54
  router.post('/projects/:id/connections/db', async (req, res) => {
50
55
  try {
51
56
  const auth = req.auth;
52
57
  const { name, dbType, credential, integrationId, description } = req.body;
58
+ if (credential && integrationId) {
59
+ return res.status(400).json({ message: 'Provide only one of credential or integrationId' });
60
+ }
61
+ // An integration (the user's, or a built-in) is a named, typed database already.
62
+ if (integrationId) {
63
+ const project = await Project.findOne({ _id: req.params.id });
64
+ if (!project)
65
+ return res.status(404).json({ message: 'Project not found' });
66
+ if (!(0, auth_1.canEditProject)(project, auth.userId))
67
+ return res.status(403).json({ message: 'Unauthorized project access' });
68
+ const result = await (0, projectIntegration_1.connectIntegration)(project, integrationId, auth, config, { kind: 'database', description });
69
+ if ('error' in result)
70
+ return res.status(result.status).json({ message: result.error });
71
+ await (0, workflowResources_1.addToWorkflow)(req.params.id, 'db', result.connection);
72
+ return res.status(201).json({ data: (0, sanitize_1.sanitizeConnection)(result.connection), dbConnections: await listNow(req.params.id) });
73
+ }
53
74
  if (!name || !dbType) {
54
75
  return res.status(400).json({ message: 'name and dbType are required' });
55
76
  }
@@ -93,7 +114,8 @@ function dbConnectionRouter(config) {
93
114
  ...(description !== undefined && { description }),
94
115
  };
95
116
  await Project.update({ _id: req.params.id }, { $push: { dbConnections: connection } });
96
- return res.status(201).json({ data: (0, sanitize_1.sanitizeConnection)(connection) });
117
+ await (0, workflowResources_1.addToWorkflow)(req.params.id, 'db', connection);
118
+ return res.status(201).json({ data: (0, sanitize_1.sanitizeConnection)(connection), dbConnections: await listNow(req.params.id) });
97
119
  }
98
120
  catch (err) {
99
121
  res.status(500).json({ message: 'Failed to add DB connection', details: err.message });
@@ -111,11 +133,13 @@ function dbConnectionRouter(config) {
111
133
  const conn = (0, connectionResolver_1.findDbConnection)(project, req.params.connId);
112
134
  if (!conn)
113
135
  return res.status(404).json({ message: 'DB connection not found' });
114
- if (conn.source === 'builtin') {
115
- return res.status(403).json({ message: 'Cannot delete a built-in database connection' });
116
- }
136
+ // A built-in is disconnected, not deleted: what the project keeps in it stays, and it can
137
+ // be connected again (routes/projectResources).
117
138
  await Project.update({ _id: req.params.id }, { $pull: { dbConnections: { id: req.params.connId } } });
118
- return res.status(200).json({ message: 'DB connection deleted successfully' });
139
+ if (conn.source === 'builtin')
140
+ await (0, projectResources_1.rememberDisconnectedBuiltin)(req.params.id, 'db', conn);
141
+ await (0, workflowResources_1.removeFromWorkflow)(req.params.id, 'db', req.params.connId);
142
+ return res.status(200).json({ message: 'DB connection deleted successfully', dbConnections: await listNow(req.params.id) });
119
143
  }
120
144
  catch (err) {
121
145
  res.status(500).json({ message: 'Failed to delete DB connection', details: err.message });
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig } from '../../types';
3
+ export declare function documentRouter(_config: StreamByConfig): Router;
@@ -0,0 +1,348 @@
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.documentRouter = documentRouter;
7
+ const express_1 = require("express");
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const manager_1 = require("../../models/manager");
10
+ const auth_1 = require("../../utils/auth");
11
+ const wsHub_1 = require("../../services/wsHub");
12
+ const documents_1 = require("../../services/documents");
13
+ const auth_2 = require("../../services/auth");
14
+ const creditsSheets_1 = require("../../services/creditsSheets");
15
+ const MAX_TITLE = 200;
16
+ const MAX_CONTENT = 1000000;
17
+ // What happened to a project's documents, to whoever is in it now: a view of a document
18
+ // refreshes itself, an editor warns of a newer version. `version` and `author` come with a save.
19
+ function broadcast(project, data) {
20
+ const projectId = String(project._id ?? project.id);
21
+ for (const member of project.members ?? []) {
22
+ if ((0, auth_1.activeMemberOf)(project, member.userId))
23
+ (0, wsHub_1.emitToUser)(member.userId, { type: 'documentEvent', data: { projectId, ...data } });
24
+ }
25
+ }
26
+ const isVersion = (value) => Number.isInteger(value) && value >= 1;
27
+ function checkTitle(value) {
28
+ if (typeof value !== 'string' || !value.trim())
29
+ return { error: 'A title is required' };
30
+ if (value.trim().length > MAX_TITLE)
31
+ return { error: `A title has at most ${MAX_TITLE} characters` };
32
+ return value.trim();
33
+ }
34
+ function checkContent(value, kind = 'document') {
35
+ if (kind === 'credits')
36
+ return (0, creditsSheets_1.checkSheetContent)(value);
37
+ if (typeof value !== 'string')
38
+ return { error: 'content must be text (markdown)' };
39
+ if (value.length > MAX_CONTENT)
40
+ return { error: `A document has at most ${MAX_CONTENT} characters` };
41
+ return value;
42
+ }
43
+ // Where a document may go: the top (null), or a folder of the same project that isn't the
44
+ // document itself nor inside it.
45
+ function checkParent(value, documents, movingId) {
46
+ if (value === null)
47
+ return null;
48
+ if (typeof value !== 'string')
49
+ return { error: 'parentId must be a folder\'s id, or null' };
50
+ const parent = documents.find(d => d.id === value);
51
+ if (!parent)
52
+ return { error: 'No such folder in this project' };
53
+ if (parent.kind !== 'folder')
54
+ return { error: 'A document goes in a folder, not in another document' };
55
+ if (movingId && (0, documents_1.withDescendants)(documents, movingId).includes(value))
56
+ return { error: 'A folder can\'t go inside itself' };
57
+ return value;
58
+ }
59
+ const failed = (value) => typeof value === 'object' && value !== null && 'error' in value;
60
+ function documentRouter(_config) {
61
+ const router = (0, express_1.Router)();
62
+ const Project = (0, manager_1.getModel)('projects');
63
+ // The project, if the caller may see it (or, with `edit`, change it); otherwise the answer.
64
+ async function projectFor(req, res, edit = false) {
65
+ const auth = req.auth;
66
+ const project = await Project.findOne({ _id: req.params.projectId });
67
+ if (!project) {
68
+ res.status(404).json({ message: 'Project not found' });
69
+ return null;
70
+ }
71
+ if (!(edit ? auth_1.canEditProject : auth_1.isProjectMember)(project, auth.userId)) {
72
+ res.status(403).json({ message: 'Unauthorized' });
73
+ return null;
74
+ }
75
+ return { project, auth, projectId: String(req.params.projectId) };
76
+ }
77
+ const conflict = (res, current) => res.status(409).json({
78
+ message: `The document changed: it's at version ${current.version} now. Read it again, and make the change on that version.`,
79
+ document: current,
80
+ });
81
+ router.get('/projects/:projectId/documents', async (req, res) => {
82
+ try {
83
+ const found = await projectFor(req, res);
84
+ if (!found)
85
+ return;
86
+ res.json({ documents: await (0, documents_1.documentStore)().list(found.projectId) });
87
+ }
88
+ catch (err) {
89
+ res.status(500).json({ message: err.message });
90
+ }
91
+ });
92
+ router.post('/projects/:projectId/documents', async (req, res) => {
93
+ try {
94
+ const found = await projectFor(req, res, true);
95
+ if (!found)
96
+ return;
97
+ const { project, auth, projectId } = found;
98
+ if (req.body?.kind !== undefined && !['document', 'folder', 'credits'].includes(req.body.kind))
99
+ return res.status(400).json({ message: "kind is 'document', 'folder' or 'credits'" });
100
+ const kind = req.body?.kind ?? 'document';
101
+ const title = checkTitle(req.body?.title);
102
+ if (failed(title))
103
+ return res.status(400).json({ message: title.error });
104
+ if (kind === 'folder' && req.body?.content)
105
+ return res.status(400).json({ message: 'A folder has no content' });
106
+ const content = kind === 'folder' ? '' : checkContent(req.body?.content ?? '', kind);
107
+ if (failed(content))
108
+ return res.status(400).json({ message: content.error });
109
+ const store = (0, documents_1.documentStore)();
110
+ const documents = await store.list(projectId);
111
+ const parentId = checkParent(req.body?.parentId ?? null, documents);
112
+ if (failed(parentId))
113
+ return res.status(400).json({ message: parentId.error });
114
+ if (req.body?.position !== undefined && !Number.isInteger(req.body.position))
115
+ return res.status(400).json({ message: 'position must be a whole number' });
116
+ // At the end of its folder, unless placed.
117
+ const siblings = documents.filter(d => d.parentId === parentId);
118
+ const position = req.body?.position ?? (siblings.length ? Math.max(...siblings.map(d => d.position)) + 1 : 0);
119
+ const now = new Date();
120
+ const author = (0, documents_1.authorOf)(auth);
121
+ const document = await store.create({
122
+ id: crypto_1.default.randomUUID(), projectId, kind, title, parentId, position, content, version: 1,
123
+ exportable: false, publishedVersion: null, createdAt: now, createdBy: author, updatedAt: now, updatedBy: author,
124
+ });
125
+ broadcast(project, { action: 'created', documentIds: [document.id], version: 1, title, author });
126
+ res.status(201).json({ document });
127
+ }
128
+ catch (err) {
129
+ res.status(500).json({ message: err.message });
130
+ }
131
+ });
132
+ router.get('/projects/:projectId/documents/:docId', async (req, res) => {
133
+ try {
134
+ const found = await projectFor(req, res);
135
+ if (!found)
136
+ return;
137
+ const document = await (0, documents_1.documentStore)().get(found.projectId, req.params.docId);
138
+ if (!document)
139
+ return res.status(404).json({ message: 'Document not found' });
140
+ res.json({ document });
141
+ }
142
+ catch (err) {
143
+ res.status(500).json({ message: err.message });
144
+ }
145
+ });
146
+ // Its title or content (a new version, made on `baseVersion`), and where it is in the tree
147
+ // or whether it's exportable (not a version).
148
+ router.patch('/projects/:projectId/documents/:docId', async (req, res) => {
149
+ try {
150
+ const found = await projectFor(req, res, true);
151
+ if (!found)
152
+ return;
153
+ const { project, auth, projectId } = found;
154
+ const { docId } = req.params;
155
+ const body = req.body ?? {};
156
+ const store = (0, documents_1.documentStore)();
157
+ const current = await store.get(projectId, docId);
158
+ if (!current)
159
+ return res.status(404).json({ message: 'Document not found' });
160
+ const changesText = body.title !== undefined || body.content !== undefined;
161
+ const changesPlace = body.parentId !== undefined || body.position !== undefined || body.exportable !== undefined;
162
+ if (!changesText && !changesPlace)
163
+ return res.status(400).json({ message: 'Nothing to change: title, content, parentId, position or exportable' });
164
+ // Everything is checked before anything changes.
165
+ let text;
166
+ if (changesText) {
167
+ if (!isVersion(body.baseVersion))
168
+ return res.status(400).json({ message: 'baseVersion is required: the version the change was made on' });
169
+ const title = body.title === undefined ? current.title : checkTitle(body.title);
170
+ if (failed(title))
171
+ return res.status(400).json({ message: title.error });
172
+ if (current.kind === 'folder' && body.content)
173
+ return res.status(400).json({ message: 'A folder has no content' });
174
+ const content = body.content === undefined ? current.content : checkContent(body.content, current.kind);
175
+ if (failed(content))
176
+ return res.status(400).json({ message: content.error });
177
+ text = { title, content };
178
+ }
179
+ const place = {};
180
+ if (body.parentId !== undefined) {
181
+ const parentId = checkParent(body.parentId, await store.list(projectId), docId);
182
+ if (failed(parentId))
183
+ return res.status(400).json({ message: parentId.error });
184
+ place.parentId = parentId;
185
+ }
186
+ if (body.position !== undefined) {
187
+ if (!Number.isInteger(body.position))
188
+ return res.status(400).json({ message: 'position must be a whole number' });
189
+ place.position = body.position;
190
+ }
191
+ if (body.exportable !== undefined) {
192
+ // Whether its exports deliver it is publishing it: a token needs docs:publish.
193
+ if (auth.apiToken && !(0, auth_2.tokenAllows)(auth.apiToken, 'docs:publish')) {
194
+ return res.status(403).json({ error: "This API token doesn't have the 'docs:publish' permission" });
195
+ }
196
+ if (typeof body.exportable !== 'boolean')
197
+ return res.status(400).json({ message: 'exportable is true or false' });
198
+ if (current.kind === 'folder')
199
+ return res.status(400).json({ message: 'A folder isn\'t exported: its documents are' });
200
+ place.exportable = body.exportable;
201
+ }
202
+ const author = (0, documents_1.authorOf)(auth);
203
+ let document = current;
204
+ if (text) {
205
+ document = await store.save(projectId, docId, body.baseVersion, text, author, new Date());
206
+ if (!document) {
207
+ const now = await store.get(projectId, docId);
208
+ return now ? conflict(res, now) : res.status(404).json({ message: 'Document not found' });
209
+ }
210
+ }
211
+ if (changesPlace)
212
+ document = await store.update(projectId, docId, place);
213
+ if (!document)
214
+ return res.status(404).json({ message: 'Document not found' });
215
+ broadcast(project, text
216
+ ? { action: 'saved', documentIds: [docId], version: document.version, title: document.title, author }
217
+ : { action: 'changed', documentIds: [docId], author });
218
+ res.json({ document });
219
+ }
220
+ catch (err) {
221
+ res.status(500).json({ message: err.message });
222
+ }
223
+ });
224
+ // What's under one of its headings, the rest untouched: a new version, made on `baseVersion`.
225
+ router.patch('/projects/:projectId/documents/:docId/sections', async (req, res) => {
226
+ try {
227
+ const found = await projectFor(req, res, true);
228
+ if (!found)
229
+ return;
230
+ const { project, auth, projectId } = found;
231
+ const { docId } = req.params;
232
+ const { baseVersion, heading, content } = req.body ?? {};
233
+ if (!isVersion(baseVersion))
234
+ return res.status(400).json({ message: 'baseVersion is required: the version the change was made on' });
235
+ if (typeof heading !== 'string' || !heading.trim())
236
+ return res.status(400).json({ message: 'heading is required: the section\'s heading, e.g. "## Architecture"' });
237
+ const body = checkContent(content);
238
+ if (failed(body))
239
+ return res.status(400).json({ message: body.error });
240
+ const store = (0, documents_1.documentStore)();
241
+ const current = await store.get(projectId, docId);
242
+ if (!current)
243
+ return res.status(404).json({ message: 'Document not found' });
244
+ if (current.kind === 'folder')
245
+ return res.status(400).json({ message: 'A folder has no content' });
246
+ if (current.kind === 'credits')
247
+ return res.status(400).json({ message: 'A credits sheet has no sections: its content is its sources' });
248
+ // A section of another version than the one it was read on could be another text.
249
+ if (current.version !== baseVersion)
250
+ return conflict(res, current);
251
+ const replaced = (0, documents_1.replaceSection)(current.content, heading, body);
252
+ if ('error' in replaced) {
253
+ return replaced.error === 'not-found'
254
+ ? res.status(404).json({ message: `No section "${heading.trim()}" in the document` })
255
+ : res.status(400).json({ message: `More than one section is "${heading.trim()}": give the heading with its #s, or update the whole document` });
256
+ }
257
+ const author = (0, documents_1.authorOf)(auth);
258
+ const document = await store.save(projectId, docId, baseVersion, { title: current.title, content: replaced.content }, author, new Date());
259
+ if (!document) {
260
+ const now = await store.get(projectId, docId);
261
+ return now ? conflict(res, now) : res.status(404).json({ message: 'Document not found' });
262
+ }
263
+ broadcast(project, { action: 'saved', documentIds: [docId], version: document.version, title: document.title, author });
264
+ res.json({ document });
265
+ }
266
+ catch (err) {
267
+ res.status(500).json({ message: err.message });
268
+ }
269
+ });
270
+ router.get('/projects/:projectId/documents/:docId/versions', async (req, res) => {
271
+ try {
272
+ const found = await projectFor(req, res);
273
+ if (!found)
274
+ return;
275
+ const store = (0, documents_1.documentStore)();
276
+ if (!(await store.get(found.projectId, req.params.docId)))
277
+ return res.status(404).json({ message: 'Document not found' });
278
+ res.json({ versions: await store.versions(found.projectId, req.params.docId) });
279
+ }
280
+ catch (err) {
281
+ res.status(500).json({ message: err.message });
282
+ }
283
+ });
284
+ router.get('/projects/:projectId/documents/:docId/versions/:version', async (req, res) => {
285
+ try {
286
+ const found = await projectFor(req, res);
287
+ if (!found)
288
+ return;
289
+ const number = Number(req.params.version);
290
+ const version = isVersion(number) ? await (0, documents_1.documentStore)().version(found.projectId, req.params.docId, number) : null;
291
+ if (!version)
292
+ return res.status(404).json({ message: 'Version not found' });
293
+ res.json({ version });
294
+ }
295
+ catch (err) {
296
+ res.status(500).json({ message: err.message });
297
+ }
298
+ });
299
+ // The version its exports deliver (null: none, it's no longer published). Editing it later
300
+ // changes nothing exported until another version is published.
301
+ router.post('/projects/:projectId/documents/:docId/publish', async (req, res) => {
302
+ try {
303
+ const found = await projectFor(req, res, true);
304
+ if (!found)
305
+ return;
306
+ const { project, auth, projectId } = found;
307
+ const { docId } = req.params;
308
+ const version = req.body?.version;
309
+ if (version !== null && !isVersion(version))
310
+ return res.status(400).json({ message: 'version is the version to publish, or null to publish none' });
311
+ const store = (0, documents_1.documentStore)();
312
+ const current = await store.get(projectId, docId);
313
+ if (!current)
314
+ return res.status(404).json({ message: 'Document not found' });
315
+ if (current.kind === 'folder')
316
+ return res.status(400).json({ message: 'A folder isn\'t published: its documents are' });
317
+ if (version !== null && version > current.version)
318
+ return res.status(404).json({ message: 'Version not found' });
319
+ const document = await store.update(projectId, docId, { publishedVersion: version });
320
+ broadcast(project, { action: 'changed', documentIds: [docId], author: (0, documents_1.authorOf)(auth) });
321
+ res.json({ document });
322
+ }
323
+ catch (err) {
324
+ res.status(500).json({ message: err.message });
325
+ }
326
+ });
327
+ // A document, or a folder with everything in it.
328
+ router.delete('/projects/:projectId/documents/:docId', async (req, res) => {
329
+ try {
330
+ const found = await projectFor(req, res, true);
331
+ if (!found)
332
+ return;
333
+ const { project, auth, projectId } = found;
334
+ const store = (0, documents_1.documentStore)();
335
+ const documents = await store.list(projectId);
336
+ if (!documents.some(d => d.id === req.params.docId))
337
+ return res.status(404).json({ message: 'Document not found' });
338
+ const ids = (0, documents_1.withDescendants)(documents, req.params.docId);
339
+ await store.remove(projectId, ids);
340
+ broadcast(project, { action: 'deleted', documentIds: ids, author: (0, documents_1.authorOf)(auth) });
341
+ res.json({ deleted: ids });
342
+ }
343
+ catch (err) {
344
+ res.status(500).json({ message: err.message });
345
+ }
346
+ });
347
+ return router;
348
+ }
@@ -1,3 +1,31 @@
1
1
  import { Router } from 'express';
2
2
  import { StreamByConfig } from '../../types';
3
+ export declare const DEFAULT_NODE_SCHEMA: {
4
+ nodes: {
5
+ id: string;
6
+ type: string;
7
+ position: {
8
+ x: number;
9
+ y: number;
10
+ };
11
+ data: {
12
+ label: string;
13
+ subtitle: string;
14
+ };
15
+ width: number;
16
+ height: number;
17
+ }[];
18
+ edges: {
19
+ id: string;
20
+ source: string;
21
+ sourceHandle: string;
22
+ target: string;
23
+ targetHandle: string;
24
+ animated: boolean;
25
+ style: {
26
+ stroke: string;
27
+ strokeWidth: number;
28
+ };
29
+ }[];
30
+ };
3
31
  export declare function exportRouter(config: StreamByConfig): Router;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_NODE_SCHEMA = void 0;
3
4
  exports.exportRouter = exportRouter;
4
5
  const express_1 = require("express");
6
+ const workflowResources_1 = require("../../services/workflowResources");
5
7
  const deliverables_1 = require("../../services/deliverables");
6
8
  const sanitize_1 = require("../../utils/sanitize");
7
9
  const manager_1 = require("../../models/manager");
@@ -11,7 +13,7 @@ const executor_1 = require("../../services/executor");
11
13
  const connectionManager_1 = require("../../adapters/database/connectionManager");
12
14
  const mongodb_1 = require("mongodb");
13
15
  const encryption_1 = require("../../utils/encryption");
14
- const DEFAULT_NODE_SCHEMA = {
16
+ exports.DEFAULT_NODE_SCHEMA = {
15
17
  nodes: [
16
18
  {
17
19
  id: 'request',
@@ -251,7 +253,7 @@ function exportRouter(config) {
251
253
  if (!name) {
252
254
  return res.status(400).json({ message: 'Missing export name' });
253
255
  }
254
- const resolvedNodeSchema = nodeSchema ?? DEFAULT_NODE_SCHEMA;
256
+ const resolvedNodeSchema = nodeSchema ?? exports.DEFAULT_NODE_SCHEMA;
255
257
  const exportType = useConnections ? 'externalApi' : 'json';
256
258
  const project = await Project.findOne({ _id: projectId });
257
259
  if (!project || !(0, auth_1.canEditProject)(project, auth.userId)) {
@@ -260,7 +262,8 @@ function exportRouter(config) {
260
262
  const mainDb = config.databases?.find(db => db.main) ?? config.databases?.[0];
261
263
  const dbType = mainDb?.type ?? 'nosql';
262
264
  const result = await (0, export_1.createExport)(config, projectId, description, name, dbType, exportType, isPrivate, allowedOrigin, resolvedNodeSchema, useConnections, useCredentials, storageDbId);
263
- res.status(201).json({ data: result, message: result.message });
265
+ await (0, workflowResources_1.addToWorkflow)(projectId, 'export', { id: result.exportId, name, method: 'GET' });
266
+ res.status(201).json({ data: result, exports: (0, sanitize_1.exportSummaries)((await Project.findOne({ _id: projectId }))?.exports), message: result.message });
264
267
  }
265
268
  catch (err) {
266
269
  res.status(500).json({ message: 'Failed to create raw export', details: err.message });
@@ -310,7 +313,8 @@ function exportRouter(config) {
310
313
  }
311
314
  await (0, export_1.deleteExport)(config, projectId, exportId, project.dbType);
312
315
  await (0, deliverables_1.deleteDeliverablesOf)(projectId, { exportId });
313
- res.status(200).json({ message: 'Export deleted successfully' });
316
+ await (0, workflowResources_1.removeFromWorkflow)(projectId, 'export', exportId);
317
+ res.status(200).json({ message: 'Export deleted successfully', exports: (0, sanitize_1.exportSummaries)((await Project.findOne({ _id: projectId }))?.exports) });
314
318
  }
315
319
  catch (err) {
316
320
  res.status(500).json({ message: 'Failed to delete export', details: err.message });
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.pipelineRouter = pipelineRouter;
7
7
  const express_1 = require("express");
8
+ const workflowResources_1 = require("../../services/workflowResources");
8
9
  const auth_1 = require("../../utils/auth");
9
10
  const connectionManager_1 = require("../../adapters/database/connectionManager");
10
11
  const manager_1 = require("../../models/manager");
@@ -86,7 +87,8 @@ function pipelineRouter(_config) {
86
87
  };
87
88
  await db.collection('pipelines').insertOne({ ...pipeline });
88
89
  await syncProjectRefs(projectId);
89
- res.status(201).json({ pipeline });
90
+ await (0, workflowResources_1.addToWorkflow)(projectId, 'pipeline', pipeline);
91
+ res.status(201).json({ pipeline, pipelines: (await Project.findOne({ _id: projectId }))?.pipelines ?? [] });
90
92
  }
91
93
  catch (err) {
92
94
  res.status(500).json({ message: err.message });
@@ -161,7 +163,8 @@ function pipelineRouter(_config) {
161
163
  return res.status(500).json({ message: 'Database not available' });
162
164
  await db.collection('pipelines').deleteOne({ id: pipelineId, projectId });
163
165
  await syncProjectRefs(projectId);
164
- res.json({ message: 'Pipeline deleted' });
166
+ await (0, workflowResources_1.removeFromWorkflow)(projectId, 'pipeline', pipelineId);
167
+ res.json({ message: 'Pipeline deleted', pipelines: (await Project.findOne({ _id: projectId }))?.pipelines ?? [] });
165
168
  }
166
169
  catch (err) {
167
170
  res.status(500).json({ message: err.message });
@@ -2,17 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.projectRouter = projectRouter;
4
4
  const express_1 = require("express");
5
+ const workflowResources_1 = require("../../services/workflowResources");
5
6
  const manager_1 = require("../../models/manager");
6
7
  const auth_1 = require("../../utils/auth");
7
8
  const connectionManager_1 = require("../../adapters/database/connectionManager");
8
9
  const sql_1 = require("../../adapters/database/sql");
9
10
  const sanitize_1 = require("../../utils/sanitize");
10
11
  const projectIntegration_1 = require("../../services/projectIntegration");
12
+ const projectResources_1 = require("./projectResources");
11
13
  const auth_2 = require("../../services/auth");
12
14
  const apiTokens_1 = require("../../services/apiTokens");
13
15
  const nosqlDb_1 = require("../../utils/nosqlDb");
14
16
  const production_1 = require("../../services/production");
15
17
  const jobStore_1 = require("../../services/jobStore");
18
+ const documents_1 = require("../../services/documents");
16
19
  function projectRouter(config) {
17
20
  const router = (0, express_1.Router)();
18
21
  const Project = (0, manager_1.getModel)('projects');
@@ -261,6 +264,8 @@ function projectRouter(config) {
261
264
  await (0, production_1.deleteProductionData)(String(projectId));
262
265
  if (projectId)
263
266
  await (0, jobStore_1.jobStore)().deleteProject(String(projectId));
267
+ if (projectId)
268
+ await (0, documents_1.documentStore)().deleteProject(String(projectId));
264
269
  const allProjects = await Project.find({});
265
270
  const projects = allProjects
266
271
  .filter(project => {
@@ -442,18 +447,11 @@ function projectRouter(config) {
442
447
  return res.status(404).json({ message: 'Project not found' });
443
448
  if (!(0, auth_1.canEditProject)(project, auth.userId))
444
449
  return res.status(403).json({ message: 'Unauthorized project access' });
445
- const alreadyConnected = [...(project.dbConnections ?? []), ...(project.storageConnections ?? []), ...(project.integrationConnections ?? [])]
446
- .some((c) => c.integrationId === integrationId);
447
- if (alreadyConnected)
448
- return res.status(400).json({ message: 'This integration is already connected to the project' });
449
- const result = await (0, projectIntegration_1.classifyIntegrationId)(integrationId, auth, config, projectId);
450
+ const result = await (0, projectIntegration_1.connectIntegration)(project, integrationId, auth, config);
450
451
  if ('error' in result)
451
452
  return res.status(result.status).json({ message: result.error });
452
- const updated = result.kind === 'database'
453
- ? await Project.update({ _id: projectId }, { $push: { dbConnections: result.connection } })
454
- : result.kind === 'storage'
455
- ? await Project.update({ _id: projectId }, { $push: { storageConnections: result.connection } })
456
- : await Project.update({ _id: projectId }, { $push: { integrationConnections: result.connection } });
453
+ await (0, workflowResources_1.addToWorkflow)(projectId, result.kind === 'database' ? 'db' : result.kind, result.connection);
454
+ const updated = await Project.findOne({ _id: projectId });
457
455
  if (!updated)
458
456
  return res.status(404).json({ message: 'Project not found or not updated' });
459
457
  res.status(201).json({ data: result.connection, project: (0, sanitize_1.sanitizeProject)(updated) });
@@ -478,13 +476,20 @@ function projectRouter(config) {
478
476
  const integrationConn = (project.integrationConnections ?? []).find((c) => c.integrationId === integrationId);
479
477
  if (!dbConn && !storageConn && !integrationConn)
480
478
  return res.status(404).json({ message: 'Integration not connected to this project' });
481
- const updated = dbConn
479
+ const removed = dbConn
482
480
  ? await Project.update({ _id: projectId }, { $pull: { dbConnections: { id: dbConn.id } } })
483
481
  : storageConn
484
482
  ? await Project.update({ _id: projectId }, { $pull: { storageConnections: { id: storageConn.id } } })
485
483
  : await Project.update({ _id: projectId }, { $pull: { integrationConnections: { id: integrationConn.id } } });
486
- if (!updated)
484
+ if (!removed)
487
485
  return res.status(404).json({ message: 'Project not found or not updated' });
486
+ // A built-in disconnected keeps its id, to come back as it was (services/projectIntegration).
487
+ const builtin = [dbConn, storageConn].find(c => c?.source === 'builtin');
488
+ if (builtin)
489
+ await (0, projectResources_1.rememberDisconnectedBuiltin)(projectId, dbConn ? 'db' : 'storage', builtin);
490
+ const gone = dbConn ?? storageConn ?? integrationConn;
491
+ await (0, workflowResources_1.removeFromWorkflow)(projectId, dbConn ? 'db' : storageConn ? 'storage' : 'service', gone.id);
492
+ const updated = await Project.findOne({ _id: projectId });
488
493
  res.status(200).json({ message: 'Integration removed', project: (0, sanitize_1.sanitizeProject)(updated) });
489
494
  }
490
495
  catch (err) {
@@ -0,0 +1,8 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig } from '../../types';
3
+ export declare function projectResourcesRouter(_config: StreamByConfig): Router;
4
+ /** A built-in disconnected: its id kept, to connect it again as it was (services/projectIntegration). */
5
+ export declare function rememberDisconnectedBuiltin(projectId: string, kind: 'db' | 'storage', conn: {
6
+ id: string;
7
+ integrationId?: string;
8
+ }): Promise<void>;