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