From 7b5fb1ecf17f9115aa00550fd9c8aa60766f3422 Mon Sep 17 00:00:00 2001 From: billsedison Date: Sat, 2 Aug 2025 15:36:59 +0800 Subject: [PATCH 1/4] Github webhook implementation --- .env.sample | 3 + .../migration.sql | 32 ++++ prisma/schema.prisma | 12 ++ src/api/api.module.ts | 6 + .../interfaces/github-webhook.interface.ts | 24 +++ src/api/webhook/webhook.controller.ts | 109 +++++++++++ src/api/webhook/webhook.service.ts | 174 ++++++++++++++++++ src/dto/webhook-event.dto.ts | 23 +++ src/shared/guards/github-signature.guard.ts | 105 +++++++++++ 9 files changed, 488 insertions(+) create mode 100644 prisma/migrations/20250802073602_github_webhook/migration.sql create mode 100644 src/api/webhook/interfaces/github-webhook.interface.ts create mode 100644 src/api/webhook/webhook.controller.ts create mode 100644 src/api/webhook/webhook.service.ts create mode 100644 src/dto/webhook-event.dto.ts create mode 100644 src/shared/guards/github-signature.guard.ts diff --git a/.env.sample b/.env.sample index 01dd94e..b4e24a1 100644 --- a/.env.sample +++ b/.env.sample @@ -1,6 +1,9 @@ POSTGRES_SCHEMA="public" DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=${POSTGRES_SCHEMA}" +# GitHub Webhook Configuration +GITHUB_WEBHOOK_SECRET="your_webhook_secret_here" + # API configs BUS_API_URL="https://api.topcoder-dev.com/v5/bus/events" CHALLENGE_API_URL="https://api.topcoder-dev.com/v5/challenges/" diff --git a/prisma/migrations/20250802073602_github_webhook/migration.sql b/prisma/migrations/20250802073602_github_webhook/migration.sql new file mode 100644 index 0000000..689b47a --- /dev/null +++ b/prisma/migrations/20250802073602_github_webhook/migration.sql @@ -0,0 +1,32 @@ +-- DropForeignKey +ALTER TABLE "reviewApplication" DROP CONSTRAINT "reviewApplication_opportunityId_fkey"; + +-- AlterTable +ALTER TABLE "reviewApplication" ALTER COLUMN "opportunityId" SET DATA TYPE TEXT, +ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "reviewOpportunity" ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- CreateTable +CREATE TABLE "gitWebhookLog" ( + "id" VARCHAR(14) NOT NULL DEFAULT nanoid(), + "eventId" TEXT NOT NULL, + "event" TEXT NOT NULL, + "eventPayload" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "gitWebhookLog_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "gitWebhookLog_eventId_idx" ON "gitWebhookLog"("eventId"); + +-- CreateIndex +CREATE INDEX "gitWebhookLog_event_idx" ON "gitWebhookLog"("event"); + +-- CreateIndex +CREATE INDEX "gitWebhookLog_createdAt_idx" ON "gitWebhookLog"("createdAt"); + +-- AddForeignKey +ALTER TABLE "reviewApplication" ADD CONSTRAINT "reviewApplication_opportunityId_fkey" FOREIGN KEY ("opportunityId") REFERENCES "reviewOpportunity"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2110987..e7cbde4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -381,3 +381,15 @@ model reviewApplication { @@index([userId]) @@index([opportunityId]) } + +model gitWebhookLog { + id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) + eventId String // X-GitHub-Delivery header + event String // X-GitHub-Event header + eventPayload Json // Complete webhook payload + createdAt DateTime @default(now()) + + @@index([eventId]) + @@index([event]) + @@index([createdAt]) +} diff --git a/src/api/api.module.ts b/src/api/api.module.ts index b1615c0..f866f55 100644 --- a/src/api/api.module.ts +++ b/src/api/api.module.ts @@ -13,6 +13,9 @@ import { ReviewOpportunityService } from './review-opportunity/reviewOpportunity import { ReviewApplicationService } from './review-application/reviewApplication.service'; import { ReviewHistoryController } from './review-history/reviewHistory.controller'; import { ChallengeApiService } from 'src/shared/modules/global/challenge.service'; +import { WebhookController } from './webhook/webhook.controller'; +import { WebhookService } from './webhook/webhook.service'; +import { GitHubSignatureGuard } from '../shared/guards/github-signature.guard'; @Module({ imports: [HttpModule, GlobalProvidersModule], @@ -26,11 +29,14 @@ import { ChallengeApiService } from 'src/shared/modules/global/challenge.service ReviewOpportunityController, ReviewApplicationController, ReviewHistoryController, + WebhookController, ], providers: [ ReviewOpportunityService, ReviewApplicationService, ChallengeApiService, + WebhookService, + GitHubSignatureGuard, ], }) export class ApiModule {} diff --git a/src/api/webhook/interfaces/github-webhook.interface.ts b/src/api/webhook/interfaces/github-webhook.interface.ts new file mode 100644 index 0000000..7a6cf12 --- /dev/null +++ b/src/api/webhook/interfaces/github-webhook.interface.ts @@ -0,0 +1,24 @@ +export interface GitHubWebhookHeaders { + 'x-github-delivery': string; + 'x-github-event': string; + 'x-hub-signature-256': string; + 'content-type': string; +} + +export interface WebhookRequest { + headers: GitHubWebhookHeaders; + body: any; // GitHub webhook payload (varies by event type) +} + +export interface WebhookResponse { + success: boolean; + message?: string; +} + +export interface ErrorResponse { + statusCode: number; + message: string; + error: string; + timestamp: string; + path: string; +} diff --git a/src/api/webhook/webhook.controller.ts b/src/api/webhook/webhook.controller.ts new file mode 100644 index 0000000..ca0cd55 --- /dev/null +++ b/src/api/webhook/webhook.controller.ts @@ -0,0 +1,109 @@ +import { + Controller, + Post, + Body, + Headers, + HttpCode, + HttpStatus, + UseGuards, +} from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse, ApiHeader } from '@nestjs/swagger'; +import { WebhookService } from './webhook.service'; +import { + WebhookEventDto, + WebhookResponseDto, +} from '../../dto/webhook-event.dto'; +import { GitHubSignatureGuard } from '../../shared/guards/github-signature.guard'; +import { LoggerService } from '../../shared/modules/global/logger.service'; + +@ApiTags('Webhooks') +@Controller('webhooks') +export class WebhookController { + private readonly logger = LoggerService.forRoot('WebhookController'); + + constructor(private readonly webhookService: WebhookService) {} + + @Post('git') + @HttpCode(HttpStatus.OK) + @UseGuards(GitHubSignatureGuard) + @ApiOperation({ + summary: 'GitHub Webhook Endpoint', + description: + 'Receives and processes GitHub webhook events with signature verification', + }) + @ApiHeader({ + name: 'X-GitHub-Delivery', + description: 'GitHub delivery UUID', + required: true, + }) + @ApiHeader({ + name: 'X-GitHub-Event', + description: 'GitHub event type', + required: true, + }) + @ApiHeader({ + name: 'X-Hub-Signature-256', + description: 'HMAC-SHA256 signature for request verification', + required: true, + }) + @ApiResponse({ + status: 200, + description: 'Webhook processed successfully', + type: WebhookResponseDto, + }) + @ApiResponse({ + status: 400, + description: 'Bad Request - Missing required headers or invalid payload', + }) + @ApiResponse({ + status: 403, + description: 'Forbidden - Invalid signature', + }) + @ApiResponse({ + status: 500, + description: 'Internal Server Error - Processing failed', + }) + async handleGitHubWebhook( + @Body() payload: any, + @Headers('x-github-delivery') delivery: string, + @Headers('x-github-event') event: string, + ): Promise { + try { + this.logger.log({ + message: 'Received GitHub webhook', + delivery, + event, + timestamp: new Date().toISOString(), + }); + + // Create webhook event DTO + const webhookEvent: WebhookEventDto = { + eventId: delivery, + event: event, + eventPayload: payload, + }; + + // Process the webhook + const result = await this.webhookService.processWebhook(webhookEvent); + + this.logger.log({ + message: 'Successfully processed GitHub webhook', + delivery, + event, + success: result.success, + }); + + return result; + } catch (error) { + this.logger.error({ + message: 'Failed to process GitHub webhook', + delivery, + event, + error: error.message, + stack: error.stack, + }); + + throw error; + } + } +} diff --git a/src/api/webhook/webhook.service.ts b/src/api/webhook/webhook.service.ts new file mode 100644 index 0000000..257e8ba --- /dev/null +++ b/src/api/webhook/webhook.service.ts @@ -0,0 +1,174 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../shared/modules/global/prisma.service'; +import { LoggerService } from '../../shared/modules/global/logger.service'; +import { PrismaErrorService } from '../../shared/modules/global/prisma-error.service'; +import { + WebhookEventDto, + WebhookResponseDto, +} from '../../dto/webhook-event.dto'; + +@Injectable() +export class WebhookService { + private readonly logger = LoggerService.forRoot('WebhookService'); + + constructor( + private readonly prisma: PrismaService, + private readonly prismaErrorService: PrismaErrorService, + ) {} + + async processWebhook( + webhookEvent: WebhookEventDto, + ): Promise { + try { + this.logger.log({ + message: 'Processing GitHub webhook event', + eventId: webhookEvent.eventId, + event: webhookEvent.event, + timestamp: new Date().toISOString(), + }); + + // Store webhook event in database + const storedEvent = await this.prisma.gitWebhookLog.create({ + data: { + eventId: webhookEvent.eventId, + event: webhookEvent.event, + eventPayload: webhookEvent.eventPayload, + }, + }); + + this.logger.log({ + message: 'Successfully stored webhook event', + eventId: webhookEvent.eventId, + event: webhookEvent.event, + storedId: storedEvent.id, + createdAt: storedEvent.createdAt, + }); + + // Future extensibility: Add event-specific handlers here + this.handleEventSpecificProcessing( + webhookEvent.event, + webhookEvent.eventPayload, + ); + + return { + success: true, + message: 'Webhook processed successfully', + }; + } catch (error) { + this.logger.error({ + message: 'Failed to process webhook event', + eventId: webhookEvent.eventId, + event: webhookEvent.event, + error: error.message, + stack: error.stack, + }); + + // Handle Prisma errors with the existing error service + if (error.code) { + this.prismaErrorService.handleError(error); + } + + throw error; + } + } + + /** + * Placeholder for future event-specific processing logic + * This method can be extended to handle different GitHub events differently + */ + private handleEventSpecificProcessing(event: string, payload: any): void { + this.logger.log({ + message: 'Event-specific processing placeholder', + event, + payloadSize: JSON.stringify(payload).length, + }); + + // Future implementation examples: + // switch (event) { + // case 'push': + // await this.handlePushEvent(payload); + // break; + // case 'pull_request': + // await this.handlePullRequestEvent(payload); + // break; + // case 'issues': + // await this.handleIssuesEvent(payload); + // break; + // default: + // this.logger.log(`No specific handler for event type: ${event}`); + // } + } + + /** + * Get webhook logs with pagination and filtering + * This method provides basic querying capabilities for webhook events + */ + async getWebhookLogs(options: { + eventId?: string; + event?: string; + limit?: number; + offset?: number; + startDate?: Date; + endDate?: Date; + }) { + try { + const { + eventId, + event, + limit = 50, + offset = 0, + startDate, + endDate, + } = options; + + const where: any = {}; + + if (eventId) { + where.eventId = eventId; + } + + if (event) { + where.event = event; + } + + if (startDate || endDate) { + where.createdAt = {}; + if (startDate) { + where.createdAt.gte = startDate; + } + if (endDate) { + where.createdAt.lte = endDate; + } + } + + const [logs, total] = await this.prisma.$transaction([ + this.prisma.gitWebhookLog.findMany({ + where, + orderBy: { createdAt: 'desc' }, + take: limit, + skip: offset, + }), + this.prisma.gitWebhookLog.count({ where }), + ]); + + return { + logs, + total, + limit, + offset, + }; + } catch (error) { + this.logger.error({ + message: 'Failed to retrieve webhook logs', + error: error.message, + options, + }); + + if (error.code) { + this.prismaErrorService.handleError(error); + } + + throw error; + } + } +} diff --git a/src/dto/webhook-event.dto.ts b/src/dto/webhook-event.dto.ts new file mode 100644 index 0000000..718fc65 --- /dev/null +++ b/src/dto/webhook-event.dto.ts @@ -0,0 +1,23 @@ +import { IsString, IsNotEmpty, IsOptional } from 'class-validator'; + +export class WebhookEventDto { + @IsString() + @IsNotEmpty() + eventId: string; // From X-GitHub-Delivery + + @IsString() + @IsNotEmpty() + event: string; // From X-GitHub-Event + + @IsNotEmpty() + eventPayload: any; // Complete webhook payload +} + +export class WebhookResponseDto { + @IsNotEmpty() + success: boolean; + + @IsString() + @IsOptional() + message?: string; +} diff --git a/src/shared/guards/github-signature.guard.ts b/src/shared/guards/github-signature.guard.ts new file mode 100644 index 0000000..5924880 --- /dev/null +++ b/src/shared/guards/github-signature.guard.ts @@ -0,0 +1,105 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, + BadRequestException, + InternalServerErrorException, +} from '@nestjs/common'; +import { Request } from 'express'; +import * as crypto from 'crypto'; +import { LoggerService } from '../modules/global/logger.service'; + +@Injectable() +export class GitHubSignatureGuard implements CanActivate { + private readonly logger = LoggerService.forRoot('GitHubSignatureGuard'); + + canActivate(context: ExecutionContext): boolean { + const request = context.switchToHttp().getRequest(); + const signature = request.headers['x-hub-signature-256'] as string; + const delivery = request.headers['x-github-delivery'] as string; + const event = request.headers['x-github-event'] as string; + + // Check if GITHUB_WEBHOOK_SECRET is configured + const secret = process.env.GITHUB_WEBHOOK_SECRET; + if (!secret) { + this.logger.error( + 'GITHUB_WEBHOOK_SECRET environment variable is not configured', + ); + throw new InternalServerErrorException('Webhook secret not configured'); + } + + // Validate required headers + if (!signature) { + this.logger.error('Missing X-Hub-Signature-256 header'); + throw new BadRequestException('Missing signature header'); + } + + if (!delivery) { + this.logger.error('Missing X-GitHub-Delivery header'); + throw new BadRequestException('Missing delivery header'); + } + + if (!event) { + this.logger.error('Missing X-GitHub-Event header'); + throw new BadRequestException('Missing event header'); + } + + // Validate signature format + if (!signature.startsWith('sha256=')) { + this.logger.error('Invalid signature format'); + throw new BadRequestException('Invalid signature format'); + } + + try { + // Get the raw body for signature verification + const payload = request.body; + let bodyString: string; + + if (typeof payload === 'string') { + bodyString = payload; + } else if (Buffer.isBuffer(payload)) { + bodyString = payload.toString('utf8'); + } else { + bodyString = JSON.stringify(payload); + } + + // Compute HMAC-SHA256 signature + const computedSignature = crypto + .createHmac('sha256', secret) + .update(bodyString, 'utf8') + .digest('hex'); + + const expectedSignature = `sha256=${computedSignature}`; + + // Extract the signature hash from the header + const providedSignature = signature; + + // Perform timing-safe comparison to prevent timing attacks + const isValid = crypto.timingSafeEqual( + Buffer.from(expectedSignature, 'utf8'), + Buffer.from(providedSignature, 'utf8'), + ); + + if (!isValid) { + this.logger.error(`Invalid webhook signature for delivery ${delivery}`); + throw new ForbiddenException('Invalid signature'); + } + + this.logger.log( + `Valid webhook signature verified for delivery ${delivery}, event ${event}`, + ); + return true; + } catch (error) { + if ( + error instanceof ForbiddenException || + error instanceof BadRequestException + ) { + throw error; + } + + this.logger.error(`Error validating webhook signature: ${error.message}`); + throw new InternalServerErrorException('Signature validation failed'); + } + } +} From 8b1b7eaeea74b204dce1a2824aba121ef2fa0cbb Mon Sep 17 00:00:00 2001 From: billsedison Date: Sat, 2 Aug 2025 15:39:05 +0800 Subject: [PATCH 2/4] Add Github webhook setup --- GITHUB_WEBHOOK_SETUP.md | 350 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 GITHUB_WEBHOOK_SETUP.md diff --git a/GITHUB_WEBHOOK_SETUP.md b/GITHUB_WEBHOOK_SETUP.md new file mode 100644 index 0000000..d655bf2 --- /dev/null +++ b/GITHUB_WEBHOOK_SETUP.md @@ -0,0 +1,350 @@ +# GitHub Webhook Integration Setup and Testing Guide + +## Overview + +The Topcoder Review API includes a secure GitHub webhook integration that receives webhook events from GitHub repositories, validates them using HMAC-SHA256 signature verification, and stores them in the database for audit and future processing. + +## Table of Contents + +1. [Quick Start](#quick-start) +2. [Environment Setup](#environment-setup) +3. [GitHub Repository Configuration](#github-repository-configuration) +4. [Local Development Setup](#local-development-setup) +5. [Testing the Integration](#testing-the-integration) +6. [API Endpoint Reference](#api-endpoint-reference) +7. [Database Schema](#database-schema) +8. [Security Considerations](#security-considerations) +9. [Troubleshooting](#troubleshooting) +10. [Monitoring and Maintenance](#monitoring-and-maintenance) + +## Quick Start + +For immediate setup, follow these steps: + +1. Generate a secure webhook secret +2. Configure environment variables +3. Set up GitHub webhook in repository settings +4. Test with a sample event + +## Environment Setup + +### Required Environment Variables + +Add the following environment variable to your application configuration: + +```bash +# .env file +GITHUB_WEBHOOK_SECRET=your_generated_secret_here +``` + +### Generate Webhook Secret + +**Using OpenSSL:** +```bash +openssl rand -hex 32 +``` + +**Example Output:** +``` +a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456 +``` + +⚠️ **Important:** Store this secret securely and use the same value in both your application environment and GitHub webhook configuration. + +### Database Setup + +The webhook integration requires the `gitWebhookLog` table. If not already created, run the database migration: + +```bash +npx prisma migrate dev +``` + +## GitHub Repository Configuration + +### Step 1: Access Repository Settings + +1. Navigate to your GitHub repository +2. Click on the **Settings** tab (requires admin permissions) +3. In the left sidebar, click **Webhooks** +4. Click **Add webhook** + +### Step 2: Configure Webhook Settings + +#### Payload URL + +**Production/Staging Environment:** +``` +https://your-api-domain.com/v6/review/webhooks/git +``` + +**Development Environment:** +``` +https://your-dev-domain.com/webhooks/git +``` + +Note: The `/v6/review` prefix is only added in production when `NODE_ENV=production`. + +#### Content Type +- Select `application/json` + +#### Secret +- Enter the webhook secret you generated earlier +- This must exactly match your `GITHUB_WEBHOOK_SECRET` environment variable + +#### SSL Verification +- Keep **Enable SSL verification** checked (recommended for production) +- For development with proper HTTPS setup, this should remain enabled + +### Step 3: Select Events + +Choose one of the following options: + +**Option A: Send Everything (Recommended for Testing)** +- Select "Send me everything" to receive all GitHub event types + +**Option B: Select Individual Events** +Common events for development workflows: +- **Pushes** - Code pushes to repository +- **Pull requests** - PR creation, updates, merges +- **Issues** - Issue creation, updates, comments +- **Issue comments** - Comments on issues and PRs +- **Releases** - Release creation and updates +- **Create** - Branch or tag creation +- **Delete** - Branch or tag deletion + +### Step 4: Activate and Create + +1. Ensure **Active** checkbox is checked +2. Click **Add webhook** +3. GitHub will automatically send a `ping` event to test the webhook + +## Local Development Setup + +Since GitHub webhooks require a publicly accessible URL, local development requires exposing your local server to the internet. + +**Install ngrok:** +```bash +npm install -g ngrok +``` + +**Setup process:** +```bash +# 1. Start your local API server +pnpm run start:dev + +# 2. In another terminal, expose your local server +ngrok http 3000 + +# 3. Copy the HTTPS URL from ngrok output +# Example: https://abc123.ngrok.io + +# 4. Use this URL in GitHub webhook settings +# https://abc123.ngrok.io/webhooks/git +``` +## Testing the Integration + +### Manual Testing + +#### 1. Verify Initial Setup + +After creating the webhook, GitHub automatically sends a `ping` event: + +1. Go to your repository's webhook settings +2. Click on your webhook +3. Check **Recent Deliveries** section +4. Look for the `ping` event with status 200 OK + +#### 2. Trigger Test Events + +**Create a Push Event:** +```bash +# Make a small change +echo "webhook test" >> test-webhook.txt +git add test-webhook.txt +git commit -m "Test webhook integration" +git push origin main +``` + +**Create an Issue:** +1. Go to your repository on GitHub +2. Click **Issues** tab +3. Click **New issue** +4. Create a test issue + +**Create a Pull Request:** +1. Create a new branch: `git checkout -b test-webhook` +2. Make changes and commit +3. Push branch: `git push origin test-webhook` +4. Open pull request on GitHub + +### Testing with curl + +You can test the webhook endpoint directly using curl with proper signature generation: + +```bash +#!/bin/bash + +# Configuration +WEBHOOK_URL="http://localhost:3000/webhooks/git" # Adjust for your environment +WEBHOOK_SECRET="your_webhook_secret_here" +PAYLOAD='{"test": "data", "repository": {"name": "test-repo"}}' +DELIVERY_ID="test-delivery-$(date +%s)" +EVENT_TYPE="push" + +# Generate signature +SIGNATURE="sha256=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | sed 's/^.* //')" + +# Send test webhook +curl -X POST "$WEBHOOK_URL" \ + -H "Content-Type: application/json" \ + -H "X-GitHub-Event: $EVENT_TYPE" \ + -H "X-GitHub-Delivery: $DELIVERY_ID" \ + -H "X-Hub-Signature-256: $SIGNATURE" \ + -d "$PAYLOAD" +``` + +## API Endpoint Reference + +### Webhook Endpoint + +**URL:** `POST /webhooks/git` (development) or `POST /v6/review/webhooks/git` (production) + +**Required Headers:** +- `Content-Type: application/json` +- `X-GitHub-Event: {event_type}` - GitHub event type (push, pull_request, etc.) +- `X-GitHub-Delivery: {delivery_id}` - Unique delivery identifier from GitHub +- `X-Hub-Signature-256: sha256={signature}` - HMAC-SHA256 signature for verification + +**Request Body:** +- GitHub webhook payload (varies by event type) + +**Response Codes:** +- `200 OK` - Webhook processed successfully +- `400 Bad Request` - Missing required headers or invalid payload +- `403 Forbidden` - Invalid signature verification +- `500 Internal Server Error` - Processing error or configuration issue + +**Success Response:** +```json +{ + "success": true, + "message": "Webhook processed successfully" +} +``` + +**Error Response:** +```json +{ + "statusCode": 403, + "message": "Invalid signature", + "error": "Forbidden", + "timestamp": "2024-01-01T00:00:00.000Z", + "path": "/webhooks/git" +} +``` + +## Database Schema + +Webhook events are stored in the `gitWebhookLog` table: + +```sql +CREATE TABLE "gitWebhookLog" ( + "id" VARCHAR(14) NOT NULL DEFAULT nanoid(), + "eventId" TEXT NOT NULL, + "event" TEXT NOT NULL, + "eventPayload" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "gitWebhookLog_pkey" PRIMARY KEY ("id") +); + +-- Indexes for efficient querying +CREATE INDEX "gitWebhookLog_eventId_idx" ON "gitWebhookLog"("eventId"); +CREATE INDEX "gitWebhookLog_event_idx" ON "gitWebhookLog"("event"); +CREATE INDEX "gitWebhookLog_createdAt_idx" ON "gitWebhookLog"("createdAt"); +``` + +### Query Examples + +**View recent webhook events:** +```sql +SELECT + id, + "eventId", + event, + "createdAt" +FROM "gitWebhookLog" +ORDER BY "createdAt" DESC +LIMIT 10; +``` + +**Filter by event type:** +```sql +SELECT * FROM "gitWebhookLog" +WHERE event = 'push' +ORDER BY "createdAt" DESC; +``` + +**View specific webhook payload:** +```sql +SELECT + event, + "eventPayload" +FROM "gitWebhookLog" +WHERE "eventId" = 'your-delivery-id'; +``` + +## Security Considerations + +### Signature Verification + +The webhook implementation uses GitHub's recommended security practices: + +1. **HMAC-SHA256 Signature:** All incoming webhooks are verified using HMAC-SHA256 +2. **Timing-Safe Comparison:** Uses `crypto.timingSafeEqual()` to prevent timing attacks +3. **Secret Protection:** Webhook secrets are stored as environment variables +4. **Header Validation:** Validates all required GitHub headers + +### Best Practices + +1. **Use HTTPS:** Always use HTTPS URLs for production webhooks +2. **Rotate Secrets:** Periodically rotate webhook secrets +3. **Monitor Access:** Regularly review webhook delivery logs +4. **Limit Events:** Only subscribe to events you actually need +5. **Access Control:** Restrict webhook configuration to repository administrators + +### Environment Security + +- Store `GITHUB_WEBHOOK_SECRET` securely using your deployment platform's secret management +- Never commit secrets to version control +- Use different secrets for different environments +- Implement proper secret rotation procedures + +### Log Analysis + +Key log messages to monitor: + +``` +# Successful webhook processing +[WebhookController] Successfully processed GitHub webhook + +# Signature validation failures +[GitHubSignatureGuard] Invalid webhook signature for delivery + +# Configuration errors +[GitHubSignatureGuard] GITHUB_WEBHOOK_SECRET environment variable is not configured +``` + +Example + +``` +[2025-08-02T01:06:48.312Z] [LOG] [Bootstrap] Server started on port 3000 +[2025-08-02T01:07:15.700Z] [LOG] [HttpRequest] {"type":"request","method":"POST","url":"/webhooks/git","ip":"::1","userAgent":"GitHub-Hookshot/4f8bd7a"} +[2025-08-02T01:07:15.739Z] [LOG] [GitHubSignatureGuard] Valid webhook signature verified for delivery 0722d0bc-6f3d-11f0-8a2d-6cc18966c098, event push +[2025-08-02T01:07:15.740Z] [LOG] [WebhookController] {"message":"Received GitHub webhook","delivery":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","timestamp":"2025-08-02T01:07:15.740Z"} +[2025-08-02T01:07:15.740Z] [LOG] [WebhookService] {"message":"Processing GitHub webhook event","eventId":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","timestamp":"2025-08-02T01:07:15.740Z"} +[2025-08-02T01:07:15.804Z] [LOG] [WebhookService] {"message":"Successfully stored webhook event","eventId":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","storedId":"9aHvEgDYPCYYnU","createdAt":"2025-08-02T01:07:15.747Z"} +[2025-08-02T01:07:15.804Z] [LOG] [WebhookService] {"message":"Event-specific processing placeholder","event":"push","payloadSize":7979} +[2025-08-02T01:07:15.804Z] [LOG] [WebhookController] {"message":"Successfully processed GitHub webhook","delivery":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","success":true} +[2025-08-02T01:07:15.804Z] [LOG] [HttpRequest] {"type":"response","statusCode":200,"method":"POST","url":"/webhooks/git","responseTime":"104ms"} +``` From 85c3c7090543cc20169b15314ec7011a85321082 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Mon, 4 Aug 2025 08:58:24 +0300 Subject: [PATCH 3/4] Gitea --- .env.sample | 4 +- .../GITEA_WEBHOOK_SETUP.md | 119 ++++++++++-------- KAFKA_SETUP.md => docs/KAFKA_SETUP.md | 0 M2M_TOKEN_GUIDE.md => docs/M2M_TOKEN_GUIDE.md | 0 .../migration.sql | 13 -- src/api/api.module.ts | 4 +- ...nterface.ts => gitea-webhook.interface.ts} | 0 src/api/webhook/webhook.controller.ts | 30 ++--- ...ture.guard.ts => gitea-signature.guard.ts} | 18 +-- 9 files changed, 92 insertions(+), 96 deletions(-) rename GITHUB_WEBHOOK_SETUP.md => docs/GITEA_WEBHOOK_SETUP.md (63%) rename KAFKA_SETUP.md => docs/KAFKA_SETUP.md (100%) rename M2M_TOKEN_GUIDE.md => docs/M2M_TOKEN_GUIDE.md (100%) rename prisma/migrations/{20250802073602_github_webhook => 20250802073602_gitea_webhook}/migration.sql (51%) rename src/api/webhook/interfaces/{github-webhook.interface.ts => gitea-webhook.interface.ts} (100%) rename src/shared/guards/{github-signature.guard.ts => gitea-signature.guard.ts} (83%) diff --git a/.env.sample b/.env.sample index 08120da..c63ec5b 100644 --- a/.env.sample +++ b/.env.sample @@ -2,8 +2,8 @@ POSTGRES_SCHEMA="public" DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=${POSTGRES_SCHEMA}" -# GitHub Webhook Configuration -GITHUB_WEBHOOK_SECRET="your_webhook_secret_here" +# Gitea Webhook Configuration +GITEA_WEBHOOK_SECRET="your_webhook_secret_here" # Kafka Configuration KAFKA_BROKERS=localhost:9092 diff --git a/GITHUB_WEBHOOK_SETUP.md b/docs/GITEA_WEBHOOK_SETUP.md similarity index 63% rename from GITHUB_WEBHOOK_SETUP.md rename to docs/GITEA_WEBHOOK_SETUP.md index d655bf2..551e47b 100644 --- a/GITHUB_WEBHOOK_SETUP.md +++ b/docs/GITEA_WEBHOOK_SETUP.md @@ -1,14 +1,14 @@ -# GitHub Webhook Integration Setup and Testing Guide +# Gitea Webhook Integration Setup and Testing Guide ## Overview -The Topcoder Review API includes a secure GitHub webhook integration that receives webhook events from GitHub repositories, validates them using HMAC-SHA256 signature verification, and stores them in the database for audit and future processing. +The Topcoder Review API includes a secure Gitea webhook integration that receives webhook events from Gitea repositories, validates them using HMAC-SHA256 signature verification, and stores them in the database for audit and future processing. ## Table of Contents 1. [Quick Start](#quick-start) 2. [Environment Setup](#environment-setup) -3. [GitHub Repository Configuration](#github-repository-configuration) +3. [Gitea Repository Configuration](#Gitea-repository-configuration) 4. [Local Development Setup](#local-development-setup) 5. [Testing the Integration](#testing-the-integration) 6. [API Endpoint Reference](#api-endpoint-reference) @@ -23,7 +23,7 @@ For immediate setup, follow these steps: 1. Generate a secure webhook secret 2. Configure environment variables -3. Set up GitHub webhook in repository settings +3. Set up Gitea webhook in repository settings 4. Test with a sample event ## Environment Setup @@ -34,22 +34,24 @@ Add the following environment variable to your application configuration: ```bash # .env file -GITHUB_WEBHOOK_SECRET=your_generated_secret_here +GITEA_WEBHOOK_SECRET=your_generated_secret_here ``` ### Generate Webhook Secret **Using OpenSSL:** + ```bash openssl rand -hex 32 ``` **Example Output:** + ``` a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456 ``` -⚠️ **Important:** Store this secret securely and use the same value in both your application environment and GitHub webhook configuration. +⚠️ **Important:** Store this secret securely and use the same value in both your application environment and Gitea webhook configuration. ### Database Setup @@ -59,11 +61,11 @@ The webhook integration requires the `gitWebhookLog` table. If not already creat npx prisma migrate dev ``` -## GitHub Repository Configuration +## Gitea Repository Configuration ### Step 1: Access Repository Settings -1. Navigate to your GitHub repository +1. Navigate to your Gitea repository 2. Click on the **Settings** tab (requires admin permissions) 3. In the left sidebar, click **Webhooks** 4. Click **Add webhook** @@ -73,25 +75,30 @@ npx prisma migrate dev #### Payload URL **Production/Staging Environment:** + ``` -https://your-api-domain.com/v6/review/webhooks/git +https://your-api-domain.com/v6/review/webhooks/gitea ``` **Development Environment:** + ``` -https://your-dev-domain.com/webhooks/git +https://your-dev-domain.com/webhooks/gitea ``` Note: The `/v6/review` prefix is only added in production when `NODE_ENV=production`. #### Content Type + - Select `application/json` #### Secret + - Enter the webhook secret you generated earlier -- This must exactly match your `GITHUB_WEBHOOK_SECRET` environment variable +- This must exactly match your `GITEA_WEBHOOK_SECRET` environment variable #### SSL Verification + - Keep **Enable SSL verification** checked (recommended for production) - For development with proper HTTPS setup, this should remain enabled @@ -100,10 +107,12 @@ Note: The `/v6/review` prefix is only added in production when `NODE_ENV=product Choose one of the following options: **Option A: Send Everything (Recommended for Testing)** -- Select "Send me everything" to receive all GitHub event types + +- Select "Send me everything" to receive all Gitea event types **Option B: Select Individual Events** Common events for development workflows: + - **Pushes** - Code pushes to repository - **Pull requests** - PR creation, updates, merges - **Issues** - Issue creation, updates, comments @@ -116,18 +125,20 @@ Common events for development workflows: 1. Ensure **Active** checkbox is checked 2. Click **Add webhook** -3. GitHub will automatically send a `ping` event to test the webhook +3. Gitea will automatically send a `ping` event to test the webhook ## Local Development Setup -Since GitHub webhooks require a publicly accessible URL, local development requires exposing your local server to the internet. +Since Gitea webhooks require a publicly accessible URL, local development requires exposing your local server to the internet. **Install ngrok:** + ```bash npm install -g ngrok ``` **Setup process:** + ```bash # 1. Start your local API server pnpm run start:dev @@ -138,16 +149,17 @@ ngrok http 3000 # 3. Copy the HTTPS URL from ngrok output # Example: https://abc123.ngrok.io -# 4. Use this URL in GitHub webhook settings -# https://abc123.ngrok.io/webhooks/git +# 4. Use this URL in Gitea webhook settings +# https://abc123.ngrok.io/webhooks/gitea ``` + ## Testing the Integration ### Manual Testing #### 1. Verify Initial Setup -After creating the webhook, GitHub automatically sends a `ping` event: +After creating the webhook, Gitea automatically sends a `ping` event: 1. Go to your repository's webhook settings 2. Click on your webhook @@ -157,6 +169,7 @@ After creating the webhook, GitHub automatically sends a `ping` event: #### 2. Trigger Test Events **Create a Push Event:** + ```bash # Make a small change echo "webhook test" >> test-webhook.txt @@ -166,16 +179,18 @@ git push origin main ``` **Create an Issue:** -1. Go to your repository on GitHub + +1. Go to your repository on Gitea 2. Click **Issues** tab 3. Click **New issue** 4. Create a test issue **Create a Pull Request:** + 1. Create a new branch: `git checkout -b test-webhook` 2. Make changes and commit 3. Push branch: `git push origin test-webhook` -4. Open pull request on GitHub +4. Open pull request on Gitea ### Testing with curl @@ -185,7 +200,7 @@ You can test the webhook endpoint directly using curl with proper signature gene #!/bin/bash # Configuration -WEBHOOK_URL="http://localhost:3000/webhooks/git" # Adjust for your environment +WEBHOOK_URL="http://localhost:3000/webhooks/gitea" # Adjust for your environment WEBHOOK_SECRET="your_webhook_secret_here" PAYLOAD='{"test": "data", "repository": {"name": "test-repo"}}' DELIVERY_ID="test-delivery-$(date +%s)" @@ -197,8 +212,8 @@ SIGNATURE="sha256=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SE # Send test webhook curl -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ - -H "X-GitHub-Event: $EVENT_TYPE" \ - -H "X-GitHub-Delivery: $DELIVERY_ID" \ + -H "X-Gitea-Event: $EVENT_TYPE" \ + -H "X-Gitea-Delivery: $DELIVERY_ID" \ -H "X-Hub-Signature-256: $SIGNATURE" \ -d "$PAYLOAD" ``` @@ -207,24 +222,28 @@ curl -X POST "$WEBHOOK_URL" \ ### Webhook Endpoint -**URL:** `POST /webhooks/git` (development) or `POST /v6/review/webhooks/git` (production) +**URL:** `POST /webhooks/gitea` (development) or `POST /v6/review/webhooks/gitea` (production) **Required Headers:** + - `Content-Type: application/json` -- `X-GitHub-Event: {event_type}` - GitHub event type (push, pull_request, etc.) -- `X-GitHub-Delivery: {delivery_id}` - Unique delivery identifier from GitHub +- `X-Gitea-Event: {event_type}` - Gitea event type (push, pull_request, etc.) +- `X-Gitea-Delivery: {delivery_id}` - Unique delivery identifier from Gitea - `X-Hub-Signature-256: sha256={signature}` - HMAC-SHA256 signature for verification **Request Body:** -- GitHub webhook payload (varies by event type) + +- Gitea webhook payload (varies by event type) **Response Codes:** + - `200 OK` - Webhook processed successfully - `400 Bad Request` - Missing required headers or invalid payload - `403 Forbidden` - Invalid signature verification - `500 Internal Server Error` - Processing error or configuration issue **Success Response:** + ```json { "success": true, @@ -233,13 +252,14 @@ curl -X POST "$WEBHOOK_URL" \ ``` **Error Response:** + ```json { "statusCode": 403, "message": "Invalid signature", "error": "Forbidden", "timestamp": "2024-01-01T00:00:00.000Z", - "path": "/webhooks/git" + "path": "/webhooks/gitea" } ``` @@ -254,7 +274,7 @@ CREATE TABLE "gitWebhookLog" ( "event" TEXT NOT NULL, "eventPayload" JSONB NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - + CONSTRAINT "gitWebhookLog_pkey" PRIMARY KEY ("id") ); @@ -267,30 +287,33 @@ CREATE INDEX "gitWebhookLog_createdAt_idx" ON "gitWebhookLog"("createdAt"); ### Query Examples **View recent webhook events:** + ```sql -SELECT +SELECT id, "eventId", event, "createdAt" -FROM "gitWebhookLog" -ORDER BY "createdAt" DESC +FROM "gitWebhookLog" +ORDER BY "createdAt" DESC LIMIT 10; ``` **Filter by event type:** + ```sql -SELECT * FROM "gitWebhookLog" -WHERE event = 'push' +SELECT * FROM "gitWebhookLog" +WHERE event = 'push' ORDER BY "createdAt" DESC; ``` **View specific webhook payload:** + ```sql -SELECT +SELECT event, "eventPayload" -FROM "gitWebhookLog" +FROM "gitWebhookLog" WHERE "eventId" = 'your-delivery-id'; ``` @@ -298,12 +321,12 @@ WHERE "eventId" = 'your-delivery-id'; ### Signature Verification -The webhook implementation uses GitHub's recommended security practices: +The webhook implementation uses Gitea's recommended security practices: 1. **HMAC-SHA256 Signature:** All incoming webhooks are verified using HMAC-SHA256 2. **Timing-Safe Comparison:** Uses `crypto.timingSafeEqual()` to prevent timing attacks 3. **Secret Protection:** Webhook secrets are stored as environment variables -4. **Header Validation:** Validates all required GitHub headers +4. **Header Validation:** Validates all required Gitea headers ### Best Practices @@ -315,7 +338,7 @@ The webhook implementation uses GitHub's recommended security practices: ### Environment Security -- Store `GITHUB_WEBHOOK_SECRET` securely using your deployment platform's secret management +- Store `GITEA_WEBHOOK_SECRET` securely using your deployment platform's secret management - Never commit secrets to version control - Use different secrets for different environments - Implement proper secret rotation procedures @@ -326,25 +349,11 @@ Key log messages to monitor: ``` # Successful webhook processing -[WebhookController] Successfully processed GitHub webhook +[WebhookController] Successfully processed Gitea webhook # Signature validation failures -[GitHubSignatureGuard] Invalid webhook signature for delivery +[GiteaSignatureGuard] Invalid webhook signature for delivery # Configuration errors -[GitHubSignatureGuard] GITHUB_WEBHOOK_SECRET environment variable is not configured -``` - -Example - -``` -[2025-08-02T01:06:48.312Z] [LOG] [Bootstrap] Server started on port 3000 -[2025-08-02T01:07:15.700Z] [LOG] [HttpRequest] {"type":"request","method":"POST","url":"/webhooks/git","ip":"::1","userAgent":"GitHub-Hookshot/4f8bd7a"} -[2025-08-02T01:07:15.739Z] [LOG] [GitHubSignatureGuard] Valid webhook signature verified for delivery 0722d0bc-6f3d-11f0-8a2d-6cc18966c098, event push -[2025-08-02T01:07:15.740Z] [LOG] [WebhookController] {"message":"Received GitHub webhook","delivery":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","timestamp":"2025-08-02T01:07:15.740Z"} -[2025-08-02T01:07:15.740Z] [LOG] [WebhookService] {"message":"Processing GitHub webhook event","eventId":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","timestamp":"2025-08-02T01:07:15.740Z"} -[2025-08-02T01:07:15.804Z] [LOG] [WebhookService] {"message":"Successfully stored webhook event","eventId":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","storedId":"9aHvEgDYPCYYnU","createdAt":"2025-08-02T01:07:15.747Z"} -[2025-08-02T01:07:15.804Z] [LOG] [WebhookService] {"message":"Event-specific processing placeholder","event":"push","payloadSize":7979} -[2025-08-02T01:07:15.804Z] [LOG] [WebhookController] {"message":"Successfully processed GitHub webhook","delivery":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","success":true} -[2025-08-02T01:07:15.804Z] [LOG] [HttpRequest] {"type":"response","statusCode":200,"method":"POST","url":"/webhooks/git","responseTime":"104ms"} +[GiteaSignatureGuard] Gitea_WEBHOOK_SECRET environment variable is not configured ``` diff --git a/KAFKA_SETUP.md b/docs/KAFKA_SETUP.md similarity index 100% rename from KAFKA_SETUP.md rename to docs/KAFKA_SETUP.md diff --git a/M2M_TOKEN_GUIDE.md b/docs/M2M_TOKEN_GUIDE.md similarity index 100% rename from M2M_TOKEN_GUIDE.md rename to docs/M2M_TOKEN_GUIDE.md diff --git a/prisma/migrations/20250802073602_github_webhook/migration.sql b/prisma/migrations/20250802073602_gitea_webhook/migration.sql similarity index 51% rename from prisma/migrations/20250802073602_github_webhook/migration.sql rename to prisma/migrations/20250802073602_gitea_webhook/migration.sql index 689b47a..d020522 100644 --- a/prisma/migrations/20250802073602_github_webhook/migration.sql +++ b/prisma/migrations/20250802073602_gitea_webhook/migration.sql @@ -1,13 +1,3 @@ --- DropForeignKey -ALTER TABLE "reviewApplication" DROP CONSTRAINT "reviewApplication_opportunityId_fkey"; - --- AlterTable -ALTER TABLE "reviewApplication" ALTER COLUMN "opportunityId" SET DATA TYPE TEXT, -ALTER COLUMN "updatedAt" DROP DEFAULT; - --- AlterTable -ALTER TABLE "reviewOpportunity" ALTER COLUMN "updatedAt" DROP DEFAULT; - -- CreateTable CREATE TABLE "gitWebhookLog" ( "id" VARCHAR(14) NOT NULL DEFAULT nanoid(), @@ -27,6 +17,3 @@ CREATE INDEX "gitWebhookLog_event_idx" ON "gitWebhookLog"("event"); -- CreateIndex CREATE INDEX "gitWebhookLog_createdAt_idx" ON "gitWebhookLog"("createdAt"); - --- AddForeignKey -ALTER TABLE "reviewApplication" ADD CONSTRAINT "reviewApplication_opportunityId_fkey" FOREIGN KEY ("opportunityId") REFERENCES "reviewOpportunity"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/api/api.module.ts b/src/api/api.module.ts index f866f55..e75677c 100644 --- a/src/api/api.module.ts +++ b/src/api/api.module.ts @@ -15,7 +15,7 @@ import { ReviewHistoryController } from './review-history/reviewHistory.controll import { ChallengeApiService } from 'src/shared/modules/global/challenge.service'; import { WebhookController } from './webhook/webhook.controller'; import { WebhookService } from './webhook/webhook.service'; -import { GitHubSignatureGuard } from '../shared/guards/github-signature.guard'; +import { GiteaSignatureGuard } from '../shared/guards/gitea-signature.guard'; @Module({ imports: [HttpModule, GlobalProvidersModule], @@ -36,7 +36,7 @@ import { GitHubSignatureGuard } from '../shared/guards/github-signature.guard'; ReviewApplicationService, ChallengeApiService, WebhookService, - GitHubSignatureGuard, + GiteaSignatureGuard, ], }) export class ApiModule {} diff --git a/src/api/webhook/interfaces/github-webhook.interface.ts b/src/api/webhook/interfaces/gitea-webhook.interface.ts similarity index 100% rename from src/api/webhook/interfaces/github-webhook.interface.ts rename to src/api/webhook/interfaces/gitea-webhook.interface.ts diff --git a/src/api/webhook/webhook.controller.ts b/src/api/webhook/webhook.controller.ts index ca0cd55..0d86561 100644 --- a/src/api/webhook/webhook.controller.ts +++ b/src/api/webhook/webhook.controller.ts @@ -13,7 +13,7 @@ import { WebhookEventDto, WebhookResponseDto, } from '../../dto/webhook-event.dto'; -import { GitHubSignatureGuard } from '../../shared/guards/github-signature.guard'; +import { GiteaSignatureGuard } from '../../shared/guards/gitea-signature.guard'; import { LoggerService } from '../../shared/modules/global/logger.service'; @ApiTags('Webhooks') @@ -23,22 +23,22 @@ export class WebhookController { constructor(private readonly webhookService: WebhookService) {} - @Post('git') + @Post('gitea') @HttpCode(HttpStatus.OK) - @UseGuards(GitHubSignatureGuard) + @UseGuards(GiteaSignatureGuard) @ApiOperation({ - summary: 'GitHub Webhook Endpoint', + summary: 'Gitea Webhook Endpoint', description: - 'Receives and processes GitHub webhook events with signature verification', + 'Receives and processes Gitea webhook events with signature verification', }) @ApiHeader({ - name: 'X-GitHub-Delivery', - description: 'GitHub delivery UUID', + name: 'X-Gitea-Delivery', + description: 'Gitea delivery UUID', required: true, }) @ApiHeader({ - name: 'X-GitHub-Event', - description: 'GitHub event type', + name: 'X-Gitea-Event', + description: 'Gitea event type', required: true, }) @ApiHeader({ @@ -63,14 +63,14 @@ export class WebhookController { status: 500, description: 'Internal Server Error - Processing failed', }) - async handleGitHubWebhook( + async handleGiteaWebhook( @Body() payload: any, - @Headers('x-github-delivery') delivery: string, - @Headers('x-github-event') event: string, + @Headers('x-gitea-delivery') delivery: string, + @Headers('x-gitea-event') event: string, ): Promise { try { this.logger.log({ - message: 'Received GitHub webhook', + message: 'Received Gitea webhook', delivery, event, timestamp: new Date().toISOString(), @@ -87,7 +87,7 @@ export class WebhookController { const result = await this.webhookService.processWebhook(webhookEvent); this.logger.log({ - message: 'Successfully processed GitHub webhook', + message: 'Successfully processed Gitea webhook', delivery, event, success: result.success, @@ -96,7 +96,7 @@ export class WebhookController { return result; } catch (error) { this.logger.error({ - message: 'Failed to process GitHub webhook', + message: 'Failed to process Gitea webhook', delivery, event, error: error.message, diff --git a/src/shared/guards/github-signature.guard.ts b/src/shared/guards/gitea-signature.guard.ts similarity index 83% rename from src/shared/guards/github-signature.guard.ts rename to src/shared/guards/gitea-signature.guard.ts index 5924880..5535489 100644 --- a/src/shared/guards/github-signature.guard.ts +++ b/src/shared/guards/gitea-signature.guard.ts @@ -11,20 +11,20 @@ import * as crypto from 'crypto'; import { LoggerService } from '../modules/global/logger.service'; @Injectable() -export class GitHubSignatureGuard implements CanActivate { - private readonly logger = LoggerService.forRoot('GitHubSignatureGuard'); +export class GiteaSignatureGuard implements CanActivate { + private readonly logger = LoggerService.forRoot('GiteaSignatureGuard'); canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const signature = request.headers['x-hub-signature-256'] as string; - const delivery = request.headers['x-github-delivery'] as string; - const event = request.headers['x-github-event'] as string; + const delivery = request.headers['x-gitea-delivery'] as string; + const event = request.headers['x-gitea-event'] as string; - // Check if GITHUB_WEBHOOK_SECRET is configured - const secret = process.env.GITHUB_WEBHOOK_SECRET; + // Check if GITEA_WEBHOOK_SECRET is configured + const secret = process.env.GITEA_WEBHOOK_SECRET; if (!secret) { this.logger.error( - 'GITHUB_WEBHOOK_SECRET environment variable is not configured', + 'GITEA_WEBHOOK_SECRET environment variable is not configured', ); throw new InternalServerErrorException('Webhook secret not configured'); } @@ -36,12 +36,12 @@ export class GitHubSignatureGuard implements CanActivate { } if (!delivery) { - this.logger.error('Missing X-GitHub-Delivery header'); + this.logger.error('Missing X-Gitea-Delivery header'); throw new BadRequestException('Missing delivery header'); } if (!event) { - this.logger.error('Missing X-GitHub-Event header'); + this.logger.error('Missing X-Gitea-Event header'); throw new BadRequestException('Missing event header'); } From 97adfa54de47e980a3f845e5fd2ee8fbc9cbb542 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Mon, 4 Aug 2025 09:10:39 +0300 Subject: [PATCH 4/4] reviewApplication fkey --- .../migration.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 prisma/migrations/20250804060038_reviewApplication_fkey/migration.sql diff --git a/prisma/migrations/20250804060038_reviewApplication_fkey/migration.sql b/prisma/migrations/20250804060038_reviewApplication_fkey/migration.sql new file mode 100644 index 0000000..2753162 --- /dev/null +++ b/prisma/migrations/20250804060038_reviewApplication_fkey/migration.sql @@ -0,0 +1,12 @@ +-- DropForeignKey +ALTER TABLE "reviewApplication" DROP CONSTRAINT "reviewApplication_opportunityId_fkey"; + +-- AlterTable +ALTER TABLE "reviewApplication" ALTER COLUMN "opportunityId" SET DATA TYPE TEXT, +ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "reviewOpportunity" ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- AddForeignKey +ALTER TABLE "reviewApplication" ADD CONSTRAINT "reviewApplication_opportunityId_fkey" FOREIGN KEY ("opportunityId") REFERENCES "reviewOpportunity"("id") ON DELETE CASCADE ON UPDATE CASCADE;