diff --git a/services/platform-service/src/modules/usage/repository.ts b/services/platform-service/src/modules/usage/repository.ts index aa505a61..fb4adbe9 100644 --- a/services/platform-service/src/modules/usage/repository.ts +++ b/services/platform-service/src/modules/usage/repository.ts @@ -22,14 +22,16 @@ export async function getByDate(userId: string, date: string): Promise { - const { userId, days = 30, limit = 100, productId = '' } = options; + const { userId, days = 30, limit = 100, productId } = options; const since = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10); - let queryText = 'SELECT * FROM c WHERE c.productId = @productId AND c.date >= @since'; - const parameters: { name: string; value: string | number }[] = [ - { name: '@productId', value: productId }, - { name: '@since', value: since }, - ]; + let queryText = 'SELECT * FROM c WHERE c.date >= @since'; + const parameters: { name: string; value: string | number }[] = [{ name: '@since', value: since }]; + + if (productId) { + queryText += ' AND c.productId = @productId'; + parameters.push({ name: '@productId', value: productId }); + } if (userId) { queryText += ' AND c.userId = @userId'; diff --git a/services/platform-service/src/modules/usage/routes.ts b/services/platform-service/src/modules/usage/routes.ts index d43d2c40..c573edd0 100644 --- a/services/platform-service/src/modules/usage/routes.ts +++ b/services/platform-service/src/modules/usage/routes.ts @@ -17,8 +17,22 @@ import { type UsageDoc, type ModelBreakdown, type SourceBreakdown, + type ProductBreakdown, } from './types.js'; +/** + * Resolve productId for usage queries. + * - If query has productId=_all → return undefined (cross-product) + * - If query has a specific productId → use it + * - Otherwise → use the request's default productId from JWT/header/env + */ +function resolveProductFilter(req: import('fastify').FastifyRequest): string | undefined { + const qp = (req.query as Record).productId; + if (qp === '_all') return undefined; + if (qp && qp.length > 0) return qp; + return getRequestProductId(req); +} + /** * Plan limits — configurable per product via PLAN_LIMITS_JSON env var. * Format: JSON object keyed by plan name, value is metric→limit map. @@ -47,7 +61,7 @@ export async function usageRoutes(app: FastifyInstance) { // List usage app.get('/usage', async req => { const { userId, days = '30', limit = '100' } = req.query as Record; - const productId = getRequestProductId(req); + const productId = resolveProductFilter(req); const records = await repo.list({ userId, days: Number(days), @@ -60,11 +74,12 @@ export async function usageRoutes(app: FastifyInstance) { // Summary app.get('/usage/summary', async req => { const { userId, days = '30' } = req.query as Record; - const productId = getRequestProductId(req); + const productId = resolveProductFilter(req); const records = await repo.list({ userId, days: Number(days), productId }); const byModel: Record = {}; const bySource: Record = {}; + const byProduct: Record = {}; for (const r of records) { const m = r.model || 'gpt-4o-mini'; if (!byModel[m]) byModel[m] = { tokens: 0, requests: 0, cost: 0 }; @@ -77,6 +92,12 @@ export async function usageRoutes(app: FastifyInstance) { bySource[s].tokens += r.tokensUsed; bySource[s].requests += r.dictations; bySource[s].cost += r.costUsd; + + const p = r.productId; + if (!byProduct[p]) byProduct[p] = { tokens: 0, requests: 0, cost: 0 }; + byProduct[p].tokens += r.tokensUsed; + byProduct[p].requests += r.dictations; + byProduct[p].cost += r.costUsd; } const modelBreakdown: ModelBreakdown[] = Object.entries(byModel).map(([model, s]) => ({ @@ -89,6 +110,11 @@ export async function usageRoutes(app: FastifyInstance) { ...s, })); + const productBreakdown: ProductBreakdown[] = Object.entries(byProduct).map(([pid, s]) => ({ + productId: pid, + ...s, + })); + return { totalWords: records.reduce((sum, r) => sum + r.words, 0), totalDictations: records.reduce((sum, r) => sum + r.dictations, 0), @@ -97,6 +123,7 @@ export async function usageRoutes(app: FastifyInstance) { records, modelBreakdown, sourceBreakdown, + productBreakdown, }; }); diff --git a/services/platform-service/src/modules/usage/types.ts b/services/platform-service/src/modules/usage/types.ts index 75c70dd3..9c3a16ba 100644 --- a/services/platform-service/src/modules/usage/types.ts +++ b/services/platform-service/src/modules/usage/types.ts @@ -43,6 +43,13 @@ export interface ModelBreakdown { cost: number; } +export interface ProductBreakdown { + productId: string; + tokens: number; + requests: number; + cost: number; +} + export interface MonthlyUsage { tokens: number; words: number;