mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-01 15:49:00 +08:00
60f8f64481
Build versioned UTC hourly rollups, validate analytics facts, and keep dashboard requests result-only with explicit coverage semantics. Move derived metrics to the server and simplify the admin dashboard around one reconcilable UTC range without export.
68 lines
3.0 KiB
TypeScript
68 lines
3.0 KiB
TypeScript
import { zValidator } from '@hono/zod-validator'
|
|
import { Hono } from 'hono'
|
|
import { z } from 'zod'
|
|
import { addCalendarDays, utcDateStart } from '../domain/admin-stats-time'
|
|
import { requireAdmin } from '../middleware/auth'
|
|
import type { Env } from '../middleware/platform'
|
|
import { requireFeature } from '../middleware/require-feature'
|
|
import {
|
|
getAdminDashboardGrowthStats,
|
|
getAdminDashboardOperationsStats,
|
|
getAdminDashboardOverviewStats,
|
|
getAdminDashboardSharingStats,
|
|
getAdminDashboardStorageStats,
|
|
getAdminDashboardTrafficStats,
|
|
} from '../usecases/admin-stats'
|
|
|
|
const dashboardDateSchema = z
|
|
.string()
|
|
.refine(isValidDashboardDate, { message: 'Expected valid yyyy-MM-dd or ISO datetime with offset' })
|
|
|
|
const rangeQuerySchema = z.object({
|
|
from: dashboardDateSchema.optional(),
|
|
to: dashboardDateSchema.optional(),
|
|
timeZone: z.literal('UTC').optional(),
|
|
})
|
|
|
|
function parseRange(query: z.infer<typeof rangeQuerySchema>): { from?: Date; to?: Date; timeZone: 'UTC' } {
|
|
return {
|
|
from: query.from ? parseDashboardDate(query.from, 'start') : undefined,
|
|
to: query.to ? parseDashboardDate(query.to, 'end') : undefined,
|
|
timeZone: 'UTC',
|
|
}
|
|
}
|
|
|
|
function isValidDashboardDate(value: string): boolean {
|
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return z.string().datetime({ offset: true }).safeParse(value).success
|
|
const date = new Date(`${value}T00:00:00.000Z`)
|
|
return !Number.isNaN(date.getTime()) && date.toISOString().startsWith(value)
|
|
}
|
|
|
|
function parseDashboardDate(value: string, boundary: 'start' | 'end'): Date {
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
if (boundary === 'start') return utcDateStart(value)
|
|
return new Date(utcDateStart(addCalendarDays(value, 1)).getTime() - 1)
|
|
}
|
|
return new Date(value)
|
|
}
|
|
|
|
export const adminStats = new Hono<Env>()
|
|
.get('/overview', requireAdmin, zValidator('query', rangeQuerySchema), async (c) =>
|
|
c.json(await getAdminDashboardOverviewStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.get('/operations', requireAdmin, requireFeature('analytics'), zValidator('query', rangeQuerySchema), async (c) =>
|
|
c.json(await getAdminDashboardOperationsStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.get('/growth', requireAdmin, requireFeature('analytics'), zValidator('query', rangeQuerySchema), async (c) =>
|
|
c.json(await getAdminDashboardGrowthStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.get('/storage', requireAdmin, requireFeature('analytics'), zValidator('query', rangeQuerySchema), async (c) =>
|
|
c.json(await getAdminDashboardStorageStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.get('/traffic', requireAdmin, requireFeature('analytics'), zValidator('query', rangeQuerySchema), async (c) =>
|
|
c.json(await getAdminDashboardTrafficStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.get('/sharing', requireAdmin, requireFeature('analytics'), zValidator('query', rangeQuerySchema), async (c) =>
|
|
c.json(await getAdminDashboardSharingStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|