Skip to content

Admin & Business

One-line: The operational and multi-tenant control plane of Prism β€” how uEngage internal admins see the whole platform (leaderboards, campaign/wallet dashboards across all brands), how a brand sees its own parent/child business dashboard and provisions WhatsApp, plus the collection of code-only verticals (Sales CRM, Help Desk live chat, QR attribution, Gamification, Salon reports, AI insights) that don't appear on the product feature Excel but ship in the same monolith.

Validation legend

Symbol Meaning
βœ… Verified directly in code
πŸ“„ From PM Excel / business source of truth
πŸ’» Code-only (no Excel/business spec)
♻️ Dual / duplicated implementation
⚰️ Legacy / being deprecated
⚠️ Known bug / risk / caveat

1. Overview

"Admin & Business" is really two audiences sharing one code neighbourhood:

  1. Internal uEngage admin (adminController.js, guarded by adminMiddleware β†’ business_id == 1 βœ…) β€” cross-brand leaderboards, campaign dashboards, and the business wallet ledger (credits/debits for SMS/WhatsApp spend) across every tenant.
  2. Brand-facing business (businessController.js on /crm_api/v2 βœ…) β€” a single parent/child dashboard payload, and Gupshup partner-token provisioning for WhatsApp.

Around these sit 6 code-only verticals πŸ’» that the PM feature Excel does not enumerate but which are live in the monolith and matter operationally:

Vertical Controller What it is
Sales CRM leadController.js βœ… uEngage's own sales pipeline (leads β†’ contacts β†’ convert to business)
Help Desk helpDeskController.js βœ… WhatsApp live-chat + agent flows + FAQ bot
QR / Attribution qrCodeController.js βœ… QR generation, scan tracking, reward attribution
Gamification gamingController.js βœ… Games/contests with wallet-point prizes
Salon vertical salonController.js βœ… Salon/spa reports incl. GSTR-1 & membership
AI insights aiController.js βœ… LLM-generated campaign copy with wallet deduction

Multi-tenancy is the through-line: every operation is scoped by parent_business_id (brand HQ) + child_business_id/business_id (outlet). The admin surface is the only place that crosses tenant boundaries β€” and it does so behind the hardcoded business_id == 1 gate.


2. Business Flow

2.1 Example journey β€” internal admin reviews platform health πŸ’»

  1. Admin (a uEngage staff user whose business_id == 1) opens the internal console.
  2. POST /crm_api/get/leaderboard β†’ adminMiddleware verifies business_id == 1 βœ…, then adminController.leaderBoard ranks brands by campaign volume from campaign_mis + business_aggreator. βœ…
  3. Admin drills into campaign dashboard (campaignDashboard) β€” sends/redemptions/new-customer counts across brands (campaign_mis, dashboard_mis, wallet_transactions). βœ…
  4. Admin opens the wallet ledger (showWalletLedger / walletLedger) β€” every credit/debit against a brand's prepaid balance (wallet_transactions + business_configs.wallet_balance). βœ…

2.2 Example journey β€” brand provisions WhatsApp βœ…πŸ“„

  1. Brand admin calls POST /crm_api/v2/gupshup/generatePartnerToken with parent_business_id.
  2. businessController.generatePartnerToken logs into the Gupshup Partner API (partner.gupshup.io/partner/account/login), fetches the app token, caches it in business_configs.gupshup_partner_token (24h refresh). βœ…
  3. getProfileDetails returns the WhatsApp business profile from Gupshup.
  4. Maps to the Go-Live Checklist step "WhatsApp setup β†’ WABA β†’ templates" (Business-Flow Β§1). πŸ“„

2.3 Example journey β€” a diner asks for a refund over WhatsApp πŸ’»

  1. Diner messages the brand's WhatsApp number β†’ helpDeskController.startChat opens a session in whatsapp_chat_bot, seeds the FAQ/agent flow from agent_flows. βœ…
  2. processCustomerReponse walks flow nodes; if it needs order data it calls internal order APIs with a service token. βœ…
  3. On escalation, a human agent picks up (agent state in whatsapp_chat_agents); the modern dashboard (dashboardControllerNew) surfaces the live chat via Socket.io. βœ… (see Dashboard-Analytics).

2.4 Edge cases

  • Non-admin hits admin route β†’ adminMiddleware returns 403 {"message":"not authorized"} because business_id != 1. βœ…
  • /crm_api/v2/dashboarData has NO auth middleware ⚠️ β€” the route in crmv2.js is mounted without authMiddleware. Treat as a security gap (see Β§13).
  • AI generation with low wallet β†’ aiController.generateResult refuses if wallet_balance < β‚Ή10. βœ…
  • Salon collections are per-tenant β†’ customer_item_dump_{parentId}, salon_mis_{parentId} β€” a wrong parentId silently reads an empty/other collection. ⚠️

3. Technical Flow

3.1 Admin surface (source β†’ auth β†’ logic β†’ DB β†’ response) βœ…

Internal console
  β”‚  POST /crm_api/get/leaderboard { business_id:1, userId, filters }
  β–Ό
adminMiddleware βœ…  (business_id == 1 check; token OR bcrypt password; fresh MySQL conn)
  β–Ό
adminController.leaderBoard
  β”‚  aggregate campaign_mis (group by business_id) + join business_aggreator + business_configs
  β–Ό
{ status_code:200, status:true, result:{ customer_data:[...], total_count } }

No queues/events/notifications on the admin read path β€” pure cross-tenant reads.

3.2 Brand dashboard (/crm_api/v2) βœ…

Brand frontend
  β”‚  POST /crm_api/v2/dashboarData { parent_business_id, selectedBId, date_filter }
  β–Ό
businessController.dashboardData   ⚠️ NO middleware in crmv2.js
  β”‚  reads business_configs (wallet_details, dashboard_fields)
  β”‚       buisness_aggregator (customer cohorts)
  β”‚       dashboard_mis (KPIs) + rating_mis + checkin_mis
  │       dashboard_modules (field→display-name map)
  β–Ό
{ result:{ customerData, wallet_details, check_in_modules, count_obj, amount_obj } }

3.3 AI insights with wallet deduction (source β†’ API β†’ external β†’ billing β†’ response) βœ…

POST /crm_api/generate/result { parent_business_id, prompt, campaign_type, filters }  [authMiddleware]
  β–Ό
aiController.generateResult
  β”‚  guard: business_configs.wallet_balance >= β‚Ή10
  β”‚  POST https://api.openai.com/v1/chat/completions  (model/keys from business_configs: ai_auth, ai_model)
  β”‚  cost = (prompt_tokens/1000)*ai_cost_input + (completion_tokens/1000)*ai_cost_output   (USD)
  │  USD→INR via api.exconvert.com ; +18% GST if gst_deduction != 0
  β”‚  DEBIT business_configs.wallet_balance
  β”‚  INSERT wallet_transactions { service_id:21, service_name:"prism ai", type:debit }
  β–Ό
{ status:true, data:[3 completions], prompt }

This is the module's one write + billing path. Others (leads, gaming winners) also touch wallet_transactions.


4. Architecture Diagram

flowchart TB
    subgraph clients["Callers"]
        ADMIN([uEngage internal admin])
        BRAND([Brand / outlet staff])
        DINER([Diner via WhatsApp])
        APP([Customer app])
    end

    subgraph auth["Auth"]
        AM[adminMiddleware βœ…<br/>business_id == 1]
        AUTH[authMiddleware βœ…]
        NONE[NONE ⚠️]
    end

    subgraph ctrl["Controllers"]
        AC[adminController βœ…<br/>leaderBoard/campaignDashboard/walletDashboard/ledger]
        BC[businessController βœ…<br/>dashboardData/generatePartnerToken]
        LC[leadController πŸ’» Sales CRM]
        HD[helpDeskController πŸ’» live chat]
        QR[qrCodeController πŸ’» attribution]
        GM[gamingController πŸ’» contests]
        SL[salonController πŸ’» reports]
        AI[aiController πŸ’» LLM + billing]
    end

    subgraph data["Datastores & services"]
        MG[(MongoDB: campaign_mis, business_aggreator,<br/>dashboard_mis, wallet_transactions,<br/>contacts_data, whatsapp_chat_bot, qr_codes,<br/>game_journeys, ai_prompts)]
        SQ[(MySQL: addo_business, users,<br/>addo_account_mapping, addo_forms_leads)]
        GUP[Gupshup Partner API]
        OAI[OpenAI API]
        S3[(AWS S3 cdn.uengage.io)]
        FCM[FCM]
    end

    ADMIN --> AM --> AC --> MG
    BRAND --> AUTH --> BC & LC & GM & SL & AI
    BRAND --> NONE --> BC
    DINER --> HD
    APP --> QR & GM
    BC --> GUP
    AI --> OAI
    AI --> MG
    HD --> S3
    QR --> S3
    GM --> FCM
    LC --> SQ
    AC & BC --> SQ

5. Folder Structure (real files) βœ…

controllers/
  adminController.js       βœ… cross-tenant admin: leaderBoard, campaignDashboard, walletDashboard,
                              showWalletLedger, walletLedger, getParentBusiness, getBusinessTransactions,
                              deleteTemplateForSms
  businessController.js    βœ… brand dashboard + Gupshup: dashboardData, generatePartnerToken, getProfileDetails
  leadController.js        πŸ’» Sales CRM (17 fns): convertLeadToContact, createContactManually, getAllContacts,
                              viewContactDetails, updateContact, mergeContacts, convert_contact_to_business,
                              getSalesMis, changeLeadStatus, upcomingFollowups, …
  helpDeskController.js    πŸ’» processCustomerReponse, startChat, uploadMedia, authenticate, customerChat,
                              getChat, getFaqs
  qrCodeController.js      πŸ’» createQrCode, scanQRCode, getCustomers, qrCodeDetails, getRewards
  gamingController.js      πŸ’» getGames, saveGame, getRunningGames, getGameUsers, declareWinner, saveGameEntry, …
  salonController.js       πŸ’» productReport, serviceReport, genderReport, gstr1Report, membershipReport,
                              incentiveReport, appointmentReport, dayWiseSales, saveOrders, … (~25 fns)
  aiController.js          πŸ’» getFilters, generateResult, regenerateResult, recentPrompts

middlewares/
  adminMiddleware.js       βœ… business_id == 1 gate (fresh MySQL connection per request)

routes/
  crmv2.js                 βœ… /crm_api/v2 (businessController) β€” ⚠️ no middleware
  crm.js                   βœ… hosts admin/lead/helpdesk/gaming/salon/ai routes
  prism_routes/qr_code_routes.js  βœ… /prism/qr_code (qrCodeController)

models/
  buisness_aggregator.js   βœ… business_aggreator (customer cohorts)
  business_campaigns_config.js βœ… business_config (wallet/campaign config)
  campaign_mis.js          βœ… campaign_mis
  parent_business.js       βœ… Parent_Id (org status registry)

6. Database

6.1 Admin / business collections & tables βœ…

Store Name Purpose Key fields / keys
MongoDB campaign_mis Campaign delivery MIS (per brand) parent_business_id, business_id, total_campaigns_sent, total_sms_sent, total_sms_price
MongoDB business_aggreator Customer cohort counts businessId, totalUser, active_user, lost_users
MongoDB business_config(business_campaigns_config.js) Wallet + campaign config parent_business_id, child_business_id, wallet_balance, sms_cost, gupshup_partner_token, ai_auth, ai_model, dashboard_fields
MongoDB wallet_transaction(wallet_transactions.js) Business wallet ledger transaction_type (credit/debit), amount, parent_business_id, child_business_id, campaign_id
MongoDB dashboard_mis Daily KPI rollup see Dashboard-Analytics Β§6.2
MongoDB dashboard_module Widget field→label registry display_name, value
MongoDB Parent_Id(parent_business.js) Parent org status parent_business_id, business_status
MySQL addo_business Merchant master (used by adminMiddleware) id, parentId, categoryId, status
MySQL users, roles, addo_account_mapping Auth / user-business mapping userId, roleId, businessId

6.2 Vertical-specific collections/tables πŸ’» βœ…

Vertical Collections / tables
Sales CRM (leadController) MongoDB contacts_data, sales_lead_mis; MySQL addo_forms_leads, addo_business, addo_account_mapping, users, addo_raise_concern
Help Desk (helpDeskController) agent_flows, whatsapp_chat_bot, whatsapp_chat_bot_messages, whatsapp_chat_agents, help_desk_faqs
QR (qrCodeController) qr_codes, qr_scans_customer_mapping, whatsapp_ordering_business, business_configs, rewards_{parentId}
Gaming (gamingController) game_types, game_journeys, game_user_collection, wallet_config, wallet_transactions
Salon (salonController) customer_item_dump_{parentId}, customer_salon_dump_{parentId}, salon_mis_{parentId}
AI (aiController) ai_filters, ai_prompts, business_configs

6.3 Relationships, keys, indexes

  • Multi-tenant key: parent_business_id + child_business_id/business_id everywhere; admin queries deliberately omit the tenant filter to span all brands.
  • ⚠️ Type inconsistency on tenant ids (Number vs String) β€” same caveat as Dashboard-Analytics.
  • ⚠️ Per-tenant collections (*_{parentId}) mean no cross-tenant leakage but also no shared index strategy; a wrong id reads an empty collection rather than erroring.
  • ⚠️ Most collections have no explicit indexes (map-03 Β§5.1); admin cross-brand aggregations over campaign_mis/wallet_transactions will slow as data grows β€” add (parent_business_id, created_at).

6.4 Common queries

// Admin leaderboard β€” cross-tenant campaign ranking
db.collection("campaign_mis").aggregate([
  { $match: { created_at: { $gte: start, $lte: end } } },
  { $group: { _id: "$business_id", total_campaigns_sent: { $sum: "$total_campaigns_sent" } } },
  { $sort: { total_campaigns_sent: -1 } }, { $limit: pageCount }
]);

// Wallet ledger for one brand
db.collection("wallet_transaction").find({
  parent_business_id: pid, transaction_type: { $in: ["credit","debit"] }
}).sort({ inserted_date: -1 });

// AI billing insert
db.collection("wallet_transaction").insertOne({
  transaction_type:"debit", amount: inrCost, service_id:21, service_name:"prism ai",
  parent_business_id: pid, inserted_date: now
});

7. APIs

7.1 Admin (/crm_api/*, adminMiddleware β†’ business_id == 1) βœ…

Route Method Auth Body Controller
/crm_api/get/leaderboard (verify path) POST adminMiddleware page_count, *_filter, date_filter, start_date, end_date adminController.leaderBoard
campaign dashboard (verify path) POST adminMiddleware page_count, filters, date_filter adminController.campaignDashboard
wallet dashboard (verify path) POST adminMiddleware page_count, parent_filter, business_filter adminController.walletDashboard
wallet ledger (verify path) POST adminMiddleware page_count, parent_business_id, child_business_id, date_filter adminController.showWalletLedger / walletLedger

Exact admin route strings live in routes/crm.js β€” verify in code; the controller functions above are confirmed exports. βœ…

7.2 Brand business (/crm_api/v2) βœ…

Route Method Auth Body Controller
/crm_api/v2/dashboarData POST none ⚠️ parent_business_id, selectedBId, date_filter, start_date, end_date businessController.dashboardData
/crm_api/v2/gupshup/generatePartnerToken POST none ⚠️ parent_business_id businessController.generatePartnerToken
/crm_api/v2/gupshup/getPartnerProfile POST none ⚠️ parent_business_id businessController.getProfileDetails

7.3 Verticals (representative β€” verify full lists in routes/crm.js) βœ…

Vertical Representative route Auth Controller fn
Sales CRM /crm_api/convert/lead/to/contact authMiddleware leadController.convertLeadToContact
Sales CRM /crm_api/get/all/contacts authMiddleware leadController.getAllContacts
Help Desk /crm_api/start/chat none ⚠️ helpDeskController.startChat
Help Desk /crm_api/authenticate jwtMiddleware helpDeskController.authenticate
Help Desk /crm_api/upload/media multer, none ⚠️ helpDeskController.uploadMedia
QR /prism/qr_code/create/qr_code authMiddleware qrCodeController.createQrCode
QR /prism/qr_code/scan/:qrCodeId none (public scan) qrCodeController.scanQRCode
Gaming /crm_api/save/game/config authMiddleware gamingController.saveGame
Gaming /crm_api/declare/winner authMiddleware gamingController.declareWinner
Salon /crm_api/get/gstr1/details authMiddleware salonController.gstr1Report
Salon /crm_api/get/membership/report authMiddleware salonController.membershipReport
AI /crm_api/generate/result authMiddleware aiController.generateResult

7.4 Representative payload β€” POST /crm_api/v2/gupshup/generatePartnerToken βœ…

Request

POST /crm_api/v2/gupshup/generatePartnerToken
Content-Type: application/json

{ "parent_business_id": 42 }

Response

{ "status_code": 200, "status": true, "message": "records fetched successfully",
  "result": { "partner_token": "sk_...", "app_id": "..." } }

Validation & failures: missing parent_business_id β†’ error; Gupshup login failure β†’ surfaced as status:false; token cached 24h in business_configs.gupshup_partner_token. βœ…

7.5 AI generate β€” failures βœ…

  • wallet_balance < β‚Ή10 β†’ refused before any OpenAI call.
  • OpenAI error β†’ no debit; error returned.
  • exconvert (USDβ†’INR) failure ⚠️ β†’ cost conversion may fail; verify fallback in code.

8. Code Walkthrough

8.1 adminMiddleware.js βœ…

adminMiddleware(req,res,next)
  β†’ if (req.body.business_id != '1') β†’ 403 "not authorized"
  β†’ fresh MySQL connection (mysqlConnection.js) β€” NOT the pool
  β†’ SELECT users JOIN roles JOIN addo_account_mapping JOIN addo_business JOIN business_category
       WHERE userId=? AND b.id=? AND all status='1'
  β†’ validate token (users.apiToken) OR bcrypt(password)
  β†’ set req.body.created_by = userName ; next()
  β†’ else 403 "invalid credentials" / 500 (fails closed)

8.2 adminController call hierarchy βœ…

leaderBoard        β†’ campaign_mis aggregate + business_aggreator + business_configs
campaignDashboard  β†’ campaign_mis + dashboard_mis + wallet_transactions
walletDashboard    β†’ business_configs.wallet_balance + wallet_transactions
showWalletLedger   β†’ wallet_transactions (paged, date-filtered) + business_configs
getParentBusiness  β†’ business_configs / Parent_Id

8.3 businessController.generatePartnerToken βœ…

generatePartnerToken(req)
  β†’ read business_configs { gupshup_app_id, gupshup_app_token }
  β†’ generateAccessToken(): POST partner.gupshup.io/partner/account/login
  β†’ GET  /partner/app/{app_id}/token
  β†’ cache in business_configs.gupshup_partner_token (24h)
  β†’ return token

8.4 aiController.generateResult βœ… (deps: axios, OpenAI, exconvert)

Guard wallet β‰₯ β‚Ή10 β†’ OpenAI chat/completions (n:3, temp 0.7) β†’ compute token cost β†’ USDβ†’INR β†’ +18% GST β†’ debit business_configs.wallet_balance β†’ wallet_transactions (service_id 21).

8.5 helpDeskController βœ… (deps: AWS S3, axios, agent flows)

startChat seeds a whatsapp_chat_bot session from agent_flows; processCustomerReponse walks nodes (may call internal order APIs with service tokens); uploadMedia puts files to S3 cdn.uengage.io/uploads/{pid}/support/. Live-chat/agent hand-off is surfaced to the modern dashboard via Socket.io (in dashboardControllerNew). βœ…

8.6 salonController βœ…

Per-tenant dumps (customer_item_dump_{parentId}, salon_mis_{parentId}) β†’ report functions (product/service/gender/GSTR-1/membership/incentive/appointment) β†’ optional XLSX export via json2xls/node-xlsx when download flag set.


9. Business Rules

9.1 Admin authorization βœ…

  • Only business_id == 1 can call admin endpoints (hardcoded in adminMiddleware). This is a super-admin convention, not a role table. ⚠️ Hardcoded β€” flagged as a security concern (infra Β§8.3).
  • Admin views are the only cross-tenant reads; everything else is tenant-scoped.

9.2 Multi-tenancy (parent/child) πŸ“„βœ…

  • Parent = brand HQ (parent_business_id); Child = outlet (child_business_id/business_id).
  • A parent dashboard aggregates its children; a child dashboard is scoped to one outlet (selectedBId).
  • Maps to the Go-Live Checklist "Create Parent ID β†’ Create Child ID(s)". πŸ“„

9.3 Wallet & billing βœ…

  • Business wallet = brand's prepaid balance in business_configs.wallet_balance; distinct from the customer loyalty wallet.
  • Debits: SMS/WhatsApp sends, and AI generation (service_id 21, "prism ai", +18% GST).
  • All movements ledgered in wallet_transactions.
  • Gaming winner prizes credit customer wallets (wallet_transactions via declareWinner, FCM notify). βœ…

9.4 Limits / configs βœ…

  • AI: min balance β‚Ή10; 3 completions per request (n:3); cost = token usage Γ— per-1k rates from business_configs.
  • Gupshup partner token: 24h cache/refresh.
  • Sales CRM: contact dedup via checkContactExists / mergeContacts.

10. Performance

  • Admin aggregations run per-request over campaign_mis / wallet_transactions across all tenants β€” the heaviest reads in this module. ⚠️ No indexes β†’ add (parent_business_id, created_at) and paginate (page_count already present). βœ…
  • Per-tenant salon collections keep working sets small (good for reads) but multiply collection count.
  • AI is bounded by the external OpenAI/exconvert latency, not DB.
  • Redis: not central here; the brand dashboard reuses dashboardControllerNew caching (see Dashboard-Analytics Β§10).
  • MySQL pools: admin/business web reads use webPool (limit 15); adminMiddleware opens a fresh single connection per request ⚠️ (not pooled) β€” a burst of admin logins can exhaust connections.

11. Logging

  • Winston (logger.info/error), daily-rotating logs. βœ…
  • requestLogger flags any plaintext password in body/query (relevant since adminMiddleware accepts password fallback). βœ…
  • External calls (Gupshup, OpenAI, exconvert, internal order APIs) should be logged with status + latency β€” verify coverage in code.
  • Wallet debits are auditable via wallet_transactions (the ledger is the log for billing).

12. Monitoring

  • Wallet integrity: reconcile sum(wallet_transactions debits/credits) vs business_configs.wallet_balance per brand β€” drift = billing bug.
  • AI spend: watch wallet_transactions where service_id:21 for spikes/failed conversions.
  • Gupshup token health: stale gupshup_partner_token (>24h) β†’ WhatsApp provisioning failures.
  • Help-desk backlog: open whatsapp_chat_bot sessions without an agent.
  • Auth failures: 403 rate on admin routes (possible probing given the hardcoded gate).

13. Troubleshooting

Issue Cause Fix Command
Admin route returns 403 business_id != 1 or bad token Use the super-admin account; verify token routes/crm.js + adminMiddleware.js
/crm_api/v2/dashboarData served without auth ⚠️ Route mounted with no middleware in crmv2.js Add authMiddleware to the route routes/crmv2.js
WhatsApp partner token fails Gupshup login/app creds wrong or expired Re-run generatePartnerToken; check gupshup_app_id/token db.business_config.findOne({parent_business_id:PID})
AI "insufficient balance" wallet_balance < β‚Ή10 Recharge business wallet db.business_config.find({parent_business_id:PID},{wallet_balance:1})
AI debit but no result / double debit exconvert or OpenAI mid-flow failure (non-atomic) ⚠️ Reconcile wallet_transactions service_id 21; add idempotency db.wallet_transaction.find({service_id:21,parent_business_id:PID})
Salon report empty Wrong parentId β†’ reads empty *_{parentId} collection Verify tenant id; check collection exists db.getCollectionNames().filter(n=>n.includes('salon_mis'))
Wallet balance β‰  ledger sum Non-atomic debit path Reconcile ledger vs wallet_balance aggregate wallet_transaction by type
Admin logins slow / conn errors adminMiddleware uses fresh non-pooled MySQL conn Route through pool / cap concurrency middlewares/adminMiddleware.js

14. FAQs

  • What makes someone an admin? business_id == 1 in adminMiddleware β€” a hardcoded super-admin, not a role. βš οΈβœ…
  • Where's the business (prepaid) wallet stored? business_configs.wallet_balance, ledgered in wallet_transactions. βœ…
  • Does AI cost the brand money? Yes β€” token cost β†’ USDβ†’INR β†’ +18% GST, debited on success (service_id 21). βœ…
  • Are the v2 routes authenticated? No β€” crmv2.js mounts them without middleware. ⚠️ Security gap.
  • Are the verticals in the product Excel? No β€” Sales CRM, Help Desk, QR, Gaming, Salon, AI are code-only πŸ’»; document them from code.
  • How is a lead turned into a live brand? leadController.convert_contact_to_business creates addo_business + mappings. βœ…

15. Cheat Sheet

ADMIN (business_id==1, adminMiddleware):
  adminController.leaderBoard / campaignDashboard / walletDashboard / showWalletLedger / walletLedger
  data: campaign_mis, business_aggreator, wallet_transactions, business_configs, dashboard_mis

BRAND (/crm_api/v2 β€” ⚠️ NO auth):
  businessController.dashboardData          β†’ business_configs+buisness_aggregator+dashboard_mis+rating_mis
  businessController.generatePartnerToken   β†’ Gupshup Partner API (24h token cache)

VERTICALS (πŸ’» code-only):
  leadController      Sales CRM     contacts_data, sales_lead_mis, MySQL addo_forms_leads/addo_business
  helpDeskController  live chat     agent_flows, whatsapp_chat_bot(_messages), S3, Socket.io
  qrCodeController    attribution   qr_codes, qr_scans_customer_mapping, S3 PNG
  gamingController    contests      game_journeys, game_user_collection, wallet_transactions, FCM
  salonController     reports       customer_item_dump_{pid}, salon_mis_{pid}, XLSX export
  aiController        LLM+billing   OpenAI, exconvert, DEBIT business_configs.wallet_balance (service_id 21, +18% GST)

MULTI-TENANCY: parent_business_id (brand) + child_business_id/business_id (outlet)
SUPER-ADMIN:   hardcoded business_id == 1

  • Dashboard-Analytics β€” businessController.dashboardData reuses the dashboard MIS/aggregator; live chat surfaces via dashboardControllerNew Socket.io.
  • Campaigns β€” admin leaderboard/campaign dashboard read campaign_mis.
  • Loyalty / [Wallet] β€” gaming prizes + AI billing touch wallet_transactions.
  • WhatsApp β€” Gupshup partner token + help-desk chat.
  • Feedback-NPS β€” rating_mis in the brand dashboard.
  • Database Β· Queues Β· Known Bugs Β· Roadmap

Knowledge Tests

Level 1 β€” MCQs

1. Who is authorized for admin endpoints? A) role = "Admin" B) any staff C) business_id == 1 (hardcoded in adminMiddleware) D) JWT scope Answer: C. βœ… Hardcoded super-admin gate.

2. Where is a brand's prepaid (SMS/WhatsApp/AI) balance stored? A) wallet (MySQL customer) B) business_configs.wallet_balance C) Redis D) dashboard_mis Answer: B. Ledgered in wallet_transactions.

3. AI generation billing includes: A) flat ₹1 B) token cost → USD→INR → +18% GST, debited on success C) no charge D) monthly subscription Answer: B. service_id 21, "prism ai".

4. Which routes are the security concern in this module? A) /stats/* B) /crm_api/v2/* (mounted without middleware) C) admin routes D) QR scan Answer: B. ⚠️ crmv2.js has no auth middleware.

5. Which are code-only verticals (not in the feature Excel)? A) Campaigns B) Loyalty C) Sales CRM, Help Desk, QR, Gaming, Salon, AI D) Dashboard Answer: C. πŸ’»

Level 2 β€” Scenarios

S1. A brand says "we were charged for AI copy we never got." Diagnose. Expected: Non-atomic AI billing β€” OpenAI/exconvert could fail after (or the debit fired without a usable result). Query wallet_transactions service_id:21 for the brand + timestamp; correlate with ai_prompts; if a debit has no matching successful completion, refund via a credit transaction and add idempotency/rollback around the debit. Confirm the β‚Ή10 guard and GST math.

S2. Internal admin leaderboard is timing out for the last quarter across all brands. Expected: Cross-tenant aggregation over campaign_mis with no index and no tenant filter. Add (created_at) / (business_id, created_at) index, ensure page_count pagination, consider pre-aggregating into an MIS collection (same pattern as Dashboard-Analytics). Also check adminMiddleware's non-pooled connection isn't compounding latency.

Level 3 β€” Code reading

Read adminMiddleware.js. (a) What exact condition rejects a non-admin? (b) Which connection does it use and why is that a risk? (c) What are the two auth factors it accepts? Expected: (a) req.body.business_id != '1' β†’ 403. (b) A fresh single MySQL connection (mysqlConnection.js), not the pool β†’ connection exhaustion under load. (c) users.apiToken token OR bcrypt password.

Level 4 β€” Architecture

The admin surface is the only cross-tenant reader in a system where parent_business_id/child_business_id scope everything. Explain the isolation model, why the business_id == 1 gate is both convenient and risky, and how you'd harden it without breaking the model. Expected: Tenant isolation via mandatory scoping keys on every query; admin deliberately drops the filter. The hardcoded gate is a single-line super-admin but has no role granularity, no audit, and is guessable/spoofable if business_id is client-supplied. Harden with a real role/permission check, server-derived identity (not body business_id), audit logging, and rate limiting β€” without changing the parent/child data model.

Level 5 β€” Prod debugging

Multiple brands report WhatsApp template/partner operations failing since this morning; generatePartnerToken returns errors. Expected: Check Gupshup Partner API status and whether gupshup_app_token/creds rotated; inspect cached gupshup_partner_token age (>24h stale); reproduce the partner.gupshup.io/account/login call and read the error; if Gupshup-side, coordinate rotation and force token refresh; add alerting on partner-token refresh failures. Confirm it's not the missing-auth /crm_api/v2 route being blocked upstream.


Practical Assignments

  1. Close the auth gap. Add authMiddleware (or a proper business-scoped guard) to the three /crm_api/v2 routes in crmv2.js, verify dashboardData/Gupshup calls still work for a legitimate brand token, and document the before/after. Deliverable: diff + test evidence.

  2. AI billing reconciliation script. Write a job that, per brand, sums wallet_transactions (service_id:21) and flags any debit lacking a corresponding successful ai_prompts entry (the "charged but no result" case). Deliverable: script + sample findings + a proposed idempotency fix.

  3. Admin leaderboard pre-aggregation. Prototype a nightly rollup (mirroring aggregationProcess) that materializes cross-brand campaign/wallet leaderboards into a dedicated collection, and repoint adminController.leaderBoard to read it. Deliverable: cron + read-path change + latency comparison.