refactor(db): migrate app pages and APIs from Prisma facade to Drizzle (3)
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
2cd0863cb8
commit
580972c0a0
40 files changed
+1075
-794
No files matched your search
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,10 +1,11 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
import { HelpCircle } from "lucide-react";
|
import { HelpCircle } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { deleteHelpQuestion, updateHelpQuestion } from "@/actions/admin-help";
|
import { deleteHelpQuestion, updateHelpQuestion } from "@/actions/admin-help";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, WebsiteHelpCenterCategories } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
export default async function EditHelpQuestion({
|
export default async function EditHelpQuestion({
|
||||||
params,
|
params,
|
||||||
@@ -20,13 +21,14 @@ export default async function EditHelpQuestion({
|
|||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const { error } = await searchParams;
|
const { error } = await searchParams;
|
||||||
let entry: Awaited<
|
let entry: typeof WebsiteHelpCenterCategories.$inferSelect | null = null;
|
||||||
ReturnType<typeof prisma.websiteHelpCenterCategories.findUnique>
|
|
||||||
> = null;
|
|
||||||
try {
|
try {
|
||||||
entry = await prisma.websiteHelpCenterCategories.findUnique({
|
const [row] = await db
|
||||||
where: { id: BigInt(id) },
|
.select()
|
||||||
});
|
.from(WebsiteHelpCenterCategories)
|
||||||
|
.where(eq(WebsiteHelpCenterCategories.id, BigInt(id)))
|
||||||
|
.limit(1);
|
||||||
|
entry = row ?? null;
|
||||||
} catch {
|
} catch {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { asc } from "drizzle-orm";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, WebsiteHelpCenterCategories } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
export default async function AdminHelpQuestions() {
|
export default async function AdminHelpQuestions() {
|
||||||
const { session, permissions } = await getAdminContext();
|
const { session, permissions } = await getAdminContext();
|
||||||
@@ -13,14 +14,16 @@ export default async function AdminHelpQuestions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.helpCenter");
|
const t = await getTranslations("pages.admin.helpCenter");
|
||||||
let entries: Awaited<
|
let entries: (typeof WebsiteHelpCenterCategories.$inferSelect)[] = [];
|
||||||
ReturnType<typeof prisma.websiteHelpCenterCategories.findMany>
|
|
||||||
> = [];
|
|
||||||
try {
|
try {
|
||||||
entries = await prisma.websiteHelpCenterCategories.findMany({
|
entries = await db
|
||||||
orderBy: [{ position: "asc" }, { id: "asc" }],
|
.select()
|
||||||
take: 500,
|
.from(WebsiteHelpCenterCategories)
|
||||||
});
|
.orderBy(
|
||||||
|
asc(WebsiteHelpCenterCategories.position),
|
||||||
|
asc(WebsiteHelpCenterCategories.id),
|
||||||
|
)
|
||||||
|
.limit(500);
|
||||||
} catch {
|
} catch {
|
||||||
entries = [];
|
entries = [];
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,8 +1,15 @@
|
|||||||
|
import { asc, eq } from "drizzle-orm";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
|
import {
|
||||||
|
AclModelPermission,
|
||||||
|
AclPermission,
|
||||||
|
AclRole,
|
||||||
|
db,
|
||||||
|
WebsiteHousekeepingPermissions,
|
||||||
|
} from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { getAuditLogs } from "@/lib/services/audit";
|
import { getAuditLogs } from "@/lib/services/audit";
|
||||||
import { AuditSection, ExportSection } from "./client";
|
import { AuditSection, ExportSection } from "./client";
|
||||||
|
|
||||||
@@ -31,12 +38,14 @@ export default async function AdminHousekeeping({
|
|||||||
const sp = await searchParams;
|
const sp = await searchParams;
|
||||||
const tab = sp.tab ?? "overview";
|
const tab = sp.tab ?? "overview";
|
||||||
|
|
||||||
type HKPerm = Awaited<
|
const allPermissions = await db
|
||||||
ReturnType<typeof prisma.websiteHousekeepingPermissions.findMany>
|
.select()
|
||||||
>[0];
|
.from(WebsiteHousekeepingPermissions)
|
||||||
const allPermissions: HKPerm[] = await prisma.websiteHousekeepingPermissions
|
.orderBy(
|
||||||
.findMany({ orderBy: [{ groupName: "asc" }, { permission: "asc" }] })
|
asc(WebsiteHousekeepingPermissions.groupName),
|
||||||
.catch(() => []);
|
asc(WebsiteHousekeepingPermissions.permission),
|
||||||
|
)
|
||||||
|
.catch((): (typeof WebsiteHousekeepingPermissions.$inferSelect)[] => []);
|
||||||
|
|
||||||
const ranks = allPermissions.map((p) => p.minRank);
|
const ranks = allPermissions.map((p) => p.minRank);
|
||||||
const maxRank = ranks.length ? Math.max(...ranks) : 0;
|
const maxRank = ranks.length ? Math.max(...ranks) : 0;
|
||||||
@@ -48,34 +57,43 @@ export default async function AdminHousekeeping({
|
|||||||
groupCounts[g] = (groupCounts[g] || 0) + 1;
|
groupCounts[g] = (groupCounts[g] || 0) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rankGroups: Record<string, typeof allPermissions> = {};
|
const rankGroups: Record<
|
||||||
|
string,
|
||||||
|
(typeof WebsiteHousekeepingPermissions.$inferSelect)[]
|
||||||
|
> = {};
|
||||||
for (const p of allPermissions) {
|
for (const p of allPermissions) {
|
||||||
if (!rankGroups[p.minRank]) rankGroups[p.minRank] = [];
|
const rk = String(p.minRank);
|
||||||
rankGroups[p.minRank].push(p);
|
if (!rankGroups[rk]) rankGroups[rk] = [];
|
||||||
|
rankGroups[rk].push(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
const aclSlugs = await prisma.aclPermission
|
const aclSlugs = await db
|
||||||
.findMany({ select: { slug: true } })
|
.select({ slug: AclPermission.slug })
|
||||||
|
.from(AclPermission)
|
||||||
.catch(() => [] as { slug: string }[]);
|
.catch(() => [] as { slug: string }[]);
|
||||||
const aclSlugSet = new Set(aclSlugs.map((p) => p.slug));
|
const aclSlugSet = new Set(aclSlugs.map((p) => p.slug));
|
||||||
|
|
||||||
const aclRolePerms = await prisma.aclModelPermission
|
const aclRolePerms = await db
|
||||||
.findMany({
|
.select({
|
||||||
where: { modelType: "Role" },
|
roleSlug: AclRole.slug,
|
||||||
include: {
|
permissionSlug: AclPermission.slug,
|
||||||
permission: { select: { slug: true } },
|
|
||||||
role: { select: { slug: true } },
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.catch(() => []);
|
.from(AclModelPermission)
|
||||||
|
.innerJoin(
|
||||||
|
AclPermission,
|
||||||
|
eq(AclModelPermission.permissionId, AclPermission.id),
|
||||||
|
)
|
||||||
|
.innerJoin(AclRole, eq(AclModelPermission.modelId, AclRole.id))
|
||||||
|
.where(eq(AclModelPermission.modelType, "Role"))
|
||||||
|
.catch((): { roleSlug: string; permissionSlug: string }[] => []);
|
||||||
|
|
||||||
const aclMinRankBySlug = new Map<string, number>();
|
const aclMinRankBySlug = new Map<string, number>();
|
||||||
for (const row of aclRolePerms) {
|
for (const row of aclRolePerms) {
|
||||||
const roleSlug = row.role.slug;
|
const roleSlug = row.roleSlug;
|
||||||
if (!roleSlug.startsWith("rank_")) continue;
|
if (!roleSlug.startsWith("rank_")) continue;
|
||||||
const rank = Number(roleSlug.slice(5));
|
const rank = Number(roleSlug.slice(5));
|
||||||
if (!Number.isFinite(rank)) continue;
|
if (!Number.isFinite(rank)) continue;
|
||||||
const slug = row.permission.slug;
|
const slug = row.permissionSlug;
|
||||||
const prev = aclMinRankBySlug.get(slug);
|
const prev = aclMinRankBySlug.get(slug);
|
||||||
if (prev === undefined || rank < prev) aclMinRankBySlug.set(slug, rank);
|
if (prev === undefined || rank < prev) aclMinRankBySlug.set(slug, rank);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { desc } from "drizzle-orm";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import {
|
import {
|
||||||
@@ -8,9 +9,9 @@ import {
|
|||||||
} from "@/actions/admin-ip";
|
} from "@/actions/admin-ip";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, WebsiteIpBlacklist, WebsiteIpWhitelist } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
type IpRow = {
|
type IpRow = {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
@@ -30,19 +31,21 @@ export default async function AdminIp() {
|
|||||||
let blacklist: IpRow[] = [];
|
let blacklist: IpRow[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
whitelist = await prisma.websiteIpWhitelist.findMany({
|
whitelist = await db
|
||||||
orderBy: { id: "desc" },
|
.select()
|
||||||
take: 200,
|
.from(WebsiteIpWhitelist)
|
||||||
});
|
.orderBy(desc(WebsiteIpWhitelist.id))
|
||||||
|
.limit(200);
|
||||||
} catch {
|
} catch {
|
||||||
whitelist = [];
|
whitelist = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
blacklist = await prisma.websiteIpBlacklist.findMany({
|
blacklist = await db
|
||||||
orderBy: { id: "desc" },
|
.select()
|
||||||
take: 200,
|
.from(WebsiteIpBlacklist)
|
||||||
});
|
.orderBy(desc(WebsiteIpBlacklist.id))
|
||||||
|
.limit(200);
|
||||||
} catch {
|
} catch {
|
||||||
blacklist = [];
|
blacklist = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
import { Package } from "lucide-react";
|
import { Package } from "lucide-react";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { AdminPageShell } from "@/components/admin/admin-page-shell";
|
import { AdminPageShell } from "@/components/admin/admin-page-shell";
|
||||||
|
import { db, ItemsBase } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { ItemsBaseEditForm } from "./items-base-edit-form";
|
import { ItemsBaseEditForm } from "./items-base-edit-form";
|
||||||
|
|
||||||
export default async function ItemsBaseDetailPage({
|
export default async function ItemsBaseDetailPage({
|
||||||
@@ -21,7 +22,11 @@ export default async function ItemsBaseDetailPage({
|
|||||||
const id = Number(rawId);
|
const id = Number(rawId);
|
||||||
if (!Number.isFinite(id) || id <= 0) notFound();
|
if (!Number.isFinite(id) || id <= 0) notFound();
|
||||||
|
|
||||||
const item = await prisma.itemsBase.findUnique({ where: { id } });
|
const [item] = await db
|
||||||
|
.select()
|
||||||
|
.from(ItemsBase)
|
||||||
|
.where(eq(ItemsBase.id, id))
|
||||||
|
.limit(1);
|
||||||
if (!item) notFound();
|
if (!item) notFound();
|
||||||
|
|
||||||
const canEdit = canAccess(permissions, PERMS.CATALOG_EDIT, session.user.rank);
|
const canEdit = canAccess(permissions, PERMS.CATALOG_EDIT, session.user.rank);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
import { LogOut } from "lucide-react";
|
import { LogOut } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
@@ -15,9 +16,9 @@ import {
|
|||||||
ADMIN_NAV_CONFIG_KEY,
|
ADMIN_NAV_CONFIG_KEY,
|
||||||
parseAdminNavConfig,
|
parseAdminNavConfig,
|
||||||
} from "@/lib/admin-nav-config";
|
} from "@/lib/admin-nav-config";
|
||||||
|
import { db, User } from "@/lib/db";
|
||||||
import { setCsrfCookie } from "@/lib/foundation/security";
|
import { setCsrfCookie } from "@/lib/foundation/security";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { siteSettings } from "@/lib/services/site-settings";
|
import { siteSettings } from "@/lib/services/site-settings";
|
||||||
|
|
||||||
// Request-time auth: requireStaff, CSRF cookie, and optional 2FA gate cannot be
|
// Request-time auth: requireStaff, CSRF cookie, and optional 2FA gate cannot be
|
||||||
@@ -37,11 +38,12 @@ export default async function AdminLayout({
|
|||||||
csrfToken = "";
|
csrfToken = "";
|
||||||
}
|
}
|
||||||
if (await siteSettings.getBool("force_staff_2fa", false)) {
|
if (await siteSettings.getBool("force_staff_2fa", false)) {
|
||||||
const u = await prisma.user
|
const u = await db
|
||||||
.findUnique({
|
.select({ twoFactorConfirmedAt: User.twoFactorConfirmedAt })
|
||||||
where: { id: staff.id },
|
.from(User)
|
||||||
select: { twoFactorConfirmedAt: true },
|
.where(eq(User.id, staff.id))
|
||||||
})
|
.limit(1)
|
||||||
|
.then((rows) => rows[0] ?? null)
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
if (!u?.twoFactorConfirmedAt) redirect("/settings/2fa?error=staffrequired");
|
if (!u?.twoFactorConfirmedAt) redirect("/settings/2fa?error=staffrequired");
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
+57
-36
File diff suppressed because it is too large.
Load diff
@@ -1,10 +1,11 @@
|
|||||||
|
import { inArray } from "drizzle-orm";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { saveMaintenance } from "@/actions/admin-maintenance";
|
import { saveMaintenance } from "@/actions/admin-maintenance";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, WebsiteSetting } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
const KEYS = [
|
const KEYS = [
|
||||||
"maintenance_enabled",
|
"maintenance_enabled",
|
||||||
@@ -22,10 +23,10 @@ export default async function AdminMaintenance() {
|
|||||||
|
|
||||||
let rows: { key: string; value: string }[] = [];
|
let rows: { key: string; value: string }[] = [];
|
||||||
try {
|
try {
|
||||||
rows = await prisma.websiteSetting.findMany({
|
rows = await db
|
||||||
where: { key: { in: [...KEYS] } },
|
.select({ key: WebsiteSetting.key, value: WebsiteSetting.value })
|
||||||
select: { key: true, value: true },
|
.from(WebsiteSetting)
|
||||||
});
|
.where(inArray(WebsiteSetting.key, [...KEYS]));
|
||||||
} catch {
|
} catch {
|
||||||
rows = [];
|
rows = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { count, desc, eq, inArray } from "drizzle-orm";
|
||||||
import { Store } from "lucide-react";
|
import { Store } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
@@ -8,9 +9,9 @@ import { AdminSimplePager } from "@/components/admin/admin-simple-pager";
|
|||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||||
|
import { db, MarketplaceItems, User } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
export default async function AdminMarketplacePage({
|
export default async function AdminMarketplacePage({
|
||||||
searchParams,
|
searchParams,
|
||||||
@@ -28,27 +29,37 @@ export default async function AdminMarketplacePage({
|
|||||||
const parsed = parseListParams(new URLSearchParams(raw));
|
const parsed = parseListParams(new URLSearchParams(raw));
|
||||||
const filter = raw.filter === "sold" ? "sold" : "active";
|
const filter = raw.filter === "sold" ? "sold" : "active";
|
||||||
|
|
||||||
const where = filter === "sold" ? { state: 2 } : { state: 1 };
|
const where = eq(MarketplaceItems.state, filter === "sold" ? 2 : 1);
|
||||||
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let rows: Awaited<ReturnType<typeof prisma.marketplaceItems.findMany>> = [];
|
let rows: (typeof MarketplaceItems.$inferSelect)[] = [];
|
||||||
let activeCount = 0;
|
let activeCount = 0;
|
||||||
let soldCount = 0;
|
let soldCount = 0;
|
||||||
let loadError = false;
|
let loadError = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[total, activeCount, soldCount] = await Promise.all([
|
const [totalRows, activeRows, soldRows] = await Promise.all([
|
||||||
prisma.marketplaceItems.count({ where }),
|
db.select({ total: count() }).from(MarketplaceItems).where(where),
|
||||||
prisma.marketplaceItems.count({ where: { state: 1 } }),
|
db
|
||||||
prisma.marketplaceItems.count({ where: { state: 2 } }),
|
.select({ total: count() })
|
||||||
|
.from(MarketplaceItems)
|
||||||
|
.where(eq(MarketplaceItems.state, 1)),
|
||||||
|
db
|
||||||
|
.select({ total: count() })
|
||||||
|
.from(MarketplaceItems)
|
||||||
|
.where(eq(MarketplaceItems.state, 2)),
|
||||||
]);
|
]);
|
||||||
|
total = Number(totalRows[0]?.total ?? 0);
|
||||||
|
activeCount = Number(activeRows[0]?.total ?? 0);
|
||||||
|
soldCount = Number(soldRows[0]?.total ?? 0);
|
||||||
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
||||||
rows = await prisma.marketplaceItems.findMany({
|
rows = await db
|
||||||
where,
|
.select()
|
||||||
orderBy: { timestamp: "desc" },
|
.from(MarketplaceItems)
|
||||||
skip: pagination.offset,
|
.where(where)
|
||||||
take: pagination.perPage,
|
.orderBy(desc(MarketplaceItems.timestamp))
|
||||||
});
|
.limit(pagination.perPage)
|
||||||
|
.offset(pagination.offset);
|
||||||
} catch {
|
} catch {
|
||||||
loadError = true;
|
loadError = true;
|
||||||
}
|
}
|
||||||
@@ -59,11 +70,10 @@ export default async function AdminMarketplacePage({
|
|||||||
];
|
];
|
||||||
const users =
|
const users =
|
||||||
userIds.length > 0
|
userIds.length > 0
|
||||||
? await prisma.user
|
? await db
|
||||||
.findMany({
|
.select({ id: User.id, username: User.username })
|
||||||
where: { id: { in: userIds } },
|
.from(User)
|
||||||
select: { id: true, username: true },
|
.where(inArray(User.id, userIds))
|
||||||
})
|
|
||||||
.catch(() => [])
|
.catch(() => [])
|
||||||
: [];
|
: [];
|
||||||
const usernameById = new Map(users.map((u) => [u.id, u.username]));
|
const usernameById = new Map(users.map((u) => [u.id, u.username]));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { count, desc, eq, inArray } from "drizzle-orm";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
import { Ban, db, SupportTickets, User } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { CfhDetail } from "./cfh-detail";
|
import { CfhDetail } from "./cfh-detail";
|
||||||
|
|
||||||
export default async function CfhDetailPage({
|
export default async function CfhDetailPage({
|
||||||
@@ -16,9 +17,11 @@ export default async function CfhDetailPage({
|
|||||||
const ticketId = Number(id);
|
const ticketId = Number(id);
|
||||||
if (Number.isNaN(ticketId)) notFound();
|
if (Number.isNaN(ticketId)) notFound();
|
||||||
|
|
||||||
const ticket = await prisma.supportTickets.findUnique({
|
const [ticket] = await db
|
||||||
where: { id: ticketId },
|
.select()
|
||||||
});
|
.from(SupportTickets)
|
||||||
|
.where(eq(SupportTickets.id, ticketId))
|
||||||
|
.limit(1);
|
||||||
if (!ticket) notFound();
|
if (!ticket) notFound();
|
||||||
|
|
||||||
const canEdit = canAccess(
|
const canEdit = canAccess(
|
||||||
@@ -30,36 +33,42 @@ export default async function CfhDetailPage({
|
|||||||
// Fetch sender, reported, mod info
|
// Fetch sender, reported, mod info
|
||||||
const userIds = [ticket.senderId, ticket.reportedId, ticket.modId].filter(
|
const userIds = [ticket.senderId, ticket.reportedId, ticket.modId].filter(
|
||||||
Boolean,
|
Boolean,
|
||||||
);
|
) as number[];
|
||||||
const users =
|
const users =
|
||||||
userIds.length > 0
|
userIds.length > 0
|
||||||
? await prisma.user.findMany({
|
? await db
|
||||||
where: { id: { in: userIds } },
|
.select({
|
||||||
select: {
|
id: User.id,
|
||||||
id: true,
|
username: User.username,
|
||||||
username: true,
|
look: User.look,
|
||||||
look: true,
|
rank: User.rank,
|
||||||
rank: true,
|
online: User.online,
|
||||||
online: true,
|
ipRegister: User.ipRegister,
|
||||||
ipRegister: true,
|
mail: User.mail,
|
||||||
mail: true,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
.from(User)
|
||||||
|
.where(inArray(User.id, userIds))
|
||||||
: [];
|
: [];
|
||||||
const userMap = Object.fromEntries(users.map((u) => [u.id, u]));
|
const userMap = Object.fromEntries(users.map((u) => [u.id, u]));
|
||||||
|
|
||||||
// Get ban/mute history for reported user
|
// Get ban/mute history for reported user
|
||||||
const reportedId = ticket.reportedId;
|
const reportedId = ticket.reportedId;
|
||||||
const [banCount, recentBans] = reportedId
|
const [banCountRows, recentBans] = reportedId
|
||||||
? await Promise.all([
|
? await Promise.all([
|
||||||
prisma.ban.count({ where: { userId: reportedId } }),
|
db
|
||||||
prisma.ban.findMany({
|
.select({ total: count() })
|
||||||
where: { userId: reportedId },
|
.from(Ban)
|
||||||
orderBy: { id: "desc" },
|
.where(eq(Ban.userId, reportedId)),
|
||||||
take: 5,
|
db
|
||||||
}),
|
.select()
|
||||||
|
.from(Ban)
|
||||||
|
.where(eq(Ban.userId, reportedId))
|
||||||
|
.orderBy(desc(Ban.id))
|
||||||
|
.limit(5),
|
||||||
])
|
])
|
||||||
: [0, []];
|
: [[{ total: 0 }], []];
|
||||||
|
|
||||||
|
const banCount = Number(banCountRows[0]?.total ?? 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CfhDetail
|
<CfhDetail
|
||||||
|
|||||||
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
File diff suppressed because it is too large.
Load diff
+39
-27
@@ -1,3 +1,4 @@
|
|||||||
|
import { count, desc, eq, gt, inArray, or } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
Activity,
|
Activity,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
@@ -9,57 +10,68 @@ import {
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { activeBanWhere } from "@/lib/bans";
|
import { unixNow } from "@/lib/bans";
|
||||||
|
import {
|
||||||
|
Ban as BanTable,
|
||||||
|
db,
|
||||||
|
StaffActivities,
|
||||||
|
User,
|
||||||
|
WebsiteArticles,
|
||||||
|
} from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
type RankCount = { rank: number; count: number };
|
type RankCount = { rank: number; count: number };
|
||||||
|
|
||||||
export default async function AdminDashboard() {
|
export default async function AdminDashboard() {
|
||||||
const t = await getTranslations("pages.admin.dashboard");
|
const t = await getTranslations("pages.admin.dashboard");
|
||||||
|
const now = unixNow();
|
||||||
let users = -1;
|
let users = -1;
|
||||||
let online = 0;
|
let online = 0;
|
||||||
let articles = 0;
|
let articles = 0;
|
||||||
let bans = 0;
|
let bans = 0;
|
||||||
try {
|
try {
|
||||||
[users, online, articles, bans] = await Promise.all([
|
const [usersRow, onlineRow, articlesRow, bansRow] = await Promise.all([
|
||||||
prisma.user.count(),
|
db.select({ total: count() }).from(User),
|
||||||
prisma.user.count({ where: { online: "1" } }),
|
db.select({ total: count() }).from(User).where(eq(User.online, "1")),
|
||||||
prisma.websiteArticles.count(),
|
db.select({ total: count() }).from(WebsiteArticles),
|
||||||
prisma.ban.count({
|
db
|
||||||
where: activeBanWhere(),
|
.select({ total: count() })
|
||||||
}),
|
.from(BanTable)
|
||||||
|
.where(or(eq(BanTable.banExpire, 0), gt(BanTable.banExpire, now))),
|
||||||
]);
|
]);
|
||||||
|
users = usersRow[0]?.total ?? 0;
|
||||||
|
online = onlineRow[0]?.total ?? 0;
|
||||||
|
articles = articlesRow[0]?.total ?? 0;
|
||||||
|
bans = bansRow[0]?.total ?? 0;
|
||||||
} catch {}
|
} catch {}
|
||||||
const dbOk = users >= 0;
|
const dbOk = users >= 0;
|
||||||
|
|
||||||
const rankGroups = await prisma.user
|
const rankGroups = await db
|
||||||
.groupBy({
|
.select({ rank: User.rank, total: count() })
|
||||||
by: ["rank"],
|
.from(User)
|
||||||
_count: { _all: true },
|
.groupBy(User.rank)
|
||||||
orderBy: { rank: "desc" },
|
.orderBy(desc(User.rank))
|
||||||
})
|
.catch(() => [] as Array<{ rank: number; total: number }>);
|
||||||
.catch(() => [] as Array<{ rank: number; _count: { _all: number } }>);
|
|
||||||
const rankCounts: RankCount[] = rankGroups
|
const rankCounts: RankCount[] = rankGroups
|
||||||
.map((g) => ({ rank: g.rank, count: g._count._all }))
|
.map((g) => ({ rank: g.rank, count: Number(g.total) }))
|
||||||
.sort((a, b) => b.count - a.count);
|
.sort((a, b) => b.count - a.count);
|
||||||
const maxRankCount = rankCounts.reduce((m, r) => Math.max(m, r.count), 0);
|
const maxRankCount = rankCounts.reduce((m, r) => Math.max(m, r.count), 0);
|
||||||
|
|
||||||
const activity = await prisma.staffActivities
|
const activity = await db
|
||||||
.findMany({ orderBy: { createdAt: "desc" }, take: 8 })
|
.select()
|
||||||
.catch(
|
.from(StaffActivities)
|
||||||
() => [] as Awaited<ReturnType<typeof prisma.staffActivities.findMany>>,
|
.orderBy(desc(StaffActivities.createdAt))
|
||||||
);
|
.limit(8)
|
||||||
|
.catch(() => [] as (typeof StaffActivities.$inferSelect)[]);
|
||||||
const staffIds = Array.from(
|
const staffIds = Array.from(
|
||||||
new Set(activity.map((a) => Number(a.userId))),
|
new Set(activity.map((a) => Number(a.userId))),
|
||||||
).filter((n) => n > 0);
|
).filter((n) => n > 0);
|
||||||
let nameById = new Map<number, string>();
|
let nameById = new Map<number, string>();
|
||||||
if (staffIds.length) {
|
if (staffIds.length) {
|
||||||
const u = await prisma.user
|
const u = await db
|
||||||
.findMany({
|
.select({ id: User.id, username: User.username })
|
||||||
where: { id: { in: staffIds } },
|
.from(User)
|
||||||
select: { id: true, username: true },
|
.where(inArray(User.id, staffIds))
|
||||||
})
|
|
||||||
.catch(() => []);
|
.catch(() => []);
|
||||||
nameById = new Map(u.map((x) => [x.id, x.username]));
|
nameById = new Map(u.map((x) => [x.id, x.username]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { count, desc, inArray } from "drizzle-orm";
|
||||||
import { Camera } from "lucide-react";
|
import { Camera } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
@@ -7,8 +8,8 @@ import { AdminPageShell } from "@/components/admin/admin-page-shell";
|
|||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||||
|
import { CameraWeb, db, User } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
export default async function AdminPhotos({
|
export default async function AdminPhotos({
|
||||||
searchParams,
|
searchParams,
|
||||||
@@ -23,16 +24,21 @@ export default async function AdminPhotos({
|
|||||||
const t = await getTranslations("pages.admin.photos");
|
const t = await getTranslations("pages.admin.photos");
|
||||||
const parsed = parseListParams(new URLSearchParams(await searchParams));
|
const parsed = parseListParams(new URLSearchParams(await searchParams));
|
||||||
|
|
||||||
const total = await prisma.cameraWeb.count().catch(() => 0);
|
const [totalRow] = await db
|
||||||
|
.select({ total: count() })
|
||||||
|
.from(CameraWeb)
|
||||||
|
.catch(() => [{ total: 0 }]);
|
||||||
|
const total = Number(totalRow?.total ?? 0);
|
||||||
const pagination = calcPagination(total, parsed.page, parsed.perPage || 24);
|
const pagination = calcPagination(total, parsed.page, parsed.perPage || 24);
|
||||||
|
|
||||||
let photos: Awaited<ReturnType<typeof prisma.cameraWeb.findMany>> = [];
|
let photos: (typeof CameraWeb.$inferSelect)[] = [];
|
||||||
try {
|
try {
|
||||||
photos = await prisma.cameraWeb.findMany({
|
photos = await db
|
||||||
orderBy: { timestamp: "desc" },
|
.select()
|
||||||
skip: pagination.offset,
|
.from(CameraWeb)
|
||||||
take: pagination.perPage,
|
.orderBy(desc(CameraWeb.timestamp))
|
||||||
});
|
.limit(pagination.perPage)
|
||||||
|
.offset(pagination.offset);
|
||||||
} catch {
|
} catch {
|
||||||
photos = [];
|
photos = [];
|
||||||
}
|
}
|
||||||
@@ -40,11 +46,10 @@ export default async function AdminPhotos({
|
|||||||
const userIds = [...new Set(photos.map((p) => p.userId))];
|
const userIds = [...new Set(photos.map((p) => p.userId))];
|
||||||
const users =
|
const users =
|
||||||
userIds.length > 0
|
userIds.length > 0
|
||||||
? await prisma.user
|
? await db
|
||||||
.findMany({
|
.select({ id: User.id, username: User.username })
|
||||||
where: { id: { in: userIds } },
|
.from(User)
|
||||||
select: { id: true, username: true },
|
.where(inArray(User.id, userIds))
|
||||||
})
|
|
||||||
.catch(() => [])
|
.catch(() => [])
|
||||||
: [];
|
: [];
|
||||||
const usernameById = new Map(users.map((u) => [u.id, u.username]));
|
const usernameById = new Map(users.map((u) => [u.id, u.username]));
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { AdminBreadcrumb } from "@/components/admin/admin-breadcrumb";
|
import { AdminBreadcrumb } from "@/components/admin/admin-breadcrumb";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { db, WebsitePoll } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { PollForm } from "../poll-form";
|
import { PollForm } from "../poll-form";
|
||||||
import { PollQuestionsManager } from "./poll-questions";
|
import { PollQuestionsManager } from "./poll-questions";
|
||||||
import { PollResultsView } from "./poll-results";
|
import { PollResultsView } from "./poll-results";
|
||||||
@@ -26,12 +27,12 @@ export default async function PollDetailPage({
|
|||||||
const t = await getTranslations("pages.admin.polls");
|
const t = await getTranslations("pages.admin.polls");
|
||||||
const tHubs = await getTranslations("pages.admin.hubs");
|
const tHubs = await getTranslations("pages.admin.hubs");
|
||||||
|
|
||||||
const poll = await prisma.websitePoll.findUnique({
|
const poll = await db.query.WebsitePoll.findFirst({
|
||||||
where: { id: pollId },
|
where: eq(WebsitePoll.id, pollId),
|
||||||
include: {
|
with: {
|
||||||
questions: {
|
questions: {
|
||||||
orderBy: { sortOrder: "asc" },
|
orderBy: (q, { asc }) => [asc(q.sortOrder)],
|
||||||
include: {
|
with: {
|
||||||
votes: true,
|
votes: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { desc } from "drizzle-orm";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import {
|
import {
|
||||||
createApiKey,
|
createApiKey,
|
||||||
@@ -6,8 +7,8 @@ import {
|
|||||||
} from "@/actions/admin-radio-api-keys";
|
} from "@/actions/admin-radio-api-keys";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, RadioApiKeys } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
export const metadata = { title: "Radio API Keys" };
|
export const metadata = { title: "Radio API Keys" };
|
||||||
|
|
||||||
@@ -34,22 +35,21 @@ export default async function AdminRadioApiKeysPage() {
|
|||||||
let dbError = false;
|
let dbError = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
keys = await prisma.radioApiKeys
|
keys = await db
|
||||||
.findMany({
|
.select({
|
||||||
select: {
|
id: RadioApiKeys.id,
|
||||||
id: true,
|
name: RadioApiKeys.name,
|
||||||
name: true,
|
key: RadioApiKeys.key,
|
||||||
key: true,
|
allowedIps: RadioApiKeys.allowedIps,
|
||||||
allowedIps: true,
|
rateLimit: RadioApiKeys.rateLimit,
|
||||||
rateLimit: true,
|
lastUsedAt: RadioApiKeys.lastUsedAt,
|
||||||
lastUsedAt: true,
|
expiresAt: RadioApiKeys.expiresAt,
|
||||||
expiresAt: true,
|
isActive: RadioApiKeys.isActive,
|
||||||
isActive: true,
|
createdAt: RadioApiKeys.createdAt,
|
||||||
createdAt: true,
|
|
||||||
},
|
|
||||||
orderBy: [{ isActive: "desc" }, { id: "desc" }],
|
|
||||||
take: 500,
|
|
||||||
})
|
})
|
||||||
|
.from(RadioApiKeys)
|
||||||
|
.orderBy(desc(RadioApiKeys.isActive), desc(RadioApiKeys.id))
|
||||||
|
.limit(500)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
dbError = true;
|
dbError = true;
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { asc } from "drizzle-orm";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import {
|
import {
|
||||||
@@ -7,8 +8,8 @@ import {
|
|||||||
} from "@/actions/admin-radio-autodj";
|
} from "@/actions/admin-radio-autodj";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, RadioAutoDjPlaylist } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
export const metadata = { title: "Radio AutoDJ" };
|
export const metadata = { title: "Radio AutoDJ" };
|
||||||
|
|
||||||
@@ -39,22 +40,24 @@ export default async function AdminRadioAutoDjPage() {
|
|||||||
let dbError = false;
|
let dbError = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tracks = await prisma.radioAutoDjPlaylist
|
tracks = await db
|
||||||
.findMany({
|
.select({
|
||||||
select: {
|
id: RadioAutoDjPlaylist.id,
|
||||||
id: true,
|
title: RadioAutoDjPlaylist.title,
|
||||||
title: true,
|
artist: RadioAutoDjPlaylist.artist,
|
||||||
artist: true,
|
album: RadioAutoDjPlaylist.album,
|
||||||
album: true,
|
artworkUrl: RadioAutoDjPlaylist.artworkUrl,
|
||||||
artworkUrl: true,
|
duration: RadioAutoDjPlaylist.duration,
|
||||||
duration: true,
|
playCount: RadioAutoDjPlaylist.playCount,
|
||||||
playCount: true,
|
lastPlayedAt: RadioAutoDjPlaylist.lastPlayedAt,
|
||||||
lastPlayedAt: true,
|
isActive: RadioAutoDjPlaylist.isActive,
|
||||||
isActive: true,
|
sortOrder: RadioAutoDjPlaylist.sortOrder,
|
||||||
sortOrder: true,
|
|
||||||
},
|
|
||||||
orderBy: [{ sortOrder: "asc" }, { title: "asc" }],
|
|
||||||
})
|
})
|
||||||
|
.from(RadioAutoDjPlaylist)
|
||||||
|
.orderBy(
|
||||||
|
asc(RadioAutoDjPlaylist.sortOrder),
|
||||||
|
asc(RadioAutoDjPlaylist.title),
|
||||||
|
)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
dbError = true;
|
dbError = true;
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { asc } from "drizzle-orm";
|
||||||
import NextImage from "next/image";
|
import NextImage from "next/image";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import {
|
import {
|
||||||
@@ -7,8 +8,8 @@ import {
|
|||||||
} from "@/actions/admin-radio-extra";
|
} from "@/actions/admin-radio-extra";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, RadioBanners } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
type Banner = {
|
type Banner = {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
@@ -28,19 +29,19 @@ export default async function AdminRadioBannersPage() {
|
|||||||
let dbError = false;
|
let dbError = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
banners = await prisma.radioBanners.findMany({
|
banners = await db
|
||||||
select: {
|
.select({
|
||||||
id: true,
|
id: RadioBanners.id,
|
||||||
userId: true,
|
userId: RadioBanners.userId,
|
||||||
imagePath: true,
|
imagePath: RadioBanners.imagePath,
|
||||||
title: true,
|
title: RadioBanners.title,
|
||||||
description: true,
|
description: RadioBanners.description,
|
||||||
sortOrder: true,
|
sortOrder: RadioBanners.sortOrder,
|
||||||
isActive: true,
|
isActive: RadioBanners.isActive,
|
||||||
createdAt: true,
|
createdAt: RadioBanners.createdAt,
|
||||||
},
|
})
|
||||||
orderBy: [{ sortOrder: "asc" }, { id: "asc" }],
|
.from(RadioBanners)
|
||||||
});
|
.orderBy(asc(RadioBanners.sortOrder), asc(RadioBanners.id));
|
||||||
} catch {
|
} catch {
|
||||||
dbError = true;
|
dbError = true;
|
||||||
banners = [];
|
banners = [];
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { desc, inArray } from "drizzle-orm";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
|
import { db, RadioHistory, User } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
type History = {
|
type History = {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
@@ -31,28 +32,28 @@ export default async function AdminRadioHistoryPage() {
|
|||||||
let dbError = false;
|
let dbError = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
history = await prisma.radioHistory.findMany({
|
history = await db
|
||||||
select: {
|
.select({
|
||||||
id: true,
|
id: RadioHistory.id,
|
||||||
userId: true,
|
userId: RadioHistory.userId,
|
||||||
showName: true,
|
showName: RadioHistory.showName,
|
||||||
startedAt: true,
|
startedAt: RadioHistory.startedAt,
|
||||||
endedAt: true,
|
endedAt: RadioHistory.endedAt,
|
||||||
listenersCount: true,
|
listenersCount: RadioHistory.listenersCount,
|
||||||
notes: true,
|
notes: RadioHistory.notes,
|
||||||
},
|
})
|
||||||
orderBy: { startedAt: "desc" },
|
.from(RadioHistory)
|
||||||
take: 100,
|
.orderBy(desc(RadioHistory.startedAt))
|
||||||
});
|
.limit(100);
|
||||||
|
|
||||||
const ids = Array.from(
|
const ids = Array.from(
|
||||||
new Set(history.map((h) => Number(h.userId))),
|
new Set(history.map((h) => Number(h.userId))),
|
||||||
).filter((n) => Number.isFinite(n) && n > 0);
|
).filter((n) => Number.isFinite(n) && n > 0);
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
const users = await prisma.user.findMany({
|
const users = await db
|
||||||
where: { id: { in: ids } },
|
.select({ id: User.id, username: User.username })
|
||||||
select: { id: true, username: true },
|
.from(User)
|
||||||
});
|
.where(inArray(User.id, ids));
|
||||||
names = new Map(users.map((u) => [String(u.id), u.username]));
|
names = new Map(users.map((u) => [String(u.id), u.username]));
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { desc } from "drizzle-orm";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { deleteShout } from "@/actions/admin-radio-moderation";
|
import { deleteShout } from "@/actions/admin-radio-moderation";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, RadioShouts } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
type Shout = {
|
type Shout = {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
@@ -18,17 +19,17 @@ export default async function AdminRadioModerationPage() {
|
|||||||
|
|
||||||
let shouts: Shout[] = [];
|
let shouts: Shout[] = [];
|
||||||
try {
|
try {
|
||||||
shouts = await prisma.radioShouts.findMany({
|
shouts = await db
|
||||||
select: {
|
.select({
|
||||||
id: true,
|
id: RadioShouts.id,
|
||||||
userId: true,
|
userId: RadioShouts.userId,
|
||||||
message: true,
|
message: RadioShouts.message,
|
||||||
createdAt: true,
|
createdAt: RadioShouts.createdAt,
|
||||||
updatedAt: true,
|
updatedAt: RadioShouts.updatedAt,
|
||||||
},
|
})
|
||||||
orderBy: { id: "desc" },
|
.from(RadioShouts)
|
||||||
take: 50,
|
.orderBy(desc(RadioShouts.id))
|
||||||
});
|
.limit(50);
|
||||||
} catch {
|
} catch {
|
||||||
shouts = [];
|
shouts = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { desc } from "drizzle-orm";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { deleteShout } from "@/actions/admin-radio-moderation";
|
import { deleteShout } from "@/actions/admin-radio-moderation";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, RadioApplications, RadioSchedules, RadioShouts } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
type Shout = {
|
type Shout = {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
@@ -52,48 +53,53 @@ export default async function AdminRadioPage() {
|
|||||||
let schedules: Schedule[] = [];
|
let schedules: Schedule[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shouts = await prisma.radioShouts.findMany({
|
shouts = await db
|
||||||
select: { id: true, userId: true, message: true, createdAt: true },
|
.select({
|
||||||
orderBy: { id: "desc" },
|
id: RadioShouts.id,
|
||||||
take: 25,
|
userId: RadioShouts.userId,
|
||||||
});
|
message: RadioShouts.message,
|
||||||
|
createdAt: RadioShouts.createdAt,
|
||||||
|
})
|
||||||
|
.from(RadioShouts)
|
||||||
|
.orderBy(desc(RadioShouts.id))
|
||||||
|
.limit(25);
|
||||||
} catch {
|
} catch {
|
||||||
shouts = [];
|
shouts = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
applications = await prisma.radioApplications.findMany({
|
applications = await db
|
||||||
select: {
|
.select({
|
||||||
id: true,
|
id: RadioApplications.id,
|
||||||
userId: true,
|
userId: RadioApplications.userId,
|
||||||
realName: true,
|
realName: RadioApplications.realName,
|
||||||
age: true,
|
age: RadioApplications.age,
|
||||||
status: true,
|
status: RadioApplications.status,
|
||||||
musicStyle: true,
|
musicStyle: RadioApplications.musicStyle,
|
||||||
discordUsername: true,
|
discordUsername: RadioApplications.discordUsername,
|
||||||
createdAt: true,
|
createdAt: RadioApplications.createdAt,
|
||||||
},
|
})
|
||||||
orderBy: { id: "desc" },
|
.from(RadioApplications)
|
||||||
take: 25,
|
.orderBy(desc(RadioApplications.id))
|
||||||
});
|
.limit(25);
|
||||||
} catch {
|
} catch {
|
||||||
applications = [];
|
applications = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
schedules = await prisma.radioSchedules.findMany({
|
schedules = await db
|
||||||
select: {
|
.select({
|
||||||
id: true,
|
id: RadioSchedules.id,
|
||||||
userId: true,
|
userId: RadioSchedules.userId,
|
||||||
day: true,
|
day: RadioSchedules.day,
|
||||||
startTime: true,
|
startTime: RadioSchedules.startTime,
|
||||||
endTime: true,
|
endTime: RadioSchedules.endTime,
|
||||||
showName: true,
|
showName: RadioSchedules.showName,
|
||||||
isActive: true,
|
isActive: RadioSchedules.isActive,
|
||||||
},
|
})
|
||||||
orderBy: { id: "desc" },
|
.from(RadioSchedules)
|
||||||
take: 25,
|
.orderBy(desc(RadioSchedules.id))
|
||||||
});
|
.limit(25);
|
||||||
} catch {
|
} catch {
|
||||||
schedules = [];
|
schedules = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { asc } from "drizzle-orm";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import {
|
import {
|
||||||
createRadioRank,
|
createRadioRank,
|
||||||
@@ -6,7 +7,7 @@ import {
|
|||||||
} from "@/actions/admin-radio-extra";
|
} from "@/actions/admin-radio-extra";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { db, RadioRanks } from "@/lib/db";
|
||||||
|
|
||||||
type Rank = {
|
type Rank = {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
@@ -23,16 +24,16 @@ export default async function AdminRadioRanksPage() {
|
|||||||
let dbError = false;
|
let dbError = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ranks = await prisma.radioRanks.findMany({
|
ranks = await db
|
||||||
select: {
|
.select({
|
||||||
id: true,
|
id: RadioRanks.id,
|
||||||
name: true,
|
name: RadioRanks.name,
|
||||||
description: true,
|
description: RadioRanks.description,
|
||||||
badgeCode: true,
|
badgeCode: RadioRanks.badgeCode,
|
||||||
isActive: true,
|
isActive: RadioRanks.isActive,
|
||||||
},
|
})
|
||||||
orderBy: { id: "asc" },
|
.from(RadioRanks)
|
||||||
});
|
.orderBy(asc(RadioRanks.id));
|
||||||
} catch {
|
} catch {
|
||||||
dbError = true;
|
dbError = true;
|
||||||
ranks = [];
|
ranks = [];
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
} from "@/actions/admin-radio-extra";
|
} from "@/actions/admin-radio-extra";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { db, WebsiteSetting } from "@/lib/db";
|
||||||
|
|
||||||
type Field = { key: string; comment: string };
|
type Field = { key: string; comment: string };
|
||||||
type Group = { title: string; fields: Field[] };
|
type Group = { title: string; fields: Field[] };
|
||||||
@@ -364,9 +364,13 @@ export default async function AdminRadioSettingsPage() {
|
|||||||
let extra: Field[] = [];
|
let extra: Field[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rows = await prisma.websiteSetting.findMany({
|
const rows = await db
|
||||||
select: { key: true, value: true, comment: true },
|
.select({
|
||||||
});
|
key: WebsiteSetting.key,
|
||||||
|
value: WebsiteSetting.value,
|
||||||
|
comment: WebsiteSetting.comment,
|
||||||
|
})
|
||||||
|
.from(WebsiteSetting);
|
||||||
values = new Map(rows.map((r) => [r.key, r.value]));
|
values = new Map(rows.map((r) => [r.key, r.value]));
|
||||||
extra = rows
|
extra = rows
|
||||||
.filter(
|
.filter(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { asc } from "drizzle-orm";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import {
|
import {
|
||||||
@@ -8,8 +9,8 @@ import {
|
|||||||
} from "@/actions/admin-rare-values";
|
} from "@/actions/admin-rare-values";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, WebsiteRareValueCategories, WebsiteRareValues } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
const CURRENCY_TYPES = ["diamonds", "credits", "duckets", "points"];
|
const CURRENCY_TYPES = ["diamonds", "credits", "duckets", "points"];
|
||||||
|
|
||||||
@@ -21,23 +22,25 @@ export default async function AdminRareValues() {
|
|||||||
|
|
||||||
const t = await getTranslations("pages.admin.rareValues");
|
const t = await getTranslations("pages.admin.rareValues");
|
||||||
|
|
||||||
let categories: Awaited<
|
let categories: (typeof WebsiteRareValueCategories.$inferSelect)[] = [];
|
||||||
ReturnType<typeof prisma.websiteRareValueCategories.findMany>
|
|
||||||
> = [];
|
|
||||||
try {
|
try {
|
||||||
categories = await prisma.websiteRareValueCategories.findMany({
|
categories = await db
|
||||||
orderBy: [{ priority: "asc" }, { name: "asc" }],
|
.select()
|
||||||
});
|
.from(WebsiteRareValueCategories)
|
||||||
|
.orderBy(
|
||||||
|
asc(WebsiteRareValueCategories.priority),
|
||||||
|
asc(WebsiteRareValueCategories.name),
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
categories = [];
|
categories = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let values: Awaited<ReturnType<typeof prisma.websiteRareValues.findMany>> =
|
let values: (typeof WebsiteRareValues.$inferSelect)[] = [];
|
||||||
[];
|
|
||||||
try {
|
try {
|
||||||
values = await prisma.websiteRareValues.findMany({
|
values = await db
|
||||||
orderBy: { name: "asc" },
|
.select()
|
||||||
});
|
.from(WebsiteRareValues)
|
||||||
|
.orderBy(asc(WebsiteRareValues.name));
|
||||||
} catch {
|
} catch {
|
||||||
values = [];
|
values = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { desc, eq, inArray } from "drizzle-orm";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { AdminBreadcrumb } from "@/components/admin/admin-breadcrumb";
|
import { AdminBreadcrumb } from "@/components/admin/admin-breadcrumb";
|
||||||
|
import { db, Items, ItemsBase, User } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { loadRoom } from "../../_lib/load-room";
|
import { loadRoom } from "../../_lib/load-room";
|
||||||
import { FurniClient } from "./furni-client";
|
import { FurniClient } from "./furni-client";
|
||||||
|
|
||||||
@@ -18,10 +19,11 @@ export default async function RoomFurniPage({
|
|||||||
const room = await loadRoom(id);
|
const room = await loadRoom(id);
|
||||||
|
|
||||||
// Load items in this room. items.item_id → items_base.id
|
// Load items in this room. items.item_id → items_base.id
|
||||||
const items = await prisma.items.findMany({
|
const items = await db
|
||||||
where: { roomId: room.id },
|
.select()
|
||||||
orderBy: { id: "desc" },
|
.from(Items)
|
||||||
});
|
.where(eq(Items.roomId, room.id))
|
||||||
|
.orderBy(desc(Items.id));
|
||||||
|
|
||||||
// Resolve item bases
|
// Resolve item bases
|
||||||
const baseIds = Array.from(
|
const baseIds = Array.from(
|
||||||
@@ -30,10 +32,15 @@ export default async function RoomFurniPage({
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
const bases = baseIds.length
|
const bases = baseIds.length
|
||||||
? await prisma.itemsBase.findMany({
|
? await db
|
||||||
where: { id: { in: baseIds } },
|
.select({
|
||||||
select: { id: true, itemName: true, publicName: true, type: true },
|
id: ItemsBase.id,
|
||||||
|
itemName: ItemsBase.itemName,
|
||||||
|
publicName: ItemsBase.publicName,
|
||||||
|
type: ItemsBase.type,
|
||||||
})
|
})
|
||||||
|
.from(ItemsBase)
|
||||||
|
.where(inArray(ItemsBase.id, baseIds))
|
||||||
: [];
|
: [];
|
||||||
const baseMap = new Map(bases.map((b) => [b.id, b]));
|
const baseMap = new Map(bases.map((b) => [b.id, b]));
|
||||||
|
|
||||||
@@ -42,10 +49,10 @@ export default async function RoomFurniPage({
|
|||||||
new Set(items.map((i) => i.userId).filter((u) => u > 0)),
|
new Set(items.map((i) => i.userId).filter((u) => u > 0)),
|
||||||
);
|
);
|
||||||
const users = userIds.length
|
const users = userIds.length
|
||||||
? await prisma.user.findMany({
|
? await db
|
||||||
where: { id: { in: userIds } },
|
.select({ id: User.id, username: User.username })
|
||||||
select: { id: true, username: true },
|
.from(User)
|
||||||
})
|
.where(inArray(User.id, userIds))
|
||||||
: [];
|
: [];
|
||||||
const userMap = new Map(users.map((u) => [u.id, u.username]));
|
const userMap = new Map(users.map((u) => [u.id, u.username]));
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,31 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { db, Rooms, User } from "@/lib/db";
|
||||||
|
|
||||||
export async function loadRoom(id: string) {
|
export async function loadRoom(id: string) {
|
||||||
const roomId = parseInt(id, 10);
|
const roomId = parseInt(id, 10);
|
||||||
if (Number.isNaN(roomId)) notFound();
|
if (Number.isNaN(roomId)) notFound();
|
||||||
|
|
||||||
const room = await prisma.rooms.findUnique({
|
const [room] = await db
|
||||||
where: { id: roomId },
|
.select()
|
||||||
});
|
.from(Rooms)
|
||||||
|
.where(eq(Rooms.id, roomId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
if (!room) notFound();
|
if (!room) notFound();
|
||||||
const owner =
|
const owner =
|
||||||
room.ownerId > 0
|
room.ownerId > 0
|
||||||
? await prisma.user.findUnique({
|
? ((
|
||||||
where: { id: room.ownerId },
|
await db
|
||||||
select: { id: true, username: true, look: true },
|
.select({
|
||||||
|
id: User.id,
|
||||||
|
username: User.username,
|
||||||
|
look: User.look,
|
||||||
})
|
})
|
||||||
|
.from(User)
|
||||||
|
.where(eq(User.id, room.ownerId))
|
||||||
|
.limit(1)
|
||||||
|
)[0] ?? null)
|
||||||
: null;
|
: null;
|
||||||
return { ...room, owner };
|
return { ...room, owner };
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { asc } from "drizzle-orm";
|
||||||
import { Settings } from "lucide-react";
|
import { Settings } from "lucide-react";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { AdminPageShell } from "@/components/admin/admin-page-shell";
|
import { AdminPageShell } from "@/components/admin/admin-page-shell";
|
||||||
|
import { db, WebsiteSetting } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { AdvancedSettingsPanel } from "./advanced-settings-panel";
|
import { AdvancedSettingsPanel } from "./advanced-settings-panel";
|
||||||
import { FIELD_DEFAULTS, MANAGED_SETTING_KEYS } from "./cms-settings-config";
|
import { FIELD_DEFAULTS, MANAGED_SETTING_KEYS } from "./cms-settings-config";
|
||||||
import { CmsSettingsForm } from "./cms-settings-form";
|
import { CmsSettingsForm } from "./cms-settings-form";
|
||||||
@@ -22,11 +23,12 @@ export default async function AdminSettings() {
|
|||||||
);
|
);
|
||||||
const t = await getTranslations("pages.admin.settings");
|
const t = await getTranslations("pages.admin.settings");
|
||||||
|
|
||||||
let settings: Awaited<ReturnType<typeof prisma.websiteSetting.findMany>> = [];
|
let settings: (typeof WebsiteSetting.$inferSelect)[] = [];
|
||||||
try {
|
try {
|
||||||
settings = await prisma.websiteSetting.findMany({
|
settings = await db
|
||||||
orderBy: { key: "asc" },
|
.select()
|
||||||
});
|
.from(WebsiteSetting)
|
||||||
|
.orderBy(asc(WebsiteSetting.key));
|
||||||
} catch {
|
} catch {
|
||||||
settings = [];
|
settings = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
import { Package } from "lucide-react";
|
import { Package } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { deleteShopArticle, updateShopArticle } from "@/actions/admin-shop";
|
import { deleteShopArticle, updateShopArticle } from "@/actions/admin-shop";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, WebsiteShopArticles } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
export default async function EditShopArticle({
|
export default async function EditShopArticle({
|
||||||
params,
|
params,
|
||||||
@@ -20,12 +21,14 @@ export default async function EditShopArticle({
|
|||||||
const t = await getTranslations("pages.admin.shop");
|
const t = await getTranslations("pages.admin.shop");
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
let pkg: Awaited<ReturnType<typeof prisma.websiteShopArticles.findUnique>> =
|
let pkg: typeof WebsiteShopArticles.$inferSelect | null = null;
|
||||||
null;
|
|
||||||
try {
|
try {
|
||||||
pkg = await prisma.websiteShopArticles.findUnique({
|
const [row] = await db
|
||||||
where: { id: BigInt(id) },
|
.select()
|
||||||
});
|
.from(WebsiteShopArticles)
|
||||||
|
.where(eq(WebsiteShopArticles.id, BigInt(id)))
|
||||||
|
.limit(1);
|
||||||
|
pkg = row ?? null;
|
||||||
} catch {
|
} catch {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-17
@@ -1,3 +1,4 @@
|
|||||||
|
import { asc, count, desc } from "drizzle-orm";
|
||||||
import { ShoppingCart } from "lucide-react";
|
import { ShoppingCart } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
@@ -8,9 +9,9 @@ import { StatusCard } from "@/components/admin/dashboard";
|
|||||||
import { CurrencyIcon } from "@/components/shared/currency-icon";
|
import { CurrencyIcon } from "@/components/shared/currency-icon";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||||
|
import { db, WebsitePaypalTransactions, WebsiteShopArticles } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
const PAID_STATUS = new Set(["completed", "paid", "success", "approved"]);
|
const PAID_STATUS = new Set(["completed", "paid", "success", "approved"]);
|
||||||
const RECENT_ORDERS = 25;
|
const RECENT_ORDERS = 25;
|
||||||
@@ -65,22 +66,24 @@ export default async function AdminShop({
|
|||||||
const parsed = parseListParams(new URLSearchParams(raw));
|
const parsed = parseListParams(new URLSearchParams(raw));
|
||||||
|
|
||||||
let packageTotal = 0;
|
let packageTotal = 0;
|
||||||
let packages: Awaited<
|
let packages: (typeof WebsiteShopArticles.$inferSelect)[] = [];
|
||||||
ReturnType<typeof prisma.websiteShopArticles.findMany>
|
|
||||||
> = [];
|
|
||||||
let loadError = false;
|
let loadError = false;
|
||||||
try {
|
try {
|
||||||
packageTotal = await prisma.websiteShopArticles.count();
|
const [totalRow] = await db
|
||||||
|
.select({ total: count() })
|
||||||
|
.from(WebsiteShopArticles);
|
||||||
|
packageTotal = Number(totalRow?.total ?? 0);
|
||||||
const pagination = calcPagination(
|
const pagination = calcPagination(
|
||||||
packageTotal,
|
packageTotal,
|
||||||
parsed.page,
|
parsed.page,
|
||||||
parsed.perPage,
|
parsed.perPage,
|
||||||
);
|
);
|
||||||
packages = await prisma.websiteShopArticles.findMany({
|
packages = await db
|
||||||
orderBy: [{ position: "asc" }, { name: "asc" }],
|
.select()
|
||||||
skip: pagination.offset,
|
.from(WebsiteShopArticles)
|
||||||
take: pagination.perPage,
|
.orderBy(asc(WebsiteShopArticles.position), asc(WebsiteShopArticles.name))
|
||||||
});
|
.limit(pagination.perPage)
|
||||||
|
.offset(pagination.offset);
|
||||||
} catch {
|
} catch {
|
||||||
packages = [];
|
packages = [];
|
||||||
packageTotal = 0;
|
packageTotal = 0;
|
||||||
@@ -93,14 +96,13 @@ export default async function AdminShop({
|
|||||||
parsed.perPage,
|
parsed.perPage,
|
||||||
);
|
);
|
||||||
|
|
||||||
let orders: Awaited<
|
let orders: (typeof WebsitePaypalTransactions.$inferSelect)[] = [];
|
||||||
ReturnType<typeof prisma.websitePaypalTransactions.findMany>
|
|
||||||
> = [];
|
|
||||||
try {
|
try {
|
||||||
orders = await prisma.websitePaypalTransactions.findMany({
|
orders = await db
|
||||||
orderBy: { createdAt: "desc" },
|
.select()
|
||||||
take: RECENT_ORDERS,
|
.from(WebsitePaypalTransactions)
|
||||||
});
|
.orderBy(desc(WebsitePaypalTransactions.createdAt))
|
||||||
|
.limit(RECENT_ORDERS);
|
||||||
} catch {
|
} catch {
|
||||||
orders = [];
|
orders = [];
|
||||||
loadError = true;
|
loadError = true;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { asc, count, gt } from "drizzle-orm";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
import { CatalogItems, db, Soundtracks } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { siteSettings } from "@/lib/services/site-settings";
|
import { siteSettings } from "@/lib/services/site-settings";
|
||||||
import {
|
import {
|
||||||
DEFAULT_SOUNDTRACK_BASE_URL,
|
DEFAULT_SOUNDTRACK_BASE_URL,
|
||||||
@@ -18,28 +19,31 @@ export default async function SoundsPage() {
|
|||||||
const canEdit = canAccess(permissions, PERMS.CATALOG_EDIT, session.user.rank);
|
const canEdit = canAccess(permissions, PERMS.CATALOG_EDIT, session.user.rank);
|
||||||
|
|
||||||
const [rows, usage, baseUrlSetting] = await Promise.all([
|
const [rows, usage, baseUrlSetting] = await Promise.all([
|
||||||
prisma.soundtracks.findMany({
|
db
|
||||||
orderBy: { id: "asc" },
|
.select({
|
||||||
select: {
|
id: Soundtracks.id,
|
||||||
id: true,
|
code: Soundtracks.code,
|
||||||
code: true,
|
name: Soundtracks.name,
|
||||||
name: true,
|
author: Soundtracks.author,
|
||||||
author: true,
|
length: Soundtracks.length,
|
||||||
length: true,
|
track: Soundtracks.track,
|
||||||
track: true,
|
})
|
||||||
},
|
.from(Soundtracks)
|
||||||
}),
|
.orderBy(asc(Soundtracks.id)),
|
||||||
prisma.catalogItems.groupBy({
|
db
|
||||||
by: ["songId"],
|
.select({
|
||||||
where: { songId: { gt: 0 } },
|
songId: CatalogItems.songId,
|
||||||
_count: { _all: true },
|
total: count(),
|
||||||
}),
|
})
|
||||||
|
.from(CatalogItems)
|
||||||
|
.where(gt(CatalogItems.songId, 0))
|
||||||
|
.groupBy(CatalogItems.songId),
|
||||||
siteSettings.get("soundtrack_base_url", DEFAULT_SOUNDTRACK_BASE_URL),
|
siteSettings.get("soundtrack_base_url", DEFAULT_SOUNDTRACK_BASE_URL),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const usageMap: Record<number, number> = {};
|
const usageMap: Record<number, number> = {};
|
||||||
for (const row of usage) {
|
for (const row of usage) {
|
||||||
if (row.songId != null) usageMap[row.songId] = row._count._all;
|
if (row.songId != null) usageMap[row.songId] = Number(row.total);
|
||||||
}
|
}
|
||||||
|
|
||||||
const soundtracks = rows.map((r) => {
|
const soundtracks = rows.map((r) => {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import { sql } from "drizzle-orm";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { AdminSimplePager } from "@/components/admin/admin-simple-pager";
|
import { AdminSimplePager } from "@/components/admin/admin-simple-pager";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||||
|
import { db } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
type SubRow = {
|
type SubRow = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -14,6 +15,19 @@ type SubRow = {
|
|||||||
expiresAt: number;
|
expiresAt: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CountRow = { c: number | bigint };
|
||||||
|
type SubSqlRow = {
|
||||||
|
id: number;
|
||||||
|
user_id: number | null;
|
||||||
|
subscription_type: string | null;
|
||||||
|
expires_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function rawRows<T>(query: ReturnType<typeof sql>): Promise<T[]> {
|
||||||
|
const [rows] = (await db.execute(query)) as unknown as [T[], unknown];
|
||||||
|
return rows ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
export default async function AdminSubscriptions({
|
export default async function AdminSubscriptions({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
@@ -36,39 +50,32 @@ export default async function AdminSubscriptions({
|
|||||||
let loadError = false;
|
let loadError = false;
|
||||||
try {
|
try {
|
||||||
const [countRows, typeRows, soonRows] = await Promise.all([
|
const [countRows, typeRows, soonRows] = await Promise.all([
|
||||||
prisma.$queryRaw<{ c: bigint }[]>`
|
rawRows<CountRow>(sql`
|
||||||
SELECT COUNT(*) AS c
|
SELECT COUNT(*) AS c
|
||||||
FROM users_subscriptions
|
FROM users_subscriptions
|
||||||
WHERE active = 1
|
WHERE active = 1
|
||||||
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) > ${now}
|
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) > ${now}
|
||||||
`,
|
`),
|
||||||
prisma.$queryRaw<{ c: bigint }[]>`
|
rawRows<CountRow>(sql`
|
||||||
SELECT COUNT(DISTINCT subscription_type) AS c
|
SELECT COUNT(DISTINCT subscription_type) AS c
|
||||||
FROM users_subscriptions
|
FROM users_subscriptions
|
||||||
WHERE active = 1
|
WHERE active = 1
|
||||||
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) > ${now}
|
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) > ${now}
|
||||||
`,
|
`),
|
||||||
prisma.$queryRaw<{ c: bigint }[]>`
|
rawRows<CountRow>(sql`
|
||||||
SELECT COUNT(*) AS c
|
SELECT COUNT(*) AS c
|
||||||
FROM users_subscriptions
|
FROM users_subscriptions
|
||||||
WHERE active = 1
|
WHERE active = 1
|
||||||
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) > ${now}
|
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) > ${now}
|
||||||
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) <= ${now + 7 * 24 * 60 * 60}
|
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) <= ${now + 7 * 24 * 60 * 60}
|
||||||
`,
|
`),
|
||||||
]);
|
]);
|
||||||
total = Number(countRows[0]?.c ?? 0);
|
total = Number(countRows[0]?.c ?? 0);
|
||||||
types = Number(typeRows[0]?.c ?? 0);
|
types = Number(typeRows[0]?.c ?? 0);
|
||||||
expiringSoon = Number(soonRows[0]?.c ?? 0);
|
expiringSoon = Number(soonRows[0]?.c ?? 0);
|
||||||
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
||||||
|
|
||||||
const rows = await prisma.$queryRaw<
|
const rows = await rawRows<SubSqlRow>(sql`
|
||||||
{
|
|
||||||
id: number;
|
|
||||||
user_id: number | null;
|
|
||||||
subscription_type: string | null;
|
|
||||||
expires_at: number;
|
|
||||||
}[]
|
|
||||||
>`
|
|
||||||
SELECT id,
|
SELECT id,
|
||||||
user_id,
|
user_id,
|
||||||
subscription_type,
|
subscription_type,
|
||||||
@@ -78,7 +85,7 @@ export default async function AdminSubscriptions({
|
|||||||
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) > ${now}
|
AND COALESCE(timestamp_start, 0) + COALESCE(duration, 0) > ${now}
|
||||||
ORDER BY expires_at ASC
|
ORDER BY expires_at ASC
|
||||||
LIMIT ${pagination.perPage} OFFSET ${pagination.offset}
|
LIMIT ${pagination.perPage} OFFSET ${pagination.offset}
|
||||||
`;
|
`);
|
||||||
|
|
||||||
active = rows.map((r) => ({
|
active = rows.map((r) => ({
|
||||||
id: Number(r.id),
|
id: Number(r.id),
|
||||||
|
|||||||
+20
-11
@@ -1,11 +1,12 @@
|
|||||||
|
import { asc, count } from "drizzle-orm";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { createTag, deleteTag, updateTag } from "@/actions/admin-tags";
|
import { createTag, deleteTag, updateTag } from "@/actions/admin-tags";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, Taggables, Tags } from "@/lib/db";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
type Tag = {
|
type Tag = {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
@@ -26,18 +27,26 @@ export default async function AdminTagsPage() {
|
|||||||
let dbError = false;
|
let dbError = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tags = await prisma.tags.findMany({
|
tags = await db
|
||||||
select: { id: true, name: true, backgroundColor: true, createdAt: true },
|
.select({
|
||||||
orderBy: { name: "asc" },
|
id: Tags.id,
|
||||||
take: 500,
|
name: Tags.name,
|
||||||
});
|
backgroundColor: Tags.backgroundColor,
|
||||||
|
createdAt: Tags.createdAt,
|
||||||
|
})
|
||||||
|
.from(Tags)
|
||||||
|
.orderBy(asc(Tags.name))
|
||||||
|
.limit(500);
|
||||||
|
|
||||||
// How many things each tag is attached to (taggables join table).
|
// How many things each tag is attached to (taggables join table).
|
||||||
const counts = await prisma.taggables.groupBy({
|
const counts = await db
|
||||||
by: ["tagId"],
|
.select({
|
||||||
_count: { tagId: true },
|
tagId: Taggables.tagId,
|
||||||
});
|
total: count(),
|
||||||
usage = new Map(counts.map((c) => [String(c.tagId), c._count.tagId]));
|
})
|
||||||
|
.from(Taggables)
|
||||||
|
.groupBy(Taggables.tagId);
|
||||||
|
usage = new Map(counts.map((c) => [String(c.tagId), Number(c.total)]));
|
||||||
} catch {
|
} catch {
|
||||||
dbError = true;
|
dbError = true;
|
||||||
tags = [];
|
tags = [];
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { asc } from "drizzle-orm";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { createTeam, deleteTeam } from "@/actions/admin-teams";
|
import { createTeam, deleteTeam } from "@/actions/admin-teams";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { db, WebsiteTeams } from "@/lib/db";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
type TeamRow = {
|
type TeamRow = {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
@@ -24,17 +25,17 @@ export default async function AdminTeams() {
|
|||||||
const t = await getTranslations("pages.admin.teams");
|
const t = await getTranslations("pages.admin.teams");
|
||||||
let teams: TeamRow[] = [];
|
let teams: TeamRow[] = [];
|
||||||
try {
|
try {
|
||||||
teams = await prisma.websiteTeams.findMany({
|
teams = await db
|
||||||
select: {
|
.select({
|
||||||
id: true,
|
id: WebsiteTeams.id,
|
||||||
rankName: true,
|
rankName: WebsiteTeams.rankName,
|
||||||
badge: true,
|
badge: WebsiteTeams.badge,
|
||||||
jobDescription: true,
|
jobDescription: WebsiteTeams.jobDescription,
|
||||||
staffColor: true,
|
staffColor: WebsiteTeams.staffColor,
|
||||||
hiddenRank: true,
|
hiddenRank: WebsiteTeams.hiddenRank,
|
||||||
},
|
})
|
||||||
orderBy: { id: "asc" },
|
.from(WebsiteTeams)
|
||||||
});
|
.orderBy(asc(WebsiteTeams.id));
|
||||||
} catch {
|
} catch {
|
||||||
teams = [];
|
teams = [];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user