@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
@@ -4,7 +4,7 @@
4
4
  "OD3_AlarmWebhook": "MONITORING",
5
5
  "OD3_Assets": "CORE",
6
6
  "OD3_Attachment": "CORE",
7
- "OD3_BDE_Article": "BDE",
7
+ "OD3_BDE_Article": "CORE",
8
8
  "OD3_BDE_Form": "BDE",
9
9
  "OD3_BDE_List": "BDE",
10
10
  "OD3_BDE_ListEntry": "BDE",
@@ -14,6 +14,7 @@
14
14
  "OD3_BDE_WorkOrder": "BDE",
15
15
  "OD3_BDE_WorkTime": "BDE",
16
16
  "OD3_Changelog": "CORE",
17
+ "OD3_Chat_Message": "CORE",
17
18
  "OD3_Company": "CORE",
18
19
  "OD3_Config": "CORE",
19
20
  "OD3_Contact": "CORE",
@@ -47,6 +48,11 @@
47
48
  "OD3_Knowledge_Video": "KNOWLEDGE",
48
49
  "OD3_Language": "CORE",
49
50
  "OD3_Log": "CORE",
51
+ "OD3_MES_Article": "MONITORING",
52
+ "OD3_MES_Order": "MONITORING",
53
+ "OD3_MES_OrderPlan": "MONITORING",
54
+ "OD3_MIAAS_MDSEndpoint": "MIAAS",
55
+ "OD3_ML_DataSelection": "MASCHINELEARNING",
50
56
  "OD3_MailTemplate": "MONITORING",
51
57
  "OD3_Mail_Groups": "MONITORING",
52
58
  "OD3_Maintenance_Downtime": "MAINTENANCE",
@@ -80,13 +86,9 @@
80
86
  "OD3_Maintenance_Ticket_QR_Code": "MAINTENANCE",
81
87
  "OD3_Maintenance_Ticket_Source": "MAINTENANCE",
82
88
  "OD3_Maintenance_Ticket_Title": "MAINTENANCE",
83
- "OD3_MES_Article": "MONITORING",
84
- "OD3_MES_Order": "MONITORING",
85
- "OD3_MES_OrderPlan": "MONITORING",
86
89
  "OD3_Meta_Config": "CORE",
90
+ "OD3_Meta_Context": "CORE",
87
91
  "OD3_Meta_Entry": "CORE",
88
- "OD3_MIAAS_MDSEndpoint": "MIAAS",
89
- "OD3_ML_DataSelection": "MASCHINELEARNING",
90
92
  "OD3_Monitoring_DataHierachies": "MONITORING",
91
93
  "OD3_Monitoring_Jobs": "MONITORING",
92
94
  "OD3_Monitoring_ParseTableSensor": "MONITORING",
@@ -100,13 +102,26 @@
100
102
  "OD3_OWPlcItem": "MONITORING",
101
103
  "OD3_Permission": "CORE",
102
104
  "OD3_Push": "CORE",
103
- "OD3_RAG_Data": "unknown",
104
- "OD3_RAG_Interview": "unknown",
105
- "OD3_RAG_Meta": "unknown",
106
- "OD3_RAG_Prompts": "unknown",
107
- "OD3_RAG_Questions": "unknown",
108
- "OD3_RAG_Request": "unknown",
105
+ "OD3_RAG_Data": "RAG",
106
+ "OD3_RAG_Interview": "RAG",
107
+ "OD3_RAG_Meta": "RAG",
108
+ "OD3_RAG_Prompts": "RAG",
109
+ "OD3_RAG_Questions": "RAG",
110
+ "OD3_RAG_Request": "RAG",
109
111
  "OD3_Report": "CORE",
112
+ "OD3_Service_Equipment": "SERVICE",
113
+ "OD3_Service_FormConfig": "SERVICE",
114
+ "OD3_Service_MeterReading": "SERVICE",
115
+ "OD3_Service_MigrationMap": "SERVICE",
116
+ "OD3_Service_Project": "SERVICE",
117
+ "OD3_Service_Schedule": "SERVICE",
118
+ "OD3_Service_Schedule_Execution": "SERVICE",
119
+ "OD3_Service_Schedule_Template": "SERVICE",
120
+ "OD3_Service_Status": "SERVICE",
121
+ "OD3_Service_Tag": "SERVICE",
122
+ "OD3_Service_Ticket": "SERVICE",
123
+ "OD3_Service_TicketCounter": "SERVICE",
124
+ "OD3_Service_TriggerState": "SERVICE",
110
125
  "OD3_Share": "CORE",
111
126
  "OD3_Source": "CORE",
112
127
  "OD3_SourceMeta": "CORE",
@@ -233,6 +233,15 @@ exports.customoptions = {
233
233
  default: "false",
234
234
  description: "Enable Parse Classes and Cloud Code for the Documentation plugin.",
235
235
  },
236
+ FEATURE_SERVICE: {
237
+ env: "OPENINC_PARSE_FEATURE_SERVICE",
238
+ type: "boolean",
239
+ required: false,
240
+ secret: false,
241
+ public: true,
242
+ default: "false",
243
+ description: "Enable Parse Classes and Cloud Code for the open.SERVICE v2 plugin.",
244
+ },
236
245
  FORCE_SCHEMA: {
237
246
  env: "OPENINC_PARSE_FORCE_SCHEMA",
238
247
  type: "boolean",
@@ -1,2 +1,2 @@
1
- export type Feature = "CORE" | "MONITORING" | "BDE" | "GTFS" | "KNOWLEDGE" | "MAINTENANCE" | "DOCUMENTATION" | "MIAAS";
1
+ export type Feature = "CORE" | "MONITORING" | "BDE" | "GTFS" | "KNOWLEDGE" | "MAINTENANCE" | "DOCUMENTATION" | "MIAAS" | "SERVICE";
2
2
  export declare function isFeatureEnabled(feature: Feature): boolean;
@@ -21,6 +21,8 @@ function isFeatureEnabled(feature) {
21
21
  return config.getBoolean("FEATURE_DOCUMENTATION");
22
22
  case "MIAAS":
23
23
  return config.getBoolean("FEATURE_MIAAS");
24
+ case "SERVICE":
25
+ return config.getBoolean("FEATURE_SERVICE");
24
26
  default:
25
27
  return false;
26
28
  }
@@ -0,0 +1 @@
1
+ export { registerMetaContext } from "./services/registerMetaContext.js";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerMetaContext = void 0;
4
+ var registerMetaContext_js_1 = require("./services/registerMetaContext.js");
5
+ Object.defineProperty(exports, "registerMetaContext", { enumerable: true, get: function () { return registerMetaContext_js_1.registerMetaContext; } });
@@ -0,0 +1,29 @@
1
+ import { Meta_Config, Tenant } from "../../../types/index.js";
2
+ /**
3
+ * Record that a meta context exists, and what is known about it.
4
+ *
5
+ * Nothing declares a context: it comes into being the moment something writes a
6
+ * `Meta_Config` or a `Meta_Entry` carrying it. Every writer therefore registers
7
+ * itself here without knowing this directory exists — including a sync job, a
8
+ * backend script, or a client instance nobody in this repo controls, none of
9
+ * which a code-side list could ever enumerate.
10
+ *
11
+ * Crucially this also covers contexts that will **never** have a config: an
12
+ * entry saved from a `customConfig` form has no `origin` and no `Meta_Config`
13
+ * behind it (see `useSaveEntry`), which makes it invisible to any listing
14
+ * derived from the config class. Here it is a first-class row with
15
+ * `hasConfig: false`.
16
+ *
17
+ * Writes are conditional — a row is only saved when something actually changed
18
+ * — so the entry hook is a read on the hot path and a write only on the rare
19
+ * occasion that a context, a class, or a stale timestamp is new.
20
+ */
21
+ export declare function registerMetaContext(params: {
22
+ context: string;
23
+ tenant?: Tenant;
24
+ /** Set when the caller is a `Meta_Config` save. */
25
+ config?: Meta_Config;
26
+ /** `entryClassName` of the entry being saved, when the caller is an entry. */
27
+ entryClassName?: string;
28
+ now?: Date;
29
+ }): Promise<void>;
@@ -0,0 +1,208 @@
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.registerMetaContext = registerMetaContext;
7
+ const node_1 = __importDefault(require("parse/node"));
8
+ const index_js_1 = require("../../../types/index.js");
9
+ /**
10
+ * How stale `lastSeenAt` has to be before an entry save refreshes it. Entries
11
+ * are written constantly; without this the directory would take a write on
12
+ * every single one, for a timestamp nobody reads to the minute.
13
+ */
14
+ const LAST_SEEN_MAX_AGE_MS = 15 * 60 * 1000;
15
+ /** Parse the stringified context array a row carries; tolerate junk. */
16
+ function parseSegments(context) {
17
+ try {
18
+ const parsed = JSON.parse(context);
19
+ return Array.isArray(parsed) && parsed.every((s) => typeof s === "string")
20
+ ? parsed
21
+ : undefined;
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ /** Every row for a (context, tenant) pair, oldest first. */
28
+ async function findRows(context, tenant) {
29
+ const query = new node_1.default.Query(index_js_1.Meta_Context)
30
+ .equalTo("context", context)
31
+ .ascending("createdAt");
32
+ if (tenant) {
33
+ query.equalTo("tenant", tenant);
34
+ }
35
+ else {
36
+ query.doesNotExist("tenant");
37
+ }
38
+ return await query.find({ useMasterKey: true });
39
+ }
40
+ /**
41
+ * Collapse rows that a concurrent first-write produced for the same
42
+ * (context, tenant).
43
+ *
44
+ * The upsert below is find-then-insert, and the project's schema tool cannot
45
+ * declare a unique index (it has no `indexes` support at all), so two entries
46
+ * for a brand-new context saved at the same moment — a bulk sync, a migration
47
+ * pass writing in parallel — can both find nothing and both insert. This
48
+ * converges them afterwards, which a unique index could not do for rows that
49
+ * already exist.
50
+ *
51
+ * The oldest row wins and absorbs what the others learned, so nothing observed
52
+ * is lost. Only runs right after an insert, so the hot path never pays for it.
53
+ */
54
+ async function dedupe(context, tenant) {
55
+ const rows = await findRows(context, tenant);
56
+ if (rows.length < 2)
57
+ return rows[0];
58
+ const [keep, ...extras] = rows;
59
+ const known = new Set(keep.classNames ?? []);
60
+ let changed = false;
61
+ for (const extra of extras) {
62
+ for (const className of extra.classNames ?? []) {
63
+ if (!known.has(className)) {
64
+ known.add(className);
65
+ keep.addUnique("classNames", className);
66
+ changed = true;
67
+ }
68
+ }
69
+ if (!keep.hasConfig && extra.hasConfig) {
70
+ keep.hasConfig = true;
71
+ keep.config = extra.config;
72
+ keep.label = extra.label;
73
+ keep.description = extra.description;
74
+ changed = true;
75
+ }
76
+ const extraFirst = extra.firstSeenAt?.getTime() ?? Infinity;
77
+ if (extraFirst < (keep.firstSeenAt?.getTime() ?? Infinity)) {
78
+ keep.firstSeenAt = extra.firstSeenAt;
79
+ changed = true;
80
+ }
81
+ const extraLast = extra.lastSeenAt?.getTime() ?? 0;
82
+ if (extraLast > (keep.lastSeenAt?.getTime() ?? 0)) {
83
+ keep.lastSeenAt = extra.lastSeenAt;
84
+ changed = true;
85
+ }
86
+ }
87
+ if (changed) {
88
+ await keep.save(null, { useMasterKey: true });
89
+ }
90
+ await node_1.default.Object.destroyAll(extras, { useMasterKey: true });
91
+ console.log(`[@openinc/parse-server-opendash][Meta_Context] Merged ${extras.length} duplicate row(s) for ${context}`);
92
+ return keep;
93
+ }
94
+ /**
95
+ * Directory rows are written from hooks, i.e. with the master key, which skips
96
+ * `defaultAclHandler` — so the ACL it would have applied is applied here.
97
+ */
98
+ function applyAcl(row, tenant) {
99
+ const acl = new node_1.default.ACL();
100
+ acl.setRoleReadAccess("od-admin", true);
101
+ acl.setRoleWriteAccess("od-admin", true);
102
+ if (tenant) {
103
+ acl.setRoleReadAccess(`od-tenant-user-${tenant.id}`, true);
104
+ acl.setRoleReadAccess(`od-tenant-admin-${tenant.id}`, true);
105
+ acl.setRoleWriteAccess(`od-tenant-admin-${tenant.id}`, true);
106
+ }
107
+ row.setACL(acl);
108
+ }
109
+ /**
110
+ * Apply what this caller knows to a row. Split out so it can be replayed onto
111
+ * the survivor of a dedupe — see `registerMetaContext`.
112
+ */
113
+ function applyFields(row, params) {
114
+ const { config, entryClassName, now } = params;
115
+ let changed = false;
116
+ if (config) {
117
+ // The config is the authority on label/description; mirror it so the
118
+ // explorer can list contexts from this class alone.
119
+ const configLabel = config.config?.label;
120
+ const configDescription = config.config?.description;
121
+ if (row.hasConfig !== true) {
122
+ row.hasConfig = true;
123
+ changed = true;
124
+ }
125
+ if (row.config?.id !== config.id) {
126
+ row.config = config;
127
+ changed = true;
128
+ }
129
+ if (row.label !== configLabel) {
130
+ row.label = configLabel;
131
+ changed = true;
132
+ }
133
+ if (row.description !== configDescription) {
134
+ row.description = configDescription;
135
+ changed = true;
136
+ }
137
+ }
138
+ else if (row.hasConfig === undefined) {
139
+ row.hasConfig = false;
140
+ changed = true;
141
+ }
142
+ if (entryClassName && !(row.classNames ?? []).includes(entryClassName)) {
143
+ // `addUnique`, not an array assignment: concurrent writers each hold a
144
+ // stale copy of `classNames`, so assigning the whole array makes the last
145
+ // save win and silently drop what the others added. This becomes an atomic
146
+ // $addToSet on the server instead.
147
+ row.addUnique("classNames", entryClassName);
148
+ changed = true;
149
+ }
150
+ const lastSeen = row.lastSeenAt?.getTime() ?? 0;
151
+ if (now.getTime() - lastSeen > LAST_SEEN_MAX_AGE_MS) {
152
+ row.lastSeenAt = now;
153
+ changed = true;
154
+ }
155
+ return changed;
156
+ }
157
+ /**
158
+ * Record that a meta context exists, and what is known about it.
159
+ *
160
+ * Nothing declares a context: it comes into being the moment something writes a
161
+ * `Meta_Config` or a `Meta_Entry` carrying it. Every writer therefore registers
162
+ * itself here without knowing this directory exists — including a sync job, a
163
+ * backend script, or a client instance nobody in this repo controls, none of
164
+ * which a code-side list could ever enumerate.
165
+ *
166
+ * Crucially this also covers contexts that will **never** have a config: an
167
+ * entry saved from a `customConfig` form has no `origin` and no `Meta_Config`
168
+ * behind it (see `useSaveEntry`), which makes it invisible to any listing
169
+ * derived from the config class. Here it is a first-class row with
170
+ * `hasConfig: false`.
171
+ *
172
+ * Writes are conditional — a row is only saved when something actually changed
173
+ * — so the entry hook is a read on the hot path and a write only on the rare
174
+ * occasion that a context, a class, or a stale timestamp is new.
175
+ */
176
+ async function registerMetaContext(params) {
177
+ const { context, tenant, config, entryClassName } = params;
178
+ if (!context)
179
+ return;
180
+ const now = params.now ?? new Date();
181
+ const existing = (await findRows(context, tenant))[0];
182
+ const row = existing ?? new index_js_1.Meta_Context();
183
+ let changed = !existing;
184
+ if (!existing) {
185
+ row.context = context;
186
+ row.segments = parseSegments(context);
187
+ row.firstSeenAt = now;
188
+ if (tenant)
189
+ row.tenant = tenant;
190
+ }
191
+ changed = applyFields(row, { config, entryClassName, now }) || changed;
192
+ if (!changed)
193
+ return;
194
+ applyAcl(row, tenant);
195
+ await row.save(null, { useMasterKey: true });
196
+ if (existing)
197
+ return;
198
+ // A concurrent writer may have inserted the same row in the meantime. If the
199
+ // merge kept theirs and destroyed ours, replay our contribution onto the
200
+ // survivor: dedupe reads the losing rows before destroying them, so an update
201
+ // that landed after that read would otherwise die with the row.
202
+ const survivor = await dedupe(context, tenant);
203
+ if (survivor && survivor.id !== row.id) {
204
+ if (applyFields(survivor, { config, entryClassName, now })) {
205
+ await survivor.save(null, { useMasterKey: true });
206
+ }
207
+ }
208
+ }
@@ -18,9 +18,32 @@ export declare namespace Permissions {
18
18
  knowledge = "maintenance:can-access-knowledge",
19
19
  kanban_view = "maintenance:can-update-kanban-view"
20
20
  }
21
+ enum SERVICE {
22
+ ticket_read = "service:can-read-ticket",
23
+ ticket_create = "service:can-create-ticket",
24
+ ticket_edit = "service:can-edit-ticket",
25
+ ticket_delete = "service:can-delete-ticket",
26
+ ticket_assign = "service:can-assign-ticket",
27
+ schedule_read = "service:can-read-schedule",
28
+ schedule_create = "service:can-create-schedule",
29
+ schedule_edit = "service:can-edit-schedule",
30
+ schedule_delete = "service:can-delete-schedule",
31
+ execution_perform = "service:can-perform-execution",
32
+ template_manage = "service:can-manage-template",
33
+ masterdata_manage = "service:can-manage-masterdata",
34
+ formconfig_manage = "service:can-manage-formconfig",
35
+ analytics_view = "service:can-view-analytics"
36
+ }
21
37
  enum CORE {
22
38
  adminoverview = "opendash:can-access-admin-overview",
23
- mailadmin = "parse-admin:can-access-mail-admin"
39
+ mailadmin = "parse-admin:can-access-mail-admin",
40
+ rmqadmin = "parse-admin:can-access-rmq-admin",
41
+ metaadmin = "meta:can-see-admin",
42
+ chatadmin = "chat:can-see-admin",
43
+ changelog = "changelog:can-see-app"
44
+ }
45
+ enum GTFS {
46
+ global = "gtfs:can-access-global"
24
47
  }
25
48
  enum DOCUMENTATION {
26
49
  see_app = "documentation:can-see-app"
@@ -28,12 +28,46 @@ var Permissions;
28
28
  // Update
29
29
  MAINTENANCE["kanban_view"] = "maintenance:can-update-kanban-view";
30
30
  })(MAINTENANCE = Permissions.MAINTENANCE || (Permissions.MAINTENANCE = {}));
31
+ let SERVICE;
32
+ (function (SERVICE) {
33
+ // Tickets
34
+ SERVICE["ticket_read"] = "service:can-read-ticket";
35
+ SERVICE["ticket_create"] = "service:can-create-ticket";
36
+ SERVICE["ticket_edit"] = "service:can-edit-ticket";
37
+ SERVICE["ticket_delete"] = "service:can-delete-ticket";
38
+ SERVICE["ticket_assign"] = "service:can-assign-ticket";
39
+ // Schedules (Wartungspläne)
40
+ SERVICE["schedule_read"] = "service:can-read-schedule";
41
+ SERVICE["schedule_create"] = "service:can-create-schedule";
42
+ SERVICE["schedule_edit"] = "service:can-edit-schedule";
43
+ SERVICE["schedule_delete"] = "service:can-delete-schedule";
44
+ // Executions (Durchführungen)
45
+ SERVICE["execution_perform"] = "service:can-perform-execution";
46
+ // Catalog / master data / form config
47
+ SERVICE["template_manage"] = "service:can-manage-template";
48
+ SERVICE["masterdata_manage"] = "service:can-manage-masterdata";
49
+ SERVICE["formconfig_manage"] = "service:can-manage-formconfig";
50
+ // Analytics / monitoring
51
+ SERVICE["analytics_view"] = "service:can-view-analytics";
52
+ })(SERVICE = Permissions.SERVICE || (Permissions.SERVICE = {}));
31
53
  let CORE;
32
54
  (function (CORE) {
33
55
  // Access
34
56
  CORE["adminoverview"] = "opendash:can-access-admin-overview";
35
57
  CORE["mailadmin"] = "parse-admin:can-access-mail-admin";
58
+ CORE["rmqadmin"] = "parse-admin:can-access-rmq-admin";
59
+ // Admin areas of the CORE-owned plugins. The owning classes are mapped to
60
+ // CORE in featuremap.json (OD3_Meta_*, OD3_Chat_Message, OD3_Changelog),
61
+ // so their permissions register under the same feature.
62
+ CORE["metaadmin"] = "meta:can-see-admin";
63
+ CORE["chatadmin"] = "chat:can-see-admin";
64
+ CORE["changelog"] = "changelog:can-see-app";
36
65
  })(CORE = Permissions.CORE || (Permissions.CORE = {}));
66
+ let GTFS;
67
+ (function (GTFS) {
68
+ // Access
69
+ GTFS["global"] = "gtfs:can-access-global";
70
+ })(GTFS = Permissions.GTFS || (Permissions.GTFS = {}));
37
71
  let DOCUMENTATION;
38
72
  (function (DOCUMENTATION) {
39
73
  // Access
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Backend port of the `@opendash/ui` ruleset evaluator
3
+ * (`libs/ui/src/features/ruleset/functions/applyRuleSet.ts`).
4
+ *
5
+ * The frontend authors condition trees with the RuleSet editor and stores them
6
+ * as JSON; notifications must be created server-side, so the same evaluation
7
+ * logic has to run here. This is a faithful, dependency-free copy of the pure
8
+ * evaluation core — keep the operator semantics in sync with the frontend.
9
+ */
10
+ export type RuleOperator = "equals" | "notEquals" | "contains" | "notContains" | "startsWith" | "endsWith" | "isEmpty" | "isNotEmpty" | "gt" | "gte" | "lt" | "lte" | "between" | "isTrue" | "isFalse" | "before" | "after";
11
+ /** A single comparison against one field. */
12
+ export interface Rule {
13
+ id: string;
14
+ kind: "rule";
15
+ field: string;
16
+ operator: RuleOperator;
17
+ value?: string | number | boolean;
18
+ /** Secondary value — only used for the "between" operator. */
19
+ value2?: number;
20
+ }
21
+ /** A logical group that chains rules and nested groups via AND / OR. */
22
+ export interface RuleGroup {
23
+ id: string;
24
+ kind: "group";
25
+ conjunction: "AND" | "OR";
26
+ rules: Array<Rule | RuleGroup>;
27
+ }
28
+ export type RuleSetValue = RuleGroup;
29
+ /** A saved ruleset-function entry as produced by the RuleSet editor. */
30
+ export interface RuleSetFunctionEntry {
31
+ ruleset: RuleSetValue;
32
+ function: string;
33
+ params?: Record<string, unknown>;
34
+ }
35
+ /** Resolves the current value for a given field name. */
36
+ export type GetValueFn = (fieldName: string) => unknown;
37
+ /**
38
+ * Evaluates a `RuleSetValue` against live data. An empty rule group always
39
+ * returns `true` (matches the frontend semantics).
40
+ */
41
+ export declare function applyRuleSet(ruleSet: RuleSetValue, getValue: GetValueFn): boolean;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Backend port of the `@opendash/ui` ruleset evaluator
4
+ * (`libs/ui/src/features/ruleset/functions/applyRuleSet.ts`).
5
+ *
6
+ * The frontend authors condition trees with the RuleSet editor and stores them
7
+ * as JSON; notifications must be created server-side, so the same evaluation
8
+ * logic has to run here. This is a faithful, dependency-free copy of the pure
9
+ * evaluation core — keep the operator semantics in sync with the frontend.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.applyRuleSet = applyRuleSet;
13
+ function evaluateRule(rule, getValue) {
14
+ const fieldValue = getValue(rule.field);
15
+ switch (rule.operator) {
16
+ case "equals":
17
+ // eslint-disable-next-line eqeqeq
18
+ return fieldValue == rule.value;
19
+ case "notEquals":
20
+ // eslint-disable-next-line eqeqeq
21
+ return fieldValue != rule.value;
22
+ case "contains":
23
+ return (typeof fieldValue === "string" &&
24
+ fieldValue.includes(String(rule.value ?? "")));
25
+ case "notContains":
26
+ return (typeof fieldValue === "string" &&
27
+ !fieldValue.includes(String(rule.value ?? "")));
28
+ case "startsWith":
29
+ return (typeof fieldValue === "string" &&
30
+ fieldValue.startsWith(String(rule.value ?? "")));
31
+ case "endsWith":
32
+ return (typeof fieldValue === "string" &&
33
+ fieldValue.endsWith(String(rule.value ?? "")));
34
+ case "isEmpty":
35
+ return (fieldValue === null || fieldValue === undefined || fieldValue === "");
36
+ case "isNotEmpty":
37
+ return (fieldValue !== null && fieldValue !== undefined && fieldValue !== "");
38
+ case "gt":
39
+ return Number(fieldValue) > Number(rule.value);
40
+ case "gte":
41
+ return Number(fieldValue) >= Number(rule.value);
42
+ case "lt":
43
+ return Number(fieldValue) < Number(rule.value);
44
+ case "lte":
45
+ return Number(fieldValue) <= Number(rule.value);
46
+ case "between":
47
+ return (Number(fieldValue) >= Number(rule.value) &&
48
+ Number(fieldValue) <= Number(rule.value2));
49
+ case "isTrue":
50
+ return fieldValue === true;
51
+ case "isFalse":
52
+ return fieldValue === false;
53
+ case "before":
54
+ return new Date(String(fieldValue)) < new Date(String(rule.value ?? ""));
55
+ case "after":
56
+ return new Date(String(fieldValue)) > new Date(String(rule.value ?? ""));
57
+ default:
58
+ return false;
59
+ }
60
+ }
61
+ function evaluateGroup(group, getValue) {
62
+ if (group.rules.length === 0)
63
+ return true;
64
+ if (group.conjunction === "AND") {
65
+ return group.rules.every((item) => item.kind === "rule"
66
+ ? evaluateRule(item, getValue)
67
+ : evaluateGroup(item, getValue));
68
+ }
69
+ return group.rules.some((item) => item.kind === "rule"
70
+ ? evaluateRule(item, getValue)
71
+ : evaluateGroup(item, getValue));
72
+ }
73
+ /**
74
+ * Evaluates a `RuleSetValue` against live data. An empty rule group always
75
+ * returns `true` (matches the frontend semantics).
76
+ */
77
+ function applyRuleSet(ruleSet, getValue) {
78
+ if (!ruleSet || ruleSet.kind !== "group")
79
+ return false;
80
+ return evaluateGroup(ruleSet, getValue);
81
+ }
@@ -9,6 +9,7 @@ const node_1 = __importDefault(require("parse/node"));
9
9
  const index_js_1 = require("../../permissions/index.js");
10
10
  const SchemaClass_js_1 = require("../classes/SchemaClass.js");
11
11
  const index_js_2 = require("../index.js");
12
+ const migrationMode_js_1 = require("./migrationMode.js");
12
13
  /**
13
14
  * Handles the default beforeSave() logic for a Parse.Object.
14
15
  *
@@ -25,6 +26,13 @@ async function defaultHandler(request) {
25
26
  console.log(`[@openinc/parse-server-opendash] Skipping default beforeSave() handler of ${className} for masterkey`);
26
27
  return;
27
28
  }
29
+ // Maintenance-key writes report `master: false` (Parse sets `request.master` from
30
+ // `auth.isMaster` only), so without this they would fall through to the
31
+ // `!request.user` throw below. See helper/migrationMode.ts.
32
+ if ((0, migrationMode_js_1.isMigrationMode)()) {
33
+ console.log(`[@openinc/parse-server-opendash] Skipping default beforeSave() handler of ${className} for migration mode`);
34
+ return;
35
+ }
28
36
  if (!request.user) {
29
37
  throw new Error();
30
38
  }
@@ -74,6 +82,11 @@ async function defaultHandler(request) {
74
82
  */
75
83
  async function defaultAclHandler(request, options) {
76
84
  const className = request.object.className;
85
+ // In migration mode the writer supplies the ACL itself (it has to replicate the
86
+ // historical owner/tenant), so applying defaults here would overwrite it.
87
+ if ((0, migrationMode_js_1.isMigrationMode)()) {
88
+ return;
89
+ }
77
90
  const currentSchema = SchemaClass_js_1.SchemaClass.getInstance().getClassSchema(className);
78
91
  if (!currentSchema) {
79
92
  console.warn(`[@openinc/parse-server-opendash] Skipping default beforeSave() ACL handler, no schema found for ${className}`);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Migration mode — an explicit escape hatch for the openservice v1 → v2 data
3
+ * migration (see `plugins/openservice/MIGRATION_PLAN.md` in the opendash repo).
4
+ *
5
+ * Why this exists: the migration writes with the **maintenance** key, because that
6
+ * is the only auth level Parse Server lets set a historical `createdAt`
7
+ * (`RestWrite.js`: `if (this.auth.isMaintenance && this.data.createdAt …)`). But
8
+ * `Auth.maintenance()` leaves `isMaster: false`, and `getRequestObject()` derives
9
+ * `request.master` from `isMaster` only — so triggers see `master === false`.
10
+ * `defaultHandler` therefore does *not* take its master-key shortcut, reaches
11
+ * `if (!request.user) throw`, and every maintenance-key write fails.
12
+ *
13
+ * So `request.master` cannot serve as an "is this the migration?" signal, and
14
+ * sniffing for a missing user would silently change behaviour for ordinary
15
+ * requests. An explicit env flag keeps the exception auditable, greppable, and
16
+ * impossible to hit by accident.
17
+ *
18
+ * While enabled, the caller takes over responsibility for everything it disables:
19
+ * `tenant`, `user` and the ACL on every object written.
20
+ */
21
+ /**
22
+ * True when `OD_MIGRATION_MODE=1`. Logs a loud one-time banner the first time it
23
+ * is observed, so a server accidentally left in migration mode is obvious in the
24
+ * logs rather than silently permissive.
25
+ */
26
+ export declare function isMigrationMode(): boolean;