diff --git a/src/app/api/admin/node/route.ts b/src/app/api/admin/node/route.ts index aa9b11c..3bde1fc 100644 --- a/src/app/api/admin/node/route.ts +++ b/src/app/api/admin/node/route.ts @@ -65,6 +65,12 @@ export async function PATCH(req: NextRequest) { if (data.faviconData !== undefined) { updateData.faviconData = data.faviconData; } + if (data.logoUrl === '') { + updateData.logoData = null; + } + if (data.faviconUrl === '') { + updateData.faviconData = null; + } [node] = await db.update(nodes) .set(updateData) diff --git a/src/app/api/admin/node/upload/route.ts b/src/app/api/admin/node/upload/route.ts index 3378f2e..4a171f2 100644 --- a/src/app/api/admin/node/upload/route.ts +++ b/src/app/api/admin/node/upload/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { db, nodes } from '@/db'; import { eq } from 'drizzle-orm'; import { requireAdmin } from '@/lib/auth/admin'; +import { getVersionedNodeAssetUrl } from '@/lib/node/assets'; // Logo constraints const MAX_LOGO_SIZE = 2 * 1024 * 1024; // 2MB @@ -112,9 +113,14 @@ export async function POST(req: NextRequest) { } // Update the appropriate field - const updateData = isLogo - ? { logoData: dataUrl, logoUrl: `/api/node/logo`, updatedAt: new Date() } - : { faviconData: dataUrl, faviconUrl: `/api/node/favicon`, updatedAt: new Date() }; + const updatedAt = new Date(); + const assetUrl = isLogo + ? getVersionedNodeAssetUrl('/api/node/logo', updatedAt) + : getVersionedNodeAssetUrl('/api/node/favicon', updatedAt); + + const updateData = isLogo + ? { logoData: dataUrl, logoUrl: assetUrl, updatedAt } + : { faviconData: dataUrl, faviconUrl: assetUrl, updatedAt }; await db.update(nodes) .set(updateData) @@ -122,7 +128,7 @@ export async function POST(req: NextRequest) { return NextResponse.json({ success: true, - url: isLogo ? '/api/node/logo' : '/api/node/favicon', + url: assetUrl, type, size: file.size, }); diff --git a/src/app/api/node/favicon/route.ts b/src/app/api/node/favicon/route.ts index e93f28f..4945290 100644 --- a/src/app/api/node/favicon/route.ts +++ b/src/app/api/node/favicon/route.ts @@ -48,7 +48,9 @@ export async function GET() { status: 200, headers: { 'Content-Type': mimeType, - 'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400', + 'Cache-Control': 'no-store, max-age=0', + Pragma: 'no-cache', + Expires: '0', 'Content-Length': buffer.length.toString(), }, }); diff --git a/src/app/api/node/logo/route.ts b/src/app/api/node/logo/route.ts index e803069..1ed17c0 100644 --- a/src/app/api/node/logo/route.ts +++ b/src/app/api/node/logo/route.ts @@ -48,7 +48,9 @@ export async function GET() { status: 200, headers: { 'Content-Type': mimeType, - 'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400', + 'Cache-Control': 'no-store, max-age=0', + Pragma: 'no-cache', + Expires: '0', 'Content-Length': buffer.length.toString(), }, }); diff --git a/src/app/api/node/route.ts b/src/app/api/node/route.ts index e4252fd..a6f87aa 100644 --- a/src/app/api/node/route.ts +++ b/src/app/api/node/route.ts @@ -3,6 +3,7 @@ import { db } from '@/db'; import { nodes, users } from '@/db'; import { eq, inArray } from 'drizzle-orm'; import { getNodePublicKey } from '@/lib/swarm/node-keys'; +import { getVersionedNodeAssetUrl } from '@/lib/node/assets'; export async function GET() { try { @@ -58,8 +59,17 @@ export async function GET() { }); } + const logoUrl = node.logoData + ? getVersionedNodeAssetUrl('/api/node/logo', node.updatedAt) + : node.logoUrl; + const faviconUrl = node.faviconData + ? getVersionedNodeAssetUrl('/api/node/favicon', node.updatedAt) + : node.faviconUrl; + return NextResponse.json({ ...node, + logoUrl, + faviconUrl, publicKey, // Always include the public key admins, // Don't expose the secret keys diff --git a/src/lib/node/assets.ts b/src/lib/node/assets.ts new file mode 100644 index 0000000..9c7db0e --- /dev/null +++ b/src/lib/node/assets.ts @@ -0,0 +1,13 @@ +export function getNodeAssetVersion(updatedAt: Date | string | null | undefined): string { + if (!updatedAt) { + return Date.now().toString(); + } + + const timestamp = new Date(updatedAt).getTime(); + return Number.isNaN(timestamp) ? Date.now().toString() : timestamp.toString(); +} + +export function getVersionedNodeAssetUrl(path: string, updatedAt: Date | string | null | undefined): string { + const version = getNodeAssetVersion(updatedAt); + return `${path}?v=${encodeURIComponent(version)}`; +}