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

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-08-01 14:15:36 +02:00
1 parent 580972c0a0
commit 9aa4f331bf
40 files changed
+1259 -927

No files matched your search

+85 -46
View File
@@ -1,9 +1,11 @@
import { and, eq } from "drizzle-orm";
import type { ResultSetHeader } from "mysql2";
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { sessionUserId } from "@/lib/auth/session-user";
import { db, WebsitePaypalTransactions } from "@/lib/db";
import { resolveHotelName } from "@/lib/hotel-name";
import { logger } from "@/lib/logger";
import { prisma } from "@/lib/prisma";
import { logServerError } from "@/lib/server-log";
import {
type CaptureResult,
@@ -18,7 +20,7 @@ import {
TopupCaptureError,
} from "@/lib/services/paypal-topup";
import { rcon } from "@/lib/services/rcon";
import { sendCurrency } from "@/lib/services/send-currency";
import { currencyDb, sendCurrency } from "@/lib/services/send-currency";
export const dynamic = "force-dynamic";
@@ -79,11 +81,18 @@ export async function POST(req: Request): Promise<Response> {
authorized = await authorizeTopupCapture(
userId,
orderId,
async (transactionId) =>
prisma.websitePaypalTransactions.findFirst({
where: { transactionId },
select: { userId: true, status: true, amount: true },
}),
async (transactionId) => {
const [row] = await db
.select({
userId: WebsitePaypalTransactions.userId,
status: WebsitePaypalTransactions.status,
amount: WebsitePaypalTransactions.amount,
})
.from(WebsitePaypalTransactions)
.where(eq(WebsitePaypalTransactions.transactionId, transactionId))
.limit(1);
return row ?? null;
},
);
} catch (error) {
if (error instanceof TopupCaptureError) {
@@ -103,21 +112,30 @@ export async function POST(req: Request): Promise<Response> {
const amount = authorized.amount ?? 0;
const credits = Math.floor(amount * creditsPerUnit());
try {
await claimTopupDelivery(() =>
prisma.websitePaypalTransactions.updateMany({
where: {
userId,
transactionId: orderId,
status: "CAPTURED_PENDING_CREDIT",
},
data: { status: "CREDIT_DELIVERING", updatedAt: new Date() },
}),
);
await sendCurrency({ rcon, db: prisma }, userId, "credits", credits);
await prisma.websitePaypalTransactions.updateMany({
where: { userId, transactionId: orderId, status: "CREDIT_DELIVERING" },
data: { status: "COMPLETED", updatedAt: new Date() },
await claimTopupDelivery(async () => {
const [result] = (await db
.update(WebsitePaypalTransactions)
.set({ status: "CREDIT_DELIVERING", updatedAt: new Date() })
.where(
and(
eq(WebsitePaypalTransactions.userId, userId),
eq(WebsitePaypalTransactions.transactionId, orderId),
eq(WebsitePaypalTransactions.status, "CAPTURED_PENDING_CREDIT"),
),
)) as unknown as [ResultSetHeader];
return { count: result.affectedRows };
});
await sendCurrency({ rcon, db: currencyDb }, userId, "credits", credits);
await db
.update(WebsitePaypalTransactions)
.set({ status: "COMPLETED", updatedAt: new Date() })
.where(
and(
eq(WebsitePaypalTransactions.userId, userId),
eq(WebsitePaypalTransactions.transactionId, orderId),
eq(WebsitePaypalTransactions.status, "CREDIT_DELIVERING"),
),
);
return NextResponse.json({
ok: true,
recovered: true,
@@ -162,17 +180,23 @@ export async function POST(req: Request): Promise<Response> {
if (result.status !== "COMPLETED") {
// Record the non-completed attempt so support can trace it.
try {
await prisma.websitePaypalTransactions.updateMany({
where: { userId, transactionId: orderId, status: "CREATED" },
data: {
await db
.update(WebsitePaypalTransactions)
.set({
status: result.status,
description: `${hotelName} top-up (not completed)`,
amount: result.amount,
currency: result.currency,
createdAt: new Date(),
updatedAt: new Date(),
},
});
})
.where(
and(
eq(WebsitePaypalTransactions.userId, userId),
eq(WebsitePaypalTransactions.transactionId, orderId),
eq(WebsitePaypalTransactions.status, "CREATED"),
),
);
} catch {
/* best-effort logging */
}
@@ -187,18 +211,24 @@ export async function POST(req: Request): Promise<Response> {
// Record the transaction BEFORE crediting so a crash mid-grant can't be
// reprocessed into a double credit (the idempotency check above keys on this).
try {
const claimed = await prisma.websitePaypalTransactions.updateMany({
where: { userId, transactionId: orderId, status: "CREATED" },
data: {
const [claimed] = (await db
.update(WebsitePaypalTransactions)
.set({
status: "CAPTURED_PENDING_CREDIT",
description: `${hotelName} top-up: ${credits} credits`,
amount: result.amount,
currency: result.currency,
createdAt: new Date(),
updatedAt: new Date(),
},
});
if (claimed.count !== 1)
})
.where(
and(
eq(WebsitePaypalTransactions.userId, userId),
eq(WebsitePaypalTransactions.transactionId, orderId),
eq(WebsitePaypalTransactions.status, "CREATED"),
),
)) as unknown as [ResultSetHeader];
if (claimed.affectedRows !== 1)
throw new Error("Top-up order was already claimed");
} catch (e) {
logger.error("PayPal capture record failed", {
@@ -218,21 +248,30 @@ export async function POST(req: Request): Promise<Response> {
// external delivery outcome is ambiguous, CREDIT_DELIVERING remains visible
// for staff reconciliation instead of automatically risking a second grant.
try {
await claimTopupDelivery(() =>
prisma.websitePaypalTransactions.updateMany({
where: {
userId,
transactionId: orderId,
status: "CAPTURED_PENDING_CREDIT",
},
data: { status: "CREDIT_DELIVERING", updatedAt: new Date() },
}),
);
await sendCurrency({ rcon, db: prisma }, userId, "credits", credits);
await prisma.websitePaypalTransactions.updateMany({
where: { userId, transactionId: orderId, status: "CREDIT_DELIVERING" },
data: { status: "COMPLETED", updatedAt: new Date() },
await claimTopupDelivery(async () => {
const [claimResult] = (await db
.update(WebsitePaypalTransactions)
.set({ status: "CREDIT_DELIVERING", updatedAt: new Date() })
.where(
and(
eq(WebsitePaypalTransactions.userId, userId),
eq(WebsitePaypalTransactions.transactionId, orderId),
eq(WebsitePaypalTransactions.status, "CAPTURED_PENDING_CREDIT"),
),
)) as unknown as [ResultSetHeader];
return { count: claimResult.affectedRows };
});
await sendCurrency({ rcon, db: currencyDb }, userId, "credits", credits);
await db
.update(WebsitePaypalTransactions)
.set({ status: "COMPLETED", updatedAt: new Date() })
.where(
and(
eq(WebsitePaypalTransactions.userId, userId),
eq(WebsitePaypalTransactions.transactionId, orderId),
eq(WebsitePaypalTransactions.status, "CREDIT_DELIVERING"),
),
);
} catch (e) {
logger.error("PayPal capture credit failed", {
module: "paypal/capture",
+2 -2
View File
@@ -2,9 +2,9 @@ import { NextResponse } from "next/server";
import { env } from "@/env";
import { auth } from "@/lib/auth";
import { sessionUserId } from "@/lib/auth/session-user";
import { db, WebsitePaypalTransactions } from "@/lib/db";
import { resolveHotelName } from "@/lib/hotel-name";
import { logger } from "@/lib/logger";
import { prisma } from "@/lib/prisma";
import {
createOrder,
creditsPerUnit,
@@ -87,7 +87,7 @@ export async function POST(req: Request): Promise<Response> {
await recordCreatedTopup(
{ userId, orderId: order.id, amount, currency: PAYPAL_CURRENCY, credits },
(data) => prisma.websitePaypalTransactions.create({ data }),
(data) => db.insert(WebsitePaypalTransactions).values(data),
);
return NextResponse.json({