diff --git a/scripts/merge-config.cjs b/scripts/merge-config.cjs index 6c88a2d916..ebe6db2897 100644 --- a/scripts/merge-config.cjs +++ b/scripts/merge-config.cjs @@ -1,47 +1,53 @@ -const fs = require('fs'); -const JSON5 = require('json5'); +const fs = require("fs"); +const JSON5 = require("json5"); const exampleFile = process.argv[2]; const targetFile = process.argv[3]; if (!exampleFile || !targetFile) { - process.exit(1); + process.exit(1); } function deepMerge(target, source) { - const result = { ...target }; - for (const key of Object.keys(source)) { - if (source[key] !== null && typeof source[key] === 'object' && !Array.isArray(source[key])) { - result[key] = deepMerge(target[key] || {}, source[key]); - } else { - if (!(key in result)) { - result[key] = source[key]; - } - } - } - return result; + const result = { ...target }; + for (const key of Object.keys(source)) { + if ( + source[key] !== null && + typeof source[key] === "object" && + !Array.isArray(source[key]) + ) { + result[key] = deepMerge(target[key] || {}, source[key]); + } else { + if (!(key in result)) { + result[key] = source[key]; + } + } + } + return result; } let example; try { - const content = fs.readFileSync(exampleFile, 'utf-8'); - example = JSON5.parse(content); + const content = fs.readFileSync(exampleFile, "utf-8"); + example = JSON5.parse(content); } catch { - process.exit(1); + process.exit(1); } let current = {}; try { - if (fs.existsSync(targetFile)) { - const content = fs.readFileSync(targetFile, 'utf-8'); - current = JSON5.parse(content); - } + if (fs.existsSync(targetFile)) { + const content = fs.readFileSync(targetFile, "utf-8"); + current = JSON5.parse(content); + } } catch { - current = {}; + current = {}; } const merged = deepMerge(current, example); -const isJson5 = targetFile.endsWith('.json5'); -const output = isJson5 ? JSON5.stringify(merged, null, 4) : JSON.stringify(merged, null, 4); -fs.writeFileSync(targetFile, output + '\n'); +const isJson5 = targetFile.endsWith(".json5"); +const output = isJson5 + ? JSON5.stringify(merged, null, 4) + : JSON.stringify(merged, null, 4); +fs.writeFileSync(targetFile, output + "\n"); diff --git a/src/actions/draw-badge.ts b/src/actions/draw-badge.ts index 54316abe86..34b672cdff 100644 --- a/src/actions/draw-badge.ts +++ b/src/actions/draw-badge.ts @@ -88,29 +88,27 @@ export async function buyBadge(formData: FormData): Promise { // Atomically deduct credits and persist the badge so a failure // between the two operations cannot orphan the user. if (price > 0) { - await prisma.$transaction( - async (tx) => { - await tx.user.update({ - where: { id: userId }, - data: { credits: { decrement: price } }, - }); + await prisma.$transaction(async (tx) => { + await tx.user.update({ + where: { id: userId }, + data: { credits: { decrement: price } }, + }); - const existing = await tx.usersBadges.findFirst({ - where: { userId, badgeCode: code }, - select: { id: true }, + const existing = await tx.usersBadges.findFirst({ + where: { userId, badgeCode: code }, + select: { id: true }, + }); + if (!existing) { + const max = await tx.usersBadges.aggregate({ + where: { userId }, + _max: { slotId: true }, }); - if (!existing) { - const max = await tx.usersBadges.aggregate({ - where: { userId }, - _max: { slotId: true }, - }); - const slotId = (max._max.slotId ?? 0) + 1; - await tx.usersBadges.create({ - data: { userId, slotId, badgeCode: code }, - }); - } - }, - ); + const slotId = (max._max.slotId ?? 0) + 1; + await tx.usersBadges.create({ + data: { userId, slotId, badgeCode: code }, + }); + } + }); } // Grant the badge live so it appears immediately for online users. diff --git a/src/actions/users.ts b/src/actions/users.ts index 26ae6053db..063eeba3a0 100644 --- a/src/actions/users.ts +++ b/src/actions/users.ts @@ -85,9 +85,9 @@ export const createUser = adminAction( "code" in err && err.code === "P2002" ) { - const target = ( - (err as { meta?: { target?: string[] } }).meta?.target as string[] - ) ?? []; + const target = + ((err as { meta?: { target?: string[] } }).meta + ?.target as string[]) ?? []; if (target.includes("username")) throw new ActionError("Username already taken"); if (target.includes("mail")) diff --git a/src/app/admin/catalog/page.tsx b/src/app/admin/catalog/page.tsx index 89f4646881..109a435a36 100644 --- a/src/app/admin/catalog/page.tsx +++ b/src/app/admin/catalog/page.tsx @@ -3,10 +3,9 @@ import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { AdminPageShell } from "@/components/admin/admin-page-shell"; import { CatalogTree } from "@/components/admin/catalog-tree"; -import { Prisma } from "@/lib/prisma"; import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; -import { prisma } from "@/lib/prisma"; +import { Prisma, prisma } from "@/lib/prisma"; import { getCatalogItemCounts } from "@/lib/services/catalog-tree"; import { CatalogActions } from "./catalog-actions"; import { CatalogManagerDialog } from "./catalog-manager-dialog-wrapper"; diff --git a/src/app/admin/help-tickets/page.tsx b/src/app/admin/help-tickets/page.tsx index 114bd5481c..bf246cb2ae 100644 --- a/src/app/admin/help-tickets/page.tsx +++ b/src/app/admin/help-tickets/page.tsx @@ -62,10 +62,7 @@ export default async function AdminHelpTicketsPage({ ? conditions[0] : { AND: conditions }; - const SORT_MAP: Record< - string, - any - > = { + const SORT_MAP: Record = { title: { title: "asc" }, open: { open: "desc" }, user: { userId: "asc" }, diff --git a/src/app/admin/moderation/cfh/page.tsx b/src/app/admin/moderation/cfh/page.tsx index 9eda22a4fd..050e22b524 100644 --- a/src/app/admin/moderation/cfh/page.tsx +++ b/src/app/admin/moderation/cfh/page.tsx @@ -65,10 +65,7 @@ export default async function CfhListPage({ ? conditions[0] : { AND: conditions }; - const SORT_MAP: Record< - string, - any - > = { + const SORT_MAP: Record = { id: { id: "desc" }, issue: { issue: "asc" }, sender: { senderId: "asc" }, diff --git a/src/app/admin/tickets/page.tsx b/src/app/admin/tickets/page.tsx index 439fdc164e..498c7eb708 100644 --- a/src/app/admin/tickets/page.tsx +++ b/src/app/admin/tickets/page.tsx @@ -53,15 +53,14 @@ export default async function AdminTicketsPage({ ? conditions[0] : { AND: conditions }; - const SORT_MAP: Record = - { - subject: { subject: "asc" }, - status: { status: "asc" }, - priority: { priority: "asc" }, - assignee: { assigneeId: "asc" }, - date: { createdAt: "desc" }, - id: { id: "desc" }, - }; + const SORT_MAP: Record = { + subject: { subject: "asc" }, + status: { status: "asc" }, + priority: { priority: "asc" }, + assignee: { assigneeId: "asc" }, + date: { createdAt: "desc" }, + id: { id: "desc" }, + }; const baseOrder = SORT_MAP[parsed.sort ?? "date"] ?? { createdAt: "desc" }; const orderField = Object.keys(baseOrder)[0] as keyof typeof baseOrder; diff --git a/src/app/admin/transactions/page.tsx b/src/app/admin/transactions/page.tsx index 8c31073dfc..b779741b32 100644 --- a/src/app/admin/transactions/page.tsx +++ b/src/app/admin/transactions/page.tsx @@ -33,10 +33,7 @@ function formatAmount(amount: number, currency: string): string { return `${value} ${currency}`; } -const SORT_MAP: Record< - string, - any -> = { +const SORT_MAP: Record = { id: { id: "desc" }, amount: { amount: "desc" }, date: { createdAt: "desc" }, diff --git a/src/app/admin/translations/emulator/page.tsx b/src/app/admin/translations/emulator/page.tsx index 7667b3ec3d..95ea8cd40f 100644 --- a/src/app/admin/translations/emulator/page.tsx +++ b/src/app/admin/translations/emulator/page.tsx @@ -29,10 +29,9 @@ export default async function EmulatorTranslationsPage({ const perPage = Math.min(Math.max(parseInt(sp.perPage || "50", 10), 10), 200); const page = Math.max(parseInt(sp.page || "1", 10), 1); - const patternFilters: any[] = - TRANSLATION_KEY_PATTERNS.map((p) => ({ - key: { contains: p.replace(/%/g, "") }, - })); + const patternFilters: any[] = TRANSLATION_KEY_PATTERNS.map((p) => ({ + key: { contains: p.replace(/%/g, "") }, + })); const baseFilter: any = { OR: patternFilters }; diff --git a/src/app/admin/users/edit/[id]/page.tsx b/src/app/admin/users/edit/[id]/page.tsx index 044eebeff4..b3089100cc 100644 --- a/src/app/admin/users/edit/[id]/page.tsx +++ b/src/app/admin/users/edit/[id]/page.tsx @@ -1,8 +1,7 @@ import { redirect } from "next/navigation"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Prisma } from "@/lib/prisma"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; -import { prisma } from "@/lib/prisma"; +import { Prisma, prisma } from "@/lib/prisma"; import { getWatchedUserIds } from "@/lib/services/watch"; import { UserAccountInfo } from "../../_components/user-account-info"; import { UserHero } from "../../_components/user-hero"; diff --git a/src/app/api/admin/prefixes/route.ts b/src/app/api/admin/prefixes/route.ts index 7e38125b92..b008d00ac9 100644 --- a/src/app/api/admin/prefixes/route.ts +++ b/src/app/api/admin/prefixes/route.ts @@ -1,8 +1,7 @@ -import { Prisma } from "@/lib/prisma"; import { withAdmin } from "@/lib/api-handler"; import { apiError, apiOk } from "@/lib/api-response"; import { PERMS } from "@/lib/permissions"; -import { prisma } from "@/lib/prisma"; +import { Prisma, prisma } from "@/lib/prisma"; interface UserPrefix { id: number; diff --git a/src/app/api/badges/leaderboard/route.ts b/src/app/api/badges/leaderboard/route.ts index d9f1276f3c..8deedef504 100644 --- a/src/app/api/badges/leaderboard/route.ts +++ b/src/app/api/badges/leaderboard/route.ts @@ -1,9 +1,8 @@ -import { Prisma } from "@/lib/prisma"; import { apiJson } from "@/lib/api"; import { bearerUserId } from "@/lib/api-auth"; import { auth } from "@/lib/auth"; import { logger } from "@/lib/logger"; -import { prisma } from "@/lib/prisma"; +import { Prisma, prisma } from "@/lib/prisma"; export const dynamic = "force-dynamic"; diff --git a/src/app/mod/cfh/page.tsx b/src/app/mod/cfh/page.tsx index f6f276ef3b..080be34fa2 100644 --- a/src/app/mod/cfh/page.tsx +++ b/src/app/mod/cfh/page.tsx @@ -61,10 +61,7 @@ export default async function ModCfhListPage({ ? conditions[0] : { AND: conditions }; - const SORT_MAP: Record< - string, - any - > = { + const SORT_MAP: Record = { id: { id: "desc" }, issue: { issue: "asc" }, sender: { senderId: "asc" }, diff --git a/src/app/mod/help-tickets/page.tsx b/src/app/mod/help-tickets/page.tsx index 606a3abb67..dd3561168c 100644 --- a/src/app/mod/help-tickets/page.tsx +++ b/src/app/mod/help-tickets/page.tsx @@ -62,10 +62,7 @@ export default async function ModHelpTicketsPage({ ? conditions[0] : { AND: conditions }; - const SORT_MAP: Record< - string, - any - > = { + const SORT_MAP: Record = { title: { title: "asc" }, open: { open: "desc" }, user: { userId: "asc" }, diff --git a/src/app/mod/tickets/page.tsx b/src/app/mod/tickets/page.tsx index da69315007..d2e88b02c4 100644 --- a/src/app/mod/tickets/page.tsx +++ b/src/app/mod/tickets/page.tsx @@ -53,15 +53,14 @@ export default async function ModTicketsPage({ ? conditions[0] : { AND: conditions }; - const SORT_MAP: Record = - { - subject: { subject: "asc" }, - status: { status: "asc" }, - priority: { priority: "asc" }, - assignee: { assigneeId: "asc" }, - date: { createdAt: "desc" }, - id: { id: "desc" }, - }; + const SORT_MAP: Record = { + subject: { subject: "asc" }, + status: { status: "asc" }, + priority: { priority: "asc" }, + assignee: { assigneeId: "asc" }, + date: { createdAt: "desc" }, + id: { id: "desc" }, + }; const baseOrder = SORT_MAP[parsed.sort ?? "date"] ?? { createdAt: "desc" }; const orderField = Object.keys(baseOrder)[0] as keyof typeof baseOrder; diff --git a/src/db/relations.ts b/src/db/relations.ts index 4cb653a070..c81547fe80 100644 --- a/src/db/relations.ts +++ b/src/db/relations.ts @@ -25,19 +25,22 @@ export const userRelations = relations(User, ({ many }) => ({ auditLogs: many(AdminAuditLog), })); -export const websiteTicketRelations = relations(WebsiteTicket, ({ one, many }) => ({ - creator: one(User, { - fields: [WebsiteTicket.creatorId], - references: [User.id], - relationName: "ticketCreator", +export const websiteTicketRelations = relations( + WebsiteTicket, + ({ one, many }) => ({ + creator: one(User, { + fields: [WebsiteTicket.creatorId], + references: [User.id], + relationName: "ticketCreator", + }), + assignee: one(User, { + fields: [WebsiteTicket.assigneeId], + references: [User.id], + relationName: "ticketAssignee", + }), + messages: many(WebsiteTicketMessage), }), - assignee: one(User, { - fields: [WebsiteTicket.assigneeId], - references: [User.id], - relationName: "ticketAssignee", - }), - messages: many(WebsiteTicketMessage), -})); +); export const websiteTicketMessageRelations = relations( WebsiteTicketMessage, @@ -79,19 +82,25 @@ export const aclModelPermissionRelations = relations( }), ); -export const websiteEventTypeRelations = relations(WebsiteEventType, ({ many }) => ({ - events: many(WebsiteEvent), -})); - -export const websiteEventRelations = relations(WebsiteEvent, ({ one, many }) => ({ - type: one(WebsiteEventType, { - fields: [WebsiteEvent.typeId], - references: [WebsiteEventType.id], +export const websiteEventTypeRelations = relations( + WebsiteEventType, + ({ many }) => ({ + events: many(WebsiteEvent), }), - registrations: many(WebsiteEventRegistration), - prizes: many(WebsiteEventPrize), - winners: many(WebsiteEventWinner), -})); +); + +export const websiteEventRelations = relations( + WebsiteEvent, + ({ one, many }) => ({ + type: one(WebsiteEventType, { + fields: [WebsiteEvent.typeId], + references: [WebsiteEventType.id], + }), + registrations: many(WebsiteEventRegistration), + prizes: many(WebsiteEventPrize), + winners: many(WebsiteEventWinner), + }), +); export const websiteEventRegistrationRelations = relations( WebsiteEventRegistration, @@ -103,19 +112,25 @@ export const websiteEventRegistrationRelations = relations( }), ); -export const websiteEventPrizeRelations = relations(WebsiteEventPrize, ({ one }) => ({ - event: one(WebsiteEvent, { - fields: [WebsiteEventPrize.eventId], - references: [WebsiteEvent.id], +export const websiteEventPrizeRelations = relations( + WebsiteEventPrize, + ({ one }) => ({ + event: one(WebsiteEvent, { + fields: [WebsiteEventPrize.eventId], + references: [WebsiteEvent.id], + }), }), -})); +); -export const websiteEventWinnerRelations = relations(WebsiteEventWinner, ({ one }) => ({ - event: one(WebsiteEvent, { - fields: [WebsiteEventWinner.eventId], - references: [WebsiteEvent.id], +export const websiteEventWinnerRelations = relations( + WebsiteEventWinner, + ({ one }) => ({ + event: one(WebsiteEvent, { + fields: [WebsiteEventWinner.eventId], + references: [WebsiteEvent.id], + }), }), -})); +); export const websitePollRelations = relations(WebsitePoll, ({ many }) => ({ questions: many(WebsitePollQuestion), @@ -132,9 +147,12 @@ export const websitePollQuestionRelations = relations( }), ); -export const websitePollVoteRelations = relations(WebsitePollVote, ({ one }) => ({ - question: one(WebsitePollQuestion, { - fields: [WebsitePollVote.questionId], - references: [WebsitePollQuestion.id], +export const websitePollVoteRelations = relations( + WebsitePollVote, + ({ one }) => ({ + question: one(WebsitePollQuestion, { + fields: [WebsitePollVote.questionId], + references: [WebsitePollQuestion.id], + }), }), -})); +); diff --git a/src/lib/admin/log-filters.ts b/src/lib/admin/log-filters.ts index a574755eed..60cb046837 100644 --- a/src/lib/admin/log-filters.ts +++ b/src/lib/admin/log-filters.ts @@ -5,9 +5,7 @@ export interface StaffActivityFilters { authorizationOnly?: boolean; } -export function buildStaffActivityWhere( - filters: StaffActivityFilters, -): any { +export function buildStaffActivityWhere(filters: StaffActivityFilters): any { const where: any = {}; if (filters.q?.trim()) where.OR = [ diff --git a/src/lib/auth/password.test.ts b/src/lib/auth/password.test.ts index 94f4a3c2a1..707a94e0d3 100644 --- a/src/lib/auth/password.test.ts +++ b/src/lib/auth/password.test.ts @@ -75,4 +75,4 @@ describe("checkLogin", () => { expect(res.valid).toBe(true); expect(res.upgradedHash).toBeUndefined(); }); -}); \ No newline at end of file +}); diff --git a/src/lib/auth/password.ts b/src/lib/auth/password.ts index bf3bac9741..9d271882dc 100644 --- a/src/lib/auth/password.ts +++ b/src/lib/auth/password.ts @@ -1,9 +1,5 @@ import { randomBytes } from "node:crypto"; -import { - bcrypt, - bcryptVerify, - md5, -} from "hash-wasm"; +import { bcrypt, bcryptVerify, md5 } from "hash-wasm"; import { env } from "@/env"; @@ -59,4 +55,4 @@ export async function checkLogin( return { valid: true, upgradedHash: await hashPassword(password) }; } return { valid: await verifyPassword(password, stored) }; -} \ No newline at end of file +} diff --git a/src/lib/prisma-facade.ts b/src/lib/prisma-facade.ts index 6d2145468f..cf45de2f95 100644 --- a/src/lib/prisma-facade.ts +++ b/src/lib/prisma-facade.ts @@ -444,7 +444,9 @@ async function runFindMany( if (condition) stmt = stmt.where(condition); if (orderBy.length) stmt = stmt.orderBy(...orderBy); if (args.take !== undefined) stmt = stmt.limit(args.take); - else if (args.skip) stmt = stmt.limit(18446744073709551615); + else if (args.skip) + // biome-ignore lint/correctness/noPrecisionLoss: SQL literal for BIGINT UNSIGNED max value (2^64-1 — exceeds JS number precision) + stmt = stmt.limit(18446744073709551615); if (args.skip) stmt = stmt.offset(args.skip); const rows = (await stmt) as any[]; @@ -804,7 +806,9 @@ async function runGroupBy( } if (args.take !== undefined) stmt = stmt.limit(args.take); - else if (args.skip) stmt = stmt.limit(18446744073709551615); + else if (args.skip) + // biome-ignore lint/correctness/noPrecisionLoss: SQL literal for BIGINT UNSIGNED max value (2^64-1 — exceeds JS number precision) + stmt = stmt.limit(18446744073709551615); if (args.skip) stmt = stmt.offset(args.skip); const rows = (await stmt) as any[]; @@ -871,6 +875,7 @@ class PrismaPromise implements PromiseLike { private readonly client: Client, ) {} + // biome-ignore lint/suspicious/noThenProperty: Custom thenable for $transaction chaining — routes through Drizzle at runtime then( onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, onRejected?: ((reason: any) => TResult2 | PromiseLike) | null, diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index d1665a4a28..c8f8310673 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,6 +1,6 @@ import type { PrismaClient } from "@/generated/prisma/client"; -import { makePrisma, Prisma as PrismaHelper } from "@/lib/prisma-facade"; import { db } from "@/lib/db"; +import { makePrisma, Prisma as PrismaHelper } from "@/lib/prisma-facade"; export const prisma = makePrisma(db) as unknown as PrismaClient; export { PrismaHelper as Prisma }; diff --git a/tsconfig.json b/tsconfig.json index cc1df3d35f..31fad022d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,4 +33,4 @@ ".next/dev/types/**/*.ts" ], "exclude": ["node_modules", "e2e", "playwright.config.ts"] -} \ No newline at end of file +}