Skip to content

Admin, Ops, System Config & Bulk Tooling

This document covers the operational backbone of the Mr. Mentor backend: the admin dashboard surface, dynamic runtime configuration (SystemConfig), the Excel/JSON bulk-upload pipeline for onboarding users, ad-hoc data cleanup of expired slots, low-level Redis inspection, the health-check endpoint, the Bull Board queue-monitoring UI, and unified issue/query reporting and triage. These are the tools the MAS operations and engineering teams use to run, observe, and repair the platform day to day.

Status: documented from source on this branch.


Overview

This domain is the "back office" of the backend. It does not belong to any single product (Mr. Mentor, Mr. Hire, MAS LMS, Sales CRM) — instead it provides cross-cutting operational capabilities that every product depends on:

  • Admin dashboard operations — aggregate KPIs (connects, revenue, tokens, users, batch-wise breakdown) for the admin console, plus user/role management, meeting management, recordings, and query triage. Implemented in src/controllers/admin.controller.ts + src/controllers/adminMas.controller.ts and mounted at /api/admin.
  • Dynamic system configuration — a key/value SystemConfig table that holds runtime config and feature flags read by this backend and, importantly, by mr-hire-backend on startup. See src/services/SystemConfigService.ts.
  • Bulk upload tooling — a two-step Excel/JSON upload → preview/auto-map → validate → import pipeline for onboarding mentees and experts in bulk. See src/services/BulkUploadService.ts and src/services/BulkUploadParserService.ts.
  • Data cleanup — manual and scheduled removal of expired, unbooked mentor slots and related housekeeping. See src/controllers/cleanup.controller.ts + src/services/SlotCleanupService.ts.
  • Redis admin — raw key inspection and deletion for debugging. See src/controllers/redis.controller.ts.
  • Health & monitoring — a liveness/readiness endpoint (/api/health) plus the Bull Board UI at /admin/queues for inspecting BullMQ queues.
  • Issue/query reporting — a unified Issues table that captures bug reports, feedback, feature requests, and payment disputes from any product, with admin triage. See src/services/IssuesService.ts.

Personas

Persona Role enum What they do here
Platform admin ADMIN, SUPERADMIN Dashboard, user management, bulk uploads, cleanup, config, issue triage
Operations / support ADMIN Triage issues and payment queries, inspect Redis, run cleanup
DevOps / on-call engineer (HTTP Basic for Bull Board) Monitor queues, check /api/health
End user / student / mentor USER, EXPERT (or anonymous) File issues via /api/issues; consume public config
mr-hire-backend service service-to-service Reads /api/system-config/:key on startup

Key concepts & entities

Concept Meaning
SystemConfig A single key/value row holding a runtime config value or feature flag. isActive acts as a soft on/off switch; getByKey only returns active rows.
Issue / Query A user-submitted bug, feedback, feature request, "other", or payment dispute. "General queries" in the admin UI are all non-payment issues; "payment queries" are issueType = payment. Backed by the single Issues table.
Bulk upload field mapping A { sourceField, targetField } pair that maps a spreadsheet column header to a known user/mentor field. Auto-suggested from header name heuristics, then confirmed by the admin.
Slot cleanup Deletion of expired, unbooked mentor Slots rows and expiry of unaccepted meetings, run on a 15-minute schedule and triggerable manually.
Bull Board A web UI bundled into the app at /admin/queues (HTTP Basic auth) that visualizes all BullMQ queues.
Warning A student-discipline / risk record (warnings table). Mentioned here because it is an ops/admin-adjacent record, but it is owned primarily by the student-engagement domain.

Main TypeORM entities

Entity File Notes
SystemConfig src/entities/SystemConfig.ts id (uuid PK), key (unique, indexed), value (text, nullable), label, description, isActive (default true), timestamps.
Issues src/entities/Issues.ts id (int PK), optional userId FK → User, name, email, issueType enum, status enum, title, description, platform (default mr mentor), attachmentUrls (simple-array), imageUrl, payment fields (amount, paymentMethod enum, transactionId), notes, timestamps.
Warning (mention) src/entities/Warning.ts id (uuid PK), studentId, type enum, severity enum, message, isActive, acknowledged, createdBy. Triage-adjacent but not owned by this domain.

Bulk upload reads/writes User, Mentor, Token, and PromotionalToken rather than owning its own entity (see src/services/BulkUploadService.ts). Cleanup operates on Slots, RequestedSlot, and MeetingLogs via src/services/SlotCleanupService.ts.


Architecture

flowchart TD
  subgraph Clients["Clients"]
    FE["Admin Dashboard (mr-mentor-frontend)"]
    USR["End user / student"]
    HIRE["mr-hire-backend (service)"]
    OPS["On-call engineer (browser)"]
  end

  subgraph Routes["Express routes"]
    R_ADMIN["/api/admin/*"]
    R_CFG["/api/system-config + /api/admin/system-config"]
    R_BULK["/api/bulk-upload/*"]
    R_CLEAN["/api/cleanup/*"]
    R_REDIS["/api/redis/keys"]
    R_HEALTH["/api/health"]
    R_ISSUES["/api/issues"]
    R_QUEUES["/admin/queues (Bull Board)"]
  end

  subgraph Controllers["Controllers"]
    C_ADMIN["AdminController + AdminMasController"]
    C_CFG["SystemConfigController"]
    C_BULK["BulkUploadController"]
    C_CLEAN["CleanupController"]
    C_REDIS["RedisController"]
    C_HEALTH["HealthController"]
    C_ISSUES["IssuesController"]
  end

  subgraph Services["Services"]
    S_DASH["AdminDashboardService"]
    S_CFG["SystemConfigService"]
    S_PARSE["BulkUploadParserService"]
    S_BULK["BulkUploadService"]
    S_CLEAN["SlotCleanupService"]
    S_HEALTH["HealthService"]
    S_ISSUES["IssuesService"]
    S_QUEUE["QueueService (BullMQ)"]
  end

  subgraph Data["Data and infra"]
    DB[("PostgreSQL via TypeORM")]
    REDIS[("Redis")]
    S3["AWS S3 (attachments)"]
  end

  FE --> R_ADMIN --> C_ADMIN --> S_DASH --> DB
  FE --> R_CFG
  USR --> R_ISSUES --> C_ISSUES --> S_ISSUES --> DB
  HIRE --> R_CFG --> C_CFG --> S_CFG --> DB
  C_CFG -->|"refresh-mr-hire"| HIRE
  FE --> R_BULK --> C_BULK --> S_PARSE
  C_BULK --> S_BULK --> DB
  S_BULK -->|"onboarding email"| S_QUEUE
  FE --> R_CLEAN --> C_CLEAN --> S_CLEAN --> DB
  C_CLEAN -->|"schedule"| S_QUEUE --> REDIS
  FE --> R_REDIS --> C_REDIS --> REDIS
  OPS --> R_HEALTH --> C_HEALTH --> S_HEALTH
  S_HEALTH --> DB
  S_HEALTH --> REDIS
  OPS --> R_QUEUES --> S_QUEUE --> REDIS
  C_ISSUES -.->|"attachments"| S3

Data model

erDiagram
  USER ||--o{ ISSUES : "files"
  USER ||--o{ WARNING : "receives"

  SYSTEM_CONFIG {
    uuid id PK
    varchar key UK
    text value
    varchar label
    varchar description
    boolean isActive
    timestamp createdAt
    timestamp updatedAt
  }

  ISSUES {
    int id PK
    uuid userId FK
    varchar name
    varchar email
    enum issueType
    enum status
    varchar title
    text description
    varchar platform
    text attachmentUrls
    varchar imageUrl
    decimal amount
    enum paymentMethod
    varchar transactionId
    text notes
    timestamp createdAt
    timestamp updatedAt
  }

  WARNING {
    uuid id PK
    uuid studentId FK
    enum type
    enum severity
    text message
    boolean isActive
    boolean acknowledged
    uuid createdBy
    timestamp createdAt
  }

SystemConfig is standalone (no FKs). Bulk upload writes into User / Mentor / PromotionalToken (documented in the auth & token domains), not into a dedicated table.

Notable enums (src/entities/Issues.ts)

  • IssueType: bug, feedback, feature-request, other, payment
  • IssueStatus: open, in-progress, resolved, pending, paid, failed
  • PaymentMethod: bank-transfer, upi, card, wallet

API surface

Paths below are the full mounted paths (mount prefix from src/routes/index.ts + path in the route file).

Issue reporting — src/routes/issues.routes.ts (mounted at /api)

Method Path Auth/role Purpose
GET /api/issues none (public) List issues (paginated).
GET /api/issues/:id none (public) Get one issue by id.
POST /api/issues none (public) Create an issue; platform taken from x-platform header.
PUT /api/issues/:id none (public) Update an issue.
DELETE /api/issues/:id none (public) Delete an issue.

Issue triage via admin — src/routes/admin.routes.ts (mounted at /api/admin)

Method Path Auth/role Purpose
GET /api/admin/issues auth + admin List general (non-payment) queries, filtered/paginated.
GET /api/admin/issues/:id auth + admin Get one query.
POST /api/admin/issues auth + admin Create a general query.
PUT /api/admin/issues/:id/status auth + admin Update query status.
GET /api/admin/queries/general auth + admin Legacy alias of the issues list.
GET/POST/PUT /api/admin/queries/general/* auth + admin Legacy general-query aliases.
GET /api/admin/queries/payment auth + admin List payment-dispute issues.
GET /api/admin/queries/payment/:id auth + admin Get one payment query.
POST /api/admin/queries/payment auth + admin Create a payment query.
PUT /api/admin/queries/payment/:id/status auth + admin Update payment query status.

Admin dashboard & user management — src/routes/admin.routes.ts (selected ops endpoints)

Method Path Auth/role Purpose
GET /api/admin/dashboard auth + admin Full aggregate dashboard payload (legacy single call).
GET /api/admin/dashboard/connects auth + admin Connects KPI panel.
GET /api/admin/dashboard/revenue auth + admin Revenue KPI panel.
GET /api/admin/dashboard/tokens auth + admin Token economy KPI panel.
GET /api/admin/dashboard/users auth + admin Users KPI panel.
GET /api/admin/dashboard/batch-wise auth + admin Batch-wise breakdown.
GET /api/admin/dashboard/batch/:batchName/students auth + admin Students in a batch.
POST /api/admin/dashboard/refresh-kpi auth + admin Force a KPI recompute.
GET /api/admin/users auth + admin List all users.
GET /api/admin/users/by-email auth + admin Lookup by exact email.
GET /api/admin/users/search-by-email auth + admin Search users by email.
GET /api/admin/users/:id auth + admin Get user by id.
PUT /api/admin/users/:id auth + admin Update user.
PUT /api/admin/users/:id/role auth + admin Change user role.
DELETE /api/admin/users/:id auth + admin Remove user.
POST /api/admin/users/:id/block auth + admin Block user.
POST /api/admin/users/:id/unblock auth + admin Unblock user.
POST /api/admin/experts /admins /students /sales /sales-head /external-hrs /batch-leads /community-managers /super-mentors auth + admin Create users of each role.

The /api/admin router also hosts large MAS-LMS, AI-classroom, WhatsApp-manager, notifications, course-plan, and LLM-gateway sub-trees. Those are documented in their own feature docs; only the operational subset is listed above.

System config — src/routes/systemConfig.routes.ts (mounted at /api)

Method Path Auth/role Purpose
GET /api/system-config/enabled-call-providers none (public) HR call-provider flags (specific route, declared before :key).
GET /api/system-config/:key none (public) Read one active config value (consumed by mr-hire-backend).
GET /api/admin/system-config auth only List all config rows.
POST /api/admin/system-config auth only Upsert a config by key.
PUT /api/admin/system-config/:id auth only Update a config row by id.
DELETE /api/admin/system-config/:id auth only Delete a config row.
POST /api/admin/system-config/refresh-mr-hire auth only Tell mr-hire-backend to refresh its call-provider cache.

Bulk upload — src/routes/bulkUpload.routes.ts (mounted at /api/bulk-upload)

Method Path Auth/role Purpose
POST /api/bulk-upload/preview auth + admin Upload file, parse, return detected fields + suggested mappings + sample.
POST /api/bulk-upload/process auth + admin Re-upload file with mappings, validate, import users.
GET /api/bulk-upload/fields auth + admin List mappable fields for a userType.
GET /api/bulk-upload/template auth + admin Download a sample .xlsx or .json template.

Cleanup — src/routes/cleanup.routes.ts (mounted at /api/cleanup)

Method Path Auth/role Purpose
DELETE /api/cleanup/slots auth + admin Run cleanup of expired unbooked slots synchronously.
GET /api/cleanup/statistics auth + admin Overall slot statistics.
GET /api/cleanup/statistics/:mentorId auth + admin Per-mentor slot statistics.
POST /api/cleanup/schedule auth + admin Enqueue a cleanup job onto the queue.
GET /api/cleanup/slots/:filter auth + admin List slots by filter (all, available, booked, future, expired, expiredUnbooked).

Redis admin — src/routes/redis.routes.ts (mounted at /api/redis)

Method Path Auth/role Purpose
GET /api/redis/keys none (see gotchas) Dump all keys and values.
DELETE /api/redis/keys/:key none (see gotchas) Delete a key.

Health — src/routes/health.routes.ts (mounted at /api)

Method Path Auth/role Purpose
GET /api/health none (public) Liveness/readiness: DB + Redis status, uptime, env. Returns 503 if unhealthy.

Queue monitoring — src/routes/bullBoard.routes.ts (mounted at /)

Method Path Auth/role Purpose
ALL /admin/queues HTTP Basic (BULL_BOARD_USERNAME / BULL_BOARD_PASSWORD) Bull Board UI for all BullMQ queues.

User journeys

1. Bulk-upload mentees/experts (two-step preview → process)

The headline ops journey. An admin uploads a spreadsheet of mentees or experts. Step 1 parses the file and auto-suggests column mappings. The admin confirms/edits the mappings in the UI, then step 2 re-submits the same file plus the confirmed mappings to actually create the users. Each row is created in its own transaction; failures are collected per-row rather than aborting the batch.

sequenceDiagram
  participant FE as Admin UI
  participant API as BulkUploadController
  participant Parse as BulkUploadParserService
  participant Bulk as BulkUploadService
  participant DB as PostgreSQL
  participant Q as QueueService

  Note over FE,API: Step 1 - preview
  FE->>API: POST /api/bulk-upload/preview with file and userType
  API->>API: multer validates type and size up to 10MB
  API->>Parse: parseExcelFile or parseJsonFile
  Parse-->>API: rows and headers
  API->>Parse: generatePreview
  Parse-->>API: detected fields, suggested mappings, sample rows
  API-->>FE: preview payload

  Note over FE,API: admin edits mappings then submits
  FE->>API: POST /api/bulk-upload/process with file, userType, fieldMappings
  API->>Parse: validateMappings requires email mapped
  alt email not mapped
    Parse-->>API: invalid
    API-->>FE: 400 invalid field mappings
  else valid
    API->>Parse: applyMappings transforms rows
    API->>API: apply defaultTokens to mentees when unset
    API->>Bulk: validateBulkData
    API->>Bulk: processBulkUpload userType and rows
    loop each row
      Bulk->>DB: check email exists
      alt duplicate or missing required
        Bulk->>Bulk: record row error and continue
      else new
        Bulk->>DB: insert User in a transaction
        opt expert
          Bulk->>DB: insert Mentor profile
          Bulk->>Q: enqueue mentor-onboarding email after commit
        end
        opt mentee with meetTokens
          Bulk->>DB: insert PromotionalToken
        end
      end
    end
    Bulk-->>API: successCount, failedCount, per-row errors
    API-->>FE: result summary 200 or 400
  end

Key details from src/services/BulkUploadService.ts: - Passwords are hashed with bcrypt; if no password column is mapped, a random 12-char password is generated. - Experts require company, role, and institute or the row fails validation. - Expert onboarding emails are queued via QueueService.addEmailJob with a 2s delay, only after the transaction commits. - Mentee meet-tokens are stored as a PromotionalToken (not a base Token), expiring 1 year out by default.

2. Dynamic system config — set a flag, then read it from another service

An admin writes a runtime config/feature flag through the admin endpoint. mr-hire-backend (or this backend) later reads it through the public :key endpoint. For call-provider settings the admin can also trigger a cache refresh in mr-hire-backend.

sequenceDiagram
  participant FE as Admin UI
  participant API as SystemConfigController
  participant Svc as SystemConfigService
  participant DB as PostgreSQL
  participant Hire as mr-hire-backend

  FE->>API: POST /api/admin/system-config key and value
  API->>API: require key and value
  API->>Svc: upsert key value label description
  Svc->>DB: find by key
  alt exists
    Svc->>DB: update value
  else new
    Svc->>DB: insert row with label default key
  end
  Svc-->>API: saved config
  API-->>FE: 200 config saved

  opt refresh hire cache
    FE->>API: POST /api/admin/system-config/refresh-mr-hire
    API->>Hire: POST call-provider refresh timeout 5s
    alt hire reachable
      Hire-->>API: ok
      API-->>FE: 200 cache refreshed
    else hire down
      API-->>FE: 200 saved, refresh failed non-critical
    end
  end

  Note over Hire,DB: later, on its own startup
  Hire->>API: GET /api/system-config/:key
  API->>Svc: getByKey only active rows
  Svc->>DB: find key and isActive true
  alt found
    Svc-->>API: config
    API-->>Hire: 200 value
  else missing or inactive
    API-->>Hire: 404 config not found
  end

3. Expired-slot cleanup — manual and scheduled

Mentor slots that expire without ever being booked accumulate noise. An admin can run cleanup synchronously, or enqueue it as a background job. The same logic runs automatically every 15 minutes via the scheduled cleanup queue.

sequenceDiagram
  participant FE as Admin UI
  participant API as CleanupController
  participant Clean as SlotCleanupService
  participant Q as QueueService
  participant W as cleanup.worker
  participant DB as PostgreSQL

  alt run now
    FE->>API: DELETE /api/cleanup/slots
    API->>Clean: cleanupExpiredUnbookedSlots
    Clean->>DB: delete expired unbooked Slots
    Clean-->>API: deletedCount and details
    API-->>FE: 200 result
  else enqueue
    FE->>API: POST /api/cleanup/schedule
    API->>Q: addCleanupJob type cleanupExpiredSlots
    Q-->>API: queued
    API-->>FE: 200 job added
    Q->>W: deliver job
    W->>Clean: cleanupExpiredUnbookedSlots
    Clean->>DB: delete expired unbooked Slots
  end

  Note over Q,W: also runs every 15 minutes from scheduleSlotCleanup on startup

Inspection helpers: GET /api/cleanup/statistics and /api/cleanup/slots/:filter let an admin preview what would be affected before deleting.

4. Issue reporting and admin triage

Any user (or anonymous visitor) files an issue. The x-platform header tags which product it came from. Admins later list, filter, and resolve issues; payment disputes are a special issueType.

sequenceDiagram
  participant U as User or visitor
  participant API as IssuesController
  participant Svc as IssuesService
  participant DB as PostgreSQL
  participant Admin as Admin UI
  participant AC as AdminController

  U->>API: POST /api/issues name email issueType description
  API->>API: set platform from x-platform header
  alt missing required fields
    API-->>U: 400 fields required
  else ok
    API->>Svc: createIssue
    Svc->>DB: insert Issues row status open
    Svc-->>API: saved issue
    API-->>U: 201 created
  end

  Note over Admin,DB: triage
  Admin->>AC: GET /api/admin/issues filters and pagination
  AC->>Svc: getGeneralIssues excludes payment type
  Svc->>DB: query with filters join user
  Svc-->>AC: issues and total
  AC-->>Admin: list
  Admin->>AC: PUT /api/admin/issues/:id/status resolved
  AC->>Svc: updateIssueStatus
  Svc->>DB: save new status
  Svc-->>AC: updated
  AC-->>Admin: 200 updated

5. Health check and queue monitoring

On-call tooling and load balancers hit /api/health; engineers open Bull Board for queue depth and failed jobs.

sequenceDiagram
  participant LB as Load balancer or on-call
  participant API as HealthController
  participant H as HealthService
  participant DB as PostgreSQL
  participant R as Redis
  participant Ops as Engineer
  participant BB as Bull Board

  LB->>API: GET /api/health
  API->>H: getHealthStatus
  H->>DB: isConnected
  H->>R: client status ready
  H-->>API: status, uptime, env, db, redis
  API->>H: isHealthy
  alt healthy
    API-->>LB: 200 data
  else db or redis down
    API-->>LB: 503 service unavailable
  end

  Ops->>BB: GET /admin/queues
  BB->>BB: HTTP Basic auth check
  alt bad credentials
    BB-->>Ops: 401 authenticate
  else ok
    BB->>R: read BullMQ queue state
    BB-->>Ops: queue dashboard
  end

6. Redis inspection (debugging)

sequenceDiagram
  participant Ops as Admin or engineer
  participant API as RedisController
  participant R as Redis

  Ops->>API: GET /api/redis/keys
  API->>R: getAllKeysAndValues
  R-->>API: keys and values
  API-->>Ops: 200 data
  Ops->>API: DELETE /api/redis/keys/:key
  API->>R: delete key
  alt key existed
    R-->>API: count 1
    API-->>Ops: 200 deleted
  else missing
    R-->>API: count 0
    API-->>Ops: 404 key not found
  end

Background jobs & async

This domain both consumes and triggers BullMQ work. Queues are created by QueueService (singleton) and surfaced in Bull Board.

Queue / worker Trigger Schedule Source
cleanupQueuecleanup.worker POST /api/cleanup/schedule and startup scheduler scheduleSlotCleanup() runs every 15 minutes (src/index.ts) src/services/SlotCleanupService.ts
kpiQueuekpi.worker POST /api/admin/dashboard/refresh-kpi and startup scheduler scheduleKpiCalculation() every 15 minutes admin dashboard
emailQueueemail.worker bulk-upload expert onboarding (addEmailJob, 2s delay) on demand src/services/BulkUploadService.ts
  • Bull Board mounts the BullMQ adapters for every queue returned by QueueService.getQueues() at /admin/queues, behind HTTP Basic auth. Board title and logo are configured in src/routes/bullBoard.routes.ts.
  • There are no inbound webhooks or Socket.IO events specific to this domain; the bulk-upload process step is synchronous (it does not enqueue the user creation itself, only the follow-up email).

See Background jobs & queues for the full queue/worker catalog.


External integrations

Integration Where Env vars Failure / fallback
mr-hire-backend SystemConfigController.refreshMrHireProvider POSTs to /call-provider/refresh MR_HIRE_BACKEND_URL (default http://localhost:8001) 5s timeout; on failure responds 200 with a "refresh failed (non-critical)" message so the config save is never blocked.
AWS S3 Issue attachmentUrls / imageUrl are pre-uploaded URLs AWS_* (see backend env) URLs stored as-is; no upload happens inside the issues flow.
Redis Health check, Redis admin, BullMQ backing store REDIS_HOST, REDIS_PORT Health returns 503 when Redis status is not ready.
PostgreSQL All persistence DB_* Health returns 503 when DB not connected.
xlsx library Bulk parse + template generation n/a Parse errors surface as 500 with the parser message.

Feature flags / runtime config

SystemConfig is the project's feature-flag and runtime-config mechanism. Flags are plain key/value rows; isActive=false hides a key from the public getByKey reader (a soft kill-switch). /api/system-config/enabled-call-providers is a specialised reader handled by HrCallConfigController and is intentionally declared before the :key route to avoid being shadowed.


Status lifecycles

Issue status

stateDiagram-v2
  [*] --> open
  open --> in_progress
  in_progress --> resolved
  open --> resolved
  resolved --> [*]

  state "payment issue track" as pay {
    [*] --> pending
    pending --> paid
    pending --> failed
    failed --> pending
  }

IssueStatus is a single enum shared by both general issues (openin-progressresolved) and payment disputes (pendingpaid / failed). The code does not enforce the transitions; updateGeneralQueryStatus only validates that the new value is a member of the enum.

SystemConfig active state

stateDiagram-v2
  [*] --> active
  active --> inactive : "set isActive false"
  inactive --> active : "set isActive true"
  active --> [*] : "delete by id"
  inactive --> [*] : "delete by id"
  note right of inactive
    getByKey returns null
    when not active
  end note

Edge cases, limits & gotchas

  • Hardening note (internal). A security/hardening observation for this area is tracked in the team's private notes (internal/security-and-hardening-notes.md) and is intentionally not published on this site.
  • Hardening note (internal). A security/hardening observation for this area is tracked in the team's private notes (internal/security-and-hardening-notes.md) and is intentionally not published on this site.
  • Hardening note (internal). A security/hardening observation for this area is tracked in the team's private notes (internal/security-and-hardening-notes.md) and is intentionally not published on this site.
  • Bulk upload is two uploads, not one. Both /preview and /process require the file in the multipart/form-data body; /process does not reuse the previously parsed file. fieldMappings arrives as a JSON string (FormData) and is parsed server-side.
  • Per-row isolation, not all-or-nothing. processBulkUpload runs each user creation in its own queryRunner transaction; a duplicate email or missing required field fails that row only and is reported in errors[]. The endpoint returns 200 if at least one row succeeded, else 400.
  • File limits. Multer caps uploads at 10 MB and accepts only .xlsx, .xls, .json (by mimetype or extension).
  • email is the only mandatory mapping. validateMappings only requires email; expert-specific requirements (company, role, institute) are enforced later in BulkUploadService.createExpert and surface as row errors.
  • graduationYear sanitation. Only accepted when it parses to an int between 1900 and 2100 (applyMappings); otherwise dropped silently.
  • getIssueById casts the id to Number. The Issues PK is an int; passing a non-numeric id yields a NaN lookup (no match) rather than a validation error.
  • Multi-platform tagging. New issues default platform to the x-platform header value, falling back to mr mentor. Admin list endpoints can filter by platform, userRole, status, type, resolution, date range, and free-text search.
  • refresh-mr-hire never fails loud. Even on a network error it returns HTTP 200 so the admin's config save is treated as successful.
  • Cleanup DELETE /slots is synchronous and destructive. It deletes rows immediately; use GET /slots/:filter first to preview. Scheduled cleanup runs the same logic every 15 minutes regardless.
  • Warning is not owned here. It is referenced for completeness; its lifecycle (acknowledge/resolve) belongs to the student-engagement domain.