Category Service
The category system manages hierarchical content classification through a Git-backed YAML storage layer. Categories are stored in .content/categories.yml and synchronized with a remote GitHub repository. The system supports CRUD operations, translations, backups, and background sync with retry logic.
Architecture Overview
Category management is split across two service classes following the Single Responsibility Principle:
| Service | File | Responsibility |
|---|---|---|
| CategoryFileService | lib/services/category-file.service.ts | File I/O operations (read/write YAML) |
| CategoryGitService | lib/services/category-git.service.ts | Full CRUD with Git commit/push and sync |
Both services operate on the same YAML files but CategoryGitService adds version control.
CategoryData Type
Categories follow this structure defined in lib/types/category.ts:
interface CategoryData {
id: string;
name: string;
}
CategoryFileService
The CategoryFileService at lib/services/category-file.service.ts handles direct file operations on categories.
Reading Categories
const { categoryFileService } = require('@/lib/services/category-file.service');
// Read all categories (English)
const categories = await categoryFileService.readCategories();
// Read categories with translations
const frenchCategories = await categoryFileService.readCategories('fr');
The service reads from categories.yml in the content directory. When a language code is provided, it looks for a translation file like categories.fr.yml and merges translations by matching id fields.
Writing Categories
await categoryFileService.writeCategories(updatedCategories);
Categories are serialized to YAML with controlled formatting:
const yamlContent = yaml.stringify(categories, {
indent: 2,
lineWidth: 0, // Disable line wrapping
minContentWidth: 0,
});
Backup
const backupPath = await categoryFileService.createBackup();
// Returns: .content/categories.backup.2026-01-15T10-30-00-000Z.yml
Singleton Access
The module exports a singleton instance:
export const categoryFileService = new CategoryFileService();
CategoryGitService
The CategoryGitService at lib/services/category-git.service.ts provides full CRUD operations with Git integration using isomorphic-git.
Initialization
import { createCategoryGitService } from '@/lib/services/category-git.service';
const service = await createCategoryGitService({
owner: 'your-org',
repo: 'your-data-repo',
token: process.env.GITHUB_TOKEN,
branch: 'main',
}, './.content');
Initialization performs:
- Creates the data directory if missing
- Clones or pulls the remote repository
- Ensures
categories.ymlexists
CRUD Operations
Create:
const newCategory = await service.createCategory({
id: 'productivity',
name: 'Productivity',
});
Creates a new category after checking for duplicate IDs and names.
Read:
const allCategories = await service.readCategories();
Update:
const updated = await service.updateCategory({
id: 'productivity',
name: 'Productivity Tools',
});
Delete:
await service.deleteCategory('productivity');
All write operations automatically commit and push to the remote repository.
Git Operations Flow
When a write operation occurs:
- Categories are written to the local YAML file
- The file is staged with
git add - A commit is created with timestamp:
Update categories - 2026-01-15T10:30:00.000Z - Changes are pushed to the remote repository
If Git operations fail, the local file is still saved and changes are queued for background sync.
Background Sync and Retry
When a push fails, the service schedules background retries:
private scheduleBackgroundSync(): void {
setTimeout(() => {
this.performBackgroundSync();
}, 30000); // Initial retry after 30 seconds
}
The retry uses exponential backoff with a maximum of 3 retries:
- Attempt 1: 30 seconds
- Attempt 2: 60 seconds
- Attempt 3: 120 seconds
- Maximum delay: 5 minutes
Sync Status
const status = await service.getSyncStatus();
// Returns:
// {
// hasPendingChanges: boolean;
// syncInProgress: boolean;
// lastSyncAttempt?: string;
// retryCount?: number;
// }
Repository Status
const repoStatus = await service.getStatus();
// Returns:
// {
// repoUrl: string;
// branch: string;
// lastSync: string;
// categoriesCount: number;
// }
Cleanup
When shutting down, call cleanup() to stop retry timers:
service.cleanup();
Translation Support
Categories support multi-language translations through separate YAML files:
categories.yml-- English (default)categories.fr.yml-- French translationscategories.es.yml-- Spanish translations
Translation files contain the same id fields with localized name values. The CategoryFileService.readCategories(lang) method merges translations automatically.
API Integration
Categories are typically managed through admin API routes:
// GET /api/admin/categories
export async function GET() {
const categories = await categoryFileService.readCategories();
return Response.json(categories);
}
// POST /api/admin/categories
export async function POST(request: Request) {
const data = await request.json();
const category = await gitService.createCategory(data);
return Response.json(category, { status: 201 });
}
// PUT /api/admin/categories/[id]
export async function PUT(request: Request) {
const data = await request.json();
const updated = await gitService.updateCategory(data);
return Response.json(updated);
}
// DELETE /api/admin/categories/[id]
export async function DELETE(request: Request, { params }) {
await gitService.deleteCategory(params.id);
return Response.json({ success: true });
}
Related Files
| File | Description |
|---|---|
lib/services/category-file.service.ts | File-based category I/O |
lib/services/category-git.service.ts | Git-backed category CRUD |
lib/types/category.ts | Category type definitions |
.content/categories.yml | Category data storage |