Skip to main content

Models Module

Product models represent specific product lines within a brand (e.g., "Air Max 90" under "Nike"). A model belongs to a single brand and a single business.

Domain Concepts

EntityDescription
ModelA named product line linked to a brand and business. Has an optional modelCode for internal referencing.
BrandParent entity — each model belongs to exactly one brand.

Architecture

Follows hexagonal architecture:

domain/
models-repository.domain.ts # IModelsRepository port + MODELS_REPOSITORY token
application/
models.service.ts # Use cases (CRUD + paginated list)
infrastructure/
models.repository.ts # Kysely adapter implementing IModelsRepository
interfaces/
models.controller.ts # REST controller with RBAC guards
dtos/
create-model.dto.ts # Validation + Swagger for creation
update-model.dto.ts # Partial update DTO
query/
paginate-models.query.ts # Pagination, search, sort query params

API Endpoints

All endpoints require authentication (Bearer token) and RBAC permission on PolicyResource.Model.

MethodPathPermissionDescription
POST/modelsCreateCreate a new model
GET/modelsReadList models (paginated, searchable)
GET/models/:idReadGet a model by ID
PATCH/models/:idUpdatePartially update a model
DELETE/models/:idDeleteDelete a model

Query Parameters (GET /models)

ParamTypeRequiredDescription
businessIdUUIDNoFilter by business
searchstringNoSearch by model name or brand name
sizenumberNoPage size
pagenumberNoPage number
orderBystringNoSort field (name)
orderasc / descNoSort direction

Create Model (POST /models)

{
"name": "Air Max 90",
"businessId": "uuid",
"brandId": "uuid",
"createdBy": "uuid",
"isActive": true,
"modelCode": "AM90-2026"
}

Update Model (PATCH /models/:id)

{
"name": "Air Max 95",
"updatedBy": "uuid",
"modelCode": "AM95-2026"
}

Design Decisions

  • Brand join on list: The findAll query joins with brand to include brandName in the response, avoiding N+1 queries.
  • Filter whitelist: Only whitelisted filter keys are accepted in the repository to prevent SQL injection through dynamic column names.
  • No soft delete: Models are hard-deleted. Use isActive: false to deactivate instead of deleting.