Skip to main content

WorkTaxonomyService Deep Dive

Overview

The WorkTaxonomyService manages the full lifecycle of work taxonomy entities: categories, tags, and collections. It provides CRUD operations that read from and write to the git-backed data repository, enforcing ownership permissions, duplicate detection, and slug-based ID generation for all taxonomy entities.

Architecture

This service bridges the API layer with the git-backed data storage. Every operation loads the current taxonomy state from the data repository (via DataGeneratorService), performs the mutation in memory, then persists the result back to git.

API Controller
|
v
WorkTaxonomyService
|
+-- WorkOwnershipService.ensureAccess/ensureCanEdit()
|
+-- UserRepository.findById()
|
+-- DataGeneratorService.getCategoriesTags() <-- read from git
|
+-- [in-memory mutation: create/update/delete]
|
+-- DataGeneratorService.saveCategories/saveTags/saveCollections() --> write to git
|
v
Response DTO

API Reference

Category Methods

getCategories(workId, userId)

Returns all categories for a work. Requires viewer access.

Returns: Promise<Category[]>

createCategory(workId, dto, userId)

Creates a new category. Requires editor access. Enforces unique names (case-insensitive).

ParameterTypeDescription
dtoCreateCategoryDto{ name, description?, icon_url?, priority? }

Returns: Promise<{ status: string; category: Category }>

updateCategory(workId, categoryId, dto, userId)

Updates an existing category by ID. Requires editor access.

ParameterTypeDescription
categoryIdstringThe slugified category ID
dtoUpdateCategoryDtoPartial update fields

Returns: Promise<{ status: string; category: Category }>

deleteCategory(workId, categoryId, userId)

Removes a category by ID. Requires editor access.

Returns: Promise<{ status: string; message: string }>

Tag Methods

getTags(workId, userId)

Returns all tags. Requires viewer access.

Returns: Promise<Tag[]>

createTag(workId, dto, userId) / updateTag(...) / deleteTag(...)

Same CRUD pattern as categories. Tags have only name as a mutable field.

Collection Methods

getCollections(workId, userId) / createCollection(...) / updateCollection(...) / deleteCollection(...)

Same CRUD pattern as categories. Collections support name, description, icon_url, and priority.

Implementation Details

ID Generation

All taxonomy entities use slugified names as their IDs via slugifyText(name). This ensures IDs are URL-friendly and match item references (items reference categories/tags by slugified ID).

Duplicate Detection

Before creating any entity, the service normalizes the name to lowercase and checks for existing entries with the same normalized name. During updates, it also checks for conflicts while excluding the entity being updated.

Partial Updates

Update methods use conditional spreading to apply only provided fields:

const updatedCategory: Category = {
...existingCategory,
...(dto.name && { name: dto.name.trim() }),
...(dto.description !== undefined && { description: dto.description?.trim() })
};

This pattern allows setting fields to undefined (by passing undefined) or removing them while preserving unmentioned fields.

Input Sanitization

All string inputs are trimmed. Names and descriptions pass through the sanitizeName and sanitizeDescription transform decorators in their respective DTOs before reaching the service.

Database Interactions

Repository / ServiceMethodPurpose
WorkOwnershipServiceensureAccess, ensureCanEditAuthorization checks
UserRepositoryfindByIdLoad the User entity for git operations
DataGeneratorServicegetCategoriesTagsRead current taxonomy from git data repo
DataGeneratorServicesaveCategories, saveTags, saveCollectionsWrite updated taxonomy to git data repo

Event System

This service does not directly emit events. Changes to taxonomy are persisted through DataGeneratorService, which handles git commits.

Error Handling

ScenarioExceptionHTTP Status
User not foundNotFoundException404
Duplicate name on createBadRequestException400
Duplicate name on updateBadRequestException400
Entity not found on update/deleteNotFoundException404
Insufficient permissionsForbiddenException (via ownership service)403

Usage Examples

// List all categories
const categories = await taxonomyService.getCategories(workId, userId);

// Create a new category
const result = await taxonomyService.createCategory(
workId,
{ name: 'Machine Learning', description: 'ML tools and frameworks', priority: 1 },
userId
);
// result.category.id === 'machine-learning'

// Create a tag
const tagResult = await taxonomyService.createTag(workId, { name: 'Open Source' }, userId);
// tagResult.tag.id === 'open-source'

// Update a category
await taxonomyService.updateCategory(
workId,
'machine-learning',
{ description: 'Updated description for ML tools' },
userId
);

// Delete a tag
await taxonomyService.deleteTag(workId, 'deprecated-tag', userId);

Configuration

No external configuration is required. Taxonomy entity constraints (max lengths, allowed characters) are enforced at the DTO validation layer.