refactor(db): migrate app pages and APIs from Prisma facade to Drizzle

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-08-01 14:15:12 +02:00
1 parent 53b350057d
commit 7c39aef5d9
40 files changed
+1124 -692

No files matched your search

+42 -24
View File
@@ -1,10 +1,16 @@
import { and, asc, eq, inArray } from "drizzle-orm";
import Link from "next/link";
import { notFound } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { ContentCard, EmptyState } from "@/components/public/ui";
import { auth } from "@/lib/auth";
import {
db,
WebsitePoll,
WebsitePollQuestion,
WebsitePollVote,
} from "@/lib/db";
import { formatDate } from "@/lib/format-date";
import { prisma } from "@/lib/prisma";
import { PollVoteForm } from "./poll-vote-form";
export const dynamic = "force-dynamic";
@@ -22,19 +28,24 @@ export default async function PollDetailPage({
const session = await auth();
const userId = Number(session?.user?.id);
const poll = await prisma.websitePoll
.findUnique({
where: { id: pollId },
include: {
questions: { orderBy: { sortOrder: "asc" } },
},
})
.catch(() => null);
const [poll] = await db
.select()
.from(WebsitePoll)
.where(eq(WebsitePoll.id, pollId))
.limit(1)
.catch(() => []);
if (!poll || (poll.status !== "active" && poll.status !== "closed")) {
notFound();
}
const questions = await db
.select()
.from(WebsitePollQuestion)
.where(eq(WebsitePollQuestion.pollId, poll.id))
.orderBy(asc(WebsitePollQuestion.sortOrder))
.catch(() => []);
const now = Date.now();
if (
poll.startsAt &&
@@ -50,24 +61,31 @@ export default async function PollDetailPage({
(poll.endsAt != null && poll.endsAt.getTime() < now);
const isOpen = poll.status === "active" && !hasEnded;
const questionIds = poll.questions.map((q) => q.id);
const questionIds = questions.map((q) => q.id);
const myVotes =
Number.isInteger(userId) && userId > 0 && questionIds.length > 0
? await prisma.websitePollVote
.findMany({
where: { userId, questionId: { in: questionIds } },
})
? await db
.select()
.from(WebsitePollVote)
.where(
and(
eq(WebsitePollVote.userId, userId),
inArray(WebsitePollVote.questionId, questionIds),
),
)
.catch(() => [])
: [];
const hasVoted = myVotes.length > 0;
const allVotes =
poll.showResults === 1
? await prisma.websitePollVote
.findMany({
where: { questionId: { in: questionIds } },
select: { questionId: true, answer: true },
poll.showResults === 1 && questionIds.length > 0
? await db
.select({
questionId: WebsitePollVote.questionId,
answer: WebsitePollVote.answer,
})
.from(WebsitePollVote)
.where(inArray(WebsitePollVote.questionId, questionIds))
.catch(() => [])
: [];
@@ -112,7 +130,7 @@ export default async function PollDetailPage({
>
<PollVoteForm
pollId={poll.id}
questions={poll.questions.map((q) => ({
questions={questions.map((q) => ({
id: q.id,
question: q.question,
type: q.type,
@@ -140,7 +158,7 @@ export default async function PollDetailPage({
{poll.showResults === 1 && (hasVoted || hasEnded) ? (
<ContentCard icon="📈" title={t("resultsTitle")}>
<div style={{ display: "grid", gap: "1.25rem" }}>
{poll.questions.map((q) => {
{questions.map((q) => {
const options = q.options
.split("\n")
.map((o) => o.trim())
@@ -179,9 +197,9 @@ export default async function PollDetailPage({
</h3>
<div style={{ display: "grid", gap: "0.55rem" }}>
{options.map((opt) => {
const count = counts.get(opt) ?? 0;
const voteCount = counts.get(opt) ?? 0;
const pct =
total > 0 ? Math.round((count / total) * 100) : 0;
total > 0 ? Math.round((voteCount / total) * 100) : 0;
return (
<div key={opt}>
<div
@@ -194,7 +212,7 @@ export default async function PollDetailPage({
>
<span>{opt}</span>
<span className="muted">
{t("voteCount", { count, pct })}
{t("voteCount", { count: voteCount, pct })}
</span>
</div>
<div
+35 -13
View File
@@ -1,9 +1,10 @@
import { and, count, desc, inArray, isNull, lte, or } from "drizzle-orm";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { ContentCard, EmptyState } from "@/components/public/ui";
import { db, WebsitePoll, WebsitePollQuestion } from "@/lib/db";
import { excerpt } from "@/lib/format";
import { formatDate } from "@/lib/format-date";
import { prisma } from "@/lib/prisma";
export const revalidate = 60;
@@ -13,20 +14,41 @@ export default async function PollsPage() {
const t = await getTranslations("pages.polls");
const now = new Date();
const polls = await prisma.websitePoll
.findMany({
where: {
status: { in: ["active", "closed"] },
OR: [{ startsAt: null }, { startsAt: { lte: now } }],
},
orderBy: { createdAt: "desc" },
take: 50,
include: {
_count: { select: { questions: true } },
},
})
const pollRows = await db
.select()
.from(WebsitePoll)
.where(
and(
inArray(WebsitePoll.status, ["active", "closed"]),
or(isNull(WebsitePoll.startsAt), lte(WebsitePoll.startsAt, now)),
),
)
.orderBy(desc(WebsitePoll.createdAt))
.limit(50)
.catch(() => []);
const pollIds = pollRows.map((p) => p.id);
const questionCounts =
pollIds.length > 0
? await db
.select({
pollId: WebsitePollQuestion.pollId,
value: count(),
})
.from(WebsitePollQuestion)
.where(inArray(WebsitePollQuestion.pollId, pollIds))
.groupBy(WebsitePollQuestion.pollId)
.catch(() => [])
: [];
const countByPoll = new Map(
questionCounts.map((r) => [r.pollId, Number(r.value)]),
);
const polls = pollRows.map((poll) => ({
...poll,
_count: { questions: countByPoll.get(poll.id) ?? 0 },
}));
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard icon="📊" title={t("title")} subtitle={t("subtitle")} />