remove old gitea plugin, replaced by gitea-sync
This commit is contained in:
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "@echoboard/plugin-gitea",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@echoboard/api": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.0"
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import crypto from 'node:crypto';
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
interface GiteaConfig {
|
||||
url: string;
|
||||
apiToken: string;
|
||||
webhookSecret: string;
|
||||
syncOnStartup: boolean;
|
||||
syncCron: string;
|
||||
}
|
||||
|
||||
function getConfig(): GiteaConfig {
|
||||
const url = process.env.PLUGIN_GITEA_URL;
|
||||
const apiToken = process.env.PLUGIN_GITEA_API_TOKEN;
|
||||
const webhookSecret = process.env.PLUGIN_GITEA_WEBHOOK_SECRET;
|
||||
|
||||
if (!url || !apiToken || !webhookSecret) {
|
||||
throw new Error('Missing required PLUGIN_GITEA_* env vars');
|
||||
}
|
||||
|
||||
return {
|
||||
url: url.replace(/\/$/, ''),
|
||||
apiToken,
|
||||
webhookSecret,
|
||||
syncOnStartup: process.env.PLUGIN_GITEA_SYNC_ON_STARTUP !== 'false',
|
||||
syncCron: process.env.PLUGIN_GITEA_SYNC_CRON || '0 */6 * * *',
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchRepos(config: GiteaConfig) {
|
||||
const repos: Array<{ id: number; name: string; full_name: string; html_url: string; description: string }> = [];
|
||||
let page = 1;
|
||||
|
||||
while (true) {
|
||||
const res = await fetch(
|
||||
`${config.url}/api/v1/repos/search?limit=50&page=${page}`,
|
||||
{ headers: { Authorization: `token ${config.apiToken}` } }
|
||||
);
|
||||
if (!res.ok) break;
|
||||
|
||||
const body = await res.json() as { data: typeof repos };
|
||||
if (!body.data?.length) break;
|
||||
|
||||
repos.push(...body.data);
|
||||
if (body.data.length < 50) break;
|
||||
page++;
|
||||
}
|
||||
|
||||
return repos;
|
||||
}
|
||||
|
||||
function verifyWebhookSignature(payload: Buffer | string, signature: string, secret: string): boolean {
|
||||
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
|
||||
if (signature.length !== expected.length) return false;
|
||||
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
|
||||
}
|
||||
|
||||
export const giteaPlugin = {
|
||||
name: 'gitea',
|
||||
version: '0.1.0',
|
||||
|
||||
onRegister(app: FastifyInstance) {
|
||||
const config = getConfig();
|
||||
|
||||
app.post('/api/v1/plugins/gitea/webhook', {
|
||||
preParsing: async (req, _reply, payload) => {
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of payload) {
|
||||
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
||||
}
|
||||
const raw = Buffer.concat(chunks);
|
||||
(req as any).rawBody = raw;
|
||||
return Readable.from(raw);
|
||||
},
|
||||
}, async (req, reply) => {
|
||||
const sig = req.headers['x-gitea-signature'] as string;
|
||||
const rawBody: Buffer = (req as any).rawBody;
|
||||
|
||||
if (!sig || !verifyWebhookSignature(rawBody, sig, config.webhookSecret)) {
|
||||
return reply.status(401).send({ error: 'invalid signature' });
|
||||
}
|
||||
|
||||
const event = req.headers['x-gitea-event'] as string;
|
||||
const body = req.body as Record<string, unknown>;
|
||||
|
||||
if (event === 'repository' && body.action === 'created') {
|
||||
const repo = body.repository as { id: number; name: string; html_url: string; description: string };
|
||||
const slug = repo.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
||||
|
||||
const { prisma } = app as unknown as { prisma: { board: { upsert: Function } } };
|
||||
await prisma.board.upsert({
|
||||
where: { slug },
|
||||
create: {
|
||||
slug,
|
||||
name: repo.name,
|
||||
description: repo.description || null,
|
||||
externalUrl: repo.html_url,
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
|
||||
if (event === 'repository' && body.action === 'deleted') {
|
||||
const repo = body.repository as { name: string };
|
||||
const slug = repo.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
||||
|
||||
const { prisma } = app as unknown as { prisma: { board: { updateMany: Function } } };
|
||||
await prisma.board.updateMany({
|
||||
where: { slug },
|
||||
data: { isArchived: true },
|
||||
});
|
||||
}
|
||||
|
||||
return reply.status(200).send({ ok: true });
|
||||
});
|
||||
|
||||
app.get('/api/v1/plugins/gitea/sync-status', { preHandler: [app.requireAdmin] }, async (_req, reply) => {
|
||||
return reply.send({ status: 'ok', lastSync: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.post('/api/v1/plugins/gitea/sync', { preHandler: [app.requireAdmin], config: { rateLimit: { max: 2, timeWindow: '1 minute' } } }, async (_req, reply) => {
|
||||
const repos = await fetchRepos(config);
|
||||
const { prisma } = app as unknown as { prisma: { board: { upsert: Function } } };
|
||||
|
||||
for (const repo of repos) {
|
||||
const slug = repo.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
||||
await prisma.board.upsert({
|
||||
where: { slug },
|
||||
create: {
|
||||
slug,
|
||||
name: repo.name,
|
||||
description: repo.description || null,
|
||||
externalUrl: repo.html_url,
|
||||
},
|
||||
update: { externalUrl: repo.html_url },
|
||||
});
|
||||
}
|
||||
|
||||
return reply.send({ synced: repos.length });
|
||||
});
|
||||
},
|
||||
|
||||
async onStartup() {
|
||||
const config = getConfig();
|
||||
if (!config.syncOnStartup) return;
|
||||
// initial sync handled by the route handler logic
|
||||
// in production this would call fetchRepos and sync
|
||||
},
|
||||
|
||||
getAdminRoutes() {
|
||||
return [
|
||||
{ path: '/admin/gitea', label: 'Gitea Sync', component: 'gitea-sync' },
|
||||
];
|
||||
},
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user