Skip to main content

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

RouteMethod
/user/hierarchyGET

CIO / SUPER_ADMIN only. AE users are already scoped to a single section and do not need this.


3. Request

3.1 Headers

HeaderRequiredDescription
AuthorizationYesBearer <sessionToken>

3.2 Query Parameters

ParamTypeRequiredAllowed ValuesDescription
locationstringNozone, circle, division, subDivision, sectionThe parent level you are filtering from
locationCodestringNoThe code of the parent location
requiredLocationstringYeszone, circle, division, subDivision, sectionThe child level you want results for

Rules:

  • location + locationCode are either both present or both absent.
  • If both absent, returns all locations at the requiredLocation level (no parent filter).
  • requiredLocation must be at the same level or below location in the hierarchy. E.g., you cannot request zone when location is division.

3.3 Examples

Use CaselocationlocationCoderequiredLocationReturns
All zoneszoneAll CE users (all zones)
Circles in zone 1zone1circleSE users where location.zoneCode = "1"
Sub-divisions in division 123division123subDivisionAEE users where location.divisionCode = "123"
Sections in sub-division 222subDivision222sectionAE users where location.subDivisionCode = "222"
All sections in zone 1 (skip levels)zone1sectionAE users where location.zoneCode = "1"
All divisions in zone 1zone1divisionEE 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

ScenarioStatusError Type
Missing requiredLocation400ValidationError (AJV)
location without locationCode (or vice versa)400ValidationError (AJV dependentRequired)
Invalid location or requiredLocation value400ValidationError (AJV enum)
requiredLocation above location in hierarchy400ValidationError (handler)
AE user tries to access403ForbiddenError (Cerbos)
Invalid/expired session401UnauthorizedError
DB connection failure500DatabaseError

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: userType is the discriminator key (indexed by default). Location code fields on user docs are efficient to filter on small collections.