1 import type { APIRoute } from 'astro'
2 import type {
3 GitHubRelease,
4 DownloadLink,
5 DownloadsResponse,
6 PlatformType,
7 ArchitectureType
8 } from '@/types/github-releases'
9
10 export const prerender = false
11
12 export const GET: APIRoute = async ({ url }) => {
13 try {
14 const GITHUB_TOKEN = import.meta.env.GITHUB_TOKEN
15 const GITHUB_REPO = 'setoelkahfi/pepakbasajawa'
16
17 if (!GITHUB_TOKEN) {
18 console.error('GITHUB_TOKEN is not configured')
19 return new Response(JSON.stringify({ error: 'Server configuration error' }), {
20 status: 500,
21 headers: { 'Content-Type': 'application/json' }
22 })
23 }
24
25 const action = url.searchParams.get('action')
26 const assetId = url.searchParams.get('assetId')
27
28 // If requesting a specific asset download
29 if (action === 'download' && assetId) {
30 const response = await fetch(
31 `https://api.github.com/repos/${GITHUB_REPO}/releases/assets/${assetId}`,
32 {
33 headers: {
34 Authorization: `Bearer ${GITHUB_TOKEN}`,
35 Accept: 'application/octet-stream',
36 'X-GitHub-Api-Version': '2022-11-28'
37 },
38 redirect: 'manual'
39 }
40 )
41
42 if (response.status === 302 || response.status === 301) {
43 const redirectUrl = response.headers.get('location')
44 if (redirectUrl) {
45 return Response.redirect(redirectUrl, 302)
46 }
47 }
48
49 return new Response(JSON.stringify({ error: 'Failed to get download URL' }), {
50 status: 500,
51 headers: { 'Content-Type': 'application/json' }
52 })
53 }
54
55 // Fetch latest release
56 const response = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`, {
57 headers: {
58 Authorization: `Bearer ${GITHUB_TOKEN}`,
59 Accept: 'application/vnd.github+json',
60 'X-GitHub-Api-Version': '2022-11-28'
61 }
62 })
63
64 if (!response.ok) {
65 const errorData = await response.text()
66 console.error('GitHub API error:', errorData)
67 return new Response(
68 JSON.stringify({
69 error: 'Failed to fetch releases',
70 details: errorData
71 }),
72 {
73 status: response.status,
74 headers: { 'Content-Type': 'application/json' }
75 }
76 )
77 }
78
79 const release: GitHubRelease = await response.json()
80 const downloads: DownloadLink[] = []
81
82 // Parse assets and categorize by platform
83 for (const asset of release.assets) {
84 const filename = asset.name.toLowerCase()
85 let platform: PlatformType | null = null
86 let architecture: ArchitectureType | undefined = undefined
87
88 // macOS
89 if (filename.includes('mac') || filename.includes('darwin') || filename.endsWith('.dmg')) {
90 platform = 'macOS'
91 if (
92 filename.includes('arm64') ||
93 filename.includes('aarch64') ||
94 filename.includes('apple')
95 ) {
96 architecture = 'Apple Silicon'
97 } else if (
98 filename.includes('x64') ||
99 filename.includes('intel') ||
100 filename.includes('x86_64')
101 ) {
102 architecture = 'Intel'
103 } else {
104 architecture = 'Universal'
105 }
106 }
107 // Windows
108 else if (filename.includes('win') || filename.endsWith('.exe') || filename.endsWith('.msi')) {
109 platform = 'Windows'
110 if (filename.includes('x64') || filename.includes('x86_64')) {
111 architecture = 'x64'
112 } else if (filename.includes('arm64')) {
113 architecture = 'ARM64'
114 } else {
115 architecture = 'x64'
116 }
117 }
118 // Linux
119 else if (
120 filename.includes('linux') ||
121 filename.endsWith('.appimage') ||
122 filename.endsWith('.deb') ||
123 filename.endsWith('.rpm') ||
124 filename.endsWith('.tar.gz')
125 ) {
126 platform = 'Linux'
127 if (filename.includes('arm64') || filename.includes('aarch64')) {
128 architecture = 'ARM64'
129 } else if (filename.includes('x64') || filename.includes('x86_64')) {
130 architecture = 'x64'
131 } else {
132 architecture = 'x64'
133 }
134 }
135
136 if (platform) {
137 downloads.push({
138 platform,
139 architecture,
140 url: `/api/download-desktop-app?action=download&assetId=${asset.id}`,
141 filename: asset.name,
142 size: asset.size,
143 version: release.tag_name,
144 assetId: asset.id
145 })
146 }
147 }
148
149 const responseData: DownloadsResponse = {
150 version: release.tag_name,
151 name: release.name,
152 published_at: release.published_at,
153 downloads
154 }
155
156 return new Response(JSON.stringify(responseData), {
157 status: 200,
158 headers: {
159 'Content-Type': 'application/json',
160 'Cache-Control': 'public, max-age=300' // Cache for 5 minutes
161 }
162 })
163 } catch (error) {
164 console.error('Error fetching releases:', error)
165 return new Response(
166 JSON.stringify({
167 error: 'Internal server error',
168 message: error instanceof Error ? error.message : 'Unknown error'
169 }),
170 { status: 500, headers: { 'Content-Type': 'application/json' } }
171 )
172 }
173 }