diff --git a/packages/app/server/routes/~/[owner]/[repo].head.ts b/packages/app/server/routes/~/[owner]/[repo].head.ts new file mode 100644 index 00000000..10a7b95c --- /dev/null +++ b/packages/app/server/routes/~/[owner]/[repo].head.ts @@ -0,0 +1,60 @@ +import { + createError, + defineEventHandler, + getRouterParams, + setHeader, +} from "h3"; +import { hasRepoReleases } from "../../../utils/bucket"; + +export default defineEventHandler(async (event) => { + const { owner, repo } = getRouterParams(event) as { + owner?: string; + repo?: string; + }; + + if (!owner || !repo) { + throw createError({ + statusCode: 400, + statusMessage: "Owner and repo are required", + }); + } + + const normalizedOwner = owner.trim(); + const normalizedRepo = repo.trim(); + + if (!normalizedOwner || !normalizedRepo) { + throw createError({ + statusCode: 400, + statusMessage: "Owner and repo are required", + }); + } + + const exactMatch = await hasRepoReleases( + event, + normalizedOwner, + normalizedRepo, + ); + const hasReleases = + exactMatch || + (await hasRepoReleases( + event, + normalizedOwner.toLowerCase(), + normalizedRepo.toLowerCase(), + )); + + setHeader( + event, + "Cache-Control", + "public, max-age=60, s-maxage=300, stale-while-revalidate=600", + ); + setHeader(event, "x-has-releases", hasReleases ? "1" : "0"); + + if (!hasReleases) { + throw createError({ + statusCode: 404, + statusMessage: "No releases found", + }); + } + + return null; +}); diff --git a/packages/app/server/utils/bucket.ts b/packages/app/server/utils/bucket.ts index f962525f..c05ed490 100644 --- a/packages/app/server/utils/bucket.ts +++ b/packages/app/server/utils/bucket.ts @@ -111,6 +111,26 @@ export async function getRepoReleaseCount( } } +export async function hasRepoReleases( + event: Event, + owner: string, + repo: string, +): Promise { + try { + const binding = useBinding(event); + const prefix = `${usePackagesBucket.base}:${owner}:${repo}:`; + const response = await binding.list({ + limit: 1, + prefix, + } as any); + + return response.objects.length > 0; + } catch (error) { + console.error(`Error checking releases for ${owner}/${repo}:`, error); + return false; + } +} + export function useTemplatesBucket(event: Event) { const storage = useBucket(event); return prefixStorage(storage, useTemplatesBucket.key);