@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,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Migration mode — an explicit escape hatch for the openservice v1 → v2 data
|
|
4
|
+
* migration (see `plugins/openservice/MIGRATION_PLAN.md` in the opendash repo).
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the migration writes with the **maintenance** key, because that
|
|
7
|
+
* is the only auth level Parse Server lets set a historical `createdAt`
|
|
8
|
+
* (`RestWrite.js`: `if (this.auth.isMaintenance && this.data.createdAt …)`). But
|
|
9
|
+
* `Auth.maintenance()` leaves `isMaster: false`, and `getRequestObject()` derives
|
|
10
|
+
* `request.master` from `isMaster` only — so triggers see `master === false`.
|
|
11
|
+
* `defaultHandler` therefore does *not* take its master-key shortcut, reaches
|
|
12
|
+
* `if (!request.user) throw`, and every maintenance-key write fails.
|
|
13
|
+
*
|
|
14
|
+
* So `request.master` cannot serve as an "is this the migration?" signal, and
|
|
15
|
+
* sniffing for a missing user would silently change behaviour for ordinary
|
|
16
|
+
* requests. An explicit env flag keeps the exception auditable, greppable, and
|
|
17
|
+
* impossible to hit by accident.
|
|
18
|
+
*
|
|
19
|
+
* While enabled, the caller takes over responsibility for everything it disables:
|
|
20
|
+
* `tenant`, `user` and the ACL on every object written.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.isMigrationMode = isMigrationMode;
|
|
24
|
+
let warned = false;
|
|
25
|
+
/**
|
|
26
|
+
* True when `OD_MIGRATION_MODE=1`. Logs a loud one-time banner the first time it
|
|
27
|
+
* is observed, so a server accidentally left in migration mode is obvious in the
|
|
28
|
+
* logs rather than silently permissive.
|
|
29
|
+
*/
|
|
30
|
+
function isMigrationMode() {
|
|
31
|
+
const enabled = process.env.OD_MIGRATION_MODE === "1";
|
|
32
|
+
if (enabled && !warned) {
|
|
33
|
+
warned = true;
|
|
34
|
+
console.warn([
|
|
35
|
+
"",
|
|
36
|
+
"!".repeat(80),
|
|
37
|
+
"[@openinc/parse-server-opendash] OD_MIGRATION_MODE=1 — MIGRATION MODE ACTIVE",
|
|
38
|
+
" · defaultHandler / defaultAclHandler are bypassed (no tenant, user or ACL",
|
|
39
|
+
" defaults are applied — the writer must set them itself)",
|
|
40
|
+
" · assignment / watcher notifications are suppressed",
|
|
41
|
+
" · tag closure reconciliation is suppressed",
|
|
42
|
+
" This must NOT be set during normal operation.",
|
|
43
|
+
"!".repeat(80),
|
|
44
|
+
"",
|
|
45
|
+
].join("\n"));
|
|
46
|
+
}
|
|
47
|
+
return enabled;
|
|
48
|
+
}
|
|
@@ -5,6 +5,7 @@ export { beforeSaveHook } from "./services/beforeSaveHook.js";
|
|
|
5
5
|
export * from "./types/hookTypes.js";
|
|
6
6
|
export { defaultAclHandler, defaultHandler } from "./helper/defaultHandler.js";
|
|
7
7
|
export { immutableField } from "./helper/immutableField.js";
|
|
8
|
+
export { isMigrationMode } from "./helper/migrationMode.js";
|
|
8
9
|
export { ensureRole } from "./helper/ensureRole.js";
|
|
9
10
|
export { ensureUserRole } from "./helper/ensureUserRole.js";
|
|
10
11
|
export { initSchema } from "./services/initSchema.js";
|
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.initDefaultRoles = exports.initDefaultData = exports.initSchema = exports.ensureUserRole = exports.ensureRole = exports.immutableField = exports.defaultHandler = exports.defaultAclHandler = exports.beforeSaveHook = exports.beforeDeleteHook = exports.afterSaveHook = exports.afterDeleteHook = void 0;
|
|
17
|
+
exports.initDefaultRoles = exports.initDefaultData = exports.initSchema = exports.ensureUserRole = exports.ensureRole = exports.isMigrationMode = exports.immutableField = exports.defaultHandler = exports.defaultAclHandler = exports.beforeSaveHook = exports.beforeDeleteHook = exports.afterSaveHook = exports.afterDeleteHook = void 0;
|
|
18
18
|
var afterDeleteHook_js_1 = require("./services/afterDeleteHook.js");
|
|
19
19
|
Object.defineProperty(exports, "afterDeleteHook", { enumerable: true, get: function () { return afterDeleteHook_js_1.afterDeleteHook; } });
|
|
20
20
|
var afterSaveHook_js_1 = require("./services/afterSaveHook.js");
|
|
@@ -29,6 +29,8 @@ Object.defineProperty(exports, "defaultAclHandler", { enumerable: true, get: fun
|
|
|
29
29
|
Object.defineProperty(exports, "defaultHandler", { enumerable: true, get: function () { return defaultHandler_js_1.defaultHandler; } });
|
|
30
30
|
var immutableField_js_1 = require("./helper/immutableField.js");
|
|
31
31
|
Object.defineProperty(exports, "immutableField", { enumerable: true, get: function () { return immutableField_js_1.immutableField; } });
|
|
32
|
+
var migrationMode_js_1 = require("./helper/migrationMode.js");
|
|
33
|
+
Object.defineProperty(exports, "isMigrationMode", { enumerable: true, get: function () { return migrationMode_js_1.isMigrationMode; } });
|
|
32
34
|
var ensureRole_js_1 = require("./helper/ensureRole.js");
|
|
33
35
|
Object.defineProperty(exports, "ensureRole", { enumerable: true, get: function () { return ensureRole_js_1.ensureRole; } });
|
|
34
36
|
var ensureUserRole_js_1 = require("./helper/ensureUserRole.js");
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.beforeSaveHook = beforeSaveHook;
|
|
7
7
|
const node_1 = __importDefault(require("parse/node"));
|
|
8
|
+
const migrationMode_js_1 = require("../helper/migrationMode.js");
|
|
8
9
|
const beforeSaveHooks = {};
|
|
9
10
|
/**
|
|
10
11
|
* Registers a beforeSave hook for a Parse class. In comparison to Parse.Cloud.beforeSave(), this function allows multiple beforeSave hooks to be registered for the same class.
|
|
@@ -17,6 +18,26 @@ const beforeSaveHooks = {};
|
|
|
17
18
|
function beforeSaveHook(target, callback) {
|
|
18
19
|
// @ts-ignore
|
|
19
20
|
const className = typeof target === "string" ? target : target.className;
|
|
21
|
+
// In migration mode, do not register a beforeSave trigger at all.
|
|
22
|
+
//
|
|
23
|
+
// This is not an optimisation — it is the only way historical timestamps can be
|
|
24
|
+
// written. Parse Server only honours a supplied `createdAt` in
|
|
25
|
+
// `setRequiredFieldsIfNeeded()`, which runs *after* `runBeforeSaveTrigger()`,
|
|
26
|
+
// and that trigger path does `this.data = response.object` unconditionally
|
|
27
|
+
// (`parse-server/lib/RestWrite.js`). `response.object` has been round-tripped
|
|
28
|
+
// through a `Parse.Object`, whose `set()` silently discards `createdAt` /
|
|
29
|
+
// `updatedAt` — so the field is gone by the time the maintenance-key branch
|
|
30
|
+
// looks for it. Verified empirically: an identical write to a class with no
|
|
31
|
+
// beforeSave trigger keeps the historical date; to a class with one, it does not.
|
|
32
|
+
//
|
|
33
|
+
// The migration therefore takes over what these hooks would have done —
|
|
34
|
+
// `tenant`, `user`, ACLs and ticket numbering — which it was already doing for
|
|
35
|
+
// the parts `OD_MIGRATION_MODE` gates off.
|
|
36
|
+
if ((0, migrationMode_js_1.isMigrationMode)()) {
|
|
37
|
+
console.warn(`[@openinc/parse-server-opendash] OD_MIGRATION_MODE: NOT registering ` +
|
|
38
|
+
`beforeSave for ${className} — historical timestamps require no trigger`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
20
41
|
if (!beforeSaveHooks[className]) {
|
|
21
42
|
beforeSaveHooks[className] = [];
|
|
22
43
|
node_1.default.Cloud.beforeSave(className, async function beforeSaveHookFunction(request) {
|
|
@@ -62,7 +62,7 @@ async function initTranslations() {
|
|
|
62
62
|
fallbackLng: "en-US",
|
|
63
63
|
// Specify available languages to match your directories
|
|
64
64
|
supportedLngs: ["de-DE", "en-US"],
|
|
65
|
-
ns: ["server", "maintenance"],
|
|
65
|
+
ns: ["server", "maintenance", "chat", "openservice"],
|
|
66
66
|
defaultNS: "server",
|
|
67
67
|
debug: false,
|
|
68
68
|
// Load resources asynchronously (await init promise)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,179 @@
|
|
|
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.init = init;
|
|
7
|
+
const node_1 = __importDefault(require("parse/node"));
|
|
8
|
+
const i18next_1 = __importDefault(require("i18next"));
|
|
9
|
+
const index_js_1 = require("../features/schema/index.js");
|
|
10
|
+
const getUserLanguage_js_1 = require("../helper/getUserLanguage.js");
|
|
11
|
+
const index_js_2 = require("../types/index.js");
|
|
12
|
+
async function init() {
|
|
13
|
+
(0, index_js_1.beforeSaveHook)(index_js_2.Chat_Message, async (request) => {
|
|
14
|
+
await (0, index_js_1.defaultHandler)(request);
|
|
15
|
+
await (0, index_js_1.defaultAclHandler)(request);
|
|
16
|
+
});
|
|
17
|
+
(0, index_js_1.afterSaveHook)(index_js_2.Chat_Message, async ({ object, original }) => {
|
|
18
|
+
try {
|
|
19
|
+
await notifyMentions(object, original);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error("[@openinc/parse-server-opendash][Chat_Message] Failed to notify mentioned users", error);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Notifies the users and roles mentioned in a chat message.
|
|
28
|
+
*
|
|
29
|
+
* Only *newly added* mentions are notified: we diff the message's current
|
|
30
|
+
* `mentions` against the previous save (`original`). On creation `original` is
|
|
31
|
+
* undefined, so every mention is new; on an edit that leaves the mentions
|
|
32
|
+
* untouched (the common case — most edits only change the text around existing
|
|
33
|
+
* mentions, react, or soft-delete) nothing is sent, so repeated edits never
|
|
34
|
+
* re-notify. The only case that re-notifies is removing a mention and re-adding
|
|
35
|
+
* it across separate edits, which is rare and harmless.
|
|
36
|
+
*
|
|
37
|
+
* Dedup + precedence: a user is notified at most once. A directly mentioned
|
|
38
|
+
* user wins over a role mention (`_User` > `_Role`) — they get the personal
|
|
39
|
+
* "you were mentioned" notification and are skipped when expanding roles. The
|
|
40
|
+
* message author is never notified for mentioning themselves.
|
|
41
|
+
*/
|
|
42
|
+
async function notifyMentions(object, original) {
|
|
43
|
+
// A soft-delete is just an update; never notify for it.
|
|
44
|
+
if (object.get("deleted"))
|
|
45
|
+
return;
|
|
46
|
+
const previousKeys = new Set(normalizeMentions(original?.get("mentions")).map(mentionKey));
|
|
47
|
+
const newMentions = normalizeMentions(object.get("mentions")).filter((mention) => !previousKeys.has(mentionKey(mention)));
|
|
48
|
+
if (newMentions.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
const username = object.get("authorName") || "System";
|
|
51
|
+
const messageText = messageSnippet(object.get("text"));
|
|
52
|
+
const url = getMessageLink(object.get("context"));
|
|
53
|
+
// Users already handled — seeded with the author so they never self-notify.
|
|
54
|
+
const notified = new Set();
|
|
55
|
+
const authorId = object.get("author")?.id;
|
|
56
|
+
if (authorId)
|
|
57
|
+
notified.add(authorId);
|
|
58
|
+
// 1. Directly mentioned users (take precedence over role membership).
|
|
59
|
+
const userIds = newMentions
|
|
60
|
+
.filter((mention) => mention.className === "_User")
|
|
61
|
+
.map((mention) => mention.id);
|
|
62
|
+
const users = userIds.length
|
|
63
|
+
? await new node_1.default.Query(node_1.default.User)
|
|
64
|
+
.containedIn("objectId", userIds)
|
|
65
|
+
.findAll({ useMasterKey: true })
|
|
66
|
+
: [];
|
|
67
|
+
for (const user of users) {
|
|
68
|
+
if (!user.id || notified.has(user.id))
|
|
69
|
+
continue;
|
|
70
|
+
notified.add(user.id);
|
|
71
|
+
await sendMentionNotification(user, "user", object, {
|
|
72
|
+
username,
|
|
73
|
+
messageText,
|
|
74
|
+
url,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// 2. Mentioned roles → their members, skipping anyone already notified.
|
|
78
|
+
const roleIds = newMentions
|
|
79
|
+
.filter((mention) => mention.className === "_Role")
|
|
80
|
+
.map((mention) => mention.id);
|
|
81
|
+
const roles = roleIds.length
|
|
82
|
+
? await new node_1.default.Query(node_1.default.Role)
|
|
83
|
+
.containedIn("objectId", roleIds)
|
|
84
|
+
.findAll({ useMasterKey: true })
|
|
85
|
+
: [];
|
|
86
|
+
for (const role of roles) {
|
|
87
|
+
const members = await role
|
|
88
|
+
.relation("users")
|
|
89
|
+
.query()
|
|
90
|
+
.findAll({ useMasterKey: true });
|
|
91
|
+
for (const member of members) {
|
|
92
|
+
if (!member.id || notified.has(member.id))
|
|
93
|
+
continue;
|
|
94
|
+
notified.add(member.id);
|
|
95
|
+
await sendMentionNotification(member, "role", object, {
|
|
96
|
+
username,
|
|
97
|
+
messageText,
|
|
98
|
+
url,
|
|
99
|
+
role: role.get("label"),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Builds and saves a single mention notification, translated into the
|
|
106
|
+
* recipient's preferred language. Delivery (email + web push) is handled
|
|
107
|
+
* automatically by the {@link Notification} afterSave hook.
|
|
108
|
+
*/
|
|
109
|
+
async function sendMentionNotification(user, kind, message, vars) {
|
|
110
|
+
await i18next_1.default.changeLanguage(await (0, getUserLanguage_js_1.getUserLanguage)(user));
|
|
111
|
+
const base = `chat:message.mention.${kind}.notification`;
|
|
112
|
+
await new index_js_2.Notification({
|
|
113
|
+
title: i18next_1.default.t(`${base}.title`),
|
|
114
|
+
description: i18next_1.default.t(`${base}.description`, {
|
|
115
|
+
username: vars.username,
|
|
116
|
+
messageText: vars.messageText,
|
|
117
|
+
role: vars.role,
|
|
118
|
+
}),
|
|
119
|
+
data: {
|
|
120
|
+
url: vars.url,
|
|
121
|
+
messageId: message.id,
|
|
122
|
+
context: message.get("context"),
|
|
123
|
+
translation: {
|
|
124
|
+
username: vars.username,
|
|
125
|
+
messageText: vars.messageText,
|
|
126
|
+
role: vars.role,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
user: user,
|
|
130
|
+
isSent: false,
|
|
131
|
+
}).save(null, { useMasterKey: true });
|
|
132
|
+
}
|
|
133
|
+
/** Coerces the raw `mentions` field into a clean list of valid mentions. */
|
|
134
|
+
function normalizeMentions(value) {
|
|
135
|
+
if (!Array.isArray(value))
|
|
136
|
+
return [];
|
|
137
|
+
return value.filter((entry) => !!entry &&
|
|
138
|
+
typeof entry.id === "string" &&
|
|
139
|
+
typeof entry.className === "string");
|
|
140
|
+
}
|
|
141
|
+
/** Stable identity of a mention, used for de-duplication and diffing. */
|
|
142
|
+
function mentionKey(mention) {
|
|
143
|
+
return `${mention.className}:${mention.id}`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Derives a deep-link path from the chat's `context`. The context is the
|
|
147
|
+
* JSON-stringified address tuple (e.g. `["service","ticket","<id>"]`). Only the
|
|
148
|
+
* service-ticket context is linkable today; anything else yields no link.
|
|
149
|
+
*/
|
|
150
|
+
function getMessageLink(context) {
|
|
151
|
+
if (typeof context !== "string")
|
|
152
|
+
return undefined;
|
|
153
|
+
try {
|
|
154
|
+
const parts = JSON.parse(context);
|
|
155
|
+
if (Array.isArray(parts) &&
|
|
156
|
+
parts[0] === "service" &&
|
|
157
|
+
parts[1] === "ticket" &&
|
|
158
|
+
parts[2]) {
|
|
159
|
+
return `/service/tickets/${parts[2]}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// context is not a JSON tuple — no link
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const SNIPPET_MAX_LENGTH = 140;
|
|
168
|
+
/** Inline mention token: `[label](#mention:_User:<id>)` → keep just `label`. */
|
|
169
|
+
const MENTION_TOKEN = /\[([^\]]*)\]\(#mention:[^)]*\)/g;
|
|
170
|
+
/** Builds a short, plain-text preview of the message for the notification body:
|
|
171
|
+
* mention tokens collapse to their label and whitespace is normalized. */
|
|
172
|
+
function messageSnippet(text) {
|
|
173
|
+
if (typeof text !== "string")
|
|
174
|
+
return "";
|
|
175
|
+
const plain = text.replace(MENTION_TOKEN, "$1").replace(/\s+/g, " ").trim();
|
|
176
|
+
return plain.length > SNIPPET_MAX_LENGTH
|
|
177
|
+
? `${plain.slice(0, SNIPPET_MAX_LENGTH - 1).trimEnd()}…`
|
|
178
|
+
: plain;
|
|
179
|
+
}
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.init = init;
|
|
4
|
-
const index_js_1 = require("../features/
|
|
5
|
-
const index_js_2 = require("../
|
|
4
|
+
const index_js_1 = require("../features/meta/index.js");
|
|
5
|
+
const index_js_2 = require("../features/schema/index.js");
|
|
6
|
+
const index_js_3 = require("../types/index.js");
|
|
6
7
|
async function init() {
|
|
7
|
-
(0,
|
|
8
|
-
|
|
9
|
-
await (0,
|
|
10
|
-
await (0, index_js_1.defaultAclHandler)(request);
|
|
11
|
-
// TODO
|
|
8
|
+
(0, index_js_2.beforeSaveHook)(index_js_3.Meta_Config, async (request) => {
|
|
9
|
+
await (0, index_js_2.defaultHandler)(request);
|
|
10
|
+
await (0, index_js_2.defaultAclHandler)(request);
|
|
12
11
|
});
|
|
13
|
-
(0,
|
|
14
|
-
|
|
15
|
-
//
|
|
12
|
+
(0, index_js_2.afterSaveHook)(index_js_3.Meta_Config, async ({ object }) => {
|
|
13
|
+
// Keeps the context directory current: existence, label and description all
|
|
14
|
+
// come from here, so a config save is what makes a context "named".
|
|
15
|
+
try {
|
|
16
|
+
await (0, index_js_1.registerMetaContext)({
|
|
17
|
+
context: object.context ?? "",
|
|
18
|
+
tenant: object.tenant,
|
|
19
|
+
config: object,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error("[@openinc/parse-server-opendash][Meta_Config] Failed to register meta context", error);
|
|
24
|
+
}
|
|
16
25
|
});
|
|
17
26
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The context directory itself. Rows are written by `registerMetaContext` from
|
|
3
|
+
* the `Meta_Config` / `Meta_Entry` hooks with the master key, which skips these
|
|
4
|
+
* handlers — they exist for the rare direct edit (renaming a label by hand).
|
|
5
|
+
*/
|
|
6
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = init;
|
|
4
|
+
const index_js_1 = require("../features/schema/index.js");
|
|
5
|
+
const index_js_2 = require("../types/index.js");
|
|
6
|
+
/**
|
|
7
|
+
* The context directory itself. Rows are written by `registerMetaContext` from
|
|
8
|
+
* the `Meta_Config` / `Meta_Entry` hooks with the master key, which skips these
|
|
9
|
+
* handlers — they exist for the rare direct edit (renaming a label by hand).
|
|
10
|
+
*/
|
|
11
|
+
async function init() {
|
|
12
|
+
(0, index_js_1.beforeSaveHook)(index_js_2.Meta_Context, async (request) => {
|
|
13
|
+
await (0, index_js_1.defaultHandler)(request);
|
|
14
|
+
await (0, index_js_1.defaultAclHandler)(request);
|
|
15
|
+
});
|
|
16
|
+
}
|
package/dist/hooks/Meta_Entry.js
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.init = init;
|
|
4
|
-
const index_js_1 = require("../features/
|
|
5
|
-
const index_js_2 = require("../
|
|
4
|
+
const index_js_1 = require("../features/meta/index.js");
|
|
5
|
+
const index_js_2 = require("../features/schema/index.js");
|
|
6
|
+
const index_js_3 = require("../types/index.js");
|
|
6
7
|
async function init() {
|
|
7
|
-
(0,
|
|
8
|
-
|
|
9
|
-
await (0,
|
|
10
|
-
await (0, index_js_1.defaultAclHandler)(request);
|
|
11
|
-
// TODO
|
|
8
|
+
(0, index_js_2.beforeSaveHook)(index_js_3.Meta_Entry, async (request) => {
|
|
9
|
+
await (0, index_js_2.defaultHandler)(request);
|
|
10
|
+
await (0, index_js_2.defaultAclHandler)(request);
|
|
12
11
|
});
|
|
13
|
-
(0,
|
|
14
|
-
|
|
15
|
-
//
|
|
12
|
+
(0, index_js_2.afterSaveHook)(index_js_3.Meta_Entry, async ({ object }) => {
|
|
13
|
+
// An entry is the only trace of a context that has no config behind it —
|
|
14
|
+
// a `customConfig` form leaves `origin` empty and creates no Meta_Config,
|
|
15
|
+
// so without this the context would exist in the data and nowhere else.
|
|
16
|
+
try {
|
|
17
|
+
await (0, index_js_1.registerMetaContext)({
|
|
18
|
+
context: object.context ?? "",
|
|
19
|
+
tenant: object.tenant,
|
|
20
|
+
entryClassName: object.entryClassName,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error("[@openinc/parse-server-opendash][Meta_Entry] Failed to register meta context", error);
|
|
25
|
+
}
|
|
16
26
|
});
|
|
17
27
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
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.init = init;
|
|
7
|
+
const node_1 = __importDefault(require("parse/node"));
|
|
8
|
+
const index_js_1 = require("../features/schema/index.js");
|
|
9
|
+
const index_js_2 = require("../types/index.js");
|
|
10
|
+
async function init() {
|
|
11
|
+
(0, index_js_1.beforeSaveHook)(index_js_2.Service_Equipment, async (request) => {
|
|
12
|
+
const { object, original, user } = request;
|
|
13
|
+
await (0, index_js_1.defaultHandler)(request);
|
|
14
|
+
await (0, index_js_1.defaultAclHandler)(request);
|
|
15
|
+
// TODO
|
|
16
|
+
});
|
|
17
|
+
(0, index_js_1.afterSaveHook)(index_js_2.Service_Equipment, async (request) => {
|
|
18
|
+
const { object, original, user } = request;
|
|
19
|
+
// TODO
|
|
20
|
+
});
|
|
21
|
+
// Block deleting equipment that is still referenced where it matters:
|
|
22
|
+
// active tickets, and meter readings (whose `equipment` pointer is required,
|
|
23
|
+
// so orphaning it would break them).
|
|
24
|
+
(0, index_js_1.beforeDeleteHook)(index_js_2.Service_Equipment, async ({ object }) => {
|
|
25
|
+
const [tickets, readings] = await Promise.all([
|
|
26
|
+
new node_1.default.Query(index_js_2.Service_Ticket)
|
|
27
|
+
.equalTo("equipment", object)
|
|
28
|
+
.notEqualTo("deleted", true)
|
|
29
|
+
.count({ useMasterKey: true }),
|
|
30
|
+
new node_1.default.Query("OD3_Service_MeterReading")
|
|
31
|
+
.equalTo("equipment", object)
|
|
32
|
+
.count({ useMasterKey: true }),
|
|
33
|
+
]);
|
|
34
|
+
if (tickets > 0 || readings > 0) {
|
|
35
|
+
throw new node_1.default.Error(node_1.default.Error.OPERATION_FORBIDDEN, `Cannot delete equipment: referenced by ${tickets} active ticket(s) and ${readings} meter reading(s).`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = init;
|
|
4
|
+
const index_js_1 = require("../features/schema/index.js");
|
|
5
|
+
const index_js_2 = require("../types/index.js");
|
|
6
|
+
async function init() {
|
|
7
|
+
(0, index_js_1.beforeSaveHook)(index_js_2.Service_FormConfig, async (request) => {
|
|
8
|
+
const { object, original, user } = request;
|
|
9
|
+
await (0, index_js_1.defaultHandler)(request);
|
|
10
|
+
await (0, index_js_1.defaultAclHandler)(request);
|
|
11
|
+
// TODO
|
|
12
|
+
});
|
|
13
|
+
(0, index_js_1.afterSaveHook)(index_js_2.Service_FormConfig, async (request) => {
|
|
14
|
+
const { object, original, user } = request;
|
|
15
|
+
// TODO
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = init;
|
|
4
|
+
const index_js_1 = require("../features/schema/index.js");
|
|
5
|
+
const index_js_2 = require("../types/index.js");
|
|
6
|
+
async function init() {
|
|
7
|
+
(0, index_js_1.beforeSaveHook)(index_js_2.Service_Project, async (request) => {
|
|
8
|
+
const { object, original, user } = request;
|
|
9
|
+
await (0, index_js_1.defaultHandler)(request);
|
|
10
|
+
await (0, index_js_1.defaultAclHandler)(request);
|
|
11
|
+
// TODO
|
|
12
|
+
});
|
|
13
|
+
(0, index_js_1.afterSaveHook)(index_js_2.Service_Project, async (request) => {
|
|
14
|
+
const { object, original, user } = request;
|
|
15
|
+
// TODO
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = init;
|
|
4
|
+
const index_js_1 = require("../features/schema/index.js");
|
|
5
|
+
const index_js_2 = require("../types/index.js");
|
|
6
|
+
async function init() {
|
|
7
|
+
(0, index_js_1.beforeSaveHook)(index_js_2.Service_Schedule, async (request) => {
|
|
8
|
+
const { object, original, user } = request;
|
|
9
|
+
await (0, index_js_1.defaultHandler)(request);
|
|
10
|
+
// `allowCustomACL` keeps an ACL set by the client instead of resetting it on
|
|
11
|
+
// every save. The frontend uses it for the per-record write lock ("Rechte
|
|
12
|
+
// nur für Ersteller", `openservice-schedule/utils/acl.ts`): the creator gets
|
|
13
|
+
// an explicit user grant, everyone else keeps read only. The handler still
|
|
14
|
+
// re-adds `od-admin` / `od-tenant-admin-<tenant>` write and
|
|
15
|
+
// `od-tenant-user-<tenant>` read afterwards, so a lock can never lock
|
|
16
|
+
// administrators out.
|
|
17
|
+
await (0, index_js_1.defaultAclHandler)(request, { allowCustomACL: true });
|
|
18
|
+
// TODO
|
|
19
|
+
});
|
|
20
|
+
(0, index_js_1.afterSaveHook)(index_js_2.Service_Schedule, async (request) => {
|
|
21
|
+
const { object, original, user } = request;
|
|
22
|
+
// TODO
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = init;
|
|
4
|
+
const index_js_1 = require("../features/schema/index.js");
|
|
5
|
+
const index_js_2 = require("../types/index.js");
|
|
6
|
+
async function init() {
|
|
7
|
+
(0, index_js_1.beforeSaveHook)(index_js_2.Service_Schedule_Execution, async (request) => {
|
|
8
|
+
const { object, original, user } = request;
|
|
9
|
+
await (0, index_js_1.defaultHandler)(request);
|
|
10
|
+
await (0, index_js_1.defaultAclHandler)(request);
|
|
11
|
+
// TODO
|
|
12
|
+
});
|
|
13
|
+
(0, index_js_1.afterSaveHook)(index_js_2.Service_Schedule_Execution, async (request) => {
|
|
14
|
+
const { object, original, user } = request;
|
|
15
|
+
// TODO
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = init;
|
|
4
|
+
const index_js_1 = require("../features/schema/index.js");
|
|
5
|
+
const index_js_2 = require("../types/index.js");
|
|
6
|
+
async function init() {
|
|
7
|
+
(0, index_js_1.beforeSaveHook)(index_js_2.Service_Schedule_Template, async (request) => {
|
|
8
|
+
const { object, original, user } = request;
|
|
9
|
+
await (0, index_js_1.defaultHandler)(request);
|
|
10
|
+
await (0, index_js_1.defaultAclHandler)(request);
|
|
11
|
+
// TODO
|
|
12
|
+
});
|
|
13
|
+
(0, index_js_1.afterSaveHook)(index_js_2.Service_Schedule_Template, async (request) => {
|
|
14
|
+
const { object, original, user } = request;
|
|
15
|
+
// TODO
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
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.init = init;
|
|
7
|
+
const node_1 = __importDefault(require("parse/node"));
|
|
8
|
+
const index_js_1 = require("../features/schema/index.js");
|
|
9
|
+
const index_js_2 = require("../types/index.js");
|
|
10
|
+
async function init() {
|
|
11
|
+
(0, index_js_1.beforeSaveHook)(index_js_2.Service_Status, async (request) => {
|
|
12
|
+
const { object, original, user } = request;
|
|
13
|
+
await (0, index_js_1.defaultHandler)(request);
|
|
14
|
+
await (0, index_js_1.defaultAclHandler)(request);
|
|
15
|
+
// TODO
|
|
16
|
+
});
|
|
17
|
+
(0, index_js_1.afterSaveHook)(index_js_2.Service_Status, async (request) => {
|
|
18
|
+
const { object, original, user } = request;
|
|
19
|
+
// TODO
|
|
20
|
+
});
|
|
21
|
+
// A ticket's `status` is required — deleting a status still in use by active
|
|
22
|
+
// tickets would orphan that required pointer. Block it.
|
|
23
|
+
(0, index_js_1.beforeDeleteHook)(index_js_2.Service_Status, async ({ object }) => {
|
|
24
|
+
const inUse = await new node_1.default.Query(index_js_2.Service_Ticket)
|
|
25
|
+
.equalTo("status", object)
|
|
26
|
+
.notEqualTo("deleted", true)
|
|
27
|
+
.count({ useMasterKey: true });
|
|
28
|
+
if (inUse > 0) {
|
|
29
|
+
throw new node_1.default.Error(node_1.default.Error.OPERATION_FORBIDDEN, `Cannot delete status: still used by ${inUse} active ticket(s).`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|