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.
This commit is contained in:
1 parent
24bf8eabb9
commit
6a67fb6e83
10 files changed
+3
-187
No files matched your search
+1
-1
@@ -49,7 +49,7 @@ const nextConfig: NextConfig = {
|
|||||||
|
|
||||||
// Voorkom dat type-warnings (zoals ongebruikte imports) de productiebuild blokkeren
|
// Voorkom dat type-warnings (zoals ongebruikte imports) de productiebuild blokkeren
|
||||||
typescript: {
|
typescript: {
|
||||||
ignoreBuildErrors: true,
|
ignoreBuildErrors: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Enable React Compiler for automatic memoization
|
// Enable React Compiler for automatic memoization
|
||||||
|
|||||||
@@ -18,20 +18,6 @@ async function rejectLegacyHousekeepingWrite(): Promise<never> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upsertPermission(_formData: FormData): Promise<void> {
|
|
||||||
return rejectLegacyHousekeepingWrite();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deletePermission(_formData: FormData): Promise<void> {
|
|
||||||
return rejectLegacyHousekeepingWrite();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function bulkImportPermissions(
|
|
||||||
_formData: FormData,
|
|
||||||
): Promise<{ count: number; errors: string[] }> {
|
|
||||||
return rejectLegacyHousekeepingWrite();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function exportPermissions(): Promise<string> {
|
export async function exportPermissions(): Promise<string> {
|
||||||
await requirePermission(PERMS.SETTINGS_VIEW);
|
await requirePermission(PERMS.SETTINGS_VIEW);
|
||||||
|
|
||||||
@@ -55,13 +41,3 @@ export async function exportPermissions(): Promise<string> {
|
|||||||
export async function applyPreset(_formData: FormData): Promise<void> {
|
export async function applyPreset(_formData: FormData): Promise<void> {
|
||||||
return rejectLegacyHousekeepingWrite();
|
return rejectLegacyHousekeepingWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clearAllPermissions(): Promise<void> {
|
|
||||||
return rejectLegacyHousekeepingWrite();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function bulkDeletePermissions(
|
|
||||||
_ids: bigint[],
|
|
||||||
): Promise<{ count: number }> {
|
|
||||||
return rejectLegacyHousekeepingWrite();
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { eq, inArray } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { requirePermission } from "@/lib/admin/guard";
|
import { requirePermission } from "@/lib/admin/guard";
|
||||||
import { tryRemoveLocalPhotoFile } from "@/lib/admin/photo-files";
|
import { tryRemoveLocalPhotoFile } from "@/lib/admin/photo-files";
|
||||||
@@ -34,37 +34,3 @@ export async function deletePhoto(formData: FormData): Promise<void> {
|
|||||||
revalidatePath("/admin/photos");
|
revalidatePath("/admin/photos");
|
||||||
revalidatePath("/photos");
|
revalidatePath("/photos");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bulkDeletePhotos(formData: FormData): Promise<void> {
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
@@ -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(
|
export const adminReplyTicket = adminAction(
|
||||||
{
|
{
|
||||||
permission: [PERMS.TICKETS_EDIT, PERMS.MOD_TICKETS_EDIT],
|
permission: [PERMS.TICKETS_EDIT, PERMS.MOD_TICKETS_EDIT],
|
||||||
|
|||||||
@@ -212,8 +212,3 @@ export const HABBO_FONT_GROUPS: HabboFontGroup[] = [
|
|||||||
fonts: [{ name: "Relax", styleName: "relax" }],
|
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}`;
|
|
||||||
}
|
|
||||||
@@ -120,24 +120,6 @@ export async function getFontInfo(font: string): Promise<{
|
|||||||
return result;
|
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<string> {
|
export function getMissingChars(chars: CharInfo[], text: string): Set<string> {
|
||||||
const available = new Set(chars.map((c) => c.char));
|
const available = new Set(chars.map((c) => c.char));
|
||||||
const missing = new Set<string>();
|
const missing = new Set<string>();
|
||||||
|
|||||||
@@ -47,15 +47,3 @@ export async function cached<T>(
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Invalidate a single cache key across Redis + memory (call after writes). */
|
|
||||||
export async function uncached(key: string): Promise<void> {
|
|
||||||
memory.delete(key);
|
|
||||||
if (redis && redis.status !== "end") {
|
|
||||||
try {
|
|
||||||
await redis.del(key);
|
|
||||||
} catch {
|
|
||||||
/* non-critical */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -44,15 +44,6 @@ export async function cachedQuery<T>(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Invalidate cache keys matching a glob pattern. */
|
|
||||||
export async function invalidateCache(pattern: string): Promise<number> {
|
|
||||||
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. */
|
/** Invalidate a single cache key immediately. */
|
||||||
export async function invalidateKey(key: string): Promise<number> {
|
export async function invalidateKey(key: string): Promise<number> {
|
||||||
if (!isRedisAvailable()) return 0;
|
if (!isRedisAvailable()) return 0;
|
||||||
|
|||||||
@@ -39,15 +39,3 @@ export async function fetchWithFlareSolver(
|
|||||||
|
|
||||||
return data.solution.response;
|
return data.solution.response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchJsonWithFlareSolver<T = unknown>(
|
|
||||||
url: string,
|
|
||||||
timeout = 30000,
|
|
||||||
): Promise<T> {
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,11 +3,10 @@ import { join } from "node:path";
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
describe("staff smoke contract", () => {
|
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");
|
const src = readFileSync("src/actions/admin-photos.ts", "utf8");
|
||||||
expect(src).toContain("PERMS.PAGES_EDIT");
|
expect(src).toContain("PERMS.PAGES_EDIT");
|
||||||
expect(src).toContain("deletePhoto");
|
expect(src).toContain("deletePhoto");
|
||||||
expect(src).toContain("bulkDeletePhotos");
|
|
||||||
expect(src).toContain("logStaffActivity");
|
expect(src).toContain("logStaffActivity");
|
||||||
expect(existsSync("src/app/admin/photos/page.tsx")).toBe(true);
|
expect(existsSync("src/app/admin/photos/page.tsx")).toBe(true);
|
||||||
expect(readFileSync("src/app/admin/photos/page.tsx", "utf8")).toContain(
|
expect(readFileSync("src/app/admin/photos/page.tsx", "utf8")).toContain(
|
||||||
|
|||||||
Reference in new issue
Block a user