@oneuptime/common 9.2.16 → 9.2.18

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 (133) hide show
  1. package/Models/DatabaseModels/CodeRepository.ts +664 -0
  2. package/Models/DatabaseModels/Index.ts +8 -0
  3. package/Models/DatabaseModels/LlmLog.ts +818 -0
  4. package/Models/DatabaseModels/LlmProvider.ts +21 -0
  5. package/Models/DatabaseModels/Project.ts +206 -0
  6. package/Models/DatabaseModels/ServiceCatalogCodeRepository.ts +549 -0
  7. package/Server/API/AIBillingAPI.ts +126 -0
  8. package/Server/API/AlertAPI.ts +139 -0
  9. package/Server/API/GitHubAPI.ts +360 -0
  10. package/Server/API/IncidentAPI.ts +258 -0
  11. package/Server/API/ScheduledMaintenanceAPI.ts +164 -0
  12. package/Server/EnvironmentConfig.ts +44 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1765580181582-MigrationName.ts +79 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1765633554715-MigrationName.ts +75 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1765801357168-MigrationName.ts +32 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1765810218488-MigrationName.ts +69 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1765830758857-MigrationName.ts +111 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1765834537501-MigrationName.ts +39 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
  20. package/Server/Services/AIBillingService.ts +247 -0
  21. package/Server/Services/AIService.ts +238 -0
  22. package/Server/Services/CodeRepositoryService.ts +10 -0
  23. package/Server/Services/IncidentService.ts +88 -0
  24. package/Server/Services/Index.ts +2 -0
  25. package/Server/Services/LlmLogService.ts +14 -0
  26. package/Server/Services/LlmProviderService.ts +58 -0
  27. package/Server/Services/ServiceCatalogCodeRepositoryService.ts +55 -0
  28. package/Server/Utils/AI/AlertAIContextBuilder.ts +264 -0
  29. package/Server/Utils/AI/IncidentAIContextBuilder.ts +710 -0
  30. package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +345 -0
  31. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +226 -0
  32. package/Server/Utils/LLM/LLMService.ts +276 -0
  33. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +166 -0
  34. package/Server/Utils/Workspace/Slack/Slack.ts +134 -0
  35. package/Server/Utils/Workspace/Workspace.ts +126 -0
  36. package/Tests/Types/Domain.test.ts +24 -3
  37. package/Types/CodeRepository/CodeRepositoryType.ts +1 -1
  38. package/Types/Domain.ts +21 -24
  39. package/Types/LlmLogStatus.ts +7 -0
  40. package/Types/Permission.ts +87 -0
  41. package/Types/ServiceCatalog/CodeRepositoryImprovementAction.ts +9 -0
  42. package/UI/Components/AI/AILoader.tsx +95 -0
  43. package/UI/Components/AI/GenerateFromAIModal.tsx +432 -0
  44. package/UI/Components/Modal/Modal.tsx +6 -1
  45. package/build/dist/Models/DatabaseModels/CodeRepository.js +689 -0
  46. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -0
  47. package/build/dist/Models/DatabaseModels/Index.js +7 -0
  48. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  49. package/build/dist/Models/DatabaseModels/LlmLog.js +856 -0
  50. package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -0
  51. package/build/dist/Models/DatabaseModels/LlmProvider.js +22 -0
  52. package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
  53. package/build/dist/Models/DatabaseModels/Project.js +220 -0
  54. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  55. package/build/dist/Models/DatabaseModels/ServiceCatalogCodeRepository.js +565 -0
  56. package/build/dist/Models/DatabaseModels/ServiceCatalogCodeRepository.js.map +1 -0
  57. package/build/dist/Server/API/AIBillingAPI.js +58 -0
  58. package/build/dist/Server/API/AIBillingAPI.js.map +1 -0
  59. package/build/dist/Server/API/AlertAPI.js +94 -0
  60. package/build/dist/Server/API/AlertAPI.js.map +1 -0
  61. package/build/dist/Server/API/GitHubAPI.js +207 -0
  62. package/build/dist/Server/API/GitHubAPI.js.map +1 -0
  63. package/build/dist/Server/API/IncidentAPI.js +171 -1
  64. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  65. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +103 -0
  66. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -0
  67. package/build/dist/Server/EnvironmentConfig.js +31 -0
  68. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  69. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765580181582-MigrationName.js +34 -0
  70. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765580181582-MigrationName.js.map +1 -0
  71. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765633554715-MigrationName.js +32 -0
  72. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765633554715-MigrationName.js.map +1 -0
  73. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765801357168-MigrationName.js +38 -0
  74. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765801357168-MigrationName.js.map +1 -0
  75. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765810218488-MigrationName.js +30 -0
  76. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765810218488-MigrationName.js.map +1 -0
  77. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765830758857-MigrationName.js +44 -0
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765830758857-MigrationName.js.map +1 -0
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765834537501-MigrationName.js +22 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765834537501-MigrationName.js.map +1 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
  82. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  83. package/build/dist/Server/Services/AIBillingService.js +187 -0
  84. package/build/dist/Server/Services/AIBillingService.js.map +1 -0
  85. package/build/dist/Server/Services/AIService.js +184 -0
  86. package/build/dist/Server/Services/AIService.js.map +1 -0
  87. package/build/dist/Server/Services/CodeRepositoryService.js +9 -0
  88. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -0
  89. package/build/dist/Server/Services/IncidentService.js +60 -0
  90. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  91. package/build/dist/Server/Services/Index.js +2 -0
  92. package/build/dist/Server/Services/Index.js.map +1 -1
  93. package/build/dist/Server/Services/LlmLogService.js +13 -0
  94. package/build/dist/Server/Services/LlmLogService.js.map +1 -0
  95. package/build/dist/Server/Services/LlmProviderService.js +65 -0
  96. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  97. package/build/dist/Server/Services/ServiceCatalogCodeRepositoryService.js +54 -0
  98. package/build/dist/Server/Services/ServiceCatalogCodeRepositoryService.js.map +1 -0
  99. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +238 -0
  100. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -0
  101. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +597 -0
  102. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -0
  103. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +311 -0
  104. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -0
  105. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +163 -0
  106. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  107. package/build/dist/Server/Utils/LLM/LLMService.js +225 -0
  108. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -0
  109. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +110 -0
  110. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  111. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +89 -0
  112. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  113. package/build/dist/Server/Utils/Workspace/Workspace.js +80 -0
  114. package/build/dist/Server/Utils/Workspace/Workspace.js.map +1 -1
  115. package/build/dist/Tests/Types/Domain.test.js +19 -3
  116. package/build/dist/Tests/Types/Domain.test.js.map +1 -1
  117. package/build/dist/Types/CodeRepository/CodeRepositoryType.js +1 -1
  118. package/build/dist/Types/CodeRepository/CodeRepositoryType.js.map +1 -1
  119. package/build/dist/Types/Domain.js +18 -16
  120. package/build/dist/Types/Domain.js.map +1 -1
  121. package/build/dist/Types/LlmLogStatus.js +8 -0
  122. package/build/dist/Types/LlmLogStatus.js.map +1 -0
  123. package/build/dist/Types/Permission.js +74 -0
  124. package/build/dist/Types/Permission.js.map +1 -1
  125. package/build/dist/Types/ServiceCatalog/CodeRepositoryImprovementAction.js +10 -0
  126. package/build/dist/Types/ServiceCatalog/CodeRepositoryImprovementAction.js.map +1 -0
  127. package/build/dist/UI/Components/AI/AILoader.js +64 -0
  128. package/build/dist/UI/Components/AI/AILoader.js.map +1 -0
  129. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +320 -0
  130. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -0
  131. package/build/dist/UI/Components/Modal/Modal.js +6 -1
  132. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  133. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import Incident from "../../Models/DatabaseModels/Incident";
2
2
  import File from "../../Models/DatabaseModels/File";
3
3
  import NotFoundException from "../../Types/Exception/NotFoundException";
4
+ import BadDataException from "../../Types/Exception/BadDataException";
4
5
  import ObjectID from "../../Types/ObjectID";
5
6
  import IncidentService, {
6
7
  Service as IncidentServiceType,
@@ -15,6 +16,13 @@ import {
15
16
  } from "../Utils/Express";
16
17
  import CommonAPI from "./CommonAPI";
17
18
  import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
19
+ import AIService, { AILogRequest, AILogResponse } from "../Services/AIService";
20
+ import IncidentAIContextBuilder, {
21
+ AIGenerationContext,
22
+ IncidentContextData,
23
+ } from "../Utils/AI/IncidentAIContextBuilder";
24
+ import JSONFunctions from "../../Types/JSONFunctions";
25
+ import Permission from "../../Types/Permission";
18
26
 
19
27
  export default class IncidentAPI extends BaseAPI<
20
28
  Incident,
@@ -36,6 +44,36 @@ export default class IncidentAPI extends BaseAPI<
36
44
  }
37
45
  },
38
46
  );
47
+
48
+ // Generate postmortem from AI
49
+ this.router.post(
50
+ `${new this.entityType()
51
+ .getCrudApiPath()
52
+ ?.toString()}/generate-postmortem-from-ai/:incidentId`,
53
+ UserMiddleware.getUserMiddleware,
54
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
55
+ try {
56
+ await this.generatePostmortemFromAI(req, res);
57
+ } catch (err) {
58
+ next(err);
59
+ }
60
+ },
61
+ );
62
+
63
+ // Generate note from AI
64
+ this.router.post(
65
+ `${new this.entityType()
66
+ .getCrudApiPath()
67
+ ?.toString()}/generate-note-from-ai/:incidentId`,
68
+ UserMiddleware.getUserMiddleware,
69
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
70
+ try {
71
+ await this.generateNoteFromAI(req, res);
72
+ } catch (err) {
73
+ next(err);
74
+ }
75
+ },
76
+ );
39
77
  }
40
78
 
41
79
  private async getPostmortemAttachment(
@@ -103,4 +141,224 @@ export default class IncidentAPI extends BaseAPI<
103
141
  Response.setNoCacheHeaders(res);
104
142
  return Response.sendFileResponse(req, res, attachment);
105
143
  }
144
+
145
+ private async generatePostmortemFromAI(
146
+ req: ExpressRequest,
147
+ res: ExpressResponse,
148
+ ): Promise<void> {
149
+ const incidentIdParam: string | undefined = req.params["incidentId"];
150
+
151
+ if (!incidentIdParam) {
152
+ throw new BadDataException("Incident ID is required");
153
+ }
154
+
155
+ let incidentId: ObjectID;
156
+
157
+ try {
158
+ incidentId = new ObjectID(incidentIdParam);
159
+ } catch {
160
+ throw new BadDataException("Invalid Incident ID");
161
+ }
162
+
163
+ const props: DatabaseCommonInteractionProps =
164
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
165
+
166
+ // Verify user has permission to edit the incident
167
+ const permissions: Array<Permission> | undefined = props
168
+ .userTenantAccessPermission?.["permissions"] as
169
+ | Array<Permission>
170
+ | undefined;
171
+
172
+ const hasPermission: boolean = permissions
173
+ ? permissions.some((p: Permission) => {
174
+ return (
175
+ p === Permission.ProjectOwner ||
176
+ p === Permission.ProjectAdmin ||
177
+ p === Permission.EditProjectIncident
178
+ );
179
+ })
180
+ : false;
181
+
182
+ if (!hasPermission && !props.isMasterAdmin) {
183
+ throw new BadDataException(
184
+ "You do not have permission to generate postmortem for this incident. You need to have one of these permissions: Project Owner, Project Admin, Edit Project Incident.",
185
+ );
186
+ }
187
+
188
+ // Get the template from request body if provided
189
+ const template: string | undefined = JSONFunctions.getJSONValueInPath(
190
+ req.body,
191
+ "template",
192
+ ) as string | undefined;
193
+
194
+ // Always include workspace messages for comprehensive context
195
+ const includeWorkspaceMessages: boolean = true;
196
+
197
+ // Get the incident to verify it exists and get the project ID
198
+ const incident: Incident | null = await this.service.findOneById({
199
+ id: incidentId,
200
+ select: {
201
+ _id: true,
202
+ projectId: true,
203
+ },
204
+ props,
205
+ });
206
+
207
+ if (!incident || !incident.projectId) {
208
+ throw new NotFoundException("Incident not found");
209
+ }
210
+
211
+ // Build incident context
212
+ const contextData: IncidentContextData =
213
+ await IncidentAIContextBuilder.buildIncidentContext({
214
+ incidentId,
215
+ includeWorkspaceMessages,
216
+ workspaceMessageLimit: 500,
217
+ });
218
+
219
+ // Format context for postmortem generation
220
+ const aiContext: AIGenerationContext =
221
+ IncidentAIContextBuilder.formatIncidentContextForPostmortem(
222
+ contextData,
223
+ template,
224
+ );
225
+
226
+ // Generate postmortem using AIService (handles billing and logging)
227
+ const aiLogRequest: AILogRequest = {
228
+ projectId: incident.projectId,
229
+ feature: "Incident Postmortem",
230
+ incidentId: incidentId,
231
+ messages: aiContext.messages,
232
+ maxTokens: 8192,
233
+ temperature: 0.7,
234
+ };
235
+
236
+ if (props.userId) {
237
+ aiLogRequest.userId = props.userId;
238
+ }
239
+
240
+ const response: AILogResponse =
241
+ await AIService.executeWithLogging(aiLogRequest);
242
+
243
+ return Response.sendJsonObjectResponse(req, res, {
244
+ postmortemNote: response.content,
245
+ });
246
+ }
247
+
248
+ private async generateNoteFromAI(
249
+ req: ExpressRequest,
250
+ res: ExpressResponse,
251
+ ): Promise<void> {
252
+ const incidentIdParam: string | undefined = req.params["incidentId"];
253
+
254
+ if (!incidentIdParam) {
255
+ throw new BadDataException("Incident ID is required");
256
+ }
257
+
258
+ let incidentId: ObjectID;
259
+
260
+ try {
261
+ incidentId = new ObjectID(incidentIdParam);
262
+ } catch {
263
+ throw new BadDataException("Invalid Incident ID");
264
+ }
265
+
266
+ const props: DatabaseCommonInteractionProps =
267
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
268
+
269
+ // Verify user has permission to edit the incident
270
+ const permissions: Array<Permission> | undefined = props
271
+ .userTenantAccessPermission?.["permissions"] as
272
+ | Array<Permission>
273
+ | undefined;
274
+
275
+ const hasPermission: boolean = permissions
276
+ ? permissions.some((p: Permission) => {
277
+ return (
278
+ p === Permission.ProjectOwner ||
279
+ p === Permission.ProjectAdmin ||
280
+ p === Permission.EditProjectIncident ||
281
+ p === Permission.CreateIncidentInternalNote ||
282
+ p === Permission.CreateIncidentPublicNote
283
+ );
284
+ })
285
+ : false;
286
+
287
+ if (!hasPermission && !props.isMasterAdmin) {
288
+ throw new BadDataException(
289
+ "You do not have permission to generate notes for this incident.",
290
+ );
291
+ }
292
+
293
+ // Get the template and note type from request body
294
+ const template: string | undefined = JSONFunctions.getJSONValueInPath(
295
+ req.body,
296
+ "template",
297
+ ) as string | undefined;
298
+
299
+ const noteType: string =
300
+ (JSONFunctions.getJSONValueInPath(req.body, "noteType") as string) ||
301
+ "internal";
302
+
303
+ if (noteType !== "public" && noteType !== "internal") {
304
+ throw new BadDataException("Note type must be 'public' or 'internal'");
305
+ }
306
+
307
+ // Always include workspace messages for comprehensive context
308
+ const includeWorkspaceMessages: boolean = true;
309
+
310
+ // Get the incident to verify it exists and get the project ID
311
+ const incident: Incident | null = await this.service.findOneById({
312
+ id: incidentId,
313
+ select: {
314
+ _id: true,
315
+ projectId: true,
316
+ },
317
+ props,
318
+ });
319
+
320
+ if (!incident || !incident.projectId) {
321
+ throw new NotFoundException("Incident not found");
322
+ }
323
+
324
+ // Build incident context
325
+ const contextData: IncidentContextData =
326
+ await IncidentAIContextBuilder.buildIncidentContext({
327
+ incidentId,
328
+ includeWorkspaceMessages,
329
+ workspaceMessageLimit: 300,
330
+ });
331
+
332
+ // Format context for note generation
333
+ const aiContext: AIGenerationContext =
334
+ IncidentAIContextBuilder.formatIncidentContextForNote(
335
+ contextData,
336
+ noteType as "public" | "internal",
337
+ template,
338
+ );
339
+
340
+ // Generate note using AIService (handles billing and logging)
341
+ const aiLogRequest: AILogRequest = {
342
+ projectId: incident.projectId,
343
+ feature:
344
+ noteType === "public"
345
+ ? "Incident Public Note"
346
+ : "Incident Internal Note",
347
+ incidentId: incidentId,
348
+ messages: aiContext.messages,
349
+ maxTokens: 4096,
350
+ temperature: 0.7,
351
+ };
352
+
353
+ if (props.userId) {
354
+ aiLogRequest.userId = props.userId;
355
+ }
356
+
357
+ const response: AILogResponse =
358
+ await AIService.executeWithLogging(aiLogRequest);
359
+
360
+ return Response.sendJsonObjectResponse(req, res, {
361
+ note: response.content,
362
+ });
363
+ }
106
364
  }
@@ -0,0 +1,164 @@
1
+ import ScheduledMaintenance from "../../Models/DatabaseModels/ScheduledMaintenance";
2
+ import NotFoundException from "../../Types/Exception/NotFoundException";
3
+ import BadDataException from "../../Types/Exception/BadDataException";
4
+ import ObjectID from "../../Types/ObjectID";
5
+ import ScheduledMaintenanceService, {
6
+ Service as ScheduledMaintenanceServiceType,
7
+ } from "../Services/ScheduledMaintenanceService";
8
+ import UserMiddleware from "../Middleware/UserAuthorization";
9
+ import Response from "../Utils/Response";
10
+ import BaseAPI from "./BaseAPI";
11
+ import {
12
+ ExpressRequest,
13
+ ExpressResponse,
14
+ NextFunction,
15
+ } from "../Utils/Express";
16
+ import CommonAPI from "./CommonAPI";
17
+ import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
18
+ import AIService, { AILogRequest, AILogResponse } from "../Services/AIService";
19
+ import ScheduledMaintenanceAIContextBuilder, {
20
+ AIGenerationContext,
21
+ ScheduledMaintenanceContextData,
22
+ } from "../Utils/AI/ScheduledMaintenanceAIContextBuilder";
23
+ import JSONFunctions from "../../Types/JSONFunctions";
24
+ import Permission from "../../Types/Permission";
25
+
26
+ export default class ScheduledMaintenanceAPI extends BaseAPI<
27
+ ScheduledMaintenance,
28
+ ScheduledMaintenanceServiceType
29
+ > {
30
+ public constructor() {
31
+ super(ScheduledMaintenance, ScheduledMaintenanceService);
32
+
33
+ // Generate note from AI
34
+ this.router.post(
35
+ `${new this.entityType()
36
+ .getCrudApiPath()
37
+ ?.toString()}/generate-note-from-ai/:scheduledMaintenanceId`,
38
+ UserMiddleware.getUserMiddleware,
39
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
40
+ try {
41
+ await this.generateNoteFromAI(req, res);
42
+ } catch (err) {
43
+ next(err);
44
+ }
45
+ },
46
+ );
47
+ }
48
+
49
+ private async generateNoteFromAI(
50
+ req: ExpressRequest,
51
+ res: ExpressResponse,
52
+ ): Promise<void> {
53
+ const scheduledMaintenanceIdParam: string | undefined =
54
+ req.params["scheduledMaintenanceId"];
55
+
56
+ if (!scheduledMaintenanceIdParam) {
57
+ throw new BadDataException("Scheduled Maintenance ID is required");
58
+ }
59
+
60
+ let scheduledMaintenanceId: ObjectID;
61
+
62
+ try {
63
+ scheduledMaintenanceId = new ObjectID(scheduledMaintenanceIdParam);
64
+ } catch {
65
+ throw new BadDataException("Invalid Scheduled Maintenance ID");
66
+ }
67
+
68
+ const props: DatabaseCommonInteractionProps =
69
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
70
+
71
+ // Verify user has permission
72
+ const permissions: Array<Permission> | undefined = props
73
+ .userTenantAccessPermission?.["permissions"] as
74
+ | Array<Permission>
75
+ | undefined;
76
+
77
+ const hasPermission: boolean = permissions
78
+ ? permissions.some((p: Permission) => {
79
+ return (
80
+ p === Permission.ProjectOwner ||
81
+ p === Permission.ProjectAdmin ||
82
+ p === Permission.EditProjectScheduledMaintenance ||
83
+ p === Permission.CreateScheduledMaintenanceInternalNote ||
84
+ p === Permission.CreateScheduledMaintenancePublicNote
85
+ );
86
+ })
87
+ : false;
88
+
89
+ if (!hasPermission && !props.isMasterAdmin) {
90
+ throw new BadDataException(
91
+ "You do not have permission to generate notes for this scheduled maintenance.",
92
+ );
93
+ }
94
+
95
+ // Get the template and note type from request body
96
+ const template: string | undefined = JSONFunctions.getJSONValueInPath(
97
+ req.body,
98
+ "template",
99
+ ) as string | undefined;
100
+
101
+ const noteType: string =
102
+ (JSONFunctions.getJSONValueInPath(req.body, "noteType") as string) ||
103
+ "internal";
104
+
105
+ if (noteType !== "public" && noteType !== "internal") {
106
+ throw new BadDataException("Note type must be 'public' or 'internal'");
107
+ }
108
+
109
+ // Get the scheduled maintenance to verify it exists and get the project ID
110
+ const scheduledMaintenance: ScheduledMaintenance | null =
111
+ await this.service.findOneById({
112
+ id: scheduledMaintenanceId,
113
+ select: {
114
+ _id: true,
115
+ projectId: true,
116
+ },
117
+ props,
118
+ });
119
+
120
+ if (!scheduledMaintenance || !scheduledMaintenance.projectId) {
121
+ throw new NotFoundException("Scheduled Maintenance not found");
122
+ }
123
+
124
+ // Build scheduled maintenance context
125
+ const contextData: ScheduledMaintenanceContextData =
126
+ await ScheduledMaintenanceAIContextBuilder.buildScheduledMaintenanceContext(
127
+ {
128
+ scheduledMaintenanceId,
129
+ },
130
+ );
131
+
132
+ // Format context for note generation
133
+ const aiContext: AIGenerationContext =
134
+ ScheduledMaintenanceAIContextBuilder.formatScheduledMaintenanceContextForNote(
135
+ contextData,
136
+ noteType as "public" | "internal",
137
+ template,
138
+ );
139
+
140
+ // Generate note using AIService (handles billing and logging)
141
+ const aiLogRequest: AILogRequest = {
142
+ projectId: scheduledMaintenance.projectId,
143
+ feature:
144
+ noteType === "public"
145
+ ? "Scheduled Maintenance Public Note"
146
+ : "Scheduled Maintenance Internal Note",
147
+ scheduledMaintenanceId: scheduledMaintenanceId,
148
+ messages: aiContext.messages,
149
+ maxTokens: 4096,
150
+ temperature: 0.7,
151
+ };
152
+
153
+ if (props.userId) {
154
+ aiLogRequest.userId = props.userId;
155
+ }
156
+
157
+ const response: AILogResponse =
158
+ await AIService.executeWithLogging(aiLogRequest);
159
+
160
+ return Response.sendJsonObjectResponse(req, res, {
161
+ note: response.content,
162
+ });
163
+ }
164
+ }
@@ -44,6 +44,7 @@ const FRONTEND_ENV_ALLOW_LIST: Array<string> = [
44
44
  "DISABLE_TELEMETRY",
45
45
  "SLACK_APP_CLIENT_ID",
46
46
  "MICROSOFT_TEAMS_APP_CLIENT_ID",
47
+ "GITHUB_APP_CLIENT_ID",
47
48
  "CAPTCHA_ENABLED",
48
49
  "CAPTCHA_SITE_KEY",
49
50
  ];
@@ -458,6 +459,49 @@ export const MicrosoftTeamsAppClientSecret: string | null =
458
459
  export const MicrosoftTeamsAppTenantId: string | null =
459
460
  process.env["MICROSOFT_TEAMS_APP_TENANT_ID"] || null;
460
461
 
462
+ // GitHub App Configuration
463
+ export const GitHubAppId: string | null = process.env["GITHUB_APP_ID"] || null;
464
+ export const GitHubAppClientId: string | null =
465
+ process.env["GITHUB_APP_CLIENT_ID"] || null;
466
+ export const GitHubAppClientSecret: string | null =
467
+ process.env["GITHUB_APP_CLIENT_SECRET"] || null;
468
+
469
+ type DecodePrivateKeyFunction = (key: string | undefined) => string | null;
470
+
471
+ // Helper function to decode base64 private key if needed
472
+ const decodePrivateKey: DecodePrivateKeyFunction = (
473
+ key: string | undefined,
474
+ ): string | null => {
475
+ if (!key) {
476
+ return null;
477
+ }
478
+
479
+ // If it starts with "-----BEGIN", it's already in PEM format
480
+ if (key.trim().startsWith("-----BEGIN")) {
481
+ return key;
482
+ }
483
+
484
+ // Otherwise, assume it's base64 encoded and decode it
485
+ try {
486
+ const decoded: string = Buffer.from(key, "base64").toString("utf-8");
487
+ // Verify it's a valid PEM key after decoding
488
+ if (decoded.trim().startsWith("-----BEGIN")) {
489
+ return decoded;
490
+ }
491
+ // If decoding doesn't produce a valid PEM, return original value
492
+ return key;
493
+ } catch {
494
+ // If decoding fails, return the original value
495
+ return key;
496
+ }
497
+ };
498
+
499
+ export const GitHubAppPrivateKey: string | null = decodePrivateKey(
500
+ process.env["GITHUB_APP_PRIVATE_KEY"],
501
+ );
502
+ export const GitHubAppWebhookSecret: string | null =
503
+ process.env["GITHUB_APP_WEBHOOK_SECRET"] || null;
504
+
461
505
  // VAPID Configuration for Web Push Notifications
462
506
  export const VapidPublicKey: string | undefined =
463
507
  process.env["VAPID_PUBLIC_KEY"] || undefined;
@@ -0,0 +1,79 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1765580181582 implements MigrationInterface {
4
+ public name = "MigrationName1765580181582";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "CodeRepository" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "repositoryHostedAt" character varying(100) NOT NULL, "organizationName" character varying(100) NOT NULL, "repositoryName" character varying(100) NOT NULL, "mainBranchName" character varying(100) DEFAULT 'main', "repositoryUrl" text, "gitHubAppInstallationId" character varying(500), "gitLabProjectId" character varying(500), "secretToken" text, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_7b5219d06a82fbc0bc4540b74f0" PRIMARY KEY ("_id"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE INDEX "IDX_a653bdc2fac520c9c8b9a7c7a6" ON "CodeRepository" ("projectId") `,
12
+ );
13
+ await queryRunner.query(
14
+ `CREATE TABLE "CodeRepositoryLabel" ("codeRepositoryId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_5adb09e0b5957488be8931f46bc" PRIMARY KEY ("codeRepositoryId", "labelId"))`,
15
+ );
16
+ await queryRunner.query(
17
+ `CREATE INDEX "IDX_7710ab8ee47601f78f3a4b76b6" ON "CodeRepositoryLabel" ("codeRepositoryId") `,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_8f7d12100e441fc72e02151fc5" ON "CodeRepositoryLabel" ("labelId") `,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "CodeRepository" ADD CONSTRAINT "FK_a653bdc2fac520c9c8b9a7c7a6a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "CodeRepository" ADD CONSTRAINT "FK_a870b71b99c87ea658c11421490" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "CodeRepository" ADD CONSTRAINT "FK_79d9249eb5f8174a6f6228311f4" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "CodeRepositoryLabel" ADD CONSTRAINT "FK_7710ab8ee47601f78f3a4b76b64" FOREIGN KEY ("codeRepositoryId") REFERENCES "CodeRepository"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "CodeRepositoryLabel" ADD CONSTRAINT "FK_8f7d12100e441fc72e02151fc56" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
42
+ );
43
+ }
44
+
45
+ public async down(queryRunner: QueryRunner): Promise<void> {
46
+ await queryRunner.query(
47
+ `ALTER TABLE "CodeRepositoryLabel" DROP CONSTRAINT "FK_8f7d12100e441fc72e02151fc56"`,
48
+ );
49
+ await queryRunner.query(
50
+ `ALTER TABLE "CodeRepositoryLabel" DROP CONSTRAINT "FK_7710ab8ee47601f78f3a4b76b64"`,
51
+ );
52
+ await queryRunner.query(
53
+ `ALTER TABLE "CodeRepository" DROP CONSTRAINT "FK_79d9249eb5f8174a6f6228311f4"`,
54
+ );
55
+ await queryRunner.query(
56
+ `ALTER TABLE "CodeRepository" DROP CONSTRAINT "FK_a870b71b99c87ea658c11421490"`,
57
+ );
58
+ await queryRunner.query(
59
+ `ALTER TABLE "CodeRepository" DROP CONSTRAINT "FK_a653bdc2fac520c9c8b9a7c7a6a"`,
60
+ );
61
+ await queryRunner.query(
62
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
63
+ );
64
+ await queryRunner.query(
65
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
66
+ );
67
+ await queryRunner.query(
68
+ `DROP INDEX "public"."IDX_8f7d12100e441fc72e02151fc5"`,
69
+ );
70
+ await queryRunner.query(
71
+ `DROP INDEX "public"."IDX_7710ab8ee47601f78f3a4b76b6"`,
72
+ );
73
+ await queryRunner.query(`DROP TABLE "CodeRepositoryLabel"`);
74
+ await queryRunner.query(
75
+ `DROP INDEX "public"."IDX_a653bdc2fac520c9c8b9a7c7a6"`,
76
+ );
77
+ await queryRunner.query(`DROP TABLE "CodeRepository"`);
78
+ }
79
+ }
@@ -0,0 +1,75 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1765633554715 implements MigrationInterface {
4
+ public name = "MigrationName1765633554715";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "ServiceCatalogCodeRepository" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "serviceCatalogId" uuid NOT NULL, "codeRepositoryId" uuid NOT NULL, "servicePathInRepository" character varying, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_1a0f992cb6c55f48d9fae93369a" PRIMARY KEY ("_id"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE INDEX "IDX_41a90624dfbb954e3b50c93dfc" ON "ServiceCatalogCodeRepository" ("projectId") `,
12
+ );
13
+ await queryRunner.query(
14
+ `CREATE INDEX "IDX_4638e464dc7cab644c7926674b" ON "ServiceCatalogCodeRepository" ("serviceCatalogId") `,
15
+ );
16
+ await queryRunner.query(
17
+ `CREATE INDEX "IDX_05b6c65746b28329def0accba9" ON "ServiceCatalogCodeRepository" ("codeRepositoryId") `,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "ServiceCatalogCodeRepository" ADD CONSTRAINT "FK_41a90624dfbb954e3b50c93dfc1" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "ServiceCatalogCodeRepository" ADD CONSTRAINT "FK_4638e464dc7cab644c7926674b8" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "ServiceCatalogCodeRepository" ADD CONSTRAINT "FK_05b6c65746b28329def0accba94" FOREIGN KEY ("codeRepositoryId") REFERENCES "CodeRepository"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "ServiceCatalogCodeRepository" ADD CONSTRAINT "FK_c838ecb46958f5de7a699f50c53" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "ServiceCatalogCodeRepository" ADD CONSTRAINT "FK_1aae6b3a023a36f7c004749afe4" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
39
+ );
40
+ }
41
+
42
+ public async down(queryRunner: QueryRunner): Promise<void> {
43
+ await queryRunner.query(
44
+ `ALTER TABLE "ServiceCatalogCodeRepository" DROP CONSTRAINT "FK_1aae6b3a023a36f7c004749afe4"`,
45
+ );
46
+ await queryRunner.query(
47
+ `ALTER TABLE "ServiceCatalogCodeRepository" DROP CONSTRAINT "FK_c838ecb46958f5de7a699f50c53"`,
48
+ );
49
+ await queryRunner.query(
50
+ `ALTER TABLE "ServiceCatalogCodeRepository" DROP CONSTRAINT "FK_05b6c65746b28329def0accba94"`,
51
+ );
52
+ await queryRunner.query(
53
+ `ALTER TABLE "ServiceCatalogCodeRepository" DROP CONSTRAINT "FK_4638e464dc7cab644c7926674b8"`,
54
+ );
55
+ await queryRunner.query(
56
+ `ALTER TABLE "ServiceCatalogCodeRepository" DROP CONSTRAINT "FK_41a90624dfbb954e3b50c93dfc1"`,
57
+ );
58
+ await queryRunner.query(
59
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
60
+ );
61
+ await queryRunner.query(
62
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
63
+ );
64
+ await queryRunner.query(
65
+ `DROP INDEX "public"."IDX_05b6c65746b28329def0accba9"`,
66
+ );
67
+ await queryRunner.query(
68
+ `DROP INDEX "public"."IDX_4638e464dc7cab644c7926674b"`,
69
+ );
70
+ await queryRunner.query(
71
+ `DROP INDEX "public"."IDX_41a90624dfbb954e3b50c93dfc"`,
72
+ );
73
+ await queryRunner.query(`DROP TABLE "ServiceCatalogCodeRepository"`);
74
+ }
75
+ }