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).
| Parameter | Type | Description |
|---|---|---|
dto | CreateCategoryDto | { 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.
| Parameter | Type | Description |
|---|---|---|
categoryId | string | The slugified category ID |
dto | UpdateCategoryDto | Partial 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 / Service | Method | Purpose |
|---|---|---|
WorkOwnershipService | ensureAccess, ensureCanEdit | Authorization checks |
UserRepository | findById | Load the User entity for git operations |
DataGeneratorService | getCategoriesTags | Read current taxonomy from git data repo |
DataGeneratorService | saveCategories, saveTags, saveCollections | Write 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
| Scenario | Exception | HTTP Status |
|---|---|---|
| User not found | NotFoundException | 404 |
| Duplicate name on create | BadRequestException | 400 |
| Duplicate name on update | BadRequestException | 400 |
| Entity not found on update/delete | NotFoundException | 404 |
| Insufficient permissions | ForbiddenException (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.
Related Services
- Work Ownership -- provides authorization for all taxonomy operations
- Work Generation -- uses taxonomy during content generation
- Import System -- creates initial taxonomy during work imports