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:
- Internal uEngage admin (
adminController.js, guarded byadminMiddlewareβbusiness_id == 1β ) β cross-brand leaderboards, campaign dashboards, and the business wallet ledger (credits/debits for SMS/WhatsApp spend) across every tenant. - Brand-facing business (
businessController.json/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 hardcodedbusiness_id == 1gate.
2. Business Flow¶
2.1 Example journey β internal admin reviews platform health π»¶
- Admin (a uEngage staff user whose
business_id == 1) opens the internal console. POST /crm_api/get/leaderboardβadminMiddlewareverifiesbusiness_id == 1β , thenadminController.leaderBoardranks brands by campaign volume fromcampaign_mis+business_aggreator. β- Admin drills into campaign dashboard (
campaignDashboard) β sends/redemptions/new-customer counts across brands (campaign_mis,dashboard_mis,wallet_transactions). β - 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 β π¶
- Brand admin calls
POST /crm_api/v2/gupshup/generatePartnerTokenwithparent_business_id. businessController.generatePartnerTokenlogs into the Gupshup Partner API (partner.gupshup.io/partner/account/login), fetches the app token, caches it inbusiness_configs.gupshup_partner_token(24h refresh). βgetProfileDetailsreturns the WhatsApp business profile from Gupshup.- 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 π»¶
- Diner messages the brand's WhatsApp number β
helpDeskController.startChatopens a session inwhatsapp_chat_bot, seeds the FAQ/agent flow fromagent_flows. β processCustomerReponsewalks flow nodes; if it needs order data it calls internal order APIs with a service token. β- 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 β
adminMiddlewarereturns403 {"message":"not authorized"}becausebusiness_id != 1. β /crm_api/v2/dashboarDatahas NO auth middleware β οΈ β the route incrmv2.jsis mounted withoutauthMiddleware. Treat as a security gap (see Β§13).- AI generation with low wallet β
aiController.generateResultrefuses ifwallet_balance < βΉ10. β - Salon collections are per-tenant β
customer_item_dump_{parentId},salon_mis_{parentId}β a wrongparentIdsilently 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_ideverywhere; 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_transactionswill 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 == 1can call admin endpoints (hardcoded inadminMiddleware). 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_transactionsviadeclareWinner, FCM notify). β
9.4 Limits / configs β ¶
- AI: min balance βΉ10; 3 completions per request (
n:3); cost = token usage Γ per-1k rates frombusiness_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_transactionsacross all tenants β the heaviest reads in this module. β οΈ No indexes β add(parent_business_id, created_at)and paginate (page_countalready 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
dashboardControllerNewcaching (see Dashboard-Analytics Β§10). - MySQL pools: admin/business web reads use
webPool(limit 15);adminMiddlewareopens 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. β requestLoggerflags any plaintext password in body/query (relevant sinceadminMiddlewareaccepts 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)vsbusiness_configs.wallet_balanceper brand β drift = billing bug. - AI spend: watch
wallet_transactionswhereservice_id:21for spikes/failed conversions. - Gupshup token health: stale
gupshup_partner_token(>24h) β WhatsApp provisioning failures. - Help-desk backlog: open
whatsapp_chat_botsessions 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 == 1inadminMiddlewareβ a hardcoded super-admin, not a role. β οΈβ - Where's the business (prepaid) wallet stored?
business_configs.wallet_balance, ledgered inwallet_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.jsmounts 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_businesscreatesaddo_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
Related Modules¶
- Dashboard-Analytics β
businessController.dashboardDatareuses the dashboard MIS/aggregator; live chat surfaces viadashboardControllerNewSocket.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_misin 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¶
-
Close the auth gap. Add
authMiddleware(or a proper business-scoped guard) to the three/crm_api/v2routes incrmv2.js, verifydashboardData/Gupshup calls still work for a legitimate brand token, and document the before/after. Deliverable: diff + test evidence. -
AI billing reconciliation script. Write a job that, per brand, sums
wallet_transactions(service_id:21) and flags any debit lacking a corresponding successfulai_promptsentry (the "charged but no result" case). Deliverable: script + sample findings + a proposed idempotency fix. -
Admin leaderboard pre-aggregation. Prototype a nightly rollup (mirroring
aggregationProcess) that materializes cross-brand campaign/wallet leaderboards into a dedicated collection, and repointadminController.leaderBoardto read it. Deliverable: cron + read-path change + latency comparison.