feat: migrate frontend to Astro + expand AI moderation + backend admin/runtime config

Frontend:
- migrate from Vite to Astro (astro.config.mjs, pages/, layouts/)
- add admin panel, settings page, command palette, error boundary
- refactor App.tsx, MascotChatbot, Sidebar, Header, DashboardLayout
- update API client, WebSocket, auth, dashboard features

Backend:
- add admin module and config routes
- refactor middlewares, Redis connection, WebSocket server/bridge
- add runtime config loader

Discord Gateway:
- refactor AI moderation: circuit breaker, concurrency limiter, fallback processor
- add media analysis client, Seaxng search, user profile learner
- add new drizzle migration

Shared:
- extend database schema, add new config fields
This commit is contained in:
asepharyana
2026-07-02 00:02:41 +07:00
parent d5c22a3959
commit d59b59a7a7
91 changed files with 11165 additions and 674 deletions
+310
View File
@@ -0,0 +1,310 @@
# Gateway Event Design — The Pulse of Discord
> *"Events are the heartbeat of a distributed system."*
> — Martin Fowler
---
## 🎯 Filosofi Gateway Events
Discord Gateway adalah **jantung event-driven** BETE:
1. **Single source of truth** — Events adalah satu-satunya cara data bergerak antar service
2. **At-least-once delivery** — Event bisa terkirim lebih dari sekali (idempotent consumers)
3. **Schema evolution** — Events punya versioning untuk backward compatibility
4. **Observable** — Setiap event tercatat untuk debugging dan audit
---
## 📦 Event Schema
### Envelope
```typescript
interface GatewayEvent<T = unknown> {
/** Event type identifier — lowercase, colon-separated */
type: string;
/** Event payload */
data: T;
/** ISO 8601 timestamp of when the event was created */
timestamp: string;
/** Unique event ID for deduplication */
eventId: string;
/** Source service name */
source: 'discord-gateway';
/** Event schema version */
version: number;
/** Optional correlation ID for tracing request flows */
correlationId?: string;
}
```
### Event Size Limits
| Limit | Value | Notes |
|-------|-------|-------|
| Max payload size | 256KB | Larger payloads → reference via URL |
| Max nesting depth | 5 levels | Prevent billion laughs attack |
| String max length | 100KB | Truncate with `... (truncated)` suffix |
---
## 📋 Event Catalog
### Message Events
```typescript
// discord:message:created
interface MessageCreatedEvent {
id: string;
channelId: string;
guildId: string;
author: {
id: string;
name: string;
discriminator: string;
avatar: string | null;
isBot: boolean;
};
content: string;
timestamp: string; // ISO 8601
editedTimestamp: string | null;
attachments: AttachmentInfo[];
replyTo?: string; // Parent message ID
}
// discord:message:updated
interface MessageUpdatedEvent {
id: string;
channelId: string;
content: string;
editedTimestamp: string;
}
// discord:message:deleted
interface MessageDeletedEvent {
id: string;
channelId: string;
guildId: string;
}
```
### Analysis Events
```typescript
// discord:message:analyzed
interface MessageAnalyzedEvent {
messageId: string;
status: 'pending' | 'complete' | 'error';
severity: 'safe' | 'low' | 'medium' | 'high' | 'critical';
categories: string[];
confidence: number; // 01
summary: string;
analyzedAt: string;
processingTimeMs: number;
}
```
### Voice Events
```typescript
// discord:voice:started
interface VoiceStartedEvent {
guildId: string;
channelId: string;
channelName: string;
startedAt: string;
participants: Array<{
userId: string;
userName: string;
}>;
}
// discord:voice:stopped
interface VoiceStoppedEvent {
guildId: string;
channelId: string;
duration: number; // seconds
segmentsCount: number;
}
// discord:voice:uploaded
interface VoiceUploadedEvent {
segmentId: string;
guildId: string;
channelId: string;
userId: string;
userName: string;
duration: number;
fileUrl: string;
fileSize: number;
timestamp: string;
}
```
### Attachment Events
```typescript
// discord:attachment:created
interface AttachmentCreatedEvent {
id: string;
messageId: string;
channelId: string;
url: string;
filename: string;
contentType: string;
size: number;
width?: number;
height?: number;
}
// discord:attachment:uploaded
interface AttachmentUploadedEvent {
id: string;
messageId: string;
storageUrl: string;
thumbnailUrl?: string;
fileSize: number;
processingTimeMs: number;
}
```
---
## 🔄 Event Lifecycle
```
┌──────────┐
│ Discord │ (messageCreate, voiceStateUpdate, etc.)
└────┬─────┘
┌──────────────┐
│ Discord.js │ (client events)
└────┬─────────┘
┌─────────────────────────────┐
│ Message Capture Controller │ (messageCapture.ts)
│ - Parse event │
│ - Store in database │
│ - Publish to Redis │
└────┬───────────────────────┘
┌─────────────────────┐
│ Redis Pub/Sub │ (channel: discord:message:created)
└────┬────────────────┘
├──────────────────────────────┐
↓ ↓
┌──────────────────┐ ┌──────────────────┐
│ Backend Service │ │ AI Moderation │
│ - Index message │ │ - Analyze text │
│ - Store in DB │ │ - Update status │
│ - Broadcast WS │ │ - Publish result │
└──────────────────┘ └──────────────────┘
```
---
## 🧪 Event Testing
```typescript
// Helper untuk generate test events
function createTestEvent<T>(type: string, data: T): GatewayEvent<T> {
return {
type,
data,
timestamp: new Date().toISOString(),
eventId: crypto.randomUUID(),
source: 'discord-gateway',
version: 1,
};
}
describe('MessageCreatedEvent', () => {
it('is properly formatted', () => {
const event = createTestEvent('discord:message:created', {
id: 'msg_1',
channelId: 'ch_1',
guildId: 'guild_1',
author: { id: 'user_1', name: 'Test', discriminator: '0000', avatar: null, isBot: false },
content: 'Hello world',
timestamp: new Date().toISOString(),
editedTimestamp: null,
attachments: [],
});
expect(event.type).toBe('discord:message:created');
expect(event.data.content).toBe('Hello world');
expect(event.source).toBe('discord-gateway');
expect(event.version).toBe(1);
});
});
```
---
## 📊 Event Performance Metrics
| Metric | Target | Alert |
|--------|--------|-------|
| Processing latency | <50ms p99 | >200ms |
| Event throughput | >1000/s | <100/s (unusual) |
| Redis publish latency | <5ms | >20ms |
| Event loss rate | 0% | >0.01% |
| Queue depth | <100 | >1000 |
---
## ⚠️ Anti-Patterns Events
### ❌ Processing-heavy event handlers
```typescript
// ❌ JANGAN — blocking event loop
eventBus.on('message:created', async (event) => {
const result = await expensiveAnalysis(event.data.content);
await db.save(result);
// Event handler for 100 msg/s = bottleneck
});
// ✅ Queue heavy work
eventBus.on('message:created', async (event) => {
await analysisQueue.add(event); // Worker processes async
});
```
### ❌ Missing idempotency
```typescript
// ❌ JANGAN — duplicate events create duplicate records
async function handleMessageCreated(event) {
await db.insert({ id: event.data.id, content: event.data.content });
// If event arrives twice → duplicate key error
}
// ✅ Idempotent: UPSERT
async function handleMessageCreated(event) {
await db.upsert({ id: event.data.id }, { content: event.data.content });
}
```
---
## 🔗 Referensi
| Sumber | Konsep |
|--------|--------|
| [Redis Pub/Sub](https://redis.io/docs/manual/pubsub/) | Event backbone |
| [CloudEvents](https://cloudevents.io/) | Event schema standard |
| [Discord Gateway](https://discord.com/developers/docs/topics/gateway) | Discord events |
---
*"Setiap event adalah denyut nadi — tanda bahwa sistem masih hidup dan berbicara."* ❄️🩵