2026-07-30 18:00:28 +08:00

205 lines
5.7 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import {
buildMaterialWhereFromSearchParams,
createMaterialQueryContext,
MaterialFilterDimension,
MaterialQueryError,
normalizeMaterialTypeForUi,
normalizeSelectableMaterialTypes,
} from '@/lib/material-query'
type FacetField = 'categoryName' | 'sceneName' | 'platformName' | 'productCategoryName'
const FIELD_TO_EXCLUDE: Record<FacetField, MaterialFilterDimension> = {
categoryName: 'category',
sceneName: 'scene',
platformName: 'platform',
productCategoryName: 'productCategory',
}
function toFacetItems(rows: any[], field: FacetField) {
return rows
.map((row) => {
const name = String(row[field] || '').trim()
return {
id: name,
name,
count: row._count?._all || 0,
}
})
.filter((item) => item.name && item.count > 0)
.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'))
}
function toMonthItems(rows: Array<{ createdAt: Date }>) {
const monthMap = new Map<string, number>()
for (const row of rows) {
const date = new Date(row.createdAt)
const value = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
monthMap.set(value, (monthMap.get(value) || 0) + 1)
}
return Array.from(monthMap.entries())
.map(([value, count]) => {
const [, month] = value.split('-')
return {
value,
label: `${value.slice(0, 4)}年${Number.parseInt(month, 10)}月`,
count,
}
})
.sort((a, b) => b.value.localeCompare(a.value))
}
function toMaterialTypeItems(rows: any[]) {
const countMap = new Map<string, number>()
for (const row of rows) {
const normalizedType = normalizeMaterialTypeForUi(row.materialType)
if (!normalizedType) {
continue
}
countMap.set(normalizedType, (countMap.get(normalizedType) || 0) + (row._count?._all || 0))
}
const orderedTypes = normalizeSelectableMaterialTypes(Array.from(countMap.keys()))
return orderedTypes.map((type) => ({
id: type,
name: type,
count: countMap.get(type) || 0,
}))
}
export async function GET(request: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session?.user || !(session.user as any).id) {
return NextResponse.json(
{ error: '未登录', code: 'UNAUTHORIZED' },
{ status: 401 },
)
}
const searchParams = request.nextUrl.searchParams
const user = session.user as any
const context = await createMaterialQueryContext(searchParams, user)
const buildWhere = async (exclude?: MaterialFilterDimension) =>
buildMaterialWhereFromSearchParams(searchParams, user, {
context,
...(exclude ? { exclude } : {}),
})
const countWithFilters = async (exclude?: MaterialFilterDimension) => {
const result = await buildWhere(exclude)
if (result.empty) {
return 0
}
return prisma.material.count({ where: result.where })
}
const groupByField = async (field: FacetField) => {
const result = await buildWhere(FIELD_TO_EXCLUDE[field])
if (result.empty) {
return []
}
const rows = await (prisma.material.groupBy as any)({
by: [field],
where: result.where,
_count: { _all: true },
})
return toFacetItems(rows, field)
}
const loadMaterialTypes = async () => {
const result = await buildWhere('materialType')
if (result.empty) {
return []
}
const rows = await (prisma.material.groupBy as any)({
by: ['materialType'],
where: result.where,
_count: { _all: true },
})
return toMaterialTypeItems(rows)
}
const loadMonths = async () => {
const result = await buildWhere('month')
if (result.empty) {
return []
}
const rows = await prisma.material.findMany({
where: result.where,
select: { createdAt: true },
orderBy: { createdAt: 'desc' },
})
return toMonthItems(rows)
}
// 每批最多并发 3 条统计查询,避免多个首页请求同时耗尽 Prisma 连接池。
const [total, categories, scenes] = await Promise.all([
countWithFilters(),
groupByField('categoryName'),
groupByField('sceneName'),
])
const [platforms, productCategories, months] = await Promise.all([
groupByField('platformName'),
groupByField('productCategoryName'),
loadMonths(),
])
const [materialTypes, materialTypeTotal, categoryTotal] = await Promise.all([
loadMaterialTypes(),
countWithFilters('materialType'),
countWithFilters('category'),
])
const [sceneTotal, platformTotal, productCategoryTotal] = await Promise.all([
countWithFilters('scene'),
countWithFilters('platform'),
countWithFilters('productCategory'),
])
const monthTotal = await countWithFilters('month')
return NextResponse.json({
total,
categories,
scenes,
platforms,
productCategories,
months,
materialTypes,
totals: {
materialType: materialTypeTotal,
category: categoryTotal,
scene: sceneTotal,
platform: platformTotal,
productCategory: productCategoryTotal,
month: monthTotal,
},
})
} catch (error) {
if (error instanceof MaterialQueryError) {
return NextResponse.json(
{ error: error.message, code: error.code },
{ status: error.status },
)
}
console.error('[Materials Facets] Error:', error)
return NextResponse.json(
{ error: '获取首页筛选统计失败', code: 'SERVER_ERROR' },
{ status: 500 },
)
}
}