60 lines
1.6 KiB
TypeScript
60 lines
1.6 KiB
TypeScript
import { mkdir, writeFile } from 'fs/promises'
|
|
import path from 'path'
|
|
import { randomUUID } from 'crypto'
|
|
|
|
import {
|
|
buildTaskDetailPublicDisplayPath,
|
|
uploadPublicDisplayToS3,
|
|
} from '@/lib/public-display'
|
|
import { resolveStoredUploadPath } from '@/lib/upload-paths'
|
|
|
|
export type TaskDetailPreviewGroup = 'main' | 'sku' | 'detail'
|
|
|
|
function buildPrivatePreviewFileName(
|
|
group: TaskDetailPreviewGroup,
|
|
fileName: string,
|
|
): string {
|
|
const normalizedFileName = String(fileName || '').trim().replace(/\\/g, '/')
|
|
const parsed = path.posix.parse(path.posix.basename(normalizedFileName))
|
|
const safeBaseName = (parsed.name || `${group}-preview`)
|
|
.replace(/[\/\\]/g, '_')
|
|
.replace(/\.\./g, '_')
|
|
.replace(/[\x00-\x1f]/g, '')
|
|
.replace(/[<>:"|?*]/g, '_')
|
|
.trim()
|
|
.slice(0, 80)
|
|
const uniqueBaseName = safeBaseName || `${group}-preview`
|
|
return `${Date.now()}-${randomUUID()}-${uniqueBaseName}.webp`
|
|
}
|
|
|
|
export async function saveTaskDetailPreview(
|
|
buffer: Buffer,
|
|
materialId: string,
|
|
group: TaskDetailPreviewGroup,
|
|
fileName: string,
|
|
isPrivate: boolean,
|
|
): Promise<string> {
|
|
if (!isPrivate) {
|
|
return uploadPublicDisplayToS3(
|
|
buffer,
|
|
buildTaskDetailPublicDisplayPath(materialId, group, fileName),
|
|
'image/webp',
|
|
)
|
|
}
|
|
|
|
const storedPath = path.posix.join(
|
|
'uploads',
|
|
'materials',
|
|
materialId,
|
|
'private-previews',
|
|
group,
|
|
buildPrivatePreviewFileName(group, fileName),
|
|
)
|
|
const absolutePath = resolveStoredUploadPath(storedPath)
|
|
|
|
await mkdir(path.dirname(absolutePath), { recursive: true })
|
|
await writeFile(absolutePath, buffer)
|
|
|
|
return storedPath
|
|
}
|