141 lines
4.3 KiB
TypeScript
141 lines
4.3 KiB
TypeScript
import { buildMaterialPrivacyWhere } from '@/lib/task-detail-privacy';
|
||
import { NextRequest, NextResponse } from 'next/server';
|
||
import { getServerSession } from 'next-auth';
|
||
import { authOptions } from '@/lib/auth';
|
||
import { prisma } from '@/lib/prisma';
|
||
import { normalizeMaterial } from '@/lib/utils';
|
||
import { rewriteMaterialStorageUrlsForClient } from '@/lib/url-rewrite';
|
||
|
||
// 强制动态渲染
|
||
export const dynamic = 'force-dynamic';
|
||
|
||
// 解析 displayImages 字段
|
||
function parseDisplayImages(displayImages: any): string[] {
|
||
if (!displayImages) return [];
|
||
|
||
if (Array.isArray(displayImages)) {
|
||
return displayImages.filter(img => typeof img === 'string' && img.trim().length > 0);
|
||
}
|
||
|
||
if (typeof displayImages === 'string') {
|
||
try {
|
||
const parsed = JSON.parse(displayImages);
|
||
if (Array.isArray(parsed)) {
|
||
return parsed.filter(img => typeof img === 'string' && img.trim().length > 0);
|
||
}
|
||
} catch (e) {
|
||
console.warn('[user/favorites] Failed to parse displayImages JSON, fallback to single value:', e);
|
||
}
|
||
return displayImages.trim() ? [displayImages] : [];
|
||
}
|
||
|
||
return [];
|
||
}
|
||
|
||
// GET - 获取当前用户的收藏列表
|
||
export async function GET(request: NextRequest) {
|
||
try {
|
||
const session = await getServerSession(authOptions);
|
||
|
||
if (!session?.user || !(session.user as any).id) {
|
||
return NextResponse.json({ error: '未登录' }, { status: 401 });
|
||
}
|
||
|
||
const userId = (session.user as any).id;
|
||
|
||
// 查询收藏记录,包含素材信息
|
||
const favorites = await prisma.materialFavorite.findMany({
|
||
where: {
|
||
userId,
|
||
material: await buildMaterialPrivacyWhere(userId),
|
||
},
|
||
include: {
|
||
material: {
|
||
select: {
|
||
id: true,
|
||
productModel: true,
|
||
previewUrl: true,
|
||
displayImages: true,
|
||
materialType: true,
|
||
categoryName: true,
|
||
sceneName: true,
|
||
platformName: true,
|
||
productCategoryName: true,
|
||
renderer: true,
|
||
downloadCount: true,
|
||
likeCount: true,
|
||
createdAt: true,
|
||
userId: true,
|
||
videoSourceUrl: true,
|
||
videoPreview540Url: true,
|
||
videoPreviewClipUrl: true,
|
||
videoCoverUrl: true,
|
||
videoProcessingStatus: true,
|
||
videoDuration: true,
|
||
zipFileName: true,
|
||
zipFileSize: true,
|
||
deletedAt: true,
|
||
} as any,
|
||
},
|
||
},
|
||
orderBy: {
|
||
createdAt: 'desc',
|
||
},
|
||
});
|
||
|
||
// 提取素材,过滤已删除的
|
||
let materials = favorites
|
||
.map((fav) => fav.material)
|
||
.filter((material) => !!material && !material.deletedAt)
|
||
.map((material: any) => {
|
||
const zipFileSize = typeof material.zipFileSize === 'bigint'
|
||
? material.zipFileSize.toString()
|
||
: material.zipFileSize ?? null;
|
||
|
||
const formatted = {
|
||
...material,
|
||
zipFileSize,
|
||
displayImages: parseDisplayImages(material.displayImages || null),
|
||
isFavorited: true,
|
||
isLiked: false,
|
||
};
|
||
|
||
// 使用智能 URL 重写(根据用户网络位置选择内网或外网地址)
|
||
return rewriteMaterialStorageUrlsForClient(normalizeMaterial(formatted), request);
|
||
});
|
||
|
||
// 批量查询点赞状态
|
||
if (materials.length > 0) {
|
||
const materialIds = materials.map((m) => m.id);
|
||
const likes = await prisma.materialLike.findMany({
|
||
where: {
|
||
userId,
|
||
materialId: { in: materialIds },
|
||
},
|
||
select: { materialId: true },
|
||
});
|
||
|
||
const likedSet = new Set(likes.map((like) => like.materialId));
|
||
materials.forEach((material) => {
|
||
material.isLiked = likedSet.has(material.id);
|
||
});
|
||
}
|
||
|
||
return NextResponse.json(materials, { headers: { 'Cache-Control': 'private, no-store' } });
|
||
} catch (error: any) {
|
||
// 兼容旧版本数据库(没有material_favorites表)
|
||
if (error?.meta?.table?.includes('material_favorites')) {
|
||
return NextResponse.json(
|
||
{ error: '收藏功能需要先运行数据库迁移' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
console.error('[UserFavorites] GET error:', error);
|
||
return NextResponse.json(
|
||
{ error: '获取收藏失败' },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|