refactor(db): migrate app pages and APIs from Prisma facade to Drizzle (2)
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
7c39aef5d9
commit
2cd0863cb8
40 files changed
+1069
-726
No files matched your search
@@ -1,7 +1,8 @@
|
||||
import { asc, count } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { db, WebsiteRareValueCategories, WebsiteRareValues } from "@/lib/db";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const revalidate = 300;
|
||||
@@ -19,10 +20,18 @@ export default async function RareValuesPage() {
|
||||
const t = await getTranslations("pages.rares");
|
||||
let categories: CategoryRow[] = [];
|
||||
try {
|
||||
categories = await prisma.websiteRareValueCategories.findMany({
|
||||
orderBy: [{ priority: "asc" }, { name: "asc" }],
|
||||
select: { id: true, name: true, badge: true, priority: true },
|
||||
});
|
||||
categories = await db
|
||||
.select({
|
||||
id: WebsiteRareValueCategories.id,
|
||||
name: WebsiteRareValueCategories.name,
|
||||
badge: WebsiteRareValueCategories.badge,
|
||||
priority: WebsiteRareValueCategories.priority,
|
||||
})
|
||||
.from(WebsiteRareValueCategories)
|
||||
.orderBy(
|
||||
asc(WebsiteRareValueCategories.priority),
|
||||
asc(WebsiteRareValueCategories.name),
|
||||
);
|
||||
} catch {
|
||||
categories = [];
|
||||
}
|
||||
@@ -30,12 +39,15 @@ export default async function RareValuesPage() {
|
||||
// Per-category rare counts (grouped so we avoid N queries).
|
||||
const counts = new Map<string, number>();
|
||||
try {
|
||||
const grouped = await prisma.websiteRareValues.groupBy({
|
||||
by: ["categoryId"],
|
||||
_count: { _all: true },
|
||||
});
|
||||
const grouped = await db
|
||||
.select({
|
||||
categoryId: WebsiteRareValues.categoryId,
|
||||
value: count(),
|
||||
})
|
||||
.from(WebsiteRareValues)
|
||||
.groupBy(WebsiteRareValues.categoryId);
|
||||
for (const g of grouped) {
|
||||
counts.set(String(g.categoryId), g._count._all);
|
||||
counts.set(String(g.categoryId), Number(g.value));
|
||||
}
|
||||
} catch {
|
||||
// leave counts empty -> shown as 0
|
||||
@@ -55,7 +67,7 @@ export default async function RareValuesPage() {
|
||||
<div className="card-grid sm-2 lg-3">
|
||||
{categories.map((c) => {
|
||||
const id = String(c.id);
|
||||
const count = counts.get(id) ?? 0;
|
||||
const rareCount = counts.get(id) ?? 0;
|
||||
const badgeSrc =
|
||||
badgesBase && c.badge ? `${badgesBase}/${c.badge}.gif` : "";
|
||||
return (
|
||||
@@ -83,7 +95,7 @@ export default async function RareValuesPage() {
|
||||
<div className="content-card-head-text">
|
||||
<p className="content-card-title">{c.name}</p>
|
||||
<p className="content-card-subtitle">
|
||||
{t("rareCount", { count })}
|
||||
{t("rareCount", { count: rareCount })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, StatBlock } from "@/components/public/ui";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { db, User } from "@/lib/db";
|
||||
import RedeemForm from "./RedeemForm";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -17,10 +18,12 @@ export default async function RedeemPage() {
|
||||
// Show the signed-in user's current credit balance for context (fail soft).
|
||||
let user: { username: string; credits: number } | null = null;
|
||||
try {
|
||||
user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: { username: true, credits: true },
|
||||
});
|
||||
const [row] = await db
|
||||
.select({ username: User.username, credits: User.credits })
|
||||
.from(User)
|
||||
.where(eq(User.id, id))
|
||||
.limit(1);
|
||||
user = row ?? null;
|
||||
} catch {
|
||||
user = null;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { count, desc, eq } from "drizzle-orm";
|
||||
import { headers } from "next/headers";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
@@ -8,9 +9,9 @@ import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
import { Reveal } from "@/components/motion-reveal";
|
||||
import { ThemeSwitcher } from "@/components/theme-switcher";
|
||||
import { cached } from "@/lib/cache";
|
||||
import { db, User } from "@/lib/db";
|
||||
import { avatarImageUrl } from "@/lib/format";
|
||||
import { resolveHotelName } from "@/lib/hotel-name";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { captchaConfig } from "@/lib/services/captcha";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
@@ -32,21 +33,23 @@ export default async function RegisterPage() {
|
||||
|
||||
const [online, recentUsers, latestUsers] = await Promise.all([
|
||||
cached("online_count", 10_000, () =>
|
||||
prisma.user.count({ where: { online: "1" } }),
|
||||
db
|
||||
.select({ total: count() })
|
||||
.from(User)
|
||||
.where(eq(User.online, "1"))
|
||||
.then((rows) => rows[0]?.total ?? 0),
|
||||
).catch(() => 0),
|
||||
prisma.user
|
||||
.findMany({
|
||||
where: { online: "1" },
|
||||
select: { username: true, look: true },
|
||||
take: 8,
|
||||
})
|
||||
db
|
||||
.select({ username: User.username, look: User.look })
|
||||
.from(User)
|
||||
.where(eq(User.online, "1"))
|
||||
.limit(8)
|
||||
.catch(() => []),
|
||||
prisma.user
|
||||
.findMany({
|
||||
orderBy: { accountCreated: "desc" },
|
||||
select: { username: true, look: true },
|
||||
take: 8,
|
||||
})
|
||||
db
|
||||
.select({ username: User.username, look: User.look })
|
||||
.from(User)
|
||||
.orderBy(desc(User.accountCreated))
|
||||
.limit(8)
|
||||
.catch(() => []),
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ContentCard, EmptyState, StatBlock } from "@/components/public/ui";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { db, Rooms, User } from "@/lib/db";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
@@ -31,24 +32,25 @@ function parseRoomId(raw: string): number | null {
|
||||
async function getRoom(raw: string) {
|
||||
const id = parseRoomId(raw);
|
||||
if (id === null) return null;
|
||||
return prisma.rooms
|
||||
.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
id: true,
|
||||
ownerId: true,
|
||||
ownerName: true,
|
||||
name: true,
|
||||
description: true,
|
||||
state: true,
|
||||
users: true,
|
||||
usersMax: true,
|
||||
category: true,
|
||||
score: true,
|
||||
tags: true,
|
||||
},
|
||||
const [room] = await db
|
||||
.select({
|
||||
id: Rooms.id,
|
||||
ownerId: Rooms.ownerId,
|
||||
ownerName: Rooms.ownerName,
|
||||
name: Rooms.name,
|
||||
description: Rooms.description,
|
||||
state: Rooms.state,
|
||||
users: Rooms.users,
|
||||
usersMax: Rooms.usersMax,
|
||||
category: Rooms.category,
|
||||
score: Rooms.score,
|
||||
tags: Rooms.tags,
|
||||
})
|
||||
.catch(() => null);
|
||||
.from(Rooms)
|
||||
.where(eq(Rooms.id, id))
|
||||
.limit(1)
|
||||
.catch(() => []);
|
||||
return room ?? null;
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
@@ -83,12 +85,14 @@ export default async function RoomPage({
|
||||
// owner_name column if the account row is missing or the query degrades.
|
||||
const owner =
|
||||
room.ownerId > 0
|
||||
? await prisma.user
|
||||
.findUnique({
|
||||
where: { id: room.ownerId },
|
||||
select: { username: true },
|
||||
})
|
||||
.catch(() => null)
|
||||
? (
|
||||
await db
|
||||
.select({ username: User.username })
|
||||
.from(User)
|
||||
.where(eq(User.id, room.ownerId))
|
||||
.limit(1)
|
||||
.catch(() => [])
|
||||
)[0]
|
||||
: null;
|
||||
const ownerName = owner?.username || room.ownerName || "Unknown";
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { and, eq, like } from "drizzle-orm";
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { db, Rooms, User } from "@/lib/db";
|
||||
import { avatarImageUrl } from "@/lib/format";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const metadata: Metadata = { title: "Search" };
|
||||
@@ -43,38 +44,31 @@ export default async function SearchPage({
|
||||
);
|
||||
}
|
||||
|
||||
const users = await prisma.user
|
||||
.findMany({
|
||||
where: {
|
||||
username: { contains: query },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
look: true,
|
||||
motto: true,
|
||||
online: true,
|
||||
rank: true,
|
||||
},
|
||||
take: 30,
|
||||
const users = await db
|
||||
.select({
|
||||
id: User.id,
|
||||
username: User.username,
|
||||
look: User.look,
|
||||
motto: User.motto,
|
||||
online: User.online,
|
||||
rank: User.rank,
|
||||
})
|
||||
.from(User)
|
||||
.where(like(User.username, `%${query}%`))
|
||||
.limit(30)
|
||||
.catch(() => []);
|
||||
|
||||
const rooms = await prisma.rooms
|
||||
.findMany({
|
||||
where: {
|
||||
name: { contains: query },
|
||||
state: "open",
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
ownerName: true,
|
||||
users: true,
|
||||
usersMax: true,
|
||||
},
|
||||
take: 12,
|
||||
const rooms = await db
|
||||
.select({
|
||||
id: Rooms.id,
|
||||
name: Rooms.name,
|
||||
ownerName: Rooms.ownerName,
|
||||
users: Rooms.users,
|
||||
usersMax: Rooms.usersMax,
|
||||
})
|
||||
.from(Rooms)
|
||||
.where(and(like(Rooms.name, `%${query}%`), eq(Rooms.state, "open")))
|
||||
.limit(12)
|
||||
.catch(() => []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
@@ -11,8 +12,8 @@ import { env } from "@/env";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { LaravelEncrypter } from "@/lib/auth/laravel-encrypter";
|
||||
import { totpKeyUri } from "@/lib/auth/totp";
|
||||
import { db, User } from "@/lib/db";
|
||||
import { resolveHotelName } from "@/lib/hotel-name";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -37,14 +38,16 @@ export default async function TwoFactorPage({
|
||||
twoFactorRecoveryCodes: string | null;
|
||||
} | null = null;
|
||||
try {
|
||||
user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
twoFactorSecret: true,
|
||||
twoFactorConfirmedAt: true,
|
||||
twoFactorRecoveryCodes: true,
|
||||
},
|
||||
});
|
||||
const [row] = await db
|
||||
.select({
|
||||
twoFactorSecret: User.twoFactorSecret,
|
||||
twoFactorConfirmedAt: User.twoFactorConfirmedAt,
|
||||
twoFactorRecoveryCodes: User.twoFactorRecoveryCodes,
|
||||
})
|
||||
.from(User)
|
||||
.where(eq(User.id, id))
|
||||
.limit(1);
|
||||
user = row ?? null;
|
||||
} catch {
|
||||
user = null;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -5,8 +6,8 @@ import { getTranslations } from "next-intl/server";
|
||||
import { updateMotto } from "@/actions/user-settings";
|
||||
import { Reveal } from "@/components/motion-reveal";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db, User } from "@/lib/db";
|
||||
import { avatarImageUrl } from "@/lib/format";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -25,10 +26,17 @@ export default async function SettingsPage() {
|
||||
mail: string | null;
|
||||
} | null = null;
|
||||
try {
|
||||
user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: { username: true, motto: true, look: true, mail: true },
|
||||
});
|
||||
const [row] = await db
|
||||
.select({
|
||||
username: User.username,
|
||||
motto: User.motto,
|
||||
look: User.look,
|
||||
mail: User.mail,
|
||||
})
|
||||
.from(User)
|
||||
.where(eq(User.id, id))
|
||||
.limit(1);
|
||||
user = row ?? null;
|
||||
} catch {
|
||||
user = null;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { desc, eq, sql } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { signOutEverywhere } from "@/actions/sessions";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db, WebsiteLoginLogs } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -54,20 +55,30 @@ export default async function SessionsPage({
|
||||
const userId = Number(session.user.id);
|
||||
const { signedOutAll } = await searchParams;
|
||||
|
||||
const logins = await prisma.websiteLoginLogs
|
||||
.findMany({
|
||||
where: { userId },
|
||||
orderBy: { id: "desc" },
|
||||
take: 30,
|
||||
select: { id: true, ip: true, userAgent: true, createdAt: true },
|
||||
const logins = await db
|
||||
.select({
|
||||
id: WebsiteLoginLogs.id,
|
||||
ip: WebsiteLoginLogs.ip,
|
||||
userAgent: WebsiteLoginLogs.userAgent,
|
||||
createdAt: WebsiteLoginLogs.createdAt,
|
||||
})
|
||||
.from(WebsiteLoginLogs)
|
||||
.where(eq(WebsiteLoginLogs.userId, userId))
|
||||
.orderBy(desc(WebsiteLoginLogs.id))
|
||||
.limit(30)
|
||||
.catch(() => []);
|
||||
|
||||
type Failed = { id: number; ip_address: string; attempted_at: Date | string };
|
||||
const failed = await prisma.$queryRaw<Failed[]>`
|
||||
SELECT id, ip_address, attempted_at FROM failed_logins
|
||||
WHERE username = ${session.user.name} ORDER BY id DESC LIMIT 15
|
||||
`.catch(() => [] as Failed[]);
|
||||
let failed: Failed[] = [];
|
||||
try {
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT id, ip_address, attempted_at FROM failed_logins
|
||||
WHERE username = ${session.user.name} ORDER BY id DESC LIMIT 15
|
||||
`)) as unknown as [Failed[], unknown];
|
||||
failed = rows ?? [];
|
||||
} catch {
|
||||
failed = [];
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,8 +1,9 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, StatBlock } from "@/components/public/ui";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { db, User } from "@/lib/db";
|
||||
import {
|
||||
creditsPerUnit,
|
||||
isPayPalConfigured,
|
||||
@@ -27,10 +28,12 @@ export default async function TopUpPage({
|
||||
// Current credits balance for context (fail soft).
|
||||
let user: { username: string; credits: number } | null = null;
|
||||
try {
|
||||
user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: { username: true, credits: true },
|
||||
});
|
||||
const [row] = await db
|
||||
.select({ username: User.username, credits: User.credits })
|
||||
.from(User)
|
||||
.where(eq(User.id, id))
|
||||
.limit(1);
|
||||
user = row ?? null;
|
||||
} catch {
|
||||
user = null;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { asc, desc, eq, gte } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { db, User, WebsiteTeams } from "@/lib/db";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const revalidate = 300;
|
||||
@@ -30,30 +31,35 @@ export default async function StaffPage() {
|
||||
|
||||
let staff: StaffMember[] = [];
|
||||
try {
|
||||
staff = await prisma.user.findMany({
|
||||
where: { rank: { gte: minStaffRank } },
|
||||
select: { username: true, look: true, rank: true, motto: true },
|
||||
orderBy: [{ rank: "desc" }, { username: "asc" }],
|
||||
take: 100,
|
||||
});
|
||||
staff = await db
|
||||
.select({
|
||||
username: User.username,
|
||||
look: User.look,
|
||||
rank: User.rank,
|
||||
motto: User.motto,
|
||||
})
|
||||
.from(User)
|
||||
.where(gte(User.rank, minStaffRank))
|
||||
.orderBy(desc(User.rank), asc(User.username))
|
||||
.limit(100);
|
||||
} catch {
|
||||
staff = [];
|
||||
}
|
||||
|
||||
let teams: Team[] = [];
|
||||
try {
|
||||
const rows = await prisma.websiteTeams.findMany({
|
||||
where: { hiddenRank: false },
|
||||
select: {
|
||||
id: true,
|
||||
rankName: true,
|
||||
badge: true,
|
||||
jobDescription: true,
|
||||
staffColor: true,
|
||||
},
|
||||
orderBy: { id: "asc" },
|
||||
});
|
||||
teams = rows.map((t) => ({ ...t, id: String(t.id) }));
|
||||
const rows = await db
|
||||
.select({
|
||||
id: WebsiteTeams.id,
|
||||
rankName: WebsiteTeams.rankName,
|
||||
badge: WebsiteTeams.badge,
|
||||
jobDescription: WebsiteTeams.jobDescription,
|
||||
staffColor: WebsiteTeams.staffColor,
|
||||
})
|
||||
.from(WebsiteTeams)
|
||||
.where(eq(WebsiteTeams.hiddenRank, false))
|
||||
.orderBy(asc(WebsiteTeams.id));
|
||||
teams = rows.map((row) => ({ ...row, id: String(row.id) }));
|
||||
} catch {
|
||||
teams = [];
|
||||
}
|
||||
@@ -69,14 +75,14 @@ export default async function StaffPage() {
|
||||
subtitle={t("ranksSubtitle")}
|
||||
>
|
||||
<div className="card-grid sm-2 lg-3">
|
||||
{teams.map((t) => (
|
||||
<div key={t.id} className="card">
|
||||
<h3 style={{ margin: "0 0 0.25rem", color: t.staffColor }}>
|
||||
{t.rankName}
|
||||
{teams.map((team) => (
|
||||
<div key={team.id} className="card">
|
||||
<h3 style={{ margin: "0 0 0.25rem", color: team.staffColor }}>
|
||||
{team.rankName}
|
||||
</h3>
|
||||
{t.jobDescription ? (
|
||||
{team.jobDescription ? (
|
||||
<p className="muted" style={{ margin: 0 }}>
|
||||
{t.jobDescription}
|
||||
{team.jobDescription}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,8 +1,9 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { CheckCircle2, Clock, MailX } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { isValidVerificationToken } from "@/actions/email-verify";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { db, User } from "@/lib/db";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -88,19 +89,20 @@ export default async function VerifyPage({
|
||||
const ok = await isValidVerificationToken(normalisedEmail, token);
|
||||
if (ok) {
|
||||
try {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { mail: normalisedEmail },
|
||||
select: { id: true, mailVerified: true },
|
||||
});
|
||||
const [user] = await db
|
||||
.select({ id: User.id, mailVerified: User.mailVerified })
|
||||
.from(User)
|
||||
.where(eq(User.mail, normalisedEmail))
|
||||
.limit(1);
|
||||
if (!user) {
|
||||
status = "invalid";
|
||||
} else if (user.mailVerified === "1") {
|
||||
status = "already";
|
||||
} else {
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { mailVerified: "1" },
|
||||
});
|
||||
await db
|
||||
.update(User)
|
||||
.set({ mailVerified: "1" })
|
||||
.where(eq(User.id, user.id));
|
||||
status = "verified";
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { asc } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Achievements, db } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
type Achievement = {
|
||||
name: string;
|
||||
@@ -24,18 +25,22 @@ export default async function AdminAchievements() {
|
||||
|
||||
let achievements: Achievement[];
|
||||
try {
|
||||
achievements = await prisma.achievements.findMany({
|
||||
select: {
|
||||
name: true,
|
||||
category: true,
|
||||
level: true,
|
||||
rewardAmount: true,
|
||||
rewardType: true,
|
||||
points: true,
|
||||
progressNeeded: true,
|
||||
},
|
||||
orderBy: [{ category: "asc" }, { name: "asc" }, { level: "asc" }],
|
||||
});
|
||||
achievements = await db
|
||||
.select({
|
||||
name: Achievements.name,
|
||||
category: Achievements.category,
|
||||
level: Achievements.level,
|
||||
rewardAmount: Achievements.rewardAmount,
|
||||
rewardType: Achievements.rewardType,
|
||||
points: Achievements.points,
|
||||
progressNeeded: Achievements.progressNeeded,
|
||||
})
|
||||
.from(Achievements)
|
||||
.orderBy(
|
||||
asc(Achievements.category),
|
||||
asc(Achievements.name),
|
||||
asc(Achievements.level),
|
||||
);
|
||||
} catch {
|
||||
achievements = [];
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Megaphone } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { updateAd } from "@/actions/admin-ads";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { db, WebsiteAds } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { EditAdDeleteButton } from "../ads-table";
|
||||
|
||||
export default async function EditAd({
|
||||
@@ -19,9 +20,14 @@ export default async function EditAd({
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
let ad: Awaited<ReturnType<typeof prisma.websiteAds.findUnique>> = null;
|
||||
let ad: typeof WebsiteAds.$inferSelect | null = null;
|
||||
try {
|
||||
ad = await prisma.websiteAds.findUnique({ where: { id: BigInt(id) } });
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(WebsiteAds)
|
||||
.where(eq(WebsiteAds.id, BigInt(id)))
|
||||
.limit(1);
|
||||
ad = row ?? null;
|
||||
} catch {
|
||||
notFound();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { desc } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { db, WebsiteAds } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { AdsTable } from "./ads-table";
|
||||
|
||||
export default async function AdminAds() {
|
||||
@@ -15,12 +16,13 @@ export default async function AdminAds() {
|
||||
}
|
||||
|
||||
const t = await getTranslations("pages.admin.ads");
|
||||
let ads: Awaited<ReturnType<typeof prisma.websiteAds.findMany>>;
|
||||
let ads: (typeof WebsiteAds.$inferSelect)[] = [];
|
||||
try {
|
||||
ads = await prisma.websiteAds.findMany({
|
||||
orderBy: { id: "desc" },
|
||||
take: 500,
|
||||
});
|
||||
ads = await db
|
||||
.select()
|
||||
.from(WebsiteAds)
|
||||
.orderBy(desc(WebsiteAds.id))
|
||||
.limit(500);
|
||||
} catch {
|
||||
ads = [];
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { desc } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { sendHotelAlert } from "@/actions/admin-alerts";
|
||||
import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AlertLogs, db } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
const CRITICAL = new Set(["critical", "error", "danger"]);
|
||||
const WARNING = new Set(["warning", "warn"]);
|
||||
@@ -58,12 +59,13 @@ export default async function AdminAlerts() {
|
||||
|
||||
const t = await getTranslations("pages.admin.alerts");
|
||||
|
||||
let alerts: Awaited<ReturnType<typeof prisma.alertLogs.findMany>>;
|
||||
let alerts: (typeof AlertLogs.$inferSelect)[] = [];
|
||||
try {
|
||||
alerts = await prisma.alertLogs.findMany({
|
||||
orderBy: { id: "desc" },
|
||||
take: 100,
|
||||
});
|
||||
alerts = await db
|
||||
.select()
|
||||
.from(AlertLogs)
|
||||
.orderBy(desc(AlertLogs.id))
|
||||
.limit(100);
|
||||
} catch {
|
||||
alerts = [];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,12 +1,13 @@
|
||||
import { desc, inArray } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { dismissApplication } from "@/actions/admin-applications";
|
||||
import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { db, User, WebsiteStaffApplications } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
type ApplicationRow = {
|
||||
id: bigint;
|
||||
@@ -25,10 +26,11 @@ export default async function AdminApplications() {
|
||||
const t = await getTranslations("pages.admin.applications");
|
||||
let applications: ApplicationRow[];
|
||||
try {
|
||||
applications = await prisma.websiteStaffApplications.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 100,
|
||||
});
|
||||
applications = await db
|
||||
.select()
|
||||
.from(WebsiteStaffApplications)
|
||||
.orderBy(desc(WebsiteStaffApplications.createdAt))
|
||||
.limit(100);
|
||||
} catch {
|
||||
applications = [];
|
||||
}
|
||||
@@ -38,10 +40,10 @@ export default async function AdminApplications() {
|
||||
if (applications.length > 0) {
|
||||
try {
|
||||
const ids = Array.from(new Set(applications.map((a) => a.userId)));
|
||||
const users = await prisma.user.findMany({
|
||||
where: { id: { in: ids } },
|
||||
select: { id: true, username: true },
|
||||
});
|
||||
const users = await db
|
||||
.select({ id: User.id, username: User.username })
|
||||
.from(User)
|
||||
.where(inArray(User.id, ids));
|
||||
for (const u of users) userMap.set(u.id, u.username);
|
||||
} catch {
|
||||
// no DB — fall back to raw ids
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Newspaper } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
@@ -5,8 +6,8 @@ import { getTranslations } from "next-intl/server";
|
||||
import { deleteArticle, updateArticle } from "@/actions/admin-articles";
|
||||
import { ArticleForm } from "@/components/admin/article-form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { db, WebsiteArticles } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export default async function EditArticle({
|
||||
params,
|
||||
@@ -23,19 +24,19 @@ export default async function EditArticle({
|
||||
const { id } = await params;
|
||||
const { error } = await searchParams;
|
||||
const t = await getTranslations("pages.admin.articles");
|
||||
const article = await prisma.websiteArticles
|
||||
.findUnique({
|
||||
where: { id: BigInt(id) },
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
title: true,
|
||||
shortStory: true,
|
||||
fullStory: true,
|
||||
image: true,
|
||||
},
|
||||
const [article] = await db
|
||||
.select({
|
||||
id: WebsiteArticles.id,
|
||||
slug: WebsiteArticles.slug,
|
||||
title: WebsiteArticles.title,
|
||||
shortStory: WebsiteArticles.shortStory,
|
||||
fullStory: WebsiteArticles.fullStory,
|
||||
image: WebsiteArticles.image,
|
||||
})
|
||||
.catch(() => null);
|
||||
.from(WebsiteArticles)
|
||||
.where(eq(WebsiteArticles.id, BigInt(id)))
|
||||
.limit(1)
|
||||
.catch(() => []);
|
||||
if (!article) notFound();
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { desc, inArray } from "drizzle-orm";
|
||||
import { Newspaper } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -6,9 +7,9 @@ import { AdminPageShell } from "@/components/admin/admin-page-shell";
|
||||
import { ArticleCard } from "@/components/admin/article-card";
|
||||
import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { db, User, WebsiteArticles } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export default async function AdminArticles({
|
||||
searchParams,
|
||||
@@ -22,19 +23,18 @@ export default async function AdminArticles({
|
||||
|
||||
const { error } = await searchParams;
|
||||
const t = await getTranslations("pages.admin.articles");
|
||||
const articles = await prisma.websiteArticles
|
||||
.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
image: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
},
|
||||
take: 50,
|
||||
const articles = await db
|
||||
.select({
|
||||
id: WebsiteArticles.id,
|
||||
title: WebsiteArticles.title,
|
||||
slug: WebsiteArticles.slug,
|
||||
image: WebsiteArticles.image,
|
||||
createdAt: WebsiteArticles.createdAt,
|
||||
userId: WebsiteArticles.userId,
|
||||
})
|
||||
.from(WebsiteArticles)
|
||||
.orderBy(desc(WebsiteArticles.createdAt))
|
||||
.limit(50)
|
||||
.catch(() => []);
|
||||
|
||||
const authorMap = new Map<number, string>();
|
||||
@@ -42,11 +42,10 @@ export default async function AdminArticles({
|
||||
.map((a) => a.userId)
|
||||
.filter((id): id is number => Boolean(id));
|
||||
if (authorIds.length > 0) {
|
||||
const authors = await prisma.user
|
||||
.findMany({
|
||||
where: { id: { in: authorIds } },
|
||||
select: { id: true, username: true },
|
||||
})
|
||||
const authors = await db
|
||||
.select({ id: User.id, username: User.username })
|
||||
.from(User)
|
||||
.where(inArray(User.id, authorIds))
|
||||
.catch(() => []);
|
||||
for (const u of authors) authorMap.set(u.id, u.username);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { asc } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { uploadBadge } from "@/actions/admin-badge-upload";
|
||||
import { giveBadge } from "@/actions/admin-badges";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { db, WebsiteBadges } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const metadata = { title: "Badges" };
|
||||
|
||||
@@ -21,12 +22,13 @@ export default async function AdminBadges({
|
||||
const t = await getTranslations("pages.admin.badges");
|
||||
const { uploaded, error } = await searchParams;
|
||||
|
||||
let badges: Awaited<ReturnType<typeof prisma.websiteBadges.findMany>>;
|
||||
let badges: (typeof WebsiteBadges.$inferSelect)[] = [];
|
||||
try {
|
||||
badges = await prisma.websiteBadges.findMany({
|
||||
orderBy: { badgeName: "asc" },
|
||||
take: 500,
|
||||
});
|
||||
badges = await db
|
||||
.select()
|
||||
.from(WebsiteBadges)
|
||||
.orderBy(asc(WebsiteBadges.badgeName))
|
||||
.limit(500);
|
||||
} catch {
|
||||
badges = [];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { asc } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { db, WebsiteBanner } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { BannersManager } from "./banners-manager";
|
||||
|
||||
export default async function BannersPage() {
|
||||
@@ -9,9 +10,10 @@ export default async function BannersPage() {
|
||||
redirect("/admin");
|
||||
}
|
||||
|
||||
const banners = await prisma.websiteBanner.findMany({
|
||||
orderBy: { sortOrder: "asc" },
|
||||
});
|
||||
const banners = await db
|
||||
.select()
|
||||
.from(WebsiteBanner)
|
||||
.orderBy(asc(WebsiteBanner.sortOrder));
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { desc, eq, gt, or } from "drizzle-orm";
|
||||
import { Ban } from "lucide-react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
@@ -5,10 +6,10 @@ import { createBan, liftBan } from "@/actions/admin-bans";
|
||||
import { AdminPageShell } from "@/components/admin/admin-page-shell";
|
||||
import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { activeBanWhere, unixNow } from "@/lib/bans";
|
||||
import { unixNow } from "@/lib/bans";
|
||||
import { Ban as BanTable, db } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export default async function AdminBans() {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
@@ -18,13 +19,14 @@ export default async function AdminBans() {
|
||||
|
||||
const t = await getTranslations("pages.admin.bans");
|
||||
const now = unixNow();
|
||||
let bans: Awaited<ReturnType<typeof prisma.ban.findMany>> = [];
|
||||
let bans: (typeof BanTable.$inferSelect)[] = [];
|
||||
try {
|
||||
bans = await prisma.ban.findMany({
|
||||
where: activeBanWhere(now),
|
||||
orderBy: { timestamp: "desc" },
|
||||
take: 100,
|
||||
});
|
||||
bans = await db
|
||||
.select()
|
||||
.from(BanTable)
|
||||
.where(or(eq(BanTable.banExpire, 0), gt(BanTable.banExpire, now)))
|
||||
.orderBy(desc(BanTable.timestamp))
|
||||
.limit(100);
|
||||
} catch {
|
||||
bans = [];
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { asc, eq } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { CalendarCampaigns, CalendarRewards, db } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
type Campaign = {
|
||||
id: number;
|
||||
@@ -48,18 +49,20 @@ export default async function AdminCalendarCampaign({
|
||||
|
||||
let campaign: Campaign | null = null;
|
||||
try {
|
||||
campaign = await prisma.calendarCampaigns.findUnique({
|
||||
where: { id: campaignId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
image: true,
|
||||
startTimestamp: true,
|
||||
totalDays: true,
|
||||
lockExpired: true,
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
const [row] = await db
|
||||
.select({
|
||||
id: CalendarCampaigns.id,
|
||||
name: CalendarCampaigns.name,
|
||||
image: CalendarCampaigns.image,
|
||||
startTimestamp: CalendarCampaigns.startTimestamp,
|
||||
totalDays: CalendarCampaigns.totalDays,
|
||||
lockExpired: CalendarCampaigns.lockExpired,
|
||||
enabled: CalendarCampaigns.enabled,
|
||||
})
|
||||
.from(CalendarCampaigns)
|
||||
.where(eq(CalendarCampaigns.id, campaignId))
|
||||
.limit(1);
|
||||
campaign = row ?? null;
|
||||
} catch {
|
||||
campaign = null;
|
||||
}
|
||||
@@ -67,25 +70,25 @@ export default async function AdminCalendarCampaign({
|
||||
|
||||
let rewards: Reward[] = [];
|
||||
try {
|
||||
rewards = await prisma.calendarRewards.findMany({
|
||||
where: { campaignId },
|
||||
select: {
|
||||
id: true,
|
||||
campaignId: true,
|
||||
productName: true,
|
||||
customImage: true,
|
||||
credits: true,
|
||||
pixels: true,
|
||||
points: true,
|
||||
pointsType: true,
|
||||
badge: true,
|
||||
itemId: true,
|
||||
subscriptionType: true,
|
||||
subscriptionDays: true,
|
||||
},
|
||||
orderBy: { id: "asc" },
|
||||
take: 500,
|
||||
});
|
||||
rewards = await db
|
||||
.select({
|
||||
id: CalendarRewards.id,
|
||||
campaignId: CalendarRewards.campaignId,
|
||||
productName: CalendarRewards.productName,
|
||||
customImage: CalendarRewards.customImage,
|
||||
credits: CalendarRewards.credits,
|
||||
pixels: CalendarRewards.pixels,
|
||||
points: CalendarRewards.points,
|
||||
pointsType: CalendarRewards.pointsType,
|
||||
badge: CalendarRewards.badge,
|
||||
itemId: CalendarRewards.itemId,
|
||||
subscriptionType: CalendarRewards.subscriptionType,
|
||||
subscriptionDays: CalendarRewards.subscriptionDays,
|
||||
})
|
||||
.from(CalendarRewards)
|
||||
.where(eq(CalendarRewards.campaignId, campaignId))
|
||||
.orderBy(asc(CalendarRewards.id))
|
||||
.limit(500);
|
||||
} catch {
|
||||
rewards = [];
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { count, desc } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { CalendarCampaigns, CalendarRewards, db } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
type Campaign = {
|
||||
id: number;
|
||||
@@ -27,19 +28,19 @@ export default async function AdminCalendar() {
|
||||
let campaigns: Campaign[] = [];
|
||||
let loadError = false;
|
||||
try {
|
||||
campaigns = await prisma.calendarCampaigns.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
image: true,
|
||||
startTimestamp: true,
|
||||
totalDays: true,
|
||||
lockExpired: true,
|
||||
enabled: true,
|
||||
},
|
||||
orderBy: { id: "desc" },
|
||||
take: 200,
|
||||
});
|
||||
campaigns = await db
|
||||
.select({
|
||||
id: CalendarCampaigns.id,
|
||||
name: CalendarCampaigns.name,
|
||||
image: CalendarCampaigns.image,
|
||||
startTimestamp: CalendarCampaigns.startTimestamp,
|
||||
totalDays: CalendarCampaigns.totalDays,
|
||||
lockExpired: CalendarCampaigns.lockExpired,
|
||||
enabled: CalendarCampaigns.enabled,
|
||||
})
|
||||
.from(CalendarCampaigns)
|
||||
.orderBy(desc(CalendarCampaigns.id))
|
||||
.limit(200);
|
||||
} catch {
|
||||
campaigns = [];
|
||||
loadError = true;
|
||||
@@ -48,13 +49,16 @@ export default async function AdminCalendar() {
|
||||
const rewardCounts = new Map<number, number>();
|
||||
let totalRewards = 0;
|
||||
try {
|
||||
const grouped = await prisma.calendarRewards.groupBy({
|
||||
by: ["campaignId"],
|
||||
_count: { _all: true },
|
||||
});
|
||||
const grouped = await db
|
||||
.select({
|
||||
campaignId: CalendarRewards.campaignId,
|
||||
total: count(),
|
||||
})
|
||||
.from(CalendarRewards)
|
||||
.groupBy(CalendarRewards.campaignId);
|
||||
for (const g of grouped) {
|
||||
rewardCounts.set(g.campaignId, g._count._all);
|
||||
totalRewards += g._count._all;
|
||||
rewardCounts.set(g.campaignId, Number(g.total));
|
||||
totalRewards += Number(g.total);
|
||||
}
|
||||
} catch {
|
||||
loadError = true;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { asc, eq } from "drizzle-orm";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { Fragment } from "react";
|
||||
import { CatalogPages, db } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { loadCatalogItemsData } from "@/lib/services/catalog-items-loader";
|
||||
import { getAncestors } from "@/lib/services/catalog-tree";
|
||||
import { CatalogDetailTabs } from "./catalog-detail-tabs";
|
||||
@@ -22,18 +23,24 @@ export default async function CatalogEditPage({
|
||||
const pageId = parseInt(id, 10);
|
||||
if (Number.isNaN(pageId)) notFound();
|
||||
|
||||
const catalogPage = await prisma.catalogPages.findUnique({
|
||||
where: { id: pageId },
|
||||
});
|
||||
const [catalogPage] = await db
|
||||
.select()
|
||||
.from(CatalogPages)
|
||||
.where(eq(CatalogPages.id, pageId))
|
||||
.limit(1);
|
||||
|
||||
if (!catalogPage) notFound();
|
||||
|
||||
const [children, itemsData, ancestors] = await Promise.all([
|
||||
prisma.catalogPages.findMany({
|
||||
where: { parentId: pageId },
|
||||
orderBy: { orderNum: "asc" },
|
||||
select: { id: true, caption: true, enabled: true },
|
||||
}),
|
||||
db
|
||||
.select({
|
||||
id: CatalogPages.id,
|
||||
caption: CatalogPages.caption,
|
||||
enabled: CatalogPages.enabled,
|
||||
})
|
||||
.from(CatalogPages)
|
||||
.where(eq(CatalogPages.parentId, pageId))
|
||||
.orderBy(asc(CatalogPages.orderNum)),
|
||||
loadCatalogItemsData(pageId),
|
||||
getAncestors(catalogPage.parentId),
|
||||
]);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { asc, eq } from "drizzle-orm";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { CatalogItemsBc, CatalogPagesBc, db } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { BcPageDetail } from "../bc-manager";
|
||||
|
||||
export default async function BcPageDetailPage({
|
||||
@@ -19,13 +20,18 @@ export default async function BcPageDetailPage({
|
||||
|
||||
const canEdit = canAccess(permissions, PERMS.CATALOG_EDIT, session.user.rank);
|
||||
|
||||
const bcPage = await prisma.catalogPagesBc.findUnique({ where: { id } });
|
||||
const [bcPage] = await db
|
||||
.select()
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, id))
|
||||
.limit(1);
|
||||
if (!bcPage) notFound();
|
||||
|
||||
const bcItems = await prisma.catalogItemsBc.findMany({
|
||||
where: { pageId: id },
|
||||
orderBy: { orderNumber: "asc" },
|
||||
});
|
||||
const bcItems = await db
|
||||
.select()
|
||||
.from(CatalogItemsBc)
|
||||
.where(eq(CatalogItemsBc.pageId, id))
|
||||
.orderBy(asc(CatalogItemsBc.orderNumber));
|
||||
|
||||
return (
|
||||
<BcPageDetail
|
||||
|
||||
+184
-106
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,5 @@
|
||||
import os from "node:os";
|
||||
import { count, desc, inArray } from "drizzle-orm";
|
||||
import { Terminal } from "lucide-react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
@@ -11,9 +12,9 @@ import {
|
||||
} from "@/components/admin/dashboard";
|
||||
import { fetchOpsHealth } from "@/lib/admin/ops-health";
|
||||
import { fetchOpsOnlineUsers } from "@/lib/admin/ops-online-users";
|
||||
import { db, EmulatorErrors, StaffActivities, User } from "@/lib/db";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import { CommandocentrumControls } from "./commandocentrum-controls";
|
||||
|
||||
@@ -46,22 +47,29 @@ export default async function CommandoCentrum() {
|
||||
|
||||
const t = await getTranslations("pages.admin.commandocentrum");
|
||||
|
||||
const [totalUsers, errors, health, maintenance, activity, onlineRoster] =
|
||||
const [totalUsersRows, errors, health, maintenance, activity, onlineRoster] =
|
||||
await Promise.all([
|
||||
prisma.user.count().catch(() => -1),
|
||||
prisma.emulatorErrors
|
||||
.findMany({ orderBy: { timestamp: "desc" }, take: 50 })
|
||||
db
|
||||
.select({ total: count() })
|
||||
.from(User)
|
||||
.catch(() => [{ total: -1 }]),
|
||||
db
|
||||
.select()
|
||||
.from(EmulatorErrors)
|
||||
.orderBy(desc(EmulatorErrors.timestamp))
|
||||
.limit(50)
|
||||
.catch(() => []),
|
||||
fetchOpsHealth(),
|
||||
siteSettings.getBool("maintenance_enabled", false),
|
||||
prisma.staffActivities
|
||||
.findMany({ orderBy: { createdAt: "desc" }, take: 12 })
|
||||
.catch(
|
||||
() =>
|
||||
[] as Awaited<ReturnType<typeof prisma.staffActivities.findMany>>,
|
||||
),
|
||||
db
|
||||
.select()
|
||||
.from(StaffActivities)
|
||||
.orderBy(desc(StaffActivities.createdAt))
|
||||
.limit(12)
|
||||
.catch(() => [] as (typeof StaffActivities.$inferSelect)[]),
|
||||
fetchOpsOnlineUsers(40),
|
||||
]);
|
||||
const totalUsers = Number(totalUsersRows[0]?.total ?? -1);
|
||||
|
||||
const dbOk = health.dbOk;
|
||||
const redisOk = health.redisOk;
|
||||
@@ -74,11 +82,10 @@ export default async function CommandoCentrum() {
|
||||
).filter((n) => n > 0);
|
||||
let nameById = new Map<number, string>();
|
||||
if (staffIds.length) {
|
||||
const users = await prisma.user
|
||||
.findMany({
|
||||
where: { id: { in: staffIds } },
|
||||
select: { id: true, username: true },
|
||||
})
|
||||
const users = await db
|
||||
.select({ id: User.id, username: User.username })
|
||||
.from(User)
|
||||
.where(inArray(User.id, staffIds))
|
||||
.catch(() => []);
|
||||
nameById = new Map(users.map((u) => [u.id, u.username]));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { count, desc } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { db, EmulatorErrors } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export default async function EmulatorErrorsPage({
|
||||
searchParams,
|
||||
@@ -19,15 +20,17 @@ export default async function EmulatorErrorsPage({
|
||||
const page = Math.max(1, Number(params.page) || 1);
|
||||
const perPage = 50;
|
||||
|
||||
const [errors, total] = await Promise.all([
|
||||
prisma.emulatorErrors.findMany({
|
||||
orderBy: { id: "desc" },
|
||||
skip: (page - 1) * perPage,
|
||||
take: perPage,
|
||||
}),
|
||||
prisma.emulatorErrors.count(),
|
||||
const [errors, totalRows] = await Promise.all([
|
||||
db
|
||||
.select()
|
||||
.from(EmulatorErrors)
|
||||
.orderBy(desc(EmulatorErrors.id))
|
||||
.limit(perPage)
|
||||
.offset((page - 1) * perPage),
|
||||
db.select({ total: count() }).from(EmulatorErrors),
|
||||
]);
|
||||
|
||||
const total = Number(totalRows[0]?.total ?? 0);
|
||||
const lastPage = Math.ceil(total / perPage);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { count, desc, eq, gte } from "drizzle-orm";
|
||||
import { AlertTriangle, Database, HeartPulse, Server } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -5,35 +6,40 @@ import { getTranslations } from "next-intl/server";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { fetchOpsHealth } from "@/lib/admin/ops-health";
|
||||
import { db, EmulatorErrors, EmulatorSettings } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { HealthCheckClient } from "./health-check-client";
|
||||
|
||||
async function getDevOpsData() {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const dayAgo = now - 86400;
|
||||
|
||||
const [health, recentErrors, errorsToday, emuVersion] = await Promise.all([
|
||||
fetchOpsHealth(),
|
||||
prisma.emulatorErrors.findMany({
|
||||
orderBy: { id: "desc" },
|
||||
take: 20,
|
||||
}),
|
||||
prisma.emulatorErrors.count({
|
||||
where: { timestamp: { gte: dayAgo } },
|
||||
}),
|
||||
prisma.emulatorSettings.findFirst({
|
||||
where: { key: "version" },
|
||||
}),
|
||||
]);
|
||||
const [health, recentErrors, errorsTodayRows, emuVersionRows] =
|
||||
await Promise.all([
|
||||
fetchOpsHealth(),
|
||||
db
|
||||
.select()
|
||||
.from(EmulatorErrors)
|
||||
.orderBy(desc(EmulatorErrors.id))
|
||||
.limit(20),
|
||||
db
|
||||
.select({ total: count() })
|
||||
.from(EmulatorErrors)
|
||||
.where(gte(EmulatorErrors.timestamp, dayAgo)),
|
||||
db
|
||||
.select({ value: EmulatorSettings.value })
|
||||
.from(EmulatorSettings)
|
||||
.where(eq(EmulatorSettings.key, "version"))
|
||||
.limit(1),
|
||||
]);
|
||||
|
||||
return {
|
||||
dbStatus: health.dbOk ? ("healthy" as const) : ("error" as const),
|
||||
dbLatency: health.dbLatencyMs,
|
||||
redisOk: health.redisOk,
|
||||
recentErrors,
|
||||
errorsToday,
|
||||
emuVersion: emuVersion?.value ?? "Unknown",
|
||||
errorsToday: Number(errorsTodayRows[0]?.total ?? 0),
|
||||
emuVersion: emuVersionRows[0]?.value ?? "Unknown",
|
||||
onlineUsers: health.onlineUsers,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asc } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import {
|
||||
@@ -6,8 +7,8 @@ import {
|
||||
updateEmailTemplate,
|
||||
} from "@/actions/admin-email-templates";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { db, EmailTemplates } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export default async function AdminEmailTemplates() {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
@@ -17,12 +18,12 @@ export default async function AdminEmailTemplates() {
|
||||
|
||||
const t = await getTranslations("pages.admin.emailTemplates");
|
||||
|
||||
let templates: Awaited<ReturnType<typeof prisma.emailTemplates.findMany>> =
|
||||
[];
|
||||
let templates: (typeof EmailTemplates.$inferSelect)[] = [];
|
||||
try {
|
||||
templates = await prisma.emailTemplates.findMany({
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
templates = await db
|
||||
.select()
|
||||
.from(EmailTemplates)
|
||||
.orderBy(asc(EmailTemplates.name));
|
||||
} catch {
|
||||
templates = [];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asc, count } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import {
|
||||
@@ -6,8 +7,8 @@ import {
|
||||
} from "@/actions/admin-emulator";
|
||||
import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { db, EmulatorSettings, EmulatorTexts } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
const TEXTS_LIMIT = 300;
|
||||
|
||||
@@ -21,9 +22,10 @@ export default async function AdminEmulator() {
|
||||
|
||||
let settings: { key: string; value: string }[] = [];
|
||||
try {
|
||||
settings = await prisma.emulatorSettings.findMany({
|
||||
orderBy: { key: "asc" },
|
||||
});
|
||||
settings = await db
|
||||
.select({ key: EmulatorSettings.key, value: EmulatorSettings.value })
|
||||
.from(EmulatorSettings)
|
||||
.orderBy(asc(EmulatorSettings.key));
|
||||
} catch {
|
||||
settings = [];
|
||||
}
|
||||
@@ -31,15 +33,17 @@ export default async function AdminEmulator() {
|
||||
let texts: { key: string; value: string }[] = [];
|
||||
let textsCount = 0;
|
||||
try {
|
||||
textsCount = await prisma.emulatorTexts.count();
|
||||
const [row] = await db.select({ total: count() }).from(EmulatorTexts);
|
||||
textsCount = Number(row?.total ?? 0);
|
||||
} catch {
|
||||
textsCount = 0;
|
||||
}
|
||||
try {
|
||||
texts = await prisma.emulatorTexts.findMany({
|
||||
orderBy: { key: "asc" },
|
||||
take: TEXTS_LIMIT,
|
||||
});
|
||||
texts = await db
|
||||
.select({ key: EmulatorTexts.key, value: EmulatorTexts.value })
|
||||
.from(EmulatorTexts)
|
||||
.orderBy(asc(EmulatorTexts.key))
|
||||
.limit(TEXTS_LIMIT);
|
||||
} catch {
|
||||
texts = [];
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { asc, eq, inArray } from "drizzle-orm";
|
||||
import { CalendarDays, Gift, Trophy, Users } from "lucide-react";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { AdminBreadcrumb } from "@/components/admin/admin-breadcrumb";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { db, User, WebsiteEvent, WebsiteEventType } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { EventForm } from "../event-form";
|
||||
import { EventPrizesManager } from "./event-prizes";
|
||||
import { EventRegistrations } from "./event-registrations";
|
||||
@@ -28,22 +29,23 @@ export default async function EventDetailPage({
|
||||
const t = await getTranslations("pages.admin.events");
|
||||
const tHubs = await getTranslations("pages.admin.hubs");
|
||||
|
||||
const event = await prisma.websiteEvent.findUnique({
|
||||
where: { id: eventId },
|
||||
include: {
|
||||
const event = await db.query.WebsiteEvent.findFirst({
|
||||
where: eq(WebsiteEvent.id, eventId),
|
||||
with: {
|
||||
type: true,
|
||||
prizes: { orderBy: { position: "asc" } },
|
||||
winners: { orderBy: { position: "asc" } },
|
||||
registrations: { orderBy: { createdAt: "desc" } },
|
||||
prizes: { orderBy: (p, { asc: a }) => [a(p.position)] },
|
||||
winners: { orderBy: (w, { asc: a }) => [a(w.position)] },
|
||||
registrations: { orderBy: (r, { desc: d }) => [d(r.createdAt)] },
|
||||
},
|
||||
});
|
||||
|
||||
if (!event) notFound();
|
||||
|
||||
const eventTypes = await prisma.websiteEventType.findMany({
|
||||
where: { isActive: 1 },
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
const eventTypes = await db
|
||||
.select()
|
||||
.from(WebsiteEventType)
|
||||
.where(eq(WebsiteEventType.isActive, 1))
|
||||
.orderBy(asc(WebsiteEventType.name));
|
||||
|
||||
// Fetch usernames for registrations and winners
|
||||
const userIds = [
|
||||
@@ -52,10 +54,10 @@ export default async function EventDetailPage({
|
||||
];
|
||||
const users =
|
||||
userIds.length > 0
|
||||
? await prisma.user.findMany({
|
||||
where: { id: { in: [...new Set(userIds)] } },
|
||||
select: { id: true, username: true },
|
||||
})
|
||||
? await db
|
||||
.select({ id: User.id, username: User.username })
|
||||
.from(User)
|
||||
.where(inArray(User.id, [...new Set(userIds)]))
|
||||
: [];
|
||||
const userMap = new Map(users.map((u) => [u.id, u.username]));
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { asc, eq } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { db, WebsiteEventType } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { EventForm } from "../event-form";
|
||||
|
||||
export default async function CreateEventPage() {
|
||||
@@ -11,10 +12,11 @@ export default async function CreateEventPage() {
|
||||
}
|
||||
|
||||
const t = await getTranslations("pages.admin.events");
|
||||
const eventTypes = await prisma.websiteEventType.findMany({
|
||||
where: { isActive: 1 },
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
const eventTypes = await db
|
||||
.select()
|
||||
.from(WebsiteEventType)
|
||||
.where(eq(WebsiteEventType.isActive, 1))
|
||||
.orderBy(asc(WebsiteEventType.name));
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { asc } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { db, WebsiteEventType } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { EventTypesManager } from "./event-types-manager";
|
||||
|
||||
export default async function EventTypesPage() {
|
||||
@@ -9,9 +10,10 @@ export default async function EventTypesPage() {
|
||||
redirect("/admin/events");
|
||||
}
|
||||
|
||||
const eventTypes = await prisma.websiteEventType.findMany({
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
const eventTypes = await db
|
||||
.select()
|
||||
.from(WebsiteEventType)
|
||||
.orderBy(asc(WebsiteEventType.name));
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { db, WebsiteSetting } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { FaviconForm } from "./favicon-form";
|
||||
|
||||
export default async function AdminFaviconPage() {
|
||||
@@ -11,9 +12,11 @@ export default async function AdminFaviconPage() {
|
||||
|
||||
let faviconUrl: string | null = null;
|
||||
try {
|
||||
const setting = await prisma.websiteSetting.findUnique({
|
||||
where: { key: "cms_favicon" },
|
||||
});
|
||||
const [setting] = await db
|
||||
.select({ value: WebsiteSetting.value })
|
||||
.from(WebsiteSetting)
|
||||
.where(eq(WebsiteSetting.key, "cms_favicon"))
|
||||
.limit(1);
|
||||
faviconUrl = setting?.value ?? null;
|
||||
} catch {
|
||||
faviconUrl = null;
|
||||
|
||||
Reference in new issue
Block a user