Design
Documentation for Modules
Platform Settings — Design Document
Aggregator module that provides administrative CRUD pages for domain core entities (tax classes, brands, categories, tags).
Architecture
Platform Settings is an aggregator module — it owns no entities of its own. Instead, it exposes CRUD pages that delegate all data operations to Domain Core lib services.
platform-settings (Business Module)
├── Consumes: @libs-core/fiscal → TaxClass CRUD
├── Consumes: @libs-core/brand-registry → Brand CRUD
├── Consumes: @libs-core/taxonomy → Category CRUD, Tag CRUD
├── Consumes: @libs-core/ui-framework → Shell components, form controls
└── Consumes: @libs-core/module-base → CRUD composables, module contract
Key Characteristic: No Own State
Platform Settings does not have:
- Its own entities or models (uses domain core models directly)
- NGXS state (delegates to domain core services)
- Domain events (domain core libs publish their own events)
- Public actions (no cross-module operations needed)
- Embeddable components (admin-only module)
This makes it an ideal reference for aggregator-pattern modules.
Dependencies
| Dependency | Import Path | Usage |
|---|---|---|
| fiscal | @libs-core/fiscal |
TaxClass model, FiscalApiService, TaxClassCreated/Updated/Deleted events |
| brand-registry | @libs-core/brand-registry |
Brand model, BrandApiService, BrandCreated/Updated/Deleted events |
| taxonomy | @libs-core/taxonomy |
Category/Tag models, TaxonomyCategoryApiService, TaxonomyTagApiService, events |
| module-base | @libs-core/module-base |
BrowserModuleContract, ModuleDecorator, useCrudList, useCrudForm, useCrudDetail |
| ui-framework | @libs-core/ui-framework |
GgsCrudListShellComponent, GgsCrudFormShellComponent, GgsFilterPanelComponent |
| security | @libs-core/security |
authGuard, contextGuard, permissionGuard |
| log-system | @libs-core/log-system |
LoggerService |
Navigation: Hierarchical
Platform Settings uses hierarchical navigation — a parent "Settings" group with 4 children. The parent item has no route of its own; it serves only as a grouping container.
navigation: {
menuItems: [
{
id: 'settings-parent',
label: 'Settings',
icon: 'cog',
order: 900,
permissions: ['settings:read'],
children: [
{ id: 'settings-tax', label: 'Tax Classes', icon: 'percent', route: 'tax-classes', order: 1, permissions: ['settings:fiscal:manage'] },
{ id: 'settings-brands', label: 'Brands', icon: 'tag', route: 'brands', order: 2, permissions: ['settings:brands:manage'] },
{ id: 'settings-categories', label: 'Categories', icon: 'sitemap', route: 'categories', order: 3, permissions: ['settings:taxonomy:manage'] },
{ id: 'settings-tags', label: 'Tags', icon: 'tags', route: 'tags', order: 4, permissions: ['settings:taxonomy:manage'] },
],
},
],
},
Sidebar rendering:
▸ Settings
├── Tax Classes
├── Brands
├── Categories
└── Tags
Permissions
| Permission ID | Description |
|---|---|
settings:read |
View any settings page |
settings:fiscal:manage |
Create, edit, delete tax classes |
settings:brands:manage |
Create, edit, delete brands |
settings:taxonomy:manage |
Create, edit, delete categories and tags |
The module uses 4 granular permissions — one per sub-section — plus a general read permission for viewing.
Components
Tax Classes Section
| Component | Description | Composable |
|---|---|---|
| TaxClassListComponent | Paginated list with search, inline status filter | useCrudList<TaxClass> with FiscalApiService |
| TaxClassFormComponent | Create/edit form (name, code, rate, description, isDefault) | useCrudForm<TaxClass> with FiscalApiService |
Brands Section
| Component | Description | Composable |
|---|---|---|
| BrandListComponent | Paginated list with search, active filter | useCrudList<Brand> with BrandApiService |
| BrandFormComponent | Create/edit form (name, slug, description, website, logo) | useCrudForm<Brand> with BrandApiService |
Categories Section
| Component | Description | Composable |
|---|---|---|
| CategoryManagerComponent | Tree view + list hybrid for category management | useCrudList<Category> with TaxonomyCategoryApiService |
| CategoryFormComponent | Create/edit form (name, slug, description, parent selector) | useCrudForm<Category> with TaxonomyCategoryApiService |
| CategoryDetailComponent | Detail view with subcategories list | useCrudDetail<Category> with TaxonomyCategoryApiService |
Tags Section
| Component | Description | Composable |
|---|---|---|
| TagListComponent | Paginated list with search, scope filter, color display | useCrudList<Tag> with TaxonomyTagApiService |
| TagFormComponent | Create/edit form (name, slug, color picker, scope) | useCrudForm<Tag> with TaxonomyTagApiService |
Routes
export const platformSettingsRoutes: Routes = [
{
path: '',
children: [
{ path: '', redirectTo: 'tax-classes', pathMatch: 'full' },
// Tax Classes
{ path: 'tax-classes', component: TaxClassListComponent, canActivate: [authGuard, contextGuard, permissionGuard('settings:fiscal:manage')] },
// Brands
{ path: 'brands', component: BrandListComponent, canActivate: [authGuard, contextGuard, permissionGuard('settings:brands:manage')] },
// Categories
{ path: 'categories', component: CategoryManagerComponent, canActivate: [authGuard, contextGuard, permissionGuard('settings:taxonomy:manage')] },
{ path: 'categories/create', component: CategoryFormComponent, canActivate: [authGuard, contextGuard, permissionGuard('settings:taxonomy:manage')] },
{ path: 'categories/:id', component: CategoryDetailComponent, canActivate: [authGuard, contextGuard, permissionGuard('settings:read')] },
{ path: 'categories/:id/edit', component: CategoryFormComponent, canActivate: [authGuard, contextGuard, permissionGuard('settings:taxonomy:manage')] },
// Tags
{ path: 'tags', component: TagListComponent, canActivate: [authGuard, contextGuard, permissionGuard('settings:taxonomy:manage')] },
],
},
];
Note: Tax Classes, Brands, and Tags use inline editing (modal/inline form on the list page), so they only need a list route. Categories have a full CRUD flow (list + create + detail + edit) because of the hierarchical tree structure.
Mock API
Platform Settings does not define its own mock API routes. It reuses the mock APIs from domain core libs:
| Domain Core Lib | Mock API Source | Endpoints |
|---|---|---|
@libs-core/fiscal |
fiscalMockAPI |
/api/fiscal/tax-classes/* |
@libs-core/brand-registry |
brandRegistryMockAPI |
/api/brands/* |
@libs-core/taxonomy |
taxonomyMockAPI |
/api/taxonomy/categories/*, /api/taxonomy/tags/* |
All three are already registered in mock-config.ts.
Module Registration
@ModuleDecorator({
id: 'platform-settings',
version: '1.0.0',
name: 'Platform Settings',
description: 'Administrative CRUD pages for tax classes, brands, categories, and tags',
basePath: 'settings',
repositories: [],
relations: [],
permissions: [
{ id: 'settings:read', name: 'settings:read', resource: 'settings', action: 'read' },
{ id: 'settings:fiscal:manage', name: 'settings:fiscal:manage', resource: 'settings', action: 'fiscal:manage' },
{ id: 'settings:brands:manage', name: 'settings:brands:manage', resource: 'settings', action: 'brands:manage' },
{ id: 'settings:taxonomy:manage', name: 'settings:taxonomy:manage', resource: 'settings', action: 'taxonomy:manage' },
],
navigation: { /* hierarchical as shown above */ },
tags: ['platform-settings', 'admin', 'configuration'],
})
@Injectable({ providedIn: 'root' })
export class PlatformSettingsModule implements BrowserModuleContract {
readonly manifest: ModuleManifest = {
id: 'platform-settings',
version: '1.0.0',
embeddableComponents: [], // No widgets — admin module
publicActions: [], // No cross-module actions
};
readonly routes = platformSettingsRoutes;
configure<T>(config: T): Observable<void> { return of(void 0); }
initialize(): Observable<void> { return of(void 0); }
start(): Observable<void> { return of(void 0); }
stop(): Observable<void> { return of(void 0); }
destroy(): Observable<void> { return of(void 0); }
}
When to Use This Pattern
Create an aggregator module when:
- Multiple domain core libs need admin CRUD pages
- The pages share a common navigation group (e.g., "Settings")
- No custom business logic is needed beyond basic CRUD
- The module's purpose is purely administrative
Do NOT create an aggregator if:
- The module needs its own entities or complex business logic
- Each domain core lib should have its own standalone module with routes