grm-shared-library 1.1.237 → 1.1.239
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/README.md +369 -369
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/kafka/topics.js +11 -0
- package/dist/cjs/kafka/topics.js.map +1 -1
- package/dist/cjs/modules/dispatch/constants/response-unit-dispatch-defaults.const.js +1 -0
- package/dist/cjs/modules/dispatch/constants/response-unit-dispatch-defaults.const.js.map +1 -1
- package/dist/cjs/modules/dispatch/dtos/response-unit-dispatch-notification.dto.js +5 -0
- package/dist/cjs/modules/dispatch/dtos/response-unit-dispatch-notification.dto.js.map +1 -1
- package/dist/cjs/modules/dispatch/dtos/update-response-unit-dispatch-status.dto.js +1 -0
- package/dist/cjs/modules/dispatch/dtos/update-response-unit-dispatch-status.dto.js.map +1 -1
- package/dist/cjs/modules/dispatch/enums/response-unit-dispatch-status.enum.js +6 -0
- package/dist/cjs/modules/dispatch/enums/response-unit-dispatch-status.enum.js.map +1 -1
- package/dist/cjs/modules/dispatch/enums/response-unit-dispatch-transition-source.enum.js +3 -1
- package/dist/cjs/modules/dispatch/enums/response-unit-dispatch-transition-source.enum.js.map +1 -1
- package/dist/cjs/modules/dispatch/utils/response-unit-dispatch-status.util.js +6 -2
- package/dist/cjs/modules/dispatch/utils/response-unit-dispatch-status.util.js.map +1 -1
- package/dist/cjs/modules/incident/enums/incident-actions.enum.js +4 -0
- package/dist/cjs/modules/incident/enums/incident-actions.enum.js.map +1 -1
- package/dist/cjs/modules/incident-sla/constants/emergency-incident-workflow.const.js +53 -0
- package/dist/cjs/modules/incident-sla/constants/emergency-incident-workflow.const.js.map +1 -0
- package/dist/cjs/modules/incident-sla/constants/incident-sla-defaults.const.js +258 -0
- package/dist/cjs/modules/incident-sla/constants/incident-sla-defaults.const.js.map +1 -0
- package/dist/cjs/modules/incident-sla/constants/incident-sla-kafka.const.js +27 -0
- package/dist/cjs/modules/incident-sla/constants/incident-sla-kafka.const.js.map +1 -0
- package/dist/cjs/modules/incident-sla/dtos/emergency-notification.dto.js +419 -0
- package/dist/cjs/modules/incident-sla/dtos/emergency-notification.dto.js.map +1 -0
- package/dist/cjs/modules/incident-sla/dtos/emergency-operations.dto.js +155 -0
- package/dist/cjs/modules/incident-sla/dtos/emergency-operations.dto.js.map +1 -0
- package/dist/cjs/modules/incident-sla/dtos/emergency-workflow.dto.js +216 -0
- package/dist/cjs/modules/incident-sla/dtos/emergency-workflow.dto.js.map +1 -0
- package/dist/cjs/modules/incident-sla/dtos/incident-sla-analytics-query.dto.js +95 -0
- package/dist/cjs/modules/incident-sla/dtos/incident-sla-analytics-query.dto.js.map +1 -0
- package/dist/cjs/modules/incident-sla/dtos/incident-sla-policy.dto.js +509 -0
- package/dist/cjs/modules/incident-sla/dtos/incident-sla-policy.dto.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/emergency-incident-stage.enum.js +32 -0
- package/dist/cjs/modules/incident-sla/enums/emergency-incident-stage.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/emergency-notification.enum.js +44 -0
- package/dist/cjs/modules/incident-sla/enums/emergency-notification.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/eta-status.enum.js +32 -0
- package/dist/cjs/modules/incident-sla/enums/eta-status.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/incident-closure.enum.js +23 -0
- package/dist/cjs/modules/incident-sla/enums/incident-closure.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-actor.enum.js +46 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-actor.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-error-code.enum.js +26 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-error-code.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-event-type.enum.js +45 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-event-type.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-stage.enum.js +26 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-stage.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-status.enum.js +38 -0
- package/dist/cjs/modules/incident-sla/enums/incident-sla-status.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/sla-escalation.enum.js +50 -0
- package/dist/cjs/modules/incident-sla/enums/sla-escalation.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/sla-exception.enum.js +27 -0
- package/dist/cjs/modules/incident-sla/enums/sla-exception.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/sla-overall-state.enum.js +28 -0
- package/dist/cjs/modules/incident-sla/enums/sla-overall-state.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/sla-pause.enum.js +28 -0
- package/dist/cjs/modules/incident-sla/enums/sla-pause.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/enums/sla-policy-scope.enum.js +19 -0
- package/dist/cjs/modules/incident-sla/enums/sla-policy-scope.enum.js.map +1 -0
- package/dist/cjs/modules/incident-sla/index.js +53 -0
- package/dist/cjs/modules/incident-sla/index.js.map +1 -0
- package/dist/cjs/modules/incident-sla/interfaces/emergency-workflow.js +3 -0
- package/dist/cjs/modules/incident-sla/interfaces/emergency-workflow.js.map +1 -0
- package/dist/cjs/modules/incident-sla/interfaces/incident-sla-analytics.js +3 -0
- package/dist/cjs/modules/incident-sla/interfaces/incident-sla-analytics.js.map +1 -0
- package/dist/cjs/modules/incident-sla/interfaces/incident-sla-events.js +3 -0
- package/dist/cjs/modules/incident-sla/interfaces/incident-sla-events.js.map +1 -0
- package/dist/cjs/modules/incident-sla/interfaces/incident-sla-policy.js +3 -0
- package/dist/cjs/modules/incident-sla/interfaces/incident-sla-policy.js.map +1 -0
- package/dist/cjs/modules/incident-sla/interfaces/incident-sla-views.js +3 -0
- package/dist/cjs/modules/incident-sla/interfaces/incident-sla-views.js.map +1 -0
- package/dist/cjs/modules/incident-sla/utils/incident-sla-eta.util.js +40 -0
- package/dist/cjs/modules/incident-sla/utils/incident-sla-eta.util.js.map +1 -0
- package/dist/cjs/modules/incident-sla/utils/incident-sla-event-id.util.js +18 -0
- package/dist/cjs/modules/incident-sla/utils/incident-sla-event-id.util.js.map +1 -0
- package/dist/cjs/modules/incident-sla/utils/incident-sla-priority.util.js +31 -0
- package/dist/cjs/modules/incident-sla/utils/incident-sla-priority.util.js.map +1 -0
- package/dist/cjs/modules/incident-sla/utils/incident-sla-time.util.js +129 -0
- package/dist/cjs/modules/incident-sla/utils/incident-sla-time.util.js.map +1 -0
- package/dist/cjs/modules/permission/data/permissions.data.js +11 -0
- package/dist/cjs/modules/permission/data/permissions.data.js.map +1 -1
- package/dist/cjs/modules/permission/enums/permission-actions.enum.js +21 -0
- package/dist/cjs/modules/permission/enums/permission-actions.enum.js.map +1 -1
- package/dist/cjs/modules/permission/enums/permissions-module.enum.js +1 -0
- package/dist/cjs/modules/permission/enums/permissions-module.enum.js.map +1 -1
- package/dist/cjs/modules/role/data/roles.data.js +37 -0
- package/dist/cjs/modules/role/data/roles.data.js.map +1 -1
- package/dist/cjs/modules/sms/constants/sms-templates.config.js +27 -1
- package/dist/cjs/modules/sms/constants/sms-templates.config.js.map +1 -1
- package/dist/cjs/modules/sms/enums/sms-template.enum.js +2 -0
- package/dist/cjs/modules/sms/enums/sms-template.enum.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/kafka/topics.js +11 -0
- package/dist/esm/kafka/topics.js.map +1 -1
- package/dist/esm/modules/dispatch/constants/response-unit-dispatch-defaults.const.js +1 -0
- package/dist/esm/modules/dispatch/constants/response-unit-dispatch-defaults.const.js.map +1 -1
- package/dist/esm/modules/dispatch/dtos/response-unit-dispatch-notification.dto.js +5 -0
- package/dist/esm/modules/dispatch/dtos/response-unit-dispatch-notification.dto.js.map +1 -1
- package/dist/esm/modules/dispatch/dtos/update-response-unit-dispatch-status.dto.js +1 -0
- package/dist/esm/modules/dispatch/dtos/update-response-unit-dispatch-status.dto.js.map +1 -1
- package/dist/esm/modules/dispatch/enums/response-unit-dispatch-status.enum.js +6 -0
- package/dist/esm/modules/dispatch/enums/response-unit-dispatch-status.enum.js.map +1 -1
- package/dist/esm/modules/dispatch/enums/response-unit-dispatch-transition-source.enum.js +3 -1
- package/dist/esm/modules/dispatch/enums/response-unit-dispatch-transition-source.enum.js.map +1 -1
- package/dist/esm/modules/dispatch/utils/response-unit-dispatch-status.util.js +6 -2
- package/dist/esm/modules/dispatch/utils/response-unit-dispatch-status.util.js.map +1 -1
- package/dist/esm/modules/incident/enums/incident-actions.enum.js +4 -0
- package/dist/esm/modules/incident/enums/incident-actions.enum.js.map +1 -1
- package/dist/esm/modules/incident-sla/constants/emergency-incident-workflow.const.js +49 -0
- package/dist/esm/modules/incident-sla/constants/emergency-incident-workflow.const.js.map +1 -0
- package/dist/esm/modules/incident-sla/constants/incident-sla-defaults.const.js +255 -0
- package/dist/esm/modules/incident-sla/constants/incident-sla-defaults.const.js.map +1 -0
- package/dist/esm/modules/incident-sla/constants/incident-sla-kafka.const.js +24 -0
- package/dist/esm/modules/incident-sla/constants/incident-sla-kafka.const.js.map +1 -0
- package/dist/esm/modules/incident-sla/dtos/emergency-notification.dto.js +405 -0
- package/dist/esm/modules/incident-sla/dtos/emergency-notification.dto.js.map +1 -0
- package/dist/esm/modules/incident-sla/dtos/emergency-operations.dto.js +144 -0
- package/dist/esm/modules/incident-sla/dtos/emergency-operations.dto.js.map +1 -0
- package/dist/esm/modules/incident-sla/dtos/emergency-workflow.dto.js +206 -0
- package/dist/esm/modules/incident-sla/dtos/emergency-workflow.dto.js.map +1 -0
- package/dist/esm/modules/incident-sla/dtos/incident-sla-analytics-query.dto.js +91 -0
- package/dist/esm/modules/incident-sla/dtos/incident-sla-analytics-query.dto.js.map +1 -0
- package/dist/esm/modules/incident-sla/dtos/incident-sla-policy.dto.js +495 -0
- package/dist/esm/modules/incident-sla/dtos/incident-sla-policy.dto.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/emergency-incident-stage.enum.js +29 -0
- package/dist/esm/modules/incident-sla/enums/emergency-incident-stage.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/emergency-notification.enum.js +41 -0
- package/dist/esm/modules/incident-sla/enums/emergency-notification.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/eta-status.enum.js +29 -0
- package/dist/esm/modules/incident-sla/enums/eta-status.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/incident-closure.enum.js +20 -0
- package/dist/esm/modules/incident-sla/enums/incident-closure.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-actor.enum.js +43 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-actor.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-error-code.enum.js +23 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-error-code.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-event-type.enum.js +42 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-event-type.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-stage.enum.js +23 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-stage.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-status.enum.js +35 -0
- package/dist/esm/modules/incident-sla/enums/incident-sla-status.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/sla-escalation.enum.js +47 -0
- package/dist/esm/modules/incident-sla/enums/sla-escalation.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/sla-exception.enum.js +24 -0
- package/dist/esm/modules/incident-sla/enums/sla-exception.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/sla-overall-state.enum.js +25 -0
- package/dist/esm/modules/incident-sla/enums/sla-overall-state.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/sla-pause.enum.js +25 -0
- package/dist/esm/modules/incident-sla/enums/sla-pause.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/enums/sla-policy-scope.enum.js +16 -0
- package/dist/esm/modules/incident-sla/enums/sla-policy-scope.enum.js.map +1 -0
- package/dist/esm/modules/incident-sla/index.js +37 -0
- package/dist/esm/modules/incident-sla/index.js.map +1 -0
- package/dist/esm/modules/incident-sla/interfaces/emergency-workflow.js +2 -0
- package/dist/esm/modules/incident-sla/interfaces/emergency-workflow.js.map +1 -0
- package/dist/esm/modules/incident-sla/interfaces/incident-sla-analytics.js +2 -0
- package/dist/esm/modules/incident-sla/interfaces/incident-sla-analytics.js.map +1 -0
- package/dist/esm/modules/incident-sla/interfaces/incident-sla-events.js +2 -0
- package/dist/esm/modules/incident-sla/interfaces/incident-sla-events.js.map +1 -0
- package/dist/esm/modules/incident-sla/interfaces/incident-sla-policy.js +2 -0
- package/dist/esm/modules/incident-sla/interfaces/incident-sla-policy.js.map +1 -0
- package/dist/esm/modules/incident-sla/interfaces/incident-sla-views.js +2 -0
- package/dist/esm/modules/incident-sla/interfaces/incident-sla-views.js.map +1 -0
- package/dist/esm/modules/incident-sla/utils/incident-sla-eta.util.js +35 -0
- package/dist/esm/modules/incident-sla/utils/incident-sla-eta.util.js.map +1 -0
- package/dist/esm/modules/incident-sla/utils/incident-sla-event-id.util.js +14 -0
- package/dist/esm/modules/incident-sla/utils/incident-sla-event-id.util.js.map +1 -0
- package/dist/esm/modules/incident-sla/utils/incident-sla-priority.util.js +25 -0
- package/dist/esm/modules/incident-sla/utils/incident-sla-priority.util.js.map +1 -0
- package/dist/esm/modules/incident-sla/utils/incident-sla-time.util.js +119 -0
- package/dist/esm/modules/incident-sla/utils/incident-sla-time.util.js.map +1 -0
- package/dist/esm/modules/permission/data/permissions.data.js +11 -0
- package/dist/esm/modules/permission/data/permissions.data.js.map +1 -1
- package/dist/esm/modules/permission/enums/permission-actions.enum.js +21 -0
- package/dist/esm/modules/permission/enums/permission-actions.enum.js.map +1 -1
- package/dist/esm/modules/permission/enums/permissions-module.enum.js +1 -0
- package/dist/esm/modules/permission/enums/permissions-module.enum.js.map +1 -1
- package/dist/esm/modules/role/data/roles.data.js +37 -0
- package/dist/esm/modules/role/data/roles.data.js.map +1 -1
- package/dist/esm/modules/sms/constants/sms-templates.config.js +27 -1
- package/dist/esm/modules/sms/constants/sms-templates.config.js.map +1 -1
- package/dist/esm/modules/sms/enums/sms-template.enum.js +2 -0
- package/dist/esm/modules/sms/enums/sms-template.enum.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/kafka/topics.d.ts +5 -0
- package/dist/types/modules/dispatch/dtos/response-unit-dispatch-notification.dto.d.ts +1 -0
- package/dist/types/modules/dispatch/enums/response-unit-dispatch-status.enum.d.ts +7 -1
- package/dist/types/modules/dispatch/enums/response-unit-dispatch-transition-source.enum.d.ts +4 -2
- package/dist/types/modules/dispatch/interfaces/incident-response-unit-dispatch.d.ts +5 -0
- package/dist/types/modules/dispatch/interfaces/response-unit-dispatch-events.d.ts +2 -0
- package/dist/types/modules/dispatch/utils/response-unit-dispatch-status.util.d.ts +1 -1
- package/dist/types/modules/incident/enums/incident-actions.enum.d.ts +4 -0
- package/dist/types/modules/incident-sla/constants/emergency-incident-workflow.const.d.ts +46 -0
- package/dist/types/modules/incident-sla/constants/incident-sla-defaults.const.d.ts +81 -0
- package/dist/types/modules/incident-sla/constants/incident-sla-kafka.const.d.ts +22 -0
- package/dist/types/modules/incident-sla/dtos/emergency-notification.dto.d.ts +99 -0
- package/dist/types/modules/incident-sla/dtos/emergency-operations.dto.d.ts +52 -0
- package/dist/types/modules/incident-sla/dtos/emergency-workflow.dto.d.ts +55 -0
- package/dist/types/modules/incident-sla/dtos/incident-sla-analytics-query.dto.d.ts +23 -0
- package/dist/types/modules/incident-sla/dtos/incident-sla-policy.dto.d.ts +104 -0
- package/dist/types/modules/incident-sla/enums/emergency-incident-stage.enum.d.ts +27 -0
- package/dist/types/modules/incident-sla/enums/emergency-notification.enum.d.ts +37 -0
- package/dist/types/modules/incident-sla/enums/eta-status.enum.d.ts +26 -0
- package/dist/types/modules/incident-sla/enums/incident-closure.enum.d.ts +17 -0
- package/dist/types/modules/incident-sla/enums/incident-sla-actor.enum.d.ts +40 -0
- package/dist/types/modules/incident-sla/enums/incident-sla-error-code.enum.d.ts +21 -0
- package/dist/types/modules/incident-sla/enums/incident-sla-event-type.enum.d.ts +36 -0
- package/dist/types/modules/incident-sla/enums/incident-sla-stage.enum.d.ts +21 -0
- package/dist/types/modules/incident-sla/enums/incident-sla-status.enum.d.ts +33 -0
- package/dist/types/modules/incident-sla/enums/sla-escalation.enum.d.ts +42 -0
- package/dist/types/modules/incident-sla/enums/sla-exception.enum.d.ts +21 -0
- package/dist/types/modules/incident-sla/enums/sla-overall-state.enum.d.ts +22 -0
- package/dist/types/modules/incident-sla/enums/sla-pause.enum.d.ts +22 -0
- package/dist/types/modules/incident-sla/enums/sla-policy-scope.enum.d.ts +14 -0
- package/dist/types/modules/incident-sla/index.d.ts +31 -0
- package/dist/types/modules/incident-sla/interfaces/emergency-workflow.d.ts +131 -0
- package/dist/types/modules/incident-sla/interfaces/incident-sla-analytics.d.ts +356 -0
- package/dist/types/modules/incident-sla/interfaces/incident-sla-events.d.ts +173 -0
- package/dist/types/modules/incident-sla/interfaces/incident-sla-policy.d.ts +147 -0
- package/dist/types/modules/incident-sla/interfaces/incident-sla-views.d.ts +224 -0
- package/dist/types/modules/incident-sla/utils/incident-sla-eta.util.d.ts +15 -0
- package/dist/types/modules/incident-sla/utils/incident-sla-event-id.util.d.ts +8 -0
- package/dist/types/modules/incident-sla/utils/incident-sla-priority.util.d.ts +14 -0
- package/dist/types/modules/incident-sla/utils/incident-sla-time.util.d.ts +65 -0
- package/dist/types/modules/permission/enums/permission-actions.enum.d.ts +6 -0
- package/dist/types/modules/permission/enums/permissions-module.enum.d.ts +2 -1
- package/dist/types/modules/sms/enums/sms-template.enum.d.ts +2 -0
- package/package.json +72 -72
package/README.md
CHANGED
|
@@ -1,369 +1,369 @@
|
|
|
1
|
-
# grm-shared-library
|
|
2
|
-
|
|
3
|
-
Common code for GRM — shared types, enums, interfaces, DTOs and constants for the
|
|
4
|
-
Clarion / Upesy backend NestJS microservices.
|
|
5
|
-
|
|
6
|
-
## Calendar dates
|
|
7
|
-
|
|
8
|
-
One definition of what a date is, because there used to be four and they had
|
|
9
|
-
drifted apart.
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
import {
|
|
13
|
-
ISO_CALENDAR_DATE_PATTERN, // the one regex - never write another
|
|
14
|
-
isCalendarDate, // a real YYYY-MM-DD day; rejects 2026-02-30
|
|
15
|
-
toCalendarDate, // Date | string -> YYYY-MM-DD | undefined
|
|
16
|
-
isPlausibleBirthDate, // not future, within 120 years, NO minimum age
|
|
17
|
-
calculateAge, isMinor, // whole years, UTC throughout
|
|
18
|
-
AGE_OF_MAJORITY_YEARS, // 18 - for COPY ONLY, never a validator
|
|
19
|
-
IsCalendarDate, IsBirthDate // class-validator decorators over the above
|
|
20
|
-
} from 'grm-shared-library';
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Why it refuses things
|
|
24
|
-
|
|
25
|
-
A calendar date has no time, no zone and no offset: somebody born on 17 April
|
|
26
|
-
1990 was born on that day in Nairobi, in Kampala and in London alike. Every bug
|
|
27
|
-
this module exists to prevent comes from treating it as a moment.
|
|
28
|
-
|
|
29
|
-
The old conversion did `new Date(freeText)` and then `.toISOString()`. Under the
|
|
30
|
-
production pods` own `TZ=Africa/Nairobi`:
|
|
31
|
-
|
|
32
|
-
| input | old result | |
|
|
33
|
-
|---|---|---|
|
|
34
|
-
| `04/17/1990` | `1990-04-16` | a day early |
|
|
35
|
-
| `new Date(1990,3,17)` | `1990-04-16` | a day early - and this is what a date picker hands you |
|
|
36
|
-
| `17/04/1990` | `undefined` | the East African spelling, refused |
|
|
37
|
-
| `1990-04-17` | `1990-04-17` | correct |
|
|
38
|
-
|
|
39
|
-
`toCalendarDate` now accepts exactly three things and refuses everything else:
|
|
40
|
-
|
|
41
|
-
1. `YYYY-MM-DD`
|
|
42
|
-
2. a full ISO-8601 instant in **UTC** (`1990-04-17T00:00:00.000Z`)
|
|
43
|
-
3. a `Date`, read in UTC - so build one with `Date.UTC`, never `new Date(y,m,d)`
|
|
44
|
-
|
|
45
|
-
An **offset** timestamp is refused too: `1990-04-17T00:00:00+03:00` is 16 April
|
|
46
|
-
in UTC, and only the sender knows which day they meant.
|
|
47
|
-
|
|
48
|
-
Free text is refused rather than guessed at. `04/17/1990` and `17/04/1990` are
|
|
49
|
-
the same eight characters in two orders, this platform serves Kenya, Uganda and
|
|
50
|
-
Tanzania where the second is the common spelling, and there is no reading of
|
|
51
|
-
that string that is safe to infer. A refused date surfaces as a field somebody
|
|
52
|
-
is asked to collect; a drifted one is a permanently wrong birthday at a partner
|
|
53
|
-
that cannot delete a member, on the record a paramedic uses to size a drug dose.
|
|
54
|
-
|
|
55
|
-
### No minimum age
|
|
56
|
-
|
|
57
|
-
`isPlausibleBirthDate` refuses a future date and one beyond a human lifespan,
|
|
58
|
-
and imposes no lower bound. Dependants on a family emergency-cover plan are
|
|
59
|
-
children by design - that is the whole point of registering them with a
|
|
60
|
-
responder - so a minimum-age rule would refuse exactly the people the feature
|
|
61
|
-
exists for. `AGE_OF_MAJORITY_YEARS` is there for wording, not for gating.
|
|
62
|
-
|
|
63
|
-
### A decorator alone guarantees nothing
|
|
64
|
-
|
|
65
|
-
`@IsCalendarDate` / `@IsBirthDate` are real validators, but on this platform
|
|
66
|
-
they are not enforcement on their own: clarion-authentication registers **no**
|
|
67
|
-
global `ValidationPipe`, the spreadsheet importer builds plain objects a pipe
|
|
68
|
-
never sees, and internal service-to-service calls bypass HTTP entirely. Any
|
|
69
|
-
service that OWNS a date must re-check it in the service layer, the way
|
|
70
|
-
`UserService.updateCountry` re-checks `isSupportedCountryCode`.
|
|
71
|
-
|
|
72
|
-
### Postgres round trip
|
|
73
|
-
|
|
74
|
-
A `type: date` column returns a **string** through this stack, not a `Date`:
|
|
75
|
-
`pg` parses OID 1082 into a local-midnight `Date` and TypeORM reads it back with
|
|
76
|
-
local components, so the two cancel out. It is correct, but correct by
|
|
77
|
-
cancellation - never pass a value from a date column into `new Date()`.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## Country & Currency (multi-country foundation)
|
|
82
|
-
|
|
83
|
-
Platform-level primitives for country-aware emergency response, newsfeed,
|
|
84
|
-
notifications, billing, maps and admin. This is the shared source of truth so
|
|
85
|
-
services stop hard-coding Kenya-specific values.
|
|
86
|
-
|
|
87
|
-
**Supported countries** (ISO 3166-1 alpha-2):
|
|
88
|
-
|
|
89
|
-
| Code | Country | Calling code | Currency | Symbol | Decimals | Timezone | Locale |
|
|
90
|
-
|------|----------|--------------|----------|--------|----------|------------------------|---------|
|
|
91
|
-
| `KE` | Kenya | `+254` | `KES` | `KSh` | 2 | `Africa/Nairobi` | `en-KE` |
|
|
92
|
-
| `UG` | Uganda | `+256` | `UGX` | `USh` | 0 | `Africa/Kampala` | `en-UG` |
|
|
93
|
-
| `TZ` | Tanzania | `+255` | `TZS` | `TSh` | 0 | `Africa/Dar_es_Salaam` | `en-TZ` |
|
|
94
|
-
|
|
95
|
-
**Supported currencies** (ISO 4217): `KES`, `UGX`, `TZS`.
|
|
96
|
-
|
|
97
|
-
### Importing
|
|
98
|
-
|
|
99
|
-
Everything is re-exported from the package root:
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
import {
|
|
103
|
-
CountryCode,
|
|
104
|
-
CurrencyCode,
|
|
105
|
-
CallingCode,
|
|
106
|
-
SupportedCountryConfig,
|
|
107
|
-
SupportedCurrencyConfig,
|
|
108
|
-
SUPPORTED_COUNTRIES, // readonly SupportedCountryConfig[]
|
|
109
|
-
SUPPORTED_COUNTRIES_BY_CODE, // Record<CountryCode, SupportedCountryConfig>
|
|
110
|
-
SUPPORTED_CURRENCIES,
|
|
111
|
-
SUPPORTED_CURRENCIES_BY_CODE,
|
|
112
|
-
DEFAULT_COUNTRY_CODE, // CountryCode.KE
|
|
113
|
-
DEFAULT_CURRENCY_CODE, // CurrencyCode.KES
|
|
114
|
-
CountryScopeDto, // optional { countryCode?, currencyCode? } request scope
|
|
115
|
-
getSupportedCountryConfig,
|
|
116
|
-
getSupportedCurrencyConfig,
|
|
117
|
-
getCurrencyForCountry,
|
|
118
|
-
getCallingCodeForCountry,
|
|
119
|
-
getCountryByCallingCode,
|
|
120
|
-
isSupportedCountryCode,
|
|
121
|
-
isSupportedCurrencyCode,
|
|
122
|
-
} from 'grm-shared-library';
|
|
123
|
-
|
|
124
|
-
getCurrencyForCountry(CountryCode.UG); // CurrencyCode.UGX
|
|
125
|
-
getCallingCodeForCountry(CountryCode.TZ); // '+255'
|
|
126
|
-
isSupportedCountryCode('US'); // false
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Backward compatibility
|
|
130
|
-
|
|
131
|
-
**Kenya (`KE` / `KES`) is the default market.** Wherever a country or currency is
|
|
132
|
-
not explicit, fall back to `DEFAULT_COUNTRY_CODE` / `DEFAULT_CURRENCY_CODE` /
|
|
133
|
-
`DEFAULT_TIMEZONE` / `DEFAULT_LOCALE`. Existing Kenyan users, packages, posts,
|
|
134
|
-
incidents, OTPs, notifications and billing flows are unaffected.
|
|
135
|
-
|
|
136
|
-
### Scope
|
|
137
|
-
|
|
138
|
-
This package provides the country/currency **primitives only**. Country
|
|
139
|
-
detection (GPS / IP / phone-number inference) and country-aware business logic
|
|
140
|
-
(phone login, newsfeed filtering, multi-currency billing, SMS routing, map
|
|
141
|
-
filtering) are delivered in later phases and should build on top of these types.
|
|
142
|
-
|
|
143
|
-
### Tests
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
npm test # compiles src/modules/country and runs the country/currency suite
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Organization notification settings
|
|
150
|
-
|
|
151
|
-
Clarion is multi-tenant, so what a customer sees in a notification belongs to the
|
|
152
|
-
**organization** that serves them. `modules/notification` carries the contract for
|
|
153
|
-
that, shared by clarion-notification (owner), the gateway (proxy) and
|
|
154
|
-
clarion-authentication (emitter):
|
|
155
|
-
|
|
156
|
-
- **`organizationId` is the key.** The four auth event payloads (`CreateOTPPayloadDto`,
|
|
157
|
-
`CreateUserRegisteredPayloadDto`, `PasswordResetPayloadDto`,
|
|
158
|
-
`AccountDeletionRequestPayloadDto`) gained an optional `organizationId`, and
|
|
159
|
-
`EmailSendRequest` / `SmsSendRequest` carry one. `appId` stays on every payload, but
|
|
160
|
-
only as the fallback for an emitter that has not been upgraded. The notification
|
|
161
|
-
service validates with `whitelist: true`, so it must be on a version that declares
|
|
162
|
-
the field or the field is stripped before anything reads it.
|
|
163
|
-
- **Branding resolves field by field** - `NotificationBrandingSource`: the
|
|
164
|
-
organization's profile, then its record in clarion-organization, then the legacy
|
|
165
|
-
per-app defaults, then the platform. `UpsertOrganizationNotificationBrandingDto`
|
|
166
|
-
turns `""` into `null`, and null means *inherit again*, not *blank*.
|
|
167
|
-
- **Email senders are verified per provider** (`EmailSenderProviderVerification`). A
|
|
168
|
-
provider only sends from an address it has `VERIFIED`; otherwise that provider uses
|
|
169
|
-
the platform sender and keeps the organization's display name and reply-to.
|
|
170
|
-
- **SMS senders are requested, then approved** (`NotificationSenderStatus`). Only
|
|
171
|
-
`ACTIVE` senders are used; everything else falls back to the country-aware default.
|
|
172
|
-
`validateSmsSenderId(type, value)` is the one definition of a valid sender.
|
|
173
|
-
- **Providers describe themselves** (`SmsProviderDescriptor`), like tracking providers
|
|
174
|
-
do. `SmsProviderName` only reserves a code; the catalogue says what is usable.
|
|
175
|
-
- Every send result records `sender` and `senderSource`
|
|
176
|
-
(`ORGANIZATION` | `PLATFORM_DEFAULT`), so "why was this Upesy-branded?" is a query.
|
|
177
|
-
|
|
178
|
-
### clarion-billing → clarion-notification (HTTP)
|
|
179
|
-
|
|
180
|
-
`BillingEmailNotificationDto`, `BillingSmsNotificationDto`, `BillingPushNotificationDto`
|
|
181
|
-
and their result types are the contract for `POST /billing/notifications/email | sms |
|
|
182
|
-
push` — the synchronous path clarion-billing uses when it wants to know a message left.
|
|
183
|
-
clarion-billing's `NotificationClientService` types its requests from these; do not
|
|
184
|
-
redeclare the fields there.
|
|
185
|
-
|
|
186
|
-
- `BillingEmailTemplate` is a **closed set**. The route renders a template by name on
|
|
187
|
-
another service's say-so, so an open string would let a caller render any template.
|
|
188
|
-
A new value needs copy defaults in clarion-notification (`api/billing`).
|
|
189
|
-
- Attachments go **by reference** (`documents[].signedUrl`), because billing PDFs are
|
|
190
|
-
private and their signed URLs expire within the hour. `signedUrl` must be `https`,
|
|
191
|
-
file names must be bare names, and a subject may not contain a line break.
|
|
192
|
-
- Every list and string is bounded; `billing-notification.spec.ts` pins the limits.
|
|
193
|
-
|
|
194
|
-
Values that end up inside email CSS or headers are validated as such: colours are
|
|
195
|
-
`#RRGGBB` only, logos are `https` only, and a sender display name may not contain a
|
|
196
|
-
line break. `notification-sender.spec.ts` pins each of those.
|
|
197
|
-
|
|
198
|
-
## Response-unit dispatch (`modules/dispatch`)
|
|
199
|
-
|
|
200
|
-
The contracts of response-unit recommendation and dispatch (design: clarion-incident
|
|
201
|
-
`docs/RESPONSE_UNIT_DISPATCH.md`).
|
|
202
|
-
|
|
203
|
-
- **Capabilities** (`modules/response-unit`): `ResponseCapability` and
|
|
204
|
-
`effectiveResponseCapabilities` - configured on the unit, else implied by its type (an
|
|
205
|
-
ambulance is MEDICAL_RESPONSE). `resolveResponseUnitAvailability` reads the manual status.
|
|
206
|
-
- **Recommendation**: `ResponseUnitRecommendation` / `ResponseUnitCandidate` - facts and
|
|
207
|
-
reasons, never scores; `matchResponseCapabilities`, `resolveResponseCapabilityRequirement`
|
|
208
|
-
(type's list, else category's), `resolveIncidentDispatchBlockers`.
|
|
209
|
-
- **Dispatch**: `IncidentResponseUnitDispatch`, `DispatchResponseUnitDto`,
|
|
210
|
-
`UpdateResponseUnitDispatchStatusDto`, the state machine (`RESPONSE_UNIT_DISPATCH_TRANSITIONS`,
|
|
211
|
-
`ACTIVE_RESPONSE_UNIT_DISPATCH_STATUSES` - which MUST equal the statuses in clarion-incident's
|
|
212
|
-
`UQ_ru_dispatch_active_unit` index), `ResponseUnitDispatchErrorCode`.
|
|
213
|
-
- **Cross-service reads**: `ResponseUnitDispatchProfile` (clarion-organization's catalogue),
|
|
214
|
-
`ResponseUnitNearbyResult` (clarion-response-tracking's GEO search).
|
|
215
|
-
- **Events**: `RESPONSE_UNIT_DISPATCH_KAFKA_TOPICS` - created / status-changed (identifiers
|
|
216
|
-
only), notification-requested (the one message with a phone number) and notification-result;
|
|
217
|
-
class DTOs for the two notification events so consumers can validate with `whitelist: true`.
|
|
218
|
-
- **Permissions**: the `Response Unit Dispatch` module - `read|create|update:response-unit-dispatch`
|
|
219
|
-
(never `update:incident`, which every app user holds).
|
|
220
|
-
- **SMS**: the `RESPONSE_UNIT_DISPATCH` template.
|
|
221
|
-
|
|
222
|
-
## Roles and permissions
|
|
223
|
-
|
|
224
|
-
This package is the authority on both. clarion-authentication seeds them into its
|
|
225
|
-
database on boot; every other service reads them from here.
|
|
226
|
-
|
|
227
|
-
| File | Holds |
|
|
228
|
-
|---|---|
|
|
229
|
-
| `role/enums/role.enum.ts` | `Roles` - the role names. |
|
|
230
|
-
| `role/constants/roles-by-scope.ts` | `ORGANIZATION_ROLES` / `CONTROL_CENTRE_ROLES` / `MOBILE_ROLES` - each role's **scope**. |
|
|
231
|
-
| `role/data/roles.data.ts` | `ROLES_DATA` - the permissions each role is seeded with. |
|
|
232
|
-
| `permission/enums/permission-actions.enum.ts` | `PermissionActions` - every action, as `verb:resource`. |
|
|
233
|
-
| `permission/data/permissions.data.ts` | `PERMISSIONS_DATA` - the catalogue role management shows, grouped by `PermissionsModule`. |
|
|
234
|
-
|
|
235
|
-
**WHAT a role may do and WHERE it may do it are separate.** Permissions answer the
|
|
236
|
-
first and are checked by the gateway's `PermissionGuard` (ANY-of). Scope answers the
|
|
237
|
-
second and comes only from which list in `roles-by-scope.ts` the role is in: the
|
|
238
|
-
gateway (`AccessScopeService`), clarion-authentication (`AuthorizationService`) and
|
|
239
|
-
clarion-socket (tracking rooms) all read those lists, and downstream services confine
|
|
240
|
-
a caller to the scope the gateway built. A role in no list gets no scope at all and
|
|
241
|
-
every tenant-scoped call it makes fails closed.
|
|
242
|
-
|
|
243
|
-
That is how one job exists at two scopes. `Organization:Response-Units-Admin` and
|
|
244
|
-
`Control-Centre:Response-Units-Admin` are seeded from the **same** permission list
|
|
245
|
-
(`RESPONSE_UNITS_ADMIN_PERMISSIONS`) - all of the `Response Unit` and `Tracking`
|
|
246
|
-
modules, plus `read:control-centre` and `read:organization`, which their own screens
|
|
247
|
-
call - and differ only in the list they are in.
|
|
248
|
-
|
|
249
|
-
### The Tracking permission module
|
|
250
|
-
|
|
251
|
-
clarion-response-tracking's routes used to be guarded by the `Response Unit` actions.
|
|
252
|
-
Every mobile user holds `read:response-unit`, so every app user could read their
|
|
253
|
-
organization's tracking provider connections, and nobody could be given tracking
|
|
254
|
-
without being given the response units. They now have their own:
|
|
255
|
-
|
|
256
|
-
| Action | Covers |
|
|
257
|
-
|---|---|
|
|
258
|
-
| `create` / `read` / `update` / `delete:tracking-provider-connection` | The link to a tracking platform (Telox, Wialon). `update` also covers test, enable, disable, reconnect. |
|
|
259
|
-
| `create` / `read` / `update` / `delete:tracking-device-binding` | Which provider unit is which response unit. `read` also covers the unmapped-units worklist; `update`, resolving / reopening an entry on it. |
|
|
260
|
-
| `read:tracking-position` | Where units are now - the incident map and live tracking. |
|
|
261
|
-
| `read:tracking-history` | Where a unit has been. Separate on purpose: a movement history is a record of somebody's working day. |
|
|
262
|
-
| `read:tracking-operations` | The tracking operations dashboard. |
|
|
263
|
-
| `manage:tracking-operations` | Reconciling the tracking service's copy of the response units (the dry run included). |
|
|
264
|
-
|
|
265
|
-
Who holds them: Owner, Admin, `Control-Centre:Admin` and both Response-Units-Admins -
|
|
266
|
-
all twelve (everybody who could administer tracking before the split still can);
|
|
267
|
-
`Organization:User` - the five `read` actions; `Control-Centre:User` and `Responder` -
|
|
268
|
-
position and history; `Mobile-User` - **none**.
|
|
269
|
-
|
|
270
|
-
### The Notification permission module
|
|
271
|
-
|
|
272
|
-
An organization's notification settings (clarion-notification) decide what its
|
|
273
|
-
**customers** see: how emails are branded, the address they come from, and the sender
|
|
274
|
-
ID its SMS arrive under. They have actions of their own rather than reusing
|
|
275
|
-
`update:organization`, because a wrong logo or sender goes out on every OTP and alert.
|
|
276
|
-
|
|
277
|
-
| Action | Covers |
|
|
278
|
-
|---|---|
|
|
279
|
-
| `read:notification-settings` | Reading the settings, the effective (resolved) branding and the SMS provider catalogue. |
|
|
280
|
-
| `manage:notification-settings` | Saving branding; adding, verifying and removing email senders; **requesting** SMS senders; sending a test email. |
|
|
281
|
-
| `approve:notification-sender` | Moving an SMS sender to `ACTIVE` / `SUSPENDED` / `REJECTED`. |
|
|
282
|
-
|
|
283
|
-
Owner and Admin hold the first two. **No role holds `approve:notification-sender`** -
|
|
284
|
-
only Super-Admin (`*`) passes it, and `roles.spec.ts` pins that. Activating a sender
|
|
285
|
-
asserts that it is registered with the provider and the telco; an organization able to
|
|
286
|
-
approve its own could send SMS under any name it typed.
|
|
287
|
-
|
|
288
|
-
### The Billing permission module and Organization:Billing-Admin
|
|
289
|
-
|
|
290
|
-
clarion-billing's routes used to be guarded by the INCIDENT actions (and 45 of the
|
|
291
|
-
gateway's 116 billing routes by nothing but a sign-in). Every app user holds
|
|
292
|
-
`create/read/update:incident` to raise an SOS, so every handset could reach almost
|
|
293
|
-
every billing management route - approving a subscription without payment and
|
|
294
|
-
granting premium trials among them. They now use the Billing module, whose actions
|
|
295
|
-
come in two kinds:
|
|
296
|
-
|
|
297
|
-
| Kind | Actions | Who holds them |
|
|
298
|
-
|---|---|---|
|
|
299
|
-
| **Organization** - an organization's own billing, scoped by clarion-billing | packages, individual and corporate clients, corporate members, beneficiaries, subscriptions (incl. manual approval, cancel, `renew:subscription`), orders, invoices (incl. `mark:invoice-paid`), receipts, payments, `read:settlement`, `read:billing-config` | Owner, Admin and `Organization:Billing-Admin` all 44; `Organization:User` the 13 reads |
|
|
300
|
-
| **Platform** - the platform's own business, or not yet scoped to one organization | billing config writes (revenue share and tax terms), settlement create / send-invoice / mark-paid, settlement batches, `read:billing-operations`, premium trials, `read:entitlement` | **no role** - only Super-Admin (`*`) |
|
|
301
|
-
|
|
302
|
-
`roles.spec.ts` lists the platform actions by hand and fails on any Billing action
|
|
303
|
-
that is in neither list, so a new one has to be placed deliberately.
|
|
304
|
-
|
|
305
|
-
`Organization:Billing-Admin` holds the organization set plus exactly what its screens
|
|
306
|
-
need from other modules: `read:user` (an individual client is a platform user; the
|
|
307
|
-
client pages fetch the name and phone), `read:incident-category-type` (the package
|
|
308
|
-
form lists the incident types a package covers) and `read:organization`. No partner
|
|
309
|
-
permission: billing asks clarion-partner-services service-to-service, and no billing
|
|
310
|
-
screen calls a partner route.
|
|
311
|
-
|
|
312
|
-
`Mobile-User` holds the three billing actions the app uses -
|
|
313
|
-
`initialize:individual-subscription`, `renew:subscription`, `verify:payment`. The
|
|
314
|
-
storefront (`GET /billing/mobile/packages`) and the self-scoped "me" routes need only
|
|
315
|
-
a sign-in; `read:package` is withheld because it would also open the admin's package
|
|
316
|
-
list, which returns every organization's packages to a caller with no organization
|
|
317
|
-
scope. Control-room roles hold no billing action.
|
|
318
|
-
|
|
319
|
-
`BILLING_MANAGER_ROLES` (Owner, Admin, Billing-Admin) and `BILLING_VIEWER_ROLES`
|
|
320
|
-
(+ Organization:User) are for the places that still decide by role NAME -
|
|
321
|
-
clarion-billing's checks on manual approval, marking an invoice paid and renewing on
|
|
322
|
-
a subscriber's behalf, and the admin's billing menu and buttons. The spec asserts each
|
|
323
|
-
manager holds the organization set in full and each viewer every read.
|
|
324
|
-
|
|
325
|
-
### The Newsfeed permission module and Organization:Newsfeed-Admin
|
|
326
|
-
|
|
327
|
-
clarion-newsfeed's admin routes were guarded by the ORGANIZATION actions and its
|
|
328
|
-
member routes by nothing but a sign-in. `read:organization` is held by every app
|
|
329
|
-
user, so any of them could list their organization's drafts; only the Owner held
|
|
330
|
-
`delete:organization`, so an Admin could not delete a post; and clarion-newsfeed let
|
|
331
|
-
any organization-scoped role moderate comments. The module now has two audiences:
|
|
332
|
-
|
|
333
|
-
| Actions | Audience | Held by |
|
|
334
|
-
|---|---|---|
|
|
335
|
-
| `create`/`read`/`update`/`publish`/`archive`/`delete:newsfeed-post`, `moderate:newsfeed-comment` | STAFF running the posts (`read:newsfeed-post` includes drafts and archived posts) | Owner, Admin, Newsfeed-Admin; Organization:User only `read:newsfeed-post` |
|
|
336
|
-
| `read:newsfeed-feed` (the published feed, a post, its comments), `engage:newsfeed-post` (like, comment, share, change one's own comment) | MEMBERS | Mobile-User, Control-Centre Admin/User, Responder (who could read the feed in the app before), plus the staff above (Organization:User reads only) |
|
|
337
|
-
|
|
338
|
-
`Organization:Newsfeed-Admin` holds the module in full and nothing else - its screens
|
|
339
|
-
call no other module. `NEWSFEED_MANAGER_ROLES` / `NEWSFEED_VIEWER_ROLES` are the
|
|
340
|
-
role-NAME lists clarion-newsfeed checks for managing/moderating and for seeing drafts.
|
|
341
|
-
|
|
342
|
-
### Adding a role: every step fails silently on its own
|
|
343
|
-
|
|
344
|
-
1. **Here** - the `Roles` value, a scope list, and a `ROLES_DATA` entry. `roles.spec.ts`
|
|
345
|
-
fails if a scoped role is unseeded, a seeded role is not in the enum, or a role
|
|
346
|
-
holds an action the catalogue does not list.
|
|
347
|
-
|
|
348
|
-
The same spec holds the rule for **actions**: every `PermissionActions` value must
|
|
349
|
-
be in `PERMISSIONS_DATA`, under exactly one module. An action outside the catalogue
|
|
350
|
-
still *works* - the gateway matches on the string - but role management can neither
|
|
351
|
-
show nor assign it, which is how the four `zone` actions went unnoticed for so long
|
|
352
|
-
(they now have a `Zone` module). Role and module names are also pinned to 50
|
|
353
|
-
characters, the width of the columns clarion-authentication stores them in.
|
|
354
|
-
2. **clarion-authentication** - `users.roles` is a Postgres ENUM array, so the label
|
|
355
|
-
needs an `ALTER TYPE "users_roles_enum" ADD VALUE IF NOT EXISTS` migration. Without
|
|
356
|
-
it the role is seeded, offered by the admin, and rejected at the INSERT.
|
|
357
|
-
3. **Every service that reads the role lists** - clarion-api-gateway,
|
|
358
|
-
clarion-authentication, clarion-socket - must INSTALL the new version (the lockfile
|
|
359
|
-
decides, not the caret) and redeploy. The gateway also validates `roles` with
|
|
360
|
-
`@IsEnum(Roles)`.
|
|
361
|
-
4. **clarion-socket** names the organization roles admitted to tracking rooms one by
|
|
362
|
-
one; an organization role that should see the fleet has to be added there.
|
|
363
|
-
5. **clarion-admin** shows menu entries by role NAME (`requiresAnyRole` in
|
|
364
|
-
`sidebar-data.ts`), from `clarion-shared-types`' mirror of `Roles`.
|
|
365
|
-
|
|
366
|
-
Deploy clarion-authentication first and let it reseed. The gateway caches role
|
|
367
|
-
permissions for five minutes, so a gateway that starts REQUIRING a new action should
|
|
368
|
-
go out after that window - or the roles that were just granted it are refused until
|
|
369
|
-
their cache entry expires.
|
|
1
|
+
# grm-shared-library
|
|
2
|
+
|
|
3
|
+
Common code for GRM — shared types, enums, interfaces, DTOs and constants for the
|
|
4
|
+
Clarion / Upesy backend NestJS microservices.
|
|
5
|
+
|
|
6
|
+
## Calendar dates
|
|
7
|
+
|
|
8
|
+
One definition of what a date is, because there used to be four and they had
|
|
9
|
+
drifted apart.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
ISO_CALENDAR_DATE_PATTERN, // the one regex - never write another
|
|
14
|
+
isCalendarDate, // a real YYYY-MM-DD day; rejects 2026-02-30
|
|
15
|
+
toCalendarDate, // Date | string -> YYYY-MM-DD | undefined
|
|
16
|
+
isPlausibleBirthDate, // not future, within 120 years, NO minimum age
|
|
17
|
+
calculateAge, isMinor, // whole years, UTC throughout
|
|
18
|
+
AGE_OF_MAJORITY_YEARS, // 18 - for COPY ONLY, never a validator
|
|
19
|
+
IsCalendarDate, IsBirthDate // class-validator decorators over the above
|
|
20
|
+
} from 'grm-shared-library';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Why it refuses things
|
|
24
|
+
|
|
25
|
+
A calendar date has no time, no zone and no offset: somebody born on 17 April
|
|
26
|
+
1990 was born on that day in Nairobi, in Kampala and in London alike. Every bug
|
|
27
|
+
this module exists to prevent comes from treating it as a moment.
|
|
28
|
+
|
|
29
|
+
The old conversion did `new Date(freeText)` and then `.toISOString()`. Under the
|
|
30
|
+
production pods` own `TZ=Africa/Nairobi`:
|
|
31
|
+
|
|
32
|
+
| input | old result | |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `04/17/1990` | `1990-04-16` | a day early |
|
|
35
|
+
| `new Date(1990,3,17)` | `1990-04-16` | a day early - and this is what a date picker hands you |
|
|
36
|
+
| `17/04/1990` | `undefined` | the East African spelling, refused |
|
|
37
|
+
| `1990-04-17` | `1990-04-17` | correct |
|
|
38
|
+
|
|
39
|
+
`toCalendarDate` now accepts exactly three things and refuses everything else:
|
|
40
|
+
|
|
41
|
+
1. `YYYY-MM-DD`
|
|
42
|
+
2. a full ISO-8601 instant in **UTC** (`1990-04-17T00:00:00.000Z`)
|
|
43
|
+
3. a `Date`, read in UTC - so build one with `Date.UTC`, never `new Date(y,m,d)`
|
|
44
|
+
|
|
45
|
+
An **offset** timestamp is refused too: `1990-04-17T00:00:00+03:00` is 16 April
|
|
46
|
+
in UTC, and only the sender knows which day they meant.
|
|
47
|
+
|
|
48
|
+
Free text is refused rather than guessed at. `04/17/1990` and `17/04/1990` are
|
|
49
|
+
the same eight characters in two orders, this platform serves Kenya, Uganda and
|
|
50
|
+
Tanzania where the second is the common spelling, and there is no reading of
|
|
51
|
+
that string that is safe to infer. A refused date surfaces as a field somebody
|
|
52
|
+
is asked to collect; a drifted one is a permanently wrong birthday at a partner
|
|
53
|
+
that cannot delete a member, on the record a paramedic uses to size a drug dose.
|
|
54
|
+
|
|
55
|
+
### No minimum age
|
|
56
|
+
|
|
57
|
+
`isPlausibleBirthDate` refuses a future date and one beyond a human lifespan,
|
|
58
|
+
and imposes no lower bound. Dependants on a family emergency-cover plan are
|
|
59
|
+
children by design - that is the whole point of registering them with a
|
|
60
|
+
responder - so a minimum-age rule would refuse exactly the people the feature
|
|
61
|
+
exists for. `AGE_OF_MAJORITY_YEARS` is there for wording, not for gating.
|
|
62
|
+
|
|
63
|
+
### A decorator alone guarantees nothing
|
|
64
|
+
|
|
65
|
+
`@IsCalendarDate` / `@IsBirthDate` are real validators, but on this platform
|
|
66
|
+
they are not enforcement on their own: clarion-authentication registers **no**
|
|
67
|
+
global `ValidationPipe`, the spreadsheet importer builds plain objects a pipe
|
|
68
|
+
never sees, and internal service-to-service calls bypass HTTP entirely. Any
|
|
69
|
+
service that OWNS a date must re-check it in the service layer, the way
|
|
70
|
+
`UserService.updateCountry` re-checks `isSupportedCountryCode`.
|
|
71
|
+
|
|
72
|
+
### Postgres round trip
|
|
73
|
+
|
|
74
|
+
A `type: date` column returns a **string** through this stack, not a `Date`:
|
|
75
|
+
`pg` parses OID 1082 into a local-midnight `Date` and TypeORM reads it back with
|
|
76
|
+
local components, so the two cancel out. It is correct, but correct by
|
|
77
|
+
cancellation - never pass a value from a date column into `new Date()`.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Country & Currency (multi-country foundation)
|
|
82
|
+
|
|
83
|
+
Platform-level primitives for country-aware emergency response, newsfeed,
|
|
84
|
+
notifications, billing, maps and admin. This is the shared source of truth so
|
|
85
|
+
services stop hard-coding Kenya-specific values.
|
|
86
|
+
|
|
87
|
+
**Supported countries** (ISO 3166-1 alpha-2):
|
|
88
|
+
|
|
89
|
+
| Code | Country | Calling code | Currency | Symbol | Decimals | Timezone | Locale |
|
|
90
|
+
|------|----------|--------------|----------|--------|----------|------------------------|---------|
|
|
91
|
+
| `KE` | Kenya | `+254` | `KES` | `KSh` | 2 | `Africa/Nairobi` | `en-KE` |
|
|
92
|
+
| `UG` | Uganda | `+256` | `UGX` | `USh` | 0 | `Africa/Kampala` | `en-UG` |
|
|
93
|
+
| `TZ` | Tanzania | `+255` | `TZS` | `TSh` | 0 | `Africa/Dar_es_Salaam` | `en-TZ` |
|
|
94
|
+
|
|
95
|
+
**Supported currencies** (ISO 4217): `KES`, `UGX`, `TZS`.
|
|
96
|
+
|
|
97
|
+
### Importing
|
|
98
|
+
|
|
99
|
+
Everything is re-exported from the package root:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import {
|
|
103
|
+
CountryCode,
|
|
104
|
+
CurrencyCode,
|
|
105
|
+
CallingCode,
|
|
106
|
+
SupportedCountryConfig,
|
|
107
|
+
SupportedCurrencyConfig,
|
|
108
|
+
SUPPORTED_COUNTRIES, // readonly SupportedCountryConfig[]
|
|
109
|
+
SUPPORTED_COUNTRIES_BY_CODE, // Record<CountryCode, SupportedCountryConfig>
|
|
110
|
+
SUPPORTED_CURRENCIES,
|
|
111
|
+
SUPPORTED_CURRENCIES_BY_CODE,
|
|
112
|
+
DEFAULT_COUNTRY_CODE, // CountryCode.KE
|
|
113
|
+
DEFAULT_CURRENCY_CODE, // CurrencyCode.KES
|
|
114
|
+
CountryScopeDto, // optional { countryCode?, currencyCode? } request scope
|
|
115
|
+
getSupportedCountryConfig,
|
|
116
|
+
getSupportedCurrencyConfig,
|
|
117
|
+
getCurrencyForCountry,
|
|
118
|
+
getCallingCodeForCountry,
|
|
119
|
+
getCountryByCallingCode,
|
|
120
|
+
isSupportedCountryCode,
|
|
121
|
+
isSupportedCurrencyCode,
|
|
122
|
+
} from 'grm-shared-library';
|
|
123
|
+
|
|
124
|
+
getCurrencyForCountry(CountryCode.UG); // CurrencyCode.UGX
|
|
125
|
+
getCallingCodeForCountry(CountryCode.TZ); // '+255'
|
|
126
|
+
isSupportedCountryCode('US'); // false
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Backward compatibility
|
|
130
|
+
|
|
131
|
+
**Kenya (`KE` / `KES`) is the default market.** Wherever a country or currency is
|
|
132
|
+
not explicit, fall back to `DEFAULT_COUNTRY_CODE` / `DEFAULT_CURRENCY_CODE` /
|
|
133
|
+
`DEFAULT_TIMEZONE` / `DEFAULT_LOCALE`. Existing Kenyan users, packages, posts,
|
|
134
|
+
incidents, OTPs, notifications and billing flows are unaffected.
|
|
135
|
+
|
|
136
|
+
### Scope
|
|
137
|
+
|
|
138
|
+
This package provides the country/currency **primitives only**. Country
|
|
139
|
+
detection (GPS / IP / phone-number inference) and country-aware business logic
|
|
140
|
+
(phone login, newsfeed filtering, multi-currency billing, SMS routing, map
|
|
141
|
+
filtering) are delivered in later phases and should build on top of these types.
|
|
142
|
+
|
|
143
|
+
### Tests
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm test # compiles src/modules/country and runs the country/currency suite
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Organization notification settings
|
|
150
|
+
|
|
151
|
+
Clarion is multi-tenant, so what a customer sees in a notification belongs to the
|
|
152
|
+
**organization** that serves them. `modules/notification` carries the contract for
|
|
153
|
+
that, shared by clarion-notification (owner), the gateway (proxy) and
|
|
154
|
+
clarion-authentication (emitter):
|
|
155
|
+
|
|
156
|
+
- **`organizationId` is the key.** The four auth event payloads (`CreateOTPPayloadDto`,
|
|
157
|
+
`CreateUserRegisteredPayloadDto`, `PasswordResetPayloadDto`,
|
|
158
|
+
`AccountDeletionRequestPayloadDto`) gained an optional `organizationId`, and
|
|
159
|
+
`EmailSendRequest` / `SmsSendRequest` carry one. `appId` stays on every payload, but
|
|
160
|
+
only as the fallback for an emitter that has not been upgraded. The notification
|
|
161
|
+
service validates with `whitelist: true`, so it must be on a version that declares
|
|
162
|
+
the field or the field is stripped before anything reads it.
|
|
163
|
+
- **Branding resolves field by field** - `NotificationBrandingSource`: the
|
|
164
|
+
organization's profile, then its record in clarion-organization, then the legacy
|
|
165
|
+
per-app defaults, then the platform. `UpsertOrganizationNotificationBrandingDto`
|
|
166
|
+
turns `""` into `null`, and null means *inherit again*, not *blank*.
|
|
167
|
+
- **Email senders are verified per provider** (`EmailSenderProviderVerification`). A
|
|
168
|
+
provider only sends from an address it has `VERIFIED`; otherwise that provider uses
|
|
169
|
+
the platform sender and keeps the organization's display name and reply-to.
|
|
170
|
+
- **SMS senders are requested, then approved** (`NotificationSenderStatus`). Only
|
|
171
|
+
`ACTIVE` senders are used; everything else falls back to the country-aware default.
|
|
172
|
+
`validateSmsSenderId(type, value)` is the one definition of a valid sender.
|
|
173
|
+
- **Providers describe themselves** (`SmsProviderDescriptor`), like tracking providers
|
|
174
|
+
do. `SmsProviderName` only reserves a code; the catalogue says what is usable.
|
|
175
|
+
- Every send result records `sender` and `senderSource`
|
|
176
|
+
(`ORGANIZATION` | `PLATFORM_DEFAULT`), so "why was this Upesy-branded?" is a query.
|
|
177
|
+
|
|
178
|
+
### clarion-billing → clarion-notification (HTTP)
|
|
179
|
+
|
|
180
|
+
`BillingEmailNotificationDto`, `BillingSmsNotificationDto`, `BillingPushNotificationDto`
|
|
181
|
+
and their result types are the contract for `POST /billing/notifications/email | sms |
|
|
182
|
+
push` — the synchronous path clarion-billing uses when it wants to know a message left.
|
|
183
|
+
clarion-billing's `NotificationClientService` types its requests from these; do not
|
|
184
|
+
redeclare the fields there.
|
|
185
|
+
|
|
186
|
+
- `BillingEmailTemplate` is a **closed set**. The route renders a template by name on
|
|
187
|
+
another service's say-so, so an open string would let a caller render any template.
|
|
188
|
+
A new value needs copy defaults in clarion-notification (`api/billing`).
|
|
189
|
+
- Attachments go **by reference** (`documents[].signedUrl`), because billing PDFs are
|
|
190
|
+
private and their signed URLs expire within the hour. `signedUrl` must be `https`,
|
|
191
|
+
file names must be bare names, and a subject may not contain a line break.
|
|
192
|
+
- Every list and string is bounded; `billing-notification.spec.ts` pins the limits.
|
|
193
|
+
|
|
194
|
+
Values that end up inside email CSS or headers are validated as such: colours are
|
|
195
|
+
`#RRGGBB` only, logos are `https` only, and a sender display name may not contain a
|
|
196
|
+
line break. `notification-sender.spec.ts` pins each of those.
|
|
197
|
+
|
|
198
|
+
## Response-unit dispatch (`modules/dispatch`)
|
|
199
|
+
|
|
200
|
+
The contracts of response-unit recommendation and dispatch (design: clarion-incident
|
|
201
|
+
`docs/RESPONSE_UNIT_DISPATCH.md`).
|
|
202
|
+
|
|
203
|
+
- **Capabilities** (`modules/response-unit`): `ResponseCapability` and
|
|
204
|
+
`effectiveResponseCapabilities` - configured on the unit, else implied by its type (an
|
|
205
|
+
ambulance is MEDICAL_RESPONSE). `resolveResponseUnitAvailability` reads the manual status.
|
|
206
|
+
- **Recommendation**: `ResponseUnitRecommendation` / `ResponseUnitCandidate` - facts and
|
|
207
|
+
reasons, never scores; `matchResponseCapabilities`, `resolveResponseCapabilityRequirement`
|
|
208
|
+
(type's list, else category's), `resolveIncidentDispatchBlockers`.
|
|
209
|
+
- **Dispatch**: `IncidentResponseUnitDispatch`, `DispatchResponseUnitDto`,
|
|
210
|
+
`UpdateResponseUnitDispatchStatusDto`, the state machine (`RESPONSE_UNIT_DISPATCH_TRANSITIONS`,
|
|
211
|
+
`ACTIVE_RESPONSE_UNIT_DISPATCH_STATUSES` - which MUST equal the statuses in clarion-incident's
|
|
212
|
+
`UQ_ru_dispatch_active_unit` index), `ResponseUnitDispatchErrorCode`.
|
|
213
|
+
- **Cross-service reads**: `ResponseUnitDispatchProfile` (clarion-organization's catalogue),
|
|
214
|
+
`ResponseUnitNearbyResult` (clarion-response-tracking's GEO search).
|
|
215
|
+
- **Events**: `RESPONSE_UNIT_DISPATCH_KAFKA_TOPICS` - created / status-changed (identifiers
|
|
216
|
+
only), notification-requested (the one message with a phone number) and notification-result;
|
|
217
|
+
class DTOs for the two notification events so consumers can validate with `whitelist: true`.
|
|
218
|
+
- **Permissions**: the `Response Unit Dispatch` module - `read|create|update:response-unit-dispatch`
|
|
219
|
+
(never `update:incident`, which every app user holds).
|
|
220
|
+
- **SMS**: the `RESPONSE_UNIT_DISPATCH` template.
|
|
221
|
+
|
|
222
|
+
## Roles and permissions
|
|
223
|
+
|
|
224
|
+
This package is the authority on both. clarion-authentication seeds them into its
|
|
225
|
+
database on boot; every other service reads them from here.
|
|
226
|
+
|
|
227
|
+
| File | Holds |
|
|
228
|
+
|---|---|
|
|
229
|
+
| `role/enums/role.enum.ts` | `Roles` - the role names. |
|
|
230
|
+
| `role/constants/roles-by-scope.ts` | `ORGANIZATION_ROLES` / `CONTROL_CENTRE_ROLES` / `MOBILE_ROLES` - each role's **scope**. |
|
|
231
|
+
| `role/data/roles.data.ts` | `ROLES_DATA` - the permissions each role is seeded with. |
|
|
232
|
+
| `permission/enums/permission-actions.enum.ts` | `PermissionActions` - every action, as `verb:resource`. |
|
|
233
|
+
| `permission/data/permissions.data.ts` | `PERMISSIONS_DATA` - the catalogue role management shows, grouped by `PermissionsModule`. |
|
|
234
|
+
|
|
235
|
+
**WHAT a role may do and WHERE it may do it are separate.** Permissions answer the
|
|
236
|
+
first and are checked by the gateway's `PermissionGuard` (ANY-of). Scope answers the
|
|
237
|
+
second and comes only from which list in `roles-by-scope.ts` the role is in: the
|
|
238
|
+
gateway (`AccessScopeService`), clarion-authentication (`AuthorizationService`) and
|
|
239
|
+
clarion-socket (tracking rooms) all read those lists, and downstream services confine
|
|
240
|
+
a caller to the scope the gateway built. A role in no list gets no scope at all and
|
|
241
|
+
every tenant-scoped call it makes fails closed.
|
|
242
|
+
|
|
243
|
+
That is how one job exists at two scopes. `Organization:Response-Units-Admin` and
|
|
244
|
+
`Control-Centre:Response-Units-Admin` are seeded from the **same** permission list
|
|
245
|
+
(`RESPONSE_UNITS_ADMIN_PERMISSIONS`) - all of the `Response Unit` and `Tracking`
|
|
246
|
+
modules, plus `read:control-centre` and `read:organization`, which their own screens
|
|
247
|
+
call - and differ only in the list they are in.
|
|
248
|
+
|
|
249
|
+
### The Tracking permission module
|
|
250
|
+
|
|
251
|
+
clarion-response-tracking's routes used to be guarded by the `Response Unit` actions.
|
|
252
|
+
Every mobile user holds `read:response-unit`, so every app user could read their
|
|
253
|
+
organization's tracking provider connections, and nobody could be given tracking
|
|
254
|
+
without being given the response units. They now have their own:
|
|
255
|
+
|
|
256
|
+
| Action | Covers |
|
|
257
|
+
|---|---|
|
|
258
|
+
| `create` / `read` / `update` / `delete:tracking-provider-connection` | The link to a tracking platform (Telox, Wialon). `update` also covers test, enable, disable, reconnect. |
|
|
259
|
+
| `create` / `read` / `update` / `delete:tracking-device-binding` | Which provider unit is which response unit. `read` also covers the unmapped-units worklist; `update`, resolving / reopening an entry on it. |
|
|
260
|
+
| `read:tracking-position` | Where units are now - the incident map and live tracking. |
|
|
261
|
+
| `read:tracking-history` | Where a unit has been. Separate on purpose: a movement history is a record of somebody's working day. |
|
|
262
|
+
| `read:tracking-operations` | The tracking operations dashboard. |
|
|
263
|
+
| `manage:tracking-operations` | Reconciling the tracking service's copy of the response units (the dry run included). |
|
|
264
|
+
|
|
265
|
+
Who holds them: Owner, Admin, `Control-Centre:Admin` and both Response-Units-Admins -
|
|
266
|
+
all twelve (everybody who could administer tracking before the split still can);
|
|
267
|
+
`Organization:User` - the five `read` actions; `Control-Centre:User` and `Responder` -
|
|
268
|
+
position and history; `Mobile-User` - **none**.
|
|
269
|
+
|
|
270
|
+
### The Notification permission module
|
|
271
|
+
|
|
272
|
+
An organization's notification settings (clarion-notification) decide what its
|
|
273
|
+
**customers** see: how emails are branded, the address they come from, and the sender
|
|
274
|
+
ID its SMS arrive under. They have actions of their own rather than reusing
|
|
275
|
+
`update:organization`, because a wrong logo or sender goes out on every OTP and alert.
|
|
276
|
+
|
|
277
|
+
| Action | Covers |
|
|
278
|
+
|---|---|
|
|
279
|
+
| `read:notification-settings` | Reading the settings, the effective (resolved) branding and the SMS provider catalogue. |
|
|
280
|
+
| `manage:notification-settings` | Saving branding; adding, verifying and removing email senders; **requesting** SMS senders; sending a test email. |
|
|
281
|
+
| `approve:notification-sender` | Moving an SMS sender to `ACTIVE` / `SUSPENDED` / `REJECTED`. |
|
|
282
|
+
|
|
283
|
+
Owner and Admin hold the first two. **No role holds `approve:notification-sender`** -
|
|
284
|
+
only Super-Admin (`*`) passes it, and `roles.spec.ts` pins that. Activating a sender
|
|
285
|
+
asserts that it is registered with the provider and the telco; an organization able to
|
|
286
|
+
approve its own could send SMS under any name it typed.
|
|
287
|
+
|
|
288
|
+
### The Billing permission module and Organization:Billing-Admin
|
|
289
|
+
|
|
290
|
+
clarion-billing's routes used to be guarded by the INCIDENT actions (and 45 of the
|
|
291
|
+
gateway's 116 billing routes by nothing but a sign-in). Every app user holds
|
|
292
|
+
`create/read/update:incident` to raise an SOS, so every handset could reach almost
|
|
293
|
+
every billing management route - approving a subscription without payment and
|
|
294
|
+
granting premium trials among them. They now use the Billing module, whose actions
|
|
295
|
+
come in two kinds:
|
|
296
|
+
|
|
297
|
+
| Kind | Actions | Who holds them |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| **Organization** - an organization's own billing, scoped by clarion-billing | packages, individual and corporate clients, corporate members, beneficiaries, subscriptions (incl. manual approval, cancel, `renew:subscription`), orders, invoices (incl. `mark:invoice-paid`), receipts, payments, `read:settlement`, `read:billing-config` | Owner, Admin and `Organization:Billing-Admin` all 44; `Organization:User` the 13 reads |
|
|
300
|
+
| **Platform** - the platform's own business, or not yet scoped to one organization | billing config writes (revenue share and tax terms), settlement create / send-invoice / mark-paid, settlement batches, `read:billing-operations`, premium trials, `read:entitlement` | **no role** - only Super-Admin (`*`) |
|
|
301
|
+
|
|
302
|
+
`roles.spec.ts` lists the platform actions by hand and fails on any Billing action
|
|
303
|
+
that is in neither list, so a new one has to be placed deliberately.
|
|
304
|
+
|
|
305
|
+
`Organization:Billing-Admin` holds the organization set plus exactly what its screens
|
|
306
|
+
need from other modules: `read:user` (an individual client is a platform user; the
|
|
307
|
+
client pages fetch the name and phone), `read:incident-category-type` (the package
|
|
308
|
+
form lists the incident types a package covers) and `read:organization`. No partner
|
|
309
|
+
permission: billing asks clarion-partner-services service-to-service, and no billing
|
|
310
|
+
screen calls a partner route.
|
|
311
|
+
|
|
312
|
+
`Mobile-User` holds the three billing actions the app uses -
|
|
313
|
+
`initialize:individual-subscription`, `renew:subscription`, `verify:payment`. The
|
|
314
|
+
storefront (`GET /billing/mobile/packages`) and the self-scoped "me" routes need only
|
|
315
|
+
a sign-in; `read:package` is withheld because it would also open the admin's package
|
|
316
|
+
list, which returns every organization's packages to a caller with no organization
|
|
317
|
+
scope. Control-room roles hold no billing action.
|
|
318
|
+
|
|
319
|
+
`BILLING_MANAGER_ROLES` (Owner, Admin, Billing-Admin) and `BILLING_VIEWER_ROLES`
|
|
320
|
+
(+ Organization:User) are for the places that still decide by role NAME -
|
|
321
|
+
clarion-billing's checks on manual approval, marking an invoice paid and renewing on
|
|
322
|
+
a subscriber's behalf, and the admin's billing menu and buttons. The spec asserts each
|
|
323
|
+
manager holds the organization set in full and each viewer every read.
|
|
324
|
+
|
|
325
|
+
### The Newsfeed permission module and Organization:Newsfeed-Admin
|
|
326
|
+
|
|
327
|
+
clarion-newsfeed's admin routes were guarded by the ORGANIZATION actions and its
|
|
328
|
+
member routes by nothing but a sign-in. `read:organization` is held by every app
|
|
329
|
+
user, so any of them could list their organization's drafts; only the Owner held
|
|
330
|
+
`delete:organization`, so an Admin could not delete a post; and clarion-newsfeed let
|
|
331
|
+
any organization-scoped role moderate comments. The module now has two audiences:
|
|
332
|
+
|
|
333
|
+
| Actions | Audience | Held by |
|
|
334
|
+
|---|---|---|
|
|
335
|
+
| `create`/`read`/`update`/`publish`/`archive`/`delete:newsfeed-post`, `moderate:newsfeed-comment` | STAFF running the posts (`read:newsfeed-post` includes drafts and archived posts) | Owner, Admin, Newsfeed-Admin; Organization:User only `read:newsfeed-post` |
|
|
336
|
+
| `read:newsfeed-feed` (the published feed, a post, its comments), `engage:newsfeed-post` (like, comment, share, change one's own comment) | MEMBERS | Mobile-User, Control-Centre Admin/User, Responder (who could read the feed in the app before), plus the staff above (Organization:User reads only) |
|
|
337
|
+
|
|
338
|
+
`Organization:Newsfeed-Admin` holds the module in full and nothing else - its screens
|
|
339
|
+
call no other module. `NEWSFEED_MANAGER_ROLES` / `NEWSFEED_VIEWER_ROLES` are the
|
|
340
|
+
role-NAME lists clarion-newsfeed checks for managing/moderating and for seeing drafts.
|
|
341
|
+
|
|
342
|
+
### Adding a role: every step fails silently on its own
|
|
343
|
+
|
|
344
|
+
1. **Here** - the `Roles` value, a scope list, and a `ROLES_DATA` entry. `roles.spec.ts`
|
|
345
|
+
fails if a scoped role is unseeded, a seeded role is not in the enum, or a role
|
|
346
|
+
holds an action the catalogue does not list.
|
|
347
|
+
|
|
348
|
+
The same spec holds the rule for **actions**: every `PermissionActions` value must
|
|
349
|
+
be in `PERMISSIONS_DATA`, under exactly one module. An action outside the catalogue
|
|
350
|
+
still *works* - the gateway matches on the string - but role management can neither
|
|
351
|
+
show nor assign it, which is how the four `zone` actions went unnoticed for so long
|
|
352
|
+
(they now have a `Zone` module). Role and module names are also pinned to 50
|
|
353
|
+
characters, the width of the columns clarion-authentication stores them in.
|
|
354
|
+
2. **clarion-authentication** - `users.roles` is a Postgres ENUM array, so the label
|
|
355
|
+
needs an `ALTER TYPE "users_roles_enum" ADD VALUE IF NOT EXISTS` migration. Without
|
|
356
|
+
it the role is seeded, offered by the admin, and rejected at the INSERT.
|
|
357
|
+
3. **Every service that reads the role lists** - clarion-api-gateway,
|
|
358
|
+
clarion-authentication, clarion-socket - must INSTALL the new version (the lockfile
|
|
359
|
+
decides, not the caret) and redeploy. The gateway also validates `roles` with
|
|
360
|
+
`@IsEnum(Roles)`.
|
|
361
|
+
4. **clarion-socket** names the organization roles admitted to tracking rooms one by
|
|
362
|
+
one; an organization role that should see the fleet has to be added there.
|
|
363
|
+
5. **clarion-admin** shows menu entries by role NAME (`requiresAnyRole` in
|
|
364
|
+
`sidebar-data.ts`), from `clarion-shared-types`' mirror of `Roles`.
|
|
365
|
+
|
|
366
|
+
Deploy clarion-authentication first and let it reseed. The gateway caches role
|
|
367
|
+
permissions for five minutes, so a gateway that starts REQUIRING a new action should
|
|
368
|
+
go out after that window - or the roles that were just granted it are refused until
|
|
369
|
+
their cache entry expires.
|