@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.
- package/dist/featuremap.json +27 -12
- package/dist/features/config/helper/customOptions.js +9 -0
- package/dist/features/config/helper/isFeatureEnabled.d.ts +1 -1
- package/dist/features/config/helper/isFeatureEnabled.js +2 -0
- package/dist/features/meta/index.d.ts +1 -0
- package/dist/features/meta/index.js +5 -0
- package/dist/features/meta/services/registerMetaContext.d.ts +29 -0
- package/dist/features/meta/services/registerMetaContext.js +208 -0
- package/dist/features/permissions/types/Permissions.d.ts +24 -1
- package/dist/features/permissions/types/Permissions.js +34 -0
- package/dist/features/ruleset/applyRuleSet.d.ts +41 -0
- package/dist/features/ruleset/applyRuleSet.js +81 -0
- package/dist/features/schema/helper/defaultHandler.js +13 -0
- package/dist/features/schema/helper/migrationMode.d.ts +26 -0
- package/dist/features/schema/helper/migrationMode.js +48 -0
- package/dist/features/schema/index.d.ts +1 -0
- package/dist/features/schema/index.js +3 -1
- package/dist/features/schema/services/beforeSaveHook.js +21 -0
- package/dist/features/translations/services/initTranslation.js +1 -1
- package/dist/hooks/Chat_Message.d.ts +1 -0
- package/dist/hooks/Chat_Message.js +179 -0
- package/dist/hooks/Meta_Config.js +19 -10
- package/dist/hooks/Meta_Context.d.ts +6 -0
- package/dist/hooks/Meta_Context.js +16 -0
- package/dist/hooks/Meta_Entry.js +20 -10
- package/dist/hooks/Service_Equipment.d.ts +1 -0
- package/dist/hooks/Service_Equipment.js +38 -0
- package/dist/hooks/Service_FormConfig.d.ts +1 -0
- package/dist/hooks/Service_FormConfig.js +17 -0
- package/dist/hooks/Service_Project.d.ts +1 -0
- package/dist/hooks/Service_Project.js +17 -0
- package/dist/hooks/Service_Schedule.d.ts +1 -0
- package/dist/hooks/Service_Schedule.js +24 -0
- package/dist/hooks/Service_Schedule_Execution.d.ts +1 -0
- package/dist/hooks/Service_Schedule_Execution.js +17 -0
- package/dist/hooks/Service_Schedule_Template.d.ts +1 -0
- package/dist/hooks/Service_Schedule_Template.js +17 -0
- package/dist/hooks/Service_Status.d.ts +1 -0
- package/dist/hooks/Service_Status.js +32 -0
- package/dist/hooks/Service_Tag.d.ts +1 -0
- package/dist/hooks/Service_Tag.js +168 -0
- package/dist/hooks/Service_Ticket.d.ts +1 -0
- package/dist/hooks/Service_Ticket.js +433 -0
- package/dist/hooks/Service_TriggerState.d.ts +1 -0
- package/dist/hooks/Service_TriggerState.js +17 -0
- package/dist/hooks/_ChangeLog.js +27 -6
- package/dist/i18n/de-DE/chat.json +18 -0
- package/dist/i18n/de-DE/openservice.json +14 -0
- package/dist/i18n/en-US/chat.json +18 -0
- package/dist/i18n/en-US/openservice.json +14 -0
- package/dist/scripts/backfill-meta-contexts.d.ts +1 -0
- package/dist/scripts/backfill-meta-contexts.js +100 -0
- package/dist/scripts/migration/lib/acl.d.ts +52 -0
- package/dist/scripts/migration/lib/acl.js +79 -0
- package/dist/scripts/migration/lib/articleMeta.d.ts +47 -0
- package/dist/scripts/migration/lib/articleMeta.js +132 -0
- package/dist/scripts/migration/lib/env.d.ts +56 -0
- package/dist/scripts/migration/lib/env.js +106 -0
- package/dist/scripts/migration/lib/latest.d.ts +47 -0
- package/dist/scripts/migration/lib/latest.js +115 -0
- package/dist/scripts/migration/lib/map.d.ts +53 -0
- package/dist/scripts/migration/lib/map.js +113 -0
- package/dist/scripts/migration/lib/material.d.ts +47 -0
- package/dist/scripts/migration/lib/material.js +51 -0
- package/dist/scripts/migration/lib/message.d.ts +78 -0
- package/dist/scripts/migration/lib/message.js +115 -0
- package/dist/scripts/migration/lib/pass.d.ts +67 -0
- package/dist/scripts/migration/lib/pass.js +89 -0
- package/dist/scripts/migration/lib/ptr.d.ts +32 -0
- package/dist/scripts/migration/lib/ptr.js +38 -0
- package/dist/scripts/migration/lib/report.d.ts +41 -0
- package/dist/scripts/migration/lib/report.js +78 -0
- package/dist/scripts/migration/lib/rest.d.ts +47 -0
- package/dist/scripts/migration/lib/rest.js +68 -0
- package/dist/scripts/migration/lib/ticket.d.ts +72 -0
- package/dist/scripts/migration/lib/ticket.js +192 -0
- package/dist/scripts/migration/passes/01-statuses.d.ts +12 -0
- package/dist/scripts/migration/passes/01-statuses.js +122 -0
- package/dist/scripts/migration/passes/02-tags.d.ts +13 -0
- package/dist/scripts/migration/passes/02-tags.js +150 -0
- package/dist/scripts/migration/passes/03-equipment.d.ts +14 -0
- package/dist/scripts/migration/passes/03-equipment.js +176 -0
- package/dist/scripts/migration/passes/04-projects.d.ts +27 -0
- package/dist/scripts/migration/passes/04-projects.js +241 -0
- package/dist/scripts/migration/passes/05-articles.d.ts +25 -0
- package/dist/scripts/migration/passes/05-articles.js +203 -0
- package/dist/scripts/migration/passes/06-formconfigs.d.ts +17 -0
- package/dist/scripts/migration/passes/06-formconfigs.js +166 -0
- package/dist/scripts/migration/passes/07-meta-configs.d.ts +46 -0
- package/dist/scripts/migration/passes/07-meta-configs.js +185 -0
- package/dist/scripts/migration/passes/08-tickets.d.ts +27 -0
- package/dist/scripts/migration/passes/08-tickets.js +353 -0
- package/dist/scripts/migration/passes/09-ticket-meta.d.ts +12 -0
- package/dist/scripts/migration/passes/09-ticket-meta.js +136 -0
- package/dist/scripts/migration/passes/10-chat.d.ts +17 -0
- package/dist/scripts/migration/passes/10-chat.js +230 -0
- package/dist/scripts/migration/passes/11-assets.d.ts +17 -0
- package/dist/scripts/migration/passes/11-assets.js +93 -0
- package/dist/scripts/migration/passes/12-history.d.ts +24 -0
- package/dist/scripts/migration/passes/12-history.js +332 -0
- package/dist/scripts/migration/run.d.ts +1 -0
- package/dist/scripts/migration/run.js +148 -0
- package/dist/scripts/migration/verify.d.ts +1 -0
- package/dist/scripts/migration/verify.js +620 -0
- package/dist/scripts/seed-v1-testdata.d.ts +41 -0
- package/dist/scripts/seed-v1-testdata.js +543 -0
- package/dist/types/BDE_Article.d.ts +3 -0
- package/dist/types/BDE_Article.js +6 -0
- package/dist/types/Chat_Message.d.ts +49 -0
- package/dist/types/Chat_Message.js +87 -0
- package/dist/types/MES_RessourceOverview.d.ts +38 -0
- package/dist/types/MES_RessourceOverview.js +63 -0
- package/dist/types/Meta_Context.d.ts +43 -0
- package/dist/types/Meta_Context.js +75 -0
- package/dist/types/Service_Equipment.d.ts +33 -0
- package/dist/types/Service_Equipment.js +57 -0
- package/dist/types/Service_FormConfig.d.ts +33 -0
- package/dist/types/Service_FormConfig.js +57 -0
- package/dist/types/Service_MeterReading.d.ts +35 -0
- package/dist/types/Service_MeterReading.js +57 -0
- package/dist/types/Service_Project.d.ts +53 -0
- package/dist/types/Service_Project.js +93 -0
- package/dist/types/Service_Schedule.d.ts +66 -0
- package/dist/types/Service_Schedule.js +117 -0
- package/dist/types/Service_Schedule_Execution.d.ts +38 -0
- package/dist/types/Service_Schedule_Execution.js +63 -0
- package/dist/types/Service_Schedule_Template.d.ts +36 -0
- package/dist/types/Service_Schedule_Template.js +60 -0
- package/dist/types/Service_Status.d.ts +30 -0
- package/dist/types/Service_Status.js +51 -0
- package/dist/types/Service_Tag.d.ts +30 -0
- package/dist/types/Service_Tag.js +51 -0
- package/dist/types/Service_Ticket.d.ts +92 -0
- package/dist/types/Service_Ticket.js +165 -0
- package/dist/types/Service_TicketCounter.d.ts +18 -0
- package/dist/types/Service_TicketCounter.js +27 -0
- package/dist/types/Service_TriggerState.d.ts +31 -0
- package/dist/types/Service_TriggerState.js +51 -0
- package/dist/types/_Session.d.ts +0 -1
- package/dist/types/_User.d.ts +2 -6
- package/dist/types/custom/MetaField.d.ts +9 -0
- package/dist/types/custom/TicketMaterial.d.ts +32 -0
- package/dist/types/custom/TicketMaterial.js +2 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.js +33 -3
- package/package.json +6 -3
- package/schema/BDE_Article.json +3 -0
- package/schema/Changelog.json +3 -6
- package/schema/Chat_Message.json +88 -0
- package/schema/Meta_Context.json +78 -0
- package/schema/Service_Equipment.json +55 -0
- package/schema/Service_FormConfig.json +66 -0
- package/schema/Service_MeterReading.json +54 -0
- package/schema/Service_MigrationMap.json +55 -0
- package/schema/Service_Project.json +77 -0
- package/schema/Service_Schedule.json +101 -0
- package/schema/Service_Schedule_Execution.json +58 -0
- package/schema/Service_Schedule_Template.json +73 -0
- package/schema/Service_Status.json +50 -0
- package/schema/Service_Tag.json +50 -0
- package/schema/Service_Ticket.json +114 -0
- package/schema/Service_TicketCounter.json +38 -0
- 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;
|