From 337db89a4a316ac9354a314b311c24caf75dc27e Mon Sep 17 00:00:00 2001 From: saltbo Date: Thu, 9 Jul 2026 22:15:05 -0400 Subject: [PATCH] fix(admin): mount stats under site api group --- server/app.ts | 2 +- server/http/admin-stats.integration.test.ts | 41 +++++++++++++-------- src/lib/api.test.ts | 14 +++---- src/lib/api.ts | 14 +++---- src/lib/rpc.ts | 2 +- 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/server/app.ts b/server/app.ts index 0ab5f903..82ee8541 100644 --- a/server/app.ts +++ b/server/app.ts @@ -223,7 +223,7 @@ export function createApp(platform: Platform, auth: Auth, deps: Deps = createDep app.route('/api/site/licensing', licensingAdmin) app.route('/api/site/branding', brandingAdmin) app.route('/api/site/audit-events', adminAudit) - app.route('/api/admin/stats', adminStats) + app.route('/api/site/stats', adminStats) app.route('/api/downloads/downloaders', downloaders) app.get('/api/health', (c) => c.json({ status: 'ok' })) diff --git a/server/http/admin-stats.integration.test.ts b/server/http/admin-stats.integration.test.ts index 7d4e0c0d..1cfa5692 100644 --- a/server/http/admin-stats.integration.test.ts +++ b/server/http/admin-stats.integration.test.ts @@ -3,13 +3,22 @@ import { describe, expect, it } from 'vitest' import { currentTrafficPeriod } from '../domain/quota' import { adminHeaders, createTestApp, seedProLicense } from '../test/setup.js' -describe('admin stats routes', () => { +describe('site stats routes', () => { + it('does not expose stats under the removed admin API group', async () => { + const { app } = await createTestApp() + const headers = await adminHeaders(app) + + const res = await app.request('/api/admin/stats/overview', { headers }) + + expect(res.status).toBe(404) + }) + it('does not expose legacy core or details stats endpoints', async () => { const { app } = await createTestApp() const headers = await adminHeaders(app) - const coreRes = await app.request('/api/admin/stats/core', { headers }) - const detailsRes = await app.request('/api/admin/stats/details', { headers }) + const coreRes = await app.request('/api/site/stats/core', { headers }) + const detailsRes = await app.request('/api/site/stats/details', { headers }) expect(coreRes.status).toBe(404) expect(detailsRes.status).toBe(404) @@ -20,7 +29,7 @@ describe('admin stats routes', () => { const headers = await adminHeaders(app) await seedStatsFixture(db) - const res = await app.request('/api/admin/stats/storage', { headers }) + const res = await app.request('/api/site/stats/storage', { headers }) const body = (await res.json()) as { error: { details: Array<{ metadata: Record }> } } expect(res.status).toBe(402) @@ -31,7 +40,7 @@ describe('admin stats routes', () => { const { app } = await createTestApp() const headers = await adminHeaders(app) - const res = await app.request('/api/admin/stats/overview?from=2026-01-01&to=2026-01-07', { headers }) + const res = await app.request('/api/site/stats/overview?from=2026-01-01&to=2026-01-07', { headers }) const body = (await res.json()) as { from: string; to: string; trends: Array<{ date: string }> } expect(res.status).toBe(200) @@ -52,19 +61,19 @@ describe('admin stats routes', () => { const { app } = await createTestApp() const headers = await adminHeaders(app) - const res = await app.request('/api/admin/stats/overview?from=2025-01-01&to=2026-01-02', { headers }) + const res = await app.request('/api/site/stats/overview?from=2025-01-01&to=2026-01-02', { headers }) expect(res.status).toBe(400) }) - it('does not publish admin stats routes in the OpenAPI document', async () => { + it('does not publish site stats routes in the OpenAPI document', async () => { const { app } = await createTestApp() const res = await app.request('/api/openapi.json') const body = (await res.json()) as { paths: Record } expect(res.status).toBe(200) - expect(Object.keys(body.paths).some((path) => path.startsWith('/api/admin/stats'))).toBe(false) + expect(Object.keys(body.paths).some((path) => path.startsWith('/api/site/stats'))).toBe(false) }) it('reads storage waterline trends from daily rollups when present', async () => { @@ -84,7 +93,7 @@ describe('admin stats routes', () => { ) `) - const res = await app.request('/api/admin/stats/storage?from=2026-01-01&to=2026-01-01', { headers }) + const res = await app.request('/api/site/stats/storage?from=2026-01-01&to=2026-01-01', { headers }) const body = (await res.json()) as { storageTrend: Array<{ date: string; usedBytes: number; newBytes: number; newFiles: number }> } @@ -100,7 +109,7 @@ describe('admin stats routes', () => { await seedStatsFixture(db) const before = await db.all<{ count: number }>(sql`SELECT COUNT(*) AS count FROM stats_rollups_daily`) - const res = await app.request('/api/admin/stats/storage?from=2000-01-01&to=2000-01-02', { headers }) + const res = await app.request('/api/site/stats/storage?from=2000-01-01&to=2000-01-02', { headers }) const after = await db.all<{ count: number }>(sql`SELECT COUNT(*) AS count FROM stats_rollups_daily`) expect(res.status).toBe(200) @@ -114,7 +123,7 @@ describe('admin stats routes', () => { await seedProLicense(db) await seedStatsFixture(db) - const res = await app.request('/api/admin/stats/traffic', { headers }) + const res = await app.request('/api/site/stats/traffic', { headers }) const body = (await res.json()) as { summary: { totalBytes: { value: number } @@ -151,7 +160,7 @@ describe('admin stats routes', () => { await seedProLicense(db) await seedStatsFixture(db) - const currentSharingRes = await app.request('/api/admin/stats/sharing', { headers }) + const currentSharingRes = await app.request('/api/site/stats/sharing', { headers }) const currentSharing = (await currentSharingRes.json()) as { summary: { views: { value: number }; downloads: { value: number } } topShares: Array<{ @@ -162,18 +171,18 @@ describe('admin stats routes', () => { downloadPercent: number }> } - const oldSharingRes = await app.request('/api/admin/stats/sharing?from=2000-01-01&to=2000-01-02', { headers }) + const oldSharingRes = await app.request('/api/site/stats/sharing?from=2000-01-01&to=2000-01-02', { headers }) const oldSharing = (await oldSharingRes.json()) as { summary: { views: { value: number }; downloads: { value: number } } topShares: unknown[] } - const currentRankingRes = await app.request('/api/admin/stats/ranking', { headers }) + const currentRankingRes = await app.request('/api/site/stats/ranking', { headers }) const currentRanking = (await currentRankingRes.json()) as { topSpaces: unknown[] storageByType: unknown[] topShares: unknown[] } - const oldRankingRes = await app.request('/api/admin/stats/ranking?from=2000-01-01&to=2000-01-02', { headers }) + const oldRankingRes = await app.request('/api/site/stats/ranking?from=2000-01-01&to=2000-01-02', { headers }) const oldRanking = (await oldRankingRes.json()) as { topSpaces: unknown[] storageByType: unknown[] @@ -221,7 +230,7 @@ describe('admin stats routes', () => { ('activity-download-heavy-3', ${orgId}, NULL, 'anonymous', 'share_download', 'share', 'share-download-heavy', 'report.pdf', '{"bytes":512,"source":"landing_share","anonymous":true}', ${nowSec}) `) - const res = await app.request('/api/admin/stats/ranking', { headers }) + const res = await app.request('/api/site/stats/ranking', { headers }) const body = (await res.json()) as { topShares: Array<{ token: string; views: number; downloads: number }> } expect(res.status).toBe(200) diff --git a/src/lib/api.test.ts b/src/lib/api.test.ts index ac0a16d9..83075a84 100644 --- a/src/lib/api.test.ts +++ b/src/lib/api.test.ts @@ -3815,7 +3815,7 @@ describe('api', () => { }) }) - describe('admin stats API', () => { + describe('site stats API', () => { const dashboardPayload = { generatedAt: '2026-07-09T00:00:00.000Z', from: '2026-07-01T00:00:00.000Z', @@ -3823,12 +3823,12 @@ describe('api', () => { } const range = { from: '2026-07-01T00:00:00.000Z', to: '2026-07-09T00:00:00.000Z' } const dashboardEndpoints = [ - { name: 'getAdminDashboardOverviewStats', path: '/api/admin/stats/overview', fn: getAdminDashboardOverviewStats }, - { name: 'getAdminDashboardGrowthStats', path: '/api/admin/stats/growth', fn: getAdminDashboardGrowthStats }, - { name: 'getAdminDashboardStorageStats', path: '/api/admin/stats/storage', fn: getAdminDashboardStorageStats }, - { name: 'getAdminDashboardTrafficStats', path: '/api/admin/stats/traffic', fn: getAdminDashboardTrafficStats }, - { name: 'getAdminDashboardSharingStats', path: '/api/admin/stats/sharing', fn: getAdminDashboardSharingStats }, - { name: 'getAdminDashboardRankingStats', path: '/api/admin/stats/ranking', fn: getAdminDashboardRankingStats }, + { name: 'getAdminDashboardOverviewStats', path: '/api/site/stats/overview', fn: getAdminDashboardOverviewStats }, + { name: 'getAdminDashboardGrowthStats', path: '/api/site/stats/growth', fn: getAdminDashboardGrowthStats }, + { name: 'getAdminDashboardStorageStats', path: '/api/site/stats/storage', fn: getAdminDashboardStorageStats }, + { name: 'getAdminDashboardTrafficStats', path: '/api/site/stats/traffic', fn: getAdminDashboardTrafficStats }, + { name: 'getAdminDashboardSharingStats', path: '/api/site/stats/sharing', fn: getAdminDashboardSharingStats }, + { name: 'getAdminDashboardRankingStats', path: '/api/site/stats/ranking', fn: getAdminDashboardRankingStats }, ] as const for (const endpoint of dashboardEndpoints) { diff --git a/src/lib/api.ts b/src/lib/api.ts index 2b0d7724..2bb258f4 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -68,7 +68,6 @@ import { adminDownloadersApi, adminQuotas, adminSiteInvitations, - adminStatsApi, adminTeams, announcementsApi, authedSharesApi, @@ -90,6 +89,7 @@ import { publicBrandingApi, publicSharesApi, publicSiteInvitations, + siteStatsApi, storages, system, teamsApi, @@ -447,27 +447,27 @@ function statsRangeQuery(filter: AdminStatsRangeFilter = {}): Record(adminStatsApi.overview.$get({ query: statsRangeQuery(filter) })) + return unwrap(siteStatsApi.overview.$get({ query: statsRangeQuery(filter) })) } export function getAdminDashboardGrowthStats(filter: AdminStatsRangeFilter = {}) { - return unwrap(adminStatsApi.growth.$get({ query: statsRangeQuery(filter) })) + return unwrap(siteStatsApi.growth.$get({ query: statsRangeQuery(filter) })) } export function getAdminDashboardStorageStats(filter: AdminStatsRangeFilter = {}) { - return unwrap(adminStatsApi.storage.$get({ query: statsRangeQuery(filter) })) + return unwrap(siteStatsApi.storage.$get({ query: statsRangeQuery(filter) })) } export function getAdminDashboardTrafficStats(filter: AdminStatsRangeFilter = {}) { - return unwrap(adminStatsApi.traffic.$get({ query: statsRangeQuery(filter) })) + return unwrap(siteStatsApi.traffic.$get({ query: statsRangeQuery(filter) })) } export function getAdminDashboardSharingStats(filter: AdminStatsRangeFilter = {}) { - return unwrap(adminStatsApi.sharing.$get({ query: statsRangeQuery(filter) })) + return unwrap(siteStatsApi.sharing.$get({ query: statsRangeQuery(filter) })) } export function getAdminDashboardRankingStats(filter: AdminStatsRangeFilter = {}) { - return unwrap(adminStatsApi.ranking.$get({ query: statsRangeQuery(filter) })) + return unwrap(siteStatsApi.ranking.$get({ query: statsRangeQuery(filter) })) } // Admin Storages API diff --git a/src/lib/rpc.ts b/src/lib/rpc.ts index be2f8967..a371eca9 100644 --- a/src/lib/rpc.ts +++ b/src/lib/rpc.ts @@ -79,5 +79,5 @@ export const licensingAdminApi = hc('/api/site/licensing', export const publicBrandingApi = hc('/api/site/branding', opts) export const brandingAdminApi = hc('/api/site/branding', opts) export const adminAuditApi = hc('/api/site/audit-events', opts) -export const adminStatsApi = hc('/api/admin/stats', opts) +export const siteStatsApi = hc('/api/site/stats', opts) export const publicSiteInvitations = hc('/api/site/invitations', opts)