WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto

refactor(appview): consolidate three identical error handlers into handleRouteError (#74)

handleReadError, handleWriteError, and handleSecurityCheckError had
byte-for-byte identical implementations. Replaced with a single
handleRouteError function, eliminating ~250 lines of duplicated code
across the error handler and its tests. Updated all 10 call sites.

https://claude.ai/code/session_018SH9pay3PqGo9JDdAv3iRj

Co-authored-by: Claude <noreply@anthropic.com>

authored by

Malpercio
Claude
and committed by
GitHub
bdb0ea96 53a5e8f0

+55 -301
+12 -144
apps/appview/src/lib/__tests__/route-errors.test.ts
··· 1 1 import { describe, it, expect, vi, afterEach } from "vitest"; 2 2 import { Hono } from "hono"; 3 3 import { 4 - handleReadError, 5 - handleWriteError, 6 - handleSecurityCheckError, 4 + handleRouteError, 7 5 safeParseJsonBody, 8 6 getForumAgentOrError, 9 7 } from "../route-errors.js"; ··· 27 25 return app; 28 26 } 29 27 30 - // ─── handleReadError ────────────────────────────────────────────────────────── 28 + // ─── handleRouteError ───────────────────────────────────────────────────────── 31 29 32 - describe("handleReadError", () => { 30 + describe("handleRouteError", () => { 33 31 it("returns 503 for network errors", async () => { 34 32 const app = makeApp((c) => 35 - handleReadError(c, new Error("fetch failed"), "Failed to read resource", { 33 + handleRouteError(c, new Error("fetch failed"), "Failed to read resource", { 36 34 operation: "GET /test", 37 35 logger: createMockLogger(), 38 36 }) ··· 49 47 50 48 it("returns 503 for database errors", async () => { 51 49 const app = makeApp((c) => 52 - handleReadError(c, new Error("database query failed"), "Failed to read resource", { 50 + handleRouteError(c, new Error("database query failed"), "Failed to read resource", { 53 51 operation: "GET /test", 54 52 logger: createMockLogger(), 55 53 }) ··· 66 64 67 65 it("returns 500 for unexpected errors", async () => { 68 66 const app = makeApp((c) => 69 - handleReadError(c, new Error("Something went wrong"), "Failed to read resource", { 67 + handleRouteError(c, new Error("Something went wrong"), "Failed to read resource", { 70 68 operation: "GET /test", 71 69 logger: createMockLogger(), 72 70 }) ··· 82 80 it("logs structured context on error", async () => { 83 81 const mockLogger = createMockLogger(); 84 82 const app = makeApp((c) => 85 - handleReadError(c, new Error("boom"), "Failed to fetch things", { 83 + handleRouteError(c, new Error("boom"), "Failed to fetch things", { 86 84 operation: "GET /test", 87 85 logger: mockLogger, 88 86 resourceId: "123", ··· 105 103 const mockLogger = createMockLogger(); 106 104 const programmingError = new TypeError("Cannot read property of undefined"); 107 105 const app = makeApp((c) => 108 - handleReadError(c, programmingError, "Failed to read resource", { 106 + handleRouteError(c, programmingError, "Failed to read resource", { 109 107 operation: "GET /test", 110 108 logger: mockLogger, 111 109 }) ··· 129 127 expect(mockLogger.error).not.toHaveBeenCalledWith( 130 128 "Failed to read resource", 131 129 expect.any(Object) 132 - ); 133 - }); 134 - }); 135 - 136 - // ─── handleWriteError ───────────────────────────────────────────────────────── 137 - 138 - describe("handleWriteError", () => { 139 - it("returns 503 for network errors", async () => { 140 - const app = makeApp((c) => 141 - handleWriteError(c, new Error("fetch failed"), "Failed to create thing", { 142 - operation: "POST /test", 143 - logger: createMockLogger(), 144 - }) 145 - ); 146 - 147 - const res = await app.request("/test", { method: "POST" }); 148 - 149 - expect(res.status).toBe(503); 150 - const data = await res.json(); 151 - expect(data.error).toBe( 152 - "Unable to reach external service. Please try again later." 153 130 ); 154 131 }); 155 132 156 - it("returns 503 for database errors", async () => { 157 - const app = makeApp((c) => 158 - handleWriteError(c, new Error("database connection lost"), "Failed to create thing", { 159 - operation: "POST /test", 160 - logger: createMockLogger(), 161 - }) 162 - ); 163 - 164 - const res = await app.request("/test", { method: "POST" }); 165 - 166 - expect(res.status).toBe(503); 167 - const data = await res.json(); 168 - expect(data.error).toBe( 169 - "Database temporarily unavailable. Please try again later." 170 - ); 171 - }); 172 - 173 - it("returns 500 for unexpected errors", async () => { 133 + it("works for write-path errors (POST endpoints)", async () => { 174 134 const app = makeApp((c) => 175 - handleWriteError(c, new Error("Something unexpected"), "Failed to create thing", { 176 - operation: "POST /test", 177 - logger: createMockLogger(), 178 - }) 179 - ); 180 - 181 - const res = await app.request("/test", { method: "POST" }); 182 - 183 - expect(res.status).toBe(500); 184 - const data = await res.json(); 185 - expect(data.error).toBe("Failed to create thing. Please contact support if this persists."); 186 - }); 187 - 188 - it("re-throws TypeError (programming error) and logs CRITICAL", async () => { 189 - const mockLogger = createMockLogger(); 190 - const programmingError = new TypeError("Cannot read property of null"); 191 - const app = makeApp((c) => 192 - handleWriteError(c, programmingError, "Failed to create thing", { 193 - operation: "POST /test", 194 - logger: mockLogger, 195 - }) 196 - ); 197 - 198 - const res = await app.request("/test", { method: "POST" }); 199 - expect(res.status).toBe(500); 200 - 201 - expect(mockLogger.error).toHaveBeenCalledWith( 202 - "CRITICAL: Programming error in POST /test", 203 - expect.objectContaining({ 135 + handleRouteError(c, new Error("fetch failed"), "Failed to create thing", { 204 136 operation: "POST /test", 205 - error: "Cannot read property of null", 206 - stack: expect.any(String), 207 - }) 208 - ); 209 - 210 - expect(mockLogger.error).not.toHaveBeenCalledWith( 211 - "Failed to create thing", 212 - expect.any(Object) 213 - ); 214 - }); 215 - }); 216 - 217 - // ─── handleSecurityCheckError ───────────────────────────────────────────────── 218 - 219 - describe("handleSecurityCheckError", () => { 220 - it("returns 503 for network errors", async () => { 221 - const app = makeApp((c) => 222 - handleSecurityCheckError(c, new Error("fetch failed"), "Unable to verify access", { 223 - operation: "POST /test - security check", 224 137 logger: createMockLogger(), 225 138 }) 226 139 ); ··· 234 147 ); 235 148 }); 236 149 237 - it("returns 503 for database errors", async () => { 238 - const app = makeApp((c) => 239 - handleSecurityCheckError(c, new Error("sql query failed"), "Unable to verify access", { 240 - operation: "POST /test - security check", 241 - logger: createMockLogger(), 242 - }) 243 - ); 244 - 245 - const res = await app.request("/test", { method: "POST" }); 246 - 247 - expect(res.status).toBe(503); 248 - const data = await res.json(); 249 - expect(data.error).toBe( 250 - "Database temporarily unavailable. Please try again later." 251 - ); 252 - }); 253 - 254 - it("returns 500 for unexpected errors (fail closed)", async () => { 150 + it("works for security check errors (fail closed)", async () => { 255 151 const app = makeApp((c) => 256 - handleSecurityCheckError(c, new Error("Something unexpected"), "Unable to verify access", { 152 + handleRouteError(c, new Error("Something unexpected"), "Unable to verify access", { 257 153 operation: "POST /test - security check", 258 154 logger: createMockLogger(), 259 155 }) ··· 265 161 const data = await res.json(); 266 162 expect(data.error).toBe( 267 163 "Unable to verify access. Please contact support if this persists." 268 - ); 269 - }); 270 - 271 - it("re-throws TypeError (programming error) and logs CRITICAL", async () => { 272 - const mockLogger = createMockLogger(); 273 - const programmingError = new TypeError("Cannot read property of undefined"); 274 - const app = makeApp((c) => 275 - handleSecurityCheckError(c, programmingError, "Unable to verify access", { 276 - operation: "POST /test - security check", 277 - logger: mockLogger, 278 - }) 279 - ); 280 - 281 - const res = await app.request("/test", { method: "POST" }); 282 - expect(res.status).toBe(500); 283 - 284 - expect(mockLogger.error).toHaveBeenCalledWith( 285 - "CRITICAL: Programming error in POST /test - security check", 286 - expect.objectContaining({ 287 - operation: "POST /test - security check", 288 - error: "Cannot read property of undefined", 289 - stack: expect.any(String), 290 - }) 291 - ); 292 - 293 - expect(mockLogger.error).not.toHaveBeenCalledWith( 294 - "Unable to verify access", 295 - expect.any(Object) 296 164 ); 297 165 }); 298 166 });
+2 -114
apps/appview/src/lib/route-errors.ts
··· 25 25 } 26 26 27 27 /** 28 - * Handle errors in read-path route handlers (GET endpoints). 28 + * Handle errors in route handlers and security checks. 29 29 * 30 30 * 1. Re-throws programming errors (TypeError, ReferenceError, SyntaxError) 31 31 * 2. Logs the error with structured context ··· 39 39 * @param userMessage - User-facing error message (e.g., "Failed to retrieve forum metadata") 40 40 * @param ctx - Structured logging context 41 41 */ 42 - export function handleReadError( 43 - c: Context, 44 - error: unknown, 45 - userMessage: string, 46 - ctx: ErrorContext 47 - ): Response { 48 - if (isProgrammingError(error)) { 49 - const { message, stack } = formatError(error); 50 - ctx.logger.error(`CRITICAL: Programming error in ${ctx.operation}`, { 51 - ...ctx, 52 - error: message, 53 - stack, 54 - }); 55 - throw error; 56 - } 57 - 58 - ctx.logger.error(userMessage, { 59 - ...ctx, 60 - error: formatError(error).message, 61 - }); 62 - 63 - if (error instanceof Error && isNetworkError(error)) { 64 - return c.json( 65 - { error: "Unable to reach external service. Please try again later." }, 66 - 503 67 - ) as unknown as Response; 68 - } 69 - 70 - if (error instanceof Error && isDatabaseError(error)) { 71 - return c.json( 72 - { error: "Database temporarily unavailable. Please try again later." }, 73 - 503 74 - ) as unknown as Response; 75 - } 76 - 77 - return c.json( 78 - { error: `${userMessage}. Please contact support if this persists.` }, 79 - 500 80 - ) as unknown as Response; 81 - } 82 - 83 - /** 84 - * Handle errors in write-path route handlers (POST/PUT/DELETE endpoints). 85 - * 86 - * 1. Re-throws programming errors (TypeError, ReferenceError, SyntaxError) 87 - * 2. Logs the error with structured context 88 - * 3. Classifies the error and returns the appropriate HTTP status: 89 - * - 503 for network errors (PDS unreachable, user should retry) 90 - * - 503 for database errors (temporary, user should retry) 91 - * - 500 for unexpected errors (server bug, needs investigation) 92 - * 93 - * @param c - Hono context 94 - * @param error - The caught error 95 - * @param userMessage - User-facing error message (e.g., "Failed to create topic") 96 - * @param ctx - Structured logging context 97 - */ 98 - export function handleWriteError( 99 - c: Context, 100 - error: unknown, 101 - userMessage: string, 102 - ctx: ErrorContext 103 - ): Response { 104 - if (isProgrammingError(error)) { 105 - const { message, stack } = formatError(error); 106 - ctx.logger.error(`CRITICAL: Programming error in ${ctx.operation}`, { 107 - ...ctx, 108 - error: message, 109 - stack, 110 - }); 111 - throw error; 112 - } 113 - 114 - ctx.logger.error(userMessage, { 115 - ...ctx, 116 - error: formatError(error).message, 117 - }); 118 - 119 - if (error instanceof Error && isNetworkError(error)) { 120 - return c.json( 121 - { error: "Unable to reach external service. Please try again later." }, 122 - 503 123 - ) as unknown as Response; 124 - } 125 - 126 - if (error instanceof Error && isDatabaseError(error)) { 127 - return c.json( 128 - { error: "Database temporarily unavailable. Please try again later." }, 129 - 503 130 - ) as unknown as Response; 131 - } 132 - 133 - return c.json( 134 - { error: `${userMessage}. Please contact support if this persists.` }, 135 - 500 136 - ) as unknown as Response; 137 - } 138 - 139 - /** 140 - * Handle errors in security-critical checks (ban check, lock check, permission verification). 141 - * 142 - * Fail-closed: returns an error response that denies the operation. 143 - * 144 - * 1. Re-throws programming errors 145 - * 2. Returns 503 for network errors (external service unreachable, user should retry) 146 - * 3. Returns 503 for database errors (temporary, user should retry) 147 - * 4. Returns 500 for unexpected errors (denying access) 148 - * 149 - * @param c - Hono context 150 - * @param error - The caught error 151 - * @param userMessage - User-facing error message (e.g., "Unable to verify permissions") 152 - * @param ctx - Structured logging context 153 - */ 154 - export function handleSecurityCheckError( 42 + export function handleRouteError( 155 43 c: Context, 156 44 error: unknown, 157 45 userMessage: string,
+2 -2
apps/appview/src/middleware/require-not-banned.ts
··· 1 1 import type { Context, Next } from "hono"; 2 2 import type { AppContext } from "../lib/app-context.js"; 3 3 import type { Variables } from "../types.js"; 4 - import { handleSecurityCheckError } from "../lib/route-errors.js"; 4 + import { handleRouteError } from "../lib/route-errors.js"; 5 5 import { getActiveBans } from "../routes/helpers.js"; 6 6 7 7 /** ··· 26 26 return c.json({ error: "You are banned from this forum" }, 403); 27 27 } 28 28 } catch (error) { 29 - return handleSecurityCheckError(c, error, "Unable to verify ban status", { 29 + return handleRouteError(c, error, "Unable to verify ban status", { 30 30 operation: `${c.req.method} ${c.req.path} - ban check`, 31 31 logger: ctx.logger, 32 32 userId: user.did,
+8 -9
apps/appview/src/routes/admin.ts
··· 9 9 import { BackfillStatus } from "../lib/backfill-manager.js"; 10 10 import { CursorManager } from "../lib/cursor-manager.js"; 11 11 import { 12 - handleReadError, 13 - handleWriteError, 12 + handleRouteError, 14 13 safeParseJsonBody, 15 14 getForumAgentOrError, 16 15 } from "../lib/route-errors.js"; ··· 128 127 targetDid, 129 128 }); 130 129 } catch (error) { 131 - return handleWriteError(c, error, "Failed to assign role", { 130 + return handleRouteError(c, error, "Failed to assign role", { 132 131 operation: "POST /api/admin/members/:did/role", 133 132 logger: ctx.logger, 134 133 targetDid, ··· 136 135 }); 137 136 } 138 137 } catch (error) { 139 - return handleReadError(c, error, "Failed to process role assignment", { 138 + return handleRouteError(c, error, "Failed to process role assignment", { 140 139 operation: "POST /api/admin/members/:did/role", 141 140 logger: ctx.logger, 142 141 targetDid, ··· 189 188 190 189 return c.json({ roles: rolesWithPermissions }); 191 190 } catch (error) { 192 - return handleReadError(c, error, "Failed to retrieve roles", { 191 + return handleRouteError(c, error, "Failed to retrieve roles", { 193 192 operation: "GET /api/admin/roles", 194 193 logger: ctx.logger, 195 194 }); ··· 237 236 isTruncated: membersList.length === 100, 238 237 }); 239 238 } catch (error) { 240 - return handleReadError(c, error, "Failed to retrieve members", { 239 + return handleRouteError(c, error, "Failed to retrieve members", { 241 240 operation: "GET /api/admin/members", 242 241 logger: ctx.logger, 243 242 }); ··· 301 300 permissions, 302 301 }); 303 302 } catch (error) { 304 - return handleReadError(c, error, "Failed to retrieve your membership", { 303 + return handleRouteError(c, error, "Failed to retrieve your membership", { 305 304 operation: "GET /api/admin/members/me", 306 305 logger: ctx.logger, 307 306 did: user.did, ··· 432 431 errorMessage: row.errorMessage, 433 432 }); 434 433 } catch (error) { 435 - return handleReadError(c, error, "Failed to fetch backfill progress", { 434 + return handleRouteError(c, error, "Failed to fetch backfill progress", { 436 435 operation: "GET /api/admin/backfill/:id", 437 436 logger: ctx.logger, 438 437 id, ··· 475 474 })), 476 475 }); 477 476 } catch (error) { 478 - return handleReadError(c, error, "Failed to fetch backfill errors", { 477 + return handleRouteError(c, error, "Failed to fetch backfill errors", { 479 478 operation: "GET /api/admin/backfill/:id/errors", 480 479 logger: ctx.logger, 481 480 id,
+4 -4
apps/appview/src/routes/boards.ts
··· 3 3 import { boards, categories, posts, users } from "@atbb/db"; 4 4 import { asc, count, eq, and, desc, isNull } from "drizzle-orm"; 5 5 import { serializeBoard, parseBigIntParam, serializePost } from "./helpers.js"; 6 - import { handleReadError } from "../lib/route-errors.js"; 6 + import { handleRouteError } from "../lib/route-errors.js"; 7 7 8 8 /** 9 9 * Factory function that creates board routes with access to app context. ··· 23 23 boards: allBoards.map(({ boards: board }) => serializeBoard(board)), 24 24 }); 25 25 } catch (error) { 26 - return handleReadError(c, error, "Failed to retrieve boards", { 26 + return handleRouteError(c, error, "Failed to retrieve boards", { 27 27 operation: "GET /api/boards", 28 28 logger: ctx.logger, 29 29 }); ··· 49 49 50 50 return c.json(serializeBoard(board)); 51 51 } catch (error) { 52 - return handleReadError(c, error, "Failed to retrieve board", { 52 + return handleRouteError(c, error, "Failed to retrieve board", { 53 53 operation: "GET /api/boards/:id", 54 54 logger: ctx.logger, 55 55 boardId: raw, ··· 117 117 limit, 118 118 }); 119 119 } catch (error) { 120 - return handleReadError(c, error, "Failed to retrieve topics", { 120 + return handleRouteError(c, error, "Failed to retrieve topics", { 121 121 operation: "GET /api/boards/:id/topics", 122 122 logger: ctx.logger, 123 123 boardId: id,
+4 -4
apps/appview/src/routes/categories.ts
··· 3 3 import { categories, boards } from "@atbb/db"; 4 4 import { eq, asc } from "drizzle-orm"; 5 5 import { serializeCategory, serializeBoard, parseBigIntParam } from "./helpers.js"; 6 - import { handleReadError } from "../lib/route-errors.js"; 6 + import { handleRouteError } from "../lib/route-errors.js"; 7 7 8 8 /** 9 9 * Factory function that creates category routes with access to app context. ··· 28 28 categories: allCategories.map(serializeCategory), 29 29 }); 30 30 } catch (error) { 31 - return handleReadError(c, error, "Failed to retrieve categories", { 31 + return handleRouteError(c, error, "Failed to retrieve categories", { 32 32 operation: "GET /api/categories", 33 33 logger: ctx.logger, 34 34 }); ··· 54 54 55 55 return c.json(serializeCategory(category)); 56 56 } catch (error) { 57 - return handleReadError(c, error, "Failed to retrieve category", { 57 + return handleRouteError(c, error, "Failed to retrieve category", { 58 58 operation: "GET /api/categories/:id", 59 59 logger: ctx.logger, 60 60 categoryId: raw, ··· 92 92 boards: categoryBoards.map(serializeBoard), 93 93 }); 94 94 } catch (error) { 95 - return handleReadError(c, error, "Failed to retrieve boards", { 95 + return handleRouteError(c, error, "Failed to retrieve boards", { 96 96 operation: "GET /api/categories/:id/boards", 97 97 logger: ctx.logger, 98 98 categoryId: id,
+2 -2
apps/appview/src/routes/forum.ts
··· 3 3 import { forums } from "@atbb/db"; 4 4 import { eq } from "drizzle-orm"; 5 5 import { serializeForum } from "./helpers.js"; 6 - import { handleReadError } from "../lib/route-errors.js"; 6 + import { handleRouteError } from "../lib/route-errors.js"; 7 7 8 8 /** 9 9 * Factory function that creates forum routes with access to app context. ··· 24 24 25 25 return c.json(serializeForum(forum)); 26 26 } catch (error) { 27 - return handleReadError(c, error, "Failed to retrieve forum metadata", { 27 + return handleRouteError(c, error, "Failed to retrieve forum metadata", { 28 28 operation: "GET /api/forum", 29 29 logger: ctx.logger, 30 30 });
+13 -14
apps/appview/src/routes/mod.ts
··· 6 6 import { requirePermission } from "../middleware/permissions.js"; 7 7 import { isProgrammingError } from "../lib/errors.js"; 8 8 import { 9 - handleWriteError, 10 - handleReadError, 9 + handleRouteError, 11 10 safeParseJsonBody, 12 11 getForumAgentOrError, 13 12 } from "../lib/route-errors.js"; ··· 136 135 return c.json({ error: "Target user not found" }, 404); 137 136 } 138 137 } catch (error) { 139 - return handleReadError(c, error, "Failed to check user membership", { 138 + return handleRouteError(c, error, "Failed to check user membership", { 140 139 operation: "POST /api/mod/ban", 141 140 logger: ctx.logger, 142 141 targetDid, ··· 193 192 alreadyActive: false, 194 193 }); 195 194 } catch (error) { 196 - return handleWriteError(c, error, "Failed to record moderation action", { 195 + return handleRouteError(c, error, "Failed to record moderation action", { 197 196 operation: "POST /api/mod/ban", 198 197 logger: ctx.logger, 199 198 moderatorDid: user.did, ··· 243 242 return c.json({ error: "Target user not found" }, 404); 244 243 } 245 244 } catch (error) { 246 - return handleReadError(c, error, "Failed to check user membership", { 245 + return handleRouteError(c, error, "Failed to check user membership", { 247 246 operation: "DELETE /api/mod/ban/:did", 248 247 logger: ctx.logger, 249 248 targetDid, ··· 301 300 alreadyActive: false, 302 301 }); 303 302 } catch (error) { 304 - return handleWriteError(c, error, "Failed to record moderation action", { 303 + return handleRouteError(c, error, "Failed to record moderation action", { 305 304 operation: "DELETE /api/mod/ban/:did", 306 305 logger: ctx.logger, 307 306 moderatorDid: user.did, ··· 356 355 357 356 topic = result; 358 357 } catch (error) { 359 - return handleReadError(c, error, "Failed to check topic", { 358 + return handleRouteError(c, error, "Failed to check topic", { 360 359 operation: "POST /api/mod/lock", 361 360 logger: ctx.logger, 362 361 topicId, ··· 426 425 alreadyActive: false, 427 426 }); 428 427 } catch (error) { 429 - return handleWriteError(c, error, "Failed to record moderation action", { 428 + return handleRouteError(c, error, "Failed to record moderation action", { 430 429 operation: "POST /api/mod/lock", 431 430 logger: ctx.logger, 432 431 moderatorDid: user.did, ··· 481 480 482 481 topic = result; 483 482 } catch (error) { 484 - return handleReadError(c, error, "Failed to check topic", { 483 + return handleRouteError(c, error, "Failed to check topic", { 485 484 operation: "DELETE /api/mod/lock/:topicId", 486 485 logger: ctx.logger, 487 486 topicId: topicIdParam, ··· 552 551 alreadyActive: false, 553 552 }); 554 553 } catch (error) { 555 - return handleWriteError(c, error, "Failed to record moderation action", { 554 + return handleRouteError(c, error, "Failed to record moderation action", { 556 555 operation: "DELETE /api/mod/lock/:topicId", 557 556 logger: ctx.logger, 558 557 moderatorDid: user.did, ··· 616 615 617 616 post = result; 618 617 } catch (error) { 619 - return handleReadError(c, error, "Failed to retrieve post", { 618 + return handleRouteError(c, error, "Failed to retrieve post", { 620 619 operation: "POST /api/mod/hide", 621 620 logger: ctx.logger, 622 621 postId, ··· 680 679 alreadyActive: false, 681 680 }, 200); 682 681 } catch (error) { 683 - return handleWriteError(c, error, "Failed to record moderation action", { 682 + return handleRouteError(c, error, "Failed to record moderation action", { 684 683 operation: "POST /api/mod/hide", 685 684 logger: ctx.logger, 686 685 moderatorDid: user.did, ··· 740 739 741 740 post = result; 742 741 } catch (error) { 743 - return handleReadError(c, error, "Failed to retrieve post", { 742 + return handleRouteError(c, error, "Failed to retrieve post", { 744 743 operation: "DELETE /api/mod/hide/:postId", 745 744 logger: ctx.logger, 746 745 postId: postIdParam, ··· 805 804 alreadyActive: false, 806 805 }, 200); 807 806 } catch (error) { 808 - return handleWriteError(c, error, "Failed to record moderation action", { 807 + return handleRouteError(c, error, "Failed to record moderation action", { 809 808 operation: "DELETE /api/mod/hide/:postId", 810 809 logger: ctx.logger, 811 810 moderatorDid: user.did,
+4 -4
apps/appview/src/routes/posts.ts
··· 5 5 import { requireAuth } from "../middleware/auth.js"; 6 6 import { requirePermission } from "../middleware/permissions.js"; 7 7 import { requireNotBanned } from "../middleware/require-not-banned.js"; 8 - import { handleWriteError, handleSecurityCheckError, safeParseJsonBody } from "../lib/route-errors.js"; 8 + import { handleRouteError, safeParseJsonBody } from "../lib/route-errors.js"; 9 9 import { 10 10 validatePostText, 11 11 parseBigIntParam, ··· 52 52 return c.json({ error: "This topic is locked and not accepting new replies" }, 403); 53 53 } 54 54 } catch (error) { 55 - return handleSecurityCheckError(c, error, "Unable to verify topic status", { 55 + return handleRouteError(c, error, "Unable to verify topic status", { 56 56 operation: "POST /api/posts - lock check", 57 57 logger: ctx.logger, 58 58 userId: user.did, ··· 65 65 try { 66 66 postsMap = await getPostsByIds(ctx.db, [rootId, parentId]); 67 67 } catch (error) { 68 - return handleWriteError(c, error, "Failed to look up posts for reply", { 68 + return handleRouteError(c, error, "Failed to look up posts for reply", { 69 69 operation: "POST /api/posts - post lookup", 70 70 logger: ctx.logger, 71 71 userId: user.did, ··· 133 133 201 134 134 ); 135 135 } catch (error) { 136 - return handleWriteError(c, error, "Failed to create post", { 136 + return handleRouteError(c, error, "Failed to create post", { 137 137 operation: "POST /api/posts", 138 138 logger: ctx.logger, 139 139 userId: user.did,
+4 -4
apps/appview/src/routes/topics.ts
··· 8 8 import { requirePermission } from "../middleware/permissions.js"; 9 9 import { requireNotBanned } from "../middleware/require-not-banned.js"; 10 10 import { parseAtUri } from "../lib/at-uri.js"; 11 - import { handleReadError, handleWriteError, safeParseJsonBody } from "../lib/route-errors.js"; 11 + import { handleRouteError, safeParseJsonBody } from "../lib/route-errors.js"; 12 12 import { isProgrammingError } from "../lib/errors.js"; 13 13 import { 14 14 parseBigIntParam, ··· 81 81 .offset(offset), 82 82 ]); 83 83 } catch (error) { 84 - return handleReadError(c, error, "Failed to retrieve replies for topic", { 84 + return handleRouteError(c, error, "Failed to retrieve replies for topic", { 85 85 operation: "GET /api/topics/:id - reply query", 86 86 logger: ctx.logger, 87 87 topicId: id, ··· 157 157 limit, 158 158 }); 159 159 } catch (error) { 160 - return handleReadError(c, error, "Failed to retrieve topic", { 160 + return handleRouteError(c, error, "Failed to retrieve topic", { 161 161 operation: "GET /api/topics/:id - topic query", 162 162 logger: ctx.logger, 163 163 topicId: id, ··· 312 312 201 313 313 ); 314 314 } catch (error) { 315 - return handleWriteError(c, error, "Failed to create topic", { 315 + return handleRouteError(c, error, "Failed to create topic", { 316 316 operation: "POST /api/topics", 317 317 logger: ctx.logger, 318 318 userId: user.did,