chore: remove dead code flagged by knip
Local Build and Deploy / deploy (push) Successful in 1m7s

Remove 19 unused source files (no importers anywhere in src/):
- src/actions/admin-permissions.ts, admin-radio.ts, admin-user-edit.ts,
  admin-users.ts (functionality lives in @/actions/users and
  @/actions/permissions)
- src/components/admin/confirm-action.tsx, page-header.tsx
- src/components/motion-elements.tsx
- src/lib/format-date.ts
- src/lib/catalog-categories/* (incl. re-export barrel)
- src/lib/foundation/{index,database,middleware,validation}.ts (errors/action
  kept, still imported directly)
- src/lib/services/imager/{avatar-renderer,memory-cache}.ts
- src/lib/services/nitro-assets.ts

Update admin-operations-contract test to drop the two removed action-file
gates (their permission coverage already exists in users.ts/permissions.ts).

Add knip.json for repeatable dead-code audits.

Verified: tsc --noEmit clean, next build succeeds, full test suite green
(301/301).
This commit is contained in:
openhands committed 2026-07-18 19:08:17 +02:00
1 parent 60eb45be73
commit 3c9c1311ec
23 files changed
+586 -2158

No files matched your search

-156
View File
@@ -1,156 +0,0 @@
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { env } from "@/env";
import { PrismaClient } from "@/generated/prisma/client";
import { logger } from "@/lib/logger";
import { DatabaseError } from "./errors";
import { getRequestId } from "./request-context";
const globalForDb = globalThis as unknown as { _db?: DbService };
interface HealthStatus {
ok: boolean;
latencyMs: number;
poolSize: number;
activeQueries: number;
error?: string;
}
export class DbService {
private readonly client: PrismaClient;
private lastHealthCheck = 0;
private healthCache: HealthStatus | null = null;
private readonly healthTtlMs = 10_000;
constructor() {
const url = new URL(env.DATABASE_URL);
const adapter = new PrismaMariaDb({
host: url.hostname,
port: Number(url.port) || 3306,
user: decodeURIComponent(url.username),
password: decodeURIComponent(url.password),
database: url.pathname.replace(/^\//, ""),
connectionLimit: env.DATABASE_POOL_SIZE,
connectTimeout: env.DATABASE_CONNECT_TIMEOUT_MS,
acquireTimeout: env.DATABASE_CONNECT_TIMEOUT_MS,
idleTimeout: env.DATABASE_IDLE_TIMEOUT_MS,
});
this.client = new PrismaClient({
adapter,
log:
env.NODE_ENV === "development"
? [
{ emit: "event", level: "query" },
{ emit: "event", level: "error" },
]
: [{ emit: "event", level: "error" }],
});
if (env.NODE_ENV === "development") {
this.client.$on("query" as never, (e: unknown) => {
const ev = e as { query: string; duration: number };
logger.debug("DB query", {
query: ev.query.slice(0, 200),
durationMs: ev.duration,
requestId: getRequestId(),
});
});
}
this.client.$on("error" as never, (e: unknown) => {
const ev = e as { message: string };
logger.error("DB error", {
message: ev.message,
requestId: getRequestId(),
});
});
}
get prisma(): PrismaClient {
return this.client;
}
async health(): Promise<HealthStatus> {
const now = Date.now();
if (this.healthCache && now - this.lastHealthCheck < this.healthTtlMs) {
return this.healthCache;
}
const start = performance.now();
try {
await this.client.$queryRaw`SELECT 1`;
const latencyMs = Math.round(performance.now() - start);
this.healthCache = {
ok: true,
latencyMs,
poolSize: env.DATABASE_POOL_SIZE,
activeQueries: 0,
};
this.lastHealthCheck = now;
return this.healthCache;
} catch (cause) {
const latencyMs = Math.round(performance.now() - start);
const message =
cause instanceof Error ? cause.message : "Unknown database error";
this.healthCache = {
ok: false,
latencyMs,
poolSize: env.DATABASE_POOL_SIZE,
activeQueries: 0,
error: message,
};
this.lastHealthCheck = now;
return this.healthCache;
}
}
async execute<T>(fn: (client: PrismaClient) => Promise<T>): Promise<T> {
try {
return await fn(this.client);
} catch (cause) {
throw new DatabaseError("Query failed", cause);
}
}
async transaction<T>(
fn: (
tx: Omit<
PrismaClient,
"$connect" | "$disconnect" | "$on" | "$use" | "$extends"
>,
) => Promise<T>,
): Promise<T> {
try {
return await this.client.$transaction(fn);
} catch (cause) {
throw new DatabaseError("Transaction failed", cause);
}
}
async rawQuery<T>(
strings: TemplateStringsArray,
...values: unknown[]
): Promise<T> {
try {
return await this.client.$queryRaw<T>(strings, ...values);
} catch (cause) {
throw new DatabaseError("Raw query failed", cause);
}
}
/**
* Execute a raw SQL string with parameterized ? placeholders.
* Named "Unsafe" because the caller is responsible for using ? placeholders
* and never interpolating user input directly into the query string.
*/
async executeRawUnsafe(query: string, ...values: unknown[]): Promise<number> {
try {
return await this.client.$executeRawUnsafe(query, ...values);
} catch (cause) {
throw new DatabaseError("Execute raw failed", cause);
}
}
}
export const db = globalForDb._db ?? new DbService();
if (env.NODE_ENV !== "production") globalForDb._db = db;
-79
View File
@@ -1,79 +0,0 @@
export {
actionError,
actionOk,
adminAction,
authAction,
handleActionError,
} from "./action";
export { DbService, db } from "./database";
export {
ConflictError,
DatabaseError,
DomainError,
ForbiddenError,
NotFoundError,
RateLimitError,
UnauthorizedError,
ValidationError,
} from "./errors";
export {
addSecurityHeaders,
chain,
protectAdminRoutes,
withRequestContext,
} from "./middleware";
export {
createStore,
elapsed,
getClientIp,
getRequestId,
getRequestStore,
runWithStore,
setContextUserId,
} from "./request-context";
export {
canonicalize,
canonicalizeFormData,
canonicalizeFormValue,
extractClientIpAsync,
redirectSafe,
safeRedirect,
sanitizeFilename,
setCsrfCookie,
validateCsrfToken,
} from "./security";
export type {
ActionContext,
ActionFailure,
ActionResult,
ActionSuccess,
AdminActionContext,
AppSession,
IpAddress,
PaginatedQuery,
PaginatedResult,
PermissionSet,
RankId,
RequestContext,
RequestId,
SessionUser,
UserId,
} from "./types";
export {
bigIntString,
boolString,
buildSearchQuery,
email,
hexColor,
idParam,
look,
nonNegativeInt,
pagination,
password,
positiveInt,
slug,
url,
username,
} from "./validation";
-102
View File
@@ -1,102 +0,0 @@
import { type NextRequest, NextResponse } from "next/server";
import { logger } from "@/lib/logger";
import { createStore, getRequestId, runWithStore } from "./request-context";
import { extractClientIpAsync, safeRedirect } from "./security";
type MiddlewareHandler = (req: NextRequest) => Promise<NextResponse | null>;
export function chain(...handlers: MiddlewareHandler[]): MiddlewareHandler {
return async (req: NextRequest) => {
for (const handler of handlers) {
const result = await handler(req);
if (result) return result;
}
return NextResponse.next();
};
}
export function withRequestContext(
handler: MiddlewareHandler,
): MiddlewareHandler {
return async (req: NextRequest) => {
const ip = await extractClientIpAsync();
const store = createStore(ip);
return runWithStore(store, async () => {
const start = Date.now();
try {
const result = await handler(req);
const duration = Date.now() - start;
logger.info("Request completed", {
method: req.method,
path: req.nextUrl.pathname,
status: result?.status ?? 200,
durationMs: duration,
requestId: getRequestId(),
});
return result;
} catch (error) {
const duration = Date.now() - start;
logger.error("Request failed", {
method: req.method,
path: req.nextUrl.pathname,
durationMs: duration,
requestId: getRequestId(),
error: error instanceof Error ? error.message : String(error),
});
const safeUrl = new URL(
safeRedirect(req.nextUrl.pathname, "/"),
req.url,
);
return NextResponse.redirect(safeUrl);
}
});
};
}
export function protectAdminRoutes(req: NextRequest): NextResponse | null {
const { pathname } = req.nextUrl;
if (!pathname.startsWith("/admin")) return null;
const authToken =
req.cookies.get("next-auth.session-token")?.value ??
req.cookies.get("__Secure-next-auth.session-token")?.value;
if (!authToken) {
const loginUrl = new URL("/login", req.url);
loginUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(loginUrl);
}
return null;
}
export function addSecurityHeaders(req: NextRequest): NextResponse | null {
if (req.method === "OPTIONS") return null;
const response = NextResponse.next();
const csp = [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https: http:",
"font-src 'self' https:",
"connect-src 'self' https: wss:",
"frame-src 'self'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
].join("; ");
response.headers.set("Content-Security-Policy", csp);
response.headers.set("X-Content-Type-Options", "nosniff");
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-XSS-Protection", "0");
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
response.headers.set(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=()",
);
return response;
}
-99
View File
@@ -1,99 +0,0 @@
import { z } from "zod";
const USERNAME_RE = /^[a-zA-Z0-9\-_.]+$/;
export const username = z
.string()
.trim()
.min(1, "Username is required")
.max(32, "Username must be at most 32 characters")
.regex(
USERNAME_RE,
"Username may only contain letters, numbers, hyphens, underscores, and dots",
)
.transform((v) => v.normalize("NFC"));
export const password = z
.string()
.min(8, "Password must be at least 8 characters")
.max(128, "Password must be at most 128 characters");
export const email = z
.string()
.trim()
.email("Invalid email address")
.max(255, "Email must be at most 255 characters")
.transform((v) => v.normalize("NFC").toLowerCase());
const HEX_COLOR_RE = /^#[0-9a-f]{6}$/i;
const SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
export const hexColor = z
.string()
.length(7, "Must be exactly 7 characters (e.g. #ff0000)")
.regex(HEX_COLOR_RE, "Must be a valid hex color (e.g. #ff0000)");
export const slug = z
.string()
.trim()
.min(1, "Slug is required")
.max(64, "Slug must be at most 64 characters")
.regex(
SLUG_RE,
"Slug must be lowercase alphanumeric with hyphens only between characters",
)
.refine(
(v) => !v.startsWith("-") && !v.endsWith("-"),
"Slug must not start or end with a hyphen",
)
.transform((v) => v.normalize("NFC"));
export const url = z
.string()
.url("Invalid URL")
.max(2048, "URL must be at most 2048 characters");
export const look = z
.string()
.max(512, "Look string must be at most 512 characters")
.regex(/^[a-zA-Z0-9.\-_\s]+$/, "Invalid look format")
.optional();
export const positiveInt = z
.number()
.int("Must be a whole number")
.positive("Must be positive");
export const nonNegativeInt = z
.number()
.int("Must be a whole number")
.nonnegative("Must not be negative");
export const bigIntString = z
.string()
.regex(/^\d+$/, "Must be a numeric string")
.transform(BigInt);
export const idParam = z
.string()
.regex(/^\d+$/, "ID must be numeric")
.transform(Number);
export const pagination = z.object({
page: z.coerce.number().int().positive().default(1),
perPage: z.coerce.number().int().min(1).max(250).default(50),
sort: z.string().optional(),
order: z.enum(["asc", "desc"]).optional(),
search: z.string().max(256).optional(),
});
export const boolString = z
.string()
.transform((v) => v === "true" || v === "1")
.or(z.boolean());
export function buildSearchQuery(fields: string[], search: string | undefined) {
if (!search?.trim()) return undefined;
const sanitized = search.normalize("NFC").trim().slice(0, 256);
return fields.map((f) => ({ [f]: { contains: sanitized } }));
}