From e7aff14c14c05fa57f591b569c0a4b16228d928d Mon Sep 17 00:00:00 2001 From: Reynaldi Chernando Date: Wed, 11 Feb 2026 13:30:00 +0700 Subject: [PATCH 1/2] Refactor workers --- app/routes/_index.tsx | 1 + app/routes/visualizer.$id.tsx | 1 + lib/puter.worker.js | 112 +++++++--------------------------- type.d.ts | 1 + 4 files changed, 26 insertions(+), 89 deletions(-) diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index dd22980..1615ff4 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -165,6 +165,7 @@ export default function IndexRoute() { initialRender: item.renderedImage || null, ownerId: item.ownerId || null, name: item.name || null, + sharedBy: item.sharedBy || null, }, }, ); diff --git a/app/routes/visualizer.$id.tsx b/app/routes/visualizer.$id.tsx index ff12153..629af70 100644 --- a/app/routes/visualizer.$id.tsx +++ b/app/routes/visualizer.$id.tsx @@ -131,6 +131,7 @@ export default function VisualizerRoute() { timestamp: Date.now(), ownerId: state.ownerId || queryOwnerId || null, isPublic: isPublicProject, + sharedBy: state.sharedBy || null, }; setResolvedItem(item); setUploadedImage(state.initialImage); diff --git a/lib/puter.worker.js b/lib/puter.worker.js index 3596474..3502adc 100644 --- a/lib/puter.worker.js +++ b/lib/puter.worker.js @@ -4,8 +4,6 @@ const PUBLIC_PREFIX = "roomify_public_"; const USER_PREFIX = "roomify_user_"; const getUserPuter = (userParam) => userParam?.puter || null; -const getMePuter = (meParam) => - meParam?.puter || (typeof me !== "undefined" ? me?.puter : null); const jsonError = (status, message, extra = {}) => new Response(JSON.stringify({ error: message, ...extra }), { @@ -47,63 +45,12 @@ const getPublicKey = (userId, projectId) => `${PUBLIC_PREFIX}${userId}_${projectId}`; const listKvValues = async (kv, pattern) => { - const entries = []; - let cursor = undefined; - - while (true) { - const page = await kv.list({ pattern, returnValues: true, cursor }); - const items = Array.isArray(page) - ? page - : Array.isArray(page?.items) - ? page.items - : []; - - items.forEach((entry) => { - if (entry && typeof entry === "object" && "key" in entry) { - entries.push(entry); - } - }); - - if (!page?.cursor) break; - cursor = page.cursor; - } - - return entries; -}; - -const listAllKvValues = async (kv) => { - const entries = []; - let cursor = undefined; - - while (true) { - const page = await kv.list({ returnValues: true, cursor }); - const items = Array.isArray(page) - ? page - : Array.isArray(page?.items) - ? page.items - : []; - - items.forEach((entry) => { - if (entry && typeof entry === "object" && "key" in entry) { - entries.push(entry); - } - }); - - if (!page?.cursor) break; - cursor = page.cursor; - } - - return entries; + return await kv.list({ pattern, returnValues: true}); }; const deleteKvByPattern = async (kv, pattern) => { if (!kv) return 0; let entries = await listKvValues(kv, pattern); - if (entries.length === 0 && pattern.endsWith("*")) { - const prefix = pattern.slice(0, -1); - const allEntries = await listAllKvValues(kv); - entries = allEntries.filter((entry) => entry?.key?.startsWith(prefix)); - } if (entries.length === 0) return 0; await Promise.all( entries.map((entry) => (entry?.key ? kv.del(entry.key) : null)), @@ -111,43 +58,34 @@ const deleteKvByPattern = async (kv, pattern) => { return entries.length; }; -const kvGetProject = async (kv, key) => kv.get(key); - const findPublicKeyByProjectId = async (mePuter, projectId) => { const entries = await listKvValues(mePuter.kv, `${PUBLIC_PREFIX}*`); const match = entries.find((entry) => entry?.value?.id === projectId); return match?.key || null; }; -router.get("/api/projects/list", async ({ user, me }) => { +router.get("/api/projects/list", async ({ user }) => { try { const userPuter = getUserPuter(user); if (!userPuter) throw new Error("Missing user Puter context."); - const mePuter = getMePuter(me); + const mePuter = me.puter; const userItems = (await listKvValues(userPuter.kv, `${PROJECT_PREFIX}*`)) - .map(({ value }) => value) - .filter((project) => project && project.id); + .map(({ value }) => value); let publicItems = []; if (mePuter) { - const entries = await listKvValues(mePuter.kv, `${PUBLIC_PREFIX}*`); - publicItems = entries - .map(({ value }) => value) - .filter((project) => project && project.id) - .map((project) => ({ - ...project, - ownerId: project.ownerId || null, - })); + publicItems = (await listKvValues(mePuter.kv, `${PUBLIC_PREFIX}*`)) + .map(({ value }) => value); } const merged = new Map(); - userItems.filter(Boolean).forEach((project) => { + userItems.forEach((project) => { merged.set(`user:${project.id}`, project); }); - publicItems.filter(Boolean).forEach((project) => { + publicItems.forEach((project) => { const key = `public:${project.ownerId || "unknown"}:${project.id}`; merged.set(key, { ...project, isPublic: true }); }); @@ -193,7 +131,7 @@ router.get("/api/projects/list", async ({ user, me }) => { } }); -router.get("/api/projects/get", async ({ request, user, me }) => { +router.get("/api/projects/get", async ({ request, user }) => { const url = new URL(request.url); const id = url.searchParams.get("id"); const scope = url.searchParams.get("scope") || "user"; @@ -202,7 +140,7 @@ router.get("/api/projects/get", async ({ request, user, me }) => { if (!id) return jsonError(400, "Project id required"); if (scope === "public") { - const mePuter = getMePuter(me); + const mePuter = me.puter; if (!mePuter) return jsonError(500, "Missing deployer Puter context."); const publicKey = ownerId @@ -211,7 +149,7 @@ router.get("/api/projects/get", async ({ request, user, me }) => { if (!publicKey) return jsonError(404, "Project not found"); - const project = await kvGetProject(mePuter.kv, publicKey); + const project = await mePuter.kv.get(publicKey); if (!project) return jsonError(404, "Project not found"); return { project }; @@ -221,12 +159,12 @@ router.get("/api/projects/get", async ({ request, user, me }) => { if (!userPuter) return jsonError(401, "Authentication required"); const key = `${PROJECT_PREFIX}${id}`; - const project = await kvGetProject(userPuter.kv, key); + const project = await userPuter.kv.get(key); if (!project) return jsonError(404, "Project not found"); return { project }; }); -router.post("/api/projects/save", async ({ request, user, me }) => { +router.post("/api/projects/save", async ({ request, user }) => { const userPuter = getUserPuter(user); if (!userPuter) return jsonError(401, "Authentication required"); @@ -246,7 +184,7 @@ router.post("/api/projects/save", async ({ request, user, me }) => { if (visibility === "public" && !userId) return jsonError(401, "User id required"); - const mePuter = getMePuter(me); + const mePuter = me.puter; const storedPayload = payload; if (visibility === "private") { @@ -254,6 +192,7 @@ router.post("/api/projects/save", async ({ request, user, me }) => { await userPuter.kv.set(key, storedPayload); if (userId && mePuter) { + // remove public project const publicKey = getPublicKey(userId, project.id); const existing = await mePuter.kv.get(publicKey); @@ -270,9 +209,10 @@ router.post("/api/projects/save", async ({ request, user, me }) => { const publicKey = getPublicKey(userId, project.id); + let username = null; try { const userInfo = await userPuter.auth.getUser(); - const username = userInfo?.username || userInfo?.name || null; + username = userInfo?.username || userInfo?.name || null; if (username) await mePuter.kv.set(`${USER_PREFIX}${userId}`, { username }); } catch { @@ -285,8 +225,8 @@ router.post("/api/projects/save", async ({ request, user, me }) => { const publicRecord = { ...storedPayload, - renderedImage: storedPayload.renderedImage, ownerId: userId, + sharedBy: username, sharedAt: new Date().toISOString(), }; @@ -298,9 +238,9 @@ router.post("/api/projects/save", async ({ request, user, me }) => { return { saved: true, id: project.id, project: publicRecord }; }); -const clearProjects = async ({ user, me }) => { +const clearProjects = async ({ user }) => { const userPuter = getUserPuter(user); - const mePuter = getMePuter(me); + const mePuter = me.puter; if (!userPuter && !mePuter) return jsonError(401, "Authentication required"); const userDeleted = userPuter?.kv @@ -323,17 +263,11 @@ const clearProjects = async ({ user, me }) => { router.post("/api/projects/clear", async (ctx) => clearProjects(ctx)); router.post("/api/hosting/clear", async (ctx) => clearProjects(ctx)); -router.post("/api/hosting/reset", async ({ user, me }) => { +router.post("/api/hosting/reset", async ({ user }) => { const userPuter = getUserPuter(user); - const mePuter = getMePuter(me); - - if (!userPuter && !mePuter) return jsonError(401, "Authentication required"); - - const tasks = []; - if (userPuter?.kv) tasks.push(userPuter.kv.del(HOSTING_CONFIG_KEY)); - if (mePuter?.kv) tasks.push(mePuter.kv.del(HOSTING_CONFIG_KEY)); + if (!userPuter) return jsonError(401, "Authentication required"); - await Promise.all(tasks); + await userPuter.kv.del(HOSTING_CONFIG_KEY); return jsonOk({ reset: true }); }); diff --git a/type.d.ts b/type.d.ts index 1a62fa5..590634f 100644 --- a/type.d.ts +++ b/type.d.ts @@ -44,6 +44,7 @@ type VisualizerLocationState = { initialRender?: string | null; ownerId?: string | null; name?: string | null; + sharedBy?: string | null; }; interface VisualizerProps { From b74e0c84ac2c0c7d84ab7bbcb8acd8e4ab2635db Mon Sep 17 00:00:00 2001 From: Reynaldi Chernando Date: Wed, 11 Feb 2026 15:43:45 +0700 Subject: [PATCH 2/2] more refactors --- lib/puter.worker.js | 255 ++++++++++++++++---------------------------- 1 file changed, 93 insertions(+), 162 deletions(-) diff --git a/lib/puter.worker.js b/lib/puter.worker.js index 3502adc..d0f625c 100644 --- a/lib/puter.worker.js +++ b/lib/puter.worker.js @@ -1,9 +1,6 @@ const HOSTING_CONFIG_KEY = "roomify_hosting_config"; const PROJECT_PREFIX = "roomify_project_"; const PUBLIC_PREFIX = "roomify_public_"; -const USER_PREFIX = "roomify_user_"; - -const getUserPuter = (userParam) => userParam?.puter || null; const jsonError = (status, message, extra = {}) => new Response(JSON.stringify({ error: message, ...extra }), { @@ -29,10 +26,7 @@ const sanitizeProjectPayload = (project) => { return rest; }; -const getUserId = async (userParam) => { - const userPuter = getUserPuter(userParam); - if (!userPuter) return null; - +const getUserId = async (userPuter) => { try { const user = await userPuter.auth.getUser(); return user?.uuid || null; @@ -41,89 +35,40 @@ const getUserId = async (userParam) => { } }; -const getPublicKey = (userId, projectId) => - `${PUBLIC_PREFIX}${userId}_${projectId}`; - -const listKvValues = async (kv, pattern) => { - return await kv.list({ pattern, returnValues: true}); -}; +const getPublicKey = (userId, projectId) => `${PUBLIC_PREFIX}${userId}_${projectId}`; const deleteKvByPattern = async (kv, pattern) => { if (!kv) return 0; - let entries = await listKvValues(kv, pattern); + let entries = await kv.list(pattern); if (entries.length === 0) return 0; await Promise.all( - entries.map((entry) => (entry?.key ? kv.del(entry.key) : null)), + entries.map((key) => kv.del(key)), ); return entries.length; }; const findPublicKeyByProjectId = async (mePuter, projectId) => { - const entries = await listKvValues(mePuter.kv, `${PUBLIC_PREFIX}*`); + const entries = await mePuter.kv.list(`${PUBLIC_PREFIX}*`, true); const match = entries.find((entry) => entry?.value?.id === projectId); return match?.key || null; }; router.get("/api/projects/list", async ({ user }) => { try { - const userPuter = getUserPuter(user); - if (!userPuter) throw new Error("Missing user Puter context."); - const mePuter = me.puter; - const userItems = (await listKvValues(userPuter.kv, `${PROJECT_PREFIX}*`)) - .map(({ value }) => value); + const userPuter = user.puter; - let publicItems = []; - - if (mePuter) { - publicItems = (await listKvValues(mePuter.kv, `${PUBLIC_PREFIX}*`)) - .map(({ value }) => value); - } - - const merged = new Map(); - userItems.forEach((project) => { - merged.set(`user:${project.id}`, project); - }); - - publicItems.forEach((project) => { - const key = `public:${project.ownerId || "unknown"}:${project.id}`; - merged.set(key, { ...project, isPublic: true }); - }); + if (!userPuter) throw new Error("Missing user Puter context."); - const hydrated = Array.from(merged.values()); - - if (mePuter) { - const ownerIds = Array.from( - new Set( - hydrated - .filter((item) => item?.isPublic && item?.ownerId) - .map((item) => item.ownerId), - ), - ); - - if (ownerIds.length > 0) { - const ownerEntries = await Promise.all( - ownerIds.map(async (ownerId) => { - const record = await mePuter.kv.get(`${USER_PREFIX}${ownerId}`); - return { ownerId, username: record?.username || null }; - }), - ); - - const ownerMap = new Map( - ownerEntries.map((entry) => [entry.ownerId, entry.username]), - ); - - hydrated.forEach((item) => { - if (item?.isPublic && item?.ownerId) { - item.sharedBy = ownerMap.get(item.ownerId) || item.sharedBy || null; - } - }); - } - } + const userItems = (await userPuter.kv.list(`${PROJECT_PREFIX}*`, true)) + .map(({ value }) => value); + const publicItems = (await mePuter.kv.list(`${PUBLIC_PREFIX}*`, true)) + .map(({ value }) => ({ ...value, isPublic: true})); - hydrated.sort((a, b) => (b?.timestamp || 0) - (a?.timestamp || 0)); + const merged = [...userItems, ...publicItems]; + merged.sort((a, b) => (b?.timestamp || 0) - (a?.timestamp || 0)); - return { projects: hydrated }; + return { projects: merged }; } catch (error) { return jsonError(500, "Failed to list projects", { message: error?.message || "Unknown error", @@ -132,116 +77,110 @@ router.get("/api/projects/list", async ({ user }) => { }); router.get("/api/projects/get", async ({ request, user }) => { - const url = new URL(request.url); - const id = url.searchParams.get("id"); - const scope = url.searchParams.get("scope") || "user"; - const ownerId = url.searchParams.get("ownerId"); - - if (!id) return jsonError(400, "Project id required"); - - if (scope === "public") { + try { const mePuter = me.puter; - if (!mePuter) return jsonError(500, "Missing deployer Puter context."); + const userPuter = user.puter; - const publicKey = ownerId - ? getPublicKey(ownerId, id) - : await findPublicKeyByProjectId(mePuter, id); + if (!userPuter) return jsonError(401, "Authentication required"); - if (!publicKey) return jsonError(404, "Project not found"); + const url = new URL(request.url); + const id = url.searchParams.get("id"); + const scope = url.searchParams.get("scope") || "user"; + const ownerId = url.searchParams.get("ownerId"); - const project = await mePuter.kv.get(publicKey); - if (!project) return jsonError(404, "Project not found"); + if (!id) return jsonError(400, "Project id required"); - return { project }; - } + if (scope === "private") { + // PRIVATE PROJECT + const key = `${PROJECT_PREFIX}${id}`; + const project = await userPuter.kv.get(key); + if (!project) return jsonError(404, "Project not found"); - const userPuter = getUserPuter(user); - if (!userPuter) return jsonError(401, "Authentication required"); + return { project }; + } else { + // PUBLIC PROJECT + const publicKey = ownerId + ? getPublicKey(ownerId, id) + : await findPublicKeyByProjectId(mePuter, id); + if (!publicKey) return jsonError(404, "Project not found"); + + const project = await mePuter.kv.get(publicKey); + if (!project) return jsonError(404, "Project not found"); - const key = `${PROJECT_PREFIX}${id}`; - const project = await userPuter.kv.get(key); - if (!project) return jsonError(404, "Project not found"); - return { project }; + return { project }; + } + } catch (error) { + return jsonError(500, "Failed to get project", { + message: error?.message || "Unknown error", + }); + } }); router.post("/api/projects/save", async ({ request, user }) => { - const userPuter = getUserPuter(user); - if (!userPuter) return jsonError(401, "Authentication required"); + try { + const mePuter = me.puter; + const userPuter = user.puter; - const body = await request.json(); - const project = body?.project; + if (!userPuter) return jsonError(401, "Authentication required"); - const visibility = body?.visibility === "public" ? "public" : "private"; - if (!project?.id || !project?.sourceImage) - return jsonError(400, "Project id and image required"); + const body = await request.json(); + const project = body?.project; - const payload = { - ...sanitizeProjectPayload(project), - updatedAt: new Date().toISOString(), - }; + const scope = body?.visibility === "public" ? "public" : "private"; + if (!project?.id || !project?.sourceImage) + return jsonError(400, "Project id and image required"); - const userId = await getUserId(user); - if (visibility === "public" && !userId) - return jsonError(401, "User id required"); + const payload = { + ...sanitizeProjectPayload(project), + updatedAt: new Date().toISOString(), + }; - const mePuter = me.puter; - const storedPayload = payload; + const userId = await getUserId(userPuter); + if (!userId) return jsonError(401, "User id required"); - if (visibility === "private") { - const key = `${PROJECT_PREFIX}${project.id}`; - await userPuter.kv.set(key, storedPayload); + if (scope === "private") { + // PRIVATE PROJECT + const key = `${PROJECT_PREFIX}${project.id}`; + await userPuter.kv.set(key, payload); - if (userId && mePuter) { - // remove public project + // remove existing public project const publicKey = getPublicKey(userId, project.id); - const existing = await mePuter.kv.get(publicKey); - - if (existing?.ownerId && existing.ownerId !== userId) - return jsonError(403, "Not allowed"); - await mePuter.kv.del(publicKey); - } - return { saved: true, id: project.id, project: storedPayload }; - } + return { saved: true, id: project.id, project: payload }; + } else { + // PUBLIC PROJECT + const publicKey = getPublicKey(userId, project.id); - if (!mePuter) return jsonError(500, "Missing deployer Puter context."); + const userInfo = await userPuter.auth.getUser(); + let username = userInfo?.username || userInfo?.name || null; - const publicKey = getPublicKey(userId, project.id); + const publicRecord = { + ...payload, + ownerId: userId, + sharedBy: username, + sharedAt: new Date().toISOString(), + }; + await mePuter.kv.set(publicKey, publicRecord); - let username = null; - try { - const userInfo = await userPuter.auth.getUser(); - username = userInfo?.username || userInfo?.name || null; + // remove existing private project + const userKey = `${PROJECT_PREFIX}${project.id}`; + await userPuter.kv.del(userKey); - if (username) await mePuter.kv.set(`${USER_PREFIX}${userId}`, { username }); - } catch { - // Best-effort user map write + return { saved: true, id: project.id, project: publicRecord }; + } + } catch (error) { + return jsonError(500, "Failed to save project", { + message: error?.message || "Unknown error", + }); } - - const existing = await mePuter.kv.get(publicKey); - if (existing?.ownerId && existing.ownerId !== userId) - return jsonError(403, "Not allowed"); - - const publicRecord = { - ...storedPayload, - ownerId: userId, - sharedBy: username, - sharedAt: new Date().toISOString(), - }; - - await mePuter.kv.set(publicKey, publicRecord); - - const userKey = `${PROJECT_PREFIX}${project.id}`; - await userPuter.kv.del(userKey); - - return { saved: true, id: project.id, project: publicRecord }; }); -const clearProjects = async ({ user }) => { - const userPuter = getUserPuter(user); +router.post("/api/projects/clear", async ({ user }) => { + const userPuter = user.puter; const mePuter = me.puter; - if (!userPuter && !mePuter) return jsonError(401, "Authentication required"); + + if (!userPuter) return jsonError(401, "Authentication required"); const userDeleted = userPuter?.kv ? await deleteKvByPattern(userPuter.kv, `${PROJECT_PREFIX}*`) @@ -249,22 +188,15 @@ const clearProjects = async ({ user }) => { const publicDeleted = mePuter?.kv ? await deleteKvByPattern(mePuter.kv, `${PUBLIC_PREFIX}*`) : 0; - const userMapDeleted = mePuter?.kv - ? await deleteKvByPattern(mePuter.kv, `${USER_PREFIX}*`) - : 0; return jsonOk({ cleared: userDeleted, clearedPublic: publicDeleted, - clearedUsers: userMapDeleted, }); -}; - -router.post("/api/projects/clear", async (ctx) => clearProjects(ctx)); -router.post("/api/hosting/clear", async (ctx) => clearProjects(ctx)); +}); -router.post("/api/hosting/reset", async ({ user }) => { - const userPuter = getUserPuter(user); +router.post("/api/hosting/clear", async ({ user }) => { + const userPuter = user.puter; if (!userPuter) return jsonError(401, "Authentication required"); await userPuter.kv.del(HOSTING_CONFIG_KEY); @@ -280,7 +212,6 @@ router.get("/*path", async ({ params }) => { "/api/projects/save", "/api/projects/clear", "/api/hosting/clear", - "/api/hosting/reset", ], }); });