streamby-core 0.42.0 → 0.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +92 -4
- package/dist/adapters/database/connectionManager.js +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -1
- package/dist/mcp/tools.js +25 -11
- package/dist/mcp/workflowNodes.js +9 -3
- package/dist/middleware/createRouter.js +2 -0
- package/dist/middleware/routes/connection.js +5 -2
- package/dist/middleware/routes/dbConnection.js +29 -5
- package/dist/middleware/routes/document.js +11 -6
- package/dist/middleware/routes/export.d.ts +28 -0
- package/dist/middleware/routes/export.js +8 -4
- package/dist/middleware/routes/member.js +24 -5
- package/dist/middleware/routes/pipeline.js +5 -2
- package/dist/middleware/routes/project.js +14 -12
- package/dist/middleware/routes/projectResources.d.ts +8 -0
- package/dist/middleware/routes/projectResources.js +51 -0
- package/dist/middleware/routes/rights.js +72 -52
- package/dist/middleware/routes/storage.js +35 -2
- package/dist/middleware/routes/storageConnection.js +38 -15
- package/dist/middleware/routes/userIntegration.js +42 -1
- package/dist/middleware/routes/workflow.js +120 -37
- package/dist/models/Model.js +13 -21
- package/dist/services/apiTokenPermissions.js +2 -0
- package/dist/services/creditsSheets.d.ts +39 -0
- package/dist/services/creditsSheets.js +129 -0
- package/dist/services/dependents.d.ts +11 -0
- package/dist/services/dependents.js +81 -0
- package/dist/services/production.d.ts +1 -1
- package/dist/services/production.js +2 -2
- package/dist/services/projectIntegration.d.ts +14 -0
- package/dist/services/projectIntegration.js +32 -0
- package/dist/services/rights.d.ts +44 -0
- package/dist/services/rights.js +123 -0
- package/dist/services/workflowResources.d.ts +90 -0
- package/dist/services/workflowResources.js +288 -0
- package/dist/types/index.d.ts +6 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.43.1
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Inviting a member to a SQL project, or asking to join one, answered 500**: both write who
|
|
8
|
+
invited the member (`invitedBy`), a column `streamby.project_members` didn't have. It's added
|
|
9
|
+
at startup to existing tables, and is in the table made new.
|
|
10
|
+
- **Accepting or rejecting an invitation to a SQL project failed too**: they changed the project's
|
|
11
|
+
`members` as a MongoDB project has them, a field a SQL project doesn't have (its members are
|
|
12
|
+
rows of `project_members`); they change the member's row now. A SQL project's members are read
|
|
13
|
+
with `invitedBy`, so whoever invited them is told the answer.
|
|
14
|
+
|
|
15
|
+
## 0.43.0
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **License metadata of assets**, for crediting the ones taken from others: `sourceUrl`,
|
|
20
|
+
`creatorName`, `creatorUrl`, `licenseType` and `comments`, all optional, in `asset_rights`.
|
|
21
|
+
The license is one of a closed list (`LICENSE_TYPES`: CC BY, CC BY-SA, CC BY-ND, CC BY-NC,
|
|
22
|
+
CC BY-NC-SA, CC BY-NC-ND, CC0, Free Standard, Standard, Editorial), checked by the server; the
|
|
23
|
+
links are http(s) URLs. It can be given with an upload (query params of `GET .../upload-url`;
|
|
24
|
+
an API token needs `rights:write` too). An `assetRightsEvent` to the project's active members
|
|
25
|
+
on each change, and when an asset is deleted.
|
|
26
|
+
- **Credits sheets**: project documents of a new kind, `credits`, that a workflow's Credits sheet
|
|
27
|
+
node makes (`CREDITS_SHEET_NODE`). One keeps only its sources (assets and Storage folders picked,
|
|
28
|
+
and the Ingest nodes connected to its node); its rows are made when it's read, from each asset's
|
|
29
|
+
metadata, with its status: complete when it has an asset and each has its creator, source and
|
|
30
|
+
license. Routes `GET /projects/:projectId/credits-sheets` and `.../credits-sheets/:docId`
|
|
31
|
+
(`rights:read`). Types `CreditsSheet`, `SheetRow`, `SheetSources` and `SheetStatus`.
|
|
32
|
+
- MCP: `get_credits_sheet`; `request_asset_upload` takes the license metadata.
|
|
33
|
+
- **The workflow defines the project.** Each resource is a node of it, bound by the resource's
|
|
34
|
+
id in its data. A resource added or removed from its section (`POST`/`DELETE` of a connection,
|
|
35
|
+
an API, an export, a pipeline, a project integration) adds or removes its node; saving the
|
|
36
|
+
workflow connects what its nodes were set to (`"__connect__:<integrationId>"`), makes the APIs
|
|
37
|
+
drafted on them (`data.connectionDraft`) and removes the resources whose nodes are gone,
|
|
38
|
+
answering `{ data, project, errors }`. A save needs `baseUpdatedAt` (a 409 over a newer version)
|
|
39
|
+
and a `nodeSchema` with `nodes` and `edges` lists (a 400 otherwise); an app connected with OAuth
|
|
40
|
+
can't remove resources through it (a 403 listing them). Pipelines and exports drafted on nodes
|
|
41
|
+
are made on save too. A node with a configuration of its own (a data source's table, an
|
|
42
|
+
Ingest's file) consumes its resource and is never merged into its node.
|
|
43
|
+
A workflow made before this is brought to it once, when it's read (`resourceNodesAt`).
|
|
44
|
+
- **What depends on a resource**, asked before removing it: `GET /projects/:id/dependents/:kind/:resourceId`
|
|
45
|
+
(connections, exports, pipelines): the workflow's, exports' and pipelines' nodes that use it,
|
|
46
|
+
and the files, folders, deliverables and shots kept for it. `GET /user/integrations/:id/dependents`:
|
|
47
|
+
the project connections that use an account's integration.
|
|
48
|
+
- **A change to a project's resources answers its list as it is then**, for the client to show
|
|
49
|
+
without asking again: connecting or disconnecting a database (`dbConnections`), a storage
|
|
50
|
+
(`storageConnections`, with built-ins' `available`) or an API (`connections`), deleting an
|
|
51
|
+
export (`exports`) or a pipeline (`pipelines`); `POST`/`DELETE /projects/:id/integrations`
|
|
52
|
+
answer the `project`; deleting an account's integration, each project it was disconnected
|
|
53
|
+
from (`affected`).
|
|
54
|
+
- **A built-in connected again keeps the id it had** (`disconnectedBuiltins` on the project; a
|
|
55
|
+
new `disconnectedBuiltins` column in a PostgreSQL projects table, added at startup), so its
|
|
56
|
+
files and records are its own again.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **`asset_rights` holds the license metadata only.** `rightsHolder`, `licenseUrl`, `territory`,
|
|
61
|
+
`expiresAt`, `usageRestrictions` and `notes` are gone: not read, and dropped from a record when
|
|
62
|
+
it's next saved; a `licenseType` off the list reads as none. `set_asset_rights` and
|
|
63
|
+
`get_asset_rights` take and give the new fields.
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- Deleting an asset or a project deletes its license metadata (it was left behind).
|
|
68
|
+
- A built-in database or storage can be disconnected from a project (it was refused with a 403,
|
|
69
|
+
though the backfill already left a disconnected one alone): what the project keeps in it stays.
|
|
70
|
+
- Deleting an account's integration disconnects the project connections that use it (they were
|
|
71
|
+
left pointing at nothing).
|
|
72
|
+
- `POST .../connections/db` and `.../connections/storage` with an `integrationId` connect a
|
|
73
|
+
built-in too (they refused it as an unknown integration, and checked the pool's `sql`/`nosql`
|
|
74
|
+
as the type): with an integration, its name and type are the integration's.
|
|
75
|
+
|
|
3
76
|
## 0.42.0
|
|
4
77
|
|
|
5
78
|
### Added
|
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ hash is stored, with a short prefix to tell it apart.
|
|
|
132
132
|
| Production | `production:read`, `production:write`, `production:delete` | Sequences, shots and tasks. |
|
|
133
133
|
| Workflow | `workflow:read`, `workflow:write` | The workflow and its pipelines. |
|
|
134
134
|
| Exports | `exports:read`, `exports:write` | The project's exports. |
|
|
135
|
-
| Rights | `rights:read`, `rights:write` | Assets'
|
|
135
|
+
| Rights | `rights:read`, `rights:write` | Assets' license metadata, and reading credits sheets. |
|
|
136
136
|
| Jobs | `jobs:read`, `jobs:run` | Following jobs; starting them (transcoding, rendering, AI, publishing a deliverable). |
|
|
137
137
|
| Documents | `docs:read`, `docs:write`, `docs:publish`, `docs:delete` | The project's documents: reading them and their history; creating, editing and moving them; publishing them (the version its exports deliver, and whether they deliver it); deleting them. |
|
|
138
138
|
| Account | `projects:create` | Creating projects. A user's token only; the token reaches what it creates. |
|
|
@@ -285,7 +285,7 @@ A token sees only the tools whose permissions it has (an app: those its scopes g
|
|
|
285
285
|
| `list_assets` (by category, optionally in one storage connection, and filtered by name), `get_asset_metadata` | `assets:read` |
|
|
286
286
|
| `update_asset_metadata` | `assets:write` |
|
|
287
287
|
| `delete_asset` | `assets:delete` |
|
|
288
|
-
| `get_asset_rights` / `set_asset_rights` | `rights:read` / `rights:write` |
|
|
288
|
+
| `get_asset_rights`, `get_credits_sheet` / `set_asset_rights` | `rights:read` / `rights:write` |
|
|
289
289
|
| `get_workflow`, `list_pipelines`, `get_pipeline` | `workflow:read` |
|
|
290
290
|
| `create_pipeline`, `update_pipeline` | `workflow:write` |
|
|
291
291
|
| `list_exports` | `project:read` and `exports:read` |
|
|
@@ -298,7 +298,7 @@ A token sees only the tools whose permissions it has (an app: those its scopes g
|
|
|
298
298
|
| `create_sequence`, `create_shot`, `create_task` | `production:write` and `workflow:write` |
|
|
299
299
|
| `delete_sequence`, `delete_shot`, `delete_task` | `production:delete` |
|
|
300
300
|
| `list_asset_versions`, `get_asset_download_url`, `list_deliverables` | `assets:read` |
|
|
301
|
-
| `request_asset_upload
|
|
301
|
+
| `request_asset_upload` (with license metadata, also `rights:write`), `mark_deliverable`, `remove_deliverable` | `assets:write` |
|
|
302
302
|
| `list_documents`, `get_document`, `list_document_versions` | `docs:read` |
|
|
303
303
|
| `create_document`, `update_document`, `update_document_section` | `docs:write` |
|
|
304
304
|
|
|
@@ -476,7 +476,7 @@ or collections), each one in a single project, in folders.
|
|
|
476
476
|
| Route | Permission |
|
|
477
477
|
|---|---|
|
|
478
478
|
| `GET /projects/:projectId/documents`: the tree, without contents | `docs:read` |
|
|
479
|
-
| `POST /projects/:projectId/documents` `{ title, content?, kind?: 'document' \| 'folder', parentId?, position? }` | `docs:write` |
|
|
479
|
+
| `POST /projects/:projectId/documents` `{ title, content?, kind?: 'document' \| 'folder' \| 'credits', parentId?, position? }` | `docs:write` |
|
|
480
480
|
| `GET /projects/:projectId/documents/:docId` | `docs:read` |
|
|
481
481
|
| `PATCH /projects/:projectId/documents/:docId` `{ baseVersion, title?, content? }` (a new version) and/or `{ parentId?, position? }` | `docs:write` |
|
|
482
482
|
| `PATCH /projects/:projectId/documents/:docId` `{ exportable }` | `docs:write` and `docs:publish` |
|
|
@@ -493,6 +493,44 @@ In MongoDB, a save writes the document and its version in a transaction when the
|
|
|
493
493
|
|
|
494
494
|
---
|
|
495
495
|
|
|
496
|
+
## ©️ License metadata and credits sheets
|
|
497
|
+
|
|
498
|
+
Each asset can carry its **license metadata**, for all its versions: where it was taken from
|
|
499
|
+
(`sourceUrl`), who made it (`creatorName`, `creatorUrl`), its license (`licenseType`) and
|
|
500
|
+
`comments`. All are optional; the links are http(s) URLs, and the license is one of
|
|
501
|
+
`CC BY`, `CC BY-SA`, `CC BY-ND`, `CC BY-NC`, `CC BY-NC-SA`, `CC BY-NC-ND`, `CC0`, `Free Standard`,
|
|
502
|
+
`Standard` and `Editorial` (`LICENSE_TYPES`, exported). It's kept in the `asset_rights`
|
|
503
|
+
collection, the only place it lives, and goes with its asset or its project when they're deleted.
|
|
504
|
+
It can be given with the upload: `licenseType`, `sourceUrl`, `creatorName`, `creatorUrl` and
|
|
505
|
+
`comments` as query params of `GET .../upload-url` (an API token needs `rights:write` too).
|
|
506
|
+
|
|
507
|
+
A **credits sheet** is a project document of kind `credits`, made by a Credits sheet node of the
|
|
508
|
+
workflow (its `data.documentId`). It keeps only what feeds it, as its content:
|
|
509
|
+
`{ "assetIds": [...], "folderIds": [...] }` (assets and Storage folders picked, with everything
|
|
510
|
+
inside them); the Ingest nodes connected to its node feed it too, with the asset each imports
|
|
511
|
+
(`data.fileId`). Its rows are made each time it's read, from each asset's metadata, so two sheets
|
|
512
|
+
with the same asset show the same, and editing a row is setting its asset's metadata. Deleting
|
|
513
|
+
the node leaves the document; an asset deleted leaves the sheet.
|
|
514
|
+
|
|
515
|
+
A sheet is **complete** when it has an asset and each one has its creator, source and license;
|
|
516
|
+
**incomplete** otherwise, each row listing what it lacks (`missing`).
|
|
517
|
+
|
|
518
|
+
- **In real time**, an `assetRightsEvent` reaches the project's active members on each change:
|
|
519
|
+
`{ projectId, action: 'saved' | 'deleted', assetIds, rights? }` (`deleted`: the asset is gone).
|
|
520
|
+
|
|
521
|
+
| Route | Permission |
|
|
522
|
+
|---|---|
|
|
523
|
+
| `GET /projects/:projectId/assets/:assetId/rights` | `rights:read` |
|
|
524
|
+
| `PUT /projects/:projectId/assets/:assetId/rights` `{ sourceUrl?, creatorName?, creatorUrl?, licenseType?, comments? }` (the fields given; empty or `null` clears one) | `rights:write` |
|
|
525
|
+
| `GET /projects/:projectId/credits-sheets`: each with `{ id, title, status, rows, incomplete }` | `rights:read` |
|
|
526
|
+
| `GET /projects/:projectId/credits-sheets/:docId`: `{ document, sources, rows, status }` | `rights:read` |
|
|
527
|
+
|
|
528
|
+
Each row: `{ assetId, name, category, sourceUrl, creatorName, creatorUrl, licenseType, comments, missing, from }`,
|
|
529
|
+
`from` saying how it got there (`asset`, `folder`, `ingest`). Editors and admins set the metadata
|
|
530
|
+
(as they edit the asset); every member reads it.
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
496
534
|
## ⚙️ Processing workers
|
|
497
535
|
|
|
498
536
|
StreamBy doesn't process assets itself (optimizing a glTF, making LODs, transcoding...): it
|
|
@@ -598,6 +636,56 @@ A job its worker doesn't complete before its deadline is sent again, up to
|
|
|
598
636
|
|
|
599
637
|
---
|
|
600
638
|
|
|
639
|
+
## 🧭 The workflow defines the project
|
|
640
|
+
|
|
641
|
+
Each of a project's resources (a database, an API, a storage, a service, an export, a pipeline)
|
|
642
|
+
is a node of its workflow, bound to it by the resource's id in its data (`connectionId`,
|
|
643
|
+
`storageConnectionId`, `integrationConnectionId`, `exportId`, `pipelineId`). One node per
|
|
644
|
+
resource; a data source reading one of a database's tables is a node of its own.
|
|
645
|
+
|
|
646
|
+
- **From a section** (or an agent): adding a resource adds its node to the saved workflow, and
|
|
647
|
+
removing it removes its nodes.
|
|
648
|
+
- **Saving the workflow** (`PATCH /projects/:id/workflow` `{ nodeSchema, baseUpdatedAt }`) makes
|
|
649
|
+
the project what it defines, then answers `{ data, project, errors }`: the workflow, the
|
|
650
|
+
project's lists as they are then, and what couldn't be done. A node set to an integration the
|
|
651
|
+
project doesn't have (`"__connect__:<integrationId>"`) connects it; an API, a pipeline or an
|
|
652
|
+
export drafted on a node (`data.connectionDraft`, `data.pipelineDraft`, `data.exportDraft`) is
|
|
653
|
+
made; a resource whose nodes are all gone is removed.
|
|
654
|
+
- `baseUpdatedAt` (the workflow's `updatedAt` as it was read) is required with a `nodeSchema`,
|
|
655
|
+
and `nodeSchema` must be `{ nodes: [...], edges: [...] }`: a 400 otherwise. Saved over a newer
|
|
656
|
+
version, it's a 409 with the workflow as it is now.
|
|
657
|
+
- An app connected with OAuth removes nothing through it, as it deletes nothing: a save that
|
|
658
|
+
would remove resources is a 403 listing them (`removing`).
|
|
659
|
+
- A node holding a configuration of its own beside its resource's id (a data source reading one
|
|
660
|
+
table, an Ingest importing one file) consumes the resource: never merged into its node.
|
|
661
|
+
- **Reading it** (`GET`) never rewrites it, except once, for a workflow made before this: the
|
|
662
|
+
nodes once drawn from the project's lists are bound by their ids, a resource without a node gets
|
|
663
|
+
one, and two nodes of the same resource are merged.
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## 🔌 Removing a project's resources
|
|
668
|
+
|
|
669
|
+
Before a connection (database, API, storage, service), an export or a pipeline is removed, the
|
|
670
|
+
UI asks what depends on it: `GET /projects/:id/dependents/:kind/:resourceId` (`kind`: `db`,
|
|
671
|
+
`api`, `storage`, `service`, `export`, `pipeline`; `project:read`) answers
|
|
672
|
+
`{ dependents: [{ type, name, id?, count? }] }`: the nodes that use it in the workflow, the
|
|
673
|
+
exports and the pipelines (`workflow`, `export`, `pipeline`: they stop working without it), and
|
|
674
|
+
what's kept for it (`files` and `folders` of a storage, `deliverables` and `shots` of an
|
|
675
|
+
export). Removing it changes none of them.
|
|
676
|
+
|
|
677
|
+
**Built-ins** (the host's database and storage) are disconnected like any connection, and what
|
|
678
|
+
the project keeps in them stays. They're connected again as any integration is, by its id in
|
|
679
|
+
the pool (`GET /user/integrations`): `POST /projects/:id/integrations` `{ integrationId }`, or
|
|
680
|
+
`POST .../connections/db` and `.../connections/storage` with `integrationId` (its name and type
|
|
681
|
+
come from the integration). One connected again keeps the id it had (`disconnectedBuiltins`
|
|
682
|
+
on the project), so its files and records are its own again.
|
|
683
|
+
|
|
684
|
+
Deleting an account's integration disconnects the project connections that use it;
|
|
685
|
+
`GET /user/integrations/:id/dependents` lists them first.
|
|
686
|
+
|
|
687
|
+
---
|
|
688
|
+
|
|
601
689
|
## 👥 Project roles
|
|
602
690
|
|
|
603
691
|
What a user can do in a project depends on their role in that project, as an active member
|
|
@@ -101,6 +101,7 @@ const createTables = async (pool) => {
|
|
|
101
101
|
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS workflow JSONB;
|
|
102
102
|
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS pipelines JSONB DEFAULT '[]';
|
|
103
103
|
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "builtinBackfilledAt" TIMESTAMP WITH TIME ZONE;
|
|
104
|
+
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "disconnectedBuiltins" JSONB DEFAULT '[]';
|
|
104
105
|
-- Written when a project is made (routes/project) and by its connection routes.
|
|
105
106
|
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS public BOOLEAN DEFAULT TRUE;
|
|
106
107
|
ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS category TEXT;
|
|
@@ -122,6 +123,7 @@ const createTables = async (pool) => {
|
|
|
122
123
|
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
123
124
|
"archivedBy" VARCHAR(255),
|
|
124
125
|
"archivedAt" TIMESTAMP WITH TIME ZONE,
|
|
126
|
+
"invitedBy" VARCHAR(255),
|
|
125
127
|
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
126
128
|
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
127
129
|
UNIQUE ("projectId", "userId")
|
|
@@ -129,6 +131,8 @@ const createTables = async (pool) => {
|
|
|
129
131
|
`);
|
|
130
132
|
await pool.query(`
|
|
131
133
|
ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active';
|
|
134
|
+
-- Who invited a pending member (or the member, asking to join): told when it's answered.
|
|
135
|
+
ALTER TABLE streamby.project_members ADD COLUMN IF NOT EXISTS "invitedBy" VARCHAR(255);
|
|
132
136
|
`);
|
|
133
137
|
console.log('✅ "project_members" table ensured to exist.');
|
|
134
138
|
// Migrate projectId FK column from UUID to TEXT if table predates this schema version
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export { createNotification } from './services/notification';
|
|
|
4
4
|
export type { UserPlan, UserSubscription, Auth, ApiTokenScope, ApiTokenKind, ApiTokenPermission, ProjectPermission, AccountPermission, OAuthClientInfo, OAuthResourceConfig, ProjectDocument, DocumentVersion, DocumentAuthor, DocumentKind } from './types';
|
|
5
5
|
export { resolveApiToken, API_TOKEN_PREFIX } from './services/apiTokens';
|
|
6
6
|
export { PROJECT_PERMISSIONS, ACCOUNT_PERMISSIONS } from './services/apiTokenPermissions';
|
|
7
|
+
export { LICENSE_TYPES } from './services/rights';
|
|
8
|
+
export type { AssetRights, LicenseType } from './services/rights';
|
|
9
|
+
export { CREDITS_SHEET_NODE } from './services/creditsSheets';
|
|
10
|
+
export type { CreditsSheet, SheetRow, SheetSources, SheetStatus } from './services/creditsSheets';
|
|
7
11
|
export type { ResolvedApiToken } from './services/apiTokens';
|
|
8
12
|
export { createOAuthTokenVerifier, looksLikeOAuthAccessToken, protectedResourceMetadata, defaultResourceMetadataUrl, permissionsForOAuthScopes, OAUTH_SCOPES, OAUTH_SCOPE_PERMISSIONS, } from './services/oauthTokens';
|
|
9
13
|
export type { VerifiedOAuthToken, OAuthTokenVerifierOptions, Jwks, Jwk, OAuthScope } from './services/oauthTokens';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OAUTH_SCOPE_PERMISSIONS = exports.OAUTH_SCOPES = exports.permissionsForOAuthScopes = exports.defaultResourceMetadataUrl = exports.protectedResourceMetadata = exports.looksLikeOAuthAccessToken = exports.createOAuthTokenVerifier = exports.ACCOUNT_PERMISSIONS = exports.PROJECT_PERMISSIONS = exports.API_TOKEN_PREFIX = exports.resolveApiToken = exports.createNotification = exports.emitToUser = exports.createStreamByRouter = void 0;
|
|
3
|
+
exports.OAUTH_SCOPE_PERMISSIONS = exports.OAUTH_SCOPES = exports.permissionsForOAuthScopes = exports.defaultResourceMetadataUrl = exports.protectedResourceMetadata = exports.looksLikeOAuthAccessToken = exports.createOAuthTokenVerifier = exports.CREDITS_SHEET_NODE = exports.LICENSE_TYPES = exports.ACCOUNT_PERMISSIONS = exports.PROJECT_PERMISSIONS = exports.API_TOKEN_PREFIX = exports.resolveApiToken = exports.createNotification = exports.emitToUser = exports.createStreamByRouter = void 0;
|
|
4
4
|
var createRouter_1 = require("./middleware/createRouter");
|
|
5
5
|
Object.defineProperty(exports, "createStreamByRouter", { enumerable: true, get: function () { return createRouter_1.createStreamByRouter; } });
|
|
6
6
|
var wsHub_1 = require("./services/wsHub");
|
|
@@ -13,6 +13,10 @@ Object.defineProperty(exports, "API_TOKEN_PREFIX", { enumerable: true, get: func
|
|
|
13
13
|
var apiTokenPermissions_1 = require("./services/apiTokenPermissions");
|
|
14
14
|
Object.defineProperty(exports, "PROJECT_PERMISSIONS", { enumerable: true, get: function () { return apiTokenPermissions_1.PROJECT_PERMISSIONS; } });
|
|
15
15
|
Object.defineProperty(exports, "ACCOUNT_PERMISSIONS", { enumerable: true, get: function () { return apiTokenPermissions_1.ACCOUNT_PERMISSIONS; } });
|
|
16
|
+
var rights_1 = require("./services/rights");
|
|
17
|
+
Object.defineProperty(exports, "LICENSE_TYPES", { enumerable: true, get: function () { return rights_1.LICENSE_TYPES; } });
|
|
18
|
+
var creditsSheets_1 = require("./services/creditsSheets");
|
|
19
|
+
Object.defineProperty(exports, "CREDITS_SHEET_NODE", { enumerable: true, get: function () { return creditsSheets_1.CREDITS_SHEET_NODE; } });
|
|
16
20
|
var oauthTokens_1 = require("./services/oauthTokens");
|
|
17
21
|
Object.defineProperty(exports, "createOAuthTokenVerifier", { enumerable: true, get: function () { return oauthTokens_1.createOAuthTokenVerifier; } });
|
|
18
22
|
Object.defineProperty(exports, "looksLikeOAuthAccessToken", { enumerable: true, get: function () { return oauthTokens_1.looksLikeOAuthAccessToken; } });
|
package/dist/mcp/tools.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TOOLS = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
+
const rights_1 = require("../services/rights");
|
|
5
6
|
const tool = (definition) => definition;
|
|
6
7
|
const id = (what) => zod_1.z.string().min(1).describe(`The ${what}'s id`);
|
|
7
8
|
const projectId = id('project');
|
|
@@ -25,14 +26,13 @@ const taskFields = {
|
|
|
25
26
|
dueDate: zod_1.z.string().optional().describe('An ISO 8601 date'),
|
|
26
27
|
notes: zod_1.z.string().optional(),
|
|
27
28
|
};
|
|
29
|
+
// An asset's license metadata: what crediting it takes. Each is optional; an empty one clears it.
|
|
28
30
|
const rightsFields = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
usageRestrictions: zod_1.z.string().optional(),
|
|
35
|
-
notes: zod_1.z.string().optional(),
|
|
31
|
+
sourceUrl: zod_1.z.string().optional().describe('Where the asset was taken from (an http(s) URL)'),
|
|
32
|
+
creatorName: zod_1.z.string().optional().describe('Who made it'),
|
|
33
|
+
creatorUrl: zod_1.z.string().optional().describe("The creator's page (an http(s) URL)"),
|
|
34
|
+
licenseType: zod_1.z.enum(rights_1.LICENSE_TYPES).or(zod_1.z.literal('')).optional().describe('Its license'),
|
|
35
|
+
comments: zod_1.z.string().optional(),
|
|
36
36
|
};
|
|
37
37
|
// Only the fields given (an unset optional field isn't sent as undefined).
|
|
38
38
|
const defined = (fields) => Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
@@ -86,7 +86,7 @@ exports.TOOLS = [
|
|
|
86
86
|
}),
|
|
87
87
|
tool({
|
|
88
88
|
name: 'get_asset_rights', title: "Get an asset's rights", permissions: ['rights:read'],
|
|
89
|
-
description: "An asset's
|
|
89
|
+
description: "An asset's license metadata: where it was taken from, its creator (name and link), its license and comments; null if it has none.",
|
|
90
90
|
input: { projectId, assetId: id('asset') },
|
|
91
91
|
projectOf: byProject,
|
|
92
92
|
call: ({ projectId, assetId }) => ({ method: 'GET', path: `/projects/${enc(projectId)}/assets/${enc(assetId)}/rights` }),
|
|
@@ -164,7 +164,7 @@ exports.TOOLS = [
|
|
|
164
164
|
}),
|
|
165
165
|
tool({
|
|
166
166
|
name: 'set_asset_rights', title: "Set an asset's rights", permissions: ['rights:write'], write: true,
|
|
167
|
-
description:
|
|
167
|
+
description: `Sets an asset's license metadata (the fields given; an empty one clears it): where it was taken from, its creator (name and link), its license (one of: ${rights_1.LICENSE_TYPES.join(', ')}) and comments. It shows in the file's Rights tab, in Storage, and in every credits sheet the asset is in.`,
|
|
168
168
|
input: { projectId, assetId: id('asset'), ...rightsFields },
|
|
169
169
|
projectOf: byProject,
|
|
170
170
|
call: ({ projectId, assetId, ...rights }) => ({ method: 'PUT', path: `/projects/${enc(projectId)}/assets/${enc(assetId)}/rights`, body: defined(rights) }),
|
|
@@ -245,6 +245,7 @@ exports.TOOLS = [
|
|
|
245
245
|
"It doesn't upload anything: the file is sent by the tool that has it (for example Blender's MCP, running Python), with an HTTP PUT of its bytes to `upload.url` and the `upload.headers` given, before `expiresAt`.",
|
|
246
246
|
"The URL works for that file only. A new version keeps the asset's URL (it replaces what it serves); the earlier ones are archived and can still be downloaded.",
|
|
247
247
|
"Categories: images, audios, videos, 3d-models (.glb, .gltf, .obj, .fbx, .stl, .ply); contentType must match it.",
|
|
248
|
+
"For a new asset taken from someone else (a CC model, a stock image), give its license metadata too, so it's credited (it needs the rights:write permission); for a new version, set_asset_rights changes it.",
|
|
248
249
|
].join(' '),
|
|
249
250
|
input: {
|
|
250
251
|
projectId,
|
|
@@ -253,10 +254,14 @@ exports.TOOLS = [
|
|
|
253
254
|
contentType: zod_1.z.string().min(1).describe('Its MIME type (e.g. model/gltf-binary, image/png, video/mp4)'),
|
|
254
255
|
assetId: zod_1.z.string().optional().describe('An existing asset to upload a new version of; a new asset if not given'),
|
|
255
256
|
connectionId: zod_1.z.string().optional().describe("A storage connection to store it in; the project's built-in storage if not given"),
|
|
257
|
+
...rightsFields,
|
|
256
258
|
},
|
|
257
259
|
projectOf: byProject,
|
|
258
|
-
call: ({ projectId, category, fileName, contentType, assetId, connectionId }) => {
|
|
259
|
-
const query = new URLSearchParams({
|
|
260
|
+
call: ({ projectId, category, fileName, contentType, assetId, connectionId, ...rights }) => {
|
|
261
|
+
const query = new URLSearchParams({
|
|
262
|
+
fileName, contentType, ...(connectionId && !assetId ? { category } : {}),
|
|
263
|
+
...(assetId ? {} : defined(rights)),
|
|
264
|
+
});
|
|
260
265
|
const files = connectionId ? `/projects/${enc(projectId)}/connections/storage/${enc(connectionId)}` : `/projects/${enc(projectId)}/storage`;
|
|
261
266
|
const path = assetId
|
|
262
267
|
? `${files}/files/${enc(assetId)}/replace-url`
|
|
@@ -358,6 +363,15 @@ exports.TOOLS = [
|
|
|
358
363
|
call: ({ projectId, jobId }) => ({ method: 'POST', path: `/projects/${enc(projectId)}/jobs/${enc(jobId)}/retry` }),
|
|
359
364
|
pick: (data) => data?.job,
|
|
360
365
|
}),
|
|
366
|
+
// ── Credits sheets ──
|
|
367
|
+
tool({
|
|
368
|
+
name: 'get_credits_sheet', title: 'Read a credits sheet', permissions: ['rights:read'],
|
|
369
|
+
description: "A credits sheet (a document of kind 'credits', made by a Credits sheet node of the workflow; list_documents lists them): a row per asset fed to it (picked on it, in a Storage folder picked, or imported by an Ingest node connected to it) with that asset's license metadata as it is now, and its status: 'complete' when it has an asset and each has its creator, source and license; 'incomplete' otherwise, each row listing what it lacks (`missing`). To fill a row in, set_asset_rights on its asset.",
|
|
370
|
+
input: { projectId, documentId: id('credits sheet') },
|
|
371
|
+
projectOf: byProject,
|
|
372
|
+
call: ({ projectId, documentId }) => ({ method: 'GET', path: `/projects/${enc(projectId)}/credits-sheets/${enc(documentId)}` }),
|
|
373
|
+
pick: (data) => data && { id: data.document?.id, title: data.document?.title, status: data.status, rows: data.rows },
|
|
374
|
+
}),
|
|
361
375
|
// ── Documents ──
|
|
362
376
|
tool({
|
|
363
377
|
name: 'list_documents', title: 'List documents', permissions: ['docs:read'],
|
|
@@ -9,6 +9,7 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
9
9
|
const manager_1 = require("../models/manager");
|
|
10
10
|
const auth_1 = require("../utils/auth");
|
|
11
11
|
const auth_2 = require("../services/auth");
|
|
12
|
+
const workflowResources_1 = require("../services/workflowResources");
|
|
12
13
|
// The node types that make a workflow's production section (as the UI groups them).
|
|
13
14
|
const PRODUCTION_NODE_TYPES = new Set(['shotNode', 'assemblyNode', 'masterNode']);
|
|
14
15
|
/** A Shot node, as the UI makes one dropped on the canvas without a position. */
|
|
@@ -32,14 +33,19 @@ async function ensureWorkflowNode(requirement, projectId, auth, api) {
|
|
|
32
33
|
const project = await (0, manager_1.getModel)('projects').findOne({ _id: projectId });
|
|
33
34
|
if (!project || !(0, auth_1.activeMemberOf)(project, auth.userId))
|
|
34
35
|
return null;
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
// The workflow as it's answered (brought to define the project, if it wasn't yet), and saved
|
|
37
|
+
// on that version: what changed since isn't undone.
|
|
38
|
+
const workflow = await (0, workflowResources_1.readWorkflow)(project);
|
|
39
|
+
const nodes = Array.isArray(workflow?.nodeSchema?.nodes) ? workflow.nodeSchema.nodes : [];
|
|
37
40
|
if (nodes.some(node => PRODUCTION_NODE_TYPES.has(node?.type)))
|
|
38
41
|
return null;
|
|
39
42
|
const answer = await api({
|
|
40
43
|
method: 'PATCH',
|
|
41
44
|
path: `/projects/${encodeURIComponent(projectId)}/workflow`,
|
|
42
|
-
body: {
|
|
45
|
+
body: {
|
|
46
|
+
nodeSchema: { nodes: [...nodes, (0, exports.newShotNode)()], edges: Array.isArray(workflow?.nodeSchema?.edges) ? workflow.nodeSchema.edges : [] },
|
|
47
|
+
baseUpdatedAt: new Date(workflow.updatedAt).toISOString(),
|
|
48
|
+
},
|
|
43
49
|
});
|
|
44
50
|
if (answer.status >= 200 && answer.status < 300)
|
|
45
51
|
return null;
|
|
@@ -45,6 +45,7 @@ const workerCallback_1 = require("./routes/workerCallback");
|
|
|
45
45
|
const workerJobs_1 = require("../services/workerJobs");
|
|
46
46
|
const deliverable_1 = require("./routes/deliverable");
|
|
47
47
|
const document_1 = require("./routes/document");
|
|
48
|
+
const projectResources_1 = require("./routes/projectResources");
|
|
48
49
|
const documents_1 = require("../services/documents");
|
|
49
50
|
function createStreamByRouter(config) {
|
|
50
51
|
const router = express_1.default.Router();
|
|
@@ -153,5 +154,6 @@ function createStreamByRouter(config) {
|
|
|
153
154
|
router.use((0, worker_1.workerRouter)({ ...config, adapter }));
|
|
154
155
|
router.use((0, deliverable_1.deliverableRouter)({ ...config, adapter }));
|
|
155
156
|
router.use((0, document_1.documentRouter)(config));
|
|
157
|
+
router.use((0, projectResources_1.projectResourcesRouter)(config));
|
|
156
158
|
return router;
|
|
157
159
|
}
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.connectionRouter = connectionRouter;
|
|
37
37
|
const express_1 = require("express");
|
|
38
|
+
const workflowResources_1 = require("../../services/workflowResources");
|
|
38
39
|
const manager_1 = require("../../models/manager");
|
|
39
40
|
const auth_1 = require("../../utils/auth");
|
|
40
41
|
const connection_1 = require("../../services/connection");
|
|
@@ -88,7 +89,8 @@ function connectionRouter(config) {
|
|
|
88
89
|
...(description !== undefined && { description }),
|
|
89
90
|
...(token !== undefined && { token }),
|
|
90
91
|
});
|
|
91
|
-
|
|
92
|
+
await (0, workflowResources_1.addToWorkflow)(projectId, 'api', connection);
|
|
93
|
+
return res.status(201).json({ data: (0, sanitize_1.sanitizeConnection)(connection), connections: (0, sanitize_1.sanitizeConnections)((await Project.findOne({ _id: projectId }))?.connections) });
|
|
92
94
|
}
|
|
93
95
|
catch (err) {
|
|
94
96
|
res.status(500).json({ message: 'Failed to add connection', details: err.message });
|
|
@@ -178,7 +180,8 @@ function connectionRouter(config) {
|
|
|
178
180
|
return res.status(404).json({ message: 'Connection not found' });
|
|
179
181
|
}
|
|
180
182
|
await (0, connection_1.deleteConnection)(config, projectId, connectionId);
|
|
181
|
-
|
|
183
|
+
await (0, workflowResources_1.removeFromWorkflow)(projectId, 'api', connectionId);
|
|
184
|
+
return res.status(200).json({ message: 'Connection deleted successfully.', connections: (0, sanitize_1.sanitizeConnections)((await Project.findOne({ _id: projectId }))?.connections) });
|
|
182
185
|
}
|
|
183
186
|
catch (err) {
|
|
184
187
|
res.status(500).json({ message: 'Failed to delete connection', details: err.message });
|
|
@@ -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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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 });
|
|
@@ -11,6 +11,7 @@ const auth_1 = require("../../utils/auth");
|
|
|
11
11
|
const wsHub_1 = require("../../services/wsHub");
|
|
12
12
|
const documents_1 = require("../../services/documents");
|
|
13
13
|
const auth_2 = require("../../services/auth");
|
|
14
|
+
const creditsSheets_1 = require("../../services/creditsSheets");
|
|
14
15
|
const MAX_TITLE = 200;
|
|
15
16
|
const MAX_CONTENT = 1000000;
|
|
16
17
|
// What happened to a project's documents, to whoever is in it now: a view of a document
|
|
@@ -30,7 +31,9 @@ function checkTitle(value) {
|
|
|
30
31
|
return { error: `A title has at most ${MAX_TITLE} characters` };
|
|
31
32
|
return value.trim();
|
|
32
33
|
}
|
|
33
|
-
function checkContent(value) {
|
|
34
|
+
function checkContent(value, kind = 'document') {
|
|
35
|
+
if (kind === 'credits')
|
|
36
|
+
return (0, creditsSheets_1.checkSheetContent)(value);
|
|
34
37
|
if (typeof value !== 'string')
|
|
35
38
|
return { error: 'content must be text (markdown)' };
|
|
36
39
|
if (value.length > MAX_CONTENT)
|
|
@@ -92,15 +95,15 @@ function documentRouter(_config) {
|
|
|
92
95
|
if (!found)
|
|
93
96
|
return;
|
|
94
97
|
const { project, auth, projectId } = found;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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';
|
|
98
101
|
const title = checkTitle(req.body?.title);
|
|
99
102
|
if (failed(title))
|
|
100
103
|
return res.status(400).json({ message: title.error });
|
|
101
104
|
if (kind === 'folder' && req.body?.content)
|
|
102
105
|
return res.status(400).json({ message: 'A folder has no content' });
|
|
103
|
-
const content = kind === 'folder' ? '' : checkContent(req.body?.content ?? '');
|
|
106
|
+
const content = kind === 'folder' ? '' : checkContent(req.body?.content ?? '', kind);
|
|
104
107
|
if (failed(content))
|
|
105
108
|
return res.status(400).json({ message: content.error });
|
|
106
109
|
const store = (0, documents_1.documentStore)();
|
|
@@ -168,7 +171,7 @@ function documentRouter(_config) {
|
|
|
168
171
|
return res.status(400).json({ message: title.error });
|
|
169
172
|
if (current.kind === 'folder' && body.content)
|
|
170
173
|
return res.status(400).json({ message: 'A folder has no content' });
|
|
171
|
-
const content = body.content === undefined ? current.content : checkContent(body.content);
|
|
174
|
+
const content = body.content === undefined ? current.content : checkContent(body.content, current.kind);
|
|
172
175
|
if (failed(content))
|
|
173
176
|
return res.status(400).json({ message: content.error });
|
|
174
177
|
text = { title, content };
|
|
@@ -240,6 +243,8 @@ function documentRouter(_config) {
|
|
|
240
243
|
return res.status(404).json({ message: 'Document not found' });
|
|
241
244
|
if (current.kind === 'folder')
|
|
242
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' });
|
|
243
248
|
// A section of another version than the one it was read on could be another text.
|
|
244
249
|
if (current.version !== baseVersion)
|
|
245
250
|
return conflict(res, current);
|
|
@@ -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;
|