Skip to content

Commit 4514a83

Browse files
committed
Refactor: Split 4 large files into sub-800-line modules
- `mcp/executor.rs` (1718 lines) → directory module with 9 files by tool category - `commands/file_system.rs` (1196 lines) → directory module with 6 files by operation type - `api-server/src/index.ts` (1099 lines) → 7 files by route domain - `write_operations/integration_test.rs` (1564 lines) → 5 files by operation type Pure mechanical splits, no logic changes. All 1282 Rust tests and 88 API server tests pass.
1 parent 4f92702 commit 4514a83

36 files changed

Lines changed: 5776 additions & 5548 deletions

apps/api-server/CLAUDE.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ app versions).
99

1010
| File | Purpose |
1111
| ------------------------------------------- | --------------------------------------------------------------------- |
12-
| `src/index.ts` | Hono app: all routes, webhook processing, admin endpoint |
12+
| `src/index.ts` | Hono app assembly: mounts route modules, wires scheduled handler |
13+
| `src/types.ts` | Shared types (`Bindings`), constants, and helpers (auth, validation) |
14+
| `src/licensing.ts` | Routes: `/activate`, `/validate`, `/webhook/paddle`, `/admin/generate`|
15+
| `src/admin.ts` | Routes: `/admin/stats`, `/admin/downloads`, `/admin/active-users`, `/admin/crashes` |
16+
| `src/telemetry.ts` | Routes: `/crash-report`, `/update-check/:version`, `/download/:version/:arch` |
17+
| `src/likes.ts` | Routes: `/likes/:slug` (GET, POST, DELETE, OPTIONS) |
18+
| `src/scheduled.ts` | Cron handler functions (crash notifications, aggregation, DB size) |
1319
| `src/license.ts` | Short code + license key generation, `LicenseType` enum |
1420
| `src/paddle.ts` | HMAC-SHA256 webhook verification, `constantTimeEqual` |
1521
| `src/paddle-api.ts` | Paddle REST client: transaction/subscription/customer fetch |
@@ -272,10 +278,6 @@ hard to explain as one person. The threshold is not published in the ToS to avoi
272278

273279
## Gotchas
274280

275-
**Gotcha**: `index.ts` is a monolith (all routes, cron handlers, helpers in one file). **Why**: Hono's pattern
276-
encourages co-located routes, and the file was already monolithic before the telemetry migration. If it keeps growing,
277-
consider extracting admin endpoints and cron handlers into separate modules.
278-
279281
**Gotcha**: `verifyAdminAuth` uses a manual type annotation for `c` instead of Hono's `Context` type. **Why**: Using
280282
`Context<{ Bindings: Bindings }>` would require importing Hono's internal generic types and threading them through. The
281283
manual shape `{ env: Bindings; req: { header: ... } }` is simpler and avoids coupling to Hono internals.

apps/api-server/src/admin.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { Hono } from 'hono'
2+
import { constantTimeEqual } from './paddle'
3+
import { type Bindings, activationCountKey, verifyAdminAuth } from './types'
4+
5+
const admin = new Hono<{ Bindings: Bindings }>()
6+
7+
const validDownloadRanges = new Set(['24h', '7d', '30d', 'all'])
8+
const validActiveUserRanges = new Set(['7d', '30d', '90d', 'all'])
9+
const validCrashRanges = new Set(['7d', '30d', '90d', 'all'])
10+
11+
// Values are hardcoded, never from user input — safe to interpolate into SQL.
12+
const rangeToSqliteInterval: Record<string, string> = {
13+
'24h': '-1 day',
14+
'7d': '-7 days',
15+
'30d': '-30 days',
16+
'90d': '-90 days',
17+
}
18+
19+
// Admin stats — returns activation count and device count
20+
// Auth: dedicated ADMIN_API_TOKEN, separate from the Paddle secrets used by /admin/generate
21+
admin.get('/admin/stats', async (c) => {
22+
const token = c.env.ADMIN_API_TOKEN
23+
if (!token) {
24+
return c.json({ error: 'Admin API not configured' }, 500)
25+
}
26+
27+
const authHeader = c.req.header('Authorization')
28+
if (!authHeader || !constantTimeEqual(authHeader, `Bearer ${token}`)) {
29+
return c.json({ error: 'Unauthorized' }, 401)
30+
}
31+
32+
const raw = await c.env.LICENSE_CODES.get(activationCountKey)
33+
const totalActivations = parseInt(raw ?? '0', 10)
34+
35+
// TODO: `activeDevices` requires querying the CF Analytics Engine SQL API
36+
// (`POST /v4/accounts/{id}/analytics_engine/sql`), which is an external HTTP call,
37+
// not available via the `DEVICE_COUNTS` binding (bindings only support `writeDataPoint`).
38+
// For v1, return null. The analytics dashboard queries CF Analytics Engine directly.
39+
const activeDevices: number | null = null
40+
41+
return c.json({ totalActivations, activeDevices })
42+
})
43+
44+
// Admin downloads — aggregated download data from D1
45+
admin.get('/admin/downloads', async (c) => {
46+
const authError = verifyAdminAuth(c)
47+
if (authError) return authError
48+
49+
const range = c.req.query('range') ?? '7d'
50+
if (!validDownloadRanges.has(range)) {
51+
return c.json({ error: 'Invalid range. Use 24h, 7d, 30d, or all' }, 400)
52+
}
53+
54+
const interval = rangeToSqliteInterval[range]
55+
const whereClause = interval ? `WHERE created_at >= datetime('now', '${interval}')` : ''
56+
57+
const { results } = await c.env.TELEMETRY_DB.prepare(
58+
`SELECT date(created_at) AS date, app_version AS version, arch, country, COUNT(*) AS count
59+
FROM downloads ${whereClause}
60+
GROUP BY date, version, arch, country
61+
ORDER BY date ASC`,
62+
).all<{ date: string; version: string; arch: string; country: string; count: number }>()
63+
64+
return c.json(results)
65+
})
66+
67+
// Admin active users — aggregated daily active user data from D1
68+
admin.get('/admin/active-users', async (c) => {
69+
const authError = verifyAdminAuth(c)
70+
if (authError) return authError
71+
72+
const range = c.req.query('range') ?? '7d'
73+
if (!validActiveUserRanges.has(range)) {
74+
return c.json({ error: 'Invalid range. Use 7d, 30d, 90d, or all' }, 400)
75+
}
76+
77+
const interval = rangeToSqliteInterval[range]
78+
const whereClause = interval ? `WHERE date >= date('now', '${interval}')` : ''
79+
80+
const { results } = await c.env.TELEMETRY_DB.prepare(
81+
`SELECT date, app_version AS version, arch, unique_users AS uniqueUsers
82+
FROM daily_active_users ${whereClause}
83+
ORDER BY date ASC`,
84+
).all<{ date: string; version: string; arch: string; uniqueUsers: number }>()
85+
86+
return c.json(results)
87+
})
88+
89+
// Admin crashes — aggregated crash data from D1
90+
admin.get('/admin/crashes', async (c) => {
91+
const authError = verifyAdminAuth(c)
92+
if (authError) return authError
93+
94+
const range = c.req.query('range') ?? '7d'
95+
if (!validCrashRanges.has(range)) {
96+
return c.json({ error: 'Invalid range. Use 7d, 30d, 90d, or all' }, 400)
97+
}
98+
99+
const interval = rangeToSqliteInterval[range]
100+
const whereClause = interval ? `WHERE created_at >= datetime('now', '${interval}')` : ''
101+
102+
const { results } = await c.env.TELEMETRY_DB.prepare(
103+
`SELECT date(created_at) AS date, top_function AS topFunction, signal,
104+
COUNT(*) AS count, GROUP_CONCAT(DISTINCT app_version) AS versions
105+
FROM crash_reports ${whereClause}
106+
GROUP BY date, topFunction, signal
107+
ORDER BY date ASC`,
108+
).all<{ date: string; topFunction: string; signal: string; count: number; versions: string }>()
109+
110+
return c.json(results)
111+
})
112+
113+
export { admin }

0 commit comments

Comments
 (0)