feat(storage): migrate Life OS cloud state to versioned records

This commit is contained in:
Codex committed 2026-09-28 18:38:29 +08:00
1 parent 6972a00445
commit 1606c41f43
17 files changed
+502 -72

No files matched your search

+2 -29
View File
@@ -1,7 +1,7 @@
import { addonsJs, bootGuardJs, i18nJs, localeEn, localeZh, localeEnJs, localeZhJs, tasksJs, calendarJs, i18nRenderersJs, browserAppJs, dataActionsJs, terminalAppJs, indexHtml, manifest, serviceWorker, icon } from "./assets.js";
import { cloudApi } from "./cloud-state.js";
const BING_ARCHIVE = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=en-US";
const STATE_FIELDS = ["profile", "memories", "museum", "settings", "system"];
async function bingMetadata() {
const response = await fetch(BING_ARCHIVE, { headers: { accept: "application/json" } });
@@ -21,38 +21,11 @@ function loginPage(request) {
return `<!doctype html><html lang="${language === localeZh ? "zh-CN" : "en"}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Life OS</title><style>html,body{margin:0;min-height:100%;font-family:ui-sans-serif,system-ui,sans-serif;background:#121513;color:#f4f6f1}body{display:grid;place-items:center;padding:24px;box-sizing:border-box}.panel{width:min(430px,100%);border:1px solid #414942;border-radius:22px;padding:34px;background:#1a201b;box-shadow:0 28px 70px #0008}.mark{font:600 11px ui-monospace,monospace;letter-spacing:.2em;color:#b8ff3d}.title{font-size:36px;letter-spacing:-.06em;margin:18px 0 10px}.copy{color:#afb9b1;line-height:1.65;margin:0 0 26px}.sign{display:flex;align-items:center;justify-content:center;text-decoration:none;background:#b8ff3d;color:#11170d;padding:14px 18px;border-radius:12px;font-size:14px;font-weight:700}.fine{margin:18px 0 0;color:#7e8a81;font:11px ui-monospace,monospace;letter-spacing:.04em;text-align:center}</style></head><body><main class="panel"><div class="mark">LIFE OS</div><h1 class="title">${text.title}</h1><p class="copy">${escape(text.description)}</p><a class="sign" href="/signin-with-chatgpt?return_to=/" target="_top">${escape(text.continue)}</a><p class="fine">${escape(text.footnote)}</p></main></body></html>`;
}
function validState(value) { return value && typeof value === "object" && !Array.isArray(value) && STATE_FIELDS.every((field) => Object.hasOwn(value, field)); }
function stateFromRow(row) { return row ? Object.fromEntries(STATE_FIELDS.map((field) => [field, JSON.parse(row[`${field}_json`])])) : null; }
async function stateApi(request, env, id) {
if (!id) return Response.json({ error: "Authentication required" }, { status: 401 });
if (!env.DB) return Response.json({ error: "Cloud storage is unavailable" }, { status: 503 });
try {
if (request.method === "GET") {
const row = await env.DB.prepare("SELECT profile_json, memories_json, museum_json, settings_json, system_json, updated_at FROM lifeos_states WHERE user_id = ?").bind(id).first();
return Response.json({ state: stateFromRow(row), updatedAt: row?.updated_at ?? null }, { headers: { "cache-control": "no-store" } });
}
if (request.method === "PUT") {
const payload = await request.json();
if (!validState(payload?.state)) return Response.json({ error: "Invalid Life OS state" }, { status: 400 });
const state = payload.state;
const serialized = STATE_FIELDS.map((field) => JSON.stringify(state[field]));
if (serialized.some((value) => value.length > 1_000_000)) return Response.json({ error: "Life OS state is too large" }, { status: 413 });
const updatedAt = new Date().toISOString();
await env.DB.prepare("INSERT INTO lifeos_states (user_id, profile_json, memories_json, museum_json, settings_json, system_json, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET profile_json = excluded.profile_json, memories_json = excluded.memories_json, museum_json = excluded.museum_json, settings_json = excluded.settings_json, system_json = excluded.system_json, updated_at = excluded.updated_at").bind(id, ...serialized, updatedAt).run();
return Response.json({ ok: true, updatedAt }, { headers: { "cache-control": "no-store" } });
}
return Response.json({ error: "Method not allowed" }, { status: 405, headers: { allow: "GET, PUT" } });
} catch (error) {
return Response.json({ error: "Cloud storage request failed", detail: String(error) }, { status: 500 });
}
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
const id = userId(request);
if (url.pathname === "/api/lifeos-state") return stateApi(request, env, id);
if (["/api/lifeos-state", "/api/lifeos-initialize", "/api/lifeos-sync"].includes(url.pathname)) return cloudApi(request, env, id, url.pathname);
try {
if (url.pathname === "/api/bing-wallpaper") return Response.json(await bingMetadata(), { headers: { "cache-control": "public, max-age=3600" } });
if (url.pathname === "/api/bing-wallpaper-image") {