Developer Docs

Blog Analytics API โ€” Developer Guide

Overview

The Blog Analytics API provides platform administrators with real-time insights into content production, publishing status, author performance, and reader engagement across all Malets on the Mallnline platform. Exposed through the blogs subgraph (apps/blogs), these admin-only queries use MongoDB aggregation pipelines to compute metrics server-side.

Component Purpose Backend
BlogAnalyticsResolver 4 admin-only content analytics queries @Resolver() with dual-layer access
BlogAnalyticsService MongoDB $group, $dateToString, $sum pipelines Blog entity aggregation
BlogAnalyticsFilter Date range presets + custom filters BlogAnalyticsDateRange 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["BlogAnalyticsResolver"] --> B["BlogAnalyticsService"]
    B --> C["Blog Model (MongoDB)"]

    A -- "admin header" --> D["@RequirePermission(MANAGE_ANALYTICS)"]
    A -- "runtime" --> E["assertPlatformAdmin(actor)"]

    subgraph "Aggregation Pipelines"
        C -- "$group by date + status" --> F["blogPostsByDay"]
        C -- "$group by status" --> G["blogStatusBreakdown"]
        C -- "$group by createdBy" --> H["topBlogAuthors"]
        C -- "$sum viewCount/likeCount" --> I["blogEngagement"]
    end

    style D fill:#dc2626,color:#fff
    style E fill:#dc2626,color:#fff
    style F fill:#8b5cf6,color:#fff
    style G fill:#8b5cf6,color:#fff
    style H fill:#8b5cf6,color:#fff
    style I fill:#8b5cf6,color:#fff

Data Source

The Blog entity includes key fields consumed by the analytics pipelines:

Field Type Aggregation Use
createdAt Date Daily post volume grouping
status BlogStatus Status distribution breakdown
createdBy String Author ranking
viewCount Int Engagement โ€” total and per-post views
likeCount Int Engagement โ€” total and per-post likes
maletId String Per-Malet content filtering

GraphQL API

Posts By Day

Daily post creation volume grouped by publishing status โ€” ideal for stacked area charts:

query BlogPostsByDay($filter: BlogAnalyticsFilter) {
	blogPostsByDay(filter: $filter) {
		days {
			date
			count
			status
		}
		totalPosts
		publishedCount
		draftCount
		periodStart
		periodEnd
	}
}

Variables:

{
	"filter": {
		"dateRange": "LAST_30_DAYS",
		"maletId": "malet-ke-safari"
	}
}

Each day entry includes the BlogStatus value (DRAFT, SCHEDULED, PUBLISHED, ARCHIVED), enabling visualization of content lifecycle distribution.

Status Breakdown

A platform-wide snapshot of all blog posts by publishing status:

query {
	blogStatusBreakdown {
		status
		count
	}
}

Returns counts across all statuses regardless of date range โ€” useful for real-time pipeline health monitoring.

Top Blog Authors

Authors ranked by post count with aggregate engagement totals:

query TopBlogAuthors($limit: Int, $filter: BlogAnalyticsFilter) {
	topBlogAuthors(limit: $limit, filter: $filter) {
		authorId
		postCount
		totalViews
		totalLikes
	}
}

Variables:

{
	"limit": 10,
	"filter": {
		"dateRange": "LAST_90_DAYS"
	}
}

Results are sorted by postCount descending. The limit parameter caps at 100. The authorId maps to a Visitor/Buyer user ID โ€” resolve to a full user profile via the nodes subgraph through federation.

Blog Engagement

Aggregate engagement metrics with the top-performing posts:

query BlogEngagement($filter: BlogAnalyticsFilter) {
	blogEngagement(filter: $filter) {
		totalViews
		totalLikes
		avgViewsPerPost
		avgLikesPerPost
		topPosts {
			id
			title
			viewCount
			likeCount
			maletId
		}
	}
}

The topPosts array returns the top 10 blog posts by viewCount descending. Only posts with viewCount > 0 are included.


Type Reference

BlogAnalyticsFilter (Input)

Field Type Default Description
dateRange BlogAnalyticsDateRange LAST_30_DAYS Preset date range
startDate DateTime โ€” Custom start date (overrides preset)
endDate DateTime โ€” Custom end date (overrides preset)
maletId ID โ€” Filter to a specific Malet

BlogAnalyticsDateRange (Enum)

enum BlogAnalyticsDateRange {
  LAST_7_DAYS    // Weekly view
  LAST_30_DAYS   // Monthly view (default)
  LAST_90_DAYS   // Quarterly view
  LAST_365_DAYS  // Annual view
  CUSTOM         // Use startDate/endDate
}

BlogPostsSummary

Field Type Description
days [BlogPostsByDay!]! Daily post breakdown
totalPosts Int! Total posts in the period
publishedCount Int! Posts with PUBLISHED status
draftCount Int! Posts with DRAFT status
periodStart DateTime! Query period start
periodEnd DateTime! Query period end

BlogPostsByDay

Field Type Description
date DateTime! Calendar date
count Int! Posts created on this day
status BlogStatus Publishing status for this group

BlogStatusBreakdown

Field Type Description
status BlogStatus! DRAFT, SCHEDULED, PUBLISHED, ARCHIVED
count Int! Total posts with this status

TopBlogAuthor

Field Type Description
authorId ID! Author user identifier
postCount Int! Total posts authored
totalViews Int! Aggregate views across all posts
totalLikes Int! Aggregate likes across all posts

BlogEngagementSummary

Field Type Description
totalViews Int! Total views across all posts
totalLikes Int! Total likes across all posts
avgViewsPerPost Float! Mean views per post
avgLikesPerPost Float! Mean likes per post
topPosts [TopBlogPost!]! Top 10 posts by views

TopBlogPost

Field Type Description
id ID! Blog post identifier
title String! Post title
viewCount Int! Number of views
likeCount Int! Number of likes
maletId ID! Owning Malet identifier

Access Control

Identical to all analytics modules across the platform:

  1. Declarative guard โ€” @RequirePermission(Permission.MANAGE_ANALYTICS)
  2. Runtime check โ€” assertPlatformAdmin(actor) verifies role === 'PLATFORM_ADMIN'
@Query(() => BlogPostsSummary)
@RequirePermission(Permission.MANAGE_ANALYTICS)
@HandleErrors()
async blogPostsByDay(
  @Args('filter', { nullable: true }) filter: BlogAnalyticsFilter,
  @CurrentActor() actor: Actor,
): Promise<BlogPostsSummary> {
  this.assertPlatformAdmin(actor);  // Defense-in-depth
  return this.analyticsService.getPostsByDay(filter);
}

Module Structure

apps/blogs/src/analytics/
โ”œโ”€โ”€ analytics.types.ts          # GraphQL types, enums, filter input
โ”œโ”€โ”€ analytics.service.ts        # MongoDB aggregation pipelines
โ”œโ”€โ”€ analytics.service.spec.ts   # 12 unit tests
โ”œโ”€โ”€ analytics.resolver.ts       # Admin-only resolver (4 queries)
โ”œโ”€โ”€ analytics.resolver.spec.ts  # 8 unit tests
โ””โ”€โ”€ analytics.module.ts         # NestJS module (Blog model import)

apps/blogs/test/
โ””โ”€โ”€ analytics.e2e-spec.ts       # 6 E2E tests

Testing

# Unit tests (20 tests across 2 suites)
npm run test -- apps/blogs --testPathPattern="analytics"

# E2E tests (6 tests)
npx jest --config apps/blogs/test/jest-e2e.json --testPathPattern="analytics" --detectOpenHandles

Key coverage: date range resolution, post-by-day aggregation with status grouping, Malet filtering, status breakdown, author ranking with limit cap, engagement computation with empty-result handling, admin access enforcement.