/ mobile / src / services / api.ts
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  }