Developer Docs

Media Analytics API โ€” Developer Guide

Overview

The Media Analytics API provides platform administrators with real-time insights into media upload activity, storage consumption, file type distribution, and processing pipeline health across the Mallnline platform. Exposed through the media subgraph (apps/media), these admin-only queries use MongoDB aggregation pipelines to compute storage and volume metrics server-side.

Component Purpose Backend
MediaAnalyticsResolver 4 admin-only storage analytics queries @Resolver() with dual-layer access
MediaAnalyticsService MongoDB $group, $sum, $sort pipelines MediaFile model aggregation
MediaAnalyticsFilter Date range presets + custom filters MediaAnalyticsDateRange enum

All operations use the same dual-layer access control pattern: @RequirePermission(Permission.MANAGE_ANALYTICS) guard + runtime PLATFORM_ADMIN role verification.


Architecture

graph TD
    A["MediaAnalyticsResolver"] --> B["MediaAnalyticsService"]
    B --> C["MediaFile Model (MongoDB)"]

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

    subgraph "Aggregation Pipelines"
        C -- "$group by date, $sum size" --> F["mediaUploadsByDay"]
        C -- "$group by mimetype" --> G["mediaTypeDistribution"]
        C -- "$group by maletId, $sort totalSize" --> H["mediaStorageByMalet"]
        C -- "$group by status" --> I["mediaStatusBreakdown"]
    end

    style D fill:#dc2626,color:#fff
    style E fill:#dc2626,color:#fff
    style F fill:#06b6d4,color:#fff
    style G fill:#06b6d4,color:#fff
    style H fill:#06b6d4,color:#fff
    style I fill:#06b6d4,color:#fff

Data Source

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

Field Type Aggregation Use
createdAt Date Daily upload volume grouping
size Int Storage bytes โ€” summed for total/average calculations
mimetype String File type distribution (image/jpeg, video/mp4, etc.)
status MediaStatus Processing health (PENDING, COMPLETE, FAILED)
maletId String Per-Malet storage isolation

GraphQL API

Uploads By Day

Daily upload volume with cumulative storage โ€” ideal for line or area charts:

query MediaUploadsByDay($filter: MediaAnalyticsFilter) {
	mediaUploadsByDay(filter: $filter) {
		days {
			date
			count
			totalSizeBytes
		}
		totalUploads
		totalStorageBytes
		periodStart
		periodEnd
	}
}

Variables:

{
	"filter": {
		"dateRange": "LAST_30_DAYS",
		"maletId": "malet-photo-studio"
	}
}

The totalSizeBytes per day enables tracking storage growth trends over time.

Type Distribution

Media files grouped by MIME type with aggregate storage:

query MediaTypes($filter: MediaAnalyticsFilter) {
	mediaTypeDistribution(filter: $filter) {
		mimetype
		count
		totalSizeBytes
	}
}

Returns entries sorted by count descending. Common types include:

MIME Type Typical Source
image/jpeg Product photos, gallery images
image/png Logos, graphics with transparency
image/webp Optimized web images (auto-generated)
video/mp4 Malet Owner product videos
image/tiff High-res print-ready gallery exports

Storage By Malet

Storage usage leaderboard ranked by total bytes:

query MediaStorage($limit: Int, $filter: MediaAnalyticsFilter) {
	mediaStorageByMalet(limit: $limit, filter: $filter) {
		maletId
		fileCount
		totalSizeBytes
		averageFileSize
	}
}

Variables:

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

Results are sorted by totalSizeBytes descending. The limit parameter caps at 100. Use averageFileSize to identify Malets uploading unusually large files that may need attention.

Status Breakdown

Processing pipeline health check:

query {
	mediaStatusBreakdown {
		status
		count
	}
}
Status Meaning
PENDING Uploaded but not yet processed (thumbnails, LQIP not generated)
COMPLETE Fully processed with thumbnails and blur placeholders
FAILED Processing failed โ€” may need manual intervention

A high PENDING count may indicate processing queue backlogs. A rising FAILED count signals infrastructure issues.


Type Reference

MediaAnalyticsFilter (Input)

Field Type Default Description
dateRange MediaAnalyticsDateRange 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

MediaAnalyticsDateRange (Enum)

enum MediaAnalyticsDateRange {
  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
}

MediaUploadsSummary

Field Type Description
days [MediaUploadsByDay!]! Daily upload breakdown
totalUploads Int! Total uploads in the period
totalStorageBytes Int! Cumulative storage bytes
periodStart DateTime! Query period start
periodEnd DateTime! Query period end

MediaUploadsByDay

Field Type Description
date DateTime! Calendar date
count Int! Files uploaded on this day
totalSizeBytes Int! Bytes uploaded on this day

MediaTypeDistribution

Field Type Description
mimetype String! MIME type (e.g., image/jpeg)
count Int! Number of files
totalSizeBytes Int! Total bytes for this type

MaletStorageUsage

Field Type Description
maletId ID! The Malet identifier
fileCount Int! Total files uploaded by this Malet
totalSizeBytes Int! Total storage consumed in bytes
averageFileSize Float! Mean file size in bytes

MediaStatusBreakdown

Field Type Description
status MediaStatus! PENDING, COMPLETE, or FAILED
count Int! Files with this processing status

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(() => MediaUploadsSummary)
@RequirePermission(Permission.MANAGE_ANALYTICS)
@HandleErrors()
async mediaUploadsByDay(
  @Args('filter', { nullable: true }) filter: MediaAnalyticsFilter,
  @CurrentActor() actor: Actor,
): Promise<MediaUploadsSummary> {
  this.assertPlatformAdmin(actor);
  return this.analyticsService.getUploadsByDay(filter);
}

Module Structure

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

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

Storage Monitoring Use Cases

Capacity Planning

# Total platform storage over the last year
query {
	mediaUploadsByDay(filter: { dateRange: LAST_365_DAYS }) {
		totalStorageBytes # Total bytes across all Malets
		totalUploads # Total files uploaded
	}
}

Malet Owner Storage Audits

# Which Malets consume the most storage?
query {
	mediaStorageByMalet(limit: 20) {
		maletId
		fileCount
		totalSizeBytes
		averageFileSize # Spot Malets uploading raw TIFFs vs compressed JPEGs
	}
}

Processing Health Alerts

# Monitor for stuck or failed processing
query {
	mediaStatusBreakdown {
		status
		count
	}
}
# Alert if FAILED count > threshold or PENDING count spikes

Testing

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

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

Key coverage: date range resolution, upload-by-day aggregation, Malet filtering, type distribution, storage ranking with limit cap, status breakdown, empty-result handling, admin access enforcement.