Files
GMW/services/backend/ARCHITECTURE.md
T
asepharyana 2ecba1691b
Deploy to VPS / deploy (push) Failing after 34s
docs: update all markdown files, switch remote to Gitea
- Update root README.md: remove Leptos/WASM/SQLite, document Next.js
- Update frontend README.md: replace create-next-app boilerplate
- Update frontend AGENTS.md: project-specific overview
- Update CLAUDE.md: remove all auth references
- Update backend ARCHITECTURE.md: remove adminAuth from middleware list
- Switch git remote from gitlab to gitea (origin → gitea)
2026-07-26 11:38:53 +07:00

8.9 KiB

Backend Service Architecture Map

Directory Structure

services/backend/
├── src/
│   ├── shared/                          # Shared infrastructure (no business logic)
│   │   ├── config/
│   │   │   └── index.ts                 # Zod-validated environment config
│   │   ├── database/
│   │   │   └── index.ts                 # Drizzle ORM initialization & connection pool
│   │   ├── errors/
│   │   │   └── index.ts                 # Custom error classes (AppError, ValidationError, etc.)
│   │   ├── logger/
│   │   │   └── index.ts                 # Pino logger with child context support
│   │   ├── middlewares/
│   │   │   └── index.ts                 # Express middleware (errorHandler, asyncHandler, rateLimit)
│   │   └── utils/                       # Utility functions (placeholder)
│   │
│   ├── modules/                         # Feature modules (Modular MVC pattern)
│   │   ├── messages/
│   │   │   ├── messages.schema.ts       # Zod validation schemas (MessageQuery, MessageCreate, MessageUpdate)
│   │   │   ├── messages.repository.ts   # Database operations (findMany, findById, create, update, delete)
│   │   │   ├── messages.service.ts      # Business logic (validation, orchestration)
│   │   │   ├── messages.controller.ts   # Request handlers (parse → service → response)
│   │   │   └── routes/
│   │   │       └── index.ts             # Express router (GET /api/messages, etc.)
│   │   │
│   │   ├── analytics/
│   │   │   ├── analytics.schema.ts
│   │   │   ├── analytics.repository.ts
│   │   │   ├── analytics.service.ts
│   │   │   ├── analytics.controller.ts
│   │   │   └── routes/
│   │   │       └── index.ts
│   │   │
│   │   ├── media/
│   │   │   ├── media.service.ts
│   │   │   └── routes/
│   │   │       └── index.ts
│   │   │
│   │   ├── voice/
│   │   │   ├── voice.service.ts
│   │   │   └── routes/
│   │   │       └── index.ts
│   │   │
│   │   └── health/
│   │       ├── health.schema.ts
│   │       ├── health.repository.ts
│   │       ├── health.service.ts
│   │       ├── health.controller.ts
│   │       └── routes/
│   │           └── index.ts
│   │
│   ├── http/
│   │   ├── app.ts                       # Express app factory (middleware, routes, error handler)
│   │   └── server.ts                    # HTTP server startup (port binding, graceful shutdown)
│   │
│   ├── ws/                              # WebSocket server (placeholder for real-time updates)
│   │   └── server.ts                    # Redis pub/sub listener for Discord Gateway events
│   │
│   └── index.ts                         # Entry point (main function, signal handlers)
│
├── package.json                         # Backend dependencies
├── tsconfig.json                        # TypeScript configuration
└── README.md                            # Backend-specific documentation

Layer Separation

1. Controller Layer

File: modules/*/[module].controller.ts Responsibility: HTTP request handling only

  • Parse request (query, params, body)
  • Validate using Zod schemas
  • Call service methods
  • Return HTTP response (200, 400, 404, 500)
  • No database calls
  • No business logic

Example:

export function handleListMessages(req: Request, res: Response, next: NextFunction) {
  return asyncHandler(async (req: Request, res: Response) => {
    const query = messageQuerySchema.parse(req.query);
    const result = await messagesService.listMessages(query);
    res.json(result);
  })(req, res, next);
}

2. Service Layer

File: modules/*/[module].service.ts Responsibility: Business logic and orchestration

  • Validate input (throw ValidationError if invalid)
  • Orchestrate repository calls
  • Apply business rules
  • Handle cross-cutting concerns (auth, permissions)
  • No database calls directly
  • No HTTP request/response handling

Example:

async listMessages(query: MessageQuery) {
  if (!query.channelId && !query.guildId) {
    throw new ValidationError("Either channelId or guildId is required");
  }
  return messagesRepository.findMany(query);
}

3. Repository Layer

File: modules/*/[module].repository.ts Responsibility: All database operations

  • Execute Drizzle ORM queries
  • Handle database errors
  • Return raw data (no transformation)
  • No business logic
  • No HTTP handling

Example:

async findMany(query: MessageQuery) {
  const db = getDatabase();
  return db.select().from(messagesTable).where(...).limit(query.limit);
}

4. Schema Layer

File: modules/*/[module].schema.ts Responsibility: Zod validation schemas

  • Define request/response types
  • Validate at controller entry point
  • Export TypeScript types

Example:

export const messageQuerySchema = z.object({
  channelId: z.string().optional(),
  limit: z.coerce.number().int().positive().default(50),
});

Module Responsibilities

Module Purpose Routes
messages Text message storage & retrieval GET /api/messages, GET /api/messages/:channelId
analytics Moderation statistics & trends GET /api/analytics/overview, /daily-trend, /hourly-stats
media Media file management GET /api/media/list, POST /api/media/upload
voice Voice recording management GET /api/voice/recordings, POST /api/voice/connect
health Service health checks GET /api/health

Data Flow

Request Flow (HTTP)

Client Request
    ↓
Express Router (routes/index.ts)
    ↓
Controller (parse request, validate schema)
    ↓
Service (business logic, validation)
    ↓
Repository (database query)
    ↓
Database (PostgreSQL)
    ↓
Repository (return data)
    ↓
Service (transform/orchestrate)
    ↓
Controller (format response)
    ↓
Client Response

Event Flow (WebSocket - Future)

Discord Gateway (publishes event)
    ↓
Redis pub/sub
    ↓
Backend WebSocket Server (ws/server.ts)
    ↓
Broadcast to connected clients
    ↓
Frontend (receives real-time update)

Dependency Rules

Allowed

  • Controller → Service
  • Service → Repository
  • Service → Config
  • Service → Logger
  • Repository → Database
  • Any layer → Errors, Logger, Config

Forbidden

  • Repository → Service (data flows up, not down)
  • Repository → Controller
  • Service → HTTP (no req/res in service)
  • Controller → Database (must go through service)
  • Cross-module repository imports (each module owns its data)

Error Handling

All errors inherit from AppError with code and statusCode:

throw new ValidationError("Invalid input", { field: "error" });  // 400
throw new NotFoundError("Message not found");                     // 404
throw new UnauthorizedError("Invalid password");                  // 401
throw new ForbiddenError("Access denied");                        // 403
throw new AppError("Custom error", "CUSTOM_CODE", 500);           // 500

Configuration

All config via environment variables (.env), validated with Zod in shared/config/index.ts:

# Server
WEBSERVER_PORT=3001
NODE_ENV=development
LOG_LEVEL=info

# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/discord_moderation
# OR
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=discord_moderation
DATABASE_USER=postgres
DATABASE_PASSWORD=secret

# Redis (optional, for pub/sub)
REDIS_URL=redis://localhost:6379

# Discord
MONITOR_GUILD_ID=123456789

# Admin
ADMIN_PASSWORD=secret123

Testing Strategy

Each module should have tests:

  • messages.repository.test.ts — Database query tests
  • messages.service.test.ts — Business logic tests
  • messages.controller.test.ts — HTTP handler tests

Use Vitest with mocked database and services.

Next Steps

  1. Migrate Drizzle schema from src/database/schema.ts to services/backend/src/shared/database/schema.ts
  2. Implement repository queries for each module using Drizzle ORM
  3. Add WebSocket server in src/ws/server.ts with Redis pub/sub listener
  4. Create Discord Gateway service in services/discord-gateway/ (separate microservice)
  5. Add Docker & CI/CD for multi-service deployment
  6. Write integration tests for full request flow

Circular Dependency Check

No circular dependencies detected:

  • Modules are independent (each owns its data)
  • Layers flow upward only (Repository → Service → Controller)
  • Shared infrastructure has no dependencies on modules
  • Cross-module communication via events (Redis pub/sub), not direct imports