diff --git a/config/db/db.sqlite-journal b/config/db/db.sqlite-journal new file mode 100644 index 000000000..46b7df587 Binary files /dev/null and b/config/db/db.sqlite-journal differ 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; } }