feat: restore published article from HF dataset on startup
Browse filesAfter a container rebuild, the local data directory is empty.
On boot, the server now pulls published assets (HTML, PDF, thumbnail,
meta) from the HF dataset back to local storage so the published
article is served immediately without needing to re-publish.
Made-with: Cursor
- backend/src/hf-storage.ts +46 -0
- backend/src/server.ts +13 -1
backend/src/hf-storage.ts
CHANGED
|
@@ -140,6 +140,52 @@ export async function flushAll(): Promise<void> {
|
|
| 140 |
await Promise.allSettled(names.map((n) => pushDocument(n)));
|
| 141 |
}
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
// ---------- Published Assets ----------
|
| 144 |
|
| 145 |
interface PublishedPayload {
|
|
|
|
| 140 |
await Promise.allSettled(names.map((n) => pushDocument(n)));
|
| 141 |
}
|
| 142 |
|
| 143 |
+
/**
|
| 144 |
+
* Pull published assets (HTML, PDF, thumbnail, meta) from the HF dataset
|
| 145 |
+
* and write them to the local data directory so the server can serve them.
|
| 146 |
+
* Called on startup to restore published state after a container rebuild.
|
| 147 |
+
*/
|
| 148 |
+
export async function pullPublishedAssets(
|
| 149 |
+
docName: string,
|
| 150 |
+
localDir: string,
|
| 151 |
+
): Promise<boolean> {
|
| 152 |
+
if (!HF_DATASET_ID) return false;
|
| 153 |
+
|
| 154 |
+
const accessToken = getToken();
|
| 155 |
+
if (!accessToken) return false;
|
| 156 |
+
|
| 157 |
+
const safeName = sanitizeName(docName);
|
| 158 |
+
const base = `published/${safeName}`;
|
| 159 |
+
const files = ["index.html", "article.pdf", "thumb.jpg", "meta.json"];
|
| 160 |
+
|
| 161 |
+
let foundAny = false;
|
| 162 |
+
|
| 163 |
+
for (const file of files) {
|
| 164 |
+
try {
|
| 165 |
+
const res = await downloadFile({
|
| 166 |
+
repo,
|
| 167 |
+
path: `${base}/${file}`,
|
| 168 |
+
accessToken,
|
| 169 |
+
});
|
| 170 |
+
if (!res) continue;
|
| 171 |
+
|
| 172 |
+
const { mkdirSync, writeFileSync } = await import("fs");
|
| 173 |
+
const { join } = await import("path");
|
| 174 |
+
const outDir = join(localDir, "published", safeName);
|
| 175 |
+
mkdirSync(outDir, { recursive: true });
|
| 176 |
+
|
| 177 |
+
const buf = Buffer.from(await res.arrayBuffer());
|
| 178 |
+
writeFileSync(join(outDir, file), buf);
|
| 179 |
+
console.log(`[hf-storage] pulled ${base}/${file} (${buf.length} bytes)`);
|
| 180 |
+
foundAny = true;
|
| 181 |
+
} catch {
|
| 182 |
+
// File may not exist (e.g. PDF not generated); skip silently
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
return foundAny;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
// ---------- Published Assets ----------
|
| 190 |
|
| 191 |
interface PublishedPayload {
|
backend/src/server.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
| 19 |
setUserToken,
|
| 20 |
uploadImageToHf,
|
| 21 |
pullDocument,
|
|
|
|
| 22 |
schedulePush,
|
| 23 |
flushAll,
|
| 24 |
} from "./hf-storage.js";
|
|
@@ -406,9 +407,20 @@ if (existsSync(staticDir)) {
|
|
| 406 |
|
| 407 |
// ---------- Start ----------
|
| 408 |
|
| 409 |
-
const server = httpServer.listen(PORT, () => {
|
| 410 |
console.log(`[server] running on http://localhost:${PORT}`);
|
| 411 |
console.log(`[server] collab websocket at ws://localhost:${PORT}/collab/:doc`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
});
|
| 413 |
|
| 414 |
// Graceful shutdown: flush pending docs to HF
|
|
|
|
| 19 |
setUserToken,
|
| 20 |
uploadImageToHf,
|
| 21 |
pullDocument,
|
| 22 |
+
pullPublishedAssets,
|
| 23 |
schedulePush,
|
| 24 |
flushAll,
|
| 25 |
} from "./hf-storage.js";
|
|
|
|
| 407 |
|
| 408 |
// ---------- Start ----------
|
| 409 |
|
| 410 |
+
const server = httpServer.listen(PORT, async () => {
|
| 411 |
console.log(`[server] running on http://localhost:${PORT}`);
|
| 412 |
console.log(`[server] collab websocket at ws://localhost:${PORT}/collab/:doc`);
|
| 413 |
+
|
| 414 |
+
// Restore published articles from HF dataset after a container rebuild
|
| 415 |
+
if (getDatasetId()) {
|
| 416 |
+
try {
|
| 417 |
+
const found = await pullPublishedAssets(DEFAULT_DOC_NAME, DATA_DIR);
|
| 418 |
+
if (found) console.log("[server] restored published article from HF dataset");
|
| 419 |
+
else console.log("[server] no published article found in HF dataset");
|
| 420 |
+
} catch (err) {
|
| 421 |
+
console.warn("[server] failed to restore published article:", (err as Error).message);
|
| 422 |
+
}
|
| 423 |
+
}
|
| 424 |
});
|
| 425 |
|
| 426 |
// Graceful shutdown: flush pending docs to HF
|