feat: 初始化慧遇书院官网一期
This commit is contained in:
81
server/utils/media.ts
Normal file
81
server/utils/media.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user