@cyberismo/backend 0.0.27 → 1.1.0

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 (268) hide show
  1. package/dist/app.d.ts +1 -1
  2. package/dist/app.js +10 -4
  3. package/dist/app.js.map +1 -1
  4. package/dist/auth/index.d.ts +1 -1
  5. package/dist/auth/index.js +1 -1
  6. package/dist/auth/index.js.map +1 -1
  7. package/dist/auth/keycloak.js +3 -0
  8. package/dist/auth/keycloak.js.map +1 -1
  9. package/dist/auth/mock.d.ts +8 -1
  10. package/dist/auth/mock.js +51 -2
  11. package/dist/auth/mock.js.map +1 -1
  12. package/dist/common/validationSchemas.d.ts +3 -1
  13. package/dist/common/validationSchemas.js +1 -0
  14. package/dist/common/validationSchemas.js.map +1 -1
  15. package/dist/domain/calculations/index.js +1 -1
  16. package/dist/domain/calculations/index.js.map +1 -1
  17. package/dist/domain/cardTypes/service.d.ts +1 -1
  18. package/dist/domain/cardTypes/service.js +26 -4
  19. package/dist/domain/cardTypes/service.js.map +1 -1
  20. package/dist/domain/cards/lib.js +7 -5
  21. package/dist/domain/cards/lib.js.map +1 -1
  22. package/dist/domain/cards/service.js +3 -6
  23. package/dist/domain/cards/service.js.map +1 -1
  24. package/dist/domain/project/index.js +32 -1
  25. package/dist/domain/project/index.js.map +1 -1
  26. package/dist/domain/project/schema.d.ts +11 -0
  27. package/dist/domain/project/schema.js +20 -0
  28. package/dist/domain/project/schema.js.map +1 -1
  29. package/dist/domain/project/service.d.ts +23 -1
  30. package/dist/domain/project/service.js +52 -1
  31. package/dist/domain/project/service.js.map +1 -1
  32. package/dist/domain/projects/index.d.ts +1 -1
  33. package/dist/domain/projects/index.js +66 -3
  34. package/dist/domain/projects/index.js.map +1 -1
  35. package/dist/domain/projects/schema.d.ts +22 -0
  36. package/dist/domain/projects/schema.js +40 -0
  37. package/dist/domain/projects/schema.js.map +1 -0
  38. package/dist/domain/projects/service.d.ts +37 -0
  39. package/dist/domain/projects/service.js +53 -0
  40. package/dist/domain/projects/service.js.map +1 -0
  41. package/dist/domain/resources/index.js +1 -1
  42. package/dist/domain/resources/index.js.map +1 -1
  43. package/dist/domain/resources/schema.d.ts +4 -1
  44. package/dist/domain/resources/schema.js +4 -2
  45. package/dist/domain/resources/schema.js.map +1 -1
  46. package/dist/domain/resources/service.d.ts +1 -1
  47. package/dist/domain/resources/service.js +7 -2
  48. package/dist/domain/resources/service.js.map +1 -1
  49. package/dist/index.d.ts +1 -1
  50. package/dist/index.js +5 -4
  51. package/dist/index.js.map +1 -1
  52. package/dist/main.js +0 -4
  53. package/dist/main.js.map +1 -1
  54. package/dist/middleware/auth.d.ts +5 -3
  55. package/dist/middleware/auth.js +7 -4
  56. package/dist/middleware/auth.js.map +1 -1
  57. package/dist/middleware/zvalidator.d.ts +1 -1
  58. package/dist/project-registry.d.ts +2 -1
  59. package/dist/project-registry.js +4 -2
  60. package/dist/project-registry.js.map +1 -1
  61. package/dist/public/THIRD-PARTY.txt +932 -1022
  62. package/dist/public/assets/abnfDiagram-VRR7QNED-Djr4KJ5E.js +1 -0
  63. package/dist/public/assets/arc-DrR4k_H-.js +1 -0
  64. package/dist/public/assets/architecture-TIHT7OUA-XL6N-4o9.js +1 -0
  65. package/dist/public/assets/architectureDiagram-ZJ3FMSHR-DsMU2Uwa.js +36 -0
  66. package/dist/public/assets/blockDiagram-677ZJIJ3-DRBmQUQJ.js +132 -0
  67. package/dist/public/assets/c4Diagram-LMCZKHZV-E0rM02LD.js +10 -0
  68. package/dist/public/assets/channel-D5fnImJy.js +1 -0
  69. package/dist/public/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  70. package/dist/public/assets/chunk-32BRIVSS-BnDY0aSV.js +1 -0
  71. package/dist/public/assets/chunk-52WLFC77-N5YugKaZ.js +10 -0
  72. package/dist/public/assets/chunk-5FCAYU7R-I3_t_Ic_.js +1 -0
  73. package/dist/public/assets/chunk-5HE753X5-Dxz40rzH.js +1 -0
  74. package/dist/public/assets/chunk-5JV3BV7I-CNwCKRLH.js +1 -0
  75. package/dist/public/assets/chunk-5TONJI2A-ezWQ_Z2j.js +2 -0
  76. package/dist/public/assets/{chunk-FMBD7UC4-Cgl-u-NC.js → chunk-5VM5RSS4-ZNzvKenW.js} +1 -1
  77. package/dist/public/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
  78. package/dist/public/assets/chunk-BIQX33UG-DS132-R_.js +1 -0
  79. package/dist/public/assets/chunk-C7G6YPKG-BN-cNjoj.js +1 -0
  80. package/dist/public/assets/chunk-CQNSW5MT-D8Mn1I73.js +1 -0
  81. package/dist/public/assets/chunk-CYSBUYHQ-Dc64Dwdo.js +1 -0
  82. package/dist/public/assets/chunk-EMLP6XTP-aIK0V8GX.js +1 -0
  83. package/dist/public/assets/chunk-EX3LRPZG-Db0XYyxy.js +231 -0
  84. package/dist/public/assets/chunk-FWX5IMBZ-BF_P2-Do.js +2 -0
  85. package/dist/public/assets/chunk-HOUHSVGY-L35GPy9G.js +1 -0
  86. package/dist/public/assets/chunk-ICXQ74PX-Bb8zaHde.js +2 -0
  87. package/dist/public/assets/chunk-JG7HCLWE-BtAXK3iQ.js +2 -0
  88. package/dist/public/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  89. package/dist/public/assets/chunk-KEIR6QF5-DEvma7oi.js +161 -0
  90. package/dist/public/assets/chunk-MOJQB5TN-D1shKbMs.js +88 -0
  91. package/dist/public/assets/chunk-MOZMSUNE-5FIqIBsY.js +1 -0
  92. package/dist/public/assets/chunk-OGEWGWER-BVIFy9xz.js +1 -0
  93. package/dist/public/assets/chunk-OSBZ3O6U-Cfd7kf1R.js +1 -0
  94. package/dist/public/assets/chunk-PUDLZKDR-Db3FSNR9.js +156 -0
  95. package/dist/public/assets/chunk-Q4XR5HBZ-BuKFRUzR.js +70 -0
  96. package/dist/public/assets/chunk-QBLGF6JB-DUGZXFh-.js +1 -0
  97. package/dist/public/assets/chunk-R7FJI6CG-D7mUd-qN.js +1 -0
  98. package/dist/public/assets/chunk-RYQCIY6F-C_3rESBd.js +1 -0
  99. package/dist/public/assets/chunk-U6XO7XAA-C3UhlwA1.js +2 -0
  100. package/dist/public/assets/chunk-V7JOEXUC-71b0ABpv.js +206 -0
  101. package/dist/public/assets/chunk-VAUOI2AC-C-GBw18h.js +1 -0
  102. package/dist/public/assets/chunk-VR4S4FIN-tUhbELKr.js +1 -0
  103. package/dist/public/assets/chunk-WYO6CB5R-BQoO62-B.js +127 -0
  104. package/dist/public/assets/chunk-XXDRQBXY-Co8n4ZHT.js +1 -0
  105. package/dist/public/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  106. package/dist/public/assets/chunk-YOTPTUD7-DhCRKU1A.js +1 -0
  107. package/dist/public/assets/chunk-ZGVPDNZ5-hpz2Ryq5.js +62 -0
  108. package/dist/public/assets/chunk-ZIRB5QZD-BpeS1aZ7.js +32 -0
  109. package/dist/public/assets/classDiagram-OUVF2IWQ-CXdVorPK.js +1 -0
  110. package/dist/public/assets/classDiagram-v2-EOCWNBFH-CXdVorPK.js +1 -0
  111. package/dist/public/assets/colors-Cg5Vh3br.js +1 -0
  112. package/dist/public/assets/cose-bilkent-JH36ORCC-4GoRbloS.js +1 -0
  113. package/dist/public/assets/cynefin-VYW2F7L2-BPH-25fI.js +1 -0
  114. package/dist/public/assets/cynefinDiagram-TSTJHNR4-PEf16euv.js +62 -0
  115. package/dist/public/assets/cytoscape.esm-C3UvhD5t.js +321 -0
  116. package/dist/public/assets/dagre-Buvkdvvj.js +1 -0
  117. package/dist/public/assets/dagre-VKFMJZFB-DPsCMBF1.js +4 -0
  118. package/dist/public/assets/defaultLocale-CNPGWpMu.js +1 -0
  119. package/dist/public/assets/diagram-FQU43EPY-DS-ryuRg.js +3 -0
  120. package/dist/public/assets/diagram-G47NLZAW-CzUoYkFf.js +24 -0
  121. package/dist/public/assets/diagram-NH7WQ7WH-MhGnE2fh.js +24 -0
  122. package/dist/public/assets/diagram-OA4YK3LP-CsQ9aFR_.js +30 -0
  123. package/dist/public/assets/diagram-WEI45ONY-Er3q49ew.js +41 -0
  124. package/dist/public/assets/dist-BzwexiH1.js +1 -0
  125. package/dist/public/assets/ebnfDiagram-CCIWWBDH-AnJAsk14.js +1 -0
  126. package/dist/public/assets/erDiagram-Q63AITRT-DulA16my.js +85 -0
  127. package/dist/public/assets/eventmodeling-45OFAUF4-CoIhTpHU.js +1 -0
  128. package/dist/public/assets/flowDiagram-23GEKE2U-BGMXUq18.js +1 -0
  129. package/dist/public/assets/ganttDiagram-NO4QXBWP-DAvCPT7_.js +292 -0
  130. package/dist/public/assets/gitGraph-TEB2WS4Q-BcPsZts5.js +1 -0
  131. package/dist/public/assets/gitGraphDiagram-IHSO6WYX-CksvJvsQ.js +106 -0
  132. package/dist/public/assets/{graphlib-B8gBHxth.js → graphlib-DS17s2tU.js} +1 -1
  133. package/dist/public/assets/index-H1CkT-JT.js +447 -0
  134. package/dist/public/assets/info-DKCQHKI2-7VGzrAVB.js +1 -0
  135. package/dist/public/assets/infoDiagram-FWYZ7A6U-D_by7lSL.js +2 -0
  136. package/dist/public/assets/init-ZlXS9mIS.js +1 -0
  137. package/dist/public/assets/{ishikawaDiagram-YF4QCWOH-D5HGS4h9.js → ishikawaDiagram-FXEZZL3T-CiLpASIW.js} +6 -6
  138. package/dist/public/assets/journeyDiagram-5HDEW3XC-DpvFmk7c.js +139 -0
  139. package/dist/public/assets/kanban-definition-HUTT4EX6-DYP_x6zD.js +89 -0
  140. package/dist/public/assets/{katex-Vhh-h91d.js → katex-DolUETbr.js} +3 -3
  141. package/dist/public/assets/{line-DNZMOnJ3.js → line-C8Sfsgt8.js} +1 -1
  142. package/dist/public/assets/linear-BWTvbP1l.js +1 -0
  143. package/dist/public/assets/map-BaFkSB1l.js +1 -0
  144. package/dist/public/assets/mermaid-parser.core--8614tkK.js +4 -0
  145. package/dist/public/assets/mindmap-definition-LN4V7U3C-3IAd4gp3.js +96 -0
  146. package/dist/public/assets/ordinal-BdBDtYTt.js +1 -0
  147. package/dist/public/assets/packet-7NZHBO7P-KYt33H0C.js +1 -0
  148. package/dist/public/assets/pegDiagram-2B236MQR-BQNFxYz2.js +1 -0
  149. package/dist/public/assets/pie-RZYD4A2V-xKz9s9I4.js +1 -0
  150. package/dist/public/assets/pieDiagram-ENE6RG2P-Cy13tg8_.js +39 -0
  151. package/dist/public/assets/quadrantDiagram-ABIIQ3AL-B3czMbdS.js +7 -0
  152. package/dist/public/assets/radar-I7S5WNFK-B4QTX-fX.js +1 -0
  153. package/dist/public/assets/railroad-3IZDKUUU-Co_9rPVC.js +1 -0
  154. package/dist/public/assets/railroad-abnf-AHOZXSZD-Bhy_c3ol.js +1 -0
  155. package/dist/public/assets/railroad-ebnf-EBAXGLYW-B9ENjG34.js +1 -0
  156. package/dist/public/assets/railroad-peg-LSFZ7HO6-CyJxDBsB.js +1 -0
  157. package/dist/public/assets/railroadDiagram-RFXS5EU6-M2j5SJF2.js +1 -0
  158. package/dist/public/assets/range-EplFIxc7.js +1 -0
  159. package/dist/public/assets/requirementDiagram-TGXJPOKE-CmvJWy_S.js +84 -0
  160. package/dist/public/assets/{rough.esm-CSKSodPl.js → rough.esm-By172zw-.js} +1 -1
  161. package/dist/public/assets/sankeyDiagram-HTMAVEWB-tAeOzMTn.js +40 -0
  162. package/dist/public/assets/sequenceDiagram-DBY2YBRQ-r0pwijM4.js +162 -0
  163. package/dist/public/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
  164. package/dist/public/assets/src-DOh7_en_.js +1 -0
  165. package/dist/public/assets/stateDiagram-2N3HPSRC-a9IdkSQG.js +1 -0
  166. package/dist/public/assets/stateDiagram-v2-6OUMAXLB-s2blPtZ-.js +1 -0
  167. package/dist/public/assets/swimlanes-5IMT3BWC-5w0Cdxk6.js +2 -0
  168. package/dist/public/assets/swimlanesDiagram-G3AALYLV-BWa-KRed.js +8 -0
  169. package/dist/public/assets/time-BM2d9MsQ.js +1 -0
  170. package/dist/public/assets/timeline-definition-FHXFAJF6-CvXlv_4Y.js +120 -0
  171. package/dist/public/assets/treeView-QDETBFTQ-B9cxjfpB.js +1 -0
  172. package/dist/public/assets/treemap-6X3UGDF4-BplEq4hB.js +1 -0
  173. package/dist/public/assets/treemap-BfjIY-m6.js +1 -0
  174. package/dist/public/assets/vennDiagram-L72KCM5P-ti2qNcvo.js +34 -0
  175. package/dist/public/assets/wardley-OPB4EBWU-BX2ghg5e.js +1 -0
  176. package/dist/public/assets/wardleyDiagram-EHGQE667-C8lblm8x.js +78 -0
  177. package/dist/public/assets/xychartDiagram-FW5EYKEG-US-_k5eu.js +7 -0
  178. package/dist/public/index.html +31 -12
  179. package/dist/types.d.ts +4 -1
  180. package/dist/types.js +3 -0
  181. package/dist/types.js.map +1 -1
  182. package/package.json +11 -10
  183. package/src/app.ts +11 -4
  184. package/src/auth/index.ts +1 -1
  185. package/src/auth/keycloak.ts +3 -0
  186. package/src/auth/mock.ts +54 -2
  187. package/src/common/validationSchemas.ts +1 -0
  188. package/src/domain/calculations/index.ts +1 -1
  189. package/src/domain/cardTypes/service.ts +26 -21
  190. package/src/domain/cards/lib.ts +11 -4
  191. package/src/domain/cards/service.ts +4 -5
  192. package/src/domain/project/index.ts +54 -0
  193. package/src/domain/project/schema.ts +23 -0
  194. package/src/domain/project/service.ts +89 -1
  195. package/src/domain/projects/index.ts +115 -3
  196. package/src/domain/projects/schema.ts +42 -0
  197. package/src/domain/projects/service.ts +98 -0
  198. package/src/domain/resources/index.ts +1 -1
  199. package/src/domain/resources/schema.ts +4 -2
  200. package/src/domain/resources/service.ts +5 -4
  201. package/src/index.ts +11 -6
  202. package/src/main.ts +0 -6
  203. package/src/middleware/auth.ts +10 -4
  204. package/src/project-registry.ts +7 -2
  205. package/src/types.ts +3 -0
  206. package/dist/public/assets/architecture-7EHR7CIX-CHU1xuYF.js +0 -1
  207. package/dist/public/assets/architectureDiagram-3BPJPVTR--CSL9yP4.js +0 -36
  208. package/dist/public/assets/blockDiagram-GPEHLZMM-KVU5YZg5.js +0 -132
  209. package/dist/public/assets/c4Diagram-AAUBKEIU-CKZoRo4y.js +0 -10
  210. package/dist/public/assets/channel-DylEVeTd.js +0 -1
  211. package/dist/public/assets/chunk-2J33WTMH-3csjo24p.js +0 -1
  212. package/dist/public/assets/chunk-3OPIFGDE-BKzRNP07.js +0 -62
  213. package/dist/public/assets/chunk-4BX2VUAB-ClBqZOcE.js +0 -1
  214. package/dist/public/assets/chunk-55IACEB6-Bgi2dUOh.js +0 -1
  215. package/dist/public/assets/chunk-5ZQYHXKU-CECM_AkK.js +0 -2
  216. package/dist/public/assets/chunk-727SXJPM-DwCEXlGv.js +0 -206
  217. package/dist/public/assets/chunk-AQP2D5EJ-4K2xdOIK.js +0 -231
  218. package/dist/public/assets/chunk-BSJP7CBP-ByoKnyMP.js +0 -1
  219. package/dist/public/assets/chunk-CSCIHK7Q-DPeQmPB5.js +0 -124
  220. package/dist/public/assets/chunk-L5ZTLDWV-pkYUefHU.js +0 -1
  221. package/dist/public/assets/chunk-ND2GUHAM-BbBhIALU.js +0 -1
  222. package/dist/public/assets/chunk-NNHCCRGN-DlpIbxXb.js +0 -159
  223. package/dist/public/assets/chunk-NZK2D7GU-Bsg3s0FQ.js +0 -1
  224. package/dist/public/assets/chunk-O5CBEL6O-CLKEM0Ng.js +0 -70
  225. package/dist/public/assets/chunk-QZHKN3VN-DYbrfjC5.js +0 -1
  226. package/dist/public/assets/classDiagram-4FO5ZUOK-BJy5zoel.js +0 -1
  227. package/dist/public/assets/classDiagram-v2-Q7XG4LA2-BJy5zoel.js +0 -1
  228. package/dist/public/assets/cose-bilkent-S5V4N54A-BW2eHW0s.js +0 -1
  229. package/dist/public/assets/cytoscape.esm-FqbQrHcz.js +0 -321
  230. package/dist/public/assets/dagre-BM42HDAG-BthzRaVA.js +0 -4
  231. package/dist/public/assets/dagre-Bx709z4p.js +0 -1
  232. package/dist/public/assets/diagram-2AECGRRQ-CQJQx4k6.js +0 -43
  233. package/dist/public/assets/diagram-5GNKFQAL-DZn5aW7S.js +0 -10
  234. package/dist/public/assets/diagram-KO2AKTUF-C-khQZwA.js +0 -3
  235. package/dist/public/assets/diagram-LMA3HP47-DgWJfUcV.js +0 -24
  236. package/dist/public/assets/diagram-OG6HWLK6-44JZNjGl.js +0 -24
  237. package/dist/public/assets/dist-Dek0Yhn0.js +0 -1
  238. package/dist/public/assets/erDiagram-TEJ5UH35-BVkYJRJ3.js +0 -85
  239. package/dist/public/assets/eventmodeling-FCH6USID-C_5XBe82.js +0 -1
  240. package/dist/public/assets/flowDiagram-I6XJVG4X-DanY8_jA.js +0 -162
  241. package/dist/public/assets/ganttDiagram-6RSMTGT7-DD1Pw4Yz.js +0 -292
  242. package/dist/public/assets/gitGraph-WXDBUCRP-D4dBNuhe.js +0 -1
  243. package/dist/public/assets/gitGraphDiagram-PVQCEYII-BPrVPTyG.js +0 -106
  244. package/dist/public/assets/index-CKFcPbR1.js +0 -729
  245. package/dist/public/assets/info-J43DQDTF-D7lvZ6o6.js +0 -1
  246. package/dist/public/assets/infoDiagram-5YYISTIA-t6t0skjQ.js +0 -2
  247. package/dist/public/assets/journeyDiagram-JHISSGLW-DZle34fL.js +0 -139
  248. package/dist/public/assets/kanban-definition-UN3LZRKU-DvUHZ0z2.js +0 -89
  249. package/dist/public/assets/mermaid-parser.core-DFZGVidJ.js +0 -4
  250. package/dist/public/assets/mindmap-definition-RKZ34NQL-Lmrs6IX6.js +0 -96
  251. package/dist/public/assets/packet-YPE3B663-AbbU2Ze4.js +0 -1
  252. package/dist/public/assets/pie-LRSECV5Y-DN4QBmSF.js +0 -1
  253. package/dist/public/assets/pieDiagram-4H26LBE5-BwD91PEd.js +0 -30
  254. package/dist/public/assets/quadrantDiagram-W4KKPZXB-DXCkBrKi.js +0 -7
  255. package/dist/public/assets/radar-GUYGQ44K-ej308gGR.js +0 -1
  256. package/dist/public/assets/requirementDiagram-4Y6WPE33-jDFKiwdn.js +0 -84
  257. package/dist/public/assets/sankeyDiagram-5OEKKPKP-D9xT2nUr.js +0 -40
  258. package/dist/public/assets/sequenceDiagram-3UESZ5HK-Bmko-3_U.js +0 -162
  259. package/dist/public/assets/src-CqxF9LGw.js +0 -1
  260. package/dist/public/assets/stateDiagram-AJRCARHV-DjPm6sNG.js +0 -1
  261. package/dist/public/assets/stateDiagram-v2-BHNVJYJU-B-cimf9q.js +0 -1
  262. package/dist/public/assets/timeline-definition-PNZ67QCA-nKPLYYzI.js +0 -120
  263. package/dist/public/assets/treeView-BLDUP644--ooV0k8a.js +0 -1
  264. package/dist/public/assets/treemap-LRROVOQU-BKq-VDOX.js +0 -1
  265. package/dist/public/assets/vennDiagram-CIIHVFJN-DHORAsW0.js +0 -34
  266. package/dist/public/assets/wardley-L42UT6IY-VU5ormp0.js +0 -1
  267. package/dist/public/assets/wardleyDiagram-YWT4CUSO-DwqR74vk.js +0 -78
  268. package/dist/public/assets/xychartDiagram-2RQKCTM6-CUU1IRcn.js +0 -7
package/src/auth/mock.ts CHANGED
@@ -11,6 +11,8 @@
11
11
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
12
  */
13
13
 
14
+ import type { MiddlewareHandler } from 'hono';
15
+ import { setCookie } from 'hono/cookie';
14
16
  import { UserRole } from '../types.js';
15
17
  import type { UserInfo } from '../types.js';
16
18
  import type { AuthProvider } from './types.js';
@@ -20,6 +22,32 @@ export interface MockUserConfig {
20
22
  email?: string;
21
23
  }
22
24
 
25
+ export const MOCK_ROLE_COOKIE = 'mock-role';
26
+ const ROLE_RESET_VALUE = 'default';
27
+
28
+ const ROLE_ALIASES: Record<string, UserRole> = {
29
+ reader: UserRole.Reader,
30
+ editor: UserRole.Editor,
31
+ admin: UserRole.Admin,
32
+ connector: UserRole.Connector,
33
+ };
34
+
35
+ function parseRole(value: string | null | undefined): UserRole | null {
36
+ if (!value) return null;
37
+ return ROLE_ALIASES[value.toLowerCase()] ?? null;
38
+ }
39
+
40
+ function readCookie(header: string | null, name: string): string | undefined {
41
+ if (!header) return undefined;
42
+ for (const part of header.split(';')) {
43
+ const eq = part.indexOf('=');
44
+ if (eq === -1) continue;
45
+ if (part.slice(0, eq).trim() !== name) continue;
46
+ return decodeURIComponent(part.slice(eq + 1).trim());
47
+ }
48
+ return undefined;
49
+ }
50
+
23
51
  export class MockAuthProvider implements AuthProvider {
24
52
  private readonly userConfig: MockUserConfig;
25
53
 
@@ -27,12 +55,36 @@ export class MockAuthProvider implements AuthProvider {
27
55
  this.userConfig = config ?? {};
28
56
  }
29
57
 
30
- async authenticate(): Promise<UserInfo> {
58
+ async authenticate(req: Request): Promise<UserInfo> {
59
+ const cookieRole = parseRole(
60
+ readCookie(req.headers.get('cookie'), MOCK_ROLE_COOKIE),
61
+ );
31
62
  return {
32
63
  id: 'mock-user',
33
64
  email: this.userConfig.email ?? 'admin@cyberismo.local',
34
65
  name: this.userConfig.name ?? 'Local Admin',
35
- role: UserRole.Admin,
66
+ role: cookieRole ?? UserRole.Admin,
36
67
  };
37
68
  }
38
69
  }
70
+
71
+ /**
72
+ * Dev-only middleware that turns `?role=<reader|editor|admin>` into a persistent
73
+ * `mock-role` cookie, and clears it on `?role=default`.
74
+ */
75
+ export function mockRoleCookieMiddleware(): MiddlewareHandler {
76
+ return async (c, next) => {
77
+ const override = new URL(c.req.url).searchParams.get('role');
78
+ if (override) {
79
+ if (override.toLowerCase() === ROLE_RESET_VALUE) {
80
+ setCookie(c, MOCK_ROLE_COOKIE, '', { path: '/', maxAge: 0 });
81
+ } else if (parseRole(override)) {
82
+ setCookie(c, MOCK_ROLE_COOKIE, override.toLowerCase(), {
83
+ path: '/',
84
+ sameSite: 'Lax',
85
+ });
86
+ }
87
+ }
88
+ await next();
89
+ };
90
+ }
@@ -21,6 +21,7 @@ export const resourceTypes = [
21
21
  'graphViews',
22
22
  'linkTypes',
23
23
  'reports',
24
+ 'skills',
24
25
  'templates',
25
26
  'workflows',
26
27
  ] as const;
@@ -47,7 +47,7 @@ const router = new Hono();
47
47
  */
48
48
  router.post(
49
49
  '/',
50
- requireRole(UserRole.Admin),
50
+ requireRole(UserRole.Admin, [UserRole.Connector]),
51
51
  zValidator('json', createCalculationSchema),
52
52
  async (c) => {
53
53
  const commands = c.get('commands');
@@ -11,7 +11,7 @@
11
11
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
12
  */
13
13
 
14
- import type { CommandManager } from '@cyberismo/data-handler';
14
+ import { resourceName, type CommandManager } from '@cyberismo/data-handler';
15
15
  import type { VisibilityGroup, FieldVisibilityBody } from './schema.js';
16
16
 
17
17
  export async function getCardTypes(commands: CommandManager) {
@@ -53,6 +53,7 @@ export async function updateFieldVisibility(
53
53
  body: FieldVisibilityBody,
54
54
  ): Promise<void> {
55
55
  const { fieldName, group: targetGroup, index: targetIndex } = body;
56
+ const target = resourceName(cardTypeName);
56
57
 
57
58
  await commands.atomic(async () => {
58
59
  // Read is now inside the write lock
@@ -87,38 +88,42 @@ export async function updateFieldVisibility(
87
88
  // If same group, just handle reordering
88
89
  if (currentGroup === targetGroup) {
89
90
  if (targetGroup !== 'hidden' && targetIndex !== undefined) {
90
- await commands.updateCmd.applyResourceOperation(
91
- cardTypeName,
92
- { key: groupToKey[targetGroup] },
93
- { name: 'rank', target: fieldName, newIndex: targetIndex },
94
- );
91
+ await commands.updateCmd.apply({
92
+ kind: 'edit',
93
+ target,
94
+ updateKey: { key: groupToKey[targetGroup] },
95
+ operation: { name: 'rank', target: fieldName, newIndex: targetIndex },
96
+ });
95
97
  }
96
98
  return;
97
99
  }
98
100
 
99
101
  // Remove from current group (if not hidden)
100
102
  if (currentGroup !== 'hidden') {
101
- await commands.updateCmd.applyResourceOperation(
102
- cardTypeName,
103
- { key: groupToKey[currentGroup] },
104
- { name: 'remove', target: fieldName },
105
- );
103
+ await commands.updateCmd.apply({
104
+ kind: 'edit',
105
+ target,
106
+ updateKey: { key: groupToKey[currentGroup] },
107
+ operation: { name: 'remove', target: fieldName },
108
+ });
106
109
  }
107
110
 
108
111
  // Add to new group (if not hidden)
109
112
  if (targetGroup !== 'hidden') {
110
- await commands.updateCmd.applyResourceOperation(
111
- cardTypeName,
112
- { key: groupToKey[targetGroup] },
113
- { name: 'add', target: fieldName },
114
- );
113
+ await commands.updateCmd.apply({
114
+ kind: 'edit',
115
+ target,
116
+ updateKey: { key: groupToKey[targetGroup] },
117
+ operation: { name: 'add', target: fieldName },
118
+ });
115
119
 
116
120
  if (targetIndex !== undefined) {
117
- await commands.updateCmd.applyResourceOperation(
118
- cardTypeName,
119
- { key: groupToKey[targetGroup] },
120
- { name: 'rank', target: fieldName, newIndex: targetIndex },
121
- );
121
+ await commands.updateCmd.apply({
122
+ kind: 'edit',
123
+ target,
124
+ updateKey: { key: groupToKey[targetGroup] },
125
+ operation: { name: 'rank', target: fieldName, newIndex: targetIndex },
126
+ });
122
127
  }
123
128
  }
124
129
  }, `Update field visibility for ${cardTypeName}`);
@@ -11,7 +11,7 @@
11
11
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
12
  */
13
13
 
14
- import Processor from '@asciidoctor/core';
14
+ import { convert } 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
17
  import { preprocessMermaidBlocksForHtml } from '@cyberismo/data-handler/utils/mermaid-renderer';
@@ -71,15 +71,15 @@ export async function getCardDetails(
71
71
  );
72
72
 
73
73
  const projectPrefix = commands.project.projectPrefix;
74
- const htmlContent = Processor()
75
- .convert(asciidocContent, {
74
+ const htmlContent = (
75
+ await convert(asciidocContent, {
76
76
  safe: 'safe',
77
77
  attributes: {
78
78
  imagesdir: `/api/projects/${projectPrefix}/cards/${key}/a`,
79
79
  icons: 'font',
80
80
  },
81
81
  })
82
- .toString();
82
+ ).toString();
83
83
 
84
84
  if (raw) {
85
85
  if (!cardDetailsResponse.metadata) {
@@ -105,6 +105,13 @@ export async function getCardDetails(
105
105
  fieldDescription: fieldType.description,
106
106
  dataType: fieldType.dataType,
107
107
  isCalculated: customField.isCalculated,
108
+ isOverridable: !!(
109
+ customField.isCalculated && customField.enableOverride
110
+ ),
111
+ overrideValue:
112
+ customField.isCalculated && customField.enableOverride
113
+ ? cardDetailsResponse.metadata[customField.name]
114
+ : undefined,
108
115
  value: cardDetailsResponse.metadata[customField.name],
109
116
  enumValues: fieldType.enumValues ?? [],
110
117
  });
@@ -11,7 +11,7 @@
11
11
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
12
  */
13
13
 
14
- import Processor from '@asciidoctor/core';
14
+ import { convert } from '@asciidoctor/core';
15
15
  import type {
16
16
  ExportPdfOptions,
17
17
  MetadataContent,
@@ -181,17 +181,16 @@ export async function parseContent(
181
181
  'inject',
182
182
  );
183
183
 
184
- const processor = Processor();
185
184
  const projectPrefix = commands.project.projectPrefix;
186
- const parsedContent = processor
187
- .convert(asciidocContent, {
185
+ const parsedContent = (
186
+ await convert(asciidocContent, {
188
187
  safe: 'safe',
189
188
  attributes: {
190
189
  imagesdir: `/api/projects/${projectPrefix}/cards/${key}/a`,
191
190
  icons: 'font',
192
191
  },
193
192
  })
194
- .toString();
193
+ ).toString();
195
194
 
196
195
  return { parsedContent };
197
196
  });
@@ -14,8 +14,11 @@
14
14
  import { Hono } from 'hono';
15
15
  import { zValidator } from '../../middleware/zvalidator.js';
16
16
  import {
17
+ addHubSchema,
18
+ cleanSchema,
17
19
  importModuleSchema,
18
20
  moduleParamSchema,
21
+ removeHubSchema,
19
22
  updateProjectSchema,
20
23
  } from './schema.js';
21
24
  import * as projectService from './service.js';
@@ -92,4 +95,55 @@ router.delete(
92
95
  },
93
96
  );
94
97
 
98
+ router.get('/hubs', requireRole(UserRole.Reader), async (c) => {
99
+ const commands = c.get('commands');
100
+ const hubs = await projectService.getHubs(commands);
101
+ return c.json(hubs);
102
+ });
103
+
104
+ router.post(
105
+ '/hubs',
106
+ requireRole(UserRole.Admin),
107
+ zValidator('json', addHubSchema),
108
+ async (c) => {
109
+ const commands = c.get('commands');
110
+ const { location } = c.req.valid('json');
111
+ const unreachable = await projectService.addHub(commands, location);
112
+ return c.json({ message: 'Hub added', unreachable });
113
+ },
114
+ );
115
+
116
+ router.delete(
117
+ '/hubs',
118
+ requireRole(UserRole.Admin),
119
+ zValidator('query', removeHubSchema),
120
+ async (c) => {
121
+ const commands = c.get('commands');
122
+ const { location } = c.req.valid('query');
123
+ await projectService.removeHub(commands, location);
124
+ return c.json({ message: 'Hub removed' });
125
+ },
126
+ );
127
+
128
+ router.post('/hubs/fetch', requireRole(UserRole.Admin), async (c) => {
129
+ const commands = c.get('commands');
130
+ const unreachable = await projectService.fetchHubs(commands);
131
+ return c.json({ message: 'Hubs fetched', unreachable });
132
+ });
133
+
134
+ // A non-empty 'failedCards' in the response is a partial success, not an error:
135
+ // the command collects per-card failures instead of throwing, so the report of
136
+ // what was and was not removed is returned with 200.
137
+ router.post(
138
+ '/clean',
139
+ requireRole(UserRole.Admin),
140
+ zValidator('json', cleanSchema),
141
+ async (c) => {
142
+ const commands = c.get('commands');
143
+ const { dryRun } = c.req.valid('json');
144
+ const result = await projectService.cleanProject(commands, dryRun);
145
+ return c.json(result);
146
+ },
147
+ );
148
+
95
149
  export default router;
@@ -20,6 +20,8 @@ export const moduleParamSchema = z.object({
20
20
  export const updateProjectSchema = z.object({
21
21
  name: z.string().optional(),
22
22
  cardKeyPrefix: z.string().optional(),
23
+ description: z.string().optional(),
24
+ category: z.string().optional(),
23
25
  gitRemoteUrl: z
24
26
  .string()
25
27
  .refine((s) => s.startsWith('https://') || s.startsWith('git@'), {
@@ -28,6 +30,27 @@ export const updateProjectSchema = z.object({
28
30
  .optional(),
29
31
  });
30
32
 
33
+ export const addHubSchema = z.object({
34
+ location: z.url({
35
+ protocol: /^https?$/,
36
+ error: 'Hub location must be a valid HTTP or HTTPS URL',
37
+ }),
38
+ });
39
+
40
+ // Removal accepts any stored location: configurations written before locations
41
+ // were validated may hold entries this schema would otherwise refuse, and those
42
+ // have to stay removable.
43
+ export const removeHubSchema = z.object({
44
+ location: z.string().min(1),
45
+ });
46
+
47
+ // 'dryRun' is required so that a request whose body did not arrive fails with
48
+ // 400 instead of defaulting into the destructive real clean. The command's
49
+ // 'cardType' narrowing is deliberately not exposed yet.
50
+ export const cleanSchema = z.object({
51
+ dryRun: z.boolean(),
52
+ });
53
+
31
54
  export const importModuleSchema = z.object({
32
55
  source: z
33
56
  .string()
@@ -12,10 +12,14 @@
12
12
  */
13
13
 
14
14
  import {
15
+ type CleanResult,
15
16
  type CommandManager,
17
+ type HubFetchFailure,
16
18
  type ModuleSettingFromHub,
17
19
  } from '@cyberismo/data-handler';
18
20
 
21
+ export type { CleanResult } from '@cyberismo/data-handler';
22
+
19
23
  export interface ProjectModule {
20
24
  name: string;
21
25
  cardKeyPrefix: string;
@@ -24,6 +28,8 @@ export interface ProjectModule {
24
28
  export interface ProjectInfo {
25
29
  name: string;
26
30
  cardKeyPrefix: string;
31
+ description: string;
32
+ category: string;
27
33
  modules: ProjectModule[];
28
34
  gitRemoteUrl: string | null;
29
35
  }
@@ -31,9 +37,25 @@ export interface ProjectInfo {
31
37
  export interface ProjectUpdatePayload {
32
38
  name?: string;
33
39
  cardKeyPrefix?: string;
40
+ description?: string;
41
+ category?: string;
34
42
  gitRemoteUrl?: string;
35
43
  }
36
44
 
45
+ export interface HubModuleInfo {
46
+ name: string;
47
+ displayName?: string;
48
+ location: string;
49
+ imported: boolean;
50
+ }
51
+
52
+ export interface HubInfo {
53
+ location: string;
54
+ displayName?: string;
55
+ description?: string;
56
+ modules: HubModuleInfo[];
57
+ }
58
+
37
59
  async function toModuleInfo(
38
60
  commands: CommandManager,
39
61
  moduleName: string,
@@ -67,6 +89,8 @@ export async function getProject(
67
89
  return {
68
90
  name: project.name,
69
91
  cardKeyPrefix: project.prefix,
92
+ description: project.description ?? '',
93
+ category: project.category ?? '',
70
94
  modules: moduleDetails,
71
95
  gitRemoteUrl,
72
96
  };
@@ -77,7 +101,7 @@ export async function updateProject(
77
101
  commands: CommandManager,
78
102
  updates: ProjectUpdatePayload,
79
103
  ): Promise<ProjectInfo> {
80
- const { name, cardKeyPrefix, gitRemoteUrl } = updates;
104
+ const { name, cardKeyPrefix, description, category, gitRemoteUrl } = updates;
81
105
 
82
106
  await commands.atomic(async () => {
83
107
  if (cardKeyPrefix) {
@@ -86,6 +110,12 @@ export async function updateProject(
86
110
  if (name) {
87
111
  await commands.project.configuration.setProjectName(name);
88
112
  }
113
+ if (description !== undefined) {
114
+ await commands.project.configuration.setDescription(description);
115
+ }
116
+ if (category !== undefined) {
117
+ await commands.project.configuration.setCategory(category);
118
+ }
89
119
  }, 'Update project settings');
90
120
 
91
121
  if (gitRemoteUrl !== undefined) {
@@ -107,9 +137,21 @@ export async function deleteModule(commands: CommandManager, module: string) {
107
137
  await commands.removeCmd.remove('module', module);
108
138
  }
109
139
 
140
+ // Hub data is only refreshed on demand, so a project that has never fetched
141
+ // would otherwise show an empty catalogue until the user asks for a refresh.
142
+ async function populateHubCache(commands: CommandManager) {
143
+ try {
144
+ await commands.fetchCmd.ensureModuleListExists();
145
+ } catch (error) {
146
+ // An unreachable hub must not fail listing the hubs we already know about.
147
+ console.warn('Failed to populate the hub cache', error);
148
+ }
149
+ }
150
+
110
151
  export async function getImportableModules(
111
152
  commands: CommandManager,
112
153
  ): Promise<ModuleSettingFromHub[]> {
154
+ await populateHubCache(commands);
113
155
  return commands.showCmd.showImportableModules(false, true);
114
156
  }
115
157
 
@@ -119,3 +161,49 @@ export async function importModule(
119
161
  ): Promise<void> {
120
162
  await commands.importCmd.importModule(source);
121
163
  }
164
+
165
+ export async function getHubs(commands: CommandManager): Promise<HubInfo[]> {
166
+ await populateHubCache(commands);
167
+ const hubs = await commands.showCmd.showHubDetails();
168
+ const importedModules = new Set(
169
+ (await commands.showCmd.showModules()).map((mod) => mod.name),
170
+ );
171
+ return hubs.map((hub) => ({
172
+ location: hub.location,
173
+ displayName: hub.displayName,
174
+ description: hub.description,
175
+ modules: hub.modules.map((mod) => ({
176
+ name: mod.name,
177
+ displayName: mod.displayName,
178
+ location: mod.location,
179
+ imported: importedModules.has(mod.name),
180
+ })),
181
+ }));
182
+ }
183
+
184
+ export async function addHub(
185
+ commands: CommandManager,
186
+ location: string,
187
+ ): Promise<HubFetchFailure[]> {
188
+ await commands.createCmd.addHubLocation(location);
189
+ // The hub is configured even when it cannot be reached right now; its
190
+ // modules appear once a later refresh succeeds.
191
+ return commands.fetchCmd.fetchHubs(true);
192
+ }
193
+
194
+ export async function removeHub(commands: CommandManager, location: string) {
195
+ await commands.removeCmd.remove('hub', location);
196
+ }
197
+
198
+ export async function fetchHubs(
199
+ commands: CommandManager,
200
+ ): Promise<HubFetchFailure[]> {
201
+ return commands.fetchCmd.fetchHubs(true);
202
+ }
203
+
204
+ export async function cleanProject(
205
+ commands: CommandManager,
206
+ dryRun: boolean,
207
+ ): Promise<CleanResult> {
208
+ return commands.cleanCmd.clean(dryRun);
209
+ }
@@ -12,11 +12,17 @@
12
12
  */
13
13
 
14
14
  import { Hono } from 'hono';
15
+ import { zValidator } from '../../middleware/zvalidator.js';
15
16
  import type { ProjectRegistry } from '../../project-registry.js';
16
- import { requireRole } from '../../middleware/auth.js';
17
+ import { requireRole, getCurrentUser } from '../../middleware/auth.js';
17
18
  import { UserRole } from '../../types.js';
19
+ import { createProjectSchema, cloneProjectSchema } from './schema.js';
20
+ import * as projectsService from './service.js';
18
21
 
19
- export function createProjectsRouter(registry: ProjectRegistry) {
22
+ export function createProjectsRouter(
23
+ registry: ProjectRegistry,
24
+ multiProjectRoot?: string,
25
+ ) {
20
26
  const router = new Hono();
21
27
 
22
28
  /**
@@ -32,8 +38,114 @@ export function createProjectsRouter(registry: ProjectRegistry) {
32
38
  * description: Unauthorized
33
39
  */
34
40
  router.get('/', requireRole(UserRole.Reader), (c) => {
35
- return c.json(registry.list());
41
+ return c.json({
42
+ projects: registry.list(),
43
+ canCreateProjects: !!multiProjectRoot,
44
+ });
36
45
  });
37
46
 
47
+ /**
48
+ * @swagger
49
+ * /api/projects:
50
+ * post:
51
+ * summary: Create a new project
52
+ * description: Creates a new project on disk, initializes git, adds default hub
53
+ */
54
+ router.post(
55
+ '/',
56
+ requireRole(UserRole.Editor),
57
+ zValidator('json', createProjectSchema),
58
+ async (c) => {
59
+ if (!multiProjectRoot) {
60
+ return c.json(
61
+ { error: 'Project creation is not available in single-project mode' },
62
+ 403,
63
+ );
64
+ }
65
+
66
+ const params = c.req.valid('json');
67
+
68
+ if (registry.has(params.prefix)) {
69
+ return c.json(
70
+ { error: `Project with prefix '${params.prefix}' already exists` },
71
+ 409,
72
+ );
73
+ }
74
+
75
+ try {
76
+ const result = await projectsService.createProject(
77
+ registry,
78
+ multiProjectRoot,
79
+ params,
80
+ getCurrentUser(c),
81
+ );
82
+ return c.json(result, 201);
83
+ } catch (error) {
84
+ return c.json(
85
+ {
86
+ error:
87
+ error instanceof Error
88
+ ? error.message
89
+ : 'Failed to create project',
90
+ },
91
+ 500,
92
+ );
93
+ }
94
+ },
95
+ );
96
+
97
+ /**
98
+ * @swagger
99
+ * /api/projects/clone:
100
+ * post:
101
+ * summary: Clone a project from a git repository
102
+ * description: Clones a git repo and registers discovered projects
103
+ */
104
+ router.post(
105
+ '/clone',
106
+ requireRole(UserRole.Editor),
107
+ zValidator('json', cloneProjectSchema),
108
+ async (c) => {
109
+ if (!multiProjectRoot) {
110
+ return c.json(
111
+ { error: 'Project cloning is not available in single-project mode' },
112
+ 403,
113
+ );
114
+ }
115
+
116
+ const { url } = c.req.valid('json');
117
+
118
+ try {
119
+ const result = await projectsService.cloneProject(
120
+ registry,
121
+ multiProjectRoot,
122
+ url,
123
+ );
124
+
125
+ if (result.projects.length === 0) {
126
+ return c.json(
127
+ {
128
+ error: 'All projects in the cloned repository already exist',
129
+ skippedDuplicates: result.skippedDuplicates,
130
+ },
131
+ 409,
132
+ );
133
+ }
134
+
135
+ return c.json(result, 201);
136
+ } catch (error) {
137
+ return c.json(
138
+ {
139
+ error:
140
+ error instanceof Error
141
+ ? error.message
142
+ : 'Failed to clone repository',
143
+ },
144
+ 500,
145
+ );
146
+ }
147
+ },
148
+ );
149
+
38
150
  return router;
39
151
  }
@@ -0,0 +1,42 @@
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
+ import { Validate } from '@cyberismo/data-handler';
16
+
17
+ export const createProjectSchema = z.object({
18
+ name: z
19
+ .string()
20
+ .min(1, 'Project name is required')
21
+ .refine((s) => Validate.isValidProjectName(s), {
22
+ message: 'Invalid project name',
23
+ }),
24
+ prefix: z
25
+ .string()
26
+ .min(3, 'Prefix must be at least 3 characters')
27
+ .max(10, 'Prefix must be at most 10 characters')
28
+ .refine((s) => Validate.validatePrefix(s), {
29
+ message: 'Prefix must contain only lowercase letters',
30
+ }),
31
+ category: z.string().optional().default(''),
32
+ description: z.string().optional().default(''),
33
+ });
34
+
35
+ export const cloneProjectSchema = z.object({
36
+ url: z
37
+ .string()
38
+ .min(1, 'Repository URL is required')
39
+ .refine((s) => s.startsWith('https://') || s.startsWith('git@'), {
40
+ message: 'URL must start with https:// or git@',
41
+ }),
42
+ });