mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-25 08:48:13 +02:00
fix: fix upgrade banner to be triggered after package upload
This commit is contained in:
parent
dbc174c867
commit
f929a332bb
2 changed files with 85 additions and 14 deletions
77
ui/src/app/api/config/latest-version/route.ts
Normal file
77
ui/src/app/api/config/latest-version/route.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { NextResponse } from "next/server";
|
||||
|
||||
const GHCR_IMAGES = ["dograh-hq/dograh-ui", "dograh-hq/dograh-api"] as const;
|
||||
const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/;
|
||||
const REVALIDATE_SECONDS = 60 * 60;
|
||||
|
||||
type Semver = [number, number, number];
|
||||
|
||||
function parseSemver(tag: string): Semver | null {
|
||||
const m = tag.match(SEMVER_RE);
|
||||
if (!m) return null;
|
||||
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
||||
}
|
||||
|
||||
function compareSemver(a: Semver, b: Semver): number {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (a[i] !== b[i]) return a[i] - b[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function fetchLatestTag(image: string): Promise<string | null> {
|
||||
const tokenRes = await fetch(
|
||||
`https://ghcr.io/token?scope=repository:${image}:pull&service=ghcr.io`,
|
||||
{ next: { revalidate: REVALIDATE_SECONDS } },
|
||||
);
|
||||
if (!tokenRes.ok) return null;
|
||||
const { token } = (await tokenRes.json()) as { token?: string };
|
||||
if (!token) return null;
|
||||
|
||||
const tagsRes = await fetch(`https://ghcr.io/v2/${image}/tags/list`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
next: { revalidate: REVALIDATE_SECONDS },
|
||||
});
|
||||
if (!tagsRes.ok) return null;
|
||||
const { tags } = (await tagsRes.json()) as { tags?: string[] };
|
||||
|
||||
let latest: { tag: string; parsed: Semver } | null = null;
|
||||
for (const tag of tags ?? []) {
|
||||
const parsed = parseSemver(tag);
|
||||
if (!parsed) continue;
|
||||
if (!latest || compareSemver(parsed, latest.parsed) > 0) {
|
||||
latest = { tag, parsed };
|
||||
}
|
||||
}
|
||||
return latest?.tag ?? null;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const results = await Promise.all(GHCR_IMAGES.map(fetchLatestTag));
|
||||
|
||||
// Only advertise an update once every image has published a tag at that
|
||||
// version — otherwise we'd nudge users to upgrade before the matching
|
||||
// container actually exists.
|
||||
let minLatest: { tag: string; parsed: Semver } | null = null;
|
||||
for (const tag of results) {
|
||||
if (!tag) return NextResponse.json({ latest: null }, { status: 200 });
|
||||
const parsed = parseSemver(tag);
|
||||
if (!parsed) return NextResponse.json({ latest: null }, { status: 200 });
|
||||
if (!minLatest || compareSemver(parsed, minLatest.parsed) < 0) {
|
||||
minLatest = { tag, parsed };
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ latest: minLatest?.tag ?? null },
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": `public, max-age=${REVALIDATE_SECONDS}, s-maxage=${REVALIDATE_SECONDS}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return NextResponse.json({ latest: null }, { status: 200 });
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue