fix: restore complete admin feature dependencies
This commit is contained in:
1 parent
5b4228261a
commit
0cd753c735
115 files changed
+18565
-4930
No files matched your search
@@ -264,7 +264,7 @@ export function CatalogTranslateTab({
|
||||
<Database className="h-3 w-3" /> items_base (name)
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<ShoppingCart className="h-3 w-3" /> catalog_name (auto)
|
||||
<ShoppingCart className="h-3 w-3" /> catalogName (auto)
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<FileJson className="h-3 w-3" /> FurnitureData.json (name + desc)
|
||||
|
||||
@@ -22,14 +22,14 @@ export default async function CatalogEditPage({
|
||||
const pageId = parseInt(id, 10)
|
||||
if (Number.isNaN(pageId)) notFound()
|
||||
|
||||
const catalogPage = await prisma.catalogPage.findUnique({
|
||||
const catalogPage = await prisma.catalogPages.findUnique({
|
||||
where: { id: pageId },
|
||||
})
|
||||
|
||||
if (!catalogPage) notFound()
|
||||
|
||||
const [children, itemsData, ancestors] = await Promise.all([
|
||||
prisma.catalogPage.findMany({
|
||||
prisma.catalogPages.findMany({
|
||||
where: { parentId: pageId },
|
||||
orderBy: { orderNum: 'asc' },
|
||||
select: { id: true, caption: true, enabled: true },
|
||||
|
||||
@@ -19,40 +19,40 @@ export default async function BcPageDetailPage({
|
||||
|
||||
const canEdit = canAccess(permissions, PERMS.CATALOG_EDIT, session.user.rank)
|
||||
|
||||
const bcPage = await prisma.catalog_pages_bc.findUnique({ where: { id } })
|
||||
const bcPage = await prisma.catalogPagesBc.findUnique({ where: { id } })
|
||||
if (!bcPage) notFound()
|
||||
|
||||
const bcItems = await prisma.catalog_items_bc.findMany({
|
||||
where: { page_id: id },
|
||||
orderBy: { order_number: 'asc' },
|
||||
const bcItems = await prisma.catalogItemsBc.findMany({
|
||||
where: { pageId: id },
|
||||
orderBy: { orderNumber: 'asc' },
|
||||
})
|
||||
|
||||
return (
|
||||
<BcPageDetail
|
||||
page={{
|
||||
id: bcPage.id,
|
||||
parentId: bcPage.parent_id,
|
||||
parentId: bcPage.parentId,
|
||||
caption: bcPage.caption,
|
||||
pageLayout: bcPage.page_layout,
|
||||
iconColor: bcPage.icon_color,
|
||||
iconImage: bcPage.icon_image,
|
||||
orderNum: bcPage.order_num,
|
||||
pageLayout: bcPage.pageLayout,
|
||||
iconColor: bcPage.iconColor,
|
||||
iconImage: bcPage.iconImage,
|
||||
orderNum: bcPage.orderNum,
|
||||
visible: bcPage.visible,
|
||||
enabled: bcPage.enabled,
|
||||
pageHeadline: bcPage.page_headline,
|
||||
pageTeaser: bcPage.page_teaser,
|
||||
pageSpecial: bcPage.page_special ?? '',
|
||||
pageText1: bcPage.page_text1 ?? '',
|
||||
pageText2: bcPage.page_text2 ?? '',
|
||||
pageTextDetails: bcPage.page_text_details ?? '',
|
||||
pageTextTeaser: bcPage.page_text_teaser ?? '',
|
||||
pageHeadline: bcPage.pageHeadline,
|
||||
pageTeaser: bcPage.pageTeaser,
|
||||
pageSpecial: bcPage.pageSpecial ?? '',
|
||||
pageText1: bcPage.pageText1 ?? '',
|
||||
pageText2: bcPage.pageText2 ?? '',
|
||||
pageTextDetails: bcPage.pageTextDetails ?? '',
|
||||
pageTextTeaser: bcPage.pageTextTeaser ?? '',
|
||||
}}
|
||||
items={bcItems.map((i) => ({
|
||||
id: i.id,
|
||||
pageId: i.page_id,
|
||||
itemIds: i.item_ids,
|
||||
catalogName: i.catalog_name,
|
||||
orderNumber: i.order_number,
|
||||
pageId: i.pageId,
|
||||
itemIds: i.itemIds,
|
||||
catalogName: i.catalogName,
|
||||
orderNumber: i.orderNumber,
|
||||
extradata: i.extradata,
|
||||
}))}
|
||||
canEdit={canEdit}
|
||||
|
||||
@@ -51,46 +51,46 @@ export default async function CatalogPagesPage({
|
||||
|
||||
if (catalogType === 'bc') {
|
||||
const [totalBcPages, totalBcItems, enabledBcPages] = await Promise.all([
|
||||
prisma.catalog_pages_bc.count(),
|
||||
prisma.catalog_items_bc.count(),
|
||||
prisma.catalog_pages_bc.count({ where: { enabled: '1' } }),
|
||||
prisma.catalogPagesBc.count(),
|
||||
prisma.catalogItemsBc.count(),
|
||||
prisma.catalogPagesBc.count({ where: { enabled: '1' } }),
|
||||
])
|
||||
|
||||
// ── BC tree view ──────────────────────────────────────────────
|
||||
let bcRootPages: TreeNodeShape[] = []
|
||||
if (viewMode === 'tree') {
|
||||
const bcRootRaw = await prisma.catalog_pages_bc.findMany({
|
||||
where: { OR: [{ parent_id: -1 }, { parent_id: 0 }] },
|
||||
orderBy: { order_num: 'asc' },
|
||||
const bcRootRaw = await prisma.catalogPagesBc.findMany({
|
||||
where: { OR: [{ parentId: -1 }, { parentId: 0 }] },
|
||||
orderBy: { orderNum: 'asc' },
|
||||
})
|
||||
const bcRootIds = bcRootRaw.map((p) => p.id)
|
||||
const [bcChildCounts, bcItemCounts] = await Promise.all([
|
||||
bcRootIds.length > 0
|
||||
? prisma.catalog_pages_bc.groupBy({
|
||||
by: ['parent_id'],
|
||||
where: { parent_id: { in: bcRootIds } },
|
||||
? prisma.catalogPagesBc.groupBy({
|
||||
by: ['parentId'],
|
||||
where: { parentId: { in: bcRootIds } },
|
||||
_count: true,
|
||||
})
|
||||
: [],
|
||||
bcRootIds.length > 0
|
||||
? prisma.catalog_items_bc.groupBy({
|
||||
by: ['page_id'],
|
||||
where: { page_id: { in: bcRootIds } },
|
||||
? prisma.catalogItemsBc.groupBy({
|
||||
by: ['pageId'],
|
||||
where: { pageId: { in: bcRootIds } },
|
||||
_count: true,
|
||||
})
|
||||
: [],
|
||||
])
|
||||
const bcChildMap = Object.fromEntries(bcChildCounts.map((c) => [c.parent_id, c._count]))
|
||||
const bcItemMap = Object.fromEntries(bcItemCounts.map((c) => [c.page_id, c._count]))
|
||||
const bcChildMap = Object.fromEntries(bcChildCounts.map((c) => [c.parentId, c._count]))
|
||||
const bcItemMap = Object.fromEntries(bcItemCounts.map((c) => [c.pageId, c._count]))
|
||||
bcRootPages = bcRootRaw.map((p) => ({
|
||||
id: p.id,
|
||||
caption: p.caption,
|
||||
parentId: p.parent_id,
|
||||
parentId: p.parentId,
|
||||
enabled: String(p.enabled),
|
||||
visible: String(p.visible),
|
||||
iconImage: p.icon_image,
|
||||
iconColor: p.icon_color,
|
||||
pageLayout: p.page_layout,
|
||||
iconImage: p.iconImage,
|
||||
iconColor: p.iconColor,
|
||||
pageLayout: p.pageLayout,
|
||||
childCount: bcChildMap[p.id] ?? 0,
|
||||
itemCount: bcItemMap[p.id] ?? 0,
|
||||
}))
|
||||
@@ -122,68 +122,68 @@ export default async function CatalogPagesPage({
|
||||
const pattern = { contains: search }
|
||||
where.OR = [{ caption: pattern }]
|
||||
}
|
||||
if (hasParent) where.parent_id = parentId
|
||||
if (hasParent) where.parentId = parentId
|
||||
|
||||
// Prisma sort key is snake_case for BC model. Translate known keys.
|
||||
const sortKeyMap: Record<string, string> = {
|
||||
parentId: 'parent_id',
|
||||
pageLayout: 'page_layout',
|
||||
orderNum: 'order_num',
|
||||
parentId: 'parentId',
|
||||
pageLayout: 'pageLayout',
|
||||
orderNum: 'orderNum',
|
||||
}
|
||||
const sortKey = sort ? (sortKeyMap[sort] ?? sort) : 'order_num'
|
||||
const sortKey = sort ? (sortKeyMap[sort] ?? sort) : 'orderNum'
|
||||
const orderBy = { [sortKey]: sort ? order : 'asc' }
|
||||
|
||||
const [pages, total] = await Promise.all([
|
||||
prisma.catalog_pages_bc.findMany({
|
||||
prisma.catalogPagesBc.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
skip: (page - 1) * perPage,
|
||||
take: perPage,
|
||||
}),
|
||||
prisma.catalog_pages_bc.count({ where }),
|
||||
prisma.catalogPagesBc.count({ where }),
|
||||
])
|
||||
|
||||
bcPagination = calcPagination(total, page, perPage)
|
||||
|
||||
const pageIds = pages.map((p) => p.id)
|
||||
const parentIds = [...new Set(pages.map((p) => p.parent_id).filter((id) => id > 0))]
|
||||
const parentIds = [...new Set(pages.map((p) => p.parentId).filter((id) => id > 0))]
|
||||
const [childCounts, itemCounts, parentPages] = await Promise.all([
|
||||
pageIds.length > 0
|
||||
? prisma.catalog_pages_bc.groupBy({
|
||||
by: ['parent_id'],
|
||||
where: { parent_id: { in: pageIds } },
|
||||
? prisma.catalogPagesBc.groupBy({
|
||||
by: ['parentId'],
|
||||
where: { parentId: { in: pageIds } },
|
||||
_count: true,
|
||||
})
|
||||
: [],
|
||||
pageIds.length > 0
|
||||
? prisma.catalog_items_bc.groupBy({
|
||||
by: ['page_id'],
|
||||
where: { page_id: { in: pageIds } },
|
||||
? prisma.catalogItemsBc.groupBy({
|
||||
by: ['pageId'],
|
||||
where: { pageId: { in: pageIds } },
|
||||
_count: true,
|
||||
})
|
||||
: [],
|
||||
parentIds.length > 0
|
||||
? prisma.catalog_pages_bc.findMany({
|
||||
? prisma.catalogPagesBc.findMany({
|
||||
where: { id: { in: parentIds } },
|
||||
select: { id: true, caption: true },
|
||||
})
|
||||
: [],
|
||||
])
|
||||
const childCountMap = Object.fromEntries(childCounts.map((c) => [c.parent_id, c._count]))
|
||||
const itemCountMap = Object.fromEntries(itemCounts.map((c) => [c.page_id, c._count]))
|
||||
const childCountMap = Object.fromEntries(childCounts.map((c) => [c.parentId, c._count]))
|
||||
const itemCountMap = Object.fromEntries(itemCounts.map((c) => [c.pageId, c._count]))
|
||||
const parentNameMap = Object.fromEntries(parentPages.map((p) => [p.id, p.caption]))
|
||||
|
||||
bcRows = pages.map((p) => ({
|
||||
id: p.id,
|
||||
caption: p.caption,
|
||||
parentId: p.parent_id,
|
||||
parentId: p.parentId,
|
||||
parentCaption:
|
||||
p.parent_id === -1 ? 'Root' : (parentNameMap[p.parent_id] ?? `#${p.parent_id}`),
|
||||
pageLayout: String(p.page_layout),
|
||||
p.parentId === -1 ? 'Root' : (parentNameMap[p.parentId] ?? `#${p.parentId}`),
|
||||
pageLayout: String(p.pageLayout),
|
||||
enabled: String(p.enabled),
|
||||
visible: String(p.visible),
|
||||
minRank: 0, // BC pages have no min rank column
|
||||
orderNum: p.order_num,
|
||||
orderNum: p.orderNum,
|
||||
itemCount: itemCountMap[p.id] ?? 0,
|
||||
childCount: childCountMap[p.id] ?? 0,
|
||||
}))
|
||||
@@ -220,29 +220,29 @@ export default async function CatalogPagesPage({
|
||||
|
||||
// Global stats (lightweight counts)
|
||||
const [totalPages, totalItems, enabledPages] = await Promise.all([
|
||||
prisma.catalogPage.count(),
|
||||
prisma.catalogItem.count(),
|
||||
prisma.catalogPage.count({ where: { enabled: '1' } }),
|
||||
prisma.catalogPages.count(),
|
||||
prisma.catalogItems.count(),
|
||||
prisma.catalogPages.count({ where: { enabled: '1' } }),
|
||||
])
|
||||
|
||||
// ── Tree view data ──────────────────────────────────────────────
|
||||
let rootPages: TreeNodeShape[] = []
|
||||
if (viewMode === 'tree') {
|
||||
const rootPagesRaw = await prisma.catalogPage.findMany({
|
||||
const rootPagesRaw = await prisma.catalogPages.findMany({
|
||||
where: { parentId: -1 },
|
||||
orderBy: { orderNum: 'asc' },
|
||||
})
|
||||
const rootIds = rootPagesRaw.map((p) => p.id)
|
||||
const [rootChildCounts, rootItemCounts] = await Promise.all([
|
||||
rootIds.length > 0
|
||||
? prisma.catalogPage.groupBy({
|
||||
? prisma.catalogPages.groupBy({
|
||||
by: ['parentId'],
|
||||
where: { parentId: { in: rootIds } },
|
||||
_count: true,
|
||||
})
|
||||
: [],
|
||||
rootIds.length > 0
|
||||
? prisma.catalogItem.groupBy({
|
||||
? prisma.catalogItems.groupBy({
|
||||
by: ['pageId'],
|
||||
where: { pageId: { in: rootIds } },
|
||||
_count: true,
|
||||
@@ -289,15 +289,15 @@ export default async function CatalogPagesPage({
|
||||
const hasParent = parentId !== undefined && !Number.isNaN(parentId)
|
||||
if (search) {
|
||||
const searchPattern = `%${search}%`
|
||||
const parentClause = hasParent ? Prisma.sql`AND cp.parent_id = ${parentId}` : Prisma.empty
|
||||
const parentClause = hasParent ? Prisma.sql`AND cp.parentId = ${parentId}` : Prisma.empty
|
||||
|
||||
const matchedIds = await prisma.$queryRaw<{ id: number }[]>(Prisma.sql`
|
||||
SELECT DISTINCT cp.id FROM catalog_pages cp
|
||||
LEFT JOIN catalog_items ci ON ci.page_id = cp.id
|
||||
LEFT JOIN items_base ib ON FIND_IN_SET(ib.id, REPLACE(ci.item_ids, ';', ','))
|
||||
LEFT JOIN catalog_items ci ON ci.pageId = cp.id
|
||||
LEFT JOIN items_base ib ON FIND_IN_SET(ib.id, REPLACE(ci.itemIds, ';', ','))
|
||||
WHERE (cp.caption LIKE ${searchPattern}
|
||||
OR cp.page_layout LIKE ${searchPattern}
|
||||
OR ci.catalog_name LIKE ${searchPattern}
|
||||
OR cp.pageLayout LIKE ${searchPattern}
|
||||
OR ci.catalogName LIKE ${searchPattern}
|
||||
OR ib.public_name LIKE ${searchPattern}
|
||||
OR ib.item_name LIKE ${searchPattern})
|
||||
${parentClause}
|
||||
@@ -310,13 +310,13 @@ export default async function CatalogPagesPage({
|
||||
const orderBy = sort ? { [sort]: order } : { orderNum: 'asc' as const }
|
||||
|
||||
const [pages, total] = await Promise.all([
|
||||
prisma.catalogPage.findMany({
|
||||
prisma.catalogPages.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
skip: (page - 1) * perPage,
|
||||
take: perPage,
|
||||
}),
|
||||
prisma.catalogPage.count({ where }),
|
||||
prisma.catalogPages.count({ where }),
|
||||
])
|
||||
|
||||
pagination = calcPagination(total, page, perPage)
|
||||
@@ -326,21 +326,21 @@ export default async function CatalogPagesPage({
|
||||
const parentIds = [...new Set(pages.map((p) => p.parentId).filter((id) => id > 0))]
|
||||
const [childCounts, itemCounts, parentPages] = await Promise.all([
|
||||
pageIds.length > 0
|
||||
? prisma.catalogPage.groupBy({
|
||||
? prisma.catalogPages.groupBy({
|
||||
by: ['parentId'],
|
||||
where: { parentId: { in: pageIds } },
|
||||
_count: true,
|
||||
})
|
||||
: [],
|
||||
pageIds.length > 0
|
||||
? prisma.catalogItem.groupBy({
|
||||
? prisma.catalogItems.groupBy({
|
||||
by: ['pageId'],
|
||||
where: { pageId: { in: pageIds } },
|
||||
_count: true,
|
||||
})
|
||||
: [],
|
||||
parentIds.length > 0
|
||||
? prisma.catalogPage.findMany({
|
||||
? prisma.catalogPages.findMany({
|
||||
where: { id: { in: parentIds } },
|
||||
select: { id: true, caption: true },
|
||||
})
|
||||
|
||||
@@ -379,7 +379,7 @@ function FurniGrid({ source }: FurniGridProps) {
|
||||
setError(data.error || 'Failed to load')
|
||||
return
|
||||
}
|
||||
setItems(data.items || [])
|
||||
setItems(data.item || [])
|
||||
setMeta(data.meta || null)
|
||||
setStats(data.stats || null)
|
||||
setSelected(new Set())
|
||||
|
||||
@@ -150,7 +150,7 @@ function LibsView() {
|
||||
setError(data.error || 'Failed to load')
|
||||
return
|
||||
}
|
||||
setItems(data.items || [])
|
||||
setItems(data.item || [])
|
||||
setTotal(data.meta?.total || 0)
|
||||
setSelected(new Set())
|
||||
} catch {
|
||||
@@ -498,7 +498,7 @@ function SetsView() {
|
||||
setError(data.error || 'Failed to load')
|
||||
return
|
||||
}
|
||||
setItems(data.items || [])
|
||||
setItems(data.item || [])
|
||||
setTotal(data.meta?.total || 0)
|
||||
setSelected(new Set())
|
||||
} catch {
|
||||
|
||||
@@ -310,7 +310,7 @@ export function ImportFurniClient() {
|
||||
setError(data.error || 'Failed to load')
|
||||
return
|
||||
}
|
||||
const loadedItems = data.items || []
|
||||
const loadedItems = data.item || []
|
||||
setItems(loadedItems)
|
||||
setMeta(data.meta || null)
|
||||
setSelected(new Set())
|
||||
|
||||
@@ -167,7 +167,10 @@ export function RankEditClient({
|
||||
}
|
||||
|
||||
function saveRank() {
|
||||
run(() => saveRankAction({ id: initialRank.id, fields: { ...general, ...perms } }), {
|
||||
const fields = Object.fromEntries(
|
||||
Object.entries({ ...general, ...perms }).map(([key, value]) => [key, String(value)]),
|
||||
)
|
||||
run(() => saveRankAction({ id: initialRank.id, fields }), {
|
||||
successMessage: 'Rank saved',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ export default async function RoomFurniPage({
|
||||
const room = await loadRoom(id)
|
||||
|
||||
// Load items in this room. items.item_id → items_base.id
|
||||
const items = await prisma.item.findMany({
|
||||
const items = await prisma.items.findMany({
|
||||
where: { roomId: room.id },
|
||||
orderBy: { id: 'desc' },
|
||||
})
|
||||
|
||||
// Resolve item bases
|
||||
const baseIds = Array.from(new Set(items.map((i) => i.itemId)))
|
||||
const baseIds = Array.from(new Set(items.map((i) => i.itemId).filter((id): id is number => id !== null)))
|
||||
const bases = baseIds.length
|
||||
? await prisma.itemsBase.findMany({
|
||||
where: { id: { in: baseIds } },
|
||||
@@ -43,10 +43,10 @@ export default async function RoomFurniPage({
|
||||
const userMap = new Map(users.map((u) => [u.id, u.username]))
|
||||
|
||||
const rows = items.map((i) => {
|
||||
const base = baseMap.get(i.itemId)
|
||||
const base = i.itemId === null ? undefined : baseMap.get(i.itemId)
|
||||
return {
|
||||
id: i.id,
|
||||
baseId: i.itemId,
|
||||
baseId: i.itemId ?? 0,
|
||||
name: base?.publicName || base?.itemName || `#${i.itemId}`,
|
||||
itemName: base?.itemName ?? '',
|
||||
type: base?.type ?? '?',
|
||||
@@ -74,7 +74,7 @@ export default async function RoomFurniPage({
|
||||
room={{
|
||||
id: room.id,
|
||||
name: room.name,
|
||||
ownerUsername: room.owner?.username ?? room.owner_name,
|
||||
ownerUsername: room.owner?.username ?? room.ownerName,
|
||||
}}
|
||||
items={rows}
|
||||
canEdit={session.user.rank >= 4}
|
||||
|
||||
@@ -5,11 +5,16 @@ export async function loadRoom(id: string) {
|
||||
const roomId = parseInt(id, 10)
|
||||
if (Number.isNaN(roomId)) notFound()
|
||||
|
||||
const room = await prisma.room.findUnique({
|
||||
const room = await prisma.rooms.findUnique({
|
||||
where: { id: roomId },
|
||||
include: { owner: { select: { id: true, username: true, look: true } } },
|
||||
})
|
||||
|
||||
if (!room) notFound()
|
||||
return room
|
||||
const owner = room.ownerId > 0
|
||||
? await prisma.user.findUnique({
|
||||
where: { id: room.ownerId },
|
||||
select: { id: true, username: true, look: true },
|
||||
})
|
||||
: null
|
||||
return { ...room, owner }
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export default async function RoomEditPage({
|
||||
</Badge>
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Owner: {room.owner.username} · {room.users}/{room.usersMax} users
|
||||
Owner: {room.owner?.username ?? room.ownerName} · {room.users}/{room.usersMax} users
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,7 +61,7 @@ export default async function RoomEditPage({
|
||||
state: room.state,
|
||||
usersMax: room.usersMax,
|
||||
users: room.users,
|
||||
ownerName: room.owner.username,
|
||||
ownerName: room.owner?.username ?? room.ownerName,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -49,12 +49,13 @@ export default async function RoomShowPage({
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="font-medium">{room.users}</span> / {room.usersMax} users
|
||||
</span>
|
||||
<Link
|
||||
href={`/admin/users/show/${room.owner.id}`}
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Owner: {room.owner.username}
|
||||
</Link>
|
||||
{room.owner ? (
|
||||
<Link href={`/admin/users/show/${room.owner.id}`} className="text-primary hover:underline">
|
||||
Owner: {room.owner.username}
|
||||
</Link>
|
||||
) : (
|
||||
<span>Owner: {room.ownerName}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
|
||||
@@ -26,7 +26,7 @@ export default async function SoundsPage() {
|
||||
track: true,
|
||||
},
|
||||
}),
|
||||
prisma.catalogItem.groupBy({
|
||||
prisma.catalogItems.groupBy({
|
||||
by: ['songId'],
|
||||
where: { songId: { gt: 0 } },
|
||||
_count: { _all: true },
|
||||
@@ -55,7 +55,8 @@ export default async function SoundsPage() {
|
||||
})
|
||||
|
||||
// Normalize: ensure trailing slash so ${base}${code}.mp3 yields a valid URL.
|
||||
const baseUrl = baseUrlSetting.endsWith('/') ? baseUrlSetting : `${baseUrlSetting}/`
|
||||
const configuredBaseUrl = baseUrlSetting ?? '/sounds/'
|
||||
const baseUrl = configuredBaseUrl.endsWith('/') ? configuredBaseUrl : `${configuredBaseUrl}/`
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -67,7 +67,7 @@ export default async function AdminTicketDetailPage({
|
||||
username: ticket.creator.username,
|
||||
look: ticket.creator.look,
|
||||
rank: ticket.creator.rank,
|
||||
mail: ticket.creator.mail,
|
||||
mail: ticket.creator.mail ?? '',
|
||||
ipRegister: ticket.creator.ipRegister,
|
||||
accountCreated: ticket.creator.accountCreated,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExternalLink, Server } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { EmulatorSettingsForm } from '@/app/[locale]/admin/settings/emulator/emulator-settings-form'
|
||||
import { EmulatorSettingsForm } from '@/components/admin/emulator-settings-form'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { Prisma } from '@/generated/prisma/client'
|
||||
import { calcPagination } from '@/lib/admin-helpers'
|
||||
@@ -31,32 +31,32 @@ export default async function EmulatorTranslationsPage({
|
||||
const perPage = Math.min(Math.max(parseInt(sp.perPage || '50', 10), 10), 200)
|
||||
const page = Math.max(parseInt(sp.page || '1', 10), 1)
|
||||
|
||||
const patternFilters: Prisma.EmulatorSettingWhereInput[] = TRANSLATION_KEY_PATTERNS.map((p) => ({
|
||||
const patternFilters: Prisma.EmulatorSettingsWhereInput[] = TRANSLATION_KEY_PATTERNS.map((p) => ({
|
||||
key: { contains: p.replace(/%/g, '') },
|
||||
}))
|
||||
|
||||
const baseFilter: Prisma.EmulatorSettingWhereInput = { OR: patternFilters }
|
||||
const baseFilter: Prisma.EmulatorSettingsWhereInput = { OR: patternFilters }
|
||||
|
||||
const conditions: Prisma.EmulatorSettingWhereInput[] = [baseFilter]
|
||||
const conditions: Prisma.EmulatorSettingsWhereInput[] = [baseFilter]
|
||||
if (group) conditions.push({ key: { startsWith: `${group}.` } })
|
||||
if (search) {
|
||||
conditions.push({
|
||||
OR: [{ key: { contains: search } }, { value: { contains: search } }],
|
||||
})
|
||||
}
|
||||
const where: Prisma.EmulatorSettingWhereInput = { AND: conditions }
|
||||
const where: Prisma.EmulatorSettingsWhereInput = { AND: conditions }
|
||||
|
||||
const [settings, total, allKeys] = await Promise.all([
|
||||
prisma.emulatorSetting.findMany({
|
||||
prisma.emulatorSettings.findMany({
|
||||
where,
|
||||
orderBy: { key: 'asc' },
|
||||
skip: (page - 1) * perPage,
|
||||
take: perPage,
|
||||
}),
|
||||
prisma.emulatorSetting.count({ where }),
|
||||
prisma.emulatorSettings.count({ where }),
|
||||
// Group chips reflect the WHOLE translation-pattern subset (so they don't
|
||||
// disappear when you click one); we ignore the group/search filters here.
|
||||
prisma.emulatorSetting.findMany({
|
||||
prisma.emulatorSettings.findMany({
|
||||
where: baseFilter,
|
||||
select: { key: true },
|
||||
}),
|
||||
|
||||
@@ -1,73 +1,69 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { notFound } from "next/navigation";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function loadUserById(id: string) {
|
||||
const numericId = parseInt(id, 10)
|
||||
const isNumeric = !Number.isNaN(numericId) && String(numericId) === id
|
||||
const numericId = Number(id);
|
||||
const isNumeric = Number.isSafeInteger(numericId) && numericId > 0 && String(numericId) === id;
|
||||
const user = await prisma.user.findUnique({ where: isNumeric ? { id: numericId } : { username: id } });
|
||||
if (!user) notFound();
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: isNumeric ? { id: numericId } : { username: id },
|
||||
include: {
|
||||
permission: true,
|
||||
settings: true,
|
||||
currencies: true,
|
||||
badges: true,
|
||||
bansReceived: {
|
||||
include: { staff: { select: { username: true } } },
|
||||
orderBy: { id: 'desc' },
|
||||
take: 5,
|
||||
},
|
||||
},
|
||||
})
|
||||
const [currencies, badges, bansReceived, permissionRows, roomCount, tradeCount, userRooms, chatLogs, commandLogs] =
|
||||
await Promise.all([
|
||||
prisma.usersCurrency.findMany({ where: { userId: user.id } }),
|
||||
prisma.usersBadges.findMany({ where: { userId: user.id }, orderBy: { slotId: "asc" } }),
|
||||
prisma.$queryRaw<Array<{ id: number; banReason: string; type: string; timestamp: number; banExpire: number; staffName: string }>>`
|
||||
SELECT b.id, b.ban_reason AS banReason, b.type, b.timestamp, b.ban_expire AS banExpire,
|
||||
COALESCE(s.username, 'Unknown') AS staffName
|
||||
FROM bans b LEFT JOIN users s ON s.id = b.user_staff_id
|
||||
WHERE b.user_id = ${user.id} ORDER BY b.id DESC LIMIT 5`,
|
||||
prisma.$queryRaw<Array<{ rankName: string }>>`
|
||||
SELECT COALESCE(rank_name, CONCAT('Rank ', id)) AS rankName FROM permissions WHERE id = ${user.rank} LIMIT 1`,
|
||||
prisma.rooms.count({ where: { ownerId: user.id } }),
|
||||
prisma.roomTradeLog.count({ where: { OR: [{ userOneId: user.id }, { userTwoId: user.id }] } }),
|
||||
prisma.rooms.findMany({
|
||||
where: { ownerId: user.id },
|
||||
orderBy: { users: "desc" },
|
||||
take: 20,
|
||||
select: { id: true, name: true, state: true, users: true, usersMax: true },
|
||||
}),
|
||||
prisma.$queryRaw<Array<{ id: number; roomId: number; timestamp: number; message: string }>>`
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY timestamp DESC) AS id, room_id AS roomId, timestamp, message
|
||||
FROM chatlogs_room WHERE user_from_id = ${user.id} ORDER BY timestamp DESC LIMIT 20`,
|
||||
prisma.$queryRaw<Array<{ id: number; timestamp: number; command: string; params: string; succes: string }>>`
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY timestamp DESC) AS id, timestamp, command, params, succes
|
||||
FROM commandlogs WHERE user_id = ${user.id} ORDER BY timestamp DESC LIMIT 10`,
|
||||
]);
|
||||
|
||||
if (!user) notFound()
|
||||
|
||||
const diamonds = user.currencies.find((c) => c.type === 5)?.amount ?? 0
|
||||
const duckets = user.currencies.find((c) => c.type === 0)?.amount ?? 0
|
||||
|
||||
const [roomCount, chatCount, tradeCount, userRooms, chatLogs, commandLogs] = await Promise.all([
|
||||
prisma.room.count({ where: { ownerId: user.id } }),
|
||||
prisma.chatLog.count({ where: { userId: user.id } }),
|
||||
prisma.trade.count({
|
||||
where: { OR: [{ userOneId: user.id }, { userTwoId: user.id }] },
|
||||
}),
|
||||
prisma.room.findMany({
|
||||
where: { ownerId: user.id },
|
||||
orderBy: { users: 'desc' },
|
||||
take: 20,
|
||||
select: { id: true, name: true, state: true, users: true, usersMax: true },
|
||||
}),
|
||||
prisma.chatLog.findMany({
|
||||
where: { userId: user.id },
|
||||
orderBy: { id: 'desc' },
|
||||
take: 20,
|
||||
select: { id: true, roomId: true, timestamp: true, message: true },
|
||||
}),
|
||||
prisma.commandLog.findMany({
|
||||
where: { userId: user.id },
|
||||
orderBy: { id: 'desc' },
|
||||
take: 10,
|
||||
select: { id: true, timestamp: true, command: true, params: true, succes: true },
|
||||
}),
|
||||
])
|
||||
|
||||
const isOnline = user.online === '1'
|
||||
const activeBan = user.bansReceived.find((b) => {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
return b.banExpire === 0 || b.banExpire > now
|
||||
})
|
||||
const diamonds = currencies.find((currency) => currency.type === 5)?.amount ?? 0;
|
||||
const duckets = currencies.find((currency) => currency.type === 0)?.amount ?? 0;
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const activeBan = bansReceived.find((ban) => ban.banExpire === 0 || ban.banExpire > now);
|
||||
const decoratedUser = {
|
||||
...user,
|
||||
mail: user.mail ?? "",
|
||||
gender: String(user.gender),
|
||||
machine_id: user.machineId,
|
||||
twoFactorConfirmed: user.twoFactorConfirmedAt ? 1 : 0,
|
||||
permission: { rankName: permissionRows[0]?.rankName ?? `Rank ${user.rank}` },
|
||||
currencies,
|
||||
badges,
|
||||
bansReceived: bansReceived.map((ban) => ({
|
||||
...ban,
|
||||
staff: { username: ban.staffName },
|
||||
})),
|
||||
};
|
||||
|
||||
return {
|
||||
user,
|
||||
user: decoratedUser,
|
||||
diamonds,
|
||||
duckets,
|
||||
roomCount,
|
||||
chatCount,
|
||||
chatCount: chatLogs.length,
|
||||
tradeCount,
|
||||
userRooms,
|
||||
chatLogs,
|
||||
commandLogs: commandLogs.map((l) => ({ ...l, succes: String(l.succes) })),
|
||||
isOnline,
|
||||
commandLogs,
|
||||
isOnline: user.online === "1",
|
||||
activeBan,
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user