141 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 }
);
}
}