diff --git a/app/game-api/src/router/public/index.ts b/app/game-api/src/router/public/index.ts index f4ef4d8..e7a0f59 100644 --- a/app/game-api/src/router/public/index.ts +++ b/app/game-api/src/router/public/index.ts @@ -42,14 +42,6 @@ type NationCountRow = { type NpcListSort = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; -type TrafficHistoryItem = { - year: number; - month: number; - refresh: number; - online: number; - date: string; -}; - const PUBLIC_CACHE_TTL_SECONDS = 600; const buildPublicCacheKey = (ctx: GameApiContext, key: string): string => @@ -171,26 +163,6 @@ const readFiniteMetaNumber = (meta: Record, key: string): numbe return typeof value === 'number' && Number.isFinite(value) ? value : 0; }; -const parseTrafficHistory = (value: unknown): TrafficHistoryItem[] => { - if (!Array.isArray(value)) { - return []; - } - - const result: TrafficHistoryItem[] = []; - for (const item of value) { - const row = asRecord(item); - const year = readFiniteMetaNumber(row, 'year'); - const month = readFiniteMetaNumber(row, 'month'); - const refresh = readFiniteMetaNumber(row, 'refresh'); - const online = readFiniteMetaNumber(row, 'online'); - const date = typeof row.date === 'string' ? row.date : ''; - if (year > 0 && month > 0 && date) { - result.push({ year, month, refresh, online, date }); - } - } - return result; -}; - const compareString = (left: string, right: string): number => { if (left === right) { return 0; @@ -250,95 +222,6 @@ export const publicRouter = router({ getNationList: procedure.query(async ({ ctx }) => { return loadCachedNationList(ctx); }), - getTraffic: procedure.query(async ({ ctx }) => { - const worldState = await ctx.db.worldState.findFirst(); - if (!worldState) { - throw new TRPCError({ - code: 'PRECONDITION_FAILED', - message: 'World state is not initialized.', - }); - } - - const meta = asRecord(worldState.meta); - const rawOnlineSince = meta.lastTurnTime ?? meta.turntime; - const parsedOnlineSince = - typeof rawOnlineSince === 'string' || rawOnlineSince instanceof Date - ? new Date(rawOnlineSince) - : null; - const onlineSince = - parsedOnlineSince && Number.isFinite(parsedOnlineSince.getTime()) - ? parsedOnlineSince - : new Date(Date.now() - worldState.tickSeconds * 1_000); - const [accessTotal, currentOnline, topAccess] = await Promise.all([ - ctx.db.generalAccessLog.aggregate({ - _sum: { - refresh: true, - refreshScoreTotal: true, - }, - }), - ctx.db.generalAccessLog.count({ - where: { - lastRefresh: { - gte: onlineSince, - }, - }, - }), - ctx.db.generalAccessLog.findMany({ - orderBy: [{ refresh: 'desc' }, { generalId: 'asc' }], - take: 5, - select: { - generalId: true, - refresh: true, - refreshScoreTotal: true, - }, - }), - ]); - - const generalIds = topAccess.map((entry) => entry.generalId); - const generalRows = - generalIds.length > 0 - ? await ctx.db.general.findMany({ - where: { id: { in: generalIds } }, - select: { id: true, name: true }, - }) - : []; - const generalName = new Map(generalRows.map((general) => [general.id, general.name])); - const totalRefresh = accessTotal._sum.refresh ?? 0; - const totalRefreshScore = accessTotal._sum.refreshScoreTotal ?? 0; - const currentRefresh = Math.max(readFiniteMetaNumber(meta, 'refresh'), totalRefresh); - const history = parseTrafficHistory(meta.recentTraffic); - history.push({ - year: worldState.currentYear, - month: worldState.currentMonth, - refresh: currentRefresh, - online: currentOnline, - date: new Date().toISOString(), - }); - - return { - history, - maxRefresh: Math.max( - 1, - readFiniteMetaNumber(meta, 'maxrefresh'), - ...history.map((entry) => entry.refresh) - ), - maxOnline: Math.max(1, readFiniteMetaNumber(meta, 'maxonline'), ...history.map((entry) => entry.online)), - suspects: [ - { - generalId: null, - name: '접속자 총합', - refresh: totalRefresh, - refreshScoreTotal: totalRefreshScore, - }, - ...topAccess.map((entry) => ({ - generalId: entry.generalId, - name: generalName.get(entry.generalId) ?? `장수 ${entry.generalId}`, - refresh: entry.refresh, - refreshScoreTotal: entry.refreshScoreTotal, - })), - ], - }; - }), getGeneralList: procedure.query(async ({ ctx }) => { const [generals, nations] = await Promise.all([ ctx.db.general.findMany({ diff --git a/app/game-api/test/publicTraffic.test.ts b/app/game-api/test/publicTraffic.test.ts deleted file mode 100644 index 78008d8..0000000 --- a/app/game-api/test/publicTraffic.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import type { RedisConnector } from '@sammo-ts/infra'; - -import { RedisAccessTokenStore } from '../src/auth/accessTokenStore.js'; -import { InMemoryFlushStore } from '../src/auth/flushStore.js'; -import { InMemoryBattleSimTransport } from '../src/battleSim/inMemoryTransport.js'; -import type { DatabaseClient, GameApiContext, GameProfile } from '../src/context.js'; -import { InMemoryTurnDaemonTransport } from '../src/daemon/inMemoryTransport.js'; -import { appRouter } from '../src/router.js'; - -const profile: GameProfile = { - id: 'che', - scenario: 'default', - name: 'che:default', -}; - -const buildContext = (): GameApiContext => { - const db = { - worldState: { - findFirst: async () => ({ - id: 1, - currentYear: 185, - currentMonth: 3, - tickSeconds: 600, - config: {}, - meta: { - lastTurnTime: '2026-07-26T03:00:00.000Z', - refresh: 12, - maxrefresh: 30, - maxonline: 5, - recentTraffic: [ - { - year: 185, - month: 2, - refresh: 30, - online: 5, - date: '2026-07-26 02:50:00', - }, - ], - }, - }), - }, - generalAccessLog: { - aggregate: async () => ({ - _sum: { - refresh: 12, - refreshScoreTotal: 21, - }, - }), - count: async (args: { where: { lastRefresh: { gte: Date } } }) => { - expect(args.where.lastRefresh.gte).toEqual(new Date('2026-07-26T03:00:00.000Z')); - return 2; - }, - findMany: async () => [ - { generalId: 7, refresh: 9, refreshScoreTotal: 15 }, - { generalId: 8, refresh: 3, refreshScoreTotal: 6 }, - ], - }, - general: { - findMany: async () => [ - { id: 7, name: '갑' }, - { id: 8, name: '을' }, - ], - }, - }; - const redis = { - get: async () => null, - set: async () => null, - } as unknown as RedisConnector['client']; - - return { - db: db as unknown as DatabaseClient, - turnDaemon: new InMemoryTurnDaemonTransport(), - battleSim: new InMemoryBattleSimTransport(), - profile, - auth: null, - uploadDir: 'uploads', - uploadPath: '/uploads', - uploadPublicUrl: null, - redis, - accessTokenStore: new RedisAccessTokenStore(redis, profile.name), - flushStore: new InMemoryFlushStore(), - gameTokenSecret: 'test-secret', - }; -}; - -describe('public.getTraffic', () => { - it('is public and returns only aggregate traffic plus allowlisted general names', async () => { - const result = await appRouter.createCaller(buildContext()).public.getTraffic(); - - expect(result.history).toHaveLength(2); - expect(result.history[0]).toEqual({ - year: 185, - month: 2, - refresh: 30, - online: 5, - date: '2026-07-26 02:50:00', - }); - expect(result.history[1]).toMatchObject({ - year: 185, - month: 3, - refresh: 12, - online: 2, - }); - expect(result.maxRefresh).toBe(30); - expect(result.maxOnline).toBe(5); - expect(result.suspects).toEqual([ - { generalId: null, name: '접속자 총합', refresh: 12, refreshScoreTotal: 21 }, - { generalId: 7, name: '갑', refresh: 9, refreshScoreTotal: 15 }, - { generalId: 8, name: '을', refresh: 3, refreshScoreTotal: 6 }, - ]); - expect(JSON.stringify(result)).not.toContain('userId'); - }); -}); diff --git a/app/game-frontend/src/router/index.ts b/app/game-frontend/src/router/index.ts index 63d0a16..ac0ccb2 100644 --- a/app/game-frontend/src/router/index.ts +++ b/app/game-frontend/src/router/index.ts @@ -32,7 +32,6 @@ import TroopView from '../views/TroopView.vue'; import YearbookView from '../views/YearbookView.vue'; import NationBettingView from '../views/NationBettingView.vue'; import NpcListView from '../views/NpcListView.vue'; -import TrafficView from '../views/TrafficView.vue'; import { useSessionStore } from '../stores/session'; const routes = [ @@ -258,11 +257,6 @@ const routes = [ requiresGeneral: true, }, }, - { - path: '/traffic', - name: 'traffic', - component: TrafficView, - }, { path: '/npc-list', name: 'npc-list', diff --git a/app/game-frontend/src/views/MainView.vue b/app/game-frontend/src/views/MainView.vue index 8459ebc..857e327 100644 --- a/app/game-frontend/src/views/MainView.vue +++ b/app/game-frontend/src/views/MainView.vue @@ -119,7 +119,6 @@ watch( 왕조일람 연감 천통국 베팅 - 접속량정보 빙의일람 게시판 전투 시뮬레이터 diff --git a/app/game-frontend/src/views/PublicView.vue b/app/game-frontend/src/views/PublicView.vue index 1624fcf..6c3684a 100644 --- a/app/game-frontend/src/views/PublicView.vue +++ b/app/game-frontend/src/views/PublicView.vue @@ -108,7 +108,6 @@ onMounted(() => { 장수 생성/빙의 메인으로 빙의일람 - 접속량정보 diff --git a/app/game-frontend/src/views/TrafficView.vue b/app/game-frontend/src/views/TrafficView.vue deleted file mode 100644 index 58b5f30..0000000 --- a/app/game-frontend/src/views/TrafficView.vue +++ /dev/null @@ -1,343 +0,0 @@ - - - - -