@openinc/parse-server-opendash 4.1.1 → 4.1.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 (163) hide show
  1. package/dist/featuremap.json +27 -12
  2. package/dist/features/config/helper/customOptions.js +9 -0
  3. package/dist/features/config/helper/isFeatureEnabled.d.ts +1 -1
  4. package/dist/features/config/helper/isFeatureEnabled.js +2 -0
  5. package/dist/features/meta/index.d.ts +1 -0
  6. package/dist/features/meta/index.js +5 -0
  7. package/dist/features/meta/services/registerMetaContext.d.ts +29 -0
  8. package/dist/features/meta/services/registerMetaContext.js +208 -0
  9. package/dist/features/permissions/types/Permissions.d.ts +24 -1
  10. package/dist/features/permissions/types/Permissions.js +34 -0
  11. package/dist/features/ruleset/applyRuleSet.d.ts +41 -0
  12. package/dist/features/ruleset/applyRuleSet.js +81 -0
  13. package/dist/features/schema/helper/defaultHandler.js +13 -0
  14. package/dist/features/schema/helper/migrationMode.d.ts +26 -0
  15. package/dist/features/schema/helper/migrationMode.js +48 -0
  16. package/dist/features/schema/index.d.ts +1 -0
  17. package/dist/features/schema/index.js +3 -1
  18. package/dist/features/schema/services/beforeSaveHook.js +21 -0
  19. package/dist/features/translations/services/initTranslation.js +1 -1
  20. package/dist/hooks/Chat_Message.d.ts +1 -0
  21. package/dist/hooks/Chat_Message.js +179 -0
  22. package/dist/hooks/Meta_Config.js +19 -10
  23. package/dist/hooks/Meta_Context.d.ts +6 -0
  24. package/dist/hooks/Meta_Context.js +16 -0
  25. package/dist/hooks/Meta_Entry.js +20 -10
  26. package/dist/hooks/Service_Equipment.d.ts +1 -0
  27. package/dist/hooks/Service_Equipment.js +38 -0
  28. package/dist/hooks/Service_FormConfig.d.ts +1 -0
  29. package/dist/hooks/Service_FormConfig.js +17 -0
  30. package/dist/hooks/Service_Project.d.ts +1 -0
  31. package/dist/hooks/Service_Project.js +17 -0
  32. package/dist/hooks/Service_Schedule.d.ts +1 -0
  33. package/dist/hooks/Service_Schedule.js +24 -0
  34. package/dist/hooks/Service_Schedule_Execution.d.ts +1 -0
  35. package/dist/hooks/Service_Schedule_Execution.js +17 -0
  36. package/dist/hooks/Service_Schedule_Template.d.ts +1 -0
  37. package/dist/hooks/Service_Schedule_Template.js +17 -0
  38. package/dist/hooks/Service_Status.d.ts +1 -0
  39. package/dist/hooks/Service_Status.js +32 -0
  40. package/dist/hooks/Service_Tag.d.ts +1 -0
  41. package/dist/hooks/Service_Tag.js +168 -0
  42. package/dist/hooks/Service_Ticket.d.ts +1 -0
  43. package/dist/hooks/Service_Ticket.js +433 -0
  44. package/dist/hooks/Service_TriggerState.d.ts +1 -0
  45. package/dist/hooks/Service_TriggerState.js +17 -0
  46. package/dist/hooks/_ChangeLog.js +27 -6
  47. package/dist/i18n/de-DE/chat.json +18 -0
  48. package/dist/i18n/de-DE/openservice.json +14 -0
  49. package/dist/i18n/en-US/chat.json +18 -0
  50. package/dist/i18n/en-US/openservice.json +14 -0
  51. package/dist/scripts/backfill-meta-contexts.d.ts +1 -0
  52. package/dist/scripts/backfill-meta-contexts.js +100 -0
  53. package/dist/scripts/migration/lib/acl.d.ts +52 -0
  54. package/dist/scripts/migration/lib/acl.js +79 -0
  55. package/dist/scripts/migration/lib/articleMeta.d.ts +47 -0
  56. package/dist/scripts/migration/lib/articleMeta.js +132 -0
  57. package/dist/scripts/migration/lib/env.d.ts +56 -0
  58. package/dist/scripts/migration/lib/env.js +106 -0
  59. package/dist/scripts/migration/lib/latest.d.ts +47 -0
  60. package/dist/scripts/migration/lib/latest.js +115 -0
  61. package/dist/scripts/migration/lib/map.d.ts +53 -0
  62. package/dist/scripts/migration/lib/map.js +113 -0
  63. package/dist/scripts/migration/lib/material.d.ts +47 -0
  64. package/dist/scripts/migration/lib/material.js +51 -0
  65. package/dist/scripts/migration/lib/message.d.ts +78 -0
  66. package/dist/scripts/migration/lib/message.js +115 -0
  67. package/dist/scripts/migration/lib/pass.d.ts +67 -0
  68. package/dist/scripts/migration/lib/pass.js +89 -0
  69. package/dist/scripts/migration/lib/ptr.d.ts +32 -0
  70. package/dist/scripts/migration/lib/ptr.js +38 -0
  71. package/dist/scripts/migration/lib/report.d.ts +41 -0
  72. package/dist/scripts/migration/lib/report.js +78 -0
  73. package/dist/scripts/migration/lib/rest.d.ts +47 -0
  74. package/dist/scripts/migration/lib/rest.js +68 -0
  75. package/dist/scripts/migration/lib/ticket.d.ts +72 -0
  76. package/dist/scripts/migration/lib/ticket.js +192 -0
  77. package/dist/scripts/migration/passes/01-statuses.d.ts +12 -0
  78. package/dist/scripts/migration/passes/01-statuses.js +122 -0
  79. package/dist/scripts/migration/passes/02-tags.d.ts +13 -0
  80. package/dist/scripts/migration/passes/02-tags.js +150 -0
  81. package/dist/scripts/migration/passes/03-equipment.d.ts +14 -0
  82. package/dist/scripts/migration/passes/03-equipment.js +176 -0
  83. package/dist/scripts/migration/passes/04-projects.d.ts +27 -0
  84. package/dist/scripts/migration/passes/04-projects.js +241 -0
  85. package/dist/scripts/migration/passes/05-articles.d.ts +25 -0
  86. package/dist/scripts/migration/passes/05-articles.js +203 -0
  87. package/dist/scripts/migration/passes/06-formconfigs.d.ts +17 -0
  88. package/dist/scripts/migration/passes/06-formconfigs.js +166 -0
  89. package/dist/scripts/migration/passes/07-meta-configs.d.ts +46 -0
  90. package/dist/scripts/migration/passes/07-meta-configs.js +185 -0
  91. package/dist/scripts/migration/passes/08-tickets.d.ts +27 -0
  92. package/dist/scripts/migration/passes/08-tickets.js +353 -0
  93. package/dist/scripts/migration/passes/09-ticket-meta.d.ts +12 -0
  94. package/dist/scripts/migration/passes/09-ticket-meta.js +136 -0
  95. package/dist/scripts/migration/passes/10-chat.d.ts +17 -0
  96. package/dist/scripts/migration/passes/10-chat.js +230 -0
  97. package/dist/scripts/migration/passes/11-assets.d.ts +17 -0
  98. package/dist/scripts/migration/passes/11-assets.js +93 -0
  99. package/dist/scripts/migration/passes/12-history.d.ts +24 -0
  100. package/dist/scripts/migration/passes/12-history.js +332 -0
  101. package/dist/scripts/migration/run.d.ts +1 -0
  102. package/dist/scripts/migration/run.js +148 -0
  103. package/dist/scripts/migration/verify.d.ts +1 -0
  104. package/dist/scripts/migration/verify.js +620 -0
  105. package/dist/scripts/seed-v1-testdata.d.ts +41 -0
  106. package/dist/scripts/seed-v1-testdata.js +543 -0
  107. package/dist/types/BDE_Article.d.ts +3 -0
  108. package/dist/types/BDE_Article.js +6 -0
  109. package/dist/types/Chat_Message.d.ts +49 -0
  110. package/dist/types/Chat_Message.js +87 -0
  111. package/dist/types/MES_RessourceOverview.d.ts +38 -0
  112. package/dist/types/MES_RessourceOverview.js +63 -0
  113. package/dist/types/Meta_Context.d.ts +43 -0
  114. package/dist/types/Meta_Context.js +75 -0
  115. package/dist/types/Service_Equipment.d.ts +33 -0
  116. package/dist/types/Service_Equipment.js +57 -0
  117. package/dist/types/Service_FormConfig.d.ts +33 -0
  118. package/dist/types/Service_FormConfig.js +57 -0
  119. package/dist/types/Service_MeterReading.d.ts +35 -0
  120. package/dist/types/Service_MeterReading.js +57 -0
  121. package/dist/types/Service_Project.d.ts +53 -0
  122. package/dist/types/Service_Project.js +93 -0
  123. package/dist/types/Service_Schedule.d.ts +66 -0
  124. package/dist/types/Service_Schedule.js +117 -0
  125. package/dist/types/Service_Schedule_Execution.d.ts +38 -0
  126. package/dist/types/Service_Schedule_Execution.js +63 -0
  127. package/dist/types/Service_Schedule_Template.d.ts +36 -0
  128. package/dist/types/Service_Schedule_Template.js +60 -0
  129. package/dist/types/Service_Status.d.ts +30 -0
  130. package/dist/types/Service_Status.js +51 -0
  131. package/dist/types/Service_Tag.d.ts +30 -0
  132. package/dist/types/Service_Tag.js +51 -0
  133. package/dist/types/Service_Ticket.d.ts +92 -0
  134. package/dist/types/Service_Ticket.js +165 -0
  135. package/dist/types/Service_TicketCounter.d.ts +18 -0
  136. package/dist/types/Service_TicketCounter.js +27 -0
  137. package/dist/types/Service_TriggerState.d.ts +31 -0
  138. package/dist/types/Service_TriggerState.js +51 -0
  139. package/dist/types/_Session.d.ts +0 -1
  140. package/dist/types/_User.d.ts +2 -6
  141. package/dist/types/custom/MetaField.d.ts +9 -0
  142. package/dist/types/custom/TicketMaterial.d.ts +32 -0
  143. package/dist/types/custom/TicketMaterial.js +2 -0
  144. package/dist/types/index.d.ts +30 -0
  145. package/dist/types/index.js +33 -3
  146. package/package.json +6 -3
  147. package/schema/BDE_Article.json +3 -0
  148. package/schema/Changelog.json +3 -6
  149. package/schema/Chat_Message.json +88 -0
  150. package/schema/Meta_Context.json +78 -0
  151. package/schema/Service_Equipment.json +55 -0
  152. package/schema/Service_FormConfig.json +66 -0
  153. package/schema/Service_MeterReading.json +54 -0
  154. package/schema/Service_MigrationMap.json +55 -0
  155. package/schema/Service_Project.json +77 -0
  156. package/schema/Service_Schedule.json +101 -0
  157. package/schema/Service_Schedule_Execution.json +58 -0
  158. package/schema/Service_Schedule_Template.json +73 -0
  159. package/schema/Service_Status.json +50 -0
  160. package/schema/Service_Tag.json +50 -0
  161. package/schema/Service_Ticket.json +114 -0
  162. package/schema/Service_TicketCounter.json +38 -0
  163. package/schema/Service_TriggerState.json +50 -0
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.pass10 = void 0;
7
+ const node_1 = __importDefault(require("parse/node"));
8
+ const index_js_1 = require("../../../types/index.js");
9
+ const acl_js_1 = require("../lib/acl.js");
10
+ const map_js_1 = require("../lib/map.js");
11
+ const ptr_js_1 = require("../lib/ptr.js");
12
+ const latest_js_1 = require("../lib/latest.js");
13
+ const message_js_1 = require("../lib/message.js");
14
+ const pass_js_1 = require("../lib/pass.js");
15
+ /**
16
+ * Pass 10 — v1 comments → `OD3_Chat_Message`, with their media as attachments.
17
+ *
18
+ * `Maintenance_Message` splits two ways: rows whose `data.type` is `"todo"` became
19
+ * `Ticket.checklist` in pass 08; everything else is a comment and lands here.
20
+ *
21
+ * Attachments: `Maintenance_Media` rows are bare Parse Files with no `OD3_Assets`
22
+ * row behind them. Rather than leaving them as URL-only attachments, this pass
23
+ * creates an `OD3_Assets` row per file in the ticket's shared context, so a legacy
24
+ * comment attachment also shows up in the detail page's asset library — the same
25
+ * place pass 11 puts the form-uploaded files. One destination regardless of which
26
+ * v1 store held the file.
27
+ *
28
+ * Ordering: top-level messages are written before replies, so `parent` resolves.
29
+ */
30
+ exports.pass10 = {
31
+ id: "10",
32
+ title: "chat",
33
+ needs: ["08"],
34
+ async run({ config, map, report, entry }) {
35
+ const messages = await (0, pass_js_1.all)(() => (0, pass_js_1.scopeToTenant)(new node_1.default.Query(index_js_1.Maintenance_Message), config.tenantId), config.batchSize);
36
+ if (messages.length === 0)
37
+ return;
38
+ // Todos became Ticket.checklist in pass 08. v1's own machine-generated change
39
+ // log (type "system") is dropped: pass 12 reconstructs that history properly as
40
+ // changelog entries, so migrating it as chat too would duplicate the timeline
41
+ // and fill the thread with untranslated i18n keys. See lib/message.ts.
42
+ const systemGenerated = messages.filter((m) => (0, message_js_1.isSystemGenerated)(m)).length;
43
+ const comments = messages.filter((m) => !(0, message_js_1.isTodo)(m) && !(0, message_js_1.isSystemGenerated)(m));
44
+ // Replies after their parents. v1 replies are shallow in practice, but a
45
+ // depth sort costs nothing and removes the assumption.
46
+ const byId = new Map();
47
+ for (const m of comments)
48
+ if (m.id)
49
+ byId.set(m.id, m);
50
+ const depth = (message) => {
51
+ let d = 0;
52
+ let cursor = (0, pass_js_1.refId)(message.get("replyTo"));
53
+ const guard = new Set();
54
+ while (cursor && byId.has(cursor) && !guard.has(cursor)) {
55
+ guard.add(cursor);
56
+ d += 1;
57
+ cursor = (0, pass_js_1.refId)(byId.get(cursor).get("replyTo"));
58
+ }
59
+ return d;
60
+ };
61
+ const ordered = [...comments].sort((a, b) => {
62
+ const byDepth = depth(a) - depth(b);
63
+ if (byDepth !== 0)
64
+ return byDepth;
65
+ return (a.createdAt?.getTime() ?? 0) - (b.createdAt?.getTime() ?? 0);
66
+ });
67
+ let orphaned = 0;
68
+ let attachmentsMigrated = 0;
69
+ for (const message of ordered) {
70
+ const sourceId = message.id;
71
+ if (!sourceId)
72
+ continue;
73
+ const oldTicketId = message.get("referencedObjectId");
74
+ const newTicketId = oldTicketId
75
+ ? map.get(index_js_1.Maintenance_Ticket.className, oldTicketId)
76
+ : undefined;
77
+ if (!newTicketId) {
78
+ // A message can reference something other than a ticket (`classname` is
79
+ // free-form in v1), so this is expected noise rather than an error.
80
+ orphaned += 1;
81
+ entry.skipped++;
82
+ continue;
83
+ }
84
+ const existingId = map.get(index_js_1.Maintenance_Message.className, sourceId);
85
+ const target = existingId
86
+ ? new index_js_1.Chat_Message({ objectId: existingId })
87
+ : new index_js_1.Chat_Message();
88
+ const tenantIdOfMessage = (0, pass_js_1.refId)(message.get("tenant"));
89
+ const authorId = (0, pass_js_1.refId)(message.get("user"));
90
+ const contextString = (0, ptr_js_1.contextToString)((0, ptr_js_1.ticketContext)(newTicketId));
91
+ // v1 had a title and a body; the chat has one text field. A real subject
92
+ // becomes a bold first line — but v1 stores an i18n *key* there when the user
93
+ // left the subject blank, and rendering that key would be visible nonsense.
94
+ // See lib/message.ts.
95
+ const text = (0, message_js_1.chatText)(message);
96
+ if (!text) {
97
+ // `Chat_Message.text` is required.
98
+ report.warn(entry, `message ${sourceId} has no usable text — skipped`);
99
+ entry.skipped++;
100
+ continue;
101
+ }
102
+ target.set("context", contextString);
103
+ target.set("text", text);
104
+ target.set("deleted", false);
105
+ if (authorId)
106
+ target.set("author", (0, ptr_js_1.userPtr)(authorId));
107
+ if (tenantIdOfMessage) {
108
+ target.set("tenant", (0, ptr_js_1.tenantPtr)(tenantIdOfMessage));
109
+ }
110
+ const replyToSourceId = (0, pass_js_1.refId)(message.get("replyTo"));
111
+ const replyToTargetId = replyToSourceId
112
+ ? map.get(index_js_1.Maintenance_Message.className, replyToSourceId)
113
+ : undefined;
114
+ const parentPtr = (0, ptr_js_1.maybePtr)(index_js_1.Chat_Message.className, replyToTargetId);
115
+ if (parentPtr)
116
+ target.set("parent", parentPtr);
117
+ else if (replyToSourceId) {
118
+ report.warn(entry, `message ${sourceId} replies to ${replyToSourceId}, which was not migrated ` +
119
+ `— it becomes a top-level comment`);
120
+ }
121
+ // ── media → Assets rows + attachments ──────────────────────────────────
122
+ const attachments = [];
123
+ const mediaRows = (await (0, latest_js_1.relationObjects)(message, "media", config.batchSize));
124
+ for (const media of mediaRows) {
125
+ const file = media.get("media");
126
+ const url = file?.url();
127
+ if (!url) {
128
+ report.warn(entry, `media ${media.id} has no file — skipped`);
129
+ continue;
130
+ }
131
+ const name = media.get("description") || file?.name() || "Datei";
132
+ // Reuse the Assets row on a re-run rather than duplicating the file.
133
+ let assetId = media.id
134
+ ? map.get(index_js_1.Maintenance_Media.className, media.id)
135
+ : undefined;
136
+ // `OD3_Assets.user` is required by the schema, but v1's
137
+ // `Maintenance_Message.user` is optional — so an author-less comment
138
+ // cannot become a library entry. Keep it as a URL-only attachment
139
+ // (still visible in the chat) rather than failing the whole message.
140
+ if (!assetId && !config.dryRun && media.id && !authorId) {
141
+ report.warn(entry, `media ${media.id} on message ${sourceId} has no author; ` +
142
+ `OD3_Assets.user is required, so it stays a URL-only attachment ` +
143
+ `and will not appear in the ticket's asset library`);
144
+ }
145
+ if (!assetId && !config.dryRun && media.id && authorId) {
146
+ const asset = new index_js_1.Assets();
147
+ asset.set("context", contextString);
148
+ asset.set("description", name);
149
+ asset.set("file", file);
150
+ asset.set("user", (0, ptr_js_1.userPtr)(authorId));
151
+ if (tenantIdOfMessage) {
152
+ asset.set("tenant", (0, ptr_js_1.tenantPtr)(tenantIdOfMessage));
153
+ }
154
+ asset.setACL((0, acl_js_1.migrationAcl)({
155
+ tenantId: tenantIdOfMessage,
156
+ userId: authorId,
157
+ allowTenantUserWrite: true,
158
+ }));
159
+ try {
160
+ assetId = await (0, acl_js_1.saveMigrated)(config, asset, {
161
+ className: index_js_1.Assets.className,
162
+ isNew: true,
163
+ createdAt: media.createdAt,
164
+ updatedAt: media.updatedAt,
165
+ });
166
+ if (assetId) {
167
+ await map.record(index_js_1.Maintenance_Media.className, media.id, index_js_1.Assets.className, assetId);
168
+ attachmentsMigrated += 1;
169
+ }
170
+ }
171
+ catch (error) {
172
+ report.error(entry, `media ${media.id} → Assets failed: ${error.message}`);
173
+ }
174
+ }
175
+ attachments.push({
176
+ name,
177
+ url,
178
+ ...(assetId ? { assetId } : {}),
179
+ });
180
+ }
181
+ if (attachments.length)
182
+ target.set("attachments", attachments);
183
+ if (!existingId) {
184
+ target.setACL((0, acl_js_1.migrationAcl)({
185
+ tenantId: tenantIdOfMessage,
186
+ userId: authorId,
187
+ allowTenantUserWrite: true,
188
+ }));
189
+ }
190
+ if (config.dryRun) {
191
+ existingId ? entry.updated++ : entry.created++;
192
+ if (!existingId) {
193
+ await map.record(index_js_1.Maintenance_Message.className, sourceId, index_js_1.Chat_Message.className, map_js_1.MigrationMap.dryId(sourceId));
194
+ }
195
+ continue;
196
+ }
197
+ try {
198
+ const newId = await (0, acl_js_1.saveMigrated)(config, target, {
199
+ className: index_js_1.Chat_Message.className,
200
+ isNew: !existingId,
201
+ createdAt: message.createdAt,
202
+ updatedAt: message.updatedAt,
203
+ });
204
+ if (existingId) {
205
+ entry.updated++;
206
+ }
207
+ else {
208
+ entry.created++;
209
+ await map.record(index_js_1.Maintenance_Message.className, sourceId, index_js_1.Chat_Message.className, newId);
210
+ }
211
+ }
212
+ catch (error) {
213
+ report.error(entry, `message ${sourceId} failed: ${error.message}`);
214
+ }
215
+ }
216
+ if (systemGenerated > 0) {
217
+ report.info(entry, `${systemGenerated} v1 system-generated change-log message(s) skipped — ` +
218
+ `pass 12 reconstructs that history as changelog entries instead`);
219
+ entry.skipped += systemGenerated;
220
+ }
221
+ if (orphaned > 0) {
222
+ report.info(entry, `${orphaned} message(s) referenced an object that is not a migrated ticket ` +
223
+ `(v1 'referencedObjectId' is not ticket-only) — skipped`);
224
+ }
225
+ if (attachmentsMigrated > 0) {
226
+ report.info(entry, `${attachmentsMigrated} Maintenance_Media file(s) became Assets rows in the ` +
227
+ `ticket context`);
228
+ }
229
+ },
230
+ };
@@ -0,0 +1,17 @@
1
+ import { type Pass } from "../lib/pass.js";
2
+ /**
3
+ * Pass 11 — re-address v1 ticket uploads onto the v2 ticket context.
4
+ *
5
+ * v1's ticket form wrote uploads to `OD3_Assets` with
6
+ * `context = "ticket-media-<ticketId>"`. v2 addresses everything attached to a
7
+ * ticket by the stringified context array `["service","ticket",<id>]` — the same
8
+ * key the chat uses — and the detail page renders an `AssetLibrary` on it.
9
+ *
10
+ * So the files themselves need no copying: only the `context` string changes, from
11
+ * the v1 prefix form to the v2 array form, carrying the *new* ticket id.
12
+ *
13
+ * v1 also wrote `ticket-media-temp-<uuid>` contexts while a ticket was still being
14
+ * created. Those are orphans by definition (the ticket id was unknown), and are
15
+ * reported rather than rewritten.
16
+ */
17
+ export declare const pass11: Pass;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.pass11 = void 0;
7
+ const node_1 = __importDefault(require("parse/node"));
8
+ const index_js_1 = require("../../../types/index.js");
9
+ const acl_js_1 = require("../lib/acl.js");
10
+ const ptr_js_1 = require("../lib/ptr.js");
11
+ const pass_js_1 = require("../lib/pass.js");
12
+ /** The v1 context prefix the ticket form wrote uploads under. */
13
+ const LEGACY_PREFIX = "ticket-media-";
14
+ /**
15
+ * Pass 11 — re-address v1 ticket uploads onto the v2 ticket context.
16
+ *
17
+ * v1's ticket form wrote uploads to `OD3_Assets` with
18
+ * `context = "ticket-media-<ticketId>"`. v2 addresses everything attached to a
19
+ * ticket by the stringified context array `["service","ticket",<id>]` — the same
20
+ * key the chat uses — and the detail page renders an `AssetLibrary` on it.
21
+ *
22
+ * So the files themselves need no copying: only the `context` string changes, from
23
+ * the v1 prefix form to the v2 array form, carrying the *new* ticket id.
24
+ *
25
+ * v1 also wrote `ticket-media-temp-<uuid>` contexts while a ticket was still being
26
+ * created. Those are orphans by definition (the ticket id was unknown), and are
27
+ * reported rather than rewritten.
28
+ */
29
+ exports.pass11 = {
30
+ id: "11",
31
+ title: "assets",
32
+ needs: ["08"],
33
+ async run({ config, map, report, entry }) {
34
+ let tempContexts = 0;
35
+ let unmapped = 0;
36
+ // Collect the full set BEFORE writing anything.
37
+ //
38
+ // This pass is the one place where the rows being written are the same rows
39
+ // being read: rewriting `context` removes the asset from
40
+ // `startsWith(LEGACY_PREFIX)`. Paging that query with `skip` while shrinking
41
+ // its result set makes every page after the first jump over unprocessed rows —
42
+ // with a 500-row batch and 1000 assets, roughly half would be silently missed.
43
+ // Reading everything up front costs one array of small objects and removes the
44
+ // hazard entirely.
45
+ const assets = await (0, pass_js_1.all)(() => new node_1.default.Query(index_js_1.Assets).startsWith("context", LEGACY_PREFIX), config.batchSize);
46
+ for (const asset of assets) {
47
+ const context = asset.get("context");
48
+ if (!context)
49
+ continue;
50
+ const suffix = context.slice(LEGACY_PREFIX.length);
51
+ if (suffix.startsWith("temp-")) {
52
+ // Left behind by an abandoned create; there is no ticket to attach to.
53
+ tempContexts += 1;
54
+ entry.skipped++;
55
+ continue;
56
+ }
57
+ const newTicketId = map.get(index_js_1.Maintenance_Ticket.className, suffix);
58
+ if (!newTicketId) {
59
+ unmapped += 1;
60
+ entry.skipped++;
61
+ continue;
62
+ }
63
+ const nextContext = (0, ptr_js_1.contextToString)((0, ptr_js_1.ticketContext)(newTicketId));
64
+ if (context === nextContext) {
65
+ entry.skipped++;
66
+ continue;
67
+ }
68
+ if (config.dryRun) {
69
+ entry.updated++;
70
+ continue;
71
+ }
72
+ asset.set("context", nextContext);
73
+ try {
74
+ // Only the context changes — the file, its ACL and its timestamps are
75
+ // deliberately left alone. An update, so it goes over the SDK: Parse ignores
76
+ // a supplied createdAt on update anyway.
77
+ await asset.save(null, acl_js_1.MIGRATION_SAVE);
78
+ entry.updated++;
79
+ }
80
+ catch (error) {
81
+ report.error(entry, `asset ${asset.id} context rewrite failed: ${error.message}`);
82
+ }
83
+ }
84
+ if (tempContexts > 0) {
85
+ report.info(entry, `${tempContexts} asset(s) still on a "ticket-media-temp-*" context — orphans ` +
86
+ `from abandoned ticket creation, left as they are`);
87
+ }
88
+ if (unmapped > 0) {
89
+ report.info(entry, `${unmapped} asset(s) reference a ticket id that was not migrated — left as ` +
90
+ `they are (their ticket may be outside the --tenant scope)`);
91
+ }
92
+ },
93
+ };
@@ -0,0 +1,24 @@
1
+ import { type Pass } from "../lib/pass.js";
2
+ /**
3
+ * Pass 12 — replay the v1 side tables into `OD3_Changelog`.
4
+ *
5
+ * The v2 ticket detail page already aggregates changelog entries for
6
+ * `OD3_Service_Ticket` by objectId and renders each field through that field's own
7
+ * Display component, so entries written here need no UI work to show up.
8
+ *
9
+ * Three things this depends on and cannot work around:
10
+ *
11
+ * - **Historical `createdAt`.** The timeline sorts, groups and labels purely by
12
+ * `entry.createdAt`. Under the master key Parse would stamp `now` on every entry
13
+ * and the whole trail would collapse onto the migration date.
14
+ * - **`ACL` inside the snapshot.** `beforeSave(OD3_Changelog)` mirrors the changed
15
+ * object's ACL, read from `value.ACL` / `original.ACL`. Omit it and history ends
16
+ * up more restrictive than the ticket it describes.
17
+ * - **Parse wire types in the snapshot.** `{__type:"Pointer"}` / `{__type:"Date"}`
18
+ * are what make the diff render reference pills and dates instead of raw JSON.
19
+ *
20
+ * By construction the final snapshot of the replay equals the flat ticket pass 08
21
+ * wrote — which `verify.ts` checks, and which is the strongest signal in the whole
22
+ * migration that both halves agree.
23
+ */
24
+ export declare const pass12: Pass;