Happy House - Ecommerce Docs
Developer ResourcesCatalog

Catalog Features and Flows

Complete feature list, actor journeys, state flows, business rules, edge cases, and diagrams for the Catalog module.

Catalog Features and Flows

Use this page for the catalog domain: what it does for users, admins, workers and systems, and how each flow behaves from start to finish.

1. Documentation Evidence

Source TypeFiles or DocsWhat Was Extracted
Technical designcatalog-slug.service.ts, catalog-search.service.ts, catalog.constants.ts, workers/catalog-job-state.tsSlug ownership, search form, job state machine, advisory lock
APIadmin/{category,brand,brand-series,job}/*.controller.ts, customer/**/*.controller.ts, bulk controllersRoute surface, permissions, rate limits
Backendcatalog-category-{write,admin,bulk,tree}.service.ts, catalog-brand*.service.ts, catalog-import-*.ts, catalog-export.service.tsBusiness rules, transactions, side effects
Schemapackages/db/src/schema/catalog/*.tsConstraints, partial indexes, slug tables
Jobspackages/jobs/src/index.tsQueue contracts, payloads

2. Feature Summary

FieldValue
Modulecatalog
Submodulecategory (tree), brand, brand series, tag (newer), jobs (import/export)
Primary user valueThe store's taxonomy — categories, brands and brand series — with permanent, honest URLs and a unified search
ActorsGuest, admin, worker/system
Main entry points/api/admin/catalog/{categories,brands,brand-series,jobs}, /api/mobile/catalog/{categories,brands,series,search}, /api/admin/catalog/tags
Main outputsTree/list/detail responses, per-item bulk results, CSV imports/exports, activity records, cache invalidations
Related docsAPI, Backend

3. Actor Matrix

ActorCan DoCannot DoAuth RequirementNotes
GuestBrowse categories (root list, detail, breadcrumbs), brands, series, search, suggestions, tagsAdmin mutations, see hidden/deleted entitiesNone (@Public())Hidden/deleted → 404 on detail routes; rate limits PUBLIC_READ 60/min, PUBLIC_SEARCH 60/min, PUBLIC_SUGGEST 300/min
AdminFull CRUD + bulk + reorder + tree ops + import/export jobs for all three entities, tag CRUDDelete a category with active children, delete a brand with active series, permanently delete anythingAdmin JWT + Categories_* / Brands_* / BrandSeries_* / Catalog_* / Tags_*Every mutation writes activity + invalidates cache; category tree mutations serialise on an advisory lock
Worker/systemRun import/export jobs, sweep stalled jobsBullMQ workerLease-based claim; cooperative cancellation

4. Capability Matrix

CapabilitySurfaceActorRoute/TriggerState ReadState WrittenLinked API Section
List siblingsAdminAdminGET /api/admin/catalog/categoriesOne tree levelAPI §3
Full treeAdminAdminGET /api/admin/catalog/categories/treeNon-deleted rowsAPI §3
Category detail/breadcrumbsAdminAdminGET /:publicId(/:breadcrumbs)Row + ancestorsAPI §3
Create/update/move/visibility/delete/restore categoryAdminAdminPOST/PATCH/DELETE on /api/admin/catalog/categoriesRow + parentCategory rows, slug rowsAPI §3
Bulk category ops + reorderAdminAdminPOST /bulk/*, PATCH /reorderRowsRows + display_orderAPI §3.6–3.7
Brand CRUD + bulk + reorderAdminAdmin/api/admin/catalog/brandsRowsRows, slugsAPI §4
Series CRUD + bulk + reorderAdminAdmin/api/admin/catalog/brand-seriesRows + brandRows, slugsAPI §5
Import/export jobsAdmin/workerAdmin → system/api/admin/catalog/jobsJob rowsJob rows, CSVAPI §6
Category/brand/series browsingStorefrontGuest/api/mobile/catalog/{categories,brands,series}Visible rowsAPI §7–9
Search + suggestionsStorefrontGuest/api/mobile/catalog/searchVisible rowsAPI §10
Tag CRUD + listingAdmin/guestBoth/api/admin/catalog/tags, /api/mobile/catalog/tagsTag rowsTag rowsProducts API §4

5. User-Facing Flows

5.1 Browse the category tree

Summary

A guest opens the storefront. The frontend fetches root categories, then drills into a category detail by slug and renders breadcrumbs. Category visibility is inherited: a visible category under a hidden or deleted ancestor is unreachable.

Sequence Diagram

Branches and Edge Cases

BranchConditionBehaviorError/Result
Retired slugSlug owned by the entity, not current200 + canonicalSlug — frontend redirectsNo 3xx from the API
Hidden/deleted entityNot visible on storefront404 on both slug branchesCATEGORY_NOT_FOUND-style
Parent hiddenVisible child under hidden parentChild unreachable everywhere404 / excluded
Tag filter resolves emptyUnknown tag slugsEmpty result, never the whole catalogueEmpty data

Search covers category, brand and brand series with pg_trgm: the % operator bounds candidates in WHERE (index-accelerated), similarity() ranks in ORDER BY — never in WHERE, which would force a full scan. A term under 2 characters is "no search" → unfiltered visible list. Category visibility is enforced inside the SQL (hidden/deleted ancestors block); series visibility requires a visible brand.

6. Admin Flows

6.1 Create a category

Permission Categories_CREATE, ADMIN_WRITE 10/min. Inside the transaction: advisory tree lock first, parent resolve + depth check (max 6), slug generation against the ownership table, insert + claim slug.

6.2 Move a category

The only parent-mutating route: PATCH /:publicId/move. Guards: cycle prevention (cannot move under own descendant), depth cap for the whole subtree, parent liveness. parent_id and depth are rewritten in one statement.

6.3 Soft delete and restore

Delete requires no active children (409 CATEGORY_HAS_ACTIVE_CHILDREN); bulk delete processes deepest-first so parents and children can go together. Restore requires a live parent. Slugs are never released by delete, so restore never fails on a taken slug.

6.4 Import / export jobs

CSV/XLSX import (25 MB, 50,000 rows) validates every row before writing anything; a category file's internal parent graph must be acyclic. Export caps at 50,000 matching rows. Import submit re-checks the entity-specific create permission (Categories_CREATE / Brands_CREATE / BrandSeries_CREATE) beyond route-level Catalog_CREATE.

7. Lifecycle and State Transitions

7.1 Slug ownership

EntityFromEvent/ActionToGuard ConditionSide Effects
category_slugcurrentrenameretiredDemote BEFORE promote (partial unique index non-deferrable)retired_at stamped; re-adoption restores original URL
any *_sluganysoft delete(unchanged)Slugs never released on deleteRestore can never fail on a slug

7.2 Catalog job state machine

EntityFromEvent/ActionToGuard ConditionSide Effects
catalog_jobqueuedclaimprocessingstatus = 'queued' OR processing past TTLstarted_at stamped
catalog_jobprocessingcompletecompletedTerminal write matches zero rows → throws CATALOG_JOB_NOT_PROCESSINGfinished_at in same UPDATE
catalog_jobqueued/processingcancelcancelledNot terminalCooperative abort for running imports

9. Data and Side Effects by Flow

FlowDB WritesCache EffectsJobsRealtimeAnalyticsNotifications
Category create/move/delete/restoreCategory + slug rowscatalog_category + search
Brand/series writesRows + slugscatalog_brand / catalog_series + search
Reorderdisplay_orderowning domain
ImportEntity rows + catalog_jobowning domaincatalog.import_entities
Exportcatalog_job + CSVcatalog.export_entities
All mutationsRow + activity recorddomain tags + Redis patterns

10. Error and Recovery Flows

ScenarioTriggerUser/System ExperienceRecoverySource
Concurrent tree movesTwo moves crossBoth validate against a snapshotAdvisory lock serialises — second waitscatalog.constants.ts
Import validation failureAny row invalid / cycleJob failed, nothing writtenFix file, resubmitcatalog-import.service.ts
Import cancelled mid-runAdmin cancelsCooperative abort, rollbackResubmitcatalog-job-state.ts
Worker died mid-runCrashJob stuck processingSweep fails it after 15 min TTLcatalog-job-sweep.*
Slug generation exhausted100 attempts taken409 CATALOG_SLUG_GENERATION_FAILEDRename sourcecatalog-slug.service.ts

11. Diagrams Required Per Module

  • Actor capability diagram — §3/§4.
  • High-level module flow — §6.1/§6.2 admin flows.
  • Sequence diagram per major flow — §5.1.
  • State machine diagram — §7.2 (jobs), §7.1 (slugs).
  • Data side-effect diagram — §9.
  • Error branch diagram — §10.

12. Mandatory Feature and Flow Deep-Dive Pack

12.1 Feature Inventory With Minor Behaviors

FeatureMinor BehaviorActorTriggerUser/System ResultBackend Side EffectSource
Category listSibling listing with deletedOnlyAdminGET /categories?deletedOnly=trueRestore workflowPartial index idx_category_parent_ordercatalog-category-admin.service.ts
Tree2,000-node capAdminGET /tree400 CATALOG_TREE_TOO_LARGEIn-app bound, not truncation
MoveRe-root (parentId: null)AdminPATCH /moveMoves to rootOne-statement subtree rewritecatalog-category-tree.service.ts
RenameUndoAdminRename backOriginal URL restoredRe-adoption of owned slugcatalog-slug.service.ts
Bulk deleteDeepest-first orderingAdminPOST /bulk/deleteParent+child batch worksSorted by depth desccatalog-category-bulk.service.ts
Bulk restoreShallowest-firstAdminPOST /bulk/restoreParent-first orderingSorted by depth asc
ReorderExact sibling setAdminPATCH /reorder409 on mismatchSet equality check
ImportSlug-based parentsAdminPOST /jobs/importNothing written on any errorAll-or-nothingcatalog-import.service.ts
ExportFormula-injection escapeAdminExport download' prefix on = + - @ cellsCSV escapingcatalog-export.service.ts
SearchTerm < 2 charsGuest?search=abUnfiltered list"No search"catalog-search.service.ts
SearchCursor stabilityGuestAny pageStable orderORDER BY ... id tiebreak
Slug routesRetired slugGuestOld URL200 + canonicalSlugOwnership table lookupcatalog-slug.service.ts

12.2 Business Process Diagram Pack

12.3 Business Rules and Policy Traceability

RuleBusiness ReasonActor ImpactEnforced InAPI ImpactBackend ImpactTests
Slug never re-hands to a different entityOld links must stay honestGuest URLs never repointOne UNIQUE over current+retiredStorefront resolves any owned slugSlug ownership tablescatalog-slug.service.spec.ts
Depth capped at 6Tree stays navigableAdmin gets 409 on deep create/moveDB CHECK + serviceCreate/move/restoreCHECK chk_category_depth_within_capprobe
Delete blocked while active childrenNo orphaned visibilityAdmin must delete children firstService + RESTRICTDelete/bulk deleteON DELETE RESTRICTprobe
Hard delete unsupportedDelete is always recoverableNothing permanently removedRESTRICT FKsNo hard-delete routeFK 23503probe
Visibility inheritedHidden parent hides subtreeGuest cannot reach hidden subtreeService + SQLStorefront 404computeAncestorChainVisiblespec
One error code per statusClients branch on statusDeterministic errorsReview fixPAGINATION_LIMIT_TOO_LARGE = 400 everywhereBadRequestException
Import requires entity-specific createRoute-level superset token must not mass-create403 without entity permissionServiceImport submitassertCanCreateEntityspec

12.4 Tradeoffs and Product Rationale

Product DecisionUser BenefitEngineering BenefitAlternativeTradeoffRisk
Slugs in ownership tablesPermanent URLsUnrepresentable re-pointingSlug column + history tableMore tables
Advisory lock on tree writesConsistent treeSerialised rare writesApplication-level checksThroughput on writesAccepted (rare)
Soft delete + RESTRICT everywhereRecoverable deletesNo cascade surprisesHard deleteStorage growsRetention policy
Trigram % in WHEREFast fuzzy searchIndex-accelerated candidatessimilarity() in WHEREFull scan if misusedDocumented rule

12.5 Flow Edge-Case Matrix

FlowEdge CaseTriggerExpected BehaviorUser/System FeedbackSource
CreateSlug collisionName already usedAuto-suffix (-2, …) up to 100 attempts409 after exhaustioncatalog-slug.service.ts
RenameSlug owned by selfRename backRe-adopt, original URL200
MoveCycleMove under descendant409 CATEGORY_CIRCULAR_HIERARCHY
MoveConcurrentTwo cross movesSerialised; second sees committed state200/409advisory lock
BulkDuplicate ids[X, X, Y]Counted once409 only if > 100 uniquededupeAndCapBulkIds
ReorderPartial setMissing sibling409 CATALOG_REORDER_INVALID_ITEM
ImportFile-internal cycleparentSlug graph cyclicJob fails, nothing writtenCATALOG_IMPORT_CYCLE_DETECTEDKahn's algorithm
ImportRetryWorker error before final attemptReleased to queuedReal retrylease claim
ExportZero rowsEmpty matchHeader-only CSV, completedSuccess
Slug resolveNo current slugCorrupt stateNot found404service invariant

12.6 Flow-to-Data Trace

FlowReadsWritesCacheJobs/EventsResponse Fields
Category list/treecategory, category_slugcatalog_categoryResponseDto + tree nodes
Category writeRow + parent + slug ownershipcategory, category_sluginvalidateactivityResponseDto
Searchcategory/brand/brand_series + slugscatalog:searchitems + pagination
ImportRows, slug ownershipEntity + slug + catalog_jobinvalidateoutbox → catalog.import_entitiesjob
ExportEntity rowscatalog_job + CSVoutbox → catalog.export_entitiesjob

12.7 Experience Quality Checklist

  • The doc explains what the actor is trying to accomplish.
  • The doc explains what the backend does that the actor does not see (lock, lease claim, literal predicates).
  • The doc covers every minor flow and branch (12.1, 12.5).
  • The doc includes user, admin, worker, and system flows.
  • The doc explains business logic, tradeoffs, and rationale.
  • The doc maps every flow to API routes and backend side effects.
  • The doc includes diagrams appropriate to each flow type.
  • The doc covers edge cases and failure recovery.

13. Completion Checklist

  • Every feature, minor action, and submodule capability is listed.
  • Every actor has allowed and forbidden behavior.
  • Every major and minor flow includes steps, branches, and diagrams.
  • Every lifecycle has a transition table and state diagram.
  • Every flow links to the API and backend docs.
  • TDD dependencies are called out where they shape behavior (no TDD pages published yet).

See Also