@cyberismo/backend 0.0.23 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/domain/cards/index.js +63 -1
  2. package/dist/domain/cards/index.js.map +1 -1
  3. package/dist/domain/cards/lib.js +3 -0
  4. package/dist/domain/cards/lib.js.map +1 -1
  5. package/dist/domain/cards/schema.d.ts +8 -0
  6. package/dist/domain/cards/schema.js +7 -0
  7. package/dist/domain/cards/schema.js.map +1 -1
  8. package/dist/domain/cards/service.d.ts +2 -0
  9. package/dist/domain/cards/service.js +16 -0
  10. package/dist/domain/cards/service.js.map +1 -1
  11. package/dist/domain/project/service.js +1 -1
  12. package/dist/domain/project/service.js.map +1 -1
  13. package/dist/domain/resources/index.js +63 -1
  14. package/dist/domain/resources/index.js.map +1 -1
  15. package/dist/domain/resources/schema.d.ts +9 -0
  16. package/dist/domain/resources/schema.js +8 -1
  17. package/dist/domain/resources/schema.js.map +1 -1
  18. package/dist/domain/resources/service.d.ts +12 -0
  19. package/dist/domain/resources/service.js +47 -4
  20. package/dist/domain/resources/service.js.map +1 -1
  21. package/dist/public/THIRD-PARTY.txt +3594 -1626
  22. package/dist/public/assets/arc-DFTvTCxD.js +1 -0
  23. package/dist/public/assets/architecture-YZFGNWBL-DsWVZJri.js +1 -0
  24. package/dist/public/assets/architectureDiagram-Q4EWVU46-AN0fWZIG.js +36 -0
  25. package/dist/public/assets/array-xS8TccZC.js +1 -0
  26. package/dist/public/assets/blockDiagram-DXYQGD6D-RrIidZT3.js +132 -0
  27. package/dist/public/assets/c4Diagram-AHTNJAMY-CW7AUKhY.js +10 -0
  28. package/dist/public/assets/channel-BxffgrNT.js +1 -0
  29. package/dist/public/assets/chunk-2KRD3SAO-D5XH6bj9.js +1 -0
  30. package/dist/public/assets/chunk-336JU56O-CVDEj5x8.js +2 -0
  31. package/dist/public/assets/chunk-426QAEUC-CIWkCWTf.js +1 -0
  32. package/dist/public/assets/chunk-4BX2VUAB-O8dxzEpn.js +1 -0
  33. package/dist/public/assets/chunk-4TB4RGXK-Bt4fWDlh.js +206 -0
  34. package/dist/public/assets/chunk-55IACEB6-R-yr7oHq.js +1 -0
  35. package/dist/public/assets/chunk-5FUZZQ4R-D7L4hZzZ.js +62 -0
  36. package/dist/public/assets/chunk-5PVQY5BW-D46cRkay.js +2 -0
  37. package/dist/public/assets/chunk-67CJDMHE-1fLguPDq.js +1 -0
  38. package/dist/public/assets/chunk-7N4EOEYR-BPbEiVZr.js +1 -0
  39. package/dist/public/assets/chunk-AA7GKIK3-DZOqN73n.js +1 -0
  40. package/dist/public/assets/chunk-BSJP7CBP-BQPMRa-Q.js +1 -0
  41. package/dist/public/assets/chunk-CFjPhJqf.js +1 -0
  42. package/dist/public/assets/chunk-CIAEETIT-CoKBG93U.js +1 -0
  43. package/dist/public/assets/chunk-EDXVE4YY-BxmMvdBY.js +1 -0
  44. package/dist/public/assets/chunk-ENJZ2VHE-BrjxYY_T.js +10 -0
  45. package/dist/public/assets/chunk-FMBD7UC4-DChB7ne2.js +15 -0
  46. package/dist/public/assets/chunk-FOC6F5B3-DyK4SoM2.js +1 -0
  47. package/dist/public/assets/chunk-ICPOFSXX-6-ABzkfw.js +122 -0
  48. package/dist/public/assets/chunk-K5T4RW27-432kjUXO.js +94 -0
  49. package/dist/public/assets/chunk-KGLVRYIC-ChpiuJys.js +1 -0
  50. package/dist/public/assets/chunk-LIHQZDEY-C65uflEj.js +1 -0
  51. package/dist/public/assets/chunk-ORNJ4GCN-DPvOqXKg.js +1 -0
  52. package/dist/public/assets/chunk-OYMX7WX6-BecMUsrL.js +231 -0
  53. package/dist/public/assets/chunk-QZHKN3VN-d1i_Lm1t.js +1 -0
  54. package/dist/public/assets/chunk-U2HBQHQK-C6yvwOAo.js +70 -0
  55. package/dist/public/assets/chunk-X2U36JSP-D1C3tOED.js +1 -0
  56. package/dist/public/assets/chunk-XPW4576I-BH37LuuF.js +32 -0
  57. package/dist/public/assets/chunk-YZCP3GAM-D83gHdmx.js +1 -0
  58. package/dist/public/assets/chunk-ZZ45TVLE-DrCjtkdu.js +1 -0
  59. package/dist/public/assets/classDiagram-6PBFFD2Q-CRYacdjd.js +1 -0
  60. package/dist/public/assets/classDiagram-v2-HSJHXN6E-qOJXC_A9.js +1 -0
  61. package/dist/public/assets/clone-Bno0nirE.js +1 -0
  62. package/dist/public/assets/colors-DZGTowqM.js +1 -0
  63. package/dist/public/assets/cose-bilkent-S5V4N54A-BdSMsFO7.js +1 -0
  64. package/dist/public/assets/cytoscape.esm-DpmcErfs.js +321 -0
  65. package/dist/public/assets/dagre-D0KOcyEK.js +1 -0
  66. package/dist/public/assets/dagre-KV5264BT-C_l1Bck_.js +4 -0
  67. package/dist/public/assets/defaultLocale-B6dPnyNg.js +1 -0
  68. package/dist/public/assets/diagram-5BDNPKRD-D6FxiIv1.js +10 -0
  69. package/dist/public/assets/diagram-G4DWMVQ6-COGBv7tQ.js +24 -0
  70. package/dist/public/assets/diagram-MMDJMWI5-U6E9w334.js +43 -0
  71. package/dist/public/assets/diagram-TYMM5635-Tvfdnlo4.js +24 -0
  72. package/dist/public/assets/dist-C6vvxvVV.js +1 -0
  73. package/dist/public/assets/erDiagram-SMLLAGMA-VcPXkmy9.js +85 -0
  74. package/dist/public/assets/flatten-BKeNCJRx.js +1 -0
  75. package/dist/public/assets/flowDiagram-DWJPFMVM-PRQdlypB.js +162 -0
  76. package/dist/public/assets/ganttDiagram-T4ZO3ILL-CSVaTciZ.js +292 -0
  77. package/dist/public/assets/gitGraph-7Q5UKJZL-C6GVmHC4.js +1 -0
  78. package/dist/public/assets/gitGraphDiagram-UUTBAWPF-DMa-Gcy1.js +106 -0
  79. package/dist/public/assets/graphlib-BYojtb6k.js +1 -0
  80. package/dist/public/assets/identity-nF-8qK2-.js +1 -0
  81. package/dist/public/assets/index-oVbUFngg.js +737 -0
  82. package/dist/public/assets/info-OMHHGYJF-CjsmQeYR.js +1 -0
  83. package/dist/public/assets/infoDiagram-42DDH7IO-COL_Na4w.js +2 -0
  84. package/dist/public/assets/init-B9nbfZCT.js +1 -0
  85. package/dist/public/assets/isEmpty-Di-NpihJ.js +1 -0
  86. package/dist/public/assets/ishikawaDiagram-UXIWVN3A-B3HoSFaq.js +70 -0
  87. package/dist/public/assets/journeyDiagram-VCZTEJTY-kH30WNF3.js +139 -0
  88. package/dist/public/assets/kanban-definition-6JOO6SKY-RdjEsejt.js +89 -0
  89. package/dist/public/assets/katex-Bfn1OZEl.js +257 -0
  90. package/dist/public/assets/line-DrKJYWGi.js +1 -0
  91. package/dist/public/assets/linear-C8MDrvxz.js +1 -0
  92. package/dist/public/assets/mermaid-parser.core-BeZK8g2G.js +4 -0
  93. package/dist/public/assets/mindmap-definition-QFDTVHPH-CNWWI4MF.js +96 -0
  94. package/dist/public/assets/ordinal-BXaEVJbT.js +1 -0
  95. package/dist/public/assets/packet-4T2RLAQJ-Dq0Z9G3r.js +1 -0
  96. package/dist/public/assets/path-C7Bv0Qdk.js +1 -0
  97. package/dist/public/assets/pie-ZZUOXDRM-Coeb8Atk.js +1 -0
  98. package/dist/public/assets/pieDiagram-DEJITSTG-CrDW-30P.js +30 -0
  99. package/dist/public/assets/quadrantDiagram-34T5L4WZ-BoDA5WQ-.js +7 -0
  100. package/dist/public/assets/radar-PYXPWWZC-BF2NN2TG.js +1 -0
  101. package/dist/public/assets/range-DuD7Go1R.js +1 -0
  102. package/dist/public/assets/reduce-5tLTMRkg.js +1 -0
  103. package/dist/public/assets/requirementDiagram-MS252O5E-BnG7epLH.js +84 -0
  104. package/dist/public/assets/rough.esm-DE7XMpOC.js +1 -0
  105. package/dist/public/assets/sankeyDiagram-XADWPNL6-oyTFoxhB.js +10 -0
  106. package/dist/public/assets/sequenceDiagram-FGHM5R23-hwG2jT_2.js +157 -0
  107. package/dist/public/assets/src-ps-3oTnY.js +1 -0
  108. package/dist/public/assets/stateDiagram-FHFEXIEX-BiYqw63j.js +1 -0
  109. package/dist/public/assets/stateDiagram-v2-QKLJ7IA2-CggSrcDh.js +1 -0
  110. package/dist/public/assets/time-Bgnk7ODM.js +1 -0
  111. package/dist/public/assets/timeline-definition-GMOUNBTQ-9MnBod43.js +120 -0
  112. package/dist/public/assets/treeView-SZITEDCU-IRdgIoRV.js +1 -0
  113. package/dist/public/assets/treemap-DK8fitek.js +1 -0
  114. package/dist/public/assets/treemap-W4RFUUIX-C-JBwyWW.js +1 -0
  115. package/dist/public/assets/vennDiagram-DHZGUBPP-sEA6JNL-.js +34 -0
  116. package/dist/public/assets/wardley-RL74JXVD-DdU04Q7E.js +1 -0
  117. package/dist/public/assets/wardleyDiagram-NUSXRM2D-Dws321pY.js +20 -0
  118. package/dist/public/assets/xychartDiagram-5P7HB3ND-BcqyvmmW.js +7 -0
  119. package/dist/public/index.html +30 -1
  120. package/package.json +7 -7
  121. package/src/domain/cards/index.ts +73 -0
  122. package/src/domain/cards/lib.ts +4 -0
  123. package/src/domain/cards/schema.ts +9 -0
  124. package/src/domain/cards/service.ts +26 -1
  125. package/src/domain/project/service.ts +1 -1
  126. package/src/domain/resources/index.ts +74 -0
  127. package/src/domain/resources/schema.ts +14 -0
  128. package/src/domain/resources/service.ts +50 -4
  129. package/dist/public/assets/index-Cdn_jRWy.js +0 -720
@@ -11,7 +11,36 @@
11
11
  name="msapplication-TileImage"
12
12
  content="/cropped-favicon-270x270.png"
13
13
  />
14
- <script type="module" crossorigin src="/assets/index-Cdn_jRWy.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-oVbUFngg.js"></script>
15
+ <link rel="modulepreload" crossorigin href="/assets/chunk-CFjPhJqf.js">
16
+ <link rel="modulepreload" crossorigin href="/assets/src-ps-3oTnY.js">
17
+ <link rel="modulepreload" crossorigin href="/assets/chunk-ICPOFSXX-6-ABzkfw.js">
18
+ <link rel="modulepreload" crossorigin href="/assets/dist-C6vvxvVV.js">
19
+ <link rel="modulepreload" crossorigin href="/assets/identity-nF-8qK2-.js">
20
+ <link rel="modulepreload" crossorigin href="/assets/chunk-5PVQY5BW-D46cRkay.js">
21
+ <link rel="modulepreload" crossorigin href="/assets/chunk-U2HBQHQK-C6yvwOAo.js">
22
+ <link rel="modulepreload" crossorigin href="/assets/defaultLocale-B6dPnyNg.js">
23
+ <link rel="modulepreload" crossorigin href="/assets/init-B9nbfZCT.js">
24
+ <link rel="modulepreload" crossorigin href="/assets/linear-C8MDrvxz.js">
25
+ <link rel="modulepreload" crossorigin href="/assets/time-Bgnk7ODM.js">
26
+ <link rel="modulepreload" crossorigin href="/assets/range-DuD7Go1R.js">
27
+ <link rel="modulepreload" crossorigin href="/assets/treemap-DK8fitek.js">
28
+ <link rel="modulepreload" crossorigin href="/assets/path-C7Bv0Qdk.js">
29
+ <link rel="modulepreload" crossorigin href="/assets/colors-DZGTowqM.js">
30
+ <link rel="modulepreload" crossorigin href="/assets/ordinal-BXaEVJbT.js">
31
+ <link rel="modulepreload" crossorigin href="/assets/arc-DFTvTCxD.js">
32
+ <link rel="modulepreload" crossorigin href="/assets/array-xS8TccZC.js">
33
+ <link rel="modulepreload" crossorigin href="/assets/line-DrKJYWGi.js">
34
+ <link rel="modulepreload" crossorigin href="/assets/isEmpty-Di-NpihJ.js">
35
+ <link rel="modulepreload" crossorigin href="/assets/chunk-X2U36JSP-D1C3tOED.js">
36
+ <link rel="modulepreload" crossorigin href="/assets/chunk-ZZ45TVLE-DrCjtkdu.js">
37
+ <link rel="modulepreload" crossorigin href="/assets/rough.esm-DE7XMpOC.js">
38
+ <link rel="modulepreload" crossorigin href="/assets/chunk-5FUZZQ4R-D7L4hZzZ.js">
39
+ <link rel="modulepreload" crossorigin href="/assets/chunk-BSJP7CBP-BQPMRa-Q.js">
40
+ <link rel="modulepreload" crossorigin href="/assets/chunk-ENJZ2VHE-BrjxYY_T.js">
41
+ <link rel="modulepreload" crossorigin href="/assets/chunk-336JU56O-CVDEj5x8.js">
42
+ <link rel="modulepreload" crossorigin href="/assets/chunk-426QAEUC-CIWkCWTf.js">
43
+ <link rel="modulepreload" crossorigin href="/assets/chunk-XPW4576I-BH37LuuF.js">
15
44
  <link rel="stylesheet" crossorigin href="/assets/index-ypsafPwV.css">
16
45
  </head>
17
46
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberismo/backend",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "Express backend for Cyberismo",
5
5
  "main": "dist/index.js",
6
6
  "keywords": [],
@@ -14,17 +14,17 @@
14
14
  "bugs": "https://github.com/CyberismoCom/cyberismo/issues",
15
15
  "dependencies": {
16
16
  "@asciidoctor/core": "^3.0.4",
17
- "@modelcontextprotocol/sdk": "^1.27.1",
18
- "@hono/node-server": "^1.19.10",
17
+ "@modelcontextprotocol/sdk": "^1.29.0",
18
+ "@hono/node-server": "^1.19.13",
19
19
  "@hono/zod-validator": "^0.7.6",
20
20
  "@types/mime-types": "^3.0.1",
21
21
  "dotenv": "^17.3.1",
22
- "hono": "^4.12.7",
23
- "jose": "^6.1.3",
22
+ "hono": "^4.12.14",
23
+ "jose": "^6.2.3",
24
24
  "mime-types": "^3.0.2",
25
25
  "zod": "^4.3.6",
26
- "@cyberismo/mcp": "0.0.23",
27
- "@cyberismo/data-handler": "0.0.23"
26
+ "@cyberismo/data-handler": "0.0.24",
27
+ "@cyberismo/mcp": "0.0.24"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@cyberismo/app": "0.0.2"
@@ -26,6 +26,8 @@ import {
26
26
  createLinkSchema,
27
27
  removeLinkSchema,
28
28
  updateLinkSchema,
29
+ exportCardPdfSchema,
30
+ type ExportCardPdfRequestBody,
29
31
  } from './schema.js';
30
32
 
31
33
  const router = new Hono();
@@ -58,6 +60,77 @@ router.get('/', requireRole(UserRole.Reader), async (c) => {
58
60
  }
59
61
  });
60
62
 
63
+ /**
64
+ * @swagger
65
+ * /api/cards/export-pdf:
66
+ * post:
67
+ * summary: Export a card as a PDF
68
+ * description: Exports the specified card as a PDF
69
+ * parameters:
70
+ * - name: key
71
+ * in: path
72
+ * required: true
73
+ * description: Card key (string)
74
+ * requestBody:
75
+ * content:
76
+ * application/json:
77
+ * schema:
78
+ * type: object
79
+ * properties:
80
+ * title:
81
+ * type: string
82
+ * description: Title of the exported PDF
83
+ * name:
84
+ * type: string
85
+ * description: Name of the exported PDF
86
+ * exportChildCards:
87
+ * type: boolean
88
+ * description: Whether to export child cards
89
+ * version:
90
+ * type: string
91
+ * description: Version of the exported PDF (optional)
92
+ * responses:
93
+ * 200:
94
+ * description: Card exported successfully
95
+ * 400:
96
+ * description: Missing or invalid parameters.
97
+ * 500:
98
+ * description: project_path not set
99
+ */
100
+ router.post(
101
+ '/export-pdf',
102
+ requireRole(UserRole.Reader),
103
+ zValidator('json', exportCardPdfSchema),
104
+ async (c) => {
105
+ const commands = c.get('commands');
106
+ const body = (await c.req.json()) as ExportCardPdfRequestBody;
107
+ try {
108
+ const result = await cardService.exportCard(commands, {
109
+ cardKey: body.cardKey,
110
+ title: body.title,
111
+ name: body.name,
112
+ recursive: body.exportChildCards,
113
+ version: body.version,
114
+ });
115
+ return new Response(result, {
116
+ status: 200,
117
+ headers: {
118
+ 'Content-Type': 'application/pdf',
119
+ 'Cache-Control': 'no-store',
120
+ },
121
+ });
122
+ } catch (error) {
123
+ return c.json(
124
+ {
125
+ error:
126
+ error instanceof Error ? error.message : 'Failed to export card',
127
+ },
128
+ 500,
129
+ );
130
+ }
131
+ },
132
+ );
133
+
61
134
  /**
62
135
  * @swagger
63
136
  * /api/cards/{key}:
@@ -14,6 +14,7 @@
14
14
  import Processor from '@asciidoctor/core';
15
15
  import type { Card } from '@cyberismo/data-handler/interfaces/project-interfaces';
16
16
  import { type CommandManager, evaluateMacros } from '@cyberismo/data-handler';
17
+ import { preprocessMermaidBlocksForHtml } from '@cyberismo/data-handler/utils/mermaid-renderer';
17
18
  import { getCardQueryResult } from '../../export.js';
18
19
  import type { TreeOptions } from '../../types.js';
19
20
  import type { QueryResult } from '@cyberismo/data-handler/types/queries';
@@ -57,6 +58,9 @@ export async function getCardDetails(
57
58
  asciidocContent = `Macro error: ${error instanceof Error ? error.message : 'Unknown error'}\n\n${asciidocContent}`;
58
59
  }
59
60
 
61
+ // Convert [mermaid] AsciiDoc blocks to passthrough HTML before asciidoctor processes them
62
+ asciidocContent = preprocessMermaidBlocksForHtml(asciidocContent);
63
+
60
64
  const htmlContent = Processor()
61
65
  .convert(asciidocContent, {
62
66
  safe: 'safe',
@@ -39,3 +39,12 @@ export const updateLinkSchema = z.object({
39
39
  previousDirection: linkDirection,
40
40
  previousDescription: z.string().optional(),
41
41
  });
42
+ export const exportCardPdfSchema = z.object({
43
+ title: z.string().min(1),
44
+ name: z.string().min(1),
45
+ cardKey: z.string(),
46
+ exportChildCards: z.boolean(),
47
+ version: z.string().min(1).optional(),
48
+ });
49
+
50
+ export type ExportCardPdfRequestBody = z.infer<typeof exportCardPdfSchema>;
@@ -12,9 +12,13 @@
12
12
  */
13
13
 
14
14
  import Processor from '@asciidoctor/core';
15
- import { type MetadataContent } from '@cyberismo/data-handler/interfaces/project-interfaces';
15
+ import type {
16
+ ExportPdfOptions,
17
+ MetadataContent,
18
+ } from '@cyberismo/data-handler/interfaces/project-interfaces';
16
19
  import type { attachmentPayload } from '@cyberismo/data-handler/interfaces/request-status-interfaces';
17
20
  import { type CommandManager, evaluateMacros } from '@cyberismo/data-handler';
21
+ import { preprocessMermaidBlocksForHtml } from '@cyberismo/data-handler/utils/mermaid-renderer';
18
22
  import { allCards } from './lib.js';
19
23
  import type { TreeOptions } from '../../types.js';
20
24
 
@@ -120,11 +124,19 @@ export async function uploadAttachments(
120
124
  };
121
125
  }
122
126
 
127
+ function validateAttachmentFileName(filename: string): void {
128
+ const decoded = decodeURIComponent(filename);
129
+ if (/(^|[/\\])\.\.([/\\]|$)/.test(decoded)) {
130
+ throw new Error('Invalid attachment filename');
131
+ }
132
+ }
133
+
123
134
  export async function removeAttachment(
124
135
  commands: CommandManager,
125
136
  key: string,
126
137
  filename: string,
127
138
  ) {
139
+ validateAttachmentFileName(filename);
128
140
  await commands.removeCmd.remove('attachment', key, filename);
129
141
  return { message: 'Attachment removed successfully' };
130
142
  }
@@ -134,6 +146,7 @@ export async function openAttachment(
134
146
  key: string,
135
147
  filename: string,
136
148
  ) {
149
+ validateAttachmentFileName(filename);
137
150
  await commands.showCmd.openAttachment(key, filename);
138
151
  return { message: 'Attachment opened successfully' };
139
152
  }
@@ -156,6 +169,9 @@ export async function parseContent(
156
169
  asciidocContent = `Macro error: ${error instanceof Error ? error.message : 'Unknown error'}\n\n${content}`;
157
170
  }
158
171
 
172
+ // Convert [mermaid] AsciiDoc blocks to passthrough HTML before asciidoctor processes them
173
+ asciidocContent = preprocessMermaidBlocksForHtml(asciidocContent);
174
+
159
175
  const processor = Processor();
160
176
  const parsedContent = processor
161
177
  .convert(asciidocContent, {
@@ -259,6 +275,7 @@ export async function getAttachment(
259
275
  key: string,
260
276
  filename: string,
261
277
  ): Promise<attachmentPayload> {
278
+ validateAttachmentFileName(filename);
262
279
  return commands.showCmd.showAttachment(key, filename);
263
280
  }
264
281
 
@@ -298,3 +315,11 @@ export async function findRelevantAttachments(
298
315
  attachment: attachment.fileName,
299
316
  }));
300
317
  }
318
+
319
+ export async function exportCard(
320
+ commands: CommandManager,
321
+ options: ExportPdfOptions,
322
+ ): Promise<Buffer> {
323
+ const result = await commands.exportCmd.exportPdfBuffer(options);
324
+ return result;
325
+ }
@@ -57,7 +57,7 @@ export async function getProject(
57
57
  const project = await commands.showCmd.showProject();
58
58
  const modules = await commands.showCmd.showModules();
59
59
  const moduleDetails = await Promise.all(
60
- modules.map((mod) => toModuleInfo(commands, mod)),
60
+ modules.map((mod) => toModuleInfo(commands, mod.name)),
61
61
  );
62
62
 
63
63
  return {
@@ -20,6 +20,8 @@ import { zValidator } from '../../middleware/zvalidator.js';
20
20
  import {
21
21
  validateResourceParamsSchema,
22
22
  updateOperationBodySchema,
23
+ workflowGraphParamsSchema,
24
+ workflowGraphQuerySchema,
23
25
  } from './schema.js';
24
26
  import { requireRole } from '../../middleware/auth.js';
25
27
 
@@ -132,6 +134,78 @@ router.delete(
132
134
  },
133
135
  );
134
136
 
137
+ /**
138
+ * @swagger
139
+ * /api/resources/{prefix}/workflows/{identifier}/graph:
140
+ * get:
141
+ * summary: Render the state-machine graph for a workflow
142
+ * description: Returns a base64-encoded sanitized SVG of the workflow's
143
+ * state-machine diagram, rendered with the built-in
144
+ * workflow graph model and view. When the optional `card`
145
+ * query parameter is provided, the diagram highlights
146
+ * that card's current workflowState.
147
+ * parameters:
148
+ * - in: path
149
+ * name: prefix
150
+ * required: true
151
+ * schema:
152
+ * type: string
153
+ * - in: path
154
+ * name: identifier
155
+ * required: true
156
+ * schema:
157
+ * type: string
158
+ * - in: query
159
+ * name: card
160
+ * required: false
161
+ * schema:
162
+ * type: string
163
+ * description: Card key whose current workflowState should be
164
+ * highlighted in the rendered diagram.
165
+ * responses:
166
+ * 200:
167
+ * description: Rendered diagram
168
+ * content:
169
+ * application/json:
170
+ * schema:
171
+ * type: object
172
+ * properties:
173
+ * svg:
174
+ * type: string
175
+ * description: Base64-encoded SVG document.
176
+ * 400:
177
+ * description: Invalid path parameters
178
+ * 404:
179
+ * description: Workflow or card not found
180
+ * 500:
181
+ * description: Server error
182
+ */
183
+ router.get(
184
+ '/:prefix/workflows/:identifier/graph',
185
+ requireRole(UserRole.Reader),
186
+ zValidator('param', workflowGraphParamsSchema),
187
+ zValidator('query', workflowGraphQuerySchema),
188
+ async (c) => {
189
+ const commands = c.get('commands');
190
+ const { prefix, identifier } = c.req.valid('param');
191
+ const { card } = c.req.valid('query');
192
+ const workflowName = `${prefix}/workflows/${identifier}`;
193
+ try {
194
+ const svg = await resourceService.getWorkflowGraph(
195
+ commands,
196
+ workflowName,
197
+ card,
198
+ );
199
+ return c.json({ svg });
200
+ } catch (error) {
201
+ if (error instanceof Error && error.message.includes('not found')) {
202
+ return c.json({ error: error.message }, 404);
203
+ }
204
+ throw error;
205
+ }
206
+ },
207
+ );
208
+
135
209
  router.post(
136
210
  '/:prefix/:type/:identifier/operation',
137
211
  requireRole(UserRole.Admin),
@@ -1,9 +1,23 @@
1
1
  import { z } from 'zod';
2
2
  import {
3
+ identifierSchema,
3
4
  resourceParamsSchema,
4
5
  resourceTypes,
5
6
  } from '../../common/validationSchemas.js';
6
7
 
8
+ export const workflowGraphParamsSchema = z.object({
9
+ prefix: z.string(),
10
+ identifier: identifierSchema,
11
+ });
12
+
13
+ export type WorkflowGraphParams = z.infer<typeof workflowGraphParamsSchema>;
14
+
15
+ export const workflowGraphQuerySchema = z.object({
16
+ card: z.string().optional(),
17
+ });
18
+
19
+ export type WorkflowGraphQuery = z.infer<typeof workflowGraphQuerySchema>;
20
+
7
21
  export const resourceFileParamsSchema = resourceParamsSchema.extend({
8
22
  file: z.string(),
9
23
  });
@@ -42,14 +42,14 @@ const resourceTypes: ResourceFolderType[] = [
42
42
 
43
43
  async function getModules(commands: CommandManager) {
44
44
  try {
45
- const moduleNames = await commands.showCmd.showModules();
45
+ const modules = await commands.showCmd.showModules();
46
46
  return Promise.all(
47
- moduleNames.map(async (moduleName) => {
47
+ modules.map(async (mod) => {
48
48
  try {
49
- const module = await commands.showCmd.showModule(moduleName);
49
+ const module = await commands.showCmd.showModule(mod.name);
50
50
  return { name: module.name, cardKeyPrefix: module.cardKeyPrefix };
51
51
  } catch {
52
- return { name: moduleName, cardKeyPrefix: moduleName };
52
+ return { name: mod.name, cardKeyPrefix: mod.name };
53
53
  }
54
54
  }),
55
55
  );
@@ -467,6 +467,52 @@ export async function validateResource(
467
467
  };
468
468
  }
469
469
 
470
+ /**
471
+ * Renders the built-in state-machine graph for a single workflow.
472
+ * When `cardKey` is provided, the card's current workflowState is
473
+ * highlighted in the diagram. The workflow lookup, card lookup and
474
+ * graph rendering all run inside the same consistency window so the
475
+ * highlighted state matches the diagram even if the card is being
476
+ * transitioned concurrently.
477
+ * @returns base64-encoded sanitized SVG.
478
+ * @throws Error with 'not found' in the message when the workflow or
479
+ * card cannot be resolved.
480
+ */
481
+ export async function getWorkflowGraph(
482
+ commands: CommandManager,
483
+ workflowName: string,
484
+ cardKey?: string,
485
+ ): Promise<string> {
486
+ return commands.consistent(async () => {
487
+ try {
488
+ await commands.showCmd.showResource(workflowName, 'workflows');
489
+ } catch (error) {
490
+ if (error instanceof Error && error.message.includes('does not exist')) {
491
+ throw new Error(`Workflow '${workflowName}' not found`, {
492
+ cause: error,
493
+ });
494
+ }
495
+ throw error;
496
+ }
497
+ let currentState: string | undefined;
498
+ if (cardKey) {
499
+ let card;
500
+ try {
501
+ card = await commands.showCmd.showCardDetails(cardKey);
502
+ } catch (error) {
503
+ throw new Error(`Card '${cardKey}' not found`, { cause: error });
504
+ }
505
+ if (!card?.metadata) {
506
+ throw new Error(`Card '${cardKey}' not found`);
507
+ }
508
+ currentState = card.metadata.workflowState;
509
+ }
510
+ return commands.calculateCmd.runWorkflowGraph(workflowName, {
511
+ currentState,
512
+ });
513
+ });
514
+ }
515
+
470
516
  /**
471
517
  * Perform an updateOperation on a resource key.
472
518
  * This delegates to data-handler Update.applyResourceOperation.