@cyberismo/backend 0.0.21 → 0.0.23
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 +25 -10
- 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 +124 -25
- package/dist/domain/cards/index.js.map +1 -1
- package/dist/domain/cards/lib.js +92 -93
- package/dist/domain/cards/lib.js.map +1 -1
- package/dist/domain/cards/presence.d.ts +50 -0
- package/dist/domain/cards/presence.js +93 -0
- package/dist/domain/cards/presence.js.map +1 -0
- package/dist/domain/cards/schema.d.ts +47 -0
- package/dist/domain/cards/schema.js +37 -0
- package/dist/domain/cards/schema.js.map +1 -0
- package/dist/domain/cards/service.d.ts +7 -3
- package/dist/domain/cards/service.js +81 -91
- package/dist/domain/cards/service.js.map +1 -1
- package/dist/domain/connectors/index.d.ts +15 -0
- package/dist/domain/connectors/index.js +37 -0
- package/dist/domain/connectors/index.js.map +1 -0
- package/dist/domain/connectors/service.d.ts +23 -0
- package/dist/domain/connectors/service.js +46 -0
- package/dist/domain/connectors/service.js.map +1 -0
- 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 +19 -6
- package/dist/domain/project/index.js.map +1 -1
- package/dist/domain/project/schema.d.ts +3 -0
- package/dist/domain/project/schema.js +8 -0
- package/dist/domain/project/schema.js.map +1 -1
- package/dist/domain/project/service.d.ts +3 -1
- package/dist/domain/project/service.js +24 -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/tree/service.js +0 -1
- package/dist/domain/tree/service.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 +16 -13
- package/dist/export.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +12 -4
- 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 +1212 -605
- package/dist/public/assets/index-Cdn_jRWy.js +720 -0
- package/dist/public/assets/index-ypsafPwV.css +1 -0
- package/dist/public/config.json +1 -0
- package/dist/public/images/broken_link.svg +7 -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 +10 -7
- package/src/app.ts +37 -15
- 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 +258 -90
- package/src/domain/cards/lib.ts +102 -100
- package/src/domain/cards/presence.ts +124 -0
- package/src/domain/cards/schema.ts +41 -0
- package/src/domain/cards/service.ts +138 -93
- package/src/domain/connectors/index.ts +39 -0
- package/src/domain/connectors/service.ts +67 -0
- 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 +40 -9
- package/src/domain/project/schema.ts +9 -0
- package/src/domain/project/service.ts +37 -17
- 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/tree/service.ts +0 -1
- package/src/domain/workflows/index.ts +13 -6
- package/src/export.ts +17 -15
- package/src/index.ts +18 -7
- 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
- package/dist/public/assets/index-Ca10XaMv.js +0 -164156
|
@@ -54,126 +54,72 @@ export async function updateFieldVisibility(
|
|
|
54
54
|
): Promise<void> {
|
|
55
55
|
const { fieldName, group: targetGroup, index: targetIndex } = body;
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
await commands.atomic(async () => {
|
|
58
|
+
// Read is now inside the write lock
|
|
59
|
+
const cardType = await commands.showCmd.showResource(
|
|
60
|
+
cardTypeName,
|
|
61
|
+
'cardTypes',
|
|
62
|
+
);
|
|
63
|
+
if (!cardType) {
|
|
64
|
+
throw new Error(`Card type '${cardTypeName}' not found`);
|
|
65
|
+
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const customFields = cardType.customFields || [];
|
|
68
|
+
const alwaysVisibleFields = cardType.alwaysVisibleFields || [];
|
|
69
|
+
const optionallyVisibleFields = cardType.optionallyVisibleFields || [];
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
);
|
|
74
|
-
if (!fieldExists) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
`Field '${fieldName}' does not exist in card type '${cardTypeName}'. `,
|
|
71
|
+
// Validate that the field exists in customFields
|
|
72
|
+
const fieldExists = customFields.some(
|
|
73
|
+
(f: { name: string }) => f.name === fieldName,
|
|
77
74
|
);
|
|
78
|
-
|
|
75
|
+
if (!fieldExists) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Field '${fieldName}' does not exist in card type '${cardTypeName}'. `,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
const currentGroup = getCurrentGroup(
|
|
82
|
+
alwaysVisibleFields,
|
|
83
|
+
optionallyVisibleFields,
|
|
84
|
+
fieldName,
|
|
85
|
+
);
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
// If same group, just handle reordering
|
|
88
|
+
if (currentGroup === targetGroup) {
|
|
89
|
+
if (targetGroup !== 'hidden' && targetIndex !== undefined) {
|
|
90
|
+
await commands.updateCmd.applyResourceOperation(
|
|
91
|
+
cardTypeName,
|
|
92
|
+
{ key: groupToKey[targetGroup] },
|
|
93
|
+
{ name: 'rank', target: fieldName, newIndex: targetIndex },
|
|
94
|
+
);
|
|
95
|
+
}
|
|
90
96
|
return;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
|
-
if (targetIndex !== undefined) {
|
|
94
|
-
await commands.updateCmd.applyResourceOperation(
|
|
95
|
-
cardTypeName,
|
|
96
|
-
{
|
|
97
|
-
key: groupToKey[targetGroup],
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: 'rank',
|
|
101
|
-
target: fieldName,
|
|
102
|
-
newIndex: targetIndex,
|
|
103
|
-
},
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Different group - need to remove from old and add to new
|
|
110
|
-
let removedFromOld = false;
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
99
|
// Remove from current group (if not hidden)
|
|
114
100
|
if (currentGroup !== 'hidden') {
|
|
115
101
|
await commands.updateCmd.applyResourceOperation(
|
|
116
102
|
cardTypeName,
|
|
117
|
-
{
|
|
118
|
-
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: 'remove',
|
|
122
|
-
target: fieldName,
|
|
123
|
-
},
|
|
103
|
+
{ key: groupToKey[currentGroup] },
|
|
104
|
+
{ name: 'remove', target: fieldName },
|
|
124
105
|
);
|
|
125
|
-
removedFromOld = true;
|
|
126
106
|
}
|
|
127
107
|
|
|
128
108
|
// Add to new group (if not hidden)
|
|
129
109
|
if (targetGroup !== 'hidden') {
|
|
130
110
|
await commands.updateCmd.applyResourceOperation(
|
|
131
111
|
cardTypeName,
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
name: 'add',
|
|
137
|
-
target: fieldName,
|
|
138
|
-
},
|
|
112
|
+
{ key: groupToKey[targetGroup] },
|
|
113
|
+
{ name: 'add', target: fieldName },
|
|
139
114
|
);
|
|
140
115
|
|
|
141
|
-
// Reorder if index specified
|
|
142
116
|
if (targetIndex !== undefined) {
|
|
143
117
|
await commands.updateCmd.applyResourceOperation(
|
|
144
118
|
cardTypeName,
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
name: 'rank',
|
|
150
|
-
target: fieldName,
|
|
151
|
-
newIndex: targetIndex,
|
|
152
|
-
},
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
} catch (error) {
|
|
157
|
-
// Attempt rollback if we removed from old group but failed to add to new
|
|
158
|
-
if (removedFromOld && currentGroup !== 'hidden') {
|
|
159
|
-
try {
|
|
160
|
-
await commands.updateCmd.applyResourceOperation(
|
|
161
|
-
cardTypeName,
|
|
162
|
-
{
|
|
163
|
-
key: groupToKey[currentGroup],
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
name: 'add',
|
|
167
|
-
target: fieldName,
|
|
168
|
-
},
|
|
169
|
-
);
|
|
170
|
-
} catch {
|
|
171
|
-
// Rollback failed - log but throw original error
|
|
172
|
-
console.error(
|
|
173
|
-
`Rollback failed for field '${fieldName}' in card type '${cardTypeName}'`,
|
|
119
|
+
{ key: groupToKey[targetGroup] },
|
|
120
|
+
{ name: 'rank', target: fieldName, newIndex: targetIndex },
|
|
174
121
|
);
|
|
175
122
|
}
|
|
176
123
|
}
|
|
177
|
-
|
|
178
|
-
}
|
|
124
|
+
}, `Update field visibility for ${cardTypeName}`);
|
|
179
125
|
}
|
|
@@ -13,10 +13,20 @@
|
|
|
13
13
|
|
|
14
14
|
import { type Context, Hono } from 'hono';
|
|
15
15
|
import type { ContentfulStatusCode } from 'hono/utils/http-status';
|
|
16
|
+
import { streamSSE } from 'hono/streaming';
|
|
16
17
|
import { getCardDetails } from './lib.js';
|
|
17
18
|
import * as cardService from './service.js';
|
|
18
19
|
import { isSSGContext, ssgParams } from 'hono/ssg';
|
|
19
20
|
import type { AppContext } from '../../types.js';
|
|
21
|
+
import { UserRole } from '../../types.js';
|
|
22
|
+
import { presenceStore } from './presence.js';
|
|
23
|
+
import { requireRole } from '../../middleware/auth.js';
|
|
24
|
+
import { zValidator } from '../../middleware/zvalidator.js';
|
|
25
|
+
import {
|
|
26
|
+
createLinkSchema,
|
|
27
|
+
removeLinkSchema,
|
|
28
|
+
updateLinkSchema,
|
|
29
|
+
} from './schema.js';
|
|
20
30
|
|
|
21
31
|
const router = new Hono();
|
|
22
32
|
|
|
@@ -34,7 +44,7 @@ const router = new Hono();
|
|
|
34
44
|
* 500:
|
|
35
45
|
* description: project_path not set.
|
|
36
46
|
*/
|
|
37
|
-
router.get('/', async (c) => {
|
|
47
|
+
router.get('/', requireRole(UserRole.Reader), async (c) => {
|
|
38
48
|
const commands = c.get('commands');
|
|
39
49
|
|
|
40
50
|
try {
|
|
@@ -75,6 +85,7 @@ router.get('/', async (c) => {
|
|
|
75
85
|
*/
|
|
76
86
|
router.get(
|
|
77
87
|
'/:key',
|
|
88
|
+
requireRole(UserRole.Reader),
|
|
78
89
|
ssgParams(async (c: AppContext) => {
|
|
79
90
|
const commands = c.get('commands');
|
|
80
91
|
const opts = c.get('tree');
|
|
@@ -144,7 +155,7 @@ router.get(
|
|
|
144
155
|
* 500:
|
|
145
156
|
* description: project_path not set.
|
|
146
157
|
*/
|
|
147
|
-
router.patch('/:key', async (c) => {
|
|
158
|
+
router.patch('/:key', requireRole(UserRole.Editor), async (c) => {
|
|
148
159
|
const commands = c.get('commands');
|
|
149
160
|
const key = c.req.param('key');
|
|
150
161
|
if (!key) {
|
|
@@ -201,7 +212,7 @@ router.patch('/:key', async (c) => {
|
|
|
201
212
|
* 500:
|
|
202
213
|
* description: project_path not set.
|
|
203
214
|
*/
|
|
204
|
-
router.delete('/:key', async (c) => {
|
|
215
|
+
router.delete('/:key', requireRole(UserRole.Editor), async (c) => {
|
|
205
216
|
const commands = c.get('commands');
|
|
206
217
|
const key = c.req.param('key');
|
|
207
218
|
if (!key) {
|
|
@@ -244,7 +255,7 @@ router.delete('/:key', async (c) => {
|
|
|
244
255
|
* 500:
|
|
245
256
|
* description: project_path not set
|
|
246
257
|
*/
|
|
247
|
-
router.post('/:key', async (c) => {
|
|
258
|
+
router.post('/:key', requireRole(UserRole.Editor), async (c) => {
|
|
248
259
|
const key = c.req.param('key');
|
|
249
260
|
if (!key) {
|
|
250
261
|
return c.text('No search key', 400);
|
|
@@ -300,7 +311,7 @@ router.post('/:key', async (c) => {
|
|
|
300
311
|
* 500:
|
|
301
312
|
* description: Server error
|
|
302
313
|
*/
|
|
303
|
-
router.post('/:key/attachments', async (c) => {
|
|
314
|
+
router.post('/:key/attachments', requireRole(UserRole.Editor), async (c) => {
|
|
304
315
|
const commands = c.get('commands');
|
|
305
316
|
const key = c.req.param('key');
|
|
306
317
|
|
|
@@ -354,25 +365,33 @@ router.post('/:key/attachments', async (c) => {
|
|
|
354
365
|
* 500:
|
|
355
366
|
* description: Server error
|
|
356
367
|
*/
|
|
357
|
-
router.delete(
|
|
358
|
-
|
|
359
|
-
|
|
368
|
+
router.delete(
|
|
369
|
+
'/:key/attachments/:filename',
|
|
370
|
+
requireRole(UserRole.Editor),
|
|
371
|
+
async (c) => {
|
|
372
|
+
const commands = c.get('commands');
|
|
373
|
+
const { key, filename } = c.req.param();
|
|
360
374
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
375
|
+
try {
|
|
376
|
+
const result = await cardService.removeAttachment(
|
|
377
|
+
commands,
|
|
378
|
+
key,
|
|
379
|
+
filename,
|
|
380
|
+
);
|
|
381
|
+
return c.json(result);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
return c.json(
|
|
384
|
+
{
|
|
385
|
+
error:
|
|
386
|
+
error instanceof Error
|
|
387
|
+
? error.message
|
|
388
|
+
: 'Failed to remove attachment',
|
|
389
|
+
},
|
|
390
|
+
500,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
);
|
|
376
395
|
|
|
377
396
|
/**
|
|
378
397
|
* @swagger
|
|
@@ -398,23 +417,29 @@ router.delete('/:key/attachments/:filename', async (c) => {
|
|
|
398
417
|
* 500:
|
|
399
418
|
* description: Server error
|
|
400
419
|
*/
|
|
401
|
-
router.post(
|
|
402
|
-
|
|
403
|
-
|
|
420
|
+
router.post(
|
|
421
|
+
'/:key/attachments/:filename/open',
|
|
422
|
+
requireRole(UserRole.Reader),
|
|
423
|
+
async (c) => {
|
|
424
|
+
const commands = c.get('commands');
|
|
425
|
+
const { key, filename } = c.req.param();
|
|
404
426
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
427
|
+
try {
|
|
428
|
+
const result = await cardService.openAttachment(commands, key, filename);
|
|
429
|
+
return c.json(result);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
return c.json(
|
|
432
|
+
{
|
|
433
|
+
error:
|
|
434
|
+
error instanceof Error
|
|
435
|
+
? error.message
|
|
436
|
+
: 'Failed to open attachment',
|
|
437
|
+
},
|
|
438
|
+
500,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
);
|
|
418
443
|
|
|
419
444
|
/**
|
|
420
445
|
* @swagger
|
|
@@ -443,7 +468,7 @@ router.post('/:key/attachments/:filename/open', async (c) => {
|
|
|
443
468
|
* 500:
|
|
444
469
|
* description: Server error
|
|
445
470
|
*/
|
|
446
|
-
router.post('/:key/parse', async (c) => {
|
|
471
|
+
router.post('/:key/parse', requireRole(UserRole.Reader), async (c) => {
|
|
447
472
|
const commands = c.get('commands');
|
|
448
473
|
const key = c.req.param('key');
|
|
449
474
|
const { content } = await c.req.json();
|
|
@@ -497,33 +522,36 @@ router.post('/:key/parse', async (c) => {
|
|
|
497
522
|
* 500:
|
|
498
523
|
* description: Server error
|
|
499
524
|
*/
|
|
500
|
-
router.post(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
525
|
+
router.post(
|
|
526
|
+
'/:key/links',
|
|
527
|
+
requireRole(UserRole.Editor),
|
|
528
|
+
zValidator('json', createLinkSchema),
|
|
529
|
+
async (c) => {
|
|
530
|
+
const commands = c.get('commands');
|
|
531
|
+
const key = c.req.param('key');
|
|
532
|
+
const { toCard, linkType, description, direction } = c.req.valid('json');
|
|
508
533
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
534
|
+
try {
|
|
535
|
+
const result = await cardService.createLink(
|
|
536
|
+
commands,
|
|
537
|
+
key,
|
|
538
|
+
toCard,
|
|
539
|
+
linkType,
|
|
540
|
+
direction,
|
|
541
|
+
description,
|
|
542
|
+
);
|
|
543
|
+
return c.json(result);
|
|
544
|
+
} catch (error) {
|
|
545
|
+
return c.json(
|
|
546
|
+
{
|
|
547
|
+
error:
|
|
548
|
+
error instanceof Error ? error.message : 'Failed to create link',
|
|
549
|
+
},
|
|
550
|
+
500,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
);
|
|
527
555
|
|
|
528
556
|
/**
|
|
529
557
|
* @swagger
|
|
@@ -556,33 +584,122 @@ router.post('/:key/links', async (c) => {
|
|
|
556
584
|
* 500:
|
|
557
585
|
* description: Server error
|
|
558
586
|
*/
|
|
559
|
-
router.delete(
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
587
|
+
router.delete(
|
|
588
|
+
'/:key/links',
|
|
589
|
+
requireRole(UserRole.Editor),
|
|
590
|
+
zValidator('json', removeLinkSchema),
|
|
591
|
+
async (c) => {
|
|
592
|
+
const commands = c.get('commands');
|
|
593
|
+
const key = c.req.param('key');
|
|
594
|
+
const { toCard, linkType, description, direction } = c.req.valid('json');
|
|
563
595
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
596
|
+
try {
|
|
597
|
+
const result = await cardService.removeLink(
|
|
598
|
+
commands,
|
|
599
|
+
key,
|
|
600
|
+
toCard,
|
|
601
|
+
linkType,
|
|
602
|
+
direction,
|
|
603
|
+
description,
|
|
604
|
+
);
|
|
605
|
+
return c.json(result);
|
|
606
|
+
} catch (error) {
|
|
607
|
+
return c.json(
|
|
608
|
+
{
|
|
609
|
+
error:
|
|
610
|
+
error instanceof Error ? error.message : 'Failed to remove link',
|
|
611
|
+
},
|
|
612
|
+
500,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
);
|
|
567
617
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
618
|
+
/**
|
|
619
|
+
* @swagger
|
|
620
|
+
* /api/cards/{key}/links:
|
|
621
|
+
* put:
|
|
622
|
+
* summary: Update a link between cards
|
|
623
|
+
* parameters:
|
|
624
|
+
* - name: key
|
|
625
|
+
* in: path
|
|
626
|
+
* required: true
|
|
627
|
+
* schema:
|
|
628
|
+
* type: string
|
|
629
|
+
* requestBody:
|
|
630
|
+
* content:
|
|
631
|
+
* application/json:
|
|
632
|
+
* schema:
|
|
633
|
+
* type: object
|
|
634
|
+
* properties:
|
|
635
|
+
* toCard:
|
|
636
|
+
* type: string
|
|
637
|
+
* linkType:
|
|
638
|
+
* type: string
|
|
639
|
+
* description:
|
|
640
|
+
* type: string
|
|
641
|
+
* direction:
|
|
642
|
+
* type: string
|
|
643
|
+
* previousToCard:
|
|
644
|
+
* type: string
|
|
645
|
+
* previousLinkType:
|
|
646
|
+
* type: string
|
|
647
|
+
* previousDirection:
|
|
648
|
+
* type: string
|
|
649
|
+
* previousDescription:
|
|
650
|
+
* type: string
|
|
651
|
+
* responses:
|
|
652
|
+
* 200:
|
|
653
|
+
* description: Link updated successfully
|
|
654
|
+
* 400:
|
|
655
|
+
* description: Invalid request
|
|
656
|
+
* 500:
|
|
657
|
+
* description: Server error
|
|
658
|
+
*/
|
|
659
|
+
router.put(
|
|
660
|
+
'/:key/links',
|
|
661
|
+
requireRole(UserRole.Editor),
|
|
662
|
+
zValidator('json', updateLinkSchema),
|
|
663
|
+
async (c) => {
|
|
664
|
+
const commands = c.get('commands');
|
|
665
|
+
const key = c.req.param('key');
|
|
666
|
+
|
|
667
|
+
const {
|
|
572
668
|
toCard,
|
|
573
669
|
linkType,
|
|
574
670
|
description,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
671
|
+
direction,
|
|
672
|
+
previousToCard,
|
|
673
|
+
previousLinkType,
|
|
674
|
+
previousDirection,
|
|
675
|
+
previousDescription,
|
|
676
|
+
} = c.req.valid('json');
|
|
677
|
+
|
|
678
|
+
try {
|
|
679
|
+
const result = await cardService.updateLink(
|
|
680
|
+
commands,
|
|
681
|
+
key,
|
|
682
|
+
toCard,
|
|
683
|
+
linkType,
|
|
684
|
+
direction,
|
|
685
|
+
previousToCard,
|
|
686
|
+
previousLinkType,
|
|
687
|
+
previousDirection,
|
|
688
|
+
description,
|
|
689
|
+
previousDescription,
|
|
690
|
+
);
|
|
691
|
+
return c.json(result);
|
|
692
|
+
} catch (error) {
|
|
693
|
+
return c.json(
|
|
694
|
+
{
|
|
695
|
+
error:
|
|
696
|
+
error instanceof Error ? error.message : 'Failed to update link',
|
|
697
|
+
},
|
|
698
|
+
500,
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
);
|
|
586
703
|
|
|
587
704
|
/**
|
|
588
705
|
* @swagger
|
|
@@ -610,11 +727,12 @@ router.delete('/:key/links', async (c) => {
|
|
|
610
727
|
*/
|
|
611
728
|
router.get(
|
|
612
729
|
'/:key/a/:attachment',
|
|
730
|
+
requireRole(UserRole.Reader),
|
|
613
731
|
ssgParams(async (c: Context) => {
|
|
614
732
|
const commands = c.get('commands');
|
|
615
733
|
return await cardService.findRelevantAttachments(commands, c.get('tree'));
|
|
616
734
|
}),
|
|
617
|
-
(c) => {
|
|
735
|
+
async (c) => {
|
|
618
736
|
const commands = c.get('commands');
|
|
619
737
|
const { key, attachment } = c.req.param();
|
|
620
738
|
const filename = decodeURI(attachment);
|
|
@@ -624,7 +742,7 @@ router.get(
|
|
|
624
742
|
}
|
|
625
743
|
|
|
626
744
|
try {
|
|
627
|
-
const attachmentResponse = cardService.getAttachment(
|
|
745
|
+
const attachmentResponse = await cardService.getAttachment(
|
|
628
746
|
commands,
|
|
629
747
|
key,
|
|
630
748
|
filename,
|
|
@@ -652,5 +770,55 @@ router.get(
|
|
|
652
770
|
}
|
|
653
771
|
},
|
|
654
772
|
);
|
|
773
|
+
/**
|
|
774
|
+
* @swagger
|
|
775
|
+
* /api/cards/{key}/presence:
|
|
776
|
+
* get:
|
|
777
|
+
* summary: SSE stream of users currently viewing or editing this card
|
|
778
|
+
* parameters:
|
|
779
|
+
* - name: key
|
|
780
|
+
* in: path
|
|
781
|
+
* required: true
|
|
782
|
+
* description: Card key (string)
|
|
783
|
+
* - name: mode
|
|
784
|
+
* in: query
|
|
785
|
+
* required: false
|
|
786
|
+
* schema:
|
|
787
|
+
* type: string
|
|
788
|
+
* enum: [viewing, editing]
|
|
789
|
+
* description: Whether the user is viewing or editing (default: viewing)
|
|
790
|
+
* responses:
|
|
791
|
+
* 200:
|
|
792
|
+
* description: SSE stream with presence events
|
|
793
|
+
*/
|
|
794
|
+
router.get('/:key/presence', requireRole(UserRole.Reader), (c) => {
|
|
795
|
+
const key = c.req.param('key');
|
|
796
|
+
const mode = c.req.query('mode') === 'editing' ? 'editing' : 'viewing';
|
|
797
|
+
const user = c.get('user');
|
|
798
|
+
|
|
799
|
+
if (!key) {
|
|
800
|
+
return c.text('No card key', 400);
|
|
801
|
+
}
|
|
655
802
|
|
|
803
|
+
return streamSSE(c, async (stream) => {
|
|
804
|
+
const connId = presenceStore.add(
|
|
805
|
+
key,
|
|
806
|
+
user,
|
|
807
|
+
mode,
|
|
808
|
+
(data) => void stream.writeSSE(data),
|
|
809
|
+
);
|
|
810
|
+
|
|
811
|
+
let aborted = false;
|
|
812
|
+
stream.onAbort(() => {
|
|
813
|
+
aborted = true;
|
|
814
|
+
presenceStore.remove(key, connId);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
// Keep connection alive with periodic heartbeat
|
|
818
|
+
while (!aborted) {
|
|
819
|
+
await stream.write(': hb\n\n');
|
|
820
|
+
await stream.sleep(30000);
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
});
|
|
656
824
|
export default router;
|