Menyelesaikan object user dan init base
This commit is contained in:
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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`
|
||||
}
|
||||
});
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { DomainEvent } from './event.interface';
|
||||
|
||||
export type EventHandler = (event: DomainEvent) => Promise<void>;
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface DomainEvent {
|
||||
readonly name: string;
|
||||
readonly payload: any;
|
||||
readonly occurredAt: Date;
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
import pino from 'pino';
|
||||
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user