Category Service
The category service ( internal/api/v1/instasites/services/categories.service.js ) powers /instasites/categories endpoints, delivering both categories and main categories with template counts for filter UIs.
📖 Overview
Primary responsibilities:
- Query
instasites.templates.categoriesandinstasites.templatesto build joined datasets. - Support free-text search, scoped category/main category filters, and count aggregation.
- Normalize results into a flat array with
id,title,type, andcountfields while also returning total template counts.
🗄️ Collections Used
instasites.templates.categories
- Operations: Read
- Model:
shared/models/instasite-template-category.js - Usage: Source of category and main-category metadata, as well as the driving collection for
$lookuppipelines.
instasites.templates
- Operations: Read
- Model:
shared/models/instasite-template.js - Usage: Supplies template documents to join against categories, enabling count aggregation per taxonomy node.
🧠 Function Reference
getCategories({ query, category_id, main_category_ids })
Purpose: Returns a combined list of categories and main categories, annotated with template counts and overall total.
Inputs:
query— Optional search term; performs case-insensitive$regexagainst category titles.category_id— Focuses aggregation on a single sub-category and fetches related main categories.main_category_ids— Array of main-category IDs; returns those nodes plus their linked sub-categories.
Business Logic Flow:
- Build a flexible aggregation (
qcatego) forinstasites.templates.categories:- No filters → return everything.
category_idprovided → pull the category, join main categories via template relationships.main_category_idsprovided → pull each main category, join sub-categories via template relationships.- Both provided → perform a simple
$matchwith$or.
- Construct parallel aggregation (
customizedQuery) forinstasites.templates:- Optionally regex match titles when
queryexists. - Use
$facetto count templates grouped bycategory_idandmain_category_id(filtered when inputs provided).
- Optionally regex match titles when
- Execute three asynchronous operations with
Promise.all:- Category aggregation pipeline built in step 1.
- Template counts facet from step 2.
countDocumentsfor total templates matching the filters.
- Normalize category aggregation output:
- Flatten the
dataarray produced by$addFields. - Convert
_idtoidfor client expectations. - Attach
countby finding the matching group in eithertemplatesCounts.categoriesortemplatesCounts.main_categories.
- Flatten the
- Return
{ data, total }, wheredatais a merged list containing both category types.
Key Rules:
- Every incoming ID is converted to
mongoose.Types.ObjectId; invalid values throw before Mongo executes (surfaced asCastError). - Counts default to
0when no templates match a node. - When
main_category_idsandcategory_idare both provided, the service returns only the explicitly requested nodes and their counts.
Error Modes:
| Code | Scenario | Response |
|---|---|---|
| 400 | Invalid ObjectId strings | Surface as validation errors before service invocation |
| 500 | Aggregation failure | Controller catches and logs generic server error |
🔍 Query Examples
| Scenario | Inputs | Output Notes |
|---|---|---|
| Search by text | { query: "health" } | Returns all categories whose titles contain "health" (case-insensitive). |
| Main-category drilldown | { main_category_ids: ["..."] } | Returns specified main categories followed by their child categories via $lookup. |
| Sub-category focus | { category_id: "..." } | Returns the selected category along with linked main categories (through template relationships). |