Compare commits

...

1 Commits

Author SHA1 Message Date
paoloricciuti
199d57a8e3 feat: wip github webhook 2025-09-26 14:59:47 +02:00
5 changed files with 93 additions and 2 deletions

View File

@@ -1,3 +1,4 @@
DATABASE_URL=file:test.db DATABASE_URL=file:test.db
DATABASE_TOKEN=needs_to_be_set_but_it_can_be_anything DATABASE_TOKEN=needs_to_be_set_but_it_can_be_anything
VOYAGE_API_KEY=your_actual_api_key_here VOYAGE_API_KEY=your_actual_api_key_here
GITHUB_WEBHOOK_SECRET=some_secret

View File

@@ -65,6 +65,7 @@
"@sveltejs/mcp-schema": "workspace:^", "@sveltejs/mcp-schema": "workspace:^",
"@sveltejs/mcp-server": "workspace:^", "@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-http": "^0.6.3", "@tmcp/transport-http": "^0.6.3",
"tmcp": "^1.14.0" "tmcp": "^1.14.0",
"valibot": "^1.1.0"
} }
} }

View File

@@ -0,0 +1,32 @@
import * as v from 'valibot';
// not the full schema but it contains the information we need
export const github_webhook_schema = v.object({
action: v.union([v.literal('closed')]),
pull_request: v.object({
patch_url: v.string(),
merged: v.boolean(),
user: v.object({
login: v.string(),
}),
}),
});
export const github_content_schema = v.object({
name: v.string(),
path: v.string(),
sha: v.string(),
size: v.number(),
url: v.string(),
html_url: v.string(),
git_url: v.string(),
download_url: v.nullable(v.string()),
type: v.literal('file'),
content: v.string(),
encoding: v.literal('base64'),
_links: v.object({
self: v.string(),
git: v.string(),
html: v.string(),
}),
});

View File

@@ -0,0 +1,54 @@
import { github_content_schema, github_webhook_schema } from '$lib/schemas/index.js';
import * as v from 'valibot';
export async function POST({ request, fetch }) {
const body = await request.json();
// TODO add secret validation
const validated_pull_request = v.safeParse(github_webhook_schema, body);
if (!validated_pull_request.success) {
return new Response('Invalid payload', { status: 400 });
}
const { pull_request } = validated_pull_request.output;
if (!pull_request.merged) {
return new Response(null, { status: 204 });
}
const patch = await fetch(pull_request.patch_url);
if (!patch.ok) {
return new Response('Failed to fetch patch', { status: 500 });
}
const patch_text = await patch.text();
const files = [
...patch_text.matchAll(
/^diff --git\sa\/(?<file>.+?)\sb\/\1\n(?:(?<action>deleted|new)\sfile mode)?/gm,
),
].map((res) => ({ file: res.groups!.file, action: res.groups?.action ?? 'modified' }));
for (const file of files) {
if (file.action === 'deleted') {
// delete path from db
continue;
}
const new_file_content = await fetch(
`https://api.github.com/repos/sveltejs/svelte.dev/contents/${file.file}`,
);
if (!new_file_content.ok) {
// push file in queue and try again later?
continue;
}
const new_file_json = await new_file_content.json();
const validated_content = v.safeParse(github_content_schema, new_file_json);
if (!validated_content.success) {
// push file in queue and try again later?
continue;
}
const content = Buffer.from(validated_content.output.content, 'base64').toString('utf-8');
// save content and distilled content in the db
console.log({ content, file: file.file });
}
return new Response(null, { status: 204 });
}

3
pnpm-lock.yaml generated
View File

@@ -62,6 +62,9 @@ importers:
tmcp: tmcp:
specifier: ^1.14.0 specifier: ^1.14.0
version: 1.14.0(typescript@5.9.2) version: 1.14.0(typescript@5.9.2)
valibot:
specifier: ^1.1.0
version: 1.1.0(typescript@5.9.2)
devDependencies: devDependencies:
'@eslint/compat': '@eslint/compat':
specifier: ^1.3.2 specifier: ^1.3.2