Architecture Overview¶
Purpose¶
A small Express service that exposes the analytics endpoints used by the customer-facing dashboard (account follower growth, post engagement, top posts, ROI views, leaderboards). The code lives in two places:
- In production, the same logic runs inside the main
someli-apiprocess. The directorysomeli-api/dashboard/is the production copy. Itsroutes/index.jsis mounted onto the main Express app at/auth(persomeli-api/CLAUDE.md). - In
someli-dashboard-be(this repo), the same code can run standalone on port 6001 under/dashboard/*, using amock/directory in place of the mainsomeli-api's real database and helpers whenNODE_ENV=development.
This document is about the standalone repo; for the production behaviour see someli-api/dashboard-analytics.md.
Folder layout¶
someli-dashboard-be/
├── conf.js # 8 lines — loads .env, exports MySQL connection settings
├── server.js # 17 lines — boots Express, mounts /dashboard
├── package.json # 13 deps; no devDeps; one script ("start": "node server.js")
├── README.md # branching workflow (dev → uat → main)
├── routes/
│ └── index.js # 974 lines — 9 GET endpoints
├── services/
│ ├── job_account_insights.js # 528 lines — helper for account-level insights
│ ├── job_growth_insights.js # 40 lines — helper for growth insights
│ └── job_post_insights.js # 520 lines — helper for per-post insights
└── mock/
├── server.js # provides `appData` stub
├── routes.js # provides `con` (sync-mysql connection) stub
├── helper.js # provides `checkuseraccount`, `getImageFromS3` stubs
├── action.js # provides `apiAction` class stub
└── middlewares/
└── auth.js # provides auth middleware stub
There is no actions/, no helper/, no modules/dbDriver/ directly in this repo — those live in someli-api/ and are require()'d at runtime via relative paths (require("../../helper/helper"), etc.) when this repo runs in production mode. In local-dev mode, the mock/ files are required instead.
Entry point — server.js¶
require("dotenv").config();
const express = require("express");
const router = require("./routes");
const expressSession = require('express-session');
const PORT = process.env.port || 6001;
const app = express();
app.use(expressSession({
secret: process.env.SESSION_SECRET || "change-me",
resave: false,
saveUninitialized: true
}));
app.use("/dashboard", router);
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Compared with someli-api/dashboard/server.js, the standalone one is missing CORS configuration. In the in-process version (someli-api/dashboard/server.js), CORS is enabled with a wildcard origin and app.options('*', ...) is registered. The standalone version inherits no CORS, so any cross-origin call to port 6001 will be blocked unless a reverse proxy adds CORS headers.
Routing pattern¶
routes/index.js opens with a runtime branch:
const isLocal = process.env.NODE_ENV === "development";
let auth, apiAction, appData, con, checkuseraccount, getImageFromS3;
if (isLocal) {
auth = require("../mock/middlewares/auth");
apiAction = require("../mock/action");
({ appData } = require("../mock/server"));
({ con } = require("../mock/routes"));
({ checkuseraccount, getImageFromS3 } = require("../mock/helper"));
} else {
apiAction = require("../../actions/actions");
({ appData } = require("../../server"));
({ con } = require("../../routes/routes"));
({ checkuseraccount, getImageFromS3 } = require("../../helper/helper"));
}
The ../../ in the "production" branch means this file expects to be checked out inside the someli-api tree (specifically at someli-api/dashboard/routes/index.js). The standalone repo's routes/index.js is therefore only runnable when NODE_ENV=development (i.e., with the mocks). To run it against the real database, the file would need to be copied into the someli-api checkout — which is exactly what happens in production.
Endpoints¶
| Verb | Path | Purpose |
|---|---|---|
| GET | /dashboard/getAccountInsights/:startTime/:endTime/:pId |
Aggregated account insights (followers, engagement) for a date range |
| GET | /dashboard/getAccountFollowers/:startTime/:endTime/:pId |
Followers time-series |
| GET | /dashboard/getTopPostsInsights/:startTime/:endTime/:pId/:cId |
Top posts by metric for a date range |
| GET | /dashboard/getPostsLikes/:startTime/:endTime/:pId |
Likes time-series |
| GET | /dashboard/getRoiViews |
ROI views |
| GET | /dashboard/getPostsViews/:startTime/:endTime/:pId |
Views time-series |
| GET | /dashboard/getPostsEngagements/:startTime/:endTime/:pId |
Engagements time-series |
| GET | /dashboard/getPostInsights/:startTime/:endTime/:pId |
Per-post insights |
| GET | /dashboard/getPostsTotalEngagements/:startTime/:endTime/:pId |
Total engagements for a date range |
:pId is a JSON-encoded array of provider_Ids (e.g., [1,3,4] for Facebook + Instagram + LinkedIn). :startTime / :endTime are date strings parsed by moment.utc(...).
The production copy in someli-api/dashboard/ has an additional endpoint not present in this standalone:
- GET
/leaderboard/:pId— paginated, search-capable, ranks members by followers and growth (1615 lines added in the production version, perdiff -rqbetween the two)
This is the major drift between the two copies.
Data access¶
All queries use sync-mysql (con.query(...)) — blocking SQL calls. This is the same pattern as the main someli-api and designer-api, and it carries the same liveness risk: a slow query blocks the entire Node event loop, including all other in-flight HTTP requests. For an analytics endpoint that's expected to run aggregations, this is a non-trivial concern.