import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import { ownerRoot } from '~/server/utils/library' import { normalizeDirectorScriptName, normalizeDirectorScriptText, type DirectorScript } from '~/utils/directorScripts' const writeChains = new Map>() function scriptsPath(owner: string) { return join(ownerRoot(owner), 'director-scripts.json') } function ensureOwner(owner: string) { mkdirSync(ownerRoot(owner), { recursive: true }) } function normalizeStored(raw: unknown): DirectorScript | null { if (!raw || typeof raw !== 'object') return null const rec = raw as Record const name = normalizeDirectorScriptName(rec.name) const text = normalizeDirectorScriptText(rec.text) if (!name || !text.trim()) return null const createdAt = Number(rec.createdAt) || Date.now() return { id: String(rec.id || '').trim() || crypto.randomUUID(), name, text, createdAt, updatedAt: Number(rec.updatedAt) || createdAt } } function readScripts(owner: string): DirectorScript[] { ensureOwner(owner) const path = scriptsPath(owner) if (!existsSync(path)) return [] try { const parsed = JSON.parse(readFileSync(path, 'utf8')) return Array.isArray(parsed) ? parsed.map(normalizeStored).filter((item): item is DirectorScript => Boolean(item)) : [] } catch { return [] } } function writeScripts(owner: string, scripts: DirectorScript[]) { ensureOwner(owner) const path = scriptsPath(owner) const tmp = `${path}.tmp` writeFileSync(tmp, JSON.stringify(scripts, null, 2)) renameSync(tmp, path) } function mutate(owner: string, fn: (scripts: DirectorScript[]) => T): Promise { const prev = writeChains.get(owner) || Promise.resolve() const run = prev.then(() => { const scripts = readScripts(owner) const result = fn(scripts) writeScripts(owner, scripts) return result }) writeChains.set(owner, run.then(() => undefined, () => undefined)) return run } export function listDirectorScripts(owner: string) { return readScripts(owner).sort((a, b) => b.updatedAt - a.updatedAt || a.name.localeCompare(b.name)) } export function getDirectorScript(owner: string, id: string) { const wanted = String(id || '').trim() const script = readScripts(owner).find(item => item.id === wanted) if (!script) throw createError({ statusCode: 404, statusMessage: 'Script not found' }) return script } export async function saveDirectorScript(owner: string, input: { name: string text: string overwrite?: boolean }) { const name = normalizeDirectorScriptName(input.name) const text = normalizeDirectorScriptText(input.text) if (!name) throw createError({ statusCode: 400, statusMessage: 'A script name is required' }) if (!text.trim()) throw createError({ statusCode: 400, statusMessage: 'A shot script is required' }) const now = Date.now() return mutate(owner, (scripts) => { const existing = scripts.find(item => item.name.toLowerCase() === name.toLowerCase()) if (existing && !input.overwrite) { throw createError({ statusCode: 409, statusMessage: `Overwrite script “${existing.name}”?` }) } if (existing) { existing.text = text existing.updatedAt = now return existing } const created: DirectorScript = { id: crypto.randomUUID(), name, text, createdAt: now, updatedAt: now } scripts.unshift(created) return created }) } export async function updateDirectorScript(owner: string, id: string, input: { name?: string text?: string }) { const wanted = String(id || '').trim() if (!wanted) throw createError({ statusCode: 400, statusMessage: 'Missing script id' }) return mutate(owner, (scripts) => { const existing = scripts.find(item => item.id === wanted) if (!existing) throw createError({ statusCode: 404, statusMessage: 'Script not found' }) if (input.name !== undefined) { const name = normalizeDirectorScriptName(input.name) if (!name) throw createError({ statusCode: 400, statusMessage: 'A script name is required' }) const clash = scripts.find(item => item.id !== wanted && item.name.toLowerCase() === name.toLowerCase()) if (clash) throw createError({ statusCode: 409, statusMessage: `A script named “${clash.name}” already exists` }) existing.name = name } if (input.text !== undefined) { const text = normalizeDirectorScriptText(input.text) if (!text.trim()) throw createError({ statusCode: 400, statusMessage: 'A shot script is required' }) existing.text = text } existing.updatedAt = Date.now() return existing }) } export async function deleteDirectorScript(owner: string, id: string) { const wanted = String(id || '').trim() if (!wanted) throw createError({ statusCode: 400, statusMessage: 'Missing script id' }) return mutate(owner, (scripts) => { const index = scripts.findIndex(item => item.id === wanted) if (index < 0) throw createError({ statusCode: 404, statusMessage: 'Script not found' }) const [removed] = scripts.splice(index, 1) return removed }) }