Job Title API - Dokumentasi Teknis
Overview
Dokumentasi teknis untuk developer yang akan maintain atau develop lebih lanjut pada Job Title API module.
Arsitektur
Job Title module menggunakan pola Command & Query dengan struktur sebagai berikut:
job-title/
├── commands/ # Command handlers untuk write operations
│ ├── create.cmd.ts
│ ├── update.cmd.ts
│ └── soft-delete.cmd.ts
├── queries/ # Query handlers untuk read operations
│ ├── get.query.ts
│ └── get-by-id.query.ts
├── dto/ # Data Transfer Objects
├── helper/ # Helper (mapping)
├── validation/ # Validators
├── job-title.controller.ts
├── job-title.service.ts
├── job-title.module.ts
└── docs/ # Dokumentasi
Tech Stack
- Framework: NestJS
- Database: PostgreSQL dengan Prisma ORM
- Validation: class-validator, class-transformer
- API Documentation: Swagger/OpenAPI
- Auth: AuthGuard (JWT), tenant dari current user
- RLS: createTenantClient untuk tenant-scoped Prisma client
Database Schema
Table: job_titles
Berdasarkan Prisma model JobTitle:
| Column | Type | Keterangan |
|---|---|---|
| id | UUID | PK, default uuid() |
| tenant_id | UUID | NOT NULL |
| name | VARCHAR(100) | NOT NULL |
| description | VARCHAR(255) | NULL |
| family_id | UUID | NULL, FK → job_families |
| level_id | UUID | NULL, FK → job_levels |
| grade_id | UUID | NULL, FK → job_grades |
| is_active | BOOLEAN | DEFAULT true |
| created_by | UUID | NULL |
| created_by_name | VARCHAR(100) | NULL |
| created_at | TIMESTAMPTZ | DEFAULT now() |
| updated_by | UUID | NULL |
| updated_by_name | VARCHAR(100) | NULL |
| updated_at | TIMESTAMPTZ | NULL, @updatedAt |
| deleted_by | UUID | NULL |
| deleted_by_name | VARCHAR(100) | NULL |
| deleted_at | TIMESTAMPTZ | NULL |
Relasi:
JobTitle.family_id→JobFamily.idJobTitle.level_id→JobLevel.idJobTitle.grade_id→JobGrade.idJobPosition.title_id→JobTitle.id
Module Structure
1. Commands (Write Operations)
CreateJobTitleCommand
- File:
commands/create.cmd.ts - Handler:
JobTitleService.create() - Alur:
- createTenantClient(tenantId)
JobLevelValidation.validateNotFound(client, { id: dto.level_id })JobGradeValidation.validateNotFound(client, { id: dto.grade_id })JobFamilyValidation.validateNotFound(client, { id: dto.family_id })- Transaction:
jobTitle.create({ tenantId, name, levelId, familyId, gradeId, description, isActive: true }) - mapToDetailResponse(result)
- Validations: DTO + FK validation (level, grade, family harus ada).
UpdateJobTitleCommand
- File:
commands/update.cmd.ts - Alur: validateNotFound → validasi FK jika dikirim → update → mapToDetailResponse
SoftDeleteJobTitleCommand
- File:
commands/soft-delete.cmd.ts - Alur: validateNotFound → update deleted_at, deleted_by
2. Queries (Read Operations)
GetJobTitleQuery
- File:
queries/get.query.ts - Query params: search, is_active, order_by, order_direction, page, page_size
- Return: JobTitlePaginatedResponseDto
GetJobTitleByIdQuery
- File:
queries/get-by-id.query.ts - Validations: Jika tidak ketemu → NotFoundException
- Return: JobTitleDetailResponseDto
3. Validation
JobTitleValidation
- File:
validation/index.ts - validateExistence(client, where) — Jika record ada, throw BadRequestException, reason
job-title.is_exist - validateNotFound(client, where) — Jika record tidak ada, throw NotFoundException, reason
job-title.is_not_found
Controller Endpoints
- Base route:
job-titles - Endpoints:
- POST
/— create - GET
/— findAll (query params) - GET
/:id— findOne - PATCH
/:id— update - DELETE
/:id— softDelete
- POST
Error Handling
Reason Codes
job-title.is_not_found— Record tidak ditemukanjob-title.is_exist— Job title sudah ada (validateExistence)
Security & Tenant Isolation
- Semua akses database melalui createTenantClient(user.tenantId).
- Tenant ID dari CurrentUser (AuthGuard).