mirror of
https://github.com/saltbo/zpan.git
synced 2026-10-31 02:41:33 +08:00
135 lines
4.2 KiB
TypeScript
135 lines
4.2 KiB
TypeScript
import { OpenAPIHono, z } from '@hono/zod-openapi'
|
|
import { AuthorizationScope } from '@shared/authorization'
|
|
import {
|
|
adminAnalyticsGrowthSchema,
|
|
adminAnalyticsOperationsSchema,
|
|
adminAnalyticsOverviewSchema,
|
|
adminAnalyticsSharingSchema,
|
|
adminAnalyticsStorageSchema,
|
|
adminAnalyticsTrafficSchema,
|
|
} from '@shared/schemas'
|
|
import { addCalendarDays, utcDateStart } from '../domain/admin-stats-time'
|
|
import type { Env } from '../middleware/platform'
|
|
import { requireFeature } from '../middleware/require-feature'
|
|
import {
|
|
getAdminDashboardGrowthStats,
|
|
getAdminDashboardOperationsStats,
|
|
getAdminDashboardOverviewStats,
|
|
getAdminDashboardSharingStats,
|
|
getAdminDashboardStorageStats,
|
|
getAdminDashboardTrafficStats,
|
|
} from '../usecases/admin-stats'
|
|
import { authRoute, jsonContent } from './openapi'
|
|
|
|
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)
|
|
}
|
|
|
|
function analyticsRoute(
|
|
operationId: string,
|
|
summary: string,
|
|
path: string,
|
|
schema: Parameters<typeof jsonContent>[0],
|
|
gated = true,
|
|
) {
|
|
return authRoute(
|
|
{ scopes: [AuthorizationScope.SITE_ANALYTICS_READ], siteRole: 'admin' },
|
|
{
|
|
operationId,
|
|
summary,
|
|
tags: ['Site Analytics'],
|
|
method: 'get',
|
|
path,
|
|
middleware: gated ? [requireFeature('analytics')] : [],
|
|
request: { query: rangeQuerySchema },
|
|
responses: { 200: jsonContent(schema, summary) },
|
|
},
|
|
)
|
|
}
|
|
|
|
const overviewRoute = analyticsRoute(
|
|
'getAdminAnalyticsOverview',
|
|
'Get analytics overview',
|
|
'/overview',
|
|
adminAnalyticsOverviewSchema,
|
|
false,
|
|
)
|
|
const operationsRoute = analyticsRoute(
|
|
'getAdminAnalyticsOperations',
|
|
'Get operations analytics',
|
|
'/operations',
|
|
adminAnalyticsOperationsSchema,
|
|
)
|
|
const growthRoute = analyticsRoute(
|
|
'getAdminAnalyticsGrowth',
|
|
'Get growth analytics',
|
|
'/growth',
|
|
adminAnalyticsGrowthSchema,
|
|
)
|
|
const storageRoute = analyticsRoute(
|
|
'getAdminAnalyticsStorage',
|
|
'Get storage analytics',
|
|
'/storage',
|
|
adminAnalyticsStorageSchema,
|
|
)
|
|
const trafficRoute = analyticsRoute(
|
|
'getAdminAnalyticsTraffic',
|
|
'Get traffic analytics',
|
|
'/traffic',
|
|
adminAnalyticsTrafficSchema,
|
|
)
|
|
const sharingRoute = analyticsRoute(
|
|
'getAdminAnalyticsSharing',
|
|
'Get sharing analytics',
|
|
'/sharing',
|
|
adminAnalyticsSharingSchema,
|
|
)
|
|
|
|
export const adminStats = new OpenAPIHono<Env>()
|
|
.openapi(overviewRoute, async (c) =>
|
|
c.json(await getAdminDashboardOverviewStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.openapi(operationsRoute, async (c) =>
|
|
c.json(await getAdminDashboardOperationsStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.openapi(growthRoute, async (c) =>
|
|
c.json(await getAdminDashboardGrowthStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.openapi(storageRoute, async (c) =>
|
|
c.json(await getAdminDashboardStorageStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.openapi(trafficRoute, async (c) =>
|
|
c.json(await getAdminDashboardTrafficStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|
|
.openapi(sharingRoute, async (c) =>
|
|
c.json(await getAdminDashboardSharingStats(c.get('deps'), parseRange(c.req.valid('query'))), 200),
|
|
)
|