Skip to content
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

Add GitLab endpoints #14

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Building SpaceBadgers has been a labor of love, aiming to offer a superior, reli
## Development

### Environment Variables
> Paste this template into `badgers-web/.env.local`
> Paste this template into `badgers-web/.env.local` for local development

```py
# Frontend Configuration
Expand All @@ -53,7 +53,8 @@ NEXT_PUBLIC_WEB_PROTO = "http" # Web frontend protocol
NEXT_PUBLIC_WEB_HOST = "127.0.0.1:3000" # Web frontend host

# API Tokens
GITHUB_TOKEN = "ghp_Foo1234567" # Required for GitHub badges
GITHUB_TOKEN = "ghp_Foo1234567" # Required for GitHub badges (private token)
GITLAB_TOKEN = "glfoo-zKvC1234" # Required for GitLab badges (private token)
CRATESIO_TOKEN = "cio51fdR1234567" # Required for crates.io badges
```

Expand Down
21 changes: 21 additions & 0 deletions badgers-web/src/app/gitlab/license/[owner]/[repo]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextRequest } from "next/server"

import Badge from '@/utils/Badge'
import GitLab from '@/utils/GitLab'

interface Params {
params: {
owner: string
repo: string
}
}

export async function GET(request: NextRequest, { params: { owner, repo } }: Params) {
const repoData = await GitLab.getClient().getRepository({ owner, repo, license: true })
const licenseName = repoData?.license?.nickname ?? repoData?.license?.key
return await Badge.generate(request, 'license', licenseName ?? 'unknown', {
color: !!licenseName ? 'blue' : 'gray'
})
}

export const runtime = 'edge'
23 changes: 23 additions & 0 deletions badgers-web/src/app/gitlab/release/[owner]/[repo]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NextRequest } from "next/server"

import Badge from '@/utils/Badge'
import GitLab from '@/utils/GitLab'

interface Params {
params: {
owner: string
repo: string
}
}

export async function GET(request: NextRequest, { params: { owner, repo } }: Params) {
const release = await GitLab.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'
13 changes: 13 additions & 0 deletions badgers-web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,19 @@ export default function Home() {
<Row name="License" path="/github/license/:owner/:repo" inject={['quintschaf', 'schafkit']} />
</div>
</Section>
<Section name="GitLab">
<div className="grid grid-cols-[auto_1fr_auto] items-center gap-x-8">
<Row name="Latest release" path="/gitlab/release/:owner/:repo" inject={['CalcProgrammer1', 'OpenRGB']} />
<Row name="Issues" path="/gitlab/issues/:owner/:repo" inject={['inkscape', 'inkscape']} />
<Row name="Open issues" path="/gitlab/open-issues/:owner/:repo" inject={['inkscape', 'inkscape']} />
<Row name="Closed issues" path="/gitlab/closed-issues/:owner/:repo" inject={['inkscape', 'inkscape']} />
<Row name="Pipelines (combined)" path="/gitlab/pipelines/:owner/:repo" inject={['inkscape', 'inkscape']} />
<Row name="Pipelines (combined)" path="/gitlab/pipelines/:owner/:repo/:branch" inject={['inkscape', 'inkscape', 'master']} />
<Row name="Pipelines (specific)" path="/gitlab/pipelines/:owner/:repo/:branch/:pipeline" inject={['inkscape', 'inkscape', 'master', 'build']} />
<Row name="Contributors" path="/gitlab/contributors/:owner/:repo" inject={['inkscape', 'inkscape']} />
<Row name="License" path="/gitlab/license/:owner/:repo" inject={['CalcProgrammer1', 'OpenRGB']} />
</div>
</Section>
<Section name="crates.io">
<div className="grid grid-cols-[auto_1fr_auto] items-center gap-x-8">
<Row name="Name" path="/crates/name/:crate" inject={['serde']} />
Expand Down
89 changes: 89 additions & 0 deletions badgers-web/src/utils/GitLab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Supports GitLab API v4

/**
* GitLab API base url
*/
const API_BASE = 'https://gitlab.com/api/v4';

type ProjectInfo = {
owner: string
repo: string
}

type GetRepositoryArgs = ProjectInfo & {
license: boolean
}

/**
* A subset of the GitLab repository response.
*
* @see https://docs.gitlab.com/api/projects.html#get-single-project
*/
type GetRepositoryResponse = {
id: number
default_branch: string
name: string
license: {
key: string
name: string
nickname: string
}
forks_count: number
star_count: number
}

type Release = {
name: string
tag_name: string
upcoming_release: boolean
}

/**
* GitLab API client
*/
class GitLabClient {
privateToken: string

constructor(privateToken: string) {
this.privateToken = privateToken
}

/**
* Build a GitLab API url with authentication.
*/
buildUrl(path: string, query: Record<string, any> = {}): string {
const queryArgs = {
...query,
private_token: this.privateToken,
}
const queryString = Object
.entries(queryArgs)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&')
return `${API_BASE}/${path}?${queryString}`
}

async getRepository({ owner, repo, license }: GetRepositoryArgs): Promise<GetRepositoryResponse | null> {
const repoId = encodeURIComponent(`${owner}/${repo}`)
const resp = await fetch(this.buildUrl(`projects/${repoId}`, { license }))
if (resp.status !== 200) return null
return await resp.json() as GetRepositoryResponse
}

async getLatestRelease({ owner, repo }: ProjectInfo): Promise<Release | null> {
const repoId = encodeURIComponent(`${owner}/${repo}`)
const resp = await fetch(this.buildUrl(`projects/${repoId}/releases`))
if (resp.status !== 200) return null
const releases = await resp.json() as Array<Release>
if (releases.length === 0) return null
const latestNonUpcomingRelease = releases.find(r => !r.upcoming_release)
if (latestNonUpcomingRelease === undefined) return null
return latestNonUpcomingRelease
}
}

export default class GitLab {
static getClient(): GitLabClient {
return new GitLabClient(process.env.GITLAB_TOKEN as string);
}
}