@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.
- package/Models/DatabaseModels/CodeRepository.ts +664 -0
- package/Models/DatabaseModels/Index.ts +8 -0
- package/Models/DatabaseModels/LlmLog.ts +818 -0
- package/Models/DatabaseModels/LlmProvider.ts +21 -0
- package/Models/DatabaseModels/Project.ts +206 -0
- package/Models/DatabaseModels/ServiceCatalogCodeRepository.ts +549 -0
- package/Server/API/AIBillingAPI.ts +126 -0
- package/Server/API/AlertAPI.ts +139 -0
- package/Server/API/GitHubAPI.ts +360 -0
- package/Server/API/IncidentAPI.ts +258 -0
- package/Server/API/ScheduledMaintenanceAPI.ts +164 -0
- package/Server/EnvironmentConfig.ts +44 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765580181582-MigrationName.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765633554715-MigrationName.ts +75 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765801357168-MigrationName.ts +32 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765810218488-MigrationName.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765830758857-MigrationName.ts +111 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765834537501-MigrationName.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
- package/Server/Services/AIBillingService.ts +247 -0
- package/Server/Services/AIService.ts +238 -0
- package/Server/Services/CodeRepositoryService.ts +10 -0
- package/Server/Services/IncidentService.ts +88 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LlmLogService.ts +14 -0
- package/Server/Services/LlmProviderService.ts +58 -0
- package/Server/Services/ServiceCatalogCodeRepositoryService.ts +55 -0
- package/Server/Utils/AI/AlertAIContextBuilder.ts +264 -0
- package/Server/Utils/AI/IncidentAIContextBuilder.ts +710 -0
- package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +345 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +226 -0
- package/Server/Utils/LLM/LLMService.ts +276 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +166 -0
- package/Server/Utils/Workspace/Slack/Slack.ts +134 -0
- package/Server/Utils/Workspace/Workspace.ts +126 -0
- package/Tests/Types/Domain.test.ts +24 -3
- package/Types/CodeRepository/CodeRepositoryType.ts +1 -1
- package/Types/Domain.ts +21 -24
- package/Types/LlmLogStatus.ts +7 -0
- package/Types/Permission.ts +87 -0
- package/Types/ServiceCatalog/CodeRepositoryImprovementAction.ts +9 -0
- package/UI/Components/AI/AILoader.tsx +95 -0
- package/UI/Components/AI/GenerateFromAIModal.tsx +432 -0
- package/UI/Components/Modal/Modal.tsx +6 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +689 -0
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +7 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LlmLog.js +856 -0
- package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LlmProvider.js +22 -0
- package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +220 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceCatalogCodeRepository.js +565 -0
- package/build/dist/Models/DatabaseModels/ServiceCatalogCodeRepository.js.map +1 -0
- package/build/dist/Server/API/AIBillingAPI.js +58 -0
- package/build/dist/Server/API/AIBillingAPI.js.map +1 -0
- package/build/dist/Server/API/AlertAPI.js +94 -0
- package/build/dist/Server/API/AlertAPI.js.map +1 -0
- package/build/dist/Server/API/GitHubAPI.js +207 -0
- package/build/dist/Server/API/GitHubAPI.js.map +1 -0
- package/build/dist/Server/API/IncidentAPI.js +171 -1
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +103 -0
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +31 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765580181582-MigrationName.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765580181582-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765633554715-MigrationName.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765633554715-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765801357168-MigrationName.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765801357168-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765810218488-MigrationName.js +30 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765810218488-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765830758857-MigrationName.js +44 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765830758857-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765834537501-MigrationName.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765834537501-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIBillingService.js +187 -0
- package/build/dist/Server/Services/AIBillingService.js.map +1 -0
- package/build/dist/Server/Services/AIService.js +184 -0
- package/build/dist/Server/Services/AIService.js.map +1 -0
- package/build/dist/Server/Services/CodeRepositoryService.js +9 -0
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -0
- package/build/dist/Server/Services/IncidentService.js +60 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +13 -0
- package/build/dist/Server/Services/LlmLogService.js.map +1 -0
- package/build/dist/Server/Services/LlmProviderService.js +65 -0
- package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
- package/build/dist/Server/Services/ServiceCatalogCodeRepositoryService.js +54 -0
- package/build/dist/Server/Services/ServiceCatalogCodeRepositoryService.js.map +1 -0
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +238 -0
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -0
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +597 -0
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -0
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +311 -0
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +163 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +225 -0
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +110 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +89 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Workspace.js +80 -0
- package/build/dist/Server/Utils/Workspace/Workspace.js.map +1 -1
- package/build/dist/Tests/Types/Domain.test.js +19 -3
- package/build/dist/Tests/Types/Domain.test.js.map +1 -1
- package/build/dist/Types/CodeRepository/CodeRepositoryType.js +1 -1
- package/build/dist/Types/CodeRepository/CodeRepositoryType.js.map +1 -1
- package/build/dist/Types/Domain.js +18 -16
- package/build/dist/Types/Domain.js.map +1 -1
- package/build/dist/Types/LlmLogStatus.js +8 -0
- package/build/dist/Types/LlmLogStatus.js.map +1 -0
- package/build/dist/Types/Permission.js +74 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/ServiceCatalog/CodeRepositoryImprovementAction.js +10 -0
- package/build/dist/Types/ServiceCatalog/CodeRepositoryImprovementAction.js.map +1 -0
- package/build/dist/UI/Components/AI/AILoader.js +64 -0
- package/build/dist/UI/Components/AI/AILoader.js.map +1 -0
- package/build/dist/UI/Components/AI/GenerateFromAIModal.js +320 -0
- package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -0
- package/build/dist/UI/Components/Modal/Modal.js +6 -1
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import Alert from "../../Models/DatabaseModels/Alert";
|
|
2
|
+
import NotFoundException from "../../Types/Exception/NotFoundException";
|
|
3
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
4
|
+
import ObjectID from "../../Types/ObjectID";
|
|
5
|
+
import AlertService, {
|
|
6
|
+
Service as AlertServiceType,
|
|
7
|
+
} from "../Services/AlertService";
|
|
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 AlertAIContextBuilder, {
|
|
20
|
+
AIGenerationContext,
|
|
21
|
+
AlertContextData,
|
|
22
|
+
} from "../Utils/AI/AlertAIContextBuilder";
|
|
23
|
+
import JSONFunctions from "../../Types/JSONFunctions";
|
|
24
|
+
import Permission from "../../Types/Permission";
|
|
25
|
+
|
|
26
|
+
export default class AlertAPI extends BaseAPI<Alert, AlertServiceType> {
|
|
27
|
+
public constructor() {
|
|
28
|
+
super(Alert, AlertService);
|
|
29
|
+
|
|
30
|
+
// Generate note from AI
|
|
31
|
+
this.router.post(
|
|
32
|
+
`${new this.entityType().getCrudApiPath()?.toString()}/generate-note-from-ai/:alertId`,
|
|
33
|
+
UserMiddleware.getUserMiddleware,
|
|
34
|
+
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
35
|
+
try {
|
|
36
|
+
await this.generateNoteFromAI(req, res);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
next(err);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private async generateNoteFromAI(
|
|
45
|
+
req: ExpressRequest,
|
|
46
|
+
res: ExpressResponse,
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
const alertIdParam: string | undefined = req.params["alertId"];
|
|
49
|
+
|
|
50
|
+
if (!alertIdParam) {
|
|
51
|
+
throw new BadDataException("Alert ID is required");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let alertId: ObjectID;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
alertId = new ObjectID(alertIdParam);
|
|
58
|
+
} catch {
|
|
59
|
+
throw new BadDataException("Invalid Alert ID");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const props: DatabaseCommonInteractionProps =
|
|
63
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
64
|
+
|
|
65
|
+
// Verify user has permission
|
|
66
|
+
const permissions: Array<Permission> | undefined = props
|
|
67
|
+
.userTenantAccessPermission?.["permissions"] as
|
|
68
|
+
| Array<Permission>
|
|
69
|
+
| undefined;
|
|
70
|
+
|
|
71
|
+
const hasPermission: boolean = permissions
|
|
72
|
+
? permissions.some((p: Permission) => {
|
|
73
|
+
return (
|
|
74
|
+
p === Permission.ProjectOwner ||
|
|
75
|
+
p === Permission.ProjectAdmin ||
|
|
76
|
+
p === Permission.EditAlert ||
|
|
77
|
+
p === Permission.CreateAlertInternalNote
|
|
78
|
+
);
|
|
79
|
+
})
|
|
80
|
+
: false;
|
|
81
|
+
|
|
82
|
+
if (!hasPermission && !props.isMasterAdmin) {
|
|
83
|
+
throw new BadDataException(
|
|
84
|
+
"You do not have permission to generate notes for this alert.",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Get the template from request body
|
|
89
|
+
const template: string | undefined = JSONFunctions.getJSONValueInPath(
|
|
90
|
+
req.body,
|
|
91
|
+
"template",
|
|
92
|
+
) as string | undefined;
|
|
93
|
+
|
|
94
|
+
// Get the alert to verify it exists and get the project ID
|
|
95
|
+
const alert: Alert | null = await this.service.findOneById({
|
|
96
|
+
id: alertId,
|
|
97
|
+
select: {
|
|
98
|
+
_id: true,
|
|
99
|
+
projectId: true,
|
|
100
|
+
},
|
|
101
|
+
props,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (!alert || !alert.projectId) {
|
|
105
|
+
throw new NotFoundException("Alert not found");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Build alert context
|
|
109
|
+
const contextData: AlertContextData =
|
|
110
|
+
await AlertAIContextBuilder.buildAlertContext({
|
|
111
|
+
alertId,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Format context for note generation
|
|
115
|
+
const aiContext: AIGenerationContext =
|
|
116
|
+
AlertAIContextBuilder.formatAlertContextForNote(contextData, template);
|
|
117
|
+
|
|
118
|
+
// Generate note using AIService (handles billing and logging)
|
|
119
|
+
const aiLogRequest: AILogRequest = {
|
|
120
|
+
projectId: alert.projectId,
|
|
121
|
+
feature: "Alert Internal Note",
|
|
122
|
+
alertId: alertId,
|
|
123
|
+
messages: aiContext.messages,
|
|
124
|
+
maxTokens: 4096,
|
|
125
|
+
temperature: 0.7,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (props.userId) {
|
|
129
|
+
aiLogRequest.userId = props.userId;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const response: AILogResponse =
|
|
133
|
+
await AIService.executeWithLogging(aiLogRequest);
|
|
134
|
+
|
|
135
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
136
|
+
note: response.content,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import Express, {
|
|
2
|
+
ExpressRequest,
|
|
3
|
+
ExpressResponse,
|
|
4
|
+
ExpressRouter,
|
|
5
|
+
} from "../Utils/Express";
|
|
6
|
+
import Response from "../Utils/Response";
|
|
7
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
8
|
+
import logger from "../Utils/Logger";
|
|
9
|
+
import { JSONObject } from "../../Types/JSON";
|
|
10
|
+
import { DashboardClientUrl, GitHubAppClientId } from "../EnvironmentConfig";
|
|
11
|
+
import ObjectID from "../../Types/ObjectID";
|
|
12
|
+
import GitHubUtil, {
|
|
13
|
+
GitHubRepository,
|
|
14
|
+
} from "../Utils/CodeRepository/GitHub/GitHub";
|
|
15
|
+
import CodeRepositoryService from "../Services/CodeRepositoryService";
|
|
16
|
+
import CodeRepository from "../../Models/DatabaseModels/CodeRepository";
|
|
17
|
+
import CodeRepositoryType from "../../Types/CodeRepository/CodeRepositoryType";
|
|
18
|
+
import URL from "../../Types/API/URL";
|
|
19
|
+
import UserMiddleware from "../Middleware/UserAuthorization";
|
|
20
|
+
import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
21
|
+
|
|
22
|
+
export default class GitHubAPI {
|
|
23
|
+
public getRouter(): ExpressRouter {
|
|
24
|
+
const router: ExpressRouter = Express.getRouter();
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* GitHub App installation callback
|
|
28
|
+
* This is called after a user installs the GitHub App
|
|
29
|
+
* The state parameter contains base64 encoded JSON with projectId and userId
|
|
30
|
+
*/
|
|
31
|
+
router.get(
|
|
32
|
+
"/github/auth/callback",
|
|
33
|
+
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
34
|
+
try {
|
|
35
|
+
// GitHub sends state parameter back which contains projectId and userId
|
|
36
|
+
const state: string | undefined = req.query["state"]?.toString();
|
|
37
|
+
|
|
38
|
+
if (!state) {
|
|
39
|
+
return Response.sendErrorResponse(
|
|
40
|
+
req,
|
|
41
|
+
res,
|
|
42
|
+
new BadDataException("State parameter is required"),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Decode the state parameter to get projectId and userId
|
|
47
|
+
let projectId: string | undefined;
|
|
48
|
+
let userId: string | undefined;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const decodedState: { projectId?: string; userId?: string } =
|
|
52
|
+
JSON.parse(Buffer.from(state, "base64").toString("utf-8"));
|
|
53
|
+
projectId = decodedState.projectId;
|
|
54
|
+
userId = decodedState.userId;
|
|
55
|
+
} catch {
|
|
56
|
+
return Response.sendErrorResponse(
|
|
57
|
+
req,
|
|
58
|
+
res,
|
|
59
|
+
new BadDataException("Invalid state parameter"),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!projectId) {
|
|
64
|
+
return Response.sendErrorResponse(
|
|
65
|
+
req,
|
|
66
|
+
res,
|
|
67
|
+
new BadDataException("Project ID is required in state"),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!userId) {
|
|
72
|
+
return Response.sendErrorResponse(
|
|
73
|
+
req,
|
|
74
|
+
res,
|
|
75
|
+
new BadDataException("User ID is required in state"),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// GitHub sends installation_id in query params after app installation
|
|
80
|
+
const installationId: string | undefined =
|
|
81
|
+
req.query["installation_id"]?.toString();
|
|
82
|
+
|
|
83
|
+
if (!installationId) {
|
|
84
|
+
return Response.sendErrorResponse(
|
|
85
|
+
req,
|
|
86
|
+
res,
|
|
87
|
+
new BadDataException(
|
|
88
|
+
"Installation ID is required. Please install the GitHub App first.",
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* Store the installation ID - we'll create repositories when user selects them
|
|
95
|
+
* For now, redirect back to dashboard with installation ID
|
|
96
|
+
*/
|
|
97
|
+
const redirectUrl: string = `${DashboardClientUrl.toString()}/dashboard/${projectId}/code-repository?installation_id=${installationId}`;
|
|
98
|
+
|
|
99
|
+
return Response.redirect(req, res, URL.fromString(redirectUrl));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
logger.error("GitHub Auth Callback Error:");
|
|
102
|
+
logger.error(error);
|
|
103
|
+
return Response.sendErrorResponse(
|
|
104
|
+
req,
|
|
105
|
+
res,
|
|
106
|
+
error instanceof Error
|
|
107
|
+
? new BadDataException(error.message)
|
|
108
|
+
: new BadDataException("An error occurred"),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Initiate GitHub App installation
|
|
115
|
+
router.get(
|
|
116
|
+
"/github/auth/install",
|
|
117
|
+
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
118
|
+
try {
|
|
119
|
+
if (!GitHubAppClientId) {
|
|
120
|
+
return Response.sendErrorResponse(
|
|
121
|
+
req,
|
|
122
|
+
res,
|
|
123
|
+
new BadDataException(
|
|
124
|
+
"GitHub App is not configured. Please set GITHUB_APP_CLIENT_ID.",
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const projectId: string | undefined =
|
|
130
|
+
req.query["projectId"]?.toString();
|
|
131
|
+
const userId: string | undefined = req.query["userId"]?.toString();
|
|
132
|
+
|
|
133
|
+
if (!projectId || !userId) {
|
|
134
|
+
return Response.sendErrorResponse(
|
|
135
|
+
req,
|
|
136
|
+
res,
|
|
137
|
+
new BadDataException("Project ID and User ID are required"),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/*
|
|
142
|
+
* Redirect to GitHub App installation page
|
|
143
|
+
* The state parameter helps us track the installation
|
|
144
|
+
*/
|
|
145
|
+
const state: string = Buffer.from(
|
|
146
|
+
JSON.stringify({ projectId, userId }),
|
|
147
|
+
).toString("base64");
|
|
148
|
+
|
|
149
|
+
const installUrl: string = `https://github.com/apps/${GitHubAppClientId}/installations/new?state=${state}`;
|
|
150
|
+
|
|
151
|
+
return Response.redirect(req, res, URL.fromString(installUrl));
|
|
152
|
+
} catch (error) {
|
|
153
|
+
logger.error("GitHub Install Redirect Error:");
|
|
154
|
+
logger.error(error);
|
|
155
|
+
return Response.sendErrorResponse(
|
|
156
|
+
req,
|
|
157
|
+
res,
|
|
158
|
+
error instanceof Error
|
|
159
|
+
? new BadDataException(error.message)
|
|
160
|
+
: new BadDataException("An error occurred"),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// List repositories for an installation
|
|
167
|
+
router.get(
|
|
168
|
+
"/github/repositories/:projectId/:installationId",
|
|
169
|
+
UserMiddleware.getUserMiddleware,
|
|
170
|
+
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
171
|
+
try {
|
|
172
|
+
const installationId: string | undefined =
|
|
173
|
+
req.params["installationId"]?.toString();
|
|
174
|
+
|
|
175
|
+
if (!installationId) {
|
|
176
|
+
return Response.sendErrorResponse(
|
|
177
|
+
req,
|
|
178
|
+
res,
|
|
179
|
+
new BadDataException("Installation ID is required"),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const repositories: Array<GitHubRepository> =
|
|
184
|
+
await GitHubUtil.listRepositoriesForInstallation(installationId);
|
|
185
|
+
|
|
186
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
187
|
+
repositories: repositories as unknown,
|
|
188
|
+
} as JSONObject);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
logger.error("GitHub List Repositories Error:");
|
|
191
|
+
logger.error(error);
|
|
192
|
+
return Response.sendErrorResponse(
|
|
193
|
+
req,
|
|
194
|
+
res,
|
|
195
|
+
error instanceof Error
|
|
196
|
+
? new BadDataException(error.message)
|
|
197
|
+
: new BadDataException("An error occurred"),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Connect a repository to a project
|
|
204
|
+
router.post(
|
|
205
|
+
"/github/repository/connect",
|
|
206
|
+
UserMiddleware.getUserMiddleware,
|
|
207
|
+
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
208
|
+
try {
|
|
209
|
+
const body: JSONObject = req.body;
|
|
210
|
+
|
|
211
|
+
const projectId: string | undefined = body["projectId"]?.toString();
|
|
212
|
+
const installationId: string | undefined =
|
|
213
|
+
body["installationId"]?.toString();
|
|
214
|
+
const repositoryName: string | undefined =
|
|
215
|
+
body["repositoryName"]?.toString();
|
|
216
|
+
const organizationName: string | undefined =
|
|
217
|
+
body["organizationName"]?.toString();
|
|
218
|
+
const name: string | undefined = body["name"]?.toString();
|
|
219
|
+
const defaultBranch: string | undefined =
|
|
220
|
+
body["defaultBranch"]?.toString();
|
|
221
|
+
const repositoryUrl: string | undefined =
|
|
222
|
+
body["repositoryUrl"]?.toString();
|
|
223
|
+
|
|
224
|
+
if (!projectId) {
|
|
225
|
+
return Response.sendErrorResponse(
|
|
226
|
+
req,
|
|
227
|
+
res,
|
|
228
|
+
new BadDataException("Project ID is required"),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!installationId) {
|
|
233
|
+
return Response.sendErrorResponse(
|
|
234
|
+
req,
|
|
235
|
+
res,
|
|
236
|
+
new BadDataException("Installation ID is required"),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!repositoryName) {
|
|
241
|
+
return Response.sendErrorResponse(
|
|
242
|
+
req,
|
|
243
|
+
res,
|
|
244
|
+
new BadDataException("Repository name is required"),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!organizationName) {
|
|
249
|
+
return Response.sendErrorResponse(
|
|
250
|
+
req,
|
|
251
|
+
res,
|
|
252
|
+
new BadDataException("Organization name is required"),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Create the code repository record
|
|
257
|
+
const codeRepository: CodeRepository = new CodeRepository();
|
|
258
|
+
codeRepository.projectId = new ObjectID(projectId);
|
|
259
|
+
codeRepository.name = name || `${organizationName}/${repositoryName}`;
|
|
260
|
+
codeRepository.repositoryHostedAt = CodeRepositoryType.GitHub;
|
|
261
|
+
codeRepository.organizationName = organizationName;
|
|
262
|
+
codeRepository.repositoryName = repositoryName;
|
|
263
|
+
codeRepository.mainBranchName = defaultBranch || "main";
|
|
264
|
+
codeRepository.gitHubAppInstallationId = installationId;
|
|
265
|
+
|
|
266
|
+
if (repositoryUrl) {
|
|
267
|
+
codeRepository.repositoryUrl = URL.fromString(repositoryUrl);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const createdRepository: CodeRepository =
|
|
271
|
+
await CodeRepositoryService.create({
|
|
272
|
+
data: codeRepository,
|
|
273
|
+
props: {
|
|
274
|
+
isRoot: true,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
279
|
+
repository: BaseModel.toJSON(createdRepository, CodeRepository),
|
|
280
|
+
} as JSONObject);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
logger.error("GitHub Connect Repository Error:");
|
|
283
|
+
logger.error(error);
|
|
284
|
+
return Response.sendErrorResponse(
|
|
285
|
+
req,
|
|
286
|
+
res,
|
|
287
|
+
error instanceof Error
|
|
288
|
+
? new BadDataException(error.message)
|
|
289
|
+
: new BadDataException("An error occurred"),
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// GitHub webhook handler
|
|
296
|
+
router.post(
|
|
297
|
+
"/github/webhook",
|
|
298
|
+
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
299
|
+
try {
|
|
300
|
+
const signature: string | undefined = req.headers[
|
|
301
|
+
"x-hub-signature-256"
|
|
302
|
+
] as string | undefined;
|
|
303
|
+
const event: string | undefined = req.headers["x-github-event"] as
|
|
304
|
+
| string
|
|
305
|
+
| undefined;
|
|
306
|
+
|
|
307
|
+
if (!signature) {
|
|
308
|
+
return Response.sendErrorResponse(
|
|
309
|
+
req,
|
|
310
|
+
res,
|
|
311
|
+
new BadDataException("Missing webhook signature"),
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Get raw body for signature verification
|
|
316
|
+
const rawBody: string = JSON.stringify(req.body);
|
|
317
|
+
|
|
318
|
+
// Verify webhook signature
|
|
319
|
+
const isValid: boolean = GitHubUtil.verifyWebhookSignature(
|
|
320
|
+
rawBody,
|
|
321
|
+
signature,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
if (!isValid) {
|
|
325
|
+
return Response.sendErrorResponse(
|
|
326
|
+
req,
|
|
327
|
+
res,
|
|
328
|
+
new BadDataException("Invalid webhook signature"),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
logger.debug(`Received GitHub webhook event: ${event}`);
|
|
333
|
+
|
|
334
|
+
/*
|
|
335
|
+
* Handle different webhook events here
|
|
336
|
+
* For now, just acknowledge receipt
|
|
337
|
+
* Future: Handle push, pull_request, check_run events
|
|
338
|
+
*/
|
|
339
|
+
|
|
340
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
341
|
+
success: true,
|
|
342
|
+
message: "Webhook received",
|
|
343
|
+
} as JSONObject);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
logger.error("GitHub Webhook Error:");
|
|
346
|
+
logger.error(error);
|
|
347
|
+
return Response.sendErrorResponse(
|
|
348
|
+
req,
|
|
349
|
+
res,
|
|
350
|
+
error instanceof Error
|
|
351
|
+
? new BadDataException(error.message)
|
|
352
|
+
: new BadDataException("An error occurred"),
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
return router;
|
|
359
|
+
}
|
|
360
|
+
}
|