import type { Database } from 'better-sqlite3' import { access, mkdir, rename } from 'node:fs/promises' import { dirname, resolve, sep } from 'node:path' export interface MediaAssetRow { id: number asset_key: string original_name: string mime_type: string extension: string bytes: number width: number height: number original_path: string derived_path: string thumbnail_path: string alt_text: string source_text: string copyright_text: string authorization_status: 'pending' | 'authorized' | 'restricted' status: 'active' | 'trash' | 'deleted' created_at: string updated_at: string deleted_at: string | null } export const mediaUrl = (relativePath: string) => `/api/media/${relativePath}` export function serializeMedia(row: MediaAssetRow, referenced = false) { return { id: row.id, key: row.asset_key, originalName: row.original_name, mimeType: row.mime_type, bytes: row.bytes, width: row.width, height: row.height, url: mediaUrl(row.derived_path), thumbnailUrl: mediaUrl(row.thumbnail_path), altText: row.alt_text, source: row.source_text, copyright: row.copyright_text, authorizationStatus: row.authorization_status, status: row.status, referenced, createdAt: row.created_at, deletedAt: row.deleted_at, } } export function isMediaReferenced(db: Database, row: MediaAssetRow) { const document = db.prepare('SELECT draft_json, published_json FROM site_documents WHERE document_key = ?').get('website') as { draft_json: string; published_json: string } const needles = [mediaUrl(row.original_path), mediaUrl(row.derived_path), mediaUrl(row.thumbnail_path)] return needles.some(path => document.draft_json.includes(path) || document.published_json.includes(path)) } export function uploadsRoot() { return resolve(process.cwd(), 'data', 'uploads') } export function safeUploadPath(relativePath: string) { const root = uploadsRoot() const fullPath = resolve(root, relativePath) if (!fullPath.startsWith(`${root}${sep}`)) throw createError({ statusCode: 400, statusMessage: '无效的文件路径' }) return fullPath } export async function moveMediaFiles(row: MediaAssetRow, target: 'trash' | 'restore') { const paths = [row.original_path, row.derived_path, row.thumbnail_path] for (const current of paths) { const sourceRelative = target === 'trash' ? current : current.replace(/^trash\//, '') const destinationRelative = target === 'trash' ? `trash/${current}` : current.replace(/^trash\//, '') const source = safeUploadPath(target === 'trash' ? sourceRelative : `trash/${sourceRelative}`) const destination = safeUploadPath(destinationRelative) try { await access(source) await mkdir(dirname(destination), { recursive: true }) await rename(source, destination) } catch (error: any) { if (error?.code !== 'ENOENT') throw error } } }