// Netlify Function: GET /api/latest-release // // The download page must not carry its own hard-coded version/hash. Forgejo is // the release source of truth; this small same-origin proxy lets the static page // read the newest release without relying on third-party CORS. const OWNER = 'skatespots'; const REPO = 'SkateSpots'; const API = `https://git.skate-spots.org/api/v1/repos/${OWNER}/${REPO}/releases/latest`; const headers = { 'content-type': 'application/json; charset=utf-8', // Short cache means a new Forgejo release appears without a redeploy, while // still shielding the Forgejo API from every page view. 'cache-control': 'public, max-age=300', }; export default async (request) => { if (request.method !== 'GET' && request.method !== 'HEAD') { return new Response(JSON.stringify({ error: 'method not allowed' }), { status: 405, headers: { ...headers, allow: 'GET, HEAD' }, }); } try { const releaseRes = await fetch(API, { headers: { accept: 'application/json' }, signal: AbortSignal.timeout(8000), }); if (!releaseRes.ok) throw new Error(`Forgejo release API returned ${releaseRes.status}`); const release = await releaseRes.json(); const version = String(release.tag_name || '').replace(/^v/, ''); const assets = Array.isArray(release.assets) ? release.assets : []; const apk = assets.find((asset) => /\.apk$/i.test(asset.name || '')); const shaAsset = assets.find((asset) => /\.apk\.sha256$/i.test(asset.name || '')); if (!version || !apk?.browser_download_url) throw new Error('Latest release has no APK asset'); let sha256 = ''; if (shaAsset?.browser_download_url) { try { const shaRes = await fetch(shaAsset.browser_download_url, { signal: AbortSignal.timeout(5000), }); if (shaRes.ok) { const text = await shaRes.text(); sha256 = text.trim().split(/\s+/)[0]?.toLowerCase() || ''; } } catch { // The APK is still useful if the checksum asset is briefly unavailable. } } return new Response(JSON.stringify({ version, apkUrl: apk.browser_download_url, sha256, size: apk.size || 0, publishedAt: release.published_at || release.created_at || null, }), { headers }); } catch (error) { return new Response(JSON.stringify({ error: 'latest release unavailable', detail: error instanceof Error ? error.message : String(error), }), { status: 502, headers: { ...headers, 'cache-control': 'no-store' }, }); } }; export const config = { path: '/api/latest-release' };