From c86f97ab2bc9d6f58387a5245443d8ddea8dd209 Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Fri, 17 Jul 2026 17:19:49 +0200 Subject: [PATCH] PAB-56: cache dashboard runtime URL config --- src/pages/monitor.astro | 4 +-- src/server/tailscale/urls.test.ts | 50 +++++++++++++++++++++++++++++++ src/server/tailscale/urls.ts | 36 ++++++++++------------ 3 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 src/server/tailscale/urls.test.ts diff --git a/src/pages/monitor.astro b/src/pages/monitor.astro index cdd111cf..4962ca49 100644 --- a/src/pages/monitor.astro +++ b/src/pages/monitor.astro @@ -1,5 +1,5 @@ --- -import { isNull } from "drizzle-orm"; +import { and, eq, isNull } from "drizzle-orm"; import { MonitorWorkspace } from "@/components/monitor/MonitorWorkspace"; import AppLayout from "@/layouts/AppLayout.astro"; import { db } from "@/server/db/client"; @@ -53,7 +53,7 @@ if (initialTab === "queue" && jobIdParam) { const allProjects = await db .select({ id: projects.id, name: projects.name, icon: projects.icon }) .from(projects) - .where(isNull(projects.deletedAt)); + .where(and(eq(projects.ownerUserId, user.id), isNull(projects.deletedAt))); --- diff --git a/src/server/tailscale/urls.test.ts b/src/server/tailscale/urls.test.ts new file mode 100644 index 00000000..11d0de58 --- /dev/null +++ b/src/server/tailscale/urls.test.ts @@ -0,0 +1,50 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const { getTailscaleConfig } = vi.hoisted(() => ({ + getTailscaleConfig: vi.fn(), +})); + +vi.mock("./config", () => ({ getTailscaleConfig })); + +import { getTailscaleProjectUrl, invalidateTailscaleUrlCache } from "./urls"; + +describe("getTailscaleProjectUrl", () => { + beforeEach(() => { + invalidateTailscaleUrlCache(); + getTailscaleConfig.mockReset(); + getTailscaleConfig.mockResolvedValue({ + enabled: true, + authKey: null, + hostname: "doce", + tailnetName: "example.ts.net", + }); + }); + + it("shares one config lookup across concurrent project URL builds", async () => { + const urls = await Promise.all( + Array.from({ length: 6 }, (_, index) => + getTailscaleProjectUrl( + `project-${index}`, + "preview", + `project-${index}`, + ), + ), + ); + + expect(getTailscaleConfig).toHaveBeenCalledTimes(1); + expect(urls).toEqual([ + "https://project-0-proje-preview.example.ts.net", + "https://project-1-proje-preview.example.ts.net", + "https://project-2-proje-preview.example.ts.net", + "https://project-3-proje-preview.example.ts.net", + "https://project-4-proje-preview.example.ts.net", + "https://project-5-proje-preview.example.ts.net", + ]); + }); + + it("preserves production URL construction", async () => { + await expect( + getTailscaleProjectUrl("my-project", "production", "abcde12345"), + ).resolves.toBe("https://my-project-abcde.example.ts.net"); + }); +}); diff --git a/src/server/tailscale/urls.ts b/src/server/tailscale/urls.ts index 4b56c74f..318cb7a2 100644 --- a/src/server/tailscale/urls.ts +++ b/src/server/tailscale/urls.ts @@ -1,27 +1,22 @@ +import { getOrSet, invalidate } from "@/server/cache/memory"; import { getTailscaleConfig } from "./config"; -let cachedConfig: { - enabled: boolean; - tailnetName: string | null; -} | null = null; - -let cacheExpiry = 0; - +const URL_CONFIG_CACHE_KEY = "tailscale:url-config"; const CACHE_TTL_MS = 30_000; -async function getCachedConfig() { - const now = Date.now(); - if (cachedConfig && now < cacheExpiry) { - return cachedConfig; - } +interface UrlConfig { + enabled: boolean; + tailnetName: string | null; +} - const config = await getTailscaleConfig(); - cachedConfig = { - enabled: config.enabled, - tailnetName: config.tailnetName, - }; - cacheExpiry = now + CACHE_TTL_MS; - return cachedConfig; +function getCachedConfig(): Promise { + return getOrSet(URL_CONFIG_CACHE_KEY, { ttlMs: CACHE_TTL_MS }, async () => { + const config = await getTailscaleConfig(); + return { + enabled: config.enabled, + tailnetName: config.tailnetName, + }; + }); } /** @@ -64,6 +59,5 @@ export async function getTailscaleAppUrl(): Promise { /** Invalidate the cached config (call after connect/disconnect). */ export function invalidateTailscaleUrlCache(): void { - cachedConfig = null; - cacheExpiry = 0; + invalidate(URL_CONFIG_CACHE_KEY); }