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