202 lines
5.5 KiB
TypeScript
202 lines
5.5 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { getServerSession } from 'next-auth'
|
|
import { authOptions } from '@/lib/auth'
|
|
import { prisma } from '@/lib/prisma'
|
|
import { createMaterialShareToken, findValidMaterialShareToken } from '@/lib/material-share-token'
|
|
import { toSharedMaterialAssetProxyUrl } from '@/lib/url-rewrite'
|
|
import {
|
|
canManageMaterialShare,
|
|
getMaterialShareActor,
|
|
} from '@/lib/material-share-permissions'
|
|
import { isPublicShareEnabled } from '@/lib/public-share-settings'
|
|
|
|
function parseDisplayImages(value: string | null): string[] {
|
|
if (!value) return []
|
|
try {
|
|
const parsed = JSON.parse(value)
|
|
if (Array.isArray(parsed)) {
|
|
return parsed.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
|
|
}
|
|
} catch (error) {
|
|
console.warn('[materials/share] Failed to parse displayImages JSON, fallback to single value:', error)
|
|
}
|
|
return value ? [value] : []
|
|
}
|
|
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
// 创建公开分享票据(登录用户)
|
|
export async function POST(req: NextRequest) {
|
|
try {
|
|
const session = await getServerSession(authOptions)
|
|
const actor = await getMaterialShareActor(session)
|
|
if (!actor) {
|
|
return NextResponse.json(
|
|
{ error: '请先登录' },
|
|
{ status: 401 }
|
|
)
|
|
}
|
|
|
|
if (!(await isPublicShareEnabled())) {
|
|
return NextResponse.json(
|
|
{ error: '公开分享功能已关闭' },
|
|
{ status: 403 }
|
|
)
|
|
}
|
|
|
|
const body = await req.json().catch(() => ({}))
|
|
const materialId = typeof body?.materialId === 'string' ? body.materialId.trim() : ''
|
|
|
|
if (!materialId) {
|
|
return NextResponse.json(
|
|
{ error: '缺少素材ID' },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
const material = await prisma.material.findFirst({
|
|
where: {
|
|
id: materialId,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
id: true,
|
|
userId: true,
|
|
ownerType: true,
|
|
ownerId: true,
|
|
createdByUserId: true,
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
department: true,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
if (!material) {
|
|
return NextResponse.json(
|
|
{ error: '素材不存在或已删除' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
if (!(await canManageMaterialShare(material, actor))) {
|
|
return NextResponse.json(
|
|
{ error: '无权分享该素材' },
|
|
{ status: 403 }
|
|
)
|
|
}
|
|
|
|
const tokenResult = await createMaterialShareToken(material.id, actor.id)
|
|
|
|
return NextResponse.json({
|
|
materialId: material.id,
|
|
shareToken: tokenResult.token,
|
|
expiresAt: tokenResult.expiresAt.toISOString(),
|
|
})
|
|
} catch (error) {
|
|
console.error('Error creating material share token:', error)
|
|
return NextResponse.json(
|
|
{ error: '创建分享链接失败' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
// Get single material for sharing (公开访问,但必须携带有效分享票据)
|
|
export async function GET(req: NextRequest) {
|
|
try {
|
|
const { searchParams } = new URL(req.url)
|
|
const id = searchParams.get('id')
|
|
const shareToken = searchParams.get('token') || searchParams.get('shareToken') || ''
|
|
|
|
if (!id) {
|
|
return NextResponse.json(
|
|
{ error: '缺少素材ID' },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
if (!(await isPublicShareEnabled())) {
|
|
return NextResponse.json(
|
|
{ error: '公开分享功能已关闭' },
|
|
{ status: 403 }
|
|
)
|
|
}
|
|
|
|
const validShareToken = await findValidMaterialShareToken(shareToken, id)
|
|
if (!validShareToken) {
|
|
return NextResponse.json(
|
|
{ error: '分享链接无效或已过期' },
|
|
{ status: 403 }
|
|
)
|
|
}
|
|
|
|
const material = await prisma.material.findFirst({
|
|
where: {
|
|
id,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
id: true,
|
|
previewUrl: true,
|
|
productModel: true,
|
|
brandName: true,
|
|
seriesName: true,
|
|
skuCode: true,
|
|
materialTexture: true,
|
|
categoryName: true,
|
|
sceneName: true,
|
|
materialType: true,
|
|
renderer: true,
|
|
renderTime: true,
|
|
imageResolution: true,
|
|
colorMode: true,
|
|
authorName: true,
|
|
zipFileName: true,
|
|
zipFileSize: true,
|
|
downloadCount: true,
|
|
likeCount: true,
|
|
displayImages: true,
|
|
createdAt: true,
|
|
isPremium: true,
|
|
videoCoverUrl: true,
|
|
videoPreview540Url: true,
|
|
videoPreviewClipUrl: true,
|
|
audioUrl: true,
|
|
}
|
|
})
|
|
|
|
if (!material) {
|
|
return NextResponse.json(
|
|
{ error: '素材不存在' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
// 转换BigInt为字符串
|
|
const materialData = {
|
|
...material,
|
|
zipFileSize: material.zipFileSize.toString(),
|
|
previewUrl: toSharedMaterialAssetProxyUrl(material.previewUrl, shareToken),
|
|
displayImages: parseDisplayImages(material.displayImages).map((url) =>
|
|
toSharedMaterialAssetProxyUrl(url, shareToken)
|
|
),
|
|
videoCoverUrl: toSharedMaterialAssetProxyUrl(material.videoCoverUrl, shareToken),
|
|
videoPreview540Url: toSharedMaterialAssetProxyUrl(material.videoPreview540Url, shareToken),
|
|
videoPreviewClipUrl: toSharedMaterialAssetProxyUrl(material.videoPreviewClipUrl, shareToken),
|
|
audioUrl: toSharedMaterialAssetProxyUrl(material.audioUrl, shareToken),
|
|
}
|
|
|
|
return NextResponse.json(materialData)
|
|
} catch (error) {
|
|
console.error('Error fetching material for share:', error)
|
|
return NextResponse.json(
|
|
{ error: '获取素材失败' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|