Skip to content

Commit 27fac90

Browse files
committed
refactor: use Hono server timing for new config APIs
1 parent 4aceea6 commit 27fac90

5 files changed

Lines changed: 86 additions & 46 deletions

File tree

server/src/core/hono-middleware.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,6 @@ export const authMiddleware = createMiddleware<{
139139
await next();
140140
});
141141

142-
// Timing middleware
143-
export const timingMiddleware = createMiddleware(async (c, next) => {
144-
const start = Date.now();
145-
await next();
146-
const duration = Date.now() - start;
147-
c.header('Server-Timing', `total;dur=${duration}`);
148-
});
149-
150142
// Helper to set JWT cookie
151143
export function setJWTCookie(c: AppContext, token: string) {
152144
setCookie(c, 'token', token, {

server/src/core/register-middlewares.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { cors } from "hono/cors";
2-
import { authMiddleware, initContainerMiddleware, timingMiddleware } from "./hono-middleware";
2+
import { timing } from "hono/timing";
3+
import { authMiddleware, initContainerMiddleware } from "./hono-middleware";
34
import type { RinApp } from "./app-types";
45

56
export function registerMiddlewares(app: RinApp) {
@@ -14,7 +15,7 @@ export function registerMiddlewares(app: RinApp) {
1415
}),
1516
);
1617

17-
app.use("*", timingMiddleware);
18+
app.use("*", timing());
1819
app.use("*", initContainerMiddleware);
1920
app.use("*", authMiddleware);
2021
}

server/src/services/__tests__/config.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,8 @@ describe("ConfigService", () => {
601601
expect(requests[0].url).toContain("message=hello webhook");
602602
expect(requests[0].init?.method).toBe("GET");
603603
expect(requests[0].init?.body).toBeUndefined();
604+
expect(res.headers.get("Server-Timing")).toContain("webhook_send");
605+
expect(res.headers.get("Server-Timing")).toContain("total");
604606
});
605607

606608
it("should return a readable error when webhook settings are invalid", async () => {

server/src/services/config.ts

Lines changed: 79 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Hono } from "hono";
2+
import { wrapTime } from "hono/timing";
23
import type { AppContext } from "../core/hono-types";
34
import { getAIConfigForFrontend, setAIConfig, getAIConfig } from "../utils/db-config";
45
import { testAIModel } from "../utils/ai";
@@ -35,10 +36,10 @@ export function ConfigService(): Hono {
3536

3637
const db = c.get('db');
3738
const env = c.get('env');
38-
const body = await c.req.json();
39+
const body = await wrapTime(c, 'request_body', c.req.json(), 'Read request body');
3940

4041
// Get current AI config from database
41-
const config = await getAIConfig(db);
42+
const config = await wrapTime(c, 'ai_config', getAIConfig(db), 'Load AI config');
4243

4344
// Build test config with overrides
4445
const testConfig = {
@@ -52,7 +53,7 @@ export function ConfigService(): Hono {
5253
const testPrompt = body.testPrompt || "Hello! This is a test message. Please respond with a simple greeting.";
5354

5455
// Use unified test function
55-
const result = await testAIModel(env, testConfig, testPrompt);
56+
const result = await wrapTime(c, 'ai_test', testAIModel(env, testConfig, testPrompt), 'Run AI test');
5657
return c.json(result);
5758
});
5859

@@ -65,7 +66,7 @@ export function ConfigService(): Hono {
6566

6667
const env = c.get('env');
6768
const serverConfig = c.get('serverConfig');
68-
const body = await c.req.json() as {
69+
const body = await wrapTime(c, 'request_body', c.req.json(), 'Read request body') as {
6970
webhook_url?: string;
7071
"webhook.method"?: string;
7172
"webhook.content_type"?: string;
@@ -74,11 +75,23 @@ export function ConfigService(): Hono {
7475
test_message?: string;
7576
};
7677

77-
const webhookUrl = body.webhook_url ?? await serverConfig.get(WEBHOOK_URL_KEY) ?? env.WEBHOOK_URL;
78-
const webhookMethod = body["webhook.method"] ?? await serverConfig.get("webhook.method") as string | undefined;
79-
const webhookContentType = body["webhook.content_type"] ?? await serverConfig.get("webhook.content_type") as string | undefined;
80-
const webhookHeaders = body["webhook.headers"] ?? await serverConfig.get("webhook.headers") as string | undefined;
81-
const webhookBodyTemplate = body["webhook.body_template"] ?? await serverConfig.get("webhook.body_template") as string | undefined;
78+
const [storedWebhookUrl, webhookMethod, webhookContentType, webhookHeaders, webhookBodyTemplate] = await wrapTime(
79+
c,
80+
'webhook_config',
81+
Promise.all([
82+
serverConfig.get(WEBHOOK_URL_KEY),
83+
serverConfig.get("webhook.method"),
84+
serverConfig.get("webhook.content_type"),
85+
serverConfig.get("webhook.headers"),
86+
serverConfig.get("webhook.body_template"),
87+
]),
88+
'Load webhook config',
89+
) as Array<string | undefined>;
90+
const webhookUrl = body.webhook_url ?? storedWebhookUrl ?? env.WEBHOOK_URL;
91+
const resolvedWebhookMethod = body["webhook.method"] ?? webhookMethod;
92+
const resolvedWebhookContentType = body["webhook.content_type"] ?? webhookContentType;
93+
const resolvedWebhookHeaders = body["webhook.headers"] ?? webhookHeaders;
94+
const resolvedWebhookBodyTemplate = body["webhook.body_template"] ?? webhookBodyTemplate;
8295
const frontendUrl = new URL(c.req.url).origin;
8396
const testMessage = body.test_message?.trim() || "This is a test webhook message from Rin settings.";
8497

@@ -87,23 +100,28 @@ export function ConfigService(): Hono {
87100
}
88101

89102
try {
90-
const response = await notify(
91-
webhookUrl,
92-
{
93-
event: "webhook.test",
94-
message: testMessage,
95-
title: "Webhook Test",
96-
url: `${frontendUrl}/admin/settings`,
97-
username: "admin",
98-
content: testMessage,
99-
description: "Manual webhook test triggered from settings.",
100-
},
101-
{
102-
method: webhookMethod,
103-
contentType: webhookContentType,
104-
headers: webhookHeaders,
105-
bodyTemplate: webhookBodyTemplate,
106-
},
103+
const response = await wrapTime(
104+
c,
105+
'webhook_send',
106+
notify(
107+
webhookUrl,
108+
{
109+
event: "webhook.test",
110+
message: testMessage,
111+
title: "Webhook Test",
112+
url: `${frontendUrl}/admin/settings`,
113+
username: "admin",
114+
content: testMessage,
115+
description: "Manual webhook test triggered from settings.",
116+
},
117+
{
118+
method: resolvedWebhookMethod,
119+
contentType: resolvedWebhookContentType,
120+
headers: resolvedWebhookHeaders,
121+
bodyTemplate: resolvedWebhookBodyTemplate,
122+
},
123+
),
124+
'Send webhook test',
107125
);
108126

109127
if (!response) {
@@ -139,7 +157,12 @@ export function ConfigService(): Hono {
139157
const clientConfig = c.get('clientConfig');
140158
const env = c.get('env');
141159

142-
return c.json(await buildCombinedConfigResponse(db, clientConfig, serverConfig, env));
160+
return c.json(await wrapTime(
161+
c,
162+
'config_response',
163+
buildCombinedConfigResponse(db, clientConfig, serverConfig, env),
164+
'Build config response',
165+
));
143166
});
144167

145168
// GET /config/health
@@ -155,7 +178,12 @@ export function ConfigService(): Hono {
155178
const clientConfig = c.get('clientConfig');
156179
const env = c.get('env');
157180

158-
return c.json(await buildHealthCheckResponse(db, clientConfig, serverConfig, env));
181+
return c.json(await wrapTime(
182+
c,
183+
'health_check',
184+
buildHealthCheckResponse(db, clientConfig, serverConfig, env),
185+
'Build health check',
186+
));
159187
});
160188

161189
app.get('/queue-status', async (c: AppContext) => {
@@ -168,7 +196,7 @@ export function ConfigService(): Hono {
168196
const db = c.get('db');
169197
const env = c.get('env');
170198

171-
return c.json(await buildQueueStatusResponse(db, env));
199+
return c.json(await wrapTime(c, 'queue_status', buildQueueStatusResponse(db, env), 'Build queue status'));
172200
});
173201

174202
app.get('/compat-tasks', async (c: AppContext) => {
@@ -178,7 +206,7 @@ export function ConfigService(): Hono {
178206
return c.text('Unauthorized', 401);
179207
}
180208

181-
return c.json(await buildCompatTasksResponse(c.get('db'), c.get('env')));
209+
return c.json(await wrapTime(c, 'compat_tasks', buildCompatTasksResponse(c.get('db'), c.get('env')), 'Build compatibility tasks'));
182210
});
183211

184212
app.post('/compat-tasks/ai-summary', async (c: AppContext) => {
@@ -189,7 +217,12 @@ export function ConfigService(): Hono {
189217
}
190218

191219
try {
192-
return c.json(await runCompatAISummaryBackfill(c.get('db'), c.get('cache'), c.get('env')));
220+
return c.json(await wrapTime(
221+
c,
222+
'compat_ai_summary',
223+
runCompatAISummaryBackfill(c.get('db'), c.get('cache'), c.get('env')),
224+
'Queue compatibility AI summaries',
225+
));
193226
} catch (error) {
194227
const message = error instanceof Error ? error.message : String(error);
195228
return c.text(message, 400);
@@ -203,7 +236,7 @@ export function ConfigService(): Hono {
203236
return c.text('Unauthorized', 401);
204237
}
205238

206-
return c.json(await listBlurhashCompatCandidates(c.get('db')));
239+
return c.json(await wrapTime(c, 'compat_blurhash_list', listBlurhashCompatCandidates(c.get('db')), 'List blurhash candidates'));
207240
});
208241

209242
app.post('/compat-tasks/blurhash/:id', async (c: AppContext) => {
@@ -218,13 +251,18 @@ export function ConfigService(): Hono {
218251
return c.text('Invalid feed id', 400);
219252
}
220253

221-
const body = await c.req.json() as { content?: string };
254+
const body = await wrapTime(c, 'request_body', c.req.json(), 'Read request body') as { content?: string };
222255
if (!body.content) {
223256
return c.text('Content is required', 400);
224257
}
225258

226259
try {
227-
return c.json(await applyBlurhashCompatUpdate(c.get('db'), c.get('cache'), id, body.content));
260+
return c.json(await wrapTime(
261+
c,
262+
'compat_blurhash_apply',
263+
applyBlurhashCompatUpdate(c.get('db'), c.get('cache'), id, body.content),
264+
'Apply blurhash compatibility update',
265+
));
228266
} catch (error) {
229267
const message = error instanceof Error ? error.message : String(error);
230268
const status = message === 'Feed not found' ? 404 : 400;
@@ -245,7 +283,12 @@ export function ConfigService(): Hono {
245283
}
246284

247285
try {
248-
await retryQueueStatusTask(c.get('db'), c.get('cache'), c.get('env'), id);
286+
await wrapTime(
287+
c,
288+
'queue_retry',
289+
retryQueueStatusTask(c.get('db'), c.get('cache'), c.get('env'), id),
290+
'Retry queue task',
291+
);
249292
return c.json({ success: true });
250293
} catch (error) {
251294
const message = error instanceof Error ? error.message : String(error);
@@ -267,7 +310,7 @@ export function ConfigService(): Hono {
267310
}
268311

269312
try {
270-
await deleteQueueStatusTask(c.get('db'), c.get('cache'), id);
313+
await wrapTime(c, 'queue_delete', deleteQueueStatusTask(c.get('db'), c.get('cache'), id), 'Delete queue task');
271314
return c.json({ success: true });
272315
} catch (error) {
273316
const message = error instanceof Error ? error.message : String(error);

server/tests/fixtures/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { drizzle } from 'drizzle-orm/bun-sqlite';
22
import { Database } from 'bun:sqlite';
33
import { Hono } from 'hono';
44
import { createMiddleware } from 'hono/factory';
5+
import { timing } from 'hono/timing';
56
import { eq } from 'drizzle-orm';
67
import * as schema from '../../src/db/schema';
78
import type { Variables, JWTUtils, OAuth2Utils, CacheImpl } from '../../src/core/hono-types';
@@ -275,6 +276,7 @@ export async function setupTestApp(
275276
const env = createMockEnv(envOverrides);
276277

277278
const app = new Hono<{ Bindings: Env; Variables: Variables }>();
279+
app.use('*', timing());
278280

279281
// Mock middleware to inject dependencies and handle auth
280282
app.use(createMiddleware<{ Bindings: Env; Variables: Variables }>(async (c, next) => {

0 commit comments

Comments
 (0)