fix(backend): enhance sequence discovery from sister website directories and sync in post-receive

This commit is contained in:
Your Name committed 2026-10-17 11:26:29 +07:00
1 parent 0bf544720b
commit ca79692b3e
3 files changed
+106 -45

No files matched your search

+92 -42
View File
@@ -19,61 +19,111 @@ export interface FrameSequenceManifest {
const getBaseDir = () => join(process.cwd(), "public", "frame-sequences");
export const getCandidateSequenceDirs = async (): Promise<string[]> => {
const candidates: string[] = [
getBaseDir(),
join(process.cwd(), "..", "website", "public", "frame-sequences"),
join(process.cwd(), "..", "..", "website", "public", "frame-sequences"),
];
try {
// Check sister projects in deployment layout (e.g. /home/ti/projects/ddc-website/apps/website/public/frame-sequences)
const possibleProjectRoots = [
join(process.cwd(), "..", ".."),
join(process.cwd(), "..", "..", ".."),
];
for (const root of possibleProjectRoots) {
if (existsSync(root)) {
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
for (const e of entries) {
if (e.isDirectory() && (e.name.includes("website") || e.name.includes("shop"))) {
candidates.push(join(root, e.name, "apps", "website", "public", "frame-sequences"));
candidates.push(join(root, e.name, "public", "frame-sequences"));
}
}
}
}
} catch (err) {
// non-fatal
}
return Array.from(new Set(candidates.filter((d) => existsSync(d))));
};
export const frameSequencesRoute = new Elysia({ prefix: "/frame-sequences" })
// 1. List all Frame Sequences (Reads manifests from public/frame-sequences/*)
// 1. List all Frame Sequences (Reads manifests from public/frame-sequences/* and candidate directories)
.get("/", async () => {
const baseDir = getBaseDir();
if (!existsSync(baseDir)) {
await mkdir(baseDir, { recursive: true });
return { sequences: [] };
await mkdir(baseDir, { recursive: true }).catch(() => {});
}
const entries = await readdir(baseDir, { withFileTypes: true });
const searchDirs = await getCandidateSequenceDirs();
const seenSlugs = new Set<string>();
const sequences: FrameSequenceManifest[] = [];
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const folderPath = join(baseDir, entry.name);
const manifestPath = join(folderPath, "manifest.json");
if (existsSync(manifestPath)) {
try {
const raw = await readFile(manifestPath, "utf8");
const manifest: FrameSequenceManifest = JSON.parse(raw);
sequences.push(manifest);
continue;
} catch (e) {
console.error(`Failed to parse manifest in ${entry.name}`, e);
}
}
// Auto-detect frames if no manifest exists
for (const dir of searchDirs) {
try {
const files = await readdir(folderPath);
const imageFiles = files
.filter((f) => /\.(webp|png|jpe?g)$/i.test(f) && !f.startsWith("."))
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (seenSlugs.has(entry.name)) continue;
if (imageFiles.length > 0) {
const autoManifest: FrameSequenceManifest = {
id: `seq_${entry.name}`,
name: entry.name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
slug: entry.name,
folderPath: `/frame-sequences/${entry.name}`,
pattern: "frame-{index}.webp",
padding: 3,
totalFrames: imageFiles.length,
fps: 24,
durationSeconds: parseFloat((imageFiles.length / 24).toFixed(1)),
posterUrl: `/frame-sequences/${entry.name}/${imageFiles[0]}`,
createdAt: new Date().toISOString(),
};
const folderPath = join(dir, entry.name);
const manifestPath = join(folderPath, "manifest.json");
await writeFile(manifestPath, JSON.stringify(autoManifest, null, 2), "utf8");
sequences.push(autoManifest);
if (existsSync(manifestPath)) {
try {
const raw = await readFile(manifestPath, "utf8");
const manifest: FrameSequenceManifest = JSON.parse(raw);
seenSlugs.add(entry.name);
sequences.push(manifest);
// Auto-sync manifest to local backend baseDir if not present
const localSeqDir = join(baseDir, entry.name);
if (!existsSync(localSeqDir)) {
await mkdir(localSeqDir, { recursive: true }).catch(() => {});
await writeFile(join(localSeqDir, "manifest.json"), raw, "utf8").catch(() => {});
}
continue;
} catch (e) {
console.error(`Failed to parse manifest in ${entry.name}`, e);
}
}
// Auto-detect frames if no manifest exists
try {
const files = await readdir(folderPath);
const imageFiles = files
.filter((f) => /\.(webp|png|jpe?g)$/i.test(f) && !f.startsWith("."))
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
if (imageFiles.length > 0) {
const autoManifest: FrameSequenceManifest = {
id: `seq_${entry.name}`,
name: entry.name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
slug: entry.name,
folderPath: `/frame-sequences/${entry.name}`,
pattern: "frame-{index}.webp",
padding: 3,
totalFrames: imageFiles.length,
fps: 24,
durationSeconds: parseFloat((imageFiles.length / 24).toFixed(1)),
posterUrl: `/frame-sequences/${entry.name}/${imageFiles[0]}`,
createdAt: new Date().toISOString(),
};
await writeFile(manifestPath, JSON.stringify(autoManifest, null, 2), "utf8").catch(() => {});
seenSlugs.add(entry.name);
sequences.push(autoManifest);
}
} catch (scanErr) {
console.error(`Error scanning sequence folder ${entry.name}`, scanErr);
}
}
} catch (err) {
console.error(`Error scanning sequence folder ${entry.name}`, err);
// Continue to next candidate dir
}
}