-
-
Notifications
You must be signed in to change notification settings - Fork 37
feat(gitea): add edge caching for tickets API to reduce Gitea traffic #629
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4afdb85
6c19930
1ea6702
e598ed9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { json } from '@sveltejs/kit'; | ||
| import axios from 'axios'; | ||
| import { env } from '$env/dynamic/private'; | ||
|
|
||
| import type { RequestHandler } from './$types'; | ||
| import type { GiteaIssue } from '$lib/types'; | ||
|
|
||
| async function fetchAllIssuesFromGitea(): Promise<GiteaIssue[]> { | ||
| if (!env.GITEA_API_URL || !env.GITEA_API_KEY) { | ||
| console.warn( | ||
| 'Gitea API configuration missing (GITEA_API_URL or GITEA_API_KEY). Returning empty array.' | ||
| ); | ||
| return []; | ||
| } | ||
|
|
||
| const headers = { | ||
| Authorization: `token ${env.GITEA_API_KEY}` | ||
| }; | ||
|
|
||
| const allIssues: GiteaIssue[] = []; | ||
| let page = 1; | ||
| // Gitea default max is often 50, request 50 per page to be safe | ||
| const limit = 50; | ||
|
|
||
| while (true) { | ||
| const response = await axios.get( | ||
| `${env.GITEA_API_URL}/api/v1/repos/teambtcmap/btcmap-data/issues?state=open&limit=${limit}&page=${page}`, | ||
| { headers } | ||
| ); | ||
|
|
||
| const issues = response.data.map((issue: GiteaIssue) => ({ | ||
| id: issue.id, | ||
| number: issue.number, | ||
| title: issue.title, | ||
| created_at: issue.created_at, | ||
| html_url: issue.html_url, | ||
| labels: issue.labels, | ||
| user: { | ||
| login: issue.user.login, | ||
| avatar_url: issue.user.avatar_url, | ||
| html_url: issue.user.html_url | ||
| }, | ||
| comments: issue.comments, | ||
| assignees: issue.assignees | ||
| })); | ||
|
|
||
| allIssues.push(...issues); | ||
|
|
||
| // If we got fewer than requested, we've reached the end | ||
| if (response.data.length < limit) break; | ||
| page++; | ||
| } | ||
|
Comment on lines
+25
to
+52
|
||
|
|
||
| return allIssues; | ||
| } | ||
|
|
||
| export const GET: RequestHandler = async ({ setHeaders }) => { | ||
| // Cache at edge for 24 hours, serve stale for another 24 hours while revalidating | ||
| setHeaders({ | ||
| 'Cache-Control': 'public, max-age=86400, stale-while-revalidate=86400' | ||
| }); | ||
|
|
||
| try { | ||
| const issues = await fetchAllIssuesFromGitea(); | ||
| return json({ issues, totalCount: issues.length }); | ||
| } catch (error) { | ||
| console.error('Failed to fetch issues from Gitea:', error); | ||
| return json({ issues: [], totalCount: 0, error: 'Failed to fetch issues' }, { status: 500 }); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,25 @@ | ||
| import { error, redirect } from '@sveltejs/kit'; | ||
| import axios from 'axios'; | ||
| import axiosRetry from 'axios-retry'; | ||
|
|
||
| import type { PageServerLoad } from './$types'; | ||
| import { getIssues } from '$lib/gitea'; | ||
| import type { GiteaIssue, Tickets } from '$lib/types'; | ||
|
|
||
| axiosRetry(axios, { retries: 3, retryDelay: axiosRetry.exponentialDelay }); | ||
|
|
||
| export const load: PageServerLoad = async ({ params }) => { | ||
| type TicketsResponse = { | ||
| issues: GiteaIssue[]; | ||
| totalCount: number; | ||
| error?: string; | ||
| }; | ||
|
Comment on lines
+10
to
+14
|
||
|
|
||
| function filterIssuesByLabel(issues: GiteaIssue[], labelName: string): GiteaIssue[] { | ||
| return issues.filter((issue) => | ||
| issue.labels.some((label) => label.name.toLowerCase() === labelName.toLowerCase()) | ||
| ); | ||
| } | ||
|
Comment on lines
+16
to
+20
|
||
|
|
||
| export const load: PageServerLoad = async ({ params, fetch }) => { | ||
| const { area, section } = params; | ||
|
|
||
| // Validate section parameter | ||
|
|
@@ -18,9 +31,15 @@ export const load: PageServerLoad = async ({ params }) => { | |
| const areaResponse = await axios.get(`https://api.btcmap.org/v2/areas/${area}`); | ||
| const fetchedArea = areaResponse.data; | ||
|
|
||
| const { issues: tickets } = await getIssues([fetchedArea.tags.url_alias]).catch(() => ({ | ||
| issues: 'error' | ||
| })); | ||
| // Fetch from cached /api/tickets endpoint and filter by area label | ||
| let tickets: Tickets; | ||
| try { | ||
| const ticketsResponse = await fetch('/api/tickets'); | ||
| const ticketsData: TicketsResponse = await ticketsResponse.json(); | ||
|
Comment on lines
+37
to
+38
|
||
| tickets = filterIssuesByLabel(ticketsData.issues, fetchedArea.tags.url_alias); | ||
| } catch { | ||
| tickets = 'error'; | ||
| } | ||
|
|
||
| const issuesResponse = await fetch('https://api.btcmap.org/rpc', { | ||
| method: 'POST', | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,25 @@ | ||
| import { error, redirect } from '@sveltejs/kit'; | ||
| import axios from 'axios'; | ||
| import axiosRetry from 'axios-retry'; | ||
|
|
||
| import type { PageServerLoad } from './$types'; | ||
| import { getIssues } from '$lib/gitea'; | ||
| import type { GiteaIssue, Tickets } from '$lib/types'; | ||
|
|
||
| axiosRetry(axios, { retries: 3, retryDelay: axiosRetry.exponentialDelay }); | ||
|
|
||
| export const load: PageServerLoad = async ({ params }) => { | ||
| type TicketsResponse = { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is already defined above, can we move this to types or |
||
| issues: GiteaIssue[]; | ||
| totalCount: number; | ||
| error?: string; | ||
| }; | ||
|
Comment on lines
+10
to
+14
|
||
|
|
||
| function filterIssuesByLabel(issues: GiteaIssue[], labelName: string): GiteaIssue[] { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This and below also duplicates above. Maybe the AI has an idea how to DRY this reasonabale? |
||
| return issues.filter((issue) => | ||
| issue.labels.some((label) => label.name.toLowerCase() === labelName.toLowerCase()) | ||
| ); | ||
| } | ||
|
Comment on lines
+16
to
+20
|
||
|
|
||
| export const load: PageServerLoad = async ({ params, fetch }) => { | ||
| const { area, section } = params; | ||
|
|
||
| // Validate section parameter - default to merchants if not provided | ||
|
|
@@ -20,9 +33,15 @@ export const load: PageServerLoad = async ({ params }) => { | |
| const areaResponse = await axios.get(`https://api.btcmap.org/v2/areas/${area}`); | ||
| const fetchedArea = areaResponse.data; | ||
|
|
||
| const { issues: tickets } = await getIssues([fetchedArea.tags.url_alias]).catch(() => ({ | ||
| issues: 'error' | ||
| })); | ||
| // Fetch from cached /api/tickets endpoint and filter by area label | ||
| let tickets: Tickets; | ||
| try { | ||
| const ticketsResponse = await fetch('/api/tickets'); | ||
| const ticketsData: TicketsResponse = await ticketsResponse.json(); | ||
|
Comment on lines
+39
to
+40
|
||
| tickets = filterIssuesByLabel(ticketsData.issues, fetchedArea.tags.url_alias); | ||
| } catch { | ||
| tickets = 'error'; | ||
| } | ||
|
|
||
| const issuesResponse = await fetch('https://api.btcmap.org/rpc', { | ||
| method: 'POST', | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,20 @@ | ||||||||||||||
| import { getIssues } from '$lib/gitea'; | ||||||||||||||
| import type { GiteaIssue } from '$lib/types'; | ||||||||||||||
| import type { PageServerLoad } from './$types'; | ||||||||||||||
|
|
||||||||||||||
| export const load: PageServerLoad = async () => { | ||||||||||||||
| type TicketsResponse = { | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above |
||||||||||||||
| issues: GiteaIssue[]; | ||||||||||||||
| totalCount: number; | ||||||||||||||
| error?: string; | ||||||||||||||
| }; | ||||||||||||||
|
Comment on lines
+4
to
+8
|
||||||||||||||
|
|
||||||||||||||
| export const load: PageServerLoad = async ({ fetch }) => { | ||||||||||||||
| try { | ||||||||||||||
| const { issues, totalCount } = await getIssues(); | ||||||||||||||
| const response = await fetch('/api/tickets'); | ||||||||||||||
|
||||||||||||||
| const response = await fetch('/api/tickets'); | |
| const response = await fetch('/api/tickets'); | |
| if (!response.ok) { | |
| throw new Error(`Failed to fetch tickets: ${response.status} ${response.statusText}`); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing axios-retry configuration for resilience. Other API endpoints in this codebase use axiosRetry with exponential backoff for better reliability. Consider adding similar retry logic here to handle transient Gitea API failures, especially since this endpoint is cached for 24 hours.