From cd82ea61e66f696c67b5c36d35187831a7c49459 Mon Sep 17 00:00:00 2001 From: "Scott E. Graves" Date: Sat, 13 Sep 2025 22:28:11 -0500 Subject: [PATCH] update --- src/api.js | 99 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 30 deletions(-) diff --git a/src/api.js b/src/api.js index ff73268..17243da 100644 --- a/src/api.js +++ b/src/api.js @@ -1,3 +1,52 @@ +import { + DeleteObjectCommand, + GetObjectCommand, + ListObjectsCommand, + S3Client, +} from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; + +const BUCKET = "repertory"; + +const oldItems = []; + +const s3 = new S3Client({ + region: "any", + endpoint: "https://gateway.storjshare.io", + forcePathStyle: true, + credentials: { + accessKeyId: process.env.R_AWS_KEY, + secretAccessKey: process.env.R_AWS_SECRET, + }, +}); + +const cleanOldItems = async () => { + console.log(`cleaning|count|${oldItems.length}`); + while (oldItems.length > 0) { + try { + const key = oldItems.pop(); + console.log(`cleaning|key|${key}`); + await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: key })); + } catch (err) { + console.error(err); + } + } +}; + +const createDownloadLink = async (key) => { + let filename = key.split("/"); + filename = filename[filename.length - 1]; + return await getSignedUrl( + s3, + new GetObjectCommand({ + Bucket: BUCKET, + Key: key, + ResponseContentDisposition: `attachment; filename="${filename}"`, + }), + { expiresIn: 3600 }, + ); +}; + const getBucketFiles = async (folderName) => { try { folderName = (folderName || "").toLowerCase(); @@ -35,15 +84,17 @@ const getBucketFiles = async (folderName) => { key: obj.Key || "", }; }) + // sort just to have deterministic selection for nightly, output order will be manual .sort((a, b) => (a.sort > b.sort ? -1 : a.sort < b.sort ? 1 : 0)); - const itemCount = {}; + const byKey = Object.fromEntries(ret.map((r) => [r.key, r])); + const ext = ".tar.gz"; - - // 1) choose which .tar.gz to keep (nightly retention unchanged) const tars = ret.filter((it) => it.name.endsWith(ext)); - const keepTarKeys = new Set(); + // Nightly retention: keep newest 3 per (product_platform_arch) + const keepTarKeys = new Set(); + const itemCount = {}; for (const t of tars) { if (folderName === "nightly") { const parts = t.name.split("_"); // 0=product,1=version,2=build,3=platform,4=arch @@ -57,21 +108,18 @@ const getBucketFiles = async (folderName) => { if (!oldItems.includes(t.key)) { oldItems.push(t.key, t.key + ".sha256", t.key + ".sig"); } - // windows companion + // mark companions old (if they exist) if (parts[3] === "windows") { const setupKey = t.key.substring(0, t.key.length - ext.length) + "_setup.exe"; - const hasSetup = ret.find((x) => x.key === setupKey); - if (hasSetup && !oldItems.includes(setupKey)) { + if (byKey[setupKey] && !oldItems.includes(setupKey)) { oldItems.push(setupKey, setupKey + ".sha256", setupKey + ".sig"); } } - // darwin companion if (parts[3] === "darwin") { const dmgKey = t.key.substring(0, t.key.length - ext.length) + ".dmg"; - const hasDmg = ret.find((x) => x.key === dmgKey); - if (hasDmg && !oldItems.includes(dmgKey)) { + if (byKey[dmgKey] && !oldItems.includes(dmgKey)) { oldItems.push(dmgKey, dmgKey + ".sha256", dmgKey + ".sig"); } } @@ -81,11 +129,7 @@ const getBucketFiles = async (folderName) => { } } - // 2) fast lookup by key - const byKey = Object.fromEntries(ret.map((r) => [r.key, r])); - - // 3) build final list strictly in the order you want for each group: - // DMG(+sidecars) -> SETUP(+sidecars) -> TAR(+sidecars) + // Build output strictly in order: DMG group -> SETUP group -> TAR group per (0..4) tuple const out = []; for (const t of tars) { if (!keepTarKeys.has(t.key)) continue; @@ -94,32 +138,26 @@ const getBucketFiles = async (folderName) => { const dmgKey = base + ".dmg"; const setupKey = base + "_setup.exe"; - // group order: 1) DMG group (if present) + // 1) dmg + sidecars const dmg = byKey[dmgKey]; if (dmg) { out.push(dmg); - const dmgSha = byKey[dmgKey + ".sha256"]; - if (dmgSha) out.push(dmgSha); - const dmgSig = byKey[dmgKey + ".sig"]; - if (dmgSig) out.push(dmgSig); + if (byKey[dmgKey + ".sha256"]) out.push(byKey[dmgKey + ".sha256"]); + if (byKey[dmgKey + ".sig"]) out.push(byKey[dmgKey + ".sig"]); } - // 2) SETUP group (if present) + // 2) setup + sidecars const setup = byKey[setupKey]; if (setup) { out.push(setup); - const setupSha = byKey[setupKey + ".sha256"]; - if (setupSha) out.push(setupSha); - const setupSig = byKey[setupKey + ".sig"]; - if (setupSig) out.push(setupSig); + if (byKey[setupKey + ".sha256"]) out.push(byKey[setupKey + ".sha256"]); + if (byKey[setupKey + ".sig"]) out.push(byKey[setupKey + ".sig"]); } - // 3) TAR group (always present for this loop) + // 3) tar.gz + sidecars out.push(t); - const tarSha = byKey[t.key + ".sha256"]; - if (tarSha) out.push(tarSha); - const tarSig = byKey[t.key + ".sig"]; - if (tarSig) out.push(tarSig); + if (byKey[t.key + ".sha256"]) out.push(byKey[t.key + ".sha256"]); + if (byKey[t.key + ".sig"]) out.push(byKey[t.key + ".sig"]); } return out; @@ -128,3 +166,4 @@ const getBucketFiles = async (folderName) => { return []; } }; +export { cleanOldItems, createDownloadLink, getBucketFiles };