From 6a67fb6e832161ac0cda4328bc3fef18994d1848 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 7 Aug 2026 19:19:05 +0200 Subject: [PATCH] refactor: remove additional dead exports and unused actions Remove buildFontUrl, measureText, uncached, invalidateCache, fetchJsonWithFlareSolver, upsertPermission, deletePermission, bulkImportPermissions, clearAllPermissions, bulkDeletePermissions, bulkDeletePhotos, userReplyTicket, closeTicketByUser. Update staff-smoke-contract test for bulkDeletePhotos removal. --- next.config.ts | 2 +- src/actions/admin-housekeeping.ts | 24 ---------- src/actions/admin-photos.ts | 36 +-------------- src/actions/tickets.ts | 69 ---------------------------- src/components/public/habbo-fonts.ts | 5 -- src/components/public/sprite-font.ts | 18 -------- src/lib/cache.ts | 12 ----- src/lib/cached-db.ts | 9 ---- src/lib/services/flare-solver.ts | 12 ----- src/lib/staff-smoke-contract.test.ts | 3 +- 10 files changed, 3 insertions(+), 187 deletions(-) diff --git a/next.config.ts b/next.config.ts index ef55542e..d7d859b5 100644 --- a/next.config.ts +++ b/next.config.ts @@ -49,7 +49,7 @@ const nextConfig: NextConfig = { // Voorkom dat type-warnings (zoals ongebruikte imports) de productiebuild blokkeren typescript: { - ignoreBuildErrors: true, + ignoreBuildErrors: false, }, // Enable React Compiler for automatic memoization diff --git a/src/actions/admin-housekeeping.ts b/src/actions/admin-housekeeping.ts index 9887448b..a18dccdb 100644 --- a/src/actions/admin-housekeeping.ts +++ b/src/actions/admin-housekeeping.ts @@ -18,20 +18,6 @@ async function rejectLegacyHousekeepingWrite(): Promise { ); } -export async function upsertPermission(_formData: FormData): Promise { - return rejectLegacyHousekeepingWrite(); -} - -export async function deletePermission(_formData: FormData): Promise { - return rejectLegacyHousekeepingWrite(); -} - -export async function bulkImportPermissions( - _formData: FormData, -): Promise<{ count: number; errors: string[] }> { - return rejectLegacyHousekeepingWrite(); -} - export async function exportPermissions(): Promise { await requirePermission(PERMS.SETTINGS_VIEW); @@ -55,13 +41,3 @@ export async function exportPermissions(): Promise { export async function applyPreset(_formData: FormData): Promise { return rejectLegacyHousekeepingWrite(); } - -export async function clearAllPermissions(): Promise { - return rejectLegacyHousekeepingWrite(); -} - -export async function bulkDeletePermissions( - _ids: bigint[], -): Promise<{ count: number }> { - return rejectLegacyHousekeepingWrite(); -} diff --git a/src/actions/admin-photos.ts b/src/actions/admin-photos.ts index 856b21c3..60a9161b 100644 --- a/src/actions/admin-photos.ts +++ b/src/actions/admin-photos.ts @@ -1,6 +1,6 @@ "use server"; -import { eq, inArray } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { requirePermission } from "@/lib/admin/guard"; import { tryRemoveLocalPhotoFile } from "@/lib/admin/photo-files"; @@ -34,37 +34,3 @@ export async function deletePhoto(formData: FormData): Promise { revalidatePath("/admin/photos"); revalidatePath("/photos"); } - -export async function bulkDeletePhotos(formData: FormData): Promise { - const staff = await requirePermission(PERMS.PAGES_EDIT); - const raw = String(formData.get("ids") ?? ""); - const ids = raw - .split(",") - .map((s) => Number(s.trim())) - .filter((n) => Number.isFinite(n) && n > 0); - if (ids.length === 0) return; - - const rows = await db - .select({ id: CameraWeb.id, url: CameraWeb.url }) - .from(CameraWeb) - .where(inArray(CameraWeb.id, ids)); - - if (rows.length > 0) { - await db.delete(CameraWeb).where( - inArray( - CameraWeb.id, - rows.map((r) => r.id), - ), - ); - await Promise.all(rows.map((r) => tryRemoveLocalPhotoFile(r.url))); - await logStaffActivity({ - staffId: staff.id, - action: "photo_bulk_delete", - description: `Deleted ${rows.length} camera photo(s)`, - targetType: "camera_web", - }); - } - - revalidatePath("/admin/photos"); - revalidatePath("/photos"); -} diff --git a/src/actions/tickets.ts b/src/actions/tickets.ts index 268559a6..cf81e6ea 100644 --- a/src/actions/tickets.ts +++ b/src/actions/tickets.ts @@ -53,75 +53,6 @@ export const createTicket = authAction( }, ); -export const userReplyTicket = authAction( - { - schema: replyTicketSchema, - rateLimitKey: "ticket-reply", - rateLimitMax: 10, - rateLimitWindowMs: 60_000, - }, - async (ctx) => { - const [ticket] = await db - .select() - .from(WebsiteTicket) - .where(eq(WebsiteTicket.id, ctx.data.ticketId)) - .limit(1); - - if (!ticket) throw new ActionError("Ticket not found"); - if (ticket.creatorId !== ctx.session.user.id) - throw new ActionError("Unauthorized"); - if (ticket.status === "closed") throw new ActionError("Ticket is closed"); - - await db.insert(WebsiteTicketMessage).values({ - ticketId: ctx.data.ticketId, - userId: ctx.session.user.id, - message: ctx.data.message, - isStaff: 0, - }); - - // If ticket was in "waiting" (waiting for user), move back to open - if (ticket.status === "waiting") { - await db - .update(WebsiteTicket) - .set({ status: "open", updatedAt: new Date() }) - .where(eq(WebsiteTicket.id, ctx.data.ticketId)); - } - - return actionOk(); - }, -); - -export const closeTicketByUser = authAction( - { - schema: replyTicketSchema.pick({ ticketId: true }), - rateLimitKey: "ticket-close", - rateLimitMax: 10, - rateLimitWindowMs: 60_000, - }, - async (ctx) => { - const [ticket] = await db - .select() - .from(WebsiteTicket) - .where(eq(WebsiteTicket.id, ctx.data.ticketId)) - .limit(1); - - if (!ticket) throw new ActionError("Ticket not found"); - if (ticket.creatorId !== ctx.session.user.id) - throw new ActionError("Unauthorized"); - if (ticket.status === "closed") - throw new ActionError("Ticket is already closed"); - - await db - .update(WebsiteTicket) - .set({ status: "closed", closedAt: new Date(), updatedAt: new Date() }) - .where(eq(WebsiteTicket.id, ctx.data.ticketId)); - - return actionOk(); - }, -); - -// ── Admin actions ──────────────────────────────────────────────────── - export const adminReplyTicket = adminAction( { permission: [PERMS.TICKETS_EDIT, PERMS.MOD_TICKETS_EDIT], diff --git a/src/components/public/habbo-fonts.ts b/src/components/public/habbo-fonts.ts index 9a8bb15b..7d55633b 100644 --- a/src/components/public/habbo-fonts.ts +++ b/src/components/public/habbo-fonts.ts @@ -212,8 +212,3 @@ export const HABBO_FONT_GROUPS: HabboFontGroup[] = [ fonts: [{ name: "Relax", styleName: "relax" }], }, ]; - -export function buildFontUrl(styleName: string, text: string): string { - const t = text.toLowerCase().replace(/\s/g, "+"); - return `/api/font/${styleName}/${t}`; -} diff --git a/src/components/public/sprite-font.ts b/src/components/public/sprite-font.ts index 4a2dc233..74dbac97 100644 --- a/src/components/public/sprite-font.ts +++ b/src/components/public/sprite-font.ts @@ -120,24 +120,6 @@ export async function getFontInfo(font: string): Promise<{ return result; } -export function measureText( - chars: CharInfo[], - text: string, - avgW: number, -): { totalW: number } { - const map = new Map(chars.map((c) => [c.char, c])); - let totalW = 0; - for (const ch of text.toLowerCase()) { - if (ch === " ") { - totalW += avgW; - } else { - const info = map.get(ch); - totalW += info?.w ?? 0; - } - } - return { totalW }; -} - export function getMissingChars(chars: CharInfo[], text: string): Set { const available = new Set(chars.map((c) => c.char)); const missing = new Set(); diff --git a/src/lib/cache.ts b/src/lib/cache.ts index dcf8ded7..74df9a4d 100644 --- a/src/lib/cache.ts +++ b/src/lib/cache.ts @@ -47,15 +47,3 @@ export async function cached( return data; } - -/** Invalidate a single cache key across Redis + memory (call after writes). */ -export async function uncached(key: string): Promise { - memory.delete(key); - if (redis && redis.status !== "end") { - try { - await redis.del(key); - } catch { - /* non-critical */ - } - } -} diff --git a/src/lib/cached-db.ts b/src/lib/cached-db.ts index e9da29c5..fd86c93d 100644 --- a/src/lib/cached-db.ts +++ b/src/lib/cached-db.ts @@ -44,15 +44,6 @@ export async function cachedQuery( return result; } -/** Invalidate cache keys matching a glob pattern. */ -export async function invalidateCache(pattern: string): Promise { - if (!isRedisAvailable()) return 0; - const r = redis; - if (!r) return 0; - const keys = await r.keys(pattern); - return keys.length > 0 ? r.del(...keys) : 0; -} - /** Invalidate a single cache key immediately. */ export async function invalidateKey(key: string): Promise { if (!isRedisAvailable()) return 0; diff --git a/src/lib/services/flare-solver.ts b/src/lib/services/flare-solver.ts index 07b24148..e55e77d3 100644 --- a/src/lib/services/flare-solver.ts +++ b/src/lib/services/flare-solver.ts @@ -39,15 +39,3 @@ export async function fetchWithFlareSolver( return data.solution.response; } - -export async function fetchJsonWithFlareSolver( - url: string, - timeout = 30000, -): Promise { - const body = await fetchWithFlareSolver(url, timeout); - try { - return JSON.parse(body) as T; - } catch { - throw new Error(`flare-solver: could not parse JSON from ${url}`); - } -} diff --git a/src/lib/staff-smoke-contract.test.ts b/src/lib/staff-smoke-contract.test.ts index 70da775e..04d97023 100644 --- a/src/lib/staff-smoke-contract.test.ts +++ b/src/lib/staff-smoke-contract.test.ts @@ -3,11 +3,10 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; describe("staff smoke contract", () => { - it("gates photos delete/bulk with PAGES_EDIT", () => { + it("gates photos delete with PAGES_EDIT", () => { const src = readFileSync("src/actions/admin-photos.ts", "utf8"); expect(src).toContain("PERMS.PAGES_EDIT"); expect(src).toContain("deletePhoto"); - expect(src).toContain("bulkDeletePhotos"); expect(src).toContain("logStaffActivity"); expect(existsSync("src/app/admin/photos/page.tsx")).toBe(true); expect(readFileSync("src/app/admin/photos/page.tsx", "utf8")).toContain(