@cyberismo/backend 0.0.21 → 0.0.22
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/dist/app.d.ts +5 -2
- package/dist/app.js +22 -9
- package/dist/app.js.map +1 -1
- package/dist/auth/index.d.ts +16 -0
- package/dist/auth/index.js +15 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/keycloak.d.ts +27 -0
- package/dist/auth/keycloak.js +81 -0
- package/dist/auth/keycloak.js.map +1 -0
- package/dist/auth/mock.d.ts +23 -0
- package/dist/auth/mock.js +28 -0
- package/dist/auth/mock.js.map +1 -0
- package/dist/auth/types.d.ts +16 -0
- package/dist/auth/types.js +14 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/domain/auth/index.d.ts +14 -0
- package/dist/domain/auth/index.js +30 -0
- package/dist/domain/auth/index.js.map +1 -0
- package/dist/domain/calculations/index.js +3 -1
- package/dist/domain/calculations/index.js.map +1 -1
- package/dist/domain/calculations/service.js +13 -11
- package/dist/domain/calculations/service.js.map +1 -1
- package/dist/domain/cardTypes/index.js +5 -3
- package/dist/domain/cardTypes/index.js.map +1 -1
- package/dist/domain/cardTypes/service.js +24 -72
- package/dist/domain/cardTypes/service.js.map +1 -1
- package/dist/domain/cards/index.js +19 -15
- package/dist/domain/cards/index.js.map +1 -1
- package/dist/domain/cards/lib.js +95 -93
- package/dist/domain/cards/lib.js.map +1 -1
- package/dist/domain/cards/service.d.ts +2 -1
- package/dist/domain/cards/service.js +60 -87
- package/dist/domain/cards/service.js.map +1 -1
- package/dist/domain/fieldTypes/index.js +4 -2
- package/dist/domain/fieldTypes/index.js.map +1 -1
- package/dist/domain/graphModels/index.js +3 -1
- package/dist/domain/graphModels/index.js.map +1 -1
- package/dist/domain/graphViews/index.js +3 -1
- package/dist/domain/graphViews/index.js.map +1 -1
- package/dist/domain/labels/index.js +4 -2
- package/dist/domain/labels/index.js.map +1 -1
- package/dist/domain/labels/service.d.ts +1 -1
- package/dist/domain/labels/service.js +2 -2
- package/dist/domain/labels/service.js.map +1 -1
- package/dist/domain/linkTypes/index.js +4 -2
- package/dist/domain/linkTypes/index.js.map +1 -1
- package/dist/domain/logicPrograms/index.js +3 -1
- package/dist/domain/logicPrograms/index.js.map +1 -1
- package/dist/domain/mcp/index.d.ts +15 -0
- package/dist/domain/mcp/index.js +127 -0
- package/dist/domain/mcp/index.js.map +1 -0
- package/dist/domain/project/index.js +7 -5
- package/dist/domain/project/index.js.map +1 -1
- package/dist/domain/project/service.js +18 -14
- package/dist/domain/project/service.js.map +1 -1
- package/dist/domain/reports/index.js +3 -1
- package/dist/domain/reports/index.js.map +1 -1
- package/dist/domain/resources/index.js +6 -4
- package/dist/domain/resources/index.js.map +1 -1
- package/dist/domain/resources/service.js +66 -64
- package/dist/domain/resources/service.js.map +1 -1
- package/dist/domain/templates/index.js +5 -3
- package/dist/domain/templates/index.js.map +1 -1
- package/dist/domain/tree/index.js +3 -1
- package/dist/domain/tree/index.js.map +1 -1
- package/dist/domain/workflows/index.js +3 -1
- package/dist/domain/workflows/index.js.map +1 -1
- package/dist/export.d.ts +6 -5
- package/dist/export.js +15 -11
- package/dist/export.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/main.js +29 -2
- package/dist/main.js.map +1 -1
- package/dist/middleware/auth.d.ts +40 -0
- package/dist/middleware/auth.js +68 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/commandManager.d.ts +2 -2
- package/dist/middleware/commandManager.js +9 -11
- package/dist/middleware/commandManager.js.map +1 -1
- package/dist/public/THIRD-PARTY.txt +37 -11
- package/dist/public/assets/index-B_lh6qtv.css +1 -0
- package/dist/public/assets/{index-Ca10XaMv.js → index-CEol8Bfi.js} +43823 -43030
- package/dist/public/config.json +1 -0
- package/dist/public/index.html +2 -2
- package/dist/types.d.ts +25 -0
- package/dist/types.js +13 -1
- package/dist/types.js.map +1 -1
- package/package.json +8 -5
- package/src/app.ts +34 -14
- package/src/auth/index.ts +17 -0
- package/src/auth/keycloak.ts +109 -0
- package/src/auth/mock.ts +38 -0
- package/src/auth/types.ts +18 -0
- package/src/domain/auth/index.ts +35 -0
- package/src/domain/calculations/index.ts +13 -6
- package/src/domain/calculations/service.ts +16 -14
- package/src/domain/cardTypes/index.ts +24 -16
- package/src/domain/cardTypes/service.ts +41 -95
- package/src/domain/cards/index.ts +62 -44
- package/src/domain/cards/lib.ts +105 -100
- package/src/domain/cards/service.ts +73 -89
- package/src/domain/fieldTypes/index.ts +23 -16
- package/src/domain/graphModels/index.ts +13 -6
- package/src/domain/graphViews/index.ts +13 -6
- package/src/domain/labels/index.ts +5 -2
- package/src/domain/labels/service.ts +2 -2
- package/src/domain/linkTypes/index.ts +14 -7
- package/src/domain/logicPrograms/index.ts +3 -0
- package/src/domain/mcp/index.ts +159 -0
- package/src/domain/project/index.ts +17 -8
- package/src/domain/project/service.ts +20 -16
- package/src/domain/reports/index.ts +13 -6
- package/src/domain/resources/index.ts +6 -1
- package/src/domain/resources/service.ts +102 -97
- package/src/domain/templates/index.ts +31 -19
- package/src/domain/tree/index.ts +3 -1
- package/src/domain/workflows/index.ts +13 -6
- package/src/export.ts +16 -13
- package/src/index.ts +10 -3
- package/src/main.ts +44 -2
- package/src/middleware/auth.ts +90 -0
- package/src/middleware/commandManager.ts +11 -14
- package/src/types.ts +27 -0
- package/dist/public/assets/index-CRSBseQM.css +0 -1
|
@@ -13,29 +13,32 @@
|
|
|
13
13
|
|
|
14
14
|
import Processor from '@asciidoctor/core';
|
|
15
15
|
import { type MetadataContent } from '@cyberismo/data-handler/interfaces/project-interfaces';
|
|
16
|
+
import type { attachmentPayload } from '@cyberismo/data-handler/interfaces/request-status-interfaces';
|
|
16
17
|
import { type CommandManager, evaluateMacros } from '@cyberismo/data-handler';
|
|
17
18
|
import { allCards } from './lib.js';
|
|
18
19
|
import type { TreeOptions } from '../../types.js';
|
|
19
20
|
|
|
20
21
|
export async function getProjectInfo(commands: CommandManager) {
|
|
21
|
-
|
|
22
|
+
return commands.consistent(async () => {
|
|
23
|
+
const projectResponse = await commands.showCmd.showProject();
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const workflowsResponse = await commands.showCmd.showWorkflowsWithDetails();
|
|
26
|
+
if (!workflowsResponse) {
|
|
27
|
+
throw new Error('No workflows found');
|
|
28
|
+
}
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const cardTypesResponse = await commands.showCmd.showCardTypesWithDetails();
|
|
31
|
+
if (!cardTypesResponse) {
|
|
32
|
+
throw new Error('No card types found');
|
|
33
|
+
}
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
return {
|
|
36
|
+
name: projectResponse.name,
|
|
37
|
+
prefix: projectResponse.prefix,
|
|
38
|
+
workflows: workflowsResponse,
|
|
39
|
+
cardTypes: cardTypesResponse,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export async function updateCard(
|
|
@@ -44,54 +47,31 @@ export async function updateCard(
|
|
|
44
47
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
48
|
body: any,
|
|
46
49
|
) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (body.state) {
|
|
50
|
-
try {
|
|
50
|
+
await commands.atomic(async () => {
|
|
51
|
+
if (body.state) {
|
|
51
52
|
await commands.transitionCmd.cardTransition(key, body.state);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
if (error instanceof Error) errors.push(error.message);
|
|
54
53
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (body.content != null) {
|
|
58
|
-
try {
|
|
54
|
+
if (body.content != null) {
|
|
59
55
|
await commands.editCmd.editCardContent(key, body.content);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
if (error instanceof Error) errors.push(error.message);
|
|
62
56
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (error instanceof Error) errors.push(error.message);
|
|
57
|
+
if (body.metadata) {
|
|
58
|
+
for (const [metadataKey, metadataValue] of Object.entries(
|
|
59
|
+
body.metadata,
|
|
60
|
+
)) {
|
|
61
|
+
await commands.editCmd.editCardMetadata(
|
|
62
|
+
key,
|
|
63
|
+
metadataKey,
|
|
64
|
+
metadataValue as MetadataContent,
|
|
65
|
+
);
|
|
73
66
|
}
|
|
74
67
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (body.parent) {
|
|
78
|
-
try {
|
|
68
|
+
if (body.parent) {
|
|
79
69
|
await commands.moveCmd.moveCard(key, body.parent);
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (error instanceof Error) errors.push(error.message);
|
|
82
70
|
}
|
|
83
|
-
|
|
84
|
-
if (body.index != null) {
|
|
85
|
-
try {
|
|
71
|
+
if (body.index != null) {
|
|
86
72
|
await commands.moveCmd.rankByIndex(key, body.index);
|
|
87
|
-
} catch (error) {
|
|
88
|
-
if (error instanceof Error) errors.push(error.message);
|
|
89
73
|
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (errors.length > 0) {
|
|
93
|
-
throw new Error(errors.join('\n'));
|
|
94
|
-
}
|
|
74
|
+
}, `Update card ${key}`);
|
|
95
75
|
}
|
|
96
76
|
|
|
97
77
|
export async function deleteCard(commands: CommandManager, key: string) {
|
|
@@ -119,18 +99,20 @@ export async function uploadAttachments(
|
|
|
119
99
|
key: string,
|
|
120
100
|
files: File[],
|
|
121
101
|
) {
|
|
122
|
-
const succeeded = [];
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
102
|
+
const succeeded: string[] = [];
|
|
103
|
+
await commands.atomic(async () => {
|
|
104
|
+
for (const file of files) {
|
|
105
|
+
if (file instanceof File) {
|
|
106
|
+
const buffer = await file.arrayBuffer();
|
|
107
|
+
await commands.createCmd.createAttachment(
|
|
108
|
+
key,
|
|
109
|
+
file.name,
|
|
110
|
+
Buffer.from(buffer),
|
|
111
|
+
);
|
|
112
|
+
succeeded.push(file.name);
|
|
113
|
+
}
|
|
132
114
|
}
|
|
133
|
-
}
|
|
115
|
+
}, `Add attachments to ${key}`);
|
|
134
116
|
|
|
135
117
|
return {
|
|
136
118
|
message: 'Attachments uploaded successfully',
|
|
@@ -161,30 +143,32 @@ export async function parseContent(
|
|
|
161
143
|
key: string,
|
|
162
144
|
content: string,
|
|
163
145
|
) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
146
|
+
return commands.consistent(async () => {
|
|
147
|
+
let asciidocContent: string;
|
|
148
|
+
try {
|
|
149
|
+
asciidocContent = await evaluateMacros(content, {
|
|
150
|
+
context: 'localApp',
|
|
151
|
+
mode: 'inject',
|
|
152
|
+
project: commands.project,
|
|
153
|
+
cardKey: key,
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
asciidocContent = `Macro error: ${error instanceof Error ? error.message : 'Unknown error'}\n\n${content}`;
|
|
157
|
+
}
|
|
175
158
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
159
|
+
const processor = Processor();
|
|
160
|
+
const parsedContent = processor
|
|
161
|
+
.convert(asciidocContent, {
|
|
162
|
+
safe: 'safe',
|
|
163
|
+
attributes: {
|
|
164
|
+
imagesdir: `/api/cards/${key}/a`,
|
|
165
|
+
icons: 'font',
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
.toString();
|
|
169
|
+
|
|
170
|
+
return { parsedContent };
|
|
171
|
+
});
|
|
188
172
|
}
|
|
189
173
|
|
|
190
174
|
export async function createLink(
|
|
@@ -209,11 +193,11 @@ export async function removeLink(
|
|
|
209
193
|
return { message: 'Link removed successfully' };
|
|
210
194
|
}
|
|
211
195
|
|
|
212
|
-
export function getAttachment(
|
|
196
|
+
export async function getAttachment(
|
|
213
197
|
commands: CommandManager,
|
|
214
198
|
key: string,
|
|
215
199
|
filename: string,
|
|
216
|
-
) {
|
|
200
|
+
): Promise<attachmentPayload> {
|
|
217
201
|
return commands.showCmd.showAttachment(key, filename);
|
|
218
202
|
}
|
|
219
203
|
|
|
@@ -15,6 +15,8 @@ import { Hono } from 'hono';
|
|
|
15
15
|
import * as fieldTypeService from './service.js';
|
|
16
16
|
import { createFieldTypeSchema } from './schema.js';
|
|
17
17
|
import { zValidator } from '../../middleware/zvalidator.js';
|
|
18
|
+
import { UserRole } from '../../types.js';
|
|
19
|
+
import { requireRole } from '../../middleware/auth.js';
|
|
18
20
|
|
|
19
21
|
const router = new Hono();
|
|
20
22
|
|
|
@@ -32,7 +34,7 @@ const router = new Hono();
|
|
|
32
34
|
* 500:
|
|
33
35
|
* description: project_path not set or other internal error
|
|
34
36
|
*/
|
|
35
|
-
router.get('/', async (c) => {
|
|
37
|
+
router.get('/', requireRole(UserRole.Reader), async (c) => {
|
|
36
38
|
const commands = c.get('commands');
|
|
37
39
|
|
|
38
40
|
try {
|
|
@@ -77,21 +79,26 @@ router.get('/', async (c) => {
|
|
|
77
79
|
* 500:
|
|
78
80
|
* description: Server error
|
|
79
81
|
*/
|
|
80
|
-
router.post(
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
router.post(
|
|
83
|
+
'/',
|
|
84
|
+
requireRole(UserRole.Admin),
|
|
85
|
+
zValidator('json', createFieldTypeSchema),
|
|
86
|
+
async (c) => {
|
|
87
|
+
const commands = c.get('commands');
|
|
88
|
+
const { identifier, dataType } = c.req.valid('json');
|
|
83
89
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
90
|
+
try {
|
|
91
|
+
await fieldTypeService.createFieldType(commands, identifier, dataType);
|
|
92
|
+
return c.json({ message: 'Field type created successfully' });
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return c.json(
|
|
95
|
+
{
|
|
96
|
+
error: `${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
97
|
+
},
|
|
98
|
+
500,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
);
|
|
96
103
|
|
|
97
104
|
export default router;
|
|
@@ -15,6 +15,8 @@ import { Hono } from 'hono';
|
|
|
15
15
|
import * as graphModelService from './service.js';
|
|
16
16
|
import { createGraphModelSchema } from './schema.js';
|
|
17
17
|
import { zValidator } from '../../middleware/zvalidator.js';
|
|
18
|
+
import { UserRole } from '../../types.js';
|
|
19
|
+
import { requireRole } from '../../middleware/auth.js';
|
|
18
20
|
|
|
19
21
|
const router = new Hono();
|
|
20
22
|
|
|
@@ -43,12 +45,17 @@ const router = new Hono();
|
|
|
43
45
|
* 500:
|
|
44
46
|
* description: Server error
|
|
45
47
|
*/
|
|
46
|
-
router.post(
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
router.post(
|
|
49
|
+
'/',
|
|
50
|
+
requireRole(UserRole.Admin),
|
|
51
|
+
zValidator('json', createGraphModelSchema),
|
|
52
|
+
async (c) => {
|
|
53
|
+
const commands = c.get('commands');
|
|
54
|
+
const { identifier } = c.req.valid('json');
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
56
|
+
await graphModelService.createGraphModel(commands, identifier);
|
|
57
|
+
return c.json({ message: 'Graph model created successfully' });
|
|
58
|
+
},
|
|
59
|
+
);
|
|
53
60
|
|
|
54
61
|
export default router;
|
|
@@ -15,6 +15,8 @@ import { Hono } from 'hono';
|
|
|
15
15
|
import * as graphViewService from './service.js';
|
|
16
16
|
import { createGraphViewSchema } from './schema.js';
|
|
17
17
|
import { zValidator } from '../../middleware/zvalidator.js';
|
|
18
|
+
import { UserRole } from '../../types.js';
|
|
19
|
+
import { requireRole } from '../../middleware/auth.js';
|
|
18
20
|
|
|
19
21
|
const router = new Hono();
|
|
20
22
|
|
|
@@ -43,11 +45,16 @@ const router = new Hono();
|
|
|
43
45
|
* 500:
|
|
44
46
|
* description: Server error
|
|
45
47
|
*/
|
|
46
|
-
router.post(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
router.post(
|
|
49
|
+
'/',
|
|
50
|
+
requireRole(UserRole.Admin),
|
|
51
|
+
zValidator('json', createGraphViewSchema),
|
|
52
|
+
async (c) => {
|
|
53
|
+
const commands = c.get('commands');
|
|
54
|
+
const { identifier } = c.req.valid('json');
|
|
55
|
+
await graphViewService.createGraphView(commands, identifier);
|
|
56
|
+
return c.json({ message: 'Graph view created successfully' });
|
|
57
|
+
},
|
|
58
|
+
);
|
|
52
59
|
|
|
53
60
|
export default router;
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import { Hono } from 'hono';
|
|
15
15
|
import * as labelsService from './service.js';
|
|
16
|
+
import { UserRole } from '../../types.js';
|
|
17
|
+
import { requireRole } from '../../middleware/auth.js';
|
|
16
18
|
|
|
17
19
|
const router = new Hono();
|
|
18
20
|
|
|
@@ -27,9 +29,10 @@ const router = new Hono();
|
|
|
27
29
|
* 500:
|
|
28
30
|
* description: Internal server error
|
|
29
31
|
*/
|
|
30
|
-
|
|
32
|
+
|
|
33
|
+
router.get('/', requireRole(UserRole.Reader), async (c) => {
|
|
31
34
|
const commands = c.get('commands');
|
|
32
|
-
const labels = labelsService.getLabels(commands);
|
|
35
|
+
const labels = await labelsService.getLabels(commands);
|
|
33
36
|
return c.json(labels);
|
|
34
37
|
});
|
|
35
38
|
|
|
@@ -18,6 +18,6 @@ import type { CommandManager } from '@cyberismo/data-handler';
|
|
|
18
18
|
* @param commands command manager used for the query
|
|
19
19
|
* @returns a list of labels
|
|
20
20
|
*/
|
|
21
|
-
export function getLabels(commands: CommandManager): string[] {
|
|
22
|
-
return commands.showCmd.showLabels().sort();
|
|
21
|
+
export async function getLabels(commands: CommandManager): Promise<string[]> {
|
|
22
|
+
return (await commands.showCmd.showLabels()).sort();
|
|
23
23
|
}
|
|
@@ -15,6 +15,8 @@ import { Hono } from 'hono';
|
|
|
15
15
|
import * as linkTypeService from './service.js';
|
|
16
16
|
import { createLinkTypeSchema } from './schema.js';
|
|
17
17
|
import { zValidator } from '../../middleware/zvalidator.js';
|
|
18
|
+
import { UserRole } from '../../types.js';
|
|
19
|
+
import { requireRole } from '../../middleware/auth.js';
|
|
18
20
|
|
|
19
21
|
const router = new Hono();
|
|
20
22
|
|
|
@@ -32,7 +34,7 @@ const router = new Hono();
|
|
|
32
34
|
* 500:
|
|
33
35
|
* description: project_path not set or other internal error
|
|
34
36
|
*/
|
|
35
|
-
router.get('/', async (c) => {
|
|
37
|
+
router.get('/', requireRole(UserRole.Reader), async (c) => {
|
|
36
38
|
const commands = c.get('commands');
|
|
37
39
|
|
|
38
40
|
try {
|
|
@@ -73,12 +75,17 @@ router.get('/', async (c) => {
|
|
|
73
75
|
* 500:
|
|
74
76
|
* description: Server error
|
|
75
77
|
*/
|
|
76
|
-
router.post(
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
router.post(
|
|
79
|
+
'/',
|
|
80
|
+
requireRole(UserRole.Admin),
|
|
81
|
+
zValidator('json', createLinkTypeSchema),
|
|
82
|
+
async (c) => {
|
|
83
|
+
const commands = c.get('commands');
|
|
84
|
+
const { identifier } = c.req.valid('json');
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
86
|
+
await linkTypeService.createLinkType(commands, identifier);
|
|
87
|
+
return c.json({ message: 'Link type created successfully' });
|
|
88
|
+
},
|
|
89
|
+
);
|
|
83
90
|
|
|
84
91
|
export default router;
|
|
@@ -15,11 +15,14 @@ import { Hono } from 'hono';
|
|
|
15
15
|
import { zValidator } from '../../middleware/zvalidator.js';
|
|
16
16
|
import { resourceParamsWithCard } from '../../common/validationSchemas.js';
|
|
17
17
|
import * as logicProgramService from './service.js';
|
|
18
|
+
import { UserRole } from '../../types.js';
|
|
19
|
+
import { requireRole } from '../../middleware/auth.js';
|
|
18
20
|
|
|
19
21
|
const router = new Hono();
|
|
20
22
|
|
|
21
23
|
router.get(
|
|
22
24
|
'/:prefix/:type/:identifier',
|
|
25
|
+
requireRole(UserRole.Reader),
|
|
23
26
|
zValidator('param', resourceParamsWithCard),
|
|
24
27
|
async (c) => {
|
|
25
28
|
const commands = c.get('commands');
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2025
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU Affero General Public License version 3 as published by
|
|
6
|
+
the Free Software Foundation.
|
|
7
|
+
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
8
|
+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
9
|
+
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
10
|
+
details. You should have received a copy of the GNU Affero General Public
|
|
11
|
+
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Hono } from 'hono';
|
|
15
|
+
import { randomUUID } from 'node:crypto';
|
|
16
|
+
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
17
|
+
import { createMcpServer } from '@cyberismo/mcp/server';
|
|
18
|
+
import type { CommandManager } from '@cyberismo/data-handler';
|
|
19
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
20
|
+
|
|
21
|
+
const MAX_SESSIONS = 100;
|
|
22
|
+
const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
23
|
+
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
24
|
+
|
|
25
|
+
interface McpSession {
|
|
26
|
+
transport: WebStandardStreamableHTTPServerTransport;
|
|
27
|
+
server: McpServer;
|
|
28
|
+
commands: CommandManager;
|
|
29
|
+
lastActivity: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const sessions = new Map<string, McpSession>();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Close and remove a session, shutting down both server and transport.
|
|
36
|
+
* Safe to call multiple times for the same id.
|
|
37
|
+
* When `skipTransportClose` is true the transport is already closing
|
|
38
|
+
* (called from onsessionclosed / onclose callbacks).
|
|
39
|
+
*/
|
|
40
|
+
async function destroySession(
|
|
41
|
+
id: string,
|
|
42
|
+
skipTransportClose = false,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const session = sessions.get(id);
|
|
45
|
+
if (!session) return;
|
|
46
|
+
sessions.delete(id);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await session.server.close();
|
|
50
|
+
} catch {
|
|
51
|
+
// Ignore close errors during cleanup
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!skipTransportClose && session.transport.close) {
|
|
55
|
+
try {
|
|
56
|
+
await session.transport.close();
|
|
57
|
+
} catch {
|
|
58
|
+
// Ignore close errors during cleanup
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Periodic cleanup of expired sessions
|
|
64
|
+
const cleanupInterval = setInterval(() => {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
for (const [id, session] of sessions) {
|
|
67
|
+
if (now - session.lastActivity > SESSION_TIMEOUT_MS) {
|
|
68
|
+
void destroySession(id);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, CLEANUP_INTERVAL_MS);
|
|
72
|
+
cleanupInterval.unref();
|
|
73
|
+
|
|
74
|
+
const router = new Hono();
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* MCP HTTP endpoint handler.
|
|
78
|
+
* Supports GET (SSE streaming), POST (messages), and DELETE (session cleanup).
|
|
79
|
+
*/
|
|
80
|
+
router.all('/', async (c) => {
|
|
81
|
+
const commands = c.get('commands');
|
|
82
|
+
const sessionId = c.req.header('mcp-session-id');
|
|
83
|
+
|
|
84
|
+
// Handle DELETE before routing to existing session so it always runs cleanup
|
|
85
|
+
if (c.req.method === 'DELETE') {
|
|
86
|
+
if (sessionId) {
|
|
87
|
+
await destroySession(sessionId);
|
|
88
|
+
}
|
|
89
|
+
return c.json({ message: 'Session closed' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Handle existing session
|
|
93
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
94
|
+
const session = sessions.get(sessionId)!;
|
|
95
|
+
session.lastActivity = Date.now();
|
|
96
|
+
const response = await session.transport.handleRequest(c.req.raw);
|
|
97
|
+
return response;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Only allow POST to create new sessions (initialize)
|
|
101
|
+
if (c.req.method !== 'POST') {
|
|
102
|
+
return c.json(
|
|
103
|
+
{ error: 'Method not allowed. Use POST to initialize a session.' },
|
|
104
|
+
405,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Reject new sessions when at capacity
|
|
109
|
+
if (sessions.size >= MAX_SESSIONS) {
|
|
110
|
+
return c.json({ error: 'Too many active sessions' }, 503);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Create new session for initialization
|
|
114
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
115
|
+
sessionIdGenerator: () => randomUUID(),
|
|
116
|
+
onsessioninitialized: (newSessionId: string) => {
|
|
117
|
+
sessions.set(newSessionId, {
|
|
118
|
+
transport,
|
|
119
|
+
server,
|
|
120
|
+
commands,
|
|
121
|
+
lastActivity: Date.now(),
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
onsessionclosed: (closedSessionId: string) => {
|
|
125
|
+
void destroySession(closedSessionId, true);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
transport.onclose = () => {
|
|
130
|
+
const sid = transport.sessionId;
|
|
131
|
+
if (sid) {
|
|
132
|
+
void destroySession(sid, true);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const server = createMcpServer(commands);
|
|
137
|
+
await server.connect(transport);
|
|
138
|
+
|
|
139
|
+
const response = await transport.handleRequest(c.req.raw);
|
|
140
|
+
return response;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* SSE endpoint for server-to-client messages
|
|
145
|
+
*/
|
|
146
|
+
router.get('/sse', async (c) => {
|
|
147
|
+
const sessionId = c.req.header('mcp-session-id');
|
|
148
|
+
|
|
149
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
150
|
+
return c.json({ error: 'Invalid or missing session ID' }, 400);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const session = sessions.get(sessionId)!;
|
|
154
|
+
session.lastActivity = Date.now();
|
|
155
|
+
const response = await session.transport.handleRequest(c.req.raw);
|
|
156
|
+
return response;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export default router;
|
|
@@ -15,25 +15,32 @@ import { Hono } from 'hono';
|
|
|
15
15
|
import { zValidator } from '../../middleware/zvalidator.js';
|
|
16
16
|
import { moduleParamSchema, updateProjectSchema } from './schema.js';
|
|
17
17
|
import * as projectService from './service.js';
|
|
18
|
+
import { UserRole } from '../../types.js';
|
|
19
|
+
import { requireRole } from '../../middleware/auth.js';
|
|
18
20
|
|
|
19
21
|
const router = new Hono();
|
|
20
22
|
|
|
21
|
-
router.get('/', async (c) => {
|
|
23
|
+
router.get('/', requireRole(UserRole.Reader), async (c) => {
|
|
22
24
|
const commands = c.get('commands');
|
|
23
25
|
|
|
24
26
|
const project = await projectService.getProject(commands);
|
|
25
27
|
return c.json(project);
|
|
26
28
|
});
|
|
27
29
|
|
|
28
|
-
router.patch(
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
router.patch(
|
|
31
|
+
'/',
|
|
32
|
+
requireRole(UserRole.Admin),
|
|
33
|
+
zValidator('json', updateProjectSchema),
|
|
34
|
+
async (c) => {
|
|
35
|
+
const commands = c.get('commands');
|
|
36
|
+
const updates = c.req.valid('json');
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
38
|
+
const project = await projectService.updateProject(commands, updates);
|
|
39
|
+
return c.json(project);
|
|
40
|
+
},
|
|
41
|
+
);
|
|
35
42
|
|
|
36
|
-
router.post('/modules/update', async (c) => {
|
|
43
|
+
router.post('/modules/update', requireRole(UserRole.Admin), async (c) => {
|
|
37
44
|
const commands = c.get('commands');
|
|
38
45
|
await projectService.updateAllModules(commands);
|
|
39
46
|
return c.json({ message: 'All modules updated' });
|
|
@@ -41,6 +48,7 @@ router.post('/modules/update', async (c) => {
|
|
|
41
48
|
|
|
42
49
|
router.post(
|
|
43
50
|
'/modules/:module/update',
|
|
51
|
+
requireRole(UserRole.Admin),
|
|
44
52
|
zValidator('param', moduleParamSchema),
|
|
45
53
|
async (c) => {
|
|
46
54
|
const commands = c.get('commands');
|
|
@@ -52,6 +60,7 @@ router.post(
|
|
|
52
60
|
|
|
53
61
|
router.delete(
|
|
54
62
|
'/modules/:module',
|
|
63
|
+
requireRole(UserRole.Admin),
|
|
55
64
|
zValidator('param', moduleParamSchema),
|
|
56
65
|
async (c) => {
|
|
57
66
|
const commands = c.get('commands');
|