import matter from 'gray-matter'; const CACHE_TIME = 3600; const GOAL_LABEL = 'Goal'; export interface IIssue { id: number; title: string; comments: number; updatedAt: string; createdAt: string; assignee: string | null; name: string | null; completedPercentage: number | null; amountCents: number | null; description: string | null; } export interface IGoals { complete: IIssue[]; inProgress: IIssue[]; planned: IIssue[]; featuredFunded: IIssue; featuredUnfunded: IIssue; } export interface IGiteaIssue { id: number; title: string; body: string; comments: number; updated_at: string; created_at: string; assignee: string | null; } const bigHairyAudaciousGoal: IIssue = { id: 55234234, title: 'BHAG', comments: 0, updatedAt: '2023-09-20T08:54:01.373Z', createdAt: '2023-09-20T08:54:01.373Z', assignee: null, name: 'Big Hairy Audacious Goal', description: 'World domination!!!!!1', amountCents: 100000000, completedPercentage: 0.04 }; const defaultGoal: IIssue = { id: 55234233, title: 'e', comments: 0, updatedAt: '2023-09-20T08:54:01.373Z', createdAt: '2023-09-20T08:54:01.373Z', assignee: null, name: 'Generic', description: 'Getting started', amountCents: 200, completedPercentage: 1 }; export function calcPercent(goalAmountCents: number, totalPledgeSumCents: number): number { if (!goalAmountCents || totalPledgeSumCents <= 0) { return 0; } const output = Math.min(100, Math.floor((totalPledgeSumCents / goalAmountCents) * 100)); return output; } export async function getGoals(pledgeSum: number): Promise { try { const openData = await fetchAndParseData('open', pledgeSum); const closedData = await fetchAndParseData('closed', pledgeSum); const inProgress = filterByAssignee(openData); const planned = filterByAssignee(openData, true); const funded = filterAndSortGoals(openData.concat(closedData), true); const unfunded = filterAndSortGoals(openData.concat(closedData), false); console.log('the following are unfunded goals') console.log(unfunded) return { complete: closedData, inProgress, planned, featuredFunded: funded[funded.length - 1] || defaultGoal, featuredUnfunded: unfunded[0] || bigHairyAudaciousGoal }; } catch (error) { console.error('Error fetching goals:', error); return null; } } function filterByAssignee(issues: IIssue[], isPlanned: boolean = false): IIssue[] { return issues.filter((issue) => (isPlanned ? issue.assignee === null : issue.assignee !== null)) } async function fetchAndParseData(state: 'open' | 'closed', pledgeSum: number): Promise { const response = await fetch(`https://gitea.futureporn.net/api/v1/repos/futureporn/pm/issues?state=${state}&labels=${GOAL_LABEL}`, { next: { revalidate: CACHE_TIME, tags: ['goals'] }, }); if (!response.ok) return []; return response.json().then(issues => issues.map((g: IGiteaIssue) => parseGiteaGoal(g, pledgeSum))); } function filterAndSortGoals(issues: IIssue[], isFunded: boolean): IIssue[] { return issues .filter((issue) => issue.amountCents) .filter((issue) => (issue.completedPercentage === 100) === isFunded) .sort((b, a) => b.amountCents! - a.amountCents!); } function parseGiteaGoal(giteaIssue: IGiteaIssue, pledgeSum: number): IIssue { const headMatter: any = matter(giteaIssue.body); return { id: giteaIssue.id, title: giteaIssue.title, comments: giteaIssue.comments, updatedAt: giteaIssue.updated_at, createdAt: giteaIssue.created_at, assignee: giteaIssue.assignee, name: headMatter.data.name || '', description: headMatter.data.description || '', amountCents: headMatter.data.amountCents || 0, completedPercentage: calcPercent(headMatter.data.amountCents, pledgeSum) }; }