diff --git a/README.md b/README.md index 4c9eedb..b8712ea 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ NEXT_PUBLIC_WEB_HOST = "127.0.0.1:3000" # Web frontend host # API Tokens GITHUB_TOKEN = "ghp_Foo1234567" # Required for GitHub badges CRATESIO_TOKEN = "cio51fdR1234567" # Required for crates.io badges +CODEBERG_TOKEN = "foobar123456789" # Required for Codeberg badges ``` ### spacebadgers diff --git a/badgers-web/src/app/codeberg/closed-issues/[owner]/[repo]/route.ts b/badgers-web/src/app/codeberg/closed-issues/[owner]/[repo]/route.ts new file mode 100644 index 0000000..b8c67f3 --- /dev/null +++ b/badgers-web/src/app/codeberg/closed-issues/[owner]/[repo]/route.ts @@ -0,0 +1,19 @@ +import { NextRequest } from "next/server" + +import Badge from '@/utils/Badge' +import Codeberg from '@/utils/Codeberg' + +interface Params { + params: { + owner: string + repo: string + } +} + +export async function GET(request: NextRequest, { params: { owner, repo } }: Params) { + const closedIssuesCount = await Codeberg.getClient().getIssuesCount({ owner, repo }, { type: 'issues', state: 'closed' }) + + return await Badge.generate(request, 'closed issues', closedIssuesCount?.toString() ?? 'None') +} + +export const runtime = 'edge' diff --git a/badgers-web/src/app/codeberg/issues/[owner]/[repo]/route.ts b/badgers-web/src/app/codeberg/issues/[owner]/[repo]/route.ts new file mode 100644 index 0000000..c09e058 --- /dev/null +++ b/badgers-web/src/app/codeberg/issues/[owner]/[repo]/route.ts @@ -0,0 +1,19 @@ +import { NextRequest } from "next/server" + +import Badge from '@/utils/Badge' +import Codeberg from '@/utils/Codeberg' + +interface Params { + params: { + owner: string + repo: string + } +} + +export async function GET(request: NextRequest, { params: { owner, repo } }: Params) { + const issuesCount = await Codeberg.getClient().getIssuesCount({ owner, repo }, { type: 'issues', state: 'all' }) + + return await Badge.generate(request, 'issues', issuesCount?.toString() ?? 'None') +} + +export const runtime = 'edge' diff --git a/badgers-web/src/app/codeberg/open-issues/[owner]/[repo]/route.ts b/badgers-web/src/app/codeberg/open-issues/[owner]/[repo]/route.ts new file mode 100644 index 0000000..fffaf1d --- /dev/null +++ b/badgers-web/src/app/codeberg/open-issues/[owner]/[repo]/route.ts @@ -0,0 +1,19 @@ +import { NextRequest } from "next/server" + +import Badge from '@/utils/Badge' +import Codeberg from '@/utils/Codeberg' + +interface Params { + params: { + owner: string + repo: string + } +} + +export async function GET(request: NextRequest, { params: { owner, repo } }: Params) { + const openIssuesCount = await Codeberg.getClient().getIssuesCount({ owner, repo }, { type: 'issues', state: 'open' }) + + return await Badge.generate(request, 'open issues', openIssuesCount?.toString() ?? 'None') +} + +export const runtime = 'edge' diff --git a/badgers-web/src/app/codeberg/release/[owner]/[repo]/route.ts b/badgers-web/src/app/codeberg/release/[owner]/[repo]/route.ts new file mode 100644 index 0000000..813dedb --- /dev/null +++ b/badgers-web/src/app/codeberg/release/[owner]/[repo]/route.ts @@ -0,0 +1,24 @@ +import { NextRequest } from 'next/server' + +import Badge from '@/utils/Badge' +import Codeberg from '@/utils/Codeberg' + +interface Params { + params: { + owner: string + repo: string + } +} + +export async function GET(request: NextRequest, { params: { owner, repo } }: Params) { + const release = await Codeberg.getClient().getLatestRelease({ owner, repo }) + const shortestName = [release?.tag_name, release?.name] + .filter(Boolean) + .reduce((a, b) => a!.length < b!.length ? a : b) + + return await Badge.generate(request, 'release', shortestName ?? 'None', { + color: !!shortestName ? 'blue' : 'yellow' + }) +} + +export const runtime = 'edge' diff --git a/badgers-web/src/app/page.tsx b/badgers-web/src/app/page.tsx index 76fa04a..ba22598 100644 --- a/badgers-web/src/app/page.tsx +++ b/badgers-web/src/app/page.tsx @@ -160,6 +160,14 @@ export default function Home() { +
+
+ + + + +
+
diff --git a/badgers-web/src/utils/Codeberg.ts b/badgers-web/src/utils/Codeberg.ts new file mode 100644 index 0000000..f8268a2 --- /dev/null +++ b/badgers-web/src/utils/Codeberg.ts @@ -0,0 +1,74 @@ +const API_BASE = 'https://codeberg.org/api/v1'; + +type ProjectInfo = { + owner: string + repo: string +} + +type Repository = { + id: number + default_branch: string + name: string + forks_count: number + stars_count: number +} + +type Release = { + name: string + tag_name: string +} + +class CodebergClient { + token: string + + constructor(token: string) { + this.token = token + } + + buildUrl(path: string, query: Record = {}): string { + const queryArgs = { + ...query, + token: this.token, + } + const queryString = Object + .entries(queryArgs) + .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) + .join('&') + + return `${API_BASE}/${path}?${queryString}` + } + + async getRepository({ owner, repo }: ProjectInfo): Promise { + const repoId = `${owner}/${repo}` + const url = this.buildUrl(`repos/${repoId}`) + const resp = await fetch(url) + + if (resp.status !== 200) return null + return await resp.json() as Repository + } + + async getIssuesCount({ owner, repo }: ProjectInfo, query: Record = {}): Promise { + const repoId = `${owner}/${repo}` + const url = this.buildUrl(`repos/${repoId}/issues`, query) + const resp = await fetch(url) + + if (resp.status !== 200) return null + const count = resp.headers.get('x-total-count') + return Number(count) + } + + async getLatestRelease({ owner, repo }: ProjectInfo): Promise { + const repoId = `${owner}/${repo}` + const url = this.buildUrl(`repos/${repoId}/releases/latest`) + const resp = await fetch(url) + + if (resp.status !== 200) return null + return await resp.json() as Release + } +} + +export default class Codeberg { + static getClient(): CodebergClient { + return new CodebergClient(process.env.CODEBERG_TOKEN as string) + } +}