diff --git a/apps/openpage-api/app/community/route.ts b/apps/openpage-api/app/community/route.ts new file mode 100644 index 000000000..8e75178c6 --- /dev/null +++ b/apps/openpage-api/app/community/route.ts @@ -0,0 +1,36 @@ +import type { NextRequest } from 'next/server'; + +import cors from '@/lib/cors'; + +const paths = [ + { path: '/total-prs', description: 'Total GitHub Forks' }, + { path: '/total-stars', description: 'Total GitHub Stars' }, + { path: '/total-forks', description: 'Total GitHub Forks' }, + { path: '/total-issues', description: 'Total GitHub Issues' }, +]; + +export function GET(request: NextRequest) { + const url = request.nextUrl.toString(); + const apis = paths.map(({ path, description }) => { + return { path: url + path, description }; + }); + + return cors( + request, + new Response(JSON.stringify(apis), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }), + ); +} + +export function OPTIONS(request: Request) { + return cors( + request, + new Response(null, { + status: 204, + }), + ); +} diff --git a/apps/openpage-api/app/community/total-forks/route.ts b/apps/openpage-api/app/community/total-forks/route.ts new file mode 100644 index 000000000..df5297aea --- /dev/null +++ b/apps/openpage-api/app/community/total-forks/route.ts @@ -0,0 +1,27 @@ +import cors from '@/lib/cors'; +import { transformRepoStats } from '@/lib/transform-repo-stats'; + +export async function GET(request: Request) { + const res = await fetch('https://stargrazer-live.onrender.com/api/stats'); + const data = await res.json(); + const transformedData = transformRepoStats(data, 'forks'); + + return cors( + request, + new Response(JSON.stringify({ 'total-forks': transformedData }), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }), + ); +} + +export function OPTIONS(request: Request) { + return cors( + request, + new Response(null, { + status: 204, + }), + ); +} diff --git a/apps/openpage-api/app/community/total-issues/route.ts b/apps/openpage-api/app/community/total-issues/route.ts new file mode 100644 index 000000000..23f160657 --- /dev/null +++ b/apps/openpage-api/app/community/total-issues/route.ts @@ -0,0 +1,27 @@ +import cors from '@/lib/cors'; +import { transformRepoStats } from '@/lib/transform-repo-stats'; + +export async function GET(request: Request) { + const res = await fetch('https://stargrazer-live.onrender.com/api/stats'); + const data = await res.json(); + const transformedData = transformRepoStats(data, 'openIssues'); + + return cors( + request, + new Response(JSON.stringify({ 'total-issues': transformedData }), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }), + ); +} + +export function OPTIONS(request: Request) { + return cors( + request, + new Response(null, { + status: 204, + }), + ); +} diff --git a/apps/openpage-api/app/community/total-prs/route.ts b/apps/openpage-api/app/community/total-prs/route.ts new file mode 100644 index 000000000..fd30b964e --- /dev/null +++ b/apps/openpage-api/app/community/total-prs/route.ts @@ -0,0 +1,27 @@ +import cors from '@/lib/cors'; +import { transformRepoStats } from '@/lib/transform-repo-stats'; + +export async function GET(request: Request) { + const res = await fetch('https://stargrazer-live.onrender.com/api/stats'); + const data = await res.json(); + const transformedData = transformRepoStats(data, 'stars'); + + return cors( + request, + new Response(JSON.stringify({ 'total-stars': transformedData }), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }), + ); +} + +export function OPTIONS(request: Request) { + return cors( + request, + new Response(null, { + status: 204, + }), + ); +} diff --git a/apps/openpage-api/app/community/total-stars/route.ts b/apps/openpage-api/app/community/total-stars/route.ts new file mode 100644 index 000000000..fd30b964e --- /dev/null +++ b/apps/openpage-api/app/community/total-stars/route.ts @@ -0,0 +1,27 @@ +import cors from '@/lib/cors'; +import { transformRepoStats } from '@/lib/transform-repo-stats'; + +export async function GET(request: Request) { + const res = await fetch('https://stargrazer-live.onrender.com/api/stats'); + const data = await res.json(); + const transformedData = transformRepoStats(data, 'stars'); + + return cors( + request, + new Response(JSON.stringify({ 'total-stars': transformedData }), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }), + ); +} + +export function OPTIONS(request: Request) { + return cors( + request, + new Response(null, { + status: 204, + }), + ); +} diff --git a/apps/openpage-api/app/route.ts b/apps/openpage-api/app/route.ts index dd487f707..b218cb0b9 100644 --- a/apps/openpage-api/app/route.ts +++ b/apps/openpage-api/app/route.ts @@ -2,7 +2,10 @@ import type { NextRequest } from 'next/server'; import cors from '@/lib/cors'; -const paths = [{ path: 'github', description: 'GitHub Data' }]; +const paths = [ + { path: 'github', description: 'GitHub Data' }, + { path: 'community', description: 'Community Data' }, +]; export function GET(request: NextRequest) { const url = request.nextUrl.toString(); diff --git a/apps/openpage-api/lib/transform-repo-stats.ts b/apps/openpage-api/lib/transform-repo-stats.ts new file mode 100644 index 000000000..e6a9b139a --- /dev/null +++ b/apps/openpage-api/lib/transform-repo-stats.ts @@ -0,0 +1,71 @@ +// Define interfaces for the data structure +type RepoStats = { + stars: number; + forks: number; + mergedPRs: number; + openIssues: number; +}; + +type DataEntry = { + [key: string]: RepoStats; +}; + +type TransformedData = { + labels: string[]; + datasets: { + label: string; + data: number[]; + }[]; +}; + +type MonthNames = { + [key: string]: string; +}; + +type MetricKey = keyof RepoStats; + +const FRIENDLY_METRIC_NAMES: { [key in MetricKey]: string } = { + stars: 'Stars', + forks: 'Forks', + mergedPRs: 'Merged PRs', + openIssues: 'Open Issues', +}; + +export function transformRepoStats(data: DataEntry, metric: MetricKey): TransformedData { + const sortedEntries = Object.entries(data).sort(([dateA], [dateB]) => { + const [yearA, monthA] = dateA.split('-').map(Number); + const [yearB, monthB] = dateB.split('-').map(Number); + return new Date(yearA, monthA - 1).getTime() - new Date(yearB, monthB - 1).getTime(); + }); + + const monthNames: MonthNames = { + '1': 'Jan', + '2': 'Feb', + '3': 'Mar', + '4': 'Apr', + '5': 'May', + '6': 'Jun', + '7': 'Jul', + '8': 'Aug', + '9': 'Sep', + '10': 'Oct', + '11': 'Nov', + '12': 'Dec', + }; + + const labels = sortedEntries.map(([date]) => { + const [year, month] = date.split('-'); + const monthIndex = parseInt(month); + return `${monthNames[monthIndex.toString()]} ${year}`; + }); + + return { + labels, + datasets: [ + { + label: `Total ${FRIENDLY_METRIC_NAMES[metric]}`, + data: sortedEntries.map(([_, stats]) => stats[metric]), + }, + ], + }; +} diff --git a/package.json b/package.json index 827d1ece6..f29cf2e45 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dev:marketing": "turbo run dev --filter=@documenso/marketing", "dev:docs": "turbo run dev --filter=@documenso/documentation", "dev:openpage-api": "turbo run dev --filter=@documenso/openpage-api", - "start": "turbo run start --filter=@documenso/web --filter=@documenso/marketing --filter=@documenso/documentation", + "start": "turbo run start --filter=@documenso/web --filter=@documenso/marketing --filter=@documenso/documentation --filter=@documenso/openpage-api", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", "format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",