Menyelesaikan object user dan init base

This commit is contained in:
Mr. Rayyan
2026-07-30 22:47:18 +07:00
parent 3efac50f39
commit fdbfb34842
128 changed files with 6785 additions and 3156 deletions
+23
View File
@@ -0,0 +1,23 @@
core/
Penjelasan singkat:
Folder core berisi fasilitas framework-level yang dipakai lintas modul. Jangan tempatkan business logic domain di sini. Core menyediakan building block seperti:
- Event bus / dispatcher
- Global middleware (request-id, correlation id)
- Global exception filters
- Logger (pino wrapper)
- Queue foundation (struktur jobs/workers/processors)
Contoh file yang ada di folder ini:
- src/core/events/event.interface.ts
- src/core/events/event-publisher.ts
- src/core/logger/logger.service.ts
- src/core/middleware/request-id.middleware.ts
- src/core/exceptions/http-exception.filter.ts
- src/core/queue/*
Panduan singkat:
- Semua event domain didefinisikan di core/events. Module boleh publish/subscribe.
- Logger tersedia sebagai utilitas global (injectable or exported instance).
- Queue hanya menyediakan pondasi; integrasi spesifik (BullMQ, Redis) dilakukan pada fase infrastructure atau worker.
+41
View File
@@ -0,0 +1,41 @@
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(
private readonly jwtService: JwtService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
const authHeader = request.headers.authorization;
if (!authHeader) {
throw new UnauthorizedException('Authorization header is missing.');
}
const [type, token] = authHeader.split(' ');
if (type !== 'Bearer' || !token) {
throw new UnauthorizedException('Invalid authorization header.');
}
try {
const payload = await this.jwtService.verifyAsync(token);
request['user'] = payload;
return true;
} catch {
throw new UnauthorizedException('Invalid or expired token.');
}
}
}
@@ -0,0 +1,12 @@
import { Request } from 'express';
export interface JwtPayload {
sub: string;
email: string;
role: string;
permissions: string[];
}
export interface AuthenticatedRequest extends Request {
user: JwtPayload;
}
+11
View File
@@ -0,0 +1,11 @@
export default () => ({
database: {
url:
`postgresql://` +
`${process.env.DB_USER}:` +
`${process.env.DB_PASSWORD}@` +
`${process.env.SERVER_HOST}:` +
`${process.env.DB_PORT}/` +
`${process.env.DB_NAME}?schema=public`
}
});
+14
View File
@@ -0,0 +1,14 @@
core/events/
Penjelasan:
Folder ini berisi pondasi untuk domain events: definisi event, publisher, dispatcher, dan handler.
Isi yang direkomendasikan:
- event.interface.ts (DomainEvent)
- event-publisher.ts (interface dan implementasi sederhana)
- event-dispatcher.ts (dispatcher untuk publish/dispatch event)
- event-handler.ts (type alias untuk handler)
Aturan:
- Event hanya bermakna untuk komunikasi lintas module atau untuk memisahkan side-effect.
- Hindari business logic kompleks di handler; handler sebaiknya mendelegasikan ke application service atau job queue.
+13
View File
@@ -0,0 +1,13 @@
import { DomainEvent } from './event.interface';
export class EventDispatcher {
private publisher = new (require('./event-publisher').InMemoryEventPublisher)();
register(handler: (e: DomainEvent) => Promise<void>) {
this.publisher.register(handler);
}
async dispatch(event: DomainEvent) {
await this.publisher.publish(event);
}
}
+3
View File
@@ -0,0 +1,3 @@
import { DomainEvent } from './event.interface';
export type EventHandler = (event: DomainEvent) => Promise<void>;
+17
View File
@@ -0,0 +1,17 @@
import { DomainEvent } from './event.interface';
export interface EventPublisher {
publish(event: DomainEvent): Promise<void>;
}
export class InMemoryEventPublisher implements EventPublisher {
private handlers: Array<(e: DomainEvent) => Promise<void>> = [];
register(handler: (e: DomainEvent) => Promise<void>) {
this.handlers.push(handler);
}
async publish(event: DomainEvent) {
await Promise.all(this.handlers.map((h) => h(event)));
}
}
+5
View File
@@ -0,0 +1,5 @@
export interface DomainEvent {
readonly name: string;
readonly payload: any;
readonly occurredAt: Date;
}
+12
View File
@@ -0,0 +1,12 @@
core/exceptions/
Penjelasan:
Berisi global exception filters dan custom exception classes.
Isi yang direkomendasikan:
- http-exception.filter.ts
- domain-exception.ts (custom domain exception base)
Aturan:
- Exception filter mengubah exception menjadi response standar API.
- Gunakan kode error konsisten (error code constant) agar client dapat menangani error.
@@ -0,0 +1,18 @@
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status = exception.getStatus ? exception.getStatus() : 500;
response.status(status).json({
success: false,
error: {
code: exception.code || 'INTERNAL_ERROR',
message: exception.message || 'Internal server error',
},
});
}
}
+12
View File
@@ -0,0 +1,12 @@
core/logger/
Penjelasan:
Folder ini berisi wrapper logger (mis. pino) dan utilitas logging global.
Isi yang direkomendasikan:
- logger.service.ts / logger.instance.ts (exported pino instance or injectable wrapper)
- logger.interceptor.ts (optional HTTP logging interceptor)
Aturan:
- Gunakan structured logging (json) dengan field requestId, module, level.
- Jangan menambahkan business logic ke logger.
+5
View File
@@ -0,0 +1,5 @@
import pino from 'pino';
export const logger = pino({
level: process.env.LOG_LEVEL || 'info',
});
+14
View File
@@ -0,0 +1,14 @@
core/middleware/
Penjelasan:
Berisi middleware global seperti request-id, correlation id, timing, rate-limit glue (middleware only, implementasi rate-limit ada di infra).
Isi yang direkomendasikan:
- request-id.middleware.ts
- correlation-id.middleware.ts
- timing.middleware.ts
Aturan:
- Middleware harus ringan dan synchronous jika memungkinkan.
- Jangan letakkan business logic di middleware.
- Middleware global didaftarkan di main.ts atau CoreModule.
@@ -0,0 +1,11 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
req.requestId = req.headers['x-request-id'] || uuidv4();
res.setHeader('X-Request-ID', req.requestId);
next();
}
}
+8
View File
@@ -0,0 +1,8 @@
Queue layer placeholder.
Structure:
- jobs/: job definitions
- workers/: worker implementations
- processors/: processors that execute jobs
Queue integration (e.g. BullMQ) should be added in infrastructure phase.
View File
+11
View File
@@ -0,0 +1,11 @@
core/queue/jobs/
Penjelasan:
Tempat definisi job (job payload shape, job name constants). Job definitions hanya mendefinisikan contract untuk job.
Contoh:
- media-scan.job.ts
- thumbnail.job.ts
Aturan:
- Job definition tidak mengeksekusi logic; worker/processors yang mengeksekusi.
View File
+12
View File
@@ -0,0 +1,12 @@
core/queue/processors/
Penjelasan:
Processor berisi fungsi yang mengeksekusi pekerjaan sebenarnya (CPU/IO heavy). Processor harus idempotent dan memiliki retry strategy.
Contoh:
- media-scan.processor.ts
- thumbnail.processor.ts
Aturan:
- Processor harus bisa dijalankan terpisah (CLI/worker process).
- Jangan panggil controller dari processor; gunakan service/infrastructure.
View File
+12
View File
@@ -0,0 +1,12 @@
core/queue/workers/
Penjelasan:
Worker mengkonsumsi job dari queue dan memanggil processor yang sesuai.
Contoh:
- media-scan.worker.ts
- thumbnail.worker.ts
Aturan:
- Worker hanya fokus pada retry/failure handling dan delegasi ke processor.
- Implementasi queue (BullMQ, Bee-Queue) ditaruh di infra/worker deployment.