Saltar al contenido principal

Addon Tags Module

Overview​

The addon-tags module manages the many-to-many association between addon and tag entities in FlowPOS. Each addonTag record links one addon to one tag and carries an isActive flag for soft-enabling or disabling the association.


Architecture​

This module follows the Hexagonal Architecture (ports & adapters) pattern used across the backend:

addon-tags/
├── addon-tags.module.ts # NestJS module wiring
├── application/
│ └── addon-tags.service.ts # Use cases
├── domain/
│ └── addon-tags-repository.domain.ts # IAddonTagsRepository port + ADDON_TAGS_REPOSITORY token + sortableAddonTagKeys
├── infrastructure/
│ └── addon-tags.repository.ts # Kysely DB adapter
└── interfaces/
├── addon-tags.controller.ts # REST endpoints
├── dtos/
│ ├── create-addon-tag.dto.ts
│ └── update-addon-tag.dto.ts
└── query/
└── paginate-addon-tags.query.ts

Dependency flow​

interfaces → application → domain ← infrastructure
  • Domain owns the IAddonTagsRepository port, the ADDON_TAGS_REPOSITORY injection token, and sortableAddonTagKeys. No framework imports.
  • Application (service) depends only on the domain interface via @Inject(ADDON_TAGS_REPOSITORY).
  • Infrastructure (repository) implements the domain port via Kysely queries.
  • Interfaces (controller + DTOs) handles HTTP concerns and delegates to the service.

Dependency Injection​

The module uses Symbol-based token DI (consistent with all other modules):

// domain/addon-tags-repository.domain.ts
export const ADDON_TAGS_REPOSITORY = Symbol("ADDON_TAGS_REPOSITORY");
export interface IAddonTagsRepository { ... }

// application/addon-tags.service.ts
@Inject(ADDON_TAGS_REPOSITORY) private readonly addonTagsRepository: IAddonTagsRepository

// addon-tags.module.ts
{ provide: ADDON_TAGS_REPOSITORY, useClass: AddonTagsRepository }

Domain Concepts​

ConceptDescription
addonTagA join record linking an addon to a tag
addonIdFK to the addon table
tagIdFK to the tag table
isActiveSoft-enables or disables the association

Main Use Cases​

Use CaseMethodEndpoint
Create addon tagPOST/addon-tags
List all addon tagsGET/addon-tags
Get addon tag by IDGET/addon-tags/:id
Update addon tagPATCH/addon-tags/:id
Delete addon tagDELETE/addon-tags/:id

API Endpoints​

All endpoints require a valid Firebase ID token (Authorization: Bearer <token> or flowpos-id-token cookie) and the AddonTag RBAC permission enforced via RolesGuard + @PermissionResource(PolicyResource.AddonTag).

POST /addon-tags​

Creates a new addon-tag association.

Required fields: addonId, tagId, isActive, createdBy

{
"addonId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"tagId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isActive": true,
"createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

Returns 201 Created with the created record.


GET /addon-tags​

Returns a paginated list of all addon-tag associations.

Query parameters:

ParamTypeDescription
pagenumberPage number (default: 1)
sizenumberPage size (0 = no limit)

Note: orderBy and search parameters are accepted by the query class but have no effect — addonTag has no text-searchable columns and the sortable keys set is currently empty.


GET /addon-tags/:id​

Returns a single addon tag by UUID. Returns 404 Not Found if the record does not exist.


PATCH /addon-tags/:id​

Updates an existing addon-tag record. All fields except updatedBy are optional.

Required fields: updatedBy

{
"isActive": false,
"updatedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

DELETE /addon-tags/:id​

Deletes an addon-tag record. Returns 204 No Content on success.


Design Decisions​

Symbol-based DI token​

The ADDON_TAGS_REPOSITORY Symbol is defined in the domain layer and used by both the application service (consumer) and the module (provider). This follows the project-wide convention for proper dependency inversion — the application layer never imports from infrastructure.

sortableAddonTagKeys lives in the domain layer​

The sort-key tuple is defined in domain/addon-tags-repository.domain.ts and re-exported from the query class. When a sortable column is added to the schema, add it to the tuple in the domain file.

addonTag has no text-searchable columns. The search parameter is accepted to maintain API compatibility with the common query mixin but has no effect on the query.

Soft activation vs. deletion​

The isActive flag allows disabling a tag association without permanently removing the record. Use PATCH to toggle it; use DELETE only when the record must be permanently removed.

RBAC enforcement​

The controller applies @UseGuards(RolesGuard) and @PermissionResource(PolicyResource.AddonTag) at the class level, with @PermissionAction on each route method. This ensures all endpoints are protected by the CASL-based permission system.