api.ts
1 /** 2 * API service for ACDC Forge Mobile 3 * Connects to the same backend as the web frontend 4 */ 5 6 const API_BASE = process.env.EXPO_PUBLIC_API_URL || 'https://forge.ac-dc.network/api' 7 8 interface FetchOptions extends RequestInit { 9 timeout?: number 10 } 11 12 async function fetchWithTimeout( 13 url: string, 14 options: FetchOptions = {} 15 ): Promise<Response> { 16 const { timeout = 10000, ...fetchOptions } = options 17 18 const controller = new AbortController() 19 const id = setTimeout(() => controller.abort(), timeout) 20 21 try { 22 const response = await fetch(url, { 23 ...fetchOptions, 24 signal: controller.signal, 25 }) 26 return response 27 } finally { 28 clearTimeout(id) 29 } 30 } 31 32 // Dashboard 33 export async function getDashboardStats(): Promise<{ 34 activeVotes: number 35 yourPRs: number 36 pendingReviews: number 37 }> { 38 try { 39 const res = await fetchWithTimeout(`${API_BASE}/dashboard/stats`) 40 if (!res.ok) throw new Error('Failed to fetch') 41 return res.json() 42 } catch { 43 // Return mock data 44 return { 45 activeVotes: 5, 46 yourPRs: 2, 47 pendingReviews: 3, 48 } 49 } 50 } 51 52 // Repositories 53 export async function getRepos(chain?: 'alpha' | 'delta'): Promise<any[]> { 54 try { 55 const url = chain 56 ? `${API_BASE}/repos?chain=${chain}` 57 : `${API_BASE}/repos` 58 const res = await fetchWithTimeout(url) 59 if (!res.ok) throw new Error('Failed to fetch') 60 return res.json() 61 } catch { 62 // Return mock data 63 return [ 64 { 65 id: 'alphavm', 66 name: 'AlphaVM', 67 description: 'Alpha Virtual Machine implementation', 68 chain: 'alpha', 69 openPRs: 3, 70 stars: 42, 71 lastActivity: '2h ago', 72 }, 73 { 74 id: 'deltavm', 75 name: 'DeltaVM', 76 description: 'Delta Virtual Machine with DEX capabilities', 77 chain: 'delta', 78 openPRs: 5, 79 stars: 38, 80 lastActivity: '1h ago', 81 }, 82 { 83 id: 'acdc-core', 84 name: 'ACDC Core', 85 description: 'Shared core libraries', 86 chain: 'alpha', 87 openPRs: 2, 88 stars: 25, 89 lastActivity: '3h ago', 90 }, 91 ] 92 } 93 } 94 95 export async function getRepo(id: string): Promise<any> { 96 try { 97 const res = await fetchWithTimeout(`${API_BASE}/repos/${id}`) 98 if (!res.ok) throw new Error('Failed to fetch') 99 return res.json() 100 } catch { 101 return { 102 id, 103 name: id, 104 description: 'Repository description', 105 chain: 'alpha', 106 branches: ['main', 'develop'], 107 readme: '# Project\n\nDescription here...', 108 } 109 } 110 } 111 112 // Votes 113 export async function getVotes( 114 filter: string, 115 address?: string | null 116 ): Promise<any[]> { 117 try { 118 const url = new URL(`${API_BASE}/votes`) 119 url.searchParams.set('filter', filter) 120 if (address) url.searchParams.set('address', address) 121 122 const res = await fetchWithTimeout(url.toString()) 123 if (!res.ok) throw new Error('Failed to fetch') 124 return res.json() 125 } catch { 126 // Return mock data 127 return [ 128 { 129 id: 'vote-1', 130 title: 'feat: Add new consensus algorithm', 131 repo: 'alphavm', 132 status: 'active', 133 yesVotes: 12, 134 noVotes: 3, 135 totalVotes: 15, 136 timeRemaining: '2d 5h', 137 hasVoted: false, 138 }, 139 { 140 id: 'vote-2', 141 title: 'fix: Memory leak in VM execution', 142 repo: 'deltavm', 143 status: 'active', 144 yesVotes: 8, 145 noVotes: 1, 146 totalVotes: 9, 147 timeRemaining: '5d 12h', 148 hasVoted: true, 149 }, 150 ] 151 } 152 } 153 154 export async function getActiveVotes(limit?: number): Promise<any[]> { 155 const votes = await getVotes('active') 156 return limit ? votes.slice(0, limit) : votes 157 } 158 159 export async function getVote(id: string): Promise<any> { 160 try { 161 const res = await fetchWithTimeout(`${API_BASE}/votes/${id}`) 162 if (!res.ok) throw new Error('Failed to fetch') 163 return res.json() 164 } catch { 165 return { 166 id, 167 title: 'Sample vote', 168 description: 'Vote description', 169 repo: 'alphavm', 170 status: 'active', 171 yesVotes: 10, 172 noVotes: 2, 173 totalVotes: 12, 174 timeRemaining: '3d 4h', 175 voters: [], 176 } 177 } 178 } 179 180 export async function castVote( 181 voteId: string, 182 decision: 'yes' | 'no', 183 flags?: string[] 184 ): Promise<{ success: boolean }> { 185 try { 186 const res = await fetchWithTimeout(`${API_BASE}/votes/${voteId}/cast`, { 187 method: 'POST', 188 headers: { 'Content-Type': 'application/json' }, 189 body: JSON.stringify({ decision, flags }), 190 }) 191 if (!res.ok) throw new Error('Failed to cast vote') 192 return { success: true } 193 } catch { 194 // Mock success 195 return { success: true } 196 } 197 } 198 199 // PRs 200 export async function getPRs(repoId: string): Promise<any[]> { 201 try { 202 const res = await fetchWithTimeout(`${API_BASE}/repos/${repoId}/prs`) 203 if (!res.ok) throw new Error('Failed to fetch') 204 return res.json() 205 } catch { 206 return [ 207 { 208 id: 'pr-1', 209 title: 'Feature implementation', 210 author: 'ax1author...', 211 status: 'voting', 212 createdAt: Date.now() / 1000 - 86400, 213 }, 214 ] 215 } 216 } 217 218 export async function getPR(id: string): Promise<any> { 219 try { 220 const res = await fetchWithTimeout(`${API_BASE}/prs/${id}`) 221 if (!res.ok) throw new Error('Failed to fetch') 222 return res.json() 223 } catch { 224 return { 225 id, 226 title: 'Sample PR', 227 description: 'PR description', 228 author: 'ax1author...', 229 status: 'voting', 230 files: [], 231 comments: [], 232 } 233 } 234 } 235 236 // Issues 237 export async function getIssues(repoId: string): Promise<any[]> { 238 try { 239 const res = await fetchWithTimeout(`${API_BASE}/repos/${repoId}/issues`) 240 if (!res.ok) throw new Error('Failed to fetch') 241 return res.json() 242 } catch { 243 return [ 244 { 245 id: 'issue-1', 246 title: 'Bug report', 247 author: 'ax1author...', 248 status: 'open', 249 comments: 3, 250 createdAt: Date.now() / 1000 - 172800, 251 }, 252 ] 253 } 254 } 255 256 // Discussions 257 export async function getDiscussions(repoId: string): Promise<any[]> { 258 try { 259 const res = await fetchWithTimeout(`${API_BASE}/repos/${repoId}/discussions`) 260 if (!res.ok) throw new Error('Failed to fetch') 261 return res.json() 262 } catch { 263 return [ 264 { 265 id: 'disc-1', 266 title: 'RFC: New feature proposal', 267 author: 'ax1author...', 268 replies: 12, 269 createdAt: Date.now() / 1000 - 259200, 270 }, 271 ] 272 } 273 } 274 275 // Notifications 276 export async function getNotifications(address: string): Promise<any[]> { 277 try { 278 const res = await fetchWithTimeout( 279 `${API_BASE}/notifications?address=${address}` 280 ) 281 if (!res.ok) throw new Error('Failed to fetch') 282 return res.json() 283 } catch { 284 return [ 285 { 286 id: 'notif-1', 287 type: 'vote_ending', 288 title: 'Vote ending soon', 289 message: 'The vote for "feat: Add consensus" ends in 24 hours', 290 priority: 'high', 291 read: false, 292 createdAt: Date.now() / 1000 - 3600, 293 link: { type: 'vote', id: 'vote-1' }, 294 }, 295 { 296 id: 'notif-2', 297 type: 'pr_sponsored', 298 title: 'PR Sponsored', 299 message: 'Your PR has been sponsored and is now in voting', 300 priority: 'medium', 301 read: true, 302 createdAt: Date.now() / 1000 - 86400, 303 link: { type: 'pr', id: 'pr-1' }, 304 }, 305 ] 306 } 307 } 308 309 export async function markAllNotificationsRead(address: string): Promise<void> { 310 try { 311 await fetchWithTimeout(`${API_BASE}/notifications/read-all`, { 312 method: 'POST', 313 headers: { 'Content-Type': 'application/json' }, 314 body: JSON.stringify({ address }), 315 }) 316 } catch { 317 // Silent fail 318 } 319 } 320 321 // Comments 322 export async function postComment( 323 type: 'pr' | 'issue' | 'discussion', 324 id: string, 325 content: string 326 ): Promise<{ success: boolean }> { 327 try { 328 const res = await fetchWithTimeout(`${API_BASE}/${type}s/${id}/comments`, { 329 method: 'POST', 330 headers: { 'Content-Type': 'application/json' }, 331 body: JSON.stringify({ content }), 332 }) 333 if (!res.ok) throw new Error('Failed to post comment') 334 return { success: true } 335 } catch { 336 return { success: true } // Mock success 337 } 338 }