@oneuptime/common 12.0.29 → 12.0.30

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 (189) hide show
  1. package/Models/DatabaseModels/Index.ts +11 -0
  2. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
  3. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
  4. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
  5. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
  6. package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
  7. package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
  8. package/Server/API/OnCallCalendarAPI.ts +2194 -0
  9. package/Server/EnvironmentConfig.ts +138 -0
  10. package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  13. package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
  14. package/Server/Services/Index.ts +12 -0
  15. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
  16. package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
  17. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
  18. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
  19. package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
  20. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
  21. package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
  22. package/Server/Services/TeamMemberService.ts +489 -1
  23. package/Server/Services/TeamService.ts +50 -0
  24. package/Server/Services/UserNotificationSettingService.ts +44 -1
  25. package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
  26. package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
  27. package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
  28. package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
  29. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
  30. package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
  31. package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
  32. package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
  33. package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
  34. package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
  35. package/Server/Utils/Response.ts +134 -0
  36. package/Server/Utils/StartServer.ts +10 -0
  37. package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
  38. package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
  39. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
  40. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
  41. package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
  42. package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
  43. package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
  44. package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
  45. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
  46. package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
  47. package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
  48. package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
  49. package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
  50. package/Tests/Server/API/Helpers.ts +8 -0
  51. package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
  52. package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
  53. package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
  54. package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
  55. package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
  56. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
  57. package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
  58. package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
  59. package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
  60. package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
  61. package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
  62. package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
  63. package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
  64. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
  65. package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
  66. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
  67. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
  68. package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
  69. package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
  70. package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
  71. package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
  72. package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
  73. package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
  74. package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
  75. package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
  76. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
  77. package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
  78. package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
  79. package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
  80. package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
  81. package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
  82. package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
  83. package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
  84. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
  85. package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
  86. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  87. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  88. package/Types/Calendar/ICalendar.ts +442 -0
  89. package/Types/Email/EmailTemplateType.ts +2 -0
  90. package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
  91. package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
  92. package/Types/OnCallDutyPolicy/Layer.ts +190 -35
  93. package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
  94. package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
  95. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
  96. package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
  97. package/build/dist/Models/DatabaseModels/Index.js +11 -0
  98. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  99. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
  100. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  101. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
  102. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
  103. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
  104. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
  105. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
  106. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
  107. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
  108. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
  109. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
  110. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
  111. package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
  112. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
  113. package/build/dist/Server/EnvironmentConfig.js +102 -0
  114. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  115. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
  116. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  121. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
  122. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
  123. package/build/dist/Server/Services/Index.js +12 -0
  124. package/build/dist/Server/Services/Index.js.map +1 -1
  125. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
  126. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  127. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
  128. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
  129. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
  130. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  131. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
  132. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  133. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
  134. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  135. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
  136. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  137. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
  138. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
  139. package/build/dist/Server/Services/TeamMemberService.js +396 -1
  140. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  141. package/build/dist/Server/Services/TeamService.js +43 -0
  142. package/build/dist/Server/Services/TeamService.js.map +1 -1
  143. package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
  144. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  145. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
  146. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
  147. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
  148. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
  149. package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
  150. package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
  151. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
  152. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
  153. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
  154. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
  155. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
  156. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
  157. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
  158. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
  159. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
  160. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
  161. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
  162. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
  163. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
  164. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
  165. package/build/dist/Server/Utils/Response.js +97 -0
  166. package/build/dist/Server/Utils/Response.js.map +1 -1
  167. package/build/dist/Server/Utils/StartServer.js +9 -1
  168. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  169. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
  170. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  171. package/build/dist/Types/Calendar/ICalendar.js +290 -0
  172. package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
  173. package/build/dist/Types/Email/EmailTemplateType.js +2 -0
  174. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  175. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
  176. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  177. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
  178. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
  179. package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
  180. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  181. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
  182. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
  183. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
  184. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
  185. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
  186. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  187. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
  188. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
  189. package/package.json +1 -1
@@ -0,0 +1,231 @@
1
+ import Project from "./Project";
2
+ import User from "./User";
3
+ import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
4
+ import Route from "../../Types/API/Route";
5
+ import AllowAccessIfSubscriptionIsUnpaid from "../../Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid";
6
+ import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
7
+ import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
8
+ import ColumnType from "../../Types/Database/ColumnType";
9
+ import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
10
+ import CurrentUserCanAccessRecordBy from "../../Types/Database/CurrentUserCanAccessRecordBy";
11
+ import TableColumn from "../../Types/Database/TableColumn";
12
+ import TableColumnType from "../../Types/Database/TableColumnType";
13
+ import TableMetadata from "../../Types/Database/TableMetadata";
14
+ import TenantColumn from "../../Types/Database/TenantColumn";
15
+ import IconProp from "../../Types/Icon/IconProp";
16
+ import ObjectID from "../../Types/ObjectID";
17
+ import Permission from "../../Types/Permission";
18
+ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
19
+
20
+ /*
21
+ * Bounds on the lead time, shared with the dashboard's custom-lead input.
22
+ * Below 15 minutes the reminder races the shift's own hand-off notification;
23
+ * above two weeks the roster is not settled enough to be worth interrupting
24
+ * for.
25
+ */
26
+ export const MIN_MINUTES_BEFORE_SHIFT: number = 15;
27
+ export const MAX_MINUTES_BEFORE_SHIFT: number = 14 * 24 * 60;
28
+
29
+ /*
30
+ * "Remind me N minutes before my on-call shift starts."
31
+ *
32
+ * One row per lead time a user has configured in a project - the settings
33
+ * page's chips (1 week / 1 day / 1 hour / 15 min / custom) are rows here.
34
+ * UNIQUE (projectId, userId, minutesBeforeShift) makes a double-click on a
35
+ * chip a no-op rather than a duplicate reminder. The reminder worker reads
36
+ * these, materializes the user's upcoming shifts, and sends through the
37
+ * user's notification settings for SEND_BEFORE_USER_ON_CALL_SHIFT_STARTS.
38
+ *
39
+ * Shaped like UserNotificationSetting: tenant scoped, owned by the user
40
+ * (@CurrentUserCanAccessRecordBy), every operation open to the owner and to
41
+ * nobody else. The service bounds minutesBeforeShift to 15 min ... 2 weeks.
42
+ */
43
+ @TenantColumn("projectId")
44
+ @AllowAccessIfSubscriptionIsUnpaid()
45
+ @TableAccessControl({
46
+ create: [Permission.CurrentUser],
47
+ read: [Permission.CurrentUser],
48
+ delete: [Permission.CurrentUser],
49
+ update: [Permission.CurrentUser],
50
+ })
51
+ @CrudApiEndpoint(new Route("/user-on-call-shift-reminder"))
52
+ @Entity({
53
+ name: "UserOnCallShiftReminder",
54
+ })
55
+ @TableMetadata({
56
+ tableName: "UserOnCallShiftReminder",
57
+ singularName: "On-Call Shift Reminder",
58
+ pluralName: "On-Call Shift Reminders",
59
+ icon: IconProp.Bell,
60
+ tableDescription:
61
+ "How long before an on-call shift starts a user wants to be reminded.",
62
+ })
63
+ @CurrentUserCanAccessRecordBy("userId")
64
+ @Index(["projectId", "userId", "minutesBeforeShift"], { unique: true })
65
+ export default class UserOnCallShiftReminder extends BaseModel {
66
+ @ColumnAccessControl({
67
+ create: [Permission.CurrentUser],
68
+ read: [Permission.CurrentUser],
69
+ update: [],
70
+ })
71
+ @TableColumn({
72
+ manyToOneRelationColumn: "projectId",
73
+ type: TableColumnType.Entity,
74
+ modelType: Project,
75
+ title: "Project",
76
+ description: "Relation to Project Resource in which this object belongs",
77
+ })
78
+ @ManyToOne(
79
+ () => {
80
+ return Project;
81
+ },
82
+ {
83
+ eager: false,
84
+ nullable: true,
85
+ onDelete: "CASCADE",
86
+ orphanedRowAction: "nullify",
87
+ },
88
+ )
89
+ @JoinColumn({ name: "projectId" })
90
+ public project?: Project = undefined;
91
+
92
+ @ColumnAccessControl({
93
+ create: [Permission.CurrentUser],
94
+ read: [Permission.CurrentUser],
95
+ update: [],
96
+ })
97
+ @Index()
98
+ @TableColumn({
99
+ type: TableColumnType.ObjectID,
100
+ required: true,
101
+ canReadOnRelationQuery: true,
102
+ title: "Project ID",
103
+ description: "ID of your OneUptime Project in which this object belongs",
104
+ example: "5f8b9c0d-e1a2-4b3c-8d5e-6f7a8b9c0d1e",
105
+ })
106
+ @Column({
107
+ type: ColumnType.ObjectID,
108
+ nullable: false,
109
+ transformer: ObjectID.getDatabaseTransformer(),
110
+ })
111
+ public projectId?: ObjectID = undefined;
112
+
113
+ @ColumnAccessControl({
114
+ create: [Permission.CurrentUser],
115
+ read: [Permission.CurrentUser],
116
+ update: [],
117
+ })
118
+ @TableColumn({
119
+ manyToOneRelationColumn: "userId",
120
+ type: TableColumnType.Entity,
121
+ modelType: User,
122
+ title: "User",
123
+ description: "Relation to the User who wants to be reminded",
124
+ })
125
+ @ManyToOne(
126
+ () => {
127
+ return User;
128
+ },
129
+ {
130
+ eager: false,
131
+ nullable: true,
132
+ onDelete: "CASCADE",
133
+ orphanedRowAction: "nullify",
134
+ },
135
+ )
136
+ @JoinColumn({ name: "userId" })
137
+ public user?: User = undefined;
138
+
139
+ @ColumnAccessControl({
140
+ create: [Permission.CurrentUser],
141
+ read: [Permission.CurrentUser],
142
+ update: [],
143
+ })
144
+ @Index()
145
+ @TableColumn({
146
+ type: TableColumnType.ObjectID,
147
+ required: true,
148
+ canReadOnRelationQuery: true,
149
+ title: "User ID",
150
+ description: "ID of the User who wants to be reminded",
151
+ example: "7c9d8e0f-a1b2-4c3d-9e5f-8a7b9c0d1e2f",
152
+ })
153
+ @Column({
154
+ type: ColumnType.ObjectID,
155
+ nullable: false,
156
+ transformer: ObjectID.getDatabaseTransformer(),
157
+ })
158
+ public userId?: ObjectID = undefined;
159
+
160
+ /*
161
+ * Lead time in minutes. 15 ... 20160 (two weeks), enforced by the service:
162
+ * below 15 the reminder races the shift's own hand-off notification, above
163
+ * two weeks the roster is not settled enough to be worth interrupting for.
164
+ */
165
+ @ColumnAccessControl({
166
+ create: [Permission.CurrentUser],
167
+ read: [Permission.CurrentUser],
168
+ update: [Permission.CurrentUser],
169
+ })
170
+ @TableColumn({
171
+ required: true,
172
+ type: TableColumnType.Number,
173
+ canReadOnRelationQuery: true,
174
+ title: "Minutes Before Shift",
175
+ description:
176
+ "How many minutes before the shift starts to send the reminder (15 to 20160).",
177
+ example: 60,
178
+ })
179
+ @Column({
180
+ type: ColumnType.Number,
181
+ nullable: false,
182
+ })
183
+ public minutesBeforeShift?: number = undefined;
184
+
185
+ @ColumnAccessControl({
186
+ create: [],
187
+ read: [],
188
+ update: [],
189
+ })
190
+ @TableColumn({
191
+ manyToOneRelationColumn: "deletedByUserId",
192
+ type: TableColumnType.Entity,
193
+ title: "Deleted by User",
194
+ modelType: User,
195
+ description:
196
+ "Relation to User who deleted this object (if this object was deleted by a User)",
197
+ })
198
+ @ManyToOne(
199
+ () => {
200
+ return User;
201
+ },
202
+ {
203
+ cascade: false,
204
+ eager: false,
205
+ nullable: true,
206
+ onDelete: "SET NULL",
207
+ orphanedRowAction: "nullify",
208
+ },
209
+ )
210
+ @JoinColumn({ name: "deletedByUserId" })
211
+ public deletedByUser?: User = undefined;
212
+
213
+ @ColumnAccessControl({
214
+ create: [],
215
+ read: [],
216
+ update: [],
217
+ })
218
+ @TableColumn({
219
+ type: TableColumnType.ObjectID,
220
+ title: "Deleted by User ID",
221
+ description:
222
+ "User ID who deleted this object (if this object was deleted by a User)",
223
+ example: "7c9d8e0f-a1b2-4c3d-9e5f-8a7b9c0d1e2f",
224
+ })
225
+ @Column({
226
+ type: ColumnType.ObjectID,
227
+ nullable: true,
228
+ transformer: ObjectID.getDatabaseTransformer(),
229
+ })
230
+ public deletedByUserId?: ObjectID = undefined;
231
+ }
@@ -0,0 +1,331 @@
1
+ import OnCallDutyPolicySchedule from "./OnCallDutyPolicySchedule";
2
+ import Project from "./Project";
3
+ import User from "./User";
4
+ import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
5
+ import Route from "../../Types/API/Route";
6
+ import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
7
+ import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
8
+ import ColumnLength from "../../Types/Database/ColumnLength";
9
+ import ColumnType from "../../Types/Database/ColumnType";
10
+ import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
11
+ import TableColumn from "../../Types/Database/TableColumn";
12
+ import TableColumnType from "../../Types/Database/TableColumnType";
13
+ import TableMetadata from "../../Types/Database/TableMetadata";
14
+ import TenantColumn from "../../Types/Database/TenantColumn";
15
+ import IconProp from "../../Types/Icon/IconProp";
16
+ import ObjectID from "../../Types/ObjectID";
17
+ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
18
+
19
+ /*
20
+ * What a shift reminder log row records.
21
+ *
22
+ * reminder - the ordinary "your shift starts in N minutes" message.
23
+ * catch-up - a late reminder for a shift the user only just acquired
24
+ * (an override created inside one of their lead windows).
25
+ * reassigned - "your shift at 18:00 is now covered by B", sent when a
26
+ * shift the user was already reminded about moves away.
27
+ */
28
+ export enum UserOnCallShiftReminderLogKind {
29
+ Reminder = "reminder",
30
+ CatchUp = "catch-up",
31
+ Reassigned = "reassigned",
32
+ }
33
+
34
+ /*
35
+ * The idempotency ledger of the shift-reminder worker.
36
+ *
37
+ * A reminder is CLAIMED by inserting a row (sentAt NULL), SENT, then STAMPED
38
+ * (sentAt set). The UNIQUE index over (userId, schedule, shiftStartsAt,
39
+ * minutesBeforeShift, kind) is the whole point: two worker replicas, a
40
+ * re-run after a crash, or a watermark that re-covers the same window all
41
+ * collapse into one send, because only one insert can win. shiftStartsAt is
42
+ * minute-truncated and seam-normalised by the worker so a start that moves
43
+ * by a second does not mint a new key.
44
+ *
45
+ * Postgres rather than Redis because compose Redis is non-persistent
46
+ * (`--save "" --appendonly no`), and a cache flush must never re-page anyone.
47
+ *
48
+ * ROOT ONLY. Every table access list is empty: nothing about this ledger is a
49
+ * user-facing resource, and a user who could delete their own claim rows
50
+ * could re-trigger reminders. The CRUD route exists only so the model is
51
+ * addressable like every other tenant model; every operation on it is denied.
52
+ */
53
+ @TenantColumn("projectId")
54
+ @TableAccessControl({
55
+ create: [],
56
+ read: [],
57
+ delete: [],
58
+ update: [],
59
+ })
60
+ @CrudApiEndpoint(new Route("/user-on-call-shift-reminder-log"))
61
+ @Entity({
62
+ name: "UserOnCallShiftReminderLog",
63
+ })
64
+ @TableMetadata({
65
+ tableName: "UserOnCallShiftReminderLog",
66
+ singularName: "On-Call Shift Reminder Log",
67
+ pluralName: "On-Call Shift Reminder Logs",
68
+ icon: IconProp.Bell,
69
+ tableDescription:
70
+ "Ledger of shift reminders claimed and sent, so each reminder is sent exactly once.",
71
+ })
72
+ @Index(
73
+ [
74
+ "userId",
75
+ "onCallDutyPolicyScheduleId",
76
+ "shiftStartsAt",
77
+ "minutesBeforeShift",
78
+ "kind",
79
+ ],
80
+ { unique: true },
81
+ )
82
+ export default class UserOnCallShiftReminderLog extends BaseModel {
83
+ @ColumnAccessControl({
84
+ create: [],
85
+ read: [],
86
+ update: [],
87
+ })
88
+ @TableColumn({
89
+ manyToOneRelationColumn: "projectId",
90
+ type: TableColumnType.Entity,
91
+ modelType: Project,
92
+ title: "Project",
93
+ description: "Relation to Project Resource in which this object belongs",
94
+ })
95
+ @ManyToOne(
96
+ () => {
97
+ return Project;
98
+ },
99
+ {
100
+ eager: false,
101
+ nullable: true,
102
+ onDelete: "CASCADE",
103
+ orphanedRowAction: "nullify",
104
+ },
105
+ )
106
+ @JoinColumn({ name: "projectId" })
107
+ public project?: Project = undefined;
108
+
109
+ @ColumnAccessControl({
110
+ create: [],
111
+ read: [],
112
+ update: [],
113
+ })
114
+ @Index()
115
+ @TableColumn({
116
+ type: TableColumnType.ObjectID,
117
+ required: true,
118
+ canReadOnRelationQuery: true,
119
+ title: "Project ID",
120
+ description: "ID of your OneUptime Project in which this object belongs",
121
+ example: "5f8b9c0d-e1a2-4b3c-8d5e-6f7a8b9c0d1e",
122
+ })
123
+ @Column({
124
+ type: ColumnType.ObjectID,
125
+ nullable: false,
126
+ transformer: ObjectID.getDatabaseTransformer(),
127
+ })
128
+ public projectId?: ObjectID = undefined;
129
+
130
+ @ColumnAccessControl({
131
+ create: [],
132
+ read: [],
133
+ update: [],
134
+ })
135
+ @TableColumn({
136
+ manyToOneRelationColumn: "userId",
137
+ type: TableColumnType.Entity,
138
+ modelType: User,
139
+ title: "User",
140
+ description: "Relation to the User this reminder was for",
141
+ })
142
+ @ManyToOne(
143
+ () => {
144
+ return User;
145
+ },
146
+ {
147
+ eager: false,
148
+ nullable: true,
149
+ onDelete: "CASCADE",
150
+ orphanedRowAction: "nullify",
151
+ },
152
+ )
153
+ @JoinColumn({ name: "userId" })
154
+ public user?: User = undefined;
155
+
156
+ @ColumnAccessControl({
157
+ create: [],
158
+ read: [],
159
+ update: [],
160
+ })
161
+ @Index()
162
+ @TableColumn({
163
+ type: TableColumnType.ObjectID,
164
+ required: true,
165
+ canReadOnRelationQuery: true,
166
+ title: "User ID",
167
+ description: "ID of the User this reminder was for",
168
+ example: "7c9d8e0f-a1b2-4c3d-9e5f-8a7b9c0d1e2f",
169
+ })
170
+ @Column({
171
+ type: ColumnType.ObjectID,
172
+ nullable: false,
173
+ transformer: ObjectID.getDatabaseTransformer(),
174
+ })
175
+ public userId?: ObjectID = undefined;
176
+
177
+ @ColumnAccessControl({
178
+ create: [],
179
+ read: [],
180
+ update: [],
181
+ })
182
+ @TableColumn({
183
+ manyToOneRelationColumn: "onCallDutyPolicyScheduleId",
184
+ type: TableColumnType.Entity,
185
+ modelType: OnCallDutyPolicySchedule,
186
+ title: "On-Call Policy Schedule",
187
+ description: "Relation to the schedule the reminded shift is on.",
188
+ })
189
+ @ManyToOne(
190
+ () => {
191
+ return OnCallDutyPolicySchedule;
192
+ },
193
+ {
194
+ eager: false,
195
+ nullable: true,
196
+ onDelete: "CASCADE",
197
+ orphanedRowAction: "nullify",
198
+ },
199
+ )
200
+ @JoinColumn({ name: "onCallDutyPolicyScheduleId" })
201
+ public onCallDutyPolicySchedule?: OnCallDutyPolicySchedule = undefined;
202
+
203
+ @ColumnAccessControl({
204
+ create: [],
205
+ read: [],
206
+ update: [],
207
+ })
208
+ @Index()
209
+ @TableColumn({
210
+ type: TableColumnType.ObjectID,
211
+ required: true,
212
+ canReadOnRelationQuery: true,
213
+ title: "On-Call Policy Schedule ID",
214
+ description: "ID of the schedule the reminded shift is on.",
215
+ example: "5f8b9c0d-e1a2-4b3c-8d5e-6f7a8b9c0d1e",
216
+ })
217
+ @Column({
218
+ type: ColumnType.ObjectID,
219
+ nullable: false,
220
+ transformer: ObjectID.getDatabaseTransformer(),
221
+ })
222
+ public onCallDutyPolicyScheduleId?: ObjectID = undefined;
223
+
224
+ /*
225
+ * The shift's start instant, minute-truncated and seam-normalised. Part of
226
+ * the idempotency key, so a shift whose computed start drifts by a second
227
+ * between two runs does not earn a second reminder.
228
+ */
229
+ @ColumnAccessControl({
230
+ create: [],
231
+ read: [],
232
+ update: [],
233
+ })
234
+ @TableColumn({
235
+ required: true,
236
+ type: TableColumnType.Date,
237
+ title: "Shift Starts At",
238
+ description: "When the reminded shift starts (minute precision)",
239
+ example: "2024-01-15T18:00:00.000Z",
240
+ })
241
+ @Column({
242
+ type: ColumnType.Date,
243
+ nullable: false,
244
+ })
245
+ public shiftStartsAt?: Date = undefined;
246
+
247
+ /*
248
+ * The configured lead this reminder was sent for; 0 for change notices
249
+ * (catch-up / reassigned), which are not tied to a lead.
250
+ */
251
+ @ColumnAccessControl({
252
+ create: [],
253
+ read: [],
254
+ update: [],
255
+ })
256
+ @TableColumn({
257
+ required: true,
258
+ isDefaultValueColumn: true,
259
+ defaultValue: 0,
260
+ type: TableColumnType.Number,
261
+ title: "Minutes Before Shift",
262
+ description:
263
+ "The lead time this reminder was sent for; 0 for change notices",
264
+ example: 60,
265
+ })
266
+ @Column({
267
+ type: ColumnType.Number,
268
+ nullable: false,
269
+ default: 0,
270
+ })
271
+ public minutesBeforeShift?: number = undefined;
272
+
273
+ @ColumnAccessControl({
274
+ create: [],
275
+ read: [],
276
+ update: [],
277
+ })
278
+ @TableColumn({
279
+ required: true,
280
+ type: TableColumnType.ShortText,
281
+ title: "Kind",
282
+ description: "reminder, catch-up or reassigned",
283
+ example: "reminder",
284
+ })
285
+ @Column({
286
+ type: ColumnType.ShortText,
287
+ length: ColumnLength.ShortText,
288
+ nullable: false,
289
+ })
290
+ public kind?: UserOnCallShiftReminderLogKind = undefined;
291
+
292
+ /*
293
+ * When the row was claimed. A claim older than ten minutes with sentAt still
294
+ * NULL is a send that died mid-flight and may be re-claimed.
295
+ */
296
+ @ColumnAccessControl({
297
+ create: [],
298
+ read: [],
299
+ update: [],
300
+ })
301
+ @TableColumn({
302
+ required: true,
303
+ type: TableColumnType.Date,
304
+ title: "Claimed At",
305
+ description: "When a worker claimed this reminder for sending",
306
+ example: "2024-01-15T17:00:00.000Z",
307
+ })
308
+ @Index()
309
+ @Column({
310
+ type: ColumnType.Date,
311
+ nullable: false,
312
+ })
313
+ public claimedAt?: Date = undefined;
314
+
315
+ @ColumnAccessControl({
316
+ create: [],
317
+ read: [],
318
+ update: [],
319
+ })
320
+ @TableColumn({
321
+ type: TableColumnType.Date,
322
+ title: "Sent At",
323
+ description: "When the reminder was actually sent; NULL while claimed",
324
+ example: "2024-01-15T17:00:02.000Z",
325
+ })
326
+ @Column({
327
+ type: ColumnType.Date,
328
+ nullable: true,
329
+ })
330
+ public sentAt?: Date = undefined;
331
+ }