Analytics Aggregation API โ Developer Guide
Overview
The Analytics Aggregation API provides platform administrators with real-time insights into revenue performance, order volume trends, and entity counts across the entire Mallnline platform. Exposed through the murchases subgraph (apps/murchases), these admin-only queries use MongoDB aggregation pipelines to compute metrics server-side with full multi-currency support.
| Component | Purpose | Backend |
|---|---|---|
| AnalyticsResolver | 4 admin-only aggregation queries | @Resolver() with dual-layer access |
| AnalyticsService | MongoDB $group, $dateToString, $sort pipelines |
Order model aggregation |
| AnalyticsFilter | Date range presets + custom filters | AnalyticsDateRange enum |
All operations are gated behind dual-layer access control: the @RequirePermission(Permission.MANAGE_ANALYTICS) decorator guard and a runtime PLATFORM_ADMIN role check.
Architecture
graph TD
A["AnalyticsResolver"] --> B["AnalyticsService"]
B --> C["Order Model (MongoDB)"]
A -- "admin header" --> D["@RequirePermission(MANAGE_ANALYTICS)"]
A -- "runtime" --> E["assertPlatformAdmin(actor)"]
subgraph "Aggregation Pipelines"
C -- "$group by date + currency" --> F["revenueByDay"]
C -- "$group by date + status" --> G["orderVolumeTrends"]
C -- "$group by status + countDocuments" --> H["platformEntityCounts"]
C -- "$group by maletId + currency, $sort" --> I["topMaletsByRevenue"]
end
style D fill:#dc2626,color:#fff
style E fill:#dc2626,color:#fff
style F fill:#3b82f6,color:#fff
style G fill:#3b82f6,color:#fff
style H fill:#3b82f6,color:#fff
style I fill:#3b82f6,color:#fff
How It Fits Together
The murchases subgraph owns the Order entity โ the authoritative source for all transaction data, including amounts, currencies, statuses, and Malet associations. The analytics API adds a privileged aggregation layer over this data:
- Multi-currency: Revenue is grouped by currency natively. A Kenyan Malet selling in KES will never have its revenue mixed with a South African Malet selling in ZAR. When multiple currencies are present without a filter, the summary reports
"MIXED". - Date range presets: Five preset ranges (
LAST_7_DAYS,LAST_30_DAYS,LAST_90_DAYS,LAST_365_DAYS,CUSTOM) with customstartDate/endDateoverrides. Default is last 30 days. - Cross-service stubs: Entity counts for Products, Services, Malets, and Users default to
0and are designed for future federation enrichment by owning subgraphs.
GraphQL API
Revenue By Day
The primary revenue query returns daily breakdowns grouped by currency:
query RevenueByDay($filter: AnalyticsFilter) {
revenueByDay(filter: $filter) {
days {
date
totalRevenue
orderCount
currency
}
totalRevenue
totalOrders
averageOrderValue
currency
}
}
Variables:
{
"filter": {
"dateRange": "LAST_30_DAYS",
"maletId": "malet-ke-safari",
"currency": "KES"
}
}
The currency field in RevenueSummary reports "MIXED" when multiple currencies are returned without a currency filter. This prevents accidental mixing of KES and USD amounts in summary totals.
Order Volume Trends
Daily order volume grouped by day and status โ ideal for stacked bar charts:
query OrderVolumeTrends($filter: AnalyticsFilter) {
orderVolumeTrends(filter: $filter) {
trends {
date
count
status
}
periodStart
periodEnd
totalOrders
}
}
Each trend entry includes the OrderStatus enum value (PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED), enabling visualization of order lifecycle distribution over time.
Platform Entity Counts
A lightweight dashboard snapshot of platform-wide metrics:
query {
platformEntityCounts {
totalOrders
totalRevenue
ordersByStatus {
status
count
}
totalProducts
totalServices
totalMalets
totalUsers
}
}
Note:
totalProducts,totalServices,totalMalets, andtotalUsersare currently0stubs. When owning subgraphs (products, services, malets, nodes) add@providesfields, the Gateway automatically composes real values via GraphQL Federation.
Top Malets by Revenue
Revenue leaderboard ranked by Malet, grouped by currency:
query TopMalets($limit: Int, $filter: AnalyticsFilter) {
topMaletsByRevenue(limit: $limit, filter: $filter) {
maletId
totalRevenue
orderCount
currency
}
}
Variables:
{
"limit": 10,
"filter": {
"dateRange": "LAST_90_DAYS",
"currency": "ZAR"
}
}
Results are sorted by totalRevenue descending. The limit parameter caps at 100.
Type Reference
AnalyticsFilter (Input)
| Field | Type | Default | Description |
|---|---|---|---|
dateRange |
AnalyticsDateRange |
LAST_30_DAYS |
Preset date range. Ignored when custom dates are set. |
startDate |
DateTime |
โ | Custom start date (overrides preset) |
endDate |
DateTime |
โ | Custom end date (overrides preset) |
maletId |
ID |
โ | Filter to a specific Malet |
currency |
String |
โ | Filter by currency code (e.g., KES, ZAR, USD) |
AnalyticsDateRange (Enum)
enum AnalyticsDateRange {
LAST_7_DAYS // 7-day window
LAST_30_DAYS // 30-day window (default)
LAST_90_DAYS // Quarterly view
LAST_365_DAYS // Annual view
CUSTOM // Use startDate/endDate
}
RevenueSummary
| Field | Type | Description |
|---|---|---|
days |
[RevenueByDay!]! |
Daily revenue breakdown |
totalRevenue |
Int! |
Total revenue in minor units (cents/pence) |
totalOrders |
Int! |
Total Murchases in the period |
averageOrderValue |
Float! |
Mean order value in minor units |
currency |
String! |
Currency code or "MIXED" if multiple |
RevenueByDay
| Field | Type | Description |
|---|---|---|
date |
DateTime! |
Calendar date |
totalRevenue |
Int! |
Revenue in minor units for this day |
orderCount |
Int! |
Number of Murchases placed |
currency |
String! |
Currency code (e.g., KES) |
OrderVolumeSummary
| Field | Type | Description |
|---|---|---|
trends |
[OrderVolumeTrend!]! |
Daily trend data |
periodStart |
DateTime! |
Start of the queried period |
periodEnd |
DateTime! |
End of the queried period |
totalOrders |
Int! |
Total Murchases in the period |
PlatformEntityCounts
| Field | Type | Source | Description |
|---|---|---|---|
totalOrders |
Int! |
Local (Order model) | All-time Murchase count |
totalRevenue |
Int! |
Local (Order model) | All-time revenue in minor units |
ordersByStatus |
[StatusCount!]! |
Local (Order model) | Count per status |
totalProducts |
Int! |
Federation stub | 0 until products subgraph contributes |
totalServices |
Int! |
Federation stub | 0 until services subgraph contributes |
totalMalets |
Int! |
Federation stub | 0 until malets subgraph contributes |
totalUsers |
Int! |
Federation stub | 0 until nodes subgraph contributes |
TopMaletRevenue
| Field | Type | Description |
|---|---|---|
maletId |
ID! |
Malet identifier |
totalRevenue |
Int! |
Revenue in minor units |
orderCount |
Int! |
Number of Murchases |
currency |
String! |
Currency code |
Multi-Currency Design
Mallnline supports Malets across different countries, each selling in their local currency:
| Region | Currency | Code |
|---|---|---|
| Kenya | Kenyan Shilling | KES |
| South Africa | South African Rand | ZAR |
| United States | US Dollar | USD |
| Nigeria | Nigerian Naira | NGN |
Revenue aggregation never mixes currencies. The $group stage groups by both date and currency, ensuring a Malet selling in KES is reported separately from one selling in USD.
When querying without a currency filter, the RevenueSummary.currency field returns "MIXED" to alert the consumer that the totalRevenue sum spans multiple denominations. Use the currency filter to get accurate single-currency totals:
# Get USD-only revenue
query {
revenueByDay(filter: { currency: "USD" }) {
totalRevenue
currency # "USD"
}
}
# Get all currencies (MIXED warning)
query {
revenueByDay {
totalRevenue
currency # "MIXED"
days {
currency # Each day shows its own currency
}
}
}
Access Control
Dual-Layer Security
Identical to the User Listing API pattern:
- Declarative guard โ
@RequirePermission(Permission.MANAGE_ANALYTICS) - Runtime check โ
assertPlatformAdmin(actor)verifiesrole === 'PLATFORM_ADMIN'
@Query(() => RevenueSummary)
@RequirePermission(Permission.MANAGE_ANALYTICS)
@HandleErrors()
async revenueByDay(
@Args('filter', { nullable: true }) filter: AnalyticsFilter,
@CurrentActor() actor: Actor,
): Promise<RevenueSummary> {
this.assertPlatformAdmin(actor); // Defense-in-depth
return this.analyticsService.getRevenueByDay(filter);
}
Permission Separation
| Permission | Scope | Use Case |
|---|---|---|
VIEW_ANALYTICS |
Malet Owner | View own Malet's analytics dashboard |
MANAGE_ANALYTICS |
Platform Admin | View global platform-wide analytics |
Module Structure
apps/murchases/src/analytics/
โโโ analytics.types.ts # GraphQL types, enums, filter input
โโโ analytics.service.ts # MongoDB aggregation pipelines
โโโ analytics.service.spec.ts # 20 unit tests
โโโ analytics.resolver.ts # Admin-only resolver (4 queries)
โโโ analytics.resolver.spec.ts # 10 unit tests
โโโ analytics.module.ts # NestJS module (Order model import)
apps/murchases/test/
โโโ analytics.e2e-spec.ts # 9 E2E tests (multi-currency)
Testing
Unit Tests
# Run analytics unit tests (30 tests across 2 suites)
npm run test -- apps/murchases --testPathPattern="analytics"
Key coverage areas:
- AnalyticsService: Date range resolution (all presets + custom), revenue aggregation by day, multi-currency grouping (MIXED label), filter pass-through (maletId, currency, date range), order volume trends by status, entity counts with status breakdown, top Malets ranking, limit capping (max 100), empty results handling
- AnalyticsResolver: Admin access control (pass/fail/unauthenticated), query delegation, filter forwarding
E2E Tests
# Run analytics E2E tests (9 tests)
npx jest --config apps/murchases/test/jest-e2e.json --testPathPattern="analytics" --detectOpenHandles
Covers: Revenue by day with seeded multi-currency orders (USD + KES), MIXED currency detection, order volume trends, platform entity counts with cross-service stubs, top Malets ranking, and admin access enforcement on all 4 queries.
Cross-Service Integration
Dependency Map
| Analytics depends on | For |
|---|---|
Order model (Typegoose) |
Transaction data, amounts, currencies, statuses |
@app/common |
Permission.MANAGE_ANALYTICS, RequirePermission, CurrentActor, HandleErrors |
Federation Enrichment (Future)
When owning subgraphs contribute @provides annotations to the PlatformEntityCounts type:
| Subgraph | Field | Mechanism |
|---|---|---|
products |
totalProducts |
countDocuments on Item model |
services |
totalServices |
countDocuments on Service model |
malets |
totalMalets |
countDocuments on Malet model |
nodes |
totalUsers |
countDocuments on User model |
Related
- Blog Analytics API โ Post volume, status distribution, author rankings, engagement
- Media Analytics API โ Upload volume, storage usage, type distribution, processing health
- Workspaces & The Tower โ Tower architecture and all 5 analytics sub-tabs
- User Listing API (Admin) โ Companion admin-only API for user management with shared auth pattern
- Organizations & Permissions โ
MANAGE_ANALYTICSpermission andPLATFORM_ADMINrole check used for access control - Admin Analytics Dashboards โ Frontend components (KPI cards, charts, leaderboards) that consume these APIs