feat(git): push webhook receiver + repo connect on service create

New public POST /api/git/webhooks/🆔 verifies provider signatures
(GitHub/Gitea HMAC-SHA256, GitLab token), filters branch pushes, matches
connected repo to services by clone_url/full_name + branch, inserts
deployment rows and enqueues runDeploymentAndSync (trigger=webhook).
Service-create dialog now connects the selected provider repo and
registers a push webhook (409-tolerant via connected-repo lookup).

api-client: connectGitRepository, listConnectedGitRepositories,
createGitWebhook typed from generated schemas. GitWebhook spec schema
corrected (events array + branch field).

Authored By: TDvorak <[email protected]>
This commit is contained in:
Tomas Dvorak committed 2026-09-17 10:30:36 +02:00
1 parent 93e3074477
commit 25703592c6
8 files changed
+550 -11

No files matched your search

+33
View File
@@ -1230,3 +1230,36 @@ export async function listGitBranches(
);
return payload.branches ?? [];
}
export type ConnectGitRepositoryInput = components['schemas']['ConnectGitRepoRequest'];
export type CreateGitWebhookInput = components['schemas']['CreateWebhookRequest'];
export type GitWebhookEntity = components['schemas']['GitWebhook'];
export async function connectGitRepository(
input: ConnectGitRepositoryInput,
): Promise<GitRepositoryEntity> {
const payload = await requestJson<{ repository?: GitRepositoryEntity }>(
'/git/repositories/connect',
{ method: 'POST', body: JSON.stringify(input) },
);
if (!payload.repository?.id) {
throw new ApiError('Connect repository response is invalid', 500);
}
return payload.repository;
}
export async function listConnectedGitRepositories(): Promise<GitRepositoryEntity[]> {
const payload = await requestJson<{ repositories?: GitRepositoryEntity[] }>('/git/repositories');
return payload.repositories ?? [];
}
export async function createGitWebhook(input: CreateGitWebhookInput): Promise<GitWebhookEntity> {
const payload = await requestJson<{ webhook?: GitWebhookEntity }>('/git/webhooks', {
method: 'POST',
body: JSON.stringify(input),
});
if (!payload.webhook?.id) {
throw new ApiError('Webhook response is invalid', 500);
}
return payload.webhook;
}