refactor(db): migrate app pages and APIs from Prisma facade to Drizzle
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
53b350057d
commit
7c39aef5d9
40 files changed
+1124
-692
No files matched your search
@@ -1,11 +1,12 @@
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import type { CSSProperties } from "react";
|
||||
import { applyDj } from "@/actions/radio-apply";
|
||||
import { ContentCard } from "@/components/public/ui";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db, RadioApplications } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -49,13 +50,17 @@ export default async function RadioApplyPage({
|
||||
|
||||
// Surface the applicant's most recent application (if any) so they aren't
|
||||
// left guessing. Isolated query: a DB hiccup degrades this to null, not 500.
|
||||
const existing = await prisma.radioApplications
|
||||
.findFirst({
|
||||
where: { userId: BigInt(userId) },
|
||||
orderBy: { id: "desc" },
|
||||
select: { id: true, status: true, createdAt: true },
|
||||
const [existing] = await db
|
||||
.select({
|
||||
id: RadioApplications.id,
|
||||
status: RadioApplications.status,
|
||||
createdAt: RadioApplications.createdAt,
|
||||
})
|
||||
.catch(() => null);
|
||||
.from(RadioApplications)
|
||||
.where(eq(RadioApplications.userId, BigInt(userId)))
|
||||
.orderBy(desc(RadioApplications.id))
|
||||
.limit(1)
|
||||
.catch(() => []);
|
||||
|
||||
const submittedDate = existing?.createdAt
|
||||
? formatDate(existing.createdAt, "date")
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard } from "@/components/public/ui";
|
||||
import { db, RadioContests } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
@@ -22,9 +23,12 @@ export default async function RadioContestDetailPage({
|
||||
notFound();
|
||||
}
|
||||
|
||||
const contest = await prisma.radioContests
|
||||
.findUnique({ where: { id: contestId } })
|
||||
.catch(() => null);
|
||||
const [contest] = await db
|
||||
.select()
|
||||
.from(RadioContests)
|
||||
.where(eq(RadioContests.id, contestId))
|
||||
.limit(1)
|
||||
.catch(() => []);
|
||||
|
||||
if (!contest) notFound();
|
||||
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { db, RadioContests } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
export default async function RadioContestsPage() {
|
||||
const t = await getTranslations("pages.radioContests");
|
||||
|
||||
const contests = await prisma.radioContests
|
||||
.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { startDate: "desc" },
|
||||
take: 50,
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
prize: true,
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
},
|
||||
const contests = await db
|
||||
.select({
|
||||
id: RadioContests.id,
|
||||
title: RadioContests.title,
|
||||
prize: RadioContests.prize,
|
||||
startDate: RadioContests.startDate,
|
||||
endDate: RadioContests.endDate,
|
||||
})
|
||||
.from(RadioContests)
|
||||
.where(eq(RadioContests.isActive, true))
|
||||
.orderBy(desc(RadioContests.startDate))
|
||||
.limit(50)
|
||||
.catch(() => []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard } from "@/components/public/ui";
|
||||
import { db, RadioGiveaways } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
@@ -22,9 +23,12 @@ export default async function RadioGiveawayDetailPage({
|
||||
notFound();
|
||||
}
|
||||
|
||||
const giveaway = await prisma.radioGiveaways
|
||||
.findUnique({ where: { id: giveawayId } })
|
||||
.catch(() => null);
|
||||
const [giveaway] = await db
|
||||
.select()
|
||||
.from(RadioGiveaways)
|
||||
.where(eq(RadioGiveaways.id, giveawayId))
|
||||
.limit(1)
|
||||
.catch(() => []);
|
||||
|
||||
if (!giveaway) notFound();
|
||||
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { db, RadioGiveaways } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
export default async function RadioGiveawaysPage() {
|
||||
const t = await getTranslations("pages.radioGiveaways");
|
||||
|
||||
const giveaways = await prisma.radioGiveaways
|
||||
.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { startDate: "desc" },
|
||||
take: 50,
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
prize: true,
|
||||
prizeAmount: true,
|
||||
prizeCurrency: true,
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
},
|
||||
const giveaways = await db
|
||||
.select({
|
||||
id: RadioGiveaways.id,
|
||||
title: RadioGiveaways.title,
|
||||
prize: RadioGiveaways.prize,
|
||||
prizeAmount: RadioGiveaways.prizeAmount,
|
||||
prizeCurrency: RadioGiveaways.prizeCurrency,
|
||||
startDate: RadioGiveaways.startDate,
|
||||
endDate: RadioGiveaways.endDate,
|
||||
})
|
||||
.from(RadioGiveaways)
|
||||
.where(eq(RadioGiveaways.isActive, true))
|
||||
.orderBy(desc(RadioGiveaways.startDate))
|
||||
.limit(50)
|
||||
.catch(() => []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, EmptyState, RankBadge } from "@/components/public/ui";
|
||||
import { db } from "@/lib/db";
|
||||
import { avatarImageUrl } from "@/lib/format";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
// The radio_listener_points table in the live AtomCMS DB carries user_id +
|
||||
// points columns, but the generated Prisma model is a stub (id/timestamps only),
|
||||
// so it has no `points`/`userId` delegate fields. We therefore read it via
|
||||
// $queryRaw, joining users for the username/look — the same approach used for
|
||||
// the keyless/@@ignore'd emulator tables (see src/app/admin/logs). MySQL may
|
||||
// return BigInt columns as bigint, so values are coerced defensively.
|
||||
type LeaderRow = {
|
||||
username: string;
|
||||
look: string;
|
||||
@@ -23,14 +18,14 @@ async function loadRows(): Promise<
|
||||
{ username: string; look: string; points: number }[]
|
||||
> {
|
||||
try {
|
||||
const rows = await prisma.$queryRaw<LeaderRow[]>`
|
||||
SELECT u.username AS username, u.look AS look, p.points AS points
|
||||
FROM radio_listener_points p
|
||||
INNER JOIN users u ON u.id = p.user_id
|
||||
ORDER BY p.points DESC
|
||||
LIMIT 50
|
||||
`;
|
||||
return rows.map((r) => ({
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT u.username AS username, u.look AS look, p.points AS points
|
||||
FROM radio_listener_points p
|
||||
INNER JOIN users u ON u.id = p.user_id
|
||||
ORDER BY p.points DESC
|
||||
LIMIT 50
|
||||
`)) as unknown as [LeaderRow[], unknown];
|
||||
return (rows ?? []).map((r) => ({
|
||||
username: r.username,
|
||||
look: r.look,
|
||||
points: Number(r.points),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { asc, eq, inArray } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, EmptyState, OnlineBadge } from "@/components/public/ui";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { db, RadioSchedules, User } from "@/lib/db";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
// Radio config + today's schedule are read live from the DB / settings table.
|
||||
@@ -78,11 +79,11 @@ export default async function RadioPage() {
|
||||
|
||||
// ── Today's schedule (isolated; a DB hiccup degrades to empty) ──
|
||||
const today = WEEKDAYS[new Date().getUTCDay()] ?? "";
|
||||
const schedules = await prisma.radioSchedules
|
||||
.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { startTime: "asc" },
|
||||
})
|
||||
const schedules = await db
|
||||
.select()
|
||||
.from(RadioSchedules)
|
||||
.where(eq(RadioSchedules.isActive, true))
|
||||
.orderBy(asc(RadioSchedules.startTime))
|
||||
.catch(() => []);
|
||||
|
||||
const todaysSlots = schedules.filter(
|
||||
@@ -94,11 +95,10 @@ export default async function RadioPage() {
|
||||
// lookup (DJ ids comfortably fit in a JS-safe integer).
|
||||
const djIds = Array.from(new Set(todaysSlots.map((s) => Number(s.userId))));
|
||||
const djs = djIds.length
|
||||
? await prisma.user
|
||||
.findMany({
|
||||
where: { id: { in: djIds } },
|
||||
select: { id: true, username: true },
|
||||
})
|
||||
? await db
|
||||
.select({ id: User.id, username: User.username })
|
||||
.from(User)
|
||||
.where(inArray(User.id, djIds))
|
||||
.catch(() => [])
|
||||
: [];
|
||||
const djById = new Map(djs.map((d) => [d.id, d.username]));
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { desc, inArray } from "drizzle-orm";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import type { CSSProperties } from "react";
|
||||
import { submitRequest } from "@/actions/radio-requests";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db, RadioSongRequests, User } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -47,11 +48,11 @@ export default async function RadioRequestsPage({
|
||||
: null;
|
||||
|
||||
const [requests, session] = await Promise.all([
|
||||
prisma.radioSongRequests
|
||||
.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 50,
|
||||
})
|
||||
db
|
||||
.select()
|
||||
.from(RadioSongRequests)
|
||||
.orderBy(desc(RadioSongRequests.createdAt))
|
||||
.limit(50)
|
||||
.catch(() => []),
|
||||
auth(),
|
||||
]);
|
||||
@@ -60,11 +61,10 @@ export default async function RadioRequestsPage({
|
||||
|
||||
const userIds = Array.from(new Set(requests.map((r) => Number(r.userId))));
|
||||
const users = userIds.length
|
||||
? await prisma.user
|
||||
.findMany({
|
||||
where: { id: { in: userIds } },
|
||||
select: { id: true, username: true },
|
||||
})
|
||||
? await db
|
||||
.select({ id: User.id, username: User.username })
|
||||
.from(User)
|
||||
.where(inArray(User.id, userIds))
|
||||
.catch(() => [])
|
||||
: [];
|
||||
const userById = new Map(users.map((u) => [u.id, u.username]));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { asc, eq, inArray } from "drizzle-orm";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { db, RadioSchedules, User } from "@/lib/db";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
@@ -35,21 +36,20 @@ type Slot = {
|
||||
export default async function RadioSchedulePage() {
|
||||
const t = await getTranslations("pages.radioSchedule");
|
||||
|
||||
const schedules = await prisma.radioSchedules
|
||||
.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { startTime: "asc" },
|
||||
})
|
||||
const schedules = await db
|
||||
.select()
|
||||
.from(RadioSchedules)
|
||||
.where(eq(RadioSchedules.isActive, true))
|
||||
.orderBy(asc(RadioSchedules.startTime))
|
||||
.catch(() => []);
|
||||
|
||||
// Resolve DJ usernames. schedule.userId is BigInt; User.id is Int — convert.
|
||||
const djIds = Array.from(new Set(schedules.map((s) => Number(s.userId))));
|
||||
const djs = djIds.length
|
||||
? await prisma.user
|
||||
.findMany({
|
||||
where: { id: { in: djIds } },
|
||||
select: { id: true, username: true },
|
||||
})
|
||||
? await db
|
||||
.select({ id: User.id, username: User.username })
|
||||
.from(User)
|
||||
.where(inArray(User.id, djIds))
|
||||
.catch(() => [])
|
||||
: [];
|
||||
const djById = new Map(djs.map((u) => [u.id, u.username]));
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { desc, inArray } from "drizzle-orm";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import type { CSSProperties } from "react";
|
||||
import { postShout } from "@/actions/radio-shouts";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db, RadioShouts, User } from "@/lib/db";
|
||||
import { avatarImageUrl } from "@/lib/format";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -37,11 +38,11 @@ export default async function RadioShoutsPage({
|
||||
const { posted, error } = await searchParams;
|
||||
|
||||
const [shouts, imagerBase, session] = await Promise.all([
|
||||
prisma.radioShouts
|
||||
.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 50,
|
||||
})
|
||||
db
|
||||
.select()
|
||||
.from(RadioShouts)
|
||||
.orderBy(desc(RadioShouts.createdAt))
|
||||
.limit(50)
|
||||
.catch(() => []),
|
||||
siteSettings.get(
|
||||
"habbo_imaging_url",
|
||||
@@ -55,11 +56,14 @@ export default async function RadioShoutsPage({
|
||||
// Resolve author usernames/looks. shout.userId is BigInt; User.id is Int.
|
||||
const authorIds = Array.from(new Set(shouts.map((s) => Number(s.userId))));
|
||||
const authors = authorIds.length
|
||||
? await prisma.user
|
||||
.findMany({
|
||||
where: { id: { in: authorIds } },
|
||||
select: { id: true, username: true, look: true },
|
||||
? await db
|
||||
.select({
|
||||
id: User.id,
|
||||
username: User.username,
|
||||
look: User.look,
|
||||
})
|
||||
.from(User)
|
||||
.where(inArray(User.id, authorIds))
|
||||
.catch(() => [])
|
||||
: [];
|
||||
const authorById = new Map(authors.map((a) => [a.id, a]));
|
||||
|
||||
Reference in new issue
Block a user