From 74b3b283f7dcba9bf2ede93843e2215bb51a0e9d Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 13 Apr 2026 21:30:19 -0700 Subject: [PATCH] Fix #2848 --- config/db/db.sqlite-journal | Bin 0 -> 8720 bytes server/routers/client/listClients.ts | 119 ++++++----------------- server/routers/client/listUserDevices.ts | 100 +++++-------------- server/routers/site/listSites.ts | 45 +++++++-- 4 files changed, 94 insertions(+), 170 deletions(-) create mode 100644 config/db/db.sqlite-journal diff --git a/config/db/db.sqlite-journal b/config/db/db.sqlite-journal new file mode 100644 index 0000000000000000000000000000000000000000..46b7df58754fea40205eefd5c1412c307c55edfc GIT binary patch literal 8720 zcmeGiO>Z05aVg89sMuaRiNXR#<3#|~A!t$j{t#*?D{>__Q^C^lkk!tnSj^KKK^D}>`04C$LcFW}|5sJT405Rb+ZkyJVo%VZ<5RB|zuOT{zkTbuRW-l2Nb*Wb>t?<5L+ zv@Ef!vG7VkI;_-d^+I`VVQZ_tx|h8U94DynKIA(cO+7^7;I0qM&jqMi%lrUoejmxQ z+MC}|WnER~4OKH#NjL2mYd4iP+uJ_cTi@vyj^5Um^{s>3iCg0BM6sv0TZg#~wc8Vv zL|h=vPh#^w&3=Z>Ui!rTD8#vRrYg>t62T)C61F0ba;!|q-ziPW5NQw8%s4^aR3$T1(}Kb*}_RFI;kr>WU# zDh!ap%rGQ|Q0iGBQRptPRkjw@*9r$~nRGt9u$r%LzH=kd&aM^r)zw~lEqx^C`sw{` z(kD11@KzIc;%#;`eEjH7*Fh)W)@DgE5VNalZQfuK@E0JTk9vz?C@Mf>E8yom8cZgg z4|w~C`3Y0t5&1*tEMU*kUtpSt^iT8;^uOuv>3`CHr@x`UroW=Ur2k5Papv&MSH30c zQ~JjfSd9P=sF(dgcFs4rDk&m55|Cy{yOO{|CN)I0%}Yw7t4If3L}FgaS2?!EG4*nB zv(7Qaa)Gdm%tp1i&Q`aX8{9U-)@zkw891zSZiTIvYRoP#>j;zR25)qAYYF3EuXd0kN=oahhd(;%NE*^v!_^ow zka^vxA_zKOW(@c1D6|Tya^V}9=J6~^l2q0GK8wDI+ zipubUA*l+Ep8{9n04DIdz>8=m^e*iWUb*5MoHe6nhgTFN7CX8-x#*1-7Y?BZ21p%y zlgKD>;$kKgyEql(uJ|Z3Df9yw^1H!zp~m7QX}G)^F?V{Yb>58P!6iZ;4iZSVRkG$6 zMrmcCEX5FB@>Jb27p8(Y=AN#b#nDmE`l9du>YVAa2bWD1tuh9Y>rT#lm2=^iX3ppH z=a?b2;b7+5;BzNA9arOEQni*7l%8l7WC@AFm~nH9?Mj92i?6A9%ZwF?n(oRGEX zg-NV~XM3y5MvRtWo`8s!OZj@xp9(3^xd9P*H) zI8{0xbD`C-91~%IU+gR$yyy1^FJJcEvkos?NBcEB(t9Vm?Q}s*mT}V4UU&Y-3feEG z{J~AA9C^2(Ng|vrlF@e(_sNnbKv(48{KxCZ)0aC{gk*A3QJX&N4;DeS@X4yV84o)` zs5Ny8wcMpAsWc=AZA?xD!(rdHnNN1pqa*136_9H6K+fk`PfR?4d{g1u=o*lQ?(Lp2 zbM!;MlcR+CUSlRb9C4Zur2pGeiQ5jMLb{($IlutuajcF;IUOh_ zlU2BJxlIg)jP6vh8YbDOHVP*T+k&WeOK2Hi8c>}lW@JJ%F#Z`-JiM-c=r{LM+^}S>FH-Ouhr3PHem2Mkpl7PuW zs}obfcT=AJ87{^j56;2(#{vEqj^Eyep^NY8`w|S>O&HcIFysp`q<#&<+%*_pdmV<^ KOOGFY9Q_&BqR?Fc literal 0 HcmV?d00001 diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index 0bf798509..f5d69857d 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -29,65 +29,9 @@ import { } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; -import NodeCache from "node-cache"; -import semver from "semver"; import { z } from "zod"; import { fromError } from "zod-validation-error"; -const olmVersionCache = new NodeCache({ stdTTL: 3600 }); - -async function getLatestOlmVersion(): Promise { - try { - const cachedVersion = olmVersionCache.get("latestOlmVersion"); - if (cachedVersion) { - return cachedVersion; - } - - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 1500); - - const response = await fetch( - "https://api.github.com/repos/fosrl/olm/tags", - { - signal: controller.signal - } - ); - - clearTimeout(timeoutId); - - if (!response.ok) { - logger.warn( - `Failed to fetch latest Olm version from GitHub: ${response.status} ${response.statusText}` - ); - return null; - } - - let tags = await response.json(); - if (!Array.isArray(tags) || tags.length === 0) { - logger.warn("No tags found for Olm repository"); - return null; - } - tags = tags.filter((version) => !version.name.includes("rc")); - const latestVersion = tags[0].name; - - olmVersionCache.set("latestOlmVersion", latestVersion, 3600); - - return latestVersion; - } catch (error: any) { - if (error.name === "AbortError") { - logger.warn("Request to fetch latest Olm version timed out (1.5s)"); - } else if (error.cause?.code === "UND_ERR_CONNECT_TIMEOUT") { - logger.warn("Connection timeout while fetching latest Olm version"); - } else { - logger.warn( - "Error fetching latest Olm version:", - error.message || error - ); - } - return null; - } -} - const listClientsParamsSchema = z.strictObject({ orgId: z.string() }); @@ -413,44 +357,45 @@ export async function listClients( }; }); - const latestOlVersionPromise = getLatestOlmVersion(); + // REMOVING THIS BECAUSE WE HAVE DIFFERENT TYPES OF CLIENTS NOW + // const latestOlmVersionPromise = getLatestOlmVersion(); - const olmsWithUpdates: OlmWithUpdateAvailable[] = clientsWithSites.map( - (client) => { - const OlmWithUpdate: OlmWithUpdateAvailable = { ...client }; - // Initially set to false, will be updated if version check succeeds - OlmWithUpdate.olmUpdateAvailable = false; - return OlmWithUpdate; - } - ); + // const olmsWithUpdates: OlmWithUpdateAvailable[] = clientsWithSites.map( + // (client) => { + // const OlmWithUpdate: OlmWithUpdateAvailable = { ...client }; + // // Initially set to false, will be updated if version check succeeds + // OlmWithUpdate.olmUpdateAvailable = false; + // return OlmWithUpdate; + // } + // ); // Try to get the latest version, but don't block if it fails - try { - const latestOlVersion = await latestOlVersionPromise; + // try { + // const latestOlmVersion = await latestOlVersionPromise; - if (latestOlVersion) { - olmsWithUpdates.forEach((client) => { - try { - client.olmUpdateAvailable = semver.lt( - client.olmVersion ? client.olmVersion : "", - latestOlVersion - ); - } catch (error) { - client.olmUpdateAvailable = false; - } - }); - } - } catch (error) { - // Log the error but don't let it block the response - logger.warn( - "Failed to check for OLM updates, continuing without update info:", - error - ); - } + // if (latestOlVersion) { + // olmsWithUpdates.forEach((client) => { + // try { + // client.olmUpdateAvailable = semver.lt( + // client.olmVersion ? client.olmVersion : "", + // latestOlVersion + // ); + // } catch (error) { + // client.olmUpdateAvailable = false; + // } + // }); + // } + // } catch (error) { + // // Log the error but don't let it block the response + // logger.warn( + // "Failed to check for OLM updates, continuing without update info:", + // error + // ); + // } return response(res, { data: { - clients: olmsWithUpdates, + clients: clientsWithSites, pagination: { total: totalCount, page, diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts index 0ae31165a..d793faf09 100644 --- a/server/routers/client/listUserDevices.ts +++ b/server/routers/client/listUserDevices.ts @@ -30,65 +30,10 @@ import { } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; -import NodeCache from "node-cache"; import semver from "semver"; import { z } from "zod"; import { fromError } from "zod-validation-error"; -const olmVersionCache = new NodeCache({ stdTTL: 3600 }); - -async function getLatestOlmVersion(): Promise { - try { - const cachedVersion = olmVersionCache.get("latestOlmVersion"); - if (cachedVersion) { - return cachedVersion; - } - - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 1500); - - const response = await fetch( - "https://api.github.com/repos/fosrl/olm/tags", - { - signal: controller.signal - } - ); - - clearTimeout(timeoutId); - - if (!response.ok) { - logger.warn( - `Failed to fetch latest Olm version from GitHub: ${response.status} ${response.statusText}` - ); - return null; - } - - let tags = await response.json(); - if (!Array.isArray(tags) || tags.length === 0) { - logger.warn("No tags found for Olm repository"); - return null; - } - tags = tags.filter((version) => !version.name.includes("rc")); - const latestVersion = tags[0].name; - - olmVersionCache.set("latestOlmVersion", latestVersion, 3600); - - return latestVersion; - } catch (error: any) { - if (error.name === "AbortError") { - logger.warn("Request to fetch latest Olm version timed out (1.5s)"); - } else if (error.cause?.code === "UND_ERR_CONNECT_TIMEOUT") { - logger.warn("Connection timeout while fetching latest Olm version"); - } else { - logger.warn( - "Error fetching latest Olm version:", - error.message || error - ); - } - return null; - } -} - const listUserDevicesParamsSchema = z.strictObject({ orgId: z.string() }); @@ -453,29 +398,30 @@ export async function listUserDevices( } ); - // Try to get the latest version, but don't block if it fails - try { - const latestOlmVersion = await getLatestOlmVersion(); + // REMOVING THIS BECAUSE WE HAVE DIFFERENT TYPES OF CLIENTS NOW + // // Try to get the latest version, but don't block if it fails + // try { + // const latestOlmVersion = await getLatestOlmVersion(); - if (latestOlmVersion) { - olmsWithUpdates.forEach((client) => { - try { - client.olmUpdateAvailable = semver.lt( - client.olmVersion ? client.olmVersion : "", - latestOlmVersion - ); - } catch (error) { - client.olmUpdateAvailable = false; - } - }); - } - } catch (error) { - // Log the error but don't let it block the response - logger.warn( - "Failed to check for OLM updates, continuing without update info:", - error - ); - } + // if (latestOlmVersion) { + // olmsWithUpdates.forEach((client) => { + // try { + // client.olmUpdateAvailable = semver.lt( + // client.olmVersion ? client.olmVersion : "", + // latestOlmVersion + // ); + // } catch (error) { + // client.olmUpdateAvailable = false; + // } + // }); + // } + // } catch (error) { + // // Log the error but don't let it block the response + // logger.warn( + // "Failed to check for OLM updates, continuing without update info:", + // error + // ); + // } return response(res, { data: { diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 6f085d74d..b65182908 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -21,6 +21,11 @@ import semver from "semver"; import { z } from "zod"; import { fromError } from "zod-validation-error"; +// Stale-while-revalidate: keeps the last successfully fetched version so that +// a transient network failure / timeout does not flip every site back to +// newtUpdateAvailable: false. +let staleNewtVersion: string | null = null; + async function getLatestNewtVersion(): Promise { try { const cachedVersion = await cache.get("latestNewtVersion"); @@ -29,7 +34,7 @@ async function getLatestNewtVersion(): Promise { } const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 1500); // Reduced timeout to 1.5 seconds + const timeoutId = setTimeout(() => controller.abort(), 1500); const response = await fetch( "https://api.github.com/repos/fosrl/newt/tags", @@ -44,18 +49,46 @@ async function getLatestNewtVersion(): Promise { logger.warn( `Failed to fetch latest Newt version from GitHub: ${response.status} ${response.statusText}` ); - return null; + return staleNewtVersion; } let tags = await response.json(); if (!Array.isArray(tags) || tags.length === 0) { logger.warn("No tags found for Newt repository"); - return null; + return staleNewtVersion; } - tags = tags.filter((version) => !version.name.includes("rc")); + + // Remove release-candidates, then sort descending by semver so that + // duplicate tags (e.g. "1.10.3" and "v1.10.3") and any ordering quirks + // from the GitHub API do not cause an older tag to be selected. + tags = tags.filter((tag: any) => !tag.name.includes("rc")); + tags.sort((a: any, b: any) => { + const va = semver.coerce(a.name); + const vb = semver.coerce(b.name); + if (!va && !vb) return 0; + if (!va) return 1; + if (!vb) return -1; + return semver.rcompare(va, vb); + }); + + // Deduplicate: keep only the first (highest) entry per normalised version + const seen = new Set(); + tags = tags.filter((tag: any) => { + const normalised = semver.coerce(tag.name)?.version; + if (!normalised || seen.has(normalised)) return false; + seen.add(normalised); + return true; + }); + + if (tags.length === 0) { + logger.warn("No valid semver tags found for Newt repository"); + return staleNewtVersion; + } + const latestVersion = tags[0].name; - await cache.set("latestNewtVersion", latestVersion, 3600); + staleNewtVersion = latestVersion; + await cache.set("cache:latestNewtVersion", latestVersion, 3600); return latestVersion; } catch (error: any) { @@ -73,7 +106,7 @@ async function getLatestNewtVersion(): Promise { error.message || error ); } - return null; + return staleNewtVersion; } }