Skip to content

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:

  1. In production, the same logic runs inside the main someli-api process. The directory someli-api/dashboard/ is the production copy. Its routes/index.js is mounted onto the main Express app at /auth (per someli-api/CLAUDE.md).
  2. In someli-dashboard-be (this repo), the same code can run standalone on port 6001 under /dashboard/*, using a mock/ directory in place of the main someli-api's real database and helpers when NODE_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, per diff -rq between 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.