chore(lint): remove dead eslint-disable directives
CI / check (push) Successful in 30s
CI / tests-imaging-browsers (push) Successful in 1m31s
CI / tests-unit (push) Successful in 1m45s
CI / tests-integration (push) Successful in 1m49s
CI / tests-quality (push) Successful in 47s
CI / tests-ui (push) Successful in 4m24s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m39s

The project lints with Biome only, so none of these 78 directives across 57
files ever suppressed anything. Directives that carried the rationale for a
flagged dynamic index access keep that rationale as a plain comment.
This commit is contained in:
openhands committed 2026-10-18 14:50:51 +02:00
1 parent 07c5904879
commit 2249303dbd
57 files changed
+29 -78

No files matched your search

-1
View File
@@ -60,7 +60,6 @@ export async function uploadBadge(formData: FormData): Promise<void> {
try { try {
const buffer = Buffer.from(await file.arrayBuffer()); const buffer = Buffer.from(await file.arrayBuffer());
const gif = await toBadgeGif(buffer); const gif = await toBadgeGif(buffer);
// eslint-disable-next-line security/detect-non-literal-fs-filename
await writeFile(target, gif); await writeFile(target, gif);
} catch { } catch {
back("error", "Could not process or write the badge file"); back("error", "Could not process or write the badge file");
+1 -1
View File
@@ -32,7 +32,7 @@ async function upsertSetting(key: string, value: string): Promise<void> {
.values({ .values({
key, key,
value, value,
// eslint-disable-next-line security/detect-object-injection -- key is one of 3 known const values // key is one of 3 known const values
comment: COMMENTS[key] ?? null, comment: COMMENTS[key] ?? null,
}) })
.onDuplicateKeyUpdate({ set: { value } }); .onDuplicateKeyUpdate({ set: { value } });
+2 -2
View File
@@ -71,10 +71,10 @@ export async function savePoints(formData: FormData): Promise<void> {
POINTS_KEYS.map((key) => POINTS_KEYS.map((key) =>
db db
.insert(WebsiteSetting) .insert(WebsiteSetting)
// eslint-disable-next-line security/detect-object-injection -- key from POINTS_KEYS const // key from POINTS_KEYS const
.values({ key, value: values[key], comment: "Radio points" }) .values({ key, value: values[key], comment: "Radio points" })
.onDuplicateKeyUpdate({ .onDuplicateKeyUpdate({
// eslint-disable-next-line security/detect-object-injection -- key from POINTS_KEYS const // key from POINTS_KEYS const
set: { value: values[key] }, set: { value: values[key] },
}), }),
), ),
+1 -1
View File
@@ -112,7 +112,7 @@ export async function saveTheme(formData: FormData): Promise<void> {
export async function applyPreset(formData: FormData): Promise<void> { export async function applyPreset(formData: FormData): Promise<void> {
const staff = await requirePermission(PERMS.SETTINGS_EDIT); const staff = await requirePermission(PERMS.SETTINGS_EDIT);
const name = String(formData.get("preset") ?? "").normalize("NFC"); const name = String(formData.get("preset") ?? "").normalize("NFC");
// eslint-disable-next-line security/detect-object-injection -- guarded by null check below // guarded by null check below
const preset = PRESETS[name]; const preset = PRESETS[name];
if (!preset) redirect("/admin/theme"); if (!preset) redirect("/admin/theme");
-4
View File
@@ -31,9 +31,7 @@ export async function saveFavicon(
} }
const buffer = upload.bytes; const buffer = upload.bytes;
// eslint-disable-next-line security/detect-non-literal-fs-filename
await mkdir(baseDir, { recursive: true }); await mkdir(baseDir, { recursive: true });
// eslint-disable-next-line security/detect-non-literal-fs-filename
await writeFile(filePath, buffer); await writeFile(filePath, buffer);
const url = `/api/media/favicon/${filename}`; const url = `/api/media/favicon/${filename}`;
@@ -46,7 +44,6 @@ export async function saveFavicon(
const oldPath = path.resolve(baseDir, oldName); const oldPath = path.resolve(baseDir, oldName);
if (oldPath.startsWith(baseDir + path.sep)) { if (oldPath.startsWith(baseDir + path.sep)) {
try { try {
// eslint-disable-next-line security/detect-non-literal-fs-filename
await unlink(oldPath); await unlink(oldPath);
} catch { } catch {
/* ignore if file doesn't exist */ /* ignore if file doesn't exist */
@@ -85,7 +82,6 @@ export async function deleteFavicon(): Promise<{
const oldPath = path.resolve(baseDir, oldName); const oldPath = path.resolve(baseDir, oldName);
if (oldPath.startsWith(baseDir + path.sep)) { if (oldPath.startsWith(baseDir + path.sep)) {
try { try {
// eslint-disable-next-line security/detect-non-literal-fs-filename
await unlink(oldPath); await unlink(oldPath);
} catch { } catch {
/* ignore */ /* ignore */
-2
View File
@@ -29,9 +29,7 @@ export async function saveLogo(
} }
const buffer = upload.bytes; const buffer = upload.bytes;
// eslint-disable-next-line security/detect-non-literal-fs-filename
await mkdir(baseDir, { recursive: true }); await mkdir(baseDir, { recursive: true });
// eslint-disable-next-line security/detect-non-literal-fs-filename
await writeFile(filePath, buffer); await writeFile(filePath, buffer);
const url = `/api/media/logo/${filename}`; const url = `/api/media/logo/${filename}`;
+1 -3
View File
@@ -278,14 +278,12 @@ export async function uploadBackground(formData: FormData): Promise<void> {
let uploaded = false; let uploaded = false;
try { try {
// eslint-disable-next-line security/detect-non-literal-fs-filename
await mkdir(MEDIA_ROOT, { recursive: true }); await mkdir(MEDIA_ROOT, { recursive: true });
const ext = file.name.split(".").pop() ?? "png"; const ext = file.name.split(".").pop() ?? "png";
const name = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`; const name = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`;
const bytes = await file.arrayBuffer(); const bytes = await file.arrayBuffer();
const filePath = resolveMediaPath(name); const filePath = resolveMediaPath(name);
if (filePath.startsWith(MEDIA_ROOT + path.sep)) { if (filePath.startsWith(MEDIA_ROOT + path.sep)) {
// eslint-disable-next-line security/detect-non-literal-fs-filename
await writeFile(filePath, Buffer.from(bytes)); await writeFile(filePath, Buffer.from(bytes));
await writeSetting("background_image", `/api/media/${name}`); await writeSetting("background_image", `/api/media/${name}`);
await writeSetting( await writeSetting(
@@ -319,7 +317,7 @@ export async function applyPresetManager(formData: FormData): Promise<void> {
const name = String(formData.get("preset") ?? "") const name = String(formData.get("preset") ?? "")
.normalize("NFC") .normalize("NFC")
.trim(); .trim();
// eslint-disable-next-line security/detect-object-injection -- guarded by null check below // guarded by null check below
const preset = PRESETS[name]; const preset = PRESETS[name];
if (!preset) redirect(MANAGER_PATH); if (!preset) redirect(MANAGER_PATH);
-1
View File
@@ -142,7 +142,6 @@ export default async function ApplyStaffPage({
<article key={String(position.id)} className="content-card"> <article key={String(position.id)} className="content-card">
<div className="content-card-head"> <div className="content-card-head">
{team?.badge ? ( {team?.badge ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img <img
src={`${BADGE_IMG_BASE}=${encodeURIComponent(team.badge)}`} src={`${BADGE_IMG_BASE}=${encodeURIComponent(team.badge)}`}
alt={rankName} alt={rankName}
-1
View File
@@ -120,7 +120,6 @@ export default async function ApplyTeamPage({
<article key={String(team.id)} className="content-card"> <article key={String(team.id)} className="content-card">
<div className="content-card-head"> <div className="content-card-head">
{team.badge ? ( {team.badge ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img <img
src={`${BADGE_IMG_BASE}=${encodeURIComponent(team.badge)}`} src={`${BADGE_IMG_BASE}=${encodeURIComponent(team.badge)}`}
alt={team.rankName} alt={team.rankName}
-1
View File
@@ -48,7 +48,6 @@ export default async function BadgesPage() {
alignItems: "flex-start", alignItems: "flex-start",
}} }}
> >
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={`${BADGE_IMG_BASE}=${encodeURIComponent(badge.badgeKey)}`} src={`${BADGE_IMG_BASE}=${encodeURIComponent(badge.badgeKey)}`}
alt={badge.badgeName} alt={badge.badgeName}
-1
View File
@@ -190,7 +190,6 @@ export default async function DrawBadgePage({
{b.badgeUrl ? ( {b.badgeUrl ? (
// Badge images are arbitrary external URLs stored by the // Badge images are arbitrary external URLs stored by the
// draw-badge tool, so a plain <img> is correct here. // draw-badge tool, so a plain <img> is correct here.
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={b.badgeUrl} src={b.badgeUrl}
alt="" alt=""
-1
View File
@@ -149,7 +149,6 @@ export default async function GuildPage({
style={{ display: "flex", gap: "1.25rem", alignItems: "flex-start" }} style={{ display: "flex", gap: "1.25rem", alignItems: "flex-start" }}
> >
{guild.badge ? ( {guild.badge ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${guild.badge}.gif`} src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${guild.badge}.gif`}
alt={`${guild.name} badge`} alt={`${guild.name} badge`}
-1
View File
@@ -74,7 +74,6 @@ export default async function GuildsPage() {
}} }}
> >
{g.badge ? ( {g.badge ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${g.badge}.gif`} src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${g.badge}.gif`}
alt={`${g.name} badge`} alt={`${g.name} badge`}
-1
View File
@@ -110,7 +110,6 @@ export default async function HelpCategoryPage({
<ContentCard icon="❓" title={title} subtitle={t("topicSubtitle")}> <ContentCard icon="❓" title={title} subtitle={t("topicSubtitle")}>
{imageSrc ? ( {imageSrc ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={imageSrc} src={imageSrc}
alt="" alt=""
-1
View File
@@ -159,7 +159,6 @@ export default async function HelpCenterPage() {
}} }}
> >
{c.imageUrl ? ( {c.imageUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={c.imageUrl} src={c.imageUrl}
alt="" alt=""
+1 -1
View File
@@ -171,7 +171,7 @@ export default async function LeaderboardPage({
const rows = await (active === "credits" const rows = await (active === "credits"
? loadCreditsRows() ? loadCreditsRows()
: active === "diamonds" || active === "duckets" : active === "diamonds" || active === "duckets"
? // eslint-disable-next-line security/detect-object-injection -- active validated as "diamonds"|"duckets" in this branch ? // active validated as "diamonds"|"duckets" in this branch
loadCurrencyRows(CURRENCY_TYPE[active]) loadCurrencyRows(CURRENCY_TYPE[active])
: loadSettingsRows( : loadSettingsRows(
SETTINGS_FIELD[active as keyof typeof SETTINGS_FIELD], SETTINGS_FIELD[active as keyof typeof SETTINGS_FIELD],
-1
View File
@@ -191,7 +191,6 @@ async function ArticlePage({
} }
> >
{article.image ? ( {article.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={article.image} src={article.image}
alt="" alt=""
-2
View File
@@ -108,7 +108,6 @@ export default async function RareCategoryPage({
}} }}
> >
{badgeSrc ? ( {badgeSrc ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={badgeSrc} src={badgeSrc}
alt={`${cat.name} badge`} alt={`${cat.name} badge`}
@@ -150,7 +149,6 @@ export default async function RareCategoryPage({
<tr key={String(r.id)}> <tr key={String(r.id)}>
<td> <td>
{iconSrc ? ( {iconSrc ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={iconSrc} src={iconSrc}
alt="" alt=""
-1
View File
@@ -88,7 +88,6 @@ export default async function RareValuesPage() {
> >
<div className="content-card-head"> <div className="content-card-head">
{badgeSrc ? ( {badgeSrc ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={badgeSrc} src={badgeSrc}
alt={`${c.name} badge`} alt={`${c.name} badge`}
+1 -1
View File
@@ -16,7 +16,7 @@ const STATE_LABELS: Record<string, { label: string; icon: string }> = {
}; };
function describeState(state: string): { label: string; icon: string } { function describeState(state: string): { label: string; icon: string } {
// eslint-disable-next-line security/detect-object-injection -- STATE_LABELS has known keys, fallback provided // STATE_LABELS has known keys, fallback provided
return STATE_LABELS[state] ?? { label: state || "Unknown", icon: "🚪" }; return STATE_LABELS[state] ?? { label: state || "Unknown", icon: "🚪" };
} }
-1
View File
@@ -249,7 +249,6 @@ export default async function ShopPage({
}} }}
> >
{a.iconUrl ? ( {a.iconUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={a.iconUrl} src={a.iconUrl}
alt="" alt=""
-1
View File
@@ -366,7 +366,6 @@ async function ProfilePage({
flexWrap: "wrap", flexWrap: "wrap",
}} }}
> >
{/* eslint-disable-next-line @next/next/no-img-element */}
<ProfileImage <ProfileImage
src={avatar} src={avatar}
alt={user.username} alt={user.username}
-1
View File
@@ -49,7 +49,6 @@ export function AdsTable({ ads }: { ads: AdRow[] }) {
{ads.map((ad) => ( {ads.map((ad) => (
<tr key={ad.id}> <tr key={ad.id}>
<td> <td>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={ad.image} src={ad.image}
alt="" alt=""
-1
View File
@@ -103,7 +103,6 @@ export default async function AdminCalendarCampaign({
<div className="admin-card flex items-center gap-5 mb-6"> <div className="admin-card flex items-center gap-5 mb-6">
{campaign.image ? ( {campaign.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
className="w-24 h-24 object-cover" className="w-24 h-24 object-cover"
src={campaign.image} src={campaign.image}
-1
View File
@@ -98,7 +98,6 @@ export default async function AdminCalendar() {
className="admin-card hover article" className="admin-card hover article"
> >
{c.image ? ( {c.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img className="article-img" src={c.image} alt={c.name} /> <img className="article-img" src={c.image} alt={c.name} />
) : ( ) : (
<div className="article-img" /> <div className="article-img" />
-1
View File
@@ -96,7 +96,6 @@ export default async function AdminGuildDetailPage({
<div className="flex flex-wrap items-start justify-between gap-4"> <div className="flex flex-wrap items-start justify-between gap-4">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{guild.badge ? ( {guild.badge ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${guild.badge}.gif`} src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${guild.badge}.gif`}
alt="" alt=""
-1
View File
@@ -155,7 +155,6 @@ export default async function AdminGuildsPage({
<td className="muted">{g.id}</td> <td className="muted">{g.id}</td>
<td> <td>
{g.badge ? ( {g.badge ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${g.badge}.gif`} src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${g.badge}.gif`}
alt="" alt=""
-1
View File
@@ -94,7 +94,6 @@ export default async function AdminPhotos({
className="admin-card" className="admin-card"
style={{ padding: 0, overflow: "hidden" }} style={{ padding: 0, overflow: "hidden" }}
> >
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={p.url} src={p.url}
alt={t("photoAlt", { id: String(p.id) })} alt={t("photoAlt", { id: String(p.id) })}
+2 -2
View File
@@ -62,7 +62,7 @@ function isRecord(v: unknown): v is Record<string, unknown> {
} }
function pickString(obj: Record<string, unknown>, key: string): string | null { function pickString(obj: Record<string, unknown>, key: string): string | null {
// eslint-disable-next-line security/detect-object-injection -- only called with hardcoded keys // only called with hardcoded keys
const v = obj[key]; const v = obj[key];
return typeof v === "string" && v.trim() !== "" ? v.trim() : null; return typeof v === "string" && v.trim() !== "" ? v.trim() : null;
} }
@@ -112,7 +112,7 @@ function findNumberDeep(
if (typeof value === "number" && Number.isFinite(value)) return value; if (typeof value === "number" && Number.isFinite(value)) return value;
if (!isRecord(value)) return null; if (!isRecord(value)) return null;
for (const key of keys) { for (const key of keys) {
// eslint-disable-next-line security/detect-object-injection -- keys from hardcoded array // keys from hardcoded array
const v = value[key]; const v = value[key];
if (typeof v === "number" && Number.isFinite(v)) return v; if (typeof v === "number" && Number.isFinite(v)) return v;
if ( if (
@@ -577,7 +577,6 @@ export function FurniClient({ room, items = [], canEdit }: Props) {
)} )}
<TableCell> <TableCell>
{icon ? ( {icon ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={icon} src={icon}
alt={item.name} alt={item.name}
@@ -733,7 +732,6 @@ export function FurniClient({ room, items = [], canEdit }: Props) {
> >
<TableCell> <TableCell>
{icon ? ( {icon ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={icon} src={icon}
alt={g.base.name} alt={g.base.name}
+1 -1
View File
@@ -493,7 +493,7 @@ export default async function AdminTheme({
width: 20, width: 20,
height: 20, height: 20,
borderRadius: 4, borderRadius: 4,
// eslint-disable-next-line security/detect-object-injection -- k from fixed theme color key list // k from fixed theme color key list
background: theme.settings[k] ?? "#64748b", background: theme.settings[k] ?? "#64748b",
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.2)", boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.2)",
}} }}
+3 -3
View File
@@ -356,7 +356,7 @@ export async function GET(req: Request) {
]); ]);
const rarity = Object.fromEntries( const rarity = Object.fromEntries(
// eslint-disable-next-line security/detect-object-injection -- rk from rarityKeys const, i is array index // rk from rarityKeys const, i is array index
rarityKeys.map((rk, i) => [rk, rarityBoards[i]]), rarityKeys.map((rk, i) => [rk, rarityBoards[i]]),
) as Record<BadgeRarityKey, BoardCore>; ) as Record<BadgeRarityKey, BoardCore>;
@@ -398,7 +398,7 @@ export async function GET(req: Request) {
totalBadgesViewer = tb; totalBadgesViewer = tb;
achievementViewer = ach; achievementViewer = ach;
rarityViewer = Object.fromEntries( rarityViewer = Object.fromEntries(
// eslint-disable-next-line security/detect-object-injection -- rk from rarityKeys const, i is array index // rk from rarityKeys const, i is array index
rarityKeys.map((rk, i) => [rk, rvs[i]]), rarityKeys.map((rk, i) => [rk, rvs[i]]),
) as Record<BadgeRarityKey, Partial<BadgeLeaderboardEntry> | undefined>; ) as Record<BadgeRarityKey, Partial<BadgeLeaderboardEntry> | undefined>;
} }
@@ -414,7 +414,7 @@ export async function GET(req: Request) {
viewerEntry: achievementViewer, viewerEntry: achievementViewer,
}, },
rarity: Object.fromEntries( rarity: Object.fromEntries(
// eslint-disable-next-line security/detect-object-injection -- rk from rarityKeys const // rk from rarityKeys const
rarityKeys.map((rk) => [ rarityKeys.map((rk) => [
rk, rk,
{ ...global.rarity[rk], viewerEntry: rarityViewer[rk] }, { ...global.rarity[rk], viewerEntry: rarityViewer[rk] },
+1 -1
View File
@@ -110,7 +110,7 @@ export async function GET(req: Request) {
async () => async () =>
type === "credits" type === "credits"
? await loadCreditsRows() ? await loadCreditsRows()
: // eslint-disable-next-line security/detect-object-injection -- type validated to "diamonds"|"duckets" : // type validated to "diamonds"|"duckets"
await loadCurrencyRows(CURRENCY_TYPE[type]), await loadCurrencyRows(CURRENCY_TYPE[type]),
// Rankings are re-sorted on every read; serving the previous order for a // Rankings are re-sorted on every read; serving the previous order for a
// minute keeps a busy leaderboard off the database. // minute keeps a busy leaderboard off the database.
+1 -3
View File
@@ -51,7 +51,6 @@ export async function GET(
headers: SECURITY_HEADERS, headers: SECURITY_HEADERS,
}); });
} }
// eslint-disable-next-line security/detect-non-literal-fs-filename
if (!existsSync(filePath)) { if (!existsSync(filePath)) {
return new NextResponse("Not found", { return new NextResponse("Not found", {
status: 404, status: 404,
@@ -59,7 +58,6 @@ export async function GET(
}); });
} }
// eslint-disable-next-line security/detect-non-literal-fs-filename
const bytes = await readFile(filePath); const bytes = await readFile(filePath);
const mime: Record<string, string> = { const mime: Record<string, string> = {
".png": "image/png", ".png": "image/png",
@@ -76,7 +74,7 @@ export async function GET(
headers: { headers: {
...SECURITY_HEADERS, ...SECURITY_HEADERS,
...(ext === ".svg" ? { "Content-Disposition": "attachment" } : {}), ...(ext === ".svg" ? { "Content-Disposition": "attachment" } : {}),
// eslint-disable-next-line security/detect-object-injection -- ext validated against ALLOWED_EXT // ext validated against ALLOWED_EXT
"Content-Type": mime[ext] ?? "application/octet-stream", "Content-Type": mime[ext] ?? "application/octet-stream",
"Cache-Control": "public, max-age=3600, must-revalidate", "Cache-Control": "public, max-age=3600, must-revalidate",
}, },
-2
View File
@@ -12,11 +12,9 @@ import { PERMS } from "@/lib/permissions";
*/ */
export const GET = withAdmin({ permission: PERMS.PAGES_EDIT }, async () => { export const GET = withAdmin({ permission: PERMS.PAGES_EDIT }, async () => {
const dir = MEDIA_ROOT; const dir = MEDIA_ROOT;
// eslint-disable-next-line security/detect-non-literal-fs-filename
if (!existsSync(dir)) { if (!existsSync(dir)) {
return apiOk({ files: [] }); return apiOk({ files: [] });
} }
// eslint-disable-next-line security/detect-non-literal-fs-filename
const files = readdirSync(dir) const files = readdirSync(dir)
.filter((f) => /\.(png|jpg|jpeg|gif|webp|svg|bmp)$/i.test(f)) .filter((f) => /\.(png|jpg|jpeg|gif|webp|svg|bmp)$/i.test(f))
.map((f) => { .map((f) => {
+1 -1
View File
@@ -35,7 +35,7 @@ function findCount(value: unknown, depth = 0): number | null {
"count", "count",
]; ];
for (const key of keys) { for (const key of keys) {
// eslint-disable-next-line security/detect-object-injection -- keys from hardcoded array // keys from hardcoded array
const v = value[key]; const v = value[key];
if (typeof v === "number" && Number.isFinite(v)) return v; if (typeof v === "number" && Number.isFinite(v)) return v;
if ( if (
-1
View File
@@ -45,7 +45,6 @@ export function ArticleCard({
<div className="admin-card overflow-hidden flex flex-col"> <div className="admin-card overflow-hidden flex flex-col">
{confirmDialog} {confirmDialog}
<div className="relative w-full aspect-[16/9] bg-[var(--admin-canvas)]"> <div className="relative w-full aspect-[16/9] bg-[var(--admin-canvas)]">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={image || ""} src={image || ""}
alt={title} alt={title}
-1
View File
@@ -296,7 +296,6 @@ export function ArticleForm({
/> />
</div> </div>
{image && !imageError ? ( {image && !imageError ? (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={image} src={image}
alt={t("imagePreview")} alt={t("imagePreview")}
@@ -715,7 +715,6 @@ function FurniThumb({
className="flex items-center justify-center rounded bg-muted/50" className="flex items-center justify-center rounded bg-muted/50"
style={{ width: size, height: size }} style={{ width: size, height: size }}
> >
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={primary} src={primary}
alt={item.itemName} alt={item.itemName}
@@ -893,7 +893,7 @@ export function SortableTree({
handleToggleEnabled, handleToggleEnabled,
handleAddSubpage, handleAddSubpage,
], ],
); // eslint-disable-line react-hooks/exhaustive-deps );
return ( return (
<div <div
@@ -34,7 +34,6 @@ export function FurniIcon({
return ( return (
<div className="w-10 h-10 rounded bg-muted/50 flex items-center justify-center overflow-hidden"> <div className="w-10 h-10 rounded bg-muted/50 flex items-center justify-center overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={localUrl} src={localUrl}
alt={classname} alt={classname}
@@ -231,7 +231,6 @@ function FurniThumb({
return ( return (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded bg-muted/50"> <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded bg-muted/50">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={primary} src={primary}
alt={classname} alt={classname}
@@ -192,7 +192,6 @@ export function ShopFurniIcon({
} }
return ( return (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={primary} src={primary}
alt={classname} alt={classname}
-1
View File
@@ -226,7 +226,6 @@ export function AdminMediaGrid() {
}} }}
> >
<div className="relative"> <div className="relative">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={validImageUrl(f.url)} src={validImageUrl(f.url)}
alt={f.name} alt={f.name}
-1
View File
@@ -156,7 +156,6 @@ export function MediaPicker({
e.currentTarget.style.borderColor = "transparent"; e.currentTarget.style.borderColor = "transparent";
}} }}
> >
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={validImageUrl(f.url)} src={validImageUrl(f.url)}
alt={f.name} alt={f.name}
@@ -51,7 +51,6 @@ export function FurniThumb({
className="flex items-center justify-center rounded bg-muted/50" className="flex items-center justify-center rounded bg-muted/50"
style={{ width: size, height: size }} style={{ width: size, height: size }}
> >
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={primary} src={primary}
alt={item.itemName} alt={item.itemName}
@@ -405,7 +405,7 @@ export function ThemeManagerPanel({
width: 14, width: 14,
height: 14, height: 14,
borderRadius: 3, borderRadius: 3,
// eslint-disable-next-line security/detect-object-injection -- k from fixed key list // k from fixed key list
background: palette.light[k], background: palette.light[k],
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.15)", boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.15)",
marginLeft: -3, marginLeft: -3,
+1 -1
View File
@@ -44,7 +44,7 @@ export function NavbarColorPicker() {
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
function apply(index: number) { function apply(index: number) {
// eslint-disable-next-line security/detect-object-injection -- index validated by findIndex + Math.max, guard below // index validated by findIndex + Math.max, guard below
const c = COLORS[index]; const c = COLORS[index];
if (!c) return; if (!c) return;
document.documentElement.style.setProperty("--color-navbar", c.value); document.documentElement.style.setProperty("--color-navbar", c.value);
+1 -3
View File
@@ -29,7 +29,7 @@ export function PhotoLightbox({ photos }: { photos: LightboxPhoto[] }) {
// has to hold the overlay itself. // has to hold the overlay itself.
const [closing, setClosing] = useState(false); const [closing, setClosing] = useState(false);
const exitTimer = useRef<ReturnType<typeof setTimeout> | null>(null); const exitTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
// eslint-disable-next-line security/detect-object-injection -- openIndex is numeric array index, guarded by null check // openIndex is numeric array index, guarded by null check
const active = openIndex !== null ? photos[openIndex] : null; const active = openIndex !== null ? photos[openIndex] : null;
const close = useCallback(() => { const close = useCallback(() => {
@@ -97,7 +97,6 @@ export function PhotoLightbox({ photos }: { photos: LightboxPhoto[] }) {
}} }}
aria-label={p.alt} aria-label={p.alt}
> >
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={p.url} src={p.url}
alt={p.alt} alt={p.alt}
@@ -180,7 +179,6 @@ export function PhotoLightbox({ photos }: { photos: LightboxPhoto[] }) {
cursor: "default", cursor: "default",
}} }}
> >
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={active.url} src={active.url}
alt={active.alt} alt={active.alt}
+1 -1
View File
@@ -59,7 +59,7 @@ function scanCharWidths(
const w = end - start; const w = end - start;
if (w > 0) { if (w > 0) {
// eslint-disable-next-line security/detect-object-injection -- ci increments within CHAR_ORDER bounds // ci increments within CHAR_ORDER bounds
chars.push({ char: CHAR_ORDER[ci], x: start, w }); chars.push({ char: CHAR_ORDER[ci], x: start, w });
ci++; ci++;
} }
-1
View File
@@ -31,7 +31,6 @@ export function AvatarImage({
(options?.headOnly ? "l" : max <= 40 ? "s" : max <= 80 ? "m" : "l"); (options?.headOnly ? "l" : max <= 40 ? "s" : max <= 80 ? "m" : "l");
return ( return (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={getAvatarUrl(figure, { ...options, size: autoSize })} src={getAvatarUrl(figure, { ...options, size: autoSize })}
alt={alt} alt={alt}
+1 -1
View File
@@ -14,6 +14,6 @@ const LABELS: Record<CurrencyType, string> = {
}; };
export function currencyLabel(type: CurrencyType): string { export function currencyLabel(type: CurrencyType): string {
// eslint-disable-next-line security/detect-object-injection -- type is CurrencyType enum // type is CurrencyType enum
return LABELS[type]; return LABELS[type];
} }
-2
View File
@@ -55,12 +55,10 @@ async function writeToFile(
): Promise<boolean> { ): Promise<boolean> {
try { try {
const logDir = resolve(process.cwd(), "storage", "logs"); const logDir = resolve(process.cwd(), "storage", "logs");
// eslint-disable-next-line security/detect-non-literal-fs-filename
await mkdir(logDir, { recursive: true }); await mkdir(logDir, { recursive: true });
const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const filename = `email-${timestamp}.html`; const filename = `email-${timestamp}.html`;
const content = `<!-- To: ${to} | From: ${from} | Subject: ${subject} -->\n${html}`; const content = `<!-- To: ${to} | From: ${from} | Subject: ${subject} -->\n${html}`;
// eslint-disable-next-line security/detect-non-literal-fs-filename
await writeFile(resolve(logDir, filename), content, "utf-8"); await writeFile(resolve(logDir, filename), content, "utf-8");
logger.info("Email written to file", { module: "email", filename }); logger.info("Email written to file", { module: "email", filename });
return true; return true;
+1 -1
View File
@@ -59,7 +59,7 @@ export async function checkVpn(ip: string): Promise<IpVerdict> {
string, string,
{ proxy?: string; type?: string } { proxy?: string; type?: string }
>; >;
// eslint-disable-next-line security/detect-object-injection -- ip is the API response key from proxycheck // ip is the API response key from proxycheck
const entry = d?.[ip]; const entry = d?.[ip];
if (entry?.proxy === "yes") if (entry?.proxy === "yes")
return { blocked: true, reason: `${entry.type ?? "Proxy"} detected` }; return { blocked: true, reason: `${entry.type ?? "Proxy"} detected` };
+1 -1
View File
@@ -84,7 +84,7 @@ export async function sendCurrency(
data: { credits: { increment: amount } }, data: { credits: { increment: amount } },
}); });
} else { } else {
// eslint-disable-next-line security/detect-object-injection -- type is CurrencyName union, not "credits" // type is CurrencyName union, not "credits"
const t = TYPE_VALUE[type]; const t = TYPE_VALUE[type];
await deps.db.usersCurrency.upsert({ await deps.db.usersCurrency.upsert({
where: { userId_type: { userId, type: t } }, where: { userId_type: { userId, type: t } },
+1 -1
View File
@@ -128,7 +128,7 @@ class SiteSettings {
): Promise<string | null> { ): Promise<string | null> {
const map = await this.load(); const map = await this.load();
if (map.has(key)) return map.get(key) as string; if (map.has(key)) return map.get(key) as string;
// eslint-disable-next-line security/detect-object-injection -- guarded by `key in DEFAULTS` // guarded by `key in DEFAULTS`
if (key in DEFAULTS) return DEFAULTS[key] as string; if (key in DEFAULTS) return DEFAULTS[key] as string;
return fallback; return fallback;
} }
+4 -4
View File
@@ -42,7 +42,7 @@ function fallbackFor(key: string): string {
const preset = PRESETS["Atom (golden)"]; const preset = PRESETS["Atom (golden)"];
if (key.endsWith("_dark")) { if (key.endsWith("_dark")) {
const base = key.slice(0, -"_dark".length) as ThemeColorKey; const base = key.slice(0, -"_dark".length) as ThemeColorKey;
return preset.dark[base] ?? ""; // eslint-disable-line security/detect-object-injection -- key derived from internal THEME_COLOR_KEYS return preset.dark[base] ?? ""; // key derived from internal THEME_COLOR_KEYS
} }
if (key in preset.light) { if (key in preset.light) {
return preset.light[key as ThemeColorKey] ?? ""; return preset.light[key as ThemeColorKey] ?? "";
@@ -63,7 +63,7 @@ function fallbackFor(key: string): string {
theme_preset: "Atom (golden)", theme_preset: "Atom (golden)",
...BACKGROUND_DEFAULTS, ...BACKGROUND_DEFAULTS,
}; };
return extras[key] ?? ""; // eslint-disable-line security/detect-object-injection -- key derived from internal EXTRA_KEYS return extras[key] ?? ""; // key derived from internal EXTRA_KEYS
} }
export async function listCustomThemes(): Promise<CustomTheme[]> { export async function listCustomThemes(): Promise<CustomTheme[]> {
@@ -106,9 +106,9 @@ export async function upsertCustomTheme(
if (id) { if (id) {
const idx = themes.findIndex((t) => t.id === id); const idx = themes.findIndex((t) => t.id === id);
if (idx >= 0) { if (idx >= 0) {
themes[idx] = { ...themes[idx], name: trimmed, settings }; // eslint-disable-line security/detect-object-injection -- idx from findIndex over internal array themes[idx] = { ...themes[idx], name: trimmed, settings }; // idx from findIndex over internal array
await persist(themes); await persist(themes);
return themes[idx]; // eslint-disable-line security/detect-object-injection -- idx from findIndex over internal array return themes[idx]; // idx from findIndex over internal array
} }
} }
const theme: CustomTheme = { const theme: CustomTheme = {