tfrere HF Staff commited on
Commit
ded4123
·
1 Parent(s): 9b7d64e

fix: robust published article restore on container rebuild

Browse files

- Add detailed logging to pullPublishedAssets for diagnostics
- Retry restore for anonymous visitors (dataset is public, no token needed)
- Add mutex to prevent concurrent pull attempts
- Add direct HTTP fetch fallback when Hub library fails
- Log SPACE_ID and HF_DATASET_ID at startup

Made-with: Cursor

Files changed (2) hide show
  1. backend/src/hf-storage.ts +37 -10
  2. backend/src/server.ts +30 -15
backend/src/hf-storage.ts CHANGED
@@ -149,40 +149,67 @@ export async function pullPublishedAssets(
149
  docName: string,
150
  localDir: string,
151
  ): Promise<boolean> {
152
- if (!HF_DATASET_ID) return false;
 
 
 
153
 
154
- // Token is optional: dataset is public, reads work without auth
155
- const accessToken = getToken() || undefined;
 
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
 
 
149
  docName: string,
150
  localDir: string,
151
  ): Promise<boolean> {
152
+ if (!HF_DATASET_ID) {
153
+ console.log("[hf-storage] pullPublished skipped: no dataset ID");
154
+ return false;
155
+ }
156
 
157
+ const token = getToken();
158
+ const accessToken = token || undefined;
159
+ console.log(`[hf-storage] pullPublished for "${docName}" from dataset ${HF_DATASET_ID} (token: ${token ? "yes" : "none - public access"})`);
160
 
161
  const safeName = sanitizeName(docName);
162
  const base = `published/${safeName}`;
163
  const files = ["index.html", "article.pdf", "thumb.jpg", "meta.json"];
164
 
165
+ const { mkdirSync, writeFileSync } = await import("fs");
166
+ const { join } = await import("path");
167
+ const outDir = join(localDir, "published", safeName);
168
+
169
  let foundAny = false;
170
 
171
  for (const file of files) {
172
  try {
173
+ // Try Hub library first
174
  const res = await downloadFile({
175
  repo,
176
  path: `${base}/${file}`,
177
  accessToken,
178
  });
179
+ if (!res) {
180
+ console.log(`[hf-storage] ${base}/${file}: not found via Hub lib`);
181
+ continue;
182
+ }
183
 
 
 
 
184
  mkdirSync(outDir, { recursive: true });
 
185
  const buf = Buffer.from(await res.arrayBuffer());
186
  writeFileSync(join(outDir, file), buf);
187
  console.log(`[hf-storage] pulled ${base}/${file} (${buf.length} bytes)`);
188
  foundAny = true;
189
+ } catch (err) {
190
+ console.warn(`[hf-storage] Hub lib failed for ${base}/${file}:`, (err as Error).message);
191
+
192
+ // Fallback: direct HTTP fetch (works for public datasets without auth)
193
+ try {
194
+ const url = `https://huggingface.co/datasets/${HF_DATASET_ID}/resolve/main/${base}/${file}`;
195
+ console.log(`[hf-storage] trying direct fetch: ${url}`);
196
+ const httpRes = await fetch(url);
197
+ if (!httpRes.ok) {
198
+ console.log(`[hf-storage] direct fetch ${file}: ${httpRes.status}`);
199
+ continue;
200
+ }
201
+ mkdirSync(outDir, { recursive: true });
202
+ const buf = Buffer.from(await httpRes.arrayBuffer());
203
+ writeFileSync(join(outDir, file), buf);
204
+ console.log(`[hf-storage] pulled ${base}/${file} via direct fetch (${buf.length} bytes)`);
205
+ foundAny = true;
206
+ } catch (fetchErr) {
207
+ console.warn(`[hf-storage] direct fetch also failed for ${file}:`, (fetchErr as Error).message);
208
+ }
209
  }
210
  }
211
 
212
+ console.log(`[hf-storage] pullPublished result: ${foundAny ? "restored" : "nothing found"}`);
213
  return foundAny;
214
  }
215
 
backend/src/server.ts CHANGED
@@ -35,10 +35,12 @@ const DEFAULT_DOC_NAME = "default";
35
 
36
  /**
37
  * Lazy restore of published assets from HF dataset.
38
- * Called at startup (may fail without token) and again on first auth.
39
- * Only runs the actual pull once successfully.
40
  */
41
  let _publishedRestored = false;
 
 
42
  async function ensurePublishedRestored(token?: string): Promise<void> {
43
  if (_publishedRestored) return;
44
  if (!getDatasetId()) return;
@@ -49,17 +51,29 @@ async function ensurePublishedRestored(token?: string): Promise<void> {
49
  return;
50
  }
51
 
 
 
 
 
 
 
52
  if (token) setUserToken(token);
53
 
54
- try {
55
- const found = await pullPublishedAssets(DEFAULT_DOC_NAME, DATA_DIR);
56
- if (found) {
57
- _publishedRestored = true;
58
- console.log("[server] restored published article from HF dataset");
 
 
 
 
 
 
59
  }
60
- } catch (err) {
61
- console.warn("[server] failed to restore published:", (err as Error).message);
62
- }
63
  }
64
 
65
  mkdirSync(DATA_DIR, { recursive: true });
@@ -384,11 +398,11 @@ if (existsSync(staticDir)) {
384
  return;
385
  }
386
 
387
- // Try lazy-restore if we have a cookie token (visitor may be logged in)
 
 
388
  const visitorToken = extractToken(req.headers.cookie) ?? undefined;
389
- if (visitorToken) {
390
- await ensurePublishedRestored(visitorToken);
391
- }
392
 
393
  // In production with OAuth: serve the published article if it exists
394
  const publishedPath = getPublishedPath(DEFAULT_DOC_NAME);
@@ -408,8 +422,9 @@ if (existsSync(staticDir)) {
408
  const server = httpServer.listen(PORT, async () => {
409
  console.log(`[server] running on http://localhost:${PORT}`);
410
  console.log(`[server] collab websocket at ws://localhost:${PORT}/collab/:doc`);
 
411
 
412
- // Try eager restore (works if HF_TOKEN env var is set)
413
  await ensurePublishedRestored();
414
  });
415
 
 
35
 
36
  /**
37
  * Lazy restore of published assets from HF dataset.
38
+ * Called at startup and on every request until successful.
39
+ * Dataset is public so no token is required for reads.
40
  */
41
  let _publishedRestored = false;
42
+ let _restoreInProgress: Promise<void> | null = null;
43
+
44
  async function ensurePublishedRestored(token?: string): Promise<void> {
45
  if (_publishedRestored) return;
46
  if (!getDatasetId()) return;
 
51
  return;
52
  }
53
 
54
+ // Prevent concurrent pulls (multiple requests arriving at the same time)
55
+ if (_restoreInProgress) {
56
+ await _restoreInProgress;
57
+ return;
58
+ }
59
+
60
  if (token) setUserToken(token);
61
 
62
+ _restoreInProgress = (async () => {
63
+ try {
64
+ const found = await pullPublishedAssets(DEFAULT_DOC_NAME, DATA_DIR);
65
+ if (found) {
66
+ _publishedRestored = true;
67
+ console.log("[server] restored published article from HF dataset");
68
+ }
69
+ } catch (err) {
70
+ console.warn("[server] failed to restore published:", (err as Error).message);
71
+ } finally {
72
+ _restoreInProgress = null;
73
  }
74
+ })();
75
+
76
+ await _restoreInProgress;
77
  }
78
 
79
  mkdirSync(DATA_DIR, { recursive: true });
 
398
  return;
399
  }
400
 
401
+ // Try lazy-restore on every request until successful.
402
+ // Dataset is public, so no token is needed for reads.
403
+ // An OAuth token is passed when available for extra reliability.
404
  const visitorToken = extractToken(req.headers.cookie) ?? undefined;
405
+ await ensurePublishedRestored(visitorToken);
 
 
406
 
407
  // In production with OAuth: serve the published article if it exists
408
  const publishedPath = getPublishedPath(DEFAULT_DOC_NAME);
 
422
  const server = httpServer.listen(PORT, async () => {
423
  console.log(`[server] running on http://localhost:${PORT}`);
424
  console.log(`[server] collab websocket at ws://localhost:${PORT}/collab/:doc`);
425
+ console.log(`[server] SPACE_ID=${process.env.SPACE_ID || "(not set)"} HF_DATASET_ID=${getDatasetId() || "(empty)"} HF_TOKEN=${process.env.HF_TOKEN ? "set" : "not set"}`);
426
 
427
+ // Eager restore: dataset is public, works without token
428
  await ensurePublishedRestored();
429
  });
430