@cyberismo/data-handler 0.0.2

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 (306) hide show
  1. package/LICENSE +702 -0
  2. package/dist/card-metadata-updater.d.ts +33 -0
  3. package/dist/card-metadata-updater.js +121 -0
  4. package/dist/card-metadata-updater.js.map +1 -0
  5. package/dist/command-handler.d.ts +96 -0
  6. package/dist/command-handler.js +557 -0
  7. package/dist/command-handler.js.map +1 -0
  8. package/dist/command-manager.d.ts +43 -0
  9. package/dist/command-manager.js +73 -0
  10. package/dist/command-manager.js.map +1 -0
  11. package/dist/commands/calculate.d.ts +86 -0
  12. package/dist/commands/calculate.js +444 -0
  13. package/dist/commands/calculate.js.map +1 -0
  14. package/dist/commands/create.d.ts +114 -0
  15. package/dist/commands/create.js +389 -0
  16. package/dist/commands/create.js.map +1 -0
  17. package/dist/commands/edit.d.ts +37 -0
  18. package/dist/commands/edit.js +99 -0
  19. package/dist/commands/edit.js.map +1 -0
  20. package/dist/commands/export-site.d.ts +45 -0
  21. package/dist/commands/export-site.js +301 -0
  22. package/dist/commands/export-site.js.map +1 -0
  23. package/dist/commands/export.d.ts +53 -0
  24. package/dist/commands/export.js +251 -0
  25. package/dist/commands/export.js.map +1 -0
  26. package/dist/commands/import.d.ts +53 -0
  27. package/dist/commands/import.js +133 -0
  28. package/dist/commands/import.js.map +1 -0
  29. package/dist/commands/index.d.ts +26 -0
  30. package/dist/commands/index.js +27 -0
  31. package/dist/commands/index.js.map +1 -0
  32. package/dist/commands/move.d.ts +55 -0
  33. package/dist/commands/move.js +341 -0
  34. package/dist/commands/move.js.map +1 -0
  35. package/dist/commands/remove.d.ts +38 -0
  36. package/dist/commands/remove.js +192 -0
  37. package/dist/commands/remove.js.map +1 -0
  38. package/dist/commands/rename.d.ts +46 -0
  39. package/dist/commands/rename.js +289 -0
  40. package/dist/commands/rename.js.map +1 -0
  41. package/dist/commands/show.d.ts +124 -0
  42. package/dist/commands/show.js +345 -0
  43. package/dist/commands/show.js.map +1 -0
  44. package/dist/commands/transition.d.ts +27 -0
  45. package/dist/commands/transition.js +92 -0
  46. package/dist/commands/transition.js.map +1 -0
  47. package/dist/commands/update.d.ts +29 -0
  48. package/dist/commands/update.js +64 -0
  49. package/dist/commands/update.js.map +1 -0
  50. package/dist/commands/validate.d.ts +143 -0
  51. package/dist/commands/validate.js +689 -0
  52. package/dist/commands/validate.js.map +1 -0
  53. package/dist/containers/card-container.d.ts +44 -0
  54. package/dist/containers/card-container.js +282 -0
  55. package/dist/containers/card-container.js.map +1 -0
  56. package/dist/containers/project/project-paths.d.ts +46 -0
  57. package/dist/containers/project/project-paths.js +105 -0
  58. package/dist/containers/project/project-paths.js.map +1 -0
  59. package/dist/containers/project/resource-collector.d.ts +86 -0
  60. package/dist/containers/project/resource-collector.js +331 -0
  61. package/dist/containers/project/resource-collector.js.map +1 -0
  62. package/dist/containers/project.d.ts +351 -0
  63. package/dist/containers/project.js +896 -0
  64. package/dist/containers/project.js.map +1 -0
  65. package/dist/containers/template.d.ts +108 -0
  66. package/dist/containers/template.js +433 -0
  67. package/dist/containers/template.js.map +1 -0
  68. package/dist/exceptions/index.d.ts +19 -0
  69. package/dist/exceptions/index.js +26 -0
  70. package/dist/exceptions/index.js.map +1 -0
  71. package/dist/index.d.ts +16 -0
  72. package/dist/index.js +15 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/interfaces/adoc.d.ts +12 -0
  75. package/dist/interfaces/adoc.js +13 -0
  76. package/dist/interfaces/adoc.js.map +1 -0
  77. package/dist/interfaces/macros.d.ts +45 -0
  78. package/dist/interfaces/macros.js +13 -0
  79. package/dist/interfaces/macros.js.map +1 -0
  80. package/dist/interfaces/project-interfaces.d.ts +121 -0
  81. package/dist/interfaces/project-interfaces.js +21 -0
  82. package/dist/interfaces/project-interfaces.js.map +1 -0
  83. package/dist/interfaces/request-status-interfaces.d.ts +28 -0
  84. package/dist/interfaces/request-status-interfaces.js +20 -0
  85. package/dist/interfaces/request-status-interfaces.js.map +1 -0
  86. package/dist/interfaces/resource-interfaces.d.ts +117 -0
  87. package/dist/interfaces/resource-interfaces.js +20 -0
  88. package/dist/interfaces/resource-interfaces.js.map +1 -0
  89. package/dist/macros/base-macro.d.ts +31 -0
  90. package/dist/macros/base-macro.js +126 -0
  91. package/dist/macros/base-macro.js.map +1 -0
  92. package/dist/macros/common.d.ts +17 -0
  93. package/dist/macros/common.js +23 -0
  94. package/dist/macros/common.js.map +1 -0
  95. package/dist/macros/createCards/index.d.ts +36 -0
  96. package/dist/macros/createCards/index.js +35 -0
  97. package/dist/macros/createCards/index.js.map +1 -0
  98. package/dist/macros/createCards/metadata.d.ts +14 -0
  99. package/dist/macros/createCards/metadata.js +18 -0
  100. package/dist/macros/createCards/metadata.js.map +1 -0
  101. package/dist/macros/graph/index.d.ts +29 -0
  102. package/dist/macros/graph/index.js +91 -0
  103. package/dist/macros/graph/index.js.map +1 -0
  104. package/dist/macros/graph/metadata.d.ts +14 -0
  105. package/dist/macros/graph/metadata.js +18 -0
  106. package/dist/macros/graph/metadata.js.map +1 -0
  107. package/dist/macros/index.d.ts +93 -0
  108. package/dist/macros/index.js +237 -0
  109. package/dist/macros/index.js.map +1 -0
  110. package/dist/macros/report/index.d.ts +26 -0
  111. package/dist/macros/report/index.js +70 -0
  112. package/dist/macros/report/index.js.map +1 -0
  113. package/dist/macros/report/metadata.d.ts +14 -0
  114. package/dist/macros/report/metadata.js +18 -0
  115. package/dist/macros/report/metadata.js.map +1 -0
  116. package/dist/macros/scoreCard/index.d.ts +30 -0
  117. package/dist/macros/scoreCard/index.js +38 -0
  118. package/dist/macros/scoreCard/index.js.map +1 -0
  119. package/dist/macros/scoreCard/metadata.d.ts +14 -0
  120. package/dist/macros/scoreCard/metadata.js +18 -0
  121. package/dist/macros/scoreCard/metadata.js.map +1 -0
  122. package/dist/macros/task-queue.d.ts +46 -0
  123. package/dist/macros/task-queue.js +69 -0
  124. package/dist/macros/task-queue.js.map +1 -0
  125. package/dist/module-manager.d.ts +62 -0
  126. package/dist/module-manager.js +350 -0
  127. package/dist/module-manager.js.map +1 -0
  128. package/dist/permissions/action-guard.d.ts +28 -0
  129. package/dist/permissions/action-guard.js +61 -0
  130. package/dist/permissions/action-guard.js.map +1 -0
  131. package/dist/project-settings.d.ts +42 -0
  132. package/dist/project-settings.js +120 -0
  133. package/dist/project-settings.js.map +1 -0
  134. package/dist/resources/array-handler.d.ts +28 -0
  135. package/dist/resources/array-handler.js +116 -0
  136. package/dist/resources/array-handler.js.map +1 -0
  137. package/dist/resources/card-type-resource.d.ts +72 -0
  138. package/dist/resources/card-type-resource.js +334 -0
  139. package/dist/resources/card-type-resource.js.map +1 -0
  140. package/dist/resources/create-defaults.d.ts +81 -0
  141. package/dist/resources/create-defaults.js +184 -0
  142. package/dist/resources/create-defaults.js.map +1 -0
  143. package/dist/resources/field-type-resource.d.ts +88 -0
  144. package/dist/resources/field-type-resource.js +411 -0
  145. package/dist/resources/field-type-resource.js.map +1 -0
  146. package/dist/resources/file-resource.d.ts +50 -0
  147. package/dist/resources/file-resource.js +301 -0
  148. package/dist/resources/file-resource.js.map +1 -0
  149. package/dist/resources/folder-resource.d.ts +66 -0
  150. package/dist/resources/folder-resource.js +100 -0
  151. package/dist/resources/folder-resource.js.map +1 -0
  152. package/dist/resources/graph-model-resource.d.ts +78 -0
  153. package/dist/resources/graph-model-resource.js +164 -0
  154. package/dist/resources/graph-model-resource.js.map +1 -0
  155. package/dist/resources/graph-view-resource.d.ts +78 -0
  156. package/dist/resources/graph-view-resource.js +163 -0
  157. package/dist/resources/graph-view-resource.js.map +1 -0
  158. package/dist/resources/link-type-resource.d.ts +62 -0
  159. package/dist/resources/link-type-resource.js +150 -0
  160. package/dist/resources/link-type-resource.js.map +1 -0
  161. package/dist/resources/report-resource.d.ts +77 -0
  162. package/dist/resources/report-resource.js +171 -0
  163. package/dist/resources/report-resource.js.map +1 -0
  164. package/dist/resources/resource-object.d.ts +108 -0
  165. package/dist/resources/resource-object.js +147 -0
  166. package/dist/resources/resource-object.js.map +1 -0
  167. package/dist/resources/template-resource.d.ts +82 -0
  168. package/dist/resources/template-resource.js +173 -0
  169. package/dist/resources/template-resource.js.map +1 -0
  170. package/dist/resources/workflow-resource.d.ts +67 -0
  171. package/dist/resources/workflow-resource.js +156 -0
  172. package/dist/resources/workflow-resource.js.map +1 -0
  173. package/dist/types/queries.d.ts +142 -0
  174. package/dist/types/queries.js +16 -0
  175. package/dist/types/queries.js.map +1 -0
  176. package/dist/utils/card-utils.d.ts +34 -0
  177. package/dist/utils/card-utils.js +78 -0
  178. package/dist/utils/card-utils.js.map +1 -0
  179. package/dist/utils/clingo-fact-builder.d.ts +58 -0
  180. package/dist/utils/clingo-fact-builder.js +126 -0
  181. package/dist/utils/clingo-fact-builder.js.map +1 -0
  182. package/dist/utils/clingo-facts.d.ts +97 -0
  183. package/dist/utils/clingo-facts.js +352 -0
  184. package/dist/utils/clingo-facts.js.map +1 -0
  185. package/dist/utils/clingo-parser.d.ts +59 -0
  186. package/dist/utils/clingo-parser.js +403 -0
  187. package/dist/utils/clingo-parser.js.map +1 -0
  188. package/dist/utils/clingo-program-builder.d.ts +39 -0
  189. package/dist/utils/clingo-program-builder.js +57 -0
  190. package/dist/utils/clingo-program-builder.js.map +1 -0
  191. package/dist/utils/common-utils.d.ts +24 -0
  192. package/dist/utils/common-utils.js +47 -0
  193. package/dist/utils/common-utils.js.map +1 -0
  194. package/dist/utils/constants.d.ts +18 -0
  195. package/dist/utils/constants.js +27 -0
  196. package/dist/utils/constants.js.map +1 -0
  197. package/dist/utils/csv.d.ts +18 -0
  198. package/dist/utils/csv.js +45 -0
  199. package/dist/utils/csv.js.map +1 -0
  200. package/dist/utils/file-utils.d.ts +69 -0
  201. package/dist/utils/file-utils.js +158 -0
  202. package/dist/utils/file-utils.js.map +1 -0
  203. package/dist/utils/json.d.ts +61 -0
  204. package/dist/utils/json.js +108 -0
  205. package/dist/utils/json.js.map +1 -0
  206. package/dist/utils/lexorank.d.ts +59 -0
  207. package/dist/utils/lexorank.js +159 -0
  208. package/dist/utils/lexorank.js.map +1 -0
  209. package/dist/utils/log-utils.d.ts +40 -0
  210. package/dist/utils/log-utils.js +109 -0
  211. package/dist/utils/log-utils.js.map +1 -0
  212. package/dist/utils/random.d.ts +19 -0
  213. package/dist/utils/random.js +34 -0
  214. package/dist/utils/random.js.map +1 -0
  215. package/dist/utils/resource-utils.d.ts +45 -0
  216. package/dist/utils/resource-utils.js +137 -0
  217. package/dist/utils/resource-utils.js.map +1 -0
  218. package/dist/utils/sanitize-svg.d.ts +18 -0
  219. package/dist/utils/sanitize-svg.js +38 -0
  220. package/dist/utils/sanitize-svg.js.map +1 -0
  221. package/dist/utils/user-preferences.d.ts +64 -0
  222. package/dist/utils/user-preferences.js +106 -0
  223. package/dist/utils/user-preferences.js.map +1 -0
  224. package/dist/utils/validate.d.ts +26 -0
  225. package/dist/utils/validate.js +53 -0
  226. package/dist/utils/validate.js.map +1 -0
  227. package/dist/utils/value-utils.d.ts +58 -0
  228. package/dist/utils/value-utils.js +181 -0
  229. package/dist/utils/value-utils.js.map +1 -0
  230. package/package.json +67 -0
  231. package/src/card-metadata-updater.ts +182 -0
  232. package/src/command-handler.ts +686 -0
  233. package/src/command-manager.ts +99 -0
  234. package/src/commands/calculate.ts +591 -0
  235. package/src/commands/create.ts +559 -0
  236. package/src/commands/edit.ts +123 -0
  237. package/src/commands/export-site.ts +356 -0
  238. package/src/commands/export.ts +315 -0
  239. package/src/commands/import.ts +169 -0
  240. package/src/commands/index.ts +42 -0
  241. package/src/commands/move.ts +451 -0
  242. package/src/commands/remove.ts +244 -0
  243. package/src/commands/rename.ts +378 -0
  244. package/src/commands/show.ts +442 -0
  245. package/src/commands/transition.ts +127 -0
  246. package/src/commands/update.ts +76 -0
  247. package/src/commands/validate.ts +962 -0
  248. package/src/containers/card-container.ts +378 -0
  249. package/src/containers/project/project-paths.ts +127 -0
  250. package/src/containers/project/resource-collector.ts +379 -0
  251. package/src/containers/project.ts +1135 -0
  252. package/src/containers/template.ts +573 -0
  253. package/src/exceptions/index.ts +29 -0
  254. package/src/index.ts +33 -0
  255. package/src/interfaces/adoc.ts +18 -0
  256. package/src/interfaces/macros.ts +54 -0
  257. package/src/interfaces/project-interfaces.ts +208 -0
  258. package/src/interfaces/request-status-interfaces.ts +30 -0
  259. package/src/interfaces/resource-interfaces.ts +179 -0
  260. package/src/macros/base-macro.ts +176 -0
  261. package/src/macros/common.ts +24 -0
  262. package/src/macros/createCards/index.ts +57 -0
  263. package/src/macros/createCards/metadata.ts +21 -0
  264. package/src/macros/graph/index.ts +130 -0
  265. package/src/macros/graph/metadata.ts +21 -0
  266. package/src/macros/index.ts +321 -0
  267. package/src/macros/report/index.ts +88 -0
  268. package/src/macros/report/metadata.ts +21 -0
  269. package/src/macros/scoreCard/index.ts +55 -0
  270. package/src/macros/scoreCard/metadata.ts +21 -0
  271. package/src/macros/task-queue.ts +79 -0
  272. package/src/module-manager.ts +443 -0
  273. package/src/permissions/action-guard.ts +77 -0
  274. package/src/project-settings.ts +140 -0
  275. package/src/resources/array-handler.ts +141 -0
  276. package/src/resources/card-type-resource.ts +455 -0
  277. package/src/resources/create-defaults.ts +216 -0
  278. package/src/resources/field-type-resource.ts +533 -0
  279. package/src/resources/file-resource.ts +433 -0
  280. package/src/resources/folder-resource.ts +140 -0
  281. package/src/resources/graph-model-resource.ts +205 -0
  282. package/src/resources/graph-view-resource.ts +199 -0
  283. package/src/resources/link-type-resource.ts +191 -0
  284. package/src/resources/report-resource.ts +224 -0
  285. package/src/resources/resource-object.ts +246 -0
  286. package/src/resources/template-resource.ts +210 -0
  287. package/src/resources/workflow-resource.ts +205 -0
  288. package/src/types/queries.ts +149 -0
  289. package/src/utils/card-utils.ts +83 -0
  290. package/src/utils/clingo-fact-builder.ts +167 -0
  291. package/src/utils/clingo-facts.ts +550 -0
  292. package/src/utils/clingo-parser.ts +519 -0
  293. package/src/utils/clingo-program-builder.ts +71 -0
  294. package/src/utils/common-utils.ts +54 -0
  295. package/src/utils/constants.ts +32 -0
  296. package/src/utils/csv.ts +53 -0
  297. package/src/utils/file-utils.ts +182 -0
  298. package/src/utils/json.ts +118 -0
  299. package/src/utils/lexorank.ts +180 -0
  300. package/src/utils/log-utils.ts +127 -0
  301. package/src/utils/random.ts +37 -0
  302. package/src/utils/resource-utils.ts +180 -0
  303. package/src/utils/sanitize-svg.ts +46 -0
  304. package/src/utils/user-preferences.ts +126 -0
  305. package/src/utils/validate.ts +66 -0
  306. package/src/utils/value-utils.ts +189 -0
@@ -0,0 +1,169 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2024
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 type { CardType } from '../interfaces/resource-interfaces.js';
15
+ import { type Create, Validate } from './index.js';
16
+ import { ModuleManager } from '../module-manager.js';
17
+ import type { ModuleSettingOptions } from '../interfaces/project-interfaces.js';
18
+ import type { Project } from '../containers/project.js';
19
+ import { readCsvFile } from '../utils/csv.js';
20
+ import { resourceName } from '../utils/resource-utils.js';
21
+ import { TemplateResource } from '../resources/template-resource.js';
22
+
23
+ export class Import {
24
+ private moduleManager: ModuleManager;
25
+ constructor(
26
+ private project: Project,
27
+ private createCmd: Create,
28
+ ) {
29
+ this.moduleManager = new ModuleManager(this.project, this);
30
+ }
31
+
32
+ /**
33
+ * Imports cards based on a csv file
34
+ * @param csvFilePath path to the csv file
35
+ * @param parentCardKey the cards in the csv file will be created under this card
36
+ * @returns card keys of the imported cards
37
+ */
38
+ public async importCsv(
39
+ csvFilePath: string,
40
+ parentCardKey?: string,
41
+ ): Promise<string[]> {
42
+ const csv = await readCsvFile(csvFilePath);
43
+
44
+ const isValid = Validate.getInstance().validateJson(csv, 'csvSchema');
45
+ if (isValid.length !== 0) {
46
+ throw new Error(isValid);
47
+ }
48
+
49
+ const importedCards = [];
50
+
51
+ for (const row of csv) {
52
+ const { title, template, description, labels, ...customFields } = row;
53
+ const templateResource = new TemplateResource(
54
+ this.project,
55
+ resourceName(template),
56
+ );
57
+ const templateObject = templateResource.templateObject();
58
+ if (!templateObject) {
59
+ throw new Error(`Template '${template}' not found`);
60
+ }
61
+
62
+ const templateCards = await templateObject.cards();
63
+ if (templateCards.length !== 1) {
64
+ console.warn(
65
+ `Template '${template}' for card '${title}' does not have exactly one card. Skipping row.`,
66
+ );
67
+ continue;
68
+ }
69
+
70
+ // Create card
71
+ const cards = await this.createCmd.createCard(template, parentCardKey);
72
+
73
+ if (cards.length !== 1) {
74
+ throw new Error('Card not created');
75
+ }
76
+ const cardKey = cards[0].key;
77
+ const card = await this.project.findSpecificCard(cardKey, {
78
+ metadata: true,
79
+ });
80
+ const cardType = await this.project.resource<CardType>(
81
+ card?.metadata?.cardType || '',
82
+ );
83
+
84
+ if (!cardType) {
85
+ throw new Error(`Card type not found for card ${cardKey}`);
86
+ }
87
+
88
+ if (description) {
89
+ await this.project.updateCardContent(cardKey, description);
90
+ }
91
+
92
+ if (labels) {
93
+ for (const label of labels.split(' ')) {
94
+ try {
95
+ await this.createCmd.createLabel(cardKey, label);
96
+ } catch (e) {
97
+ console.error(
98
+ `Failed to create label ${label}: ${e instanceof Error ? e.message : 'Unknown error'}`,
99
+ );
100
+ }
101
+ }
102
+ }
103
+
104
+ await this.project.updateCardMetadataKey(cardKey, 'title', title);
105
+ for (const [key, value] of Object.entries(customFields)) {
106
+ if (cardType.customFields.find((field) => field.name === key)) {
107
+ await this.project.updateCardMetadataKey(cardKey, key, value);
108
+ }
109
+ }
110
+ importedCards.push(cardKey);
111
+ }
112
+ return importedCards;
113
+ }
114
+
115
+ /**
116
+ * Imports a module to a project. Copies resources to the project.
117
+ * Resources will be added to a new directory under '.cards/modules'.
118
+ * The name of the new folder will be module's prefix.
119
+ *
120
+ * Note that file references are relative, and thus URI must be
121
+ * 'file:<relative path>', instead of 'file://<relative path>'.
122
+ *
123
+ * @param source Path to module that will be imported
124
+ * @param destination Path to project that will receive the imported module
125
+ * @param options Additional options for module import. Optional.
126
+ * branch: Git branch for module from Git.
127
+ * private: If true, uses credentials to clone the repository
128
+ */
129
+ public async importModule(
130
+ source: string,
131
+ destination?: string,
132
+ options?: ModuleSettingOptions,
133
+ ) {
134
+ const gitModule = source.startsWith('https');
135
+ const modulePrefix = gitModule
136
+ ? await this.moduleManager.importGitModule(source, options)
137
+ : await this.moduleManager.importFileModule(source, destination);
138
+
139
+ if (!modulePrefix) {
140
+ throw new Error(
141
+ `Cannot find prefix for imported module '${source}'. Import cancelled.`,
142
+ );
143
+ }
144
+
145
+ // Add module as a dependency.
146
+ return this.project.importModule({
147
+ name: modulePrefix,
148
+ branch: options ? options.branch : undefined,
149
+ private: options ? options.private : undefined,
150
+ location: gitModule ? source : `file:${source}`,
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Updates all imported modules.
156
+ */
157
+ public async updateAllModules() {
158
+ return this.moduleManager.update();
159
+ }
160
+
161
+ /**
162
+ * Updates 'moduleName' module from its source.
163
+ * Modules using gitUrl, are first copied to .temp
164
+ * @param moduleName module name (prefix) to update
165
+ */
166
+ public async updateExistingModule(moduleName: string) {
167
+ await this.moduleManager.importFileModule(moduleName);
168
+ }
169
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2025
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation. 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. See the GNU Affero
9
+ 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 { Calculate } from './calculate.js';
15
+ import { Create } from './create.js';
16
+ import { Edit } from './edit.js';
17
+ import { Export } from './export.js';
18
+ import { ExportSite } from './export-site.js';
19
+ import { Import } from './import.js';
20
+ import { Move } from './move.js';
21
+ import { Remove } from './remove.js';
22
+ import { Rename } from './rename.js';
23
+ import { Show } from './show.js';
24
+ import { Transition } from './transition.js';
25
+ import { Update } from './update.js';
26
+ import { Validate } from './validate.js';
27
+
28
+ export {
29
+ Calculate,
30
+ Create,
31
+ Edit,
32
+ Export,
33
+ ExportSite,
34
+ Import,
35
+ Move,
36
+ Remove,
37
+ Rename,
38
+ Show,
39
+ Transition,
40
+ Update,
41
+ Validate,
42
+ };
@@ -0,0 +1,451 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2024
4
+
5
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
6
+
7
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
8
+
9
+ You should have received a copy of the GNU Affero General Public
10
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
+ */
12
+
13
+ // node
14
+ import { join, sep } from 'node:path';
15
+
16
+ import { ActionGuard } from '../permissions/action-guard.js';
17
+ import { copyDir, deleteDir } from '../utils/file-utils.js';
18
+ import type { Calculate } from './index.js';
19
+ import type {
20
+ Card,
21
+ FetchCardDetails,
22
+ } from '../interfaces/project-interfaces.js';
23
+ import { type Project, ResourcesFrom } from '../containers/project.js';
24
+ import {
25
+ EMPTY_RANK,
26
+ FIRST_RANK,
27
+ getRankAfter,
28
+ getRankBetween,
29
+ rebalanceRanks,
30
+ sortItems,
31
+ } from '../utils/lexorank.js';
32
+ import { isTemplateCard } from '../utils/card-utils.js';
33
+ import { resourceName } from '../utils/resource-utils.js';
34
+ import { TemplateResource } from '../resources/template-resource.js';
35
+
36
+ // @todo - we should have project wide constants, so that if we need them, only the const value needs to be changed.
37
+ const ROOT: string = 'root';
38
+
39
+ export class Move {
40
+ constructor(
41
+ private project: Project,
42
+ private calculateCmd: Calculate,
43
+ ) {}
44
+
45
+ // Fetches a card (either template or project card).
46
+ private async getCard(cardKey: string, options: FetchCardDetails) {
47
+ let card: Card | undefined;
48
+ const templateCard = await this.project.isTemplateCard(cardKey);
49
+ if (templateCard) {
50
+ card = (await this.project.allTemplateCards(options)).find(
51
+ (card) => card.key === cardKey,
52
+ );
53
+ } else {
54
+ card = await this.project.findSpecificCard(cardKey, options);
55
+ }
56
+ if (!card) {
57
+ throw new Error('Card was not found from the project');
58
+ }
59
+ return card;
60
+ }
61
+
62
+ // Returns children of a parent card or root cards
63
+ private async getSiblings(card: Card) {
64
+ const parentCardKey = card.parent || ROOT;
65
+
66
+ // since we don't know if 'root' is templateRoot or cardRoot, we need to check the card
67
+ if (parentCardKey === ROOT) {
68
+ if (isTemplateCard(card)) {
69
+ const template = this.project.createTemplateObjectFromCard(card);
70
+ if (!template) {
71
+ throw new Error(
72
+ `Cannot find template for the template card '${card.key}'`,
73
+ );
74
+ }
75
+ if (card?.path.includes(`${sep}modules${sep}`)) {
76
+ throw new Error(`Cannot rank module cards`);
77
+ }
78
+ return template.cards();
79
+ }
80
+ }
81
+
82
+ let parentCard;
83
+ if (parentCardKey !== ROOT) {
84
+ parentCard = await this.project.findSpecificCard(parentCardKey, {
85
+ children: true,
86
+ metadata: true,
87
+ });
88
+ if (!parentCard) {
89
+ throw new Error(`Card ${parentCardKey} not found from project`);
90
+ }
91
+ }
92
+
93
+ if (parentCard) {
94
+ return parentCard.children || [];
95
+ }
96
+
97
+ return this.project.showProjectCards();
98
+ }
99
+
100
+ //
101
+ private async rebalanceCards(cards: Card[]) {
102
+ const ranks = rebalanceRanks(cards.length);
103
+
104
+ cards = sortItems(cards, (item) => item.metadata?.rank || 'z');
105
+
106
+ for (let i = 0; i < cards.length; i++) {
107
+ const card = cards[i];
108
+ await this.project.updateCardMetadataKey(card.key, 'rank', ranks[i]);
109
+ }
110
+ }
111
+
112
+ // Rebalances the project recursively.
113
+ private async rebalanceProjectRecursively(cards: Card[]) {
114
+ const ranks = rebalanceRanks(cards.length);
115
+
116
+ cards = sortItems(cards, (item) => item.metadata?.rank || 'z');
117
+
118
+ for (let i = 0; i < cards.length; i++) {
119
+ const card = cards[i];
120
+ await this.project.updateCardMetadataKey(card.key, 'rank', ranks[i]);
121
+ if (card.children && card.children.length > 0) {
122
+ await this.rebalanceProjectRecursively(card.children);
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Moves card from 'destination' to 'source'.
129
+ * @param source source card to move
130
+ * @param destination destination card where source card will be moved to; or to root
131
+ */
132
+ public async moveCard(source: string, destination: string) {
133
+ if (source === ROOT) {
134
+ throw new Error('Cannot move "root"');
135
+ }
136
+ if (source === destination) {
137
+ throw new Error(`Card cannot be moved to itself`);
138
+ }
139
+ const promiseContainer = [];
140
+ promiseContainer.push(this.project.findSpecificCard(source));
141
+ if (destination !== ROOT) {
142
+ promiseContainer.push(this.project.findSpecificCard(destination));
143
+ } else {
144
+ const returnObject: Card = {
145
+ key: '',
146
+ path: this.project.paths.cardRootFolder,
147
+ children: [],
148
+ attachments: [],
149
+ };
150
+ promiseContainer.push(Promise.resolve(returnObject));
151
+ }
152
+ const [sourceCard, destinationCard] = await Promise.all(promiseContainer);
153
+
154
+ if (!sourceCard) {
155
+ throw new Error(`Card ${source} not found from project`);
156
+ }
157
+ if (!destinationCard) {
158
+ throw new Error(`Card ${destination} not found from project`);
159
+ }
160
+
161
+ if (destinationCard.path.includes(source)) {
162
+ throw new Error(`Card cannot be moved to inside itself`);
163
+ }
164
+
165
+ // Imported templates cannot be modified.
166
+ if (
167
+ destinationCard.path.includes(`${sep}modules`) ||
168
+ sourceCard.path.includes(`${sep}modules${sep}`)
169
+ ) {
170
+ throw new Error(`Cannot modify imported module templates`);
171
+ }
172
+
173
+ const bothTemplateCards =
174
+ isTemplateCard(sourceCard) && isTemplateCard(destinationCard);
175
+ const bothProjectCards =
176
+ this.project.hasCard(sourceCard.key) &&
177
+ this.project.hasCard(destinationCard.key);
178
+ if (!(bothTemplateCards || bothProjectCards)) {
179
+ throw new Error(
180
+ `Cards cannot be moved from project to template or vice versa`,
181
+ );
182
+ }
183
+
184
+ const destinationPath =
185
+ destination === ROOT
186
+ ? join(this.project.paths.cardRootFolder, sourceCard.key)
187
+ : join(destinationCard.path, 'c', sourceCard.key);
188
+
189
+ // if the card is already in the destination, do nothing
190
+ if (sourceCard.path === destinationPath) {
191
+ return;
192
+ }
193
+
194
+ // if both are project cards, make sure source card can be moved
195
+ const actionGuard = new ActionGuard(this.calculateCmd);
196
+ await actionGuard.checkPermission('move', source);
197
+
198
+ // rerank the card in the new location
199
+ // it will be the last one in the new location
200
+ let children;
201
+ if (destination !== ROOT) {
202
+ const parent = await this.project.findSpecificCard(destination, {
203
+ children: true,
204
+ metadata: true,
205
+ });
206
+ if (!parent) {
207
+ throw new Error(`Parent card ${destination} not found from project`);
208
+ }
209
+ children = parent.children;
210
+ } else {
211
+ children = await this.project.showProjectCards();
212
+ }
213
+
214
+ if (!children) {
215
+ throw new Error(`Children not found from card ${destination}`);
216
+ }
217
+
218
+ children = sortItems(children, (item) => item?.metadata?.rank || '1|z');
219
+ const lastChild = children[children.length - 1];
220
+
221
+ const rank =
222
+ lastChild && lastChild.metadata
223
+ ? getRankAfter(lastChild.metadata.rank)
224
+ : FIRST_RANK;
225
+ await this.project.updateCardMetadataKey(sourceCard.key, 'rank', rank);
226
+ await copyDir(sourceCard.path, destinationPath);
227
+ await deleteDir(sourceCard.path);
228
+ }
229
+
230
+ /**
231
+ * Ranks card using position given as 'index'.
232
+ * @param cardKey card key
233
+ * @param index to which position should card be ranked to
234
+ */
235
+ public async rankByIndex(cardKey: string, index: number) {
236
+ if (index < 0) {
237
+ throw new Error(`Index must be greater than 0`);
238
+ }
239
+ if (index === 0) {
240
+ await this.rankFirst(cardKey);
241
+ return;
242
+ }
243
+
244
+ const card = await this.project.findSpecificCard(cardKey, {
245
+ metadata: true,
246
+ parent: true,
247
+ });
248
+
249
+ if (!card || !card.parent) {
250
+ throw new Error(`Card ${cardKey} not found from project`);
251
+ }
252
+
253
+ const children = sortItems(
254
+ await this.getSiblings(card),
255
+ (item) => item.metadata?.rank || EMPTY_RANK,
256
+ );
257
+
258
+ if (!children || children.length === 0) {
259
+ throw new Error(`Children not found from card ${card.parent}`);
260
+ }
261
+
262
+ if (children.length < index) {
263
+ throw new Error(`Index ${index} is out of bounds`);
264
+ }
265
+ await this.rankCard(cardKey, children[index - 1].key);
266
+ }
267
+
268
+ /**
269
+ * Sets the rank of a card to be after another card.
270
+ * @param cardKey Card to rank
271
+ * @param beforeCardKey Card key after which the card will be ranked
272
+ */
273
+ public async rankCard(cardKey: string, beforeCardKey: string) {
274
+ const card = await this.project.findSpecificCard(cardKey, {
275
+ metadata: true,
276
+ parent: true,
277
+ });
278
+ if (!card) {
279
+ throw new Error(`Card ${cardKey} not found from project`);
280
+ }
281
+
282
+ const beforeCard = await this.project.findSpecificCard(beforeCardKey, {
283
+ metadata: true,
284
+ parent: true,
285
+ });
286
+
287
+ if (!beforeCard) {
288
+ throw new Error(`Card ${beforeCardKey} not found from project`);
289
+ }
290
+
291
+ if (beforeCard.parent !== card.parent) {
292
+ throw new Error(`Cards must be from the same parent`);
293
+ }
294
+
295
+ const children = sortItems(
296
+ await this.getSiblings(beforeCard),
297
+ (item) => item.metadata?.rank || EMPTY_RANK,
298
+ );
299
+
300
+ if (!children) {
301
+ throw new Error(`Children not found from card ${beforeCard.parent}`);
302
+ }
303
+
304
+ const beforeCardIndex = children.findIndex(
305
+ (child) => child.key === beforeCard.key,
306
+ );
307
+
308
+ if (beforeCardIndex === -1) {
309
+ throw new Error(
310
+ `Card ${beforeCardKey} is not a child of ${beforeCard.parent}`,
311
+ );
312
+ }
313
+
314
+ if (
315
+ children[beforeCardIndex].key === cardKey ||
316
+ children[beforeCardIndex + 1]?.key === cardKey
317
+ ) {
318
+ throw new Error(`Card ${cardKey} is already in the correct position`);
319
+ }
320
+
321
+ if (beforeCardIndex === children.length - 1) {
322
+ await this.project.updateCardMetadataKey(
323
+ cardKey,
324
+ 'rank',
325
+ getRankAfter(beforeCard.metadata?.rank as string),
326
+ );
327
+ } else {
328
+ await this.project.updateCardMetadataKey(
329
+ cardKey,
330
+ 'rank',
331
+ getRankBetween(
332
+ beforeCard.metadata?.rank as string,
333
+ children[beforeCardIndex + 1].metadata?.rank as string,
334
+ ),
335
+ );
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Ranks card first.
341
+ * @param cardKey card key
342
+ */
343
+ public async rankFirst(cardKey: string) {
344
+ const card = await this.getCard(cardKey, {
345
+ metadata: true,
346
+ parent: true,
347
+ });
348
+
349
+ const children = sortItems(
350
+ await this.getSiblings(card),
351
+ (item) => item.metadata?.rank || EMPTY_RANK,
352
+ );
353
+
354
+ if (!children || children.length === 0) {
355
+ throw new Error(`Children not found from card ${card.parent}`);
356
+ }
357
+
358
+ if (children[0].key === cardKey && children[0].metadata?.rank) {
359
+ return;
360
+ }
361
+
362
+ const firstRank = children[0].metadata?.rank;
363
+ if (!firstRank) {
364
+ await this.project.updateCardMetadataKey(cardKey, 'rank', FIRST_RANK);
365
+ return;
366
+ }
367
+
368
+ // Set the rank to be the first one
369
+ if (firstRank === FIRST_RANK) {
370
+ // if the first card is already at the first rank, we need to move the card to the next one
371
+ const secondRank = children[1].metadata?.rank;
372
+ if (!secondRank) {
373
+ throw new Error(`Second rank not found`);
374
+ }
375
+ const rankBetween = getRankBetween(firstRank, secondRank);
376
+ await this.project.updateCardMetadataKey(
377
+ children[0].key,
378
+ 'rank',
379
+ rankBetween,
380
+ );
381
+ await this.project.updateCardMetadataKey(cardKey, 'rank', firstRank);
382
+ } else {
383
+ // if the card is not at the first rank, we just use the first rank
384
+ await this.project.updateCardMetadataKey(cardKey, 'rank', FIRST_RANK);
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Rebalances the ranks of the children of a card.
390
+ * @param parentCardKey parent card key
391
+ */
392
+ public async rebalanceChildren(parentCardKey: string) {
393
+ const parentCard = await this.project.findSpecificCard(parentCardKey, {
394
+ children: true,
395
+ metadata: true,
396
+ });
397
+ if (!parentCard || !parentCard.children) {
398
+ throw new Error(`Card ${parentCardKey} not found from project`);
399
+ }
400
+ await this.rebalanceCards(parentCard.children);
401
+ }
402
+
403
+ /**
404
+ * Rebalances the ranks of the cards in the whole project, including templates
405
+ * Can be used even if the ranks do not exist
406
+ */
407
+ public async rebalanceProject() {
408
+ const cards = await this.project.showProjectCards();
409
+
410
+ await this.rebalanceProjectRecursively(cards);
411
+
412
+ // rebalance templates
413
+ const templates = await this.project.templates(ResourcesFrom.localOnly);
414
+ for (const template of templates) {
415
+ const templateResource = new TemplateResource(
416
+ this.project,
417
+ resourceName(template.name),
418
+ );
419
+ const templateObject = templateResource.templateObject();
420
+
421
+ if (!templateObject) {
422
+ throw new Error(`Template '${template.name}' not found`);
423
+ }
424
+
425
+ const templateCards = await templateObject.cards('', {
426
+ parent: true,
427
+ metadata: true,
428
+ });
429
+
430
+ const cardGroups = templateCards.reduce(
431
+ (result, card) => {
432
+ // template card root cards have a parent(the template itself) so this shouldn't happen
433
+ if (!card.parent) {
434
+ return result;
435
+ }
436
+ // if the parent does not exist yet in the result, we create it
437
+ if (!result[card.parent]) {
438
+ result[card.parent] = [];
439
+ }
440
+ result[card.parent].push(card);
441
+ return result;
442
+ },
443
+ {} as Record<string, Card[]>,
444
+ );
445
+
446
+ for (const [, cards] of Object.entries(cardGroups)) {
447
+ await this.rebalanceCards(cards);
448
+ }
449
+ }
450
+ }
451
+ }