- Add lib/db (schema, postgres.js client, enums, seed, migrations) on Drizzle - Rewrite all lib and admin action queries from Prisma to Drizzle - Keep existing table/column names so no data migration is needed - Preserve signed-cookie admin auth unchanged - Map unique-violation handling from Prisma P2002 to SQLSTATE 23505 - Swap deps, scripts, Makefile, and Dockerfile from Prisma to Drizzle
This commit is contained in:
+26
-22
@@ -1,8 +1,10 @@
|
||||
import { createHash, createHmac, timingSafeEqual } from "crypto";
|
||||
import { and, eq, like, lt } from "drizzle-orm";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { prisma } from "./prisma";
|
||||
import { db } from "./db";
|
||||
import { appConfig } from "./db/schema";
|
||||
import { getAdminAppPath } from "./admin-routing";
|
||||
|
||||
export const ADMIN_SESSION_COOKIE = "moh_admin_session";
|
||||
@@ -137,11 +139,11 @@ function getLockoutKey(ip: string): string {
|
||||
async function cleanupExpiredLockouts(): Promise<void> {
|
||||
try {
|
||||
const cutoff = new Date(Date.now() - LOCKOUT_SECONDS * 2 * 1000);
|
||||
await prisma.$executeRaw`
|
||||
DELETE FROM "AppConfig"
|
||||
WHERE key LIKE ${`${ADMIN_LOCKOUT_KEY_PREFIX}:%`}
|
||||
AND "updatedAt" < ${cutoff}
|
||||
`;
|
||||
await db
|
||||
.delete(appConfig)
|
||||
.where(
|
||||
and(like(appConfig.key, `${ADMIN_LOCKOUT_KEY_PREFIX}:%`), lt(appConfig.updatedAt, cutoff)),
|
||||
);
|
||||
} catch {
|
||||
// Non-critical — ignore cleanup errors.
|
||||
}
|
||||
@@ -216,11 +218,12 @@ export async function getAdminLockState(): Promise<{ locked: boolean; remainingS
|
||||
try {
|
||||
const ip = await getClientIp();
|
||||
const key = getLockoutKey(ip);
|
||||
const config = await prisma.appConfig.findUnique({
|
||||
where: { key },
|
||||
select: { value: true },
|
||||
});
|
||||
const state = parseFailState(config?.value);
|
||||
const rows = await db
|
||||
.select({ value: appConfig.value })
|
||||
.from(appConfig)
|
||||
.where(eq(appConfig.key, key))
|
||||
.limit(1);
|
||||
const state = parseFailState(rows[0]?.value);
|
||||
const now = Date.now();
|
||||
|
||||
if (state.lockUntil > now) {
|
||||
@@ -244,23 +247,24 @@ export async function registerFailedAdminAttempt(): Promise<{ locked: boolean; r
|
||||
|
||||
await cleanupExpiredLockouts();
|
||||
|
||||
const config = await prisma.appConfig.findUnique({
|
||||
where: { key },
|
||||
select: { value: true },
|
||||
});
|
||||
const rows = await db
|
||||
.select({ value: appConfig.value })
|
||||
.from(appConfig)
|
||||
.where(eq(appConfig.key, key))
|
||||
.limit(1);
|
||||
|
||||
const current = parseFailState(config?.value);
|
||||
const current = parseFailState(rows[0]?.value);
|
||||
// If a previous lockout has expired, reset the counter.
|
||||
const baseAttempts = current.lockUntil > 0 && current.lockUntil < now ? 0 : current.attempts;
|
||||
const attempts = baseAttempts + 1;
|
||||
const locked = attempts >= MAX_FAILED_ATTEMPTS;
|
||||
const lockUntil = locked ? now + LOCKOUT_SECONDS * 1000 : 0;
|
||||
const value = JSON.stringify({ attempts, lockUntil });
|
||||
|
||||
await prisma.appConfig.upsert({
|
||||
where: { key },
|
||||
update: { value: JSON.stringify({ attempts, lockUntil }) },
|
||||
create: { key, value: JSON.stringify({ attempts, lockUntil }) },
|
||||
});
|
||||
await db
|
||||
.insert(appConfig)
|
||||
.values({ key, value })
|
||||
.onConflictDoUpdate({ target: appConfig.key, set: { value, updatedAt: new Date() } });
|
||||
|
||||
return {
|
||||
locked,
|
||||
@@ -276,7 +280,7 @@ export async function resetAdminFailedAttempts(): Promise<void> {
|
||||
try {
|
||||
const ip = await getClientIp();
|
||||
const key = getLockoutKey(ip);
|
||||
await prisma.appConfig.deleteMany({ where: { key } });
|
||||
await db.delete(appConfig).where(eq(appConfig.key, key));
|
||||
} catch {
|
||||
// Non-critical — ignore.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user