Styles Module
Product styles represent classification groups within a brand (e.g., "IPA - India Pale Ale" under a beer brand, or "Slim Fit" under a clothing brand). A style belongs to a single brand and a single business.
Domain Concepts
| Entity | Description |
|---|---|
| Style | A named classification linked to a brand and business. Has an optional styleCode for internal referencing. |
| Brand | Parent entity — each style belongs to exactly one brand. |
Architecture
Follows hexagonal architecture:
domain/
styles-repository.domain.ts # IStylesRepository port + STYLES_REPOSITORY token
application/
styles.service.ts # Use cases (CRUD + paginated list)
infrastructure/
styles.repository.ts # Kysely adapter implementing IStylesRepository
interfaces/
styles.controller.ts # REST controller with RBAC guards
dtos/
create-style.dto.ts # Validation + Swagger for creation
update-style.dto.ts # Partial update DTO
query/
paginate-styles.query.ts # Pagination, search, sort query params
API Endpoints
All endpoints require authentication (Bearer token) and RBAC permission on PolicyResource.Style.
| Method | Path | Permission | Description |
|---|---|---|---|
POST | /styles | Create | Create a new style |
GET | /styles | Read | List styles (paginated, searchable) |
GET | /styles/:id | Read | Get a style by ID |
PATCH | /styles/:id | Update | Partially update a style |
DELETE | /styles/:id | Delete | Delete a style |
Query Parameters (GET /styles)
| Param | Type | Required | Description |
|---|---|---|---|
businessId | UUID | No | Filter by business |
search | string | No | Search by style name or brand name |
size | number | No | Page size |
page | number | No | Page number |
orderBy | string | No | Sort field (name) |
order | asc / desc | No | Sort direction |
Create Style (POST /styles)
{
"name": "IPA - India Pale Ale",
"businessId": "uuid",
"brandId": "uuid",
"createdBy": "uuid",
"isActive": true,
"styleCode": "STY-001"
}
Update Style (PATCH /styles/:id)
{
"name": "IPA - West Coast",
"updatedBy": "uuid",
"styleCode": "STY-002"
}
Design Decisions
- Brand join on list: The
findAllquery joins withbrandto includebrandNamein 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: Styles are hard-deleted. Use
isActive: falseto deactivate instead of deleting. - Data import: Styles can be bulk-imported via the data-import module. CSV columns:
name(required),code(optional),brandName(required). Brands must be imported first.