feat(api): outbound webhooks — server_down + player_banned events
Some checks failed
CI / backend-types (push) Successful in 10s
CI / frontend-build (push) Successful in 15s
CI / agent-tests (push) Failing after 30s
CI / integration (push) Has been skipped

Roadmap 'Webhook events': per-license outbound webhooks with HMAC-SHA256
signatures (X-Corrosion-Signature), 5s timeout, fire-and-forget (a webhook
failure never breaks the triggering action), last_delivery_at/last_status
tracked.

- migration 024_webhooks; Webhook entity (events as simple-array);
  WebhooksModule (@Global, exports WebhooksService) wired into app.module;
  CRUD controller (license-scoped, webhooks.view/manage).
- Hooked events: players.performAction ban -> 'player_banned';
  host-agent-consumer going-offline + staleness sweep -> 'server_down'.
- 'wipe_completed' event lands next (needs wipe status from the agent reply).

Backend tsc green. Migration applies on a fresh DB (Saturday).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-06-12 02:13:13 -04:00
parent 55c9893131
commit 0effaaf86c
10 changed files with 507 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
import { IsString, IsNotEmpty, IsUrl, IsArray, ArrayNotEmpty, IsOptional, MaxLength } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateWebhookDto {
@ApiProperty({ description: 'Human-readable label for this webhook', maxLength: 100 })
@IsString()
@IsNotEmpty()
@MaxLength(100)
name: string;
@ApiProperty({ description: 'HTTPS URL to POST events to' })
@IsUrl({ protocols: ['https', 'http'], require_tld: false })
url: string;
@ApiProperty({
description: 'Event keys to subscribe to',
example: ['player_banned', 'server_down'],
type: [String],
})
@IsArray()
@ArrayNotEmpty()
@IsString({ each: true })
events: string[];
@ApiPropertyOptional({
description: 'HMAC-SHA256 signing secret. Auto-generated if omitted.',
maxLength: 128,
})
@IsOptional()
@IsString()
@MaxLength(128)
secret?: string;
}

View File

@@ -0,0 +1,31 @@
import { IsString, IsUrl, IsArray, ArrayNotEmpty, IsOptional, IsBoolean, MaxLength } from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger';
export class UpdateWebhookDto {
@ApiPropertyOptional({ description: 'Human-readable label for this webhook', maxLength: 100 })
@IsOptional()
@IsString()
@MaxLength(100)
name?: string;
@ApiPropertyOptional({ description: 'HTTPS URL to POST events to' })
@IsOptional()
@IsUrl({ protocols: ['https', 'http'], require_tld: false })
url?: string;
@ApiPropertyOptional({
description: 'Event keys to subscribe to',
example: ['player_banned', 'server_down'],
type: [String],
})
@IsOptional()
@IsArray()
@ArrayNotEmpty()
@IsString({ each: true })
events?: string[];
@ApiPropertyOptional({ description: 'Enable or disable this webhook' })
@IsOptional()
@IsBoolean()
is_active?: boolean;
}