@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
package/src/domain/cards/lib.ts
CHANGED
|
@@ -30,122 +30,124 @@ export async function getCardDetails(
|
|
|
30
30
|
staticMode: boolean,
|
|
31
31
|
raw: boolean,
|
|
32
32
|
): Promise<result> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
return commands.consistent(async () => {
|
|
34
|
+
let cardDetailsResponse: Card;
|
|
35
|
+
try {
|
|
36
|
+
cardDetailsResponse = await commands.showCmd.showCardDetails(key);
|
|
37
|
+
} catch {
|
|
38
|
+
return { status: 400, message: `Card ${key} not found from project` };
|
|
39
|
+
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
if (!cardDetailsResponse) {
|
|
42
|
+
return { status: 400, message: `Card ${key} not found from project` };
|
|
43
|
+
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
let asciidocContent = '';
|
|
46
|
+
try {
|
|
47
|
+
asciidocContent = await evaluateMacros(
|
|
48
|
+
cardDetailsResponse.content || '',
|
|
49
|
+
{
|
|
50
|
+
context: staticMode ? 'exportedSite' : 'localApp',
|
|
51
|
+
mode: staticMode ? 'staticSite' : 'inject',
|
|
52
|
+
project: commands.project,
|
|
53
|
+
cardKey: key,
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
asciidocContent = `Macro error: ${error instanceof Error ? error.message : 'Unknown error'}\n\n${asciidocContent}`;
|
|
58
|
+
}
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
asciidocContent = `Macro error: ${error instanceof Error ? error.message : 'Unknown error'}\n\n${asciidocContent}`;
|
|
59
|
-
}
|
|
60
|
+
const htmlContent = Processor()
|
|
61
|
+
.convert(asciidocContent, {
|
|
62
|
+
safe: 'safe',
|
|
63
|
+
attributes: {
|
|
64
|
+
imagesdir: `/api/cards/${key}/a`,
|
|
65
|
+
icons: 'font',
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
.toString();
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
.toString();
|
|
70
|
+
if (raw) {
|
|
71
|
+
if (!cardDetailsResponse.metadata) {
|
|
72
|
+
throw new Error('Card has no metadata');
|
|
73
|
+
}
|
|
74
|
+
const cardType = await commands.showCmd.showResource(
|
|
75
|
+
cardDetailsResponse.metadata.cardType,
|
|
76
|
+
'cardTypes',
|
|
77
|
+
);
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
const fields = [];
|
|
80
|
+
let i = 0;
|
|
81
|
+
for (const customField of cardType.customFields) {
|
|
82
|
+
const fieldType = await commands.showCmd.showResource(
|
|
83
|
+
customField.name,
|
|
84
|
+
'fieldTypes',
|
|
85
|
+
);
|
|
86
|
+
fields.push({
|
|
87
|
+
key: customField.name,
|
|
88
|
+
visibility: 'always',
|
|
89
|
+
index: i++,
|
|
90
|
+
fieldDisplayName: fieldType.displayName,
|
|
91
|
+
fieldDescription: fieldType.description,
|
|
92
|
+
dataType: fieldType.dataType,
|
|
93
|
+
isCalculated: customField.isCalculated,
|
|
94
|
+
value: cardDetailsResponse.metadata[customField.name],
|
|
95
|
+
enumValues: fieldType.enumValues ?? [],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
status: 200,
|
|
100
|
+
data: {
|
|
101
|
+
key: cardDetailsResponse.key,
|
|
102
|
+
rank: cardDetailsResponse.metadata?.rank,
|
|
103
|
+
title: cardDetailsResponse.metadata?.title || '',
|
|
104
|
+
cardType: cardDetailsResponse.metadata?.cardType || '',
|
|
105
|
+
cardTypeDisplayName: cardDetailsResponse.metadata.cardType,
|
|
106
|
+
workflowState: '',
|
|
107
|
+
lastUpdated: cardDetailsResponse.metadata.lastUpdated,
|
|
108
|
+
createdAt: cardDetailsResponse.metadata.createdAt,
|
|
109
|
+
fields,
|
|
110
|
+
labels: cardDetailsResponse.metadata?.labels || [],
|
|
111
|
+
links: [],
|
|
112
|
+
notifications: [],
|
|
113
|
+
policyChecks: {
|
|
114
|
+
successes: [],
|
|
115
|
+
failures: [],
|
|
116
|
+
},
|
|
117
|
+
deniedOperations: {
|
|
118
|
+
transition: [],
|
|
119
|
+
move: [],
|
|
120
|
+
delete: [],
|
|
121
|
+
editField: [],
|
|
122
|
+
editContent: [],
|
|
123
|
+
},
|
|
124
|
+
rawContent: cardDetailsResponse.content || '',
|
|
125
|
+
parsedContent: htmlContent,
|
|
126
|
+
attachments: cardDetailsResponse.attachments,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
74
129
|
}
|
|
75
|
-
const cardType = await commands.showCmd.showResource(
|
|
76
|
-
cardDetailsResponse.metadata.cardType,
|
|
77
|
-
'cardTypes',
|
|
78
|
-
);
|
|
79
130
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
key: customField.name,
|
|
89
|
-
visibility: 'always',
|
|
90
|
-
index: i++,
|
|
91
|
-
fieldDisplayName: fieldType.displayName,
|
|
92
|
-
fieldDescription: fieldType.description,
|
|
93
|
-
dataType: fieldType.dataType,
|
|
94
|
-
isCalculated: customField.isCalculated,
|
|
95
|
-
value: cardDetailsResponse.metadata[customField.name],
|
|
96
|
-
enumValues: fieldType.enumValues ?? [],
|
|
97
|
-
});
|
|
131
|
+
const card = staticMode
|
|
132
|
+
? await getCardQueryResult(commands, key)
|
|
133
|
+
: await commands.calculateCmd.runQuery('card', 'localApp', {
|
|
134
|
+
cardKey: key,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (card.length !== 1) {
|
|
138
|
+
throw new Error('Query failed. Check card-query syntax');
|
|
98
139
|
}
|
|
140
|
+
|
|
99
141
|
return {
|
|
100
142
|
status: 200,
|
|
101
143
|
data: {
|
|
102
|
-
|
|
103
|
-
rank: cardDetailsResponse.metadata?.rank,
|
|
104
|
-
title: cardDetailsResponse.metadata?.title || '',
|
|
105
|
-
cardType: cardDetailsResponse.metadata?.cardType || '',
|
|
106
|
-
cardTypeDisplayName: cardDetailsResponse.metadata.cardType,
|
|
107
|
-
workflowState: '',
|
|
108
|
-
lastUpdated: cardDetailsResponse.metadata.lastUpdated,
|
|
109
|
-
fields,
|
|
110
|
-
labels: cardDetailsResponse.metadata?.labels || [],
|
|
111
|
-
links: [],
|
|
112
|
-
notifications: [],
|
|
113
|
-
policyChecks: {
|
|
114
|
-
successes: [],
|
|
115
|
-
failures: [],
|
|
116
|
-
},
|
|
117
|
-
deniedOperations: {
|
|
118
|
-
transition: [],
|
|
119
|
-
move: [],
|
|
120
|
-
delete: [],
|
|
121
|
-
editField: [],
|
|
122
|
-
editContent: [],
|
|
123
|
-
},
|
|
144
|
+
...card[0],
|
|
124
145
|
rawContent: cardDetailsResponse.content || '',
|
|
125
146
|
parsedContent: htmlContent,
|
|
126
147
|
attachments: cardDetailsResponse.attachments,
|
|
127
148
|
},
|
|
128
149
|
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const card = staticMode
|
|
132
|
-
? await getCardQueryResult(commands.project.basePath, key)
|
|
133
|
-
: await commands.calculateCmd.runQuery('card', 'localApp', {
|
|
134
|
-
cardKey: key,
|
|
135
|
-
});
|
|
136
|
-
if (card.length !== 1) {
|
|
137
|
-
throw new Error('Query failed. Check card-query syntax');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
status: 200,
|
|
142
|
-
data: {
|
|
143
|
-
...card[0],
|
|
144
|
-
rawContent: cardDetailsResponse.content || '',
|
|
145
|
-
parsedContent: htmlContent,
|
|
146
|
-
attachments: cardDetailsResponse.attachments,
|
|
147
|
-
},
|
|
148
|
-
};
|
|
150
|
+
});
|
|
149
151
|
}
|
|
150
152
|
/**
|
|
151
153
|
* Returns all cards from a tree query, flattened.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2026
|
|
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. This program is distributed in the hope that it
|
|
7
|
+
will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
8
|
+
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
9
|
+
See the GNU Affero General Public License for more details.
|
|
10
|
+
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 type { UserInfo } from '../../types.js';
|
|
15
|
+
import type { SSEMessage } from 'hono/streaming';
|
|
16
|
+
|
|
17
|
+
export interface PresenceEntry {
|
|
18
|
+
userId: string;
|
|
19
|
+
userName: string;
|
|
20
|
+
mode: 'viewing' | 'editing';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Connection {
|
|
24
|
+
user: UserInfo;
|
|
25
|
+
mode: 'viewing' | 'editing';
|
|
26
|
+
send: (message: SSEMessage) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* In-memory presence tracker for card editing/viewing.
|
|
31
|
+
* Tracks which users are currently looking at or editing each card,
|
|
32
|
+
* and notifies all connected clients via SSE when presence changes.
|
|
33
|
+
*/
|
|
34
|
+
class PresenceStore {
|
|
35
|
+
// cardKey -> connectionId -> Connection
|
|
36
|
+
private connections = new Map<string, Map<string, Connection>>();
|
|
37
|
+
private nextId = 0;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Add a user connection for a card. Returns a connection ID for later removal.
|
|
41
|
+
*/
|
|
42
|
+
add(
|
|
43
|
+
cardKey: string,
|
|
44
|
+
user: UserInfo,
|
|
45
|
+
mode: 'viewing' | 'editing',
|
|
46
|
+
send: (message: SSEMessage) => void,
|
|
47
|
+
): string {
|
|
48
|
+
const connId = String(this.nextId++);
|
|
49
|
+
|
|
50
|
+
if (!this.connections.has(cardKey)) {
|
|
51
|
+
this.connections.set(cardKey, new Map());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.connections.get(cardKey)!.set(connId, { user, mode, send });
|
|
55
|
+
this.broadcast(cardKey);
|
|
56
|
+
|
|
57
|
+
return connId;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Remove a connection and broadcast updated presence.
|
|
62
|
+
*/
|
|
63
|
+
remove(cardKey: string, connId: string): void {
|
|
64
|
+
const cardConns = this.connections.get(cardKey);
|
|
65
|
+
if (!cardConns) return;
|
|
66
|
+
|
|
67
|
+
cardConns.delete(connId);
|
|
68
|
+
|
|
69
|
+
if (cardConns.size === 0) {
|
|
70
|
+
this.connections.delete(cardKey);
|
|
71
|
+
} else {
|
|
72
|
+
this.broadcast(cardKey);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get current presence list for a card.
|
|
78
|
+
*/
|
|
79
|
+
getPresence(cardKey: string): PresenceEntry[] {
|
|
80
|
+
const cardConns = this.connections.get(cardKey);
|
|
81
|
+
if (!cardConns) return [];
|
|
82
|
+
|
|
83
|
+
// Deduplicate by userId — if a user has multiple connections,
|
|
84
|
+
// prefer the 'editing' mode
|
|
85
|
+
const byUser = new Map<string, PresenceEntry>();
|
|
86
|
+
for (const conn of cardConns.values()) {
|
|
87
|
+
const existing = byUser.get(conn.user.id);
|
|
88
|
+
if (!existing || conn.mode === 'editing') {
|
|
89
|
+
byUser.set(conn.user.id, {
|
|
90
|
+
userId: conn.user.id,
|
|
91
|
+
userName: conn.user.name,
|
|
92
|
+
mode: conn.mode,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Array.from(byUser.values());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Broadcast current presence to all connected clients for a card.
|
|
102
|
+
*/
|
|
103
|
+
private broadcast(cardKey: string): void {
|
|
104
|
+
const cardConns = this.connections.get(cardKey);
|
|
105
|
+
if (!cardConns) return;
|
|
106
|
+
|
|
107
|
+
const presence = this.getPresence(cardKey);
|
|
108
|
+
const data = JSON.stringify({ editors: presence });
|
|
109
|
+
const message: SSEMessage = { event: 'presence', data };
|
|
110
|
+
|
|
111
|
+
for (const conn of cardConns.values()) {
|
|
112
|
+
conn.send(message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Remove all connections. Intended for test cleanup.
|
|
118
|
+
*/
|
|
119
|
+
removeAll(): void {
|
|
120
|
+
this.connections.clear();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const presenceStore = new PresenceStore();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2026
|
|
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 { z } from 'zod';
|
|
15
|
+
|
|
16
|
+
const linkDirection = z.enum(['outbound', 'inbound']);
|
|
17
|
+
|
|
18
|
+
export const createLinkSchema = z.object({
|
|
19
|
+
toCard: z.string(),
|
|
20
|
+
linkType: z.string(),
|
|
21
|
+
direction: linkDirection.default('outbound'),
|
|
22
|
+
description: z.string().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const removeLinkSchema = z.object({
|
|
26
|
+
toCard: z.string(),
|
|
27
|
+
linkType: z.string(),
|
|
28
|
+
direction: linkDirection.default('outbound'),
|
|
29
|
+
description: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const updateLinkSchema = z.object({
|
|
33
|
+
toCard: z.string(),
|
|
34
|
+
linkType: z.string(),
|
|
35
|
+
direction: linkDirection,
|
|
36
|
+
description: z.string().optional(),
|
|
37
|
+
previousToCard: z.string(),
|
|
38
|
+
previousLinkType: z.string(),
|
|
39
|
+
previousDirection: linkDirection,
|
|
40
|
+
previousDescription: z.string().optional(),
|
|
41
|
+
});
|
|
@@ -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,59 +143,122 @@ 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
|
+
}
|
|
158
|
+
|
|
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();
|
|
175
169
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
.convert(asciidocContent, {
|
|
179
|
-
safe: 'safe',
|
|
180
|
-
attributes: {
|
|
181
|
-
imagesdir: `/api/cards/${key}/a`,
|
|
182
|
-
icons: 'font',
|
|
183
|
-
},
|
|
184
|
-
})
|
|
185
|
-
.toString();
|
|
186
|
-
|
|
187
|
-
return { parsedContent };
|
|
170
|
+
return { parsedContent };
|
|
171
|
+
});
|
|
188
172
|
}
|
|
189
173
|
|
|
190
174
|
export async function createLink(
|
|
191
175
|
commands: CommandManager,
|
|
192
176
|
key: string,
|
|
193
|
-
|
|
177
|
+
target: string,
|
|
194
178
|
linkType: string,
|
|
179
|
+
direction: 'outbound' | 'inbound' = 'outbound',
|
|
195
180
|
description?: string,
|
|
196
181
|
) {
|
|
197
|
-
|
|
182
|
+
// For outbound: key is source, target is destination
|
|
183
|
+
// For inbound: target is source, key is destination
|
|
184
|
+
const source = direction === 'outbound' ? key : target;
|
|
185
|
+
const destination = direction === 'outbound' ? target : key;
|
|
186
|
+
|
|
187
|
+
await commands.createCmd.createLink(
|
|
188
|
+
source,
|
|
189
|
+
destination,
|
|
190
|
+
linkType,
|
|
191
|
+
description,
|
|
192
|
+
direction,
|
|
193
|
+
);
|
|
194
|
+
|
|
198
195
|
return { message: 'Link created successfully' };
|
|
199
196
|
}
|
|
200
197
|
|
|
201
198
|
export async function removeLink(
|
|
202
199
|
commands: CommandManager,
|
|
203
200
|
key: string,
|
|
204
|
-
|
|
201
|
+
target: string,
|
|
205
202
|
linkType: string,
|
|
203
|
+
direction: 'outbound' | 'inbound' = 'outbound',
|
|
206
204
|
description?: string,
|
|
207
205
|
) {
|
|
208
|
-
|
|
206
|
+
// For outbound: key is source, target is destination
|
|
207
|
+
// For inbound: target is source, key is destination
|
|
208
|
+
const source = direction === 'outbound' ? key : target;
|
|
209
|
+
const destination = direction === 'outbound' ? target : key;
|
|
210
|
+
await commands.removeCmd.remove(
|
|
211
|
+
'link',
|
|
212
|
+
source,
|
|
213
|
+
destination,
|
|
214
|
+
linkType,
|
|
215
|
+
description,
|
|
216
|
+
);
|
|
209
217
|
return { message: 'Link removed successfully' };
|
|
210
218
|
}
|
|
211
219
|
|
|
212
|
-
export function
|
|
220
|
+
export async function updateLink(
|
|
213
221
|
commands: CommandManager,
|
|
214
222
|
key: string,
|
|
215
|
-
|
|
223
|
+
toCard: string,
|
|
224
|
+
linkType: string,
|
|
225
|
+
direction: 'outbound' | 'inbound',
|
|
226
|
+
previousToCard: string,
|
|
227
|
+
previousLinkType: string,
|
|
228
|
+
previousDirection: 'outbound' | 'inbound',
|
|
229
|
+
linkDescription?: string,
|
|
230
|
+
previousLinkDescription?: string,
|
|
216
231
|
) {
|
|
232
|
+
// For simplicity create the new link first so that duplicate-link validation runs before
|
|
233
|
+
// the old link is removed. This also handles direction changes.
|
|
234
|
+
return commands.atomic(async () => {
|
|
235
|
+
await createLink(
|
|
236
|
+
commands,
|
|
237
|
+
key,
|
|
238
|
+
toCard,
|
|
239
|
+
linkType,
|
|
240
|
+
direction,
|
|
241
|
+
linkDescription,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
await removeLink(
|
|
245
|
+
commands,
|
|
246
|
+
key,
|
|
247
|
+
previousToCard,
|
|
248
|
+
previousLinkType,
|
|
249
|
+
previousDirection,
|
|
250
|
+
previousLinkDescription,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
return { message: 'Link updated successfully' };
|
|
254
|
+
}, `Update link on ${key} to ${toCard}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function getAttachment(
|
|
258
|
+
commands: CommandManager,
|
|
259
|
+
key: string,
|
|
260
|
+
filename: string,
|
|
261
|
+
): Promise<attachmentPayload> {
|
|
217
262
|
return commands.showCmd.showAttachment(key, filename);
|
|
218
263
|
}
|
|
219
264
|
|