LLD: Fetch Location Hierarchy API
1. Overview
Returns a list of locations at a requested hierarchy level, filtered by a parent location. CIO-only endpoint that powers the "Display results for" cascading dropdown modal on the AI Audit page.
The MESCOM hierarchy is: Zone → Circle → Division → Sub-Division → Section
Each level maps to a user type: CE (zone), SE (circle), EE (division), AEE (sub-division), AE (section). The API queries the user collection to find all users (locations) at the requested level that fall under the given parent.
2. Endpoint
| Route | Method |
|---|---|
/user/hierarchy | GET |
CIO / SUPER_ADMIN only. AE users are already scoped to a single section and do not need this.
3. Request
3.1 Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <sessionToken> |
3.2 Query Parameters
| Param | Type | Required | Allowed Values | Description |
|---|---|---|---|---|
location | string | No | zone, circle, division, subDivision, section | The parent level you are filtering from |
locationCode | string | No | — | The code of the parent location |
requiredLocation | string | Yes | zone, circle, division, subDivision, section | The child level you want results for |
Rules:
location+locationCodeare either both present or both absent.- If both absent, returns all locations at the
requiredLocationlevel (no parent filter). requiredLocationmust be at the same level or belowlocationin the hierarchy. E.g., you cannot requestzonewhenlocationisdivision.
3.3 Examples
| Use Case | location | locationCode | requiredLocation | Returns |
|---|---|---|---|---|
| All zones | — | — | zone | All CE users (all zones) |
| Circles in zone 1 | zone | 1 | circle | SE users where location.zoneCode = "1" |
| Sub-divisions in division 123 | division | 123 | subDivision | AEE users where location.divisionCode = "123" |
| Sections in sub-division 222 | subDivision | 222 | section | AE users where location.subDivisionCode = "222" |
| All sections in zone 1 (skip levels) | zone | 1 | section | AE users where location.zoneCode = "1" |
| All divisions in zone 1 | zone | 1 | division | EE users where location.zoneCode = "1" |
4. Response
4.1 Success (200)
{
"success": true,
"message": "Hierarchy fetched successfully",
"data": {
"locations": [
{ "code": "Z31S42", "name": "Mangaluru Circle" },
{ "code": "Z31S43", "name": "Udupi Circle" },
{ "code": "Z31S44", "name": "Puttur Circle" }
]
}
}
4.2 Bad Request (400)
{
"success": false,
"message": "location and locationCode must be provided together"
}
{
"success": false,
"message": "requiredLocation must be at or below location in the hierarchy"
}
4.3 Forbidden (403)
{
"success": false,
"message": "Only CIO and SUPER_ADMIN users can access this endpoint"
}
5. Business Logic
5.1 Hierarchy Level Mapping
const HIERARCHY = {
zone: { userType: "CE", codeField: "zoneCode", nameField: "zoneName" },
circle: { userType: "SE", codeField: "circleCode", nameField: "circleName" },
division: { userType: "EE", codeField: "divisionCode", nameField: "divisionName" },
subDivision: { userType: "AEE", codeField: "subDivisionCode", nameField: "subDivisionName" },
section: { userType: "AE", codeField: "sectionCode", nameField: "sectionName" },
};
const HIERARCHY_ORDER = ["zone", "circle", "division", "subDivision", "section"];
5.2 Validate Hierarchy Order
IF location is provided:
parentIndex = HIERARCHY_ORDER.indexOf(location)
requiredIndex = HIERARCHY_ORDER.indexOf(requiredLocation)
IF requiredIndex < parentIndex → 400 "requiredLocation must be at or below location"
5.3 Build Query Filter
requiredLevel = HIERARCHY[requiredLocation]
filter = { userType: requiredLevel.userType }
IF location + locationCode provided:
parentLevel = HIERARCHY[location]
filter["location." + parentLevel.codeField] = locationCode
Key insight: Since every user doc stores the full location chain up to their level (e.g., an AE user has zoneCode, circleCode, divisionCode, subDivisionCode, sectionCode), we can filter at any ancestor level directly. Requesting sections under a zone works because AE users have location.zoneCode on their doc.
5.4 DB Query
Single query on the user collection:
const users = await baseUserModel.find(
filter,
{
[`location.${requiredLevel.codeField}`]: 1,
[`location.${requiredLevel.nameField}`]: 1,
_id: 0,
}
).lean();
5.5 Transform Response
const locations = users.map(u => ({
code: u.location[requiredLevel.codeField],
name: u.location[requiredLevel.nameField],
}));
5.6 Deduplicate
If requiredLocation is above location (same level query) or there are multiple users at the same location, deduplicate by code:
const unique = [...new Map(locations.map(l => [l.code, l])).values()];
This handles the case where multiple AE users exist in the same section — we only want distinct locations.
6. Validation Schema (AJV)
6.1 Query String Schema
{
type: "object",
properties: {
location: {
type: "string",
enum: ["zone", "circle", "division", "subDivision", "section"],
},
locationCode: {
type: "string",
transform: ["trim"],
},
requiredLocation: {
type: "string",
enum: ["zone", "circle", "division", "subDivision", "section"],
},
},
required: ["requiredLocation"],
additionalProperties: false,
dependentRequired: {
location: ["locationCode"],
locationCode: ["location"],
},
errorMessage: {
additionalProperties: "Unsupported properties in query parameters",
required: { requiredLocation: "requiredLocation is required" },
dependentRequired: "location and locationCode must be provided together",
},
}
6.2 Response Serialization
{
"2xx": responseSerializer("2xx", {
type: "object",
properties: {
locations: {
type: "array",
items: {
type: "object",
properties: {
code: { type: "string" },
name: { type: "string" },
},
},
},
},
}),
"4xx": responseSerializer("4xx"),
"5xx": responseSerializer("5xx"),
}
7. Route Config
{
routeId: "fetchHierarchy",
authentication: {
// Requires full auth (validation + verification)
},
authorization: {
resource: "user",
action: "read",
id: "idNotApplicable",
},
}
8. Cerbos Policy
New policy file — only CIO and SUPER_ADMIN can access:
# api/cerbos/policies/user.yml (new)
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: default
resource: user
rules:
- actions: ["read"]
effect: EFFECT_ALLOW
roles: ["SUPER_ADMIN", "CIO"]
9. File Structure
api/src/entities/user/
user.route.v1.js — Route definition
controller/
fetchHierarchy.js — Handler, schema, config exports
api/cerbos/policies/
user.yml — New Cerbos policy (CIO + SUPER_ADMIN only)
New entity folder user/ — auto-discovered by the route scanner.
10. Error Handling
| Scenario | Status | Error Type |
|---|---|---|
Missing requiredLocation | 400 | ValidationError (AJV) |
location without locationCode (or vice versa) | 400 | ValidationError (AJV dependentRequired) |
Invalid location or requiredLocation value | 400 | ValidationError (AJV enum) |
requiredLocation above location in hierarchy | 400 | ValidationError (handler) |
| AE user tries to access | 403 | ForbiddenError (Cerbos) |
| Invalid/expired session | 401 | UnauthorizedError |
| DB connection failure | 500 | DatabaseError |
11. Performance Notes
- Single DB query per request — no joins, no aggregation.
- Small dataset — user collection is tiny (one user per hierarchy node). No pagination needed.
- Deduplication done in-memory (negligible cost on small result sets).
- Cacheable — hierarchy rarely changes. Could add Redis caching with long TTL if needed.
- Index coverage:
userTypeis the discriminator key (indexed by default). Location code fields on user docs are efficient to filter on small collections.