2023-08-03 03:25:10 -04:00
|
|
|
import 'dotenv/config'
|
2023-08-02 18:09:44 -04:00
|
|
|
import express from 'express'
|
2023-08-02 18:37:47 -04:00
|
|
|
import { Eta } from 'eta'
|
2023-08-02 19:54:54 -04:00
|
|
|
import render, { renderHtml } from './lib/render.js'
|
2023-08-02 23:00:44 -04:00
|
|
|
import languageColors from './lib/languagecolors.js'
|
2023-09-10 14:58:43 -04:00
|
|
|
import { fetch } from 'undici'
|
2023-08-03 08:48:52 -04:00
|
|
|
import hash from './lib/hash.js'
|
2023-08-03 15:28:31 -04:00
|
|
|
import { DMSans, ComicSans } from './lib/fonts.js'
|
2023-08-03 03:13:34 -04:00
|
|
|
// satori is using the native fetch api, causing a warning, so make node-fetch the default
|
|
|
|
globalThis.fetch = fetch
|
2023-08-02 18:51:06 -04:00
|
|
|
|
2023-08-03 03:25:10 -04:00
|
|
|
const forgejoBaseUrl = process.env.FORGEJO_BASE_URL
|
|
|
|
if (!forgejoBaseUrl) throw new Error('FORGEJO_BASE_URL unspecified')
|
|
|
|
|
2023-08-02 18:51:06 -04:00
|
|
|
const debug = process.env.DEBUG ? true : false
|
|
|
|
|
2023-08-02 18:37:47 -04:00
|
|
|
const eta = new Eta({
|
|
|
|
views: 'views',
|
2023-08-02 18:51:06 -04:00
|
|
|
cache: !debug
|
2023-08-02 18:37:47 -04:00
|
|
|
})
|
2023-08-02 18:09:44 -04:00
|
|
|
|
2023-08-02 18:17:21 -04:00
|
|
|
const app = express()
|
2023-08-02 18:09:44 -04:00
|
|
|
|
2023-08-02 18:17:21 -04:00
|
|
|
app.get('/', function (req, res) {
|
|
|
|
res.redirect('https://git.gay/gitgay/og.git')
|
|
|
|
})
|
2023-08-02 18:09:44 -04:00
|
|
|
|
2023-08-02 22:00:54 -04:00
|
|
|
function getLanguagePercentages(languages) {
|
|
|
|
let sum = 0
|
|
|
|
const values = Object.values(languages)
|
|
|
|
for (const value of values) {
|
|
|
|
sum += value
|
|
|
|
}
|
|
|
|
return Object.fromEntries(
|
|
|
|
Object.keys(languages).map(language => {
|
|
|
|
return [language, (languages[language] / sum) * 100]
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-08-03 07:52:39 -04:00
|
|
|
async function getLanguages(owner, repo) {
|
|
|
|
const languagesResp = await fetch(
|
2023-08-03 03:25:10 -04:00
|
|
|
`${forgejoBaseUrl}/api/v1/repos/${encodeURIComponent(
|
2023-08-03 07:52:39 -04:00
|
|
|
owner
|
|
|
|
)}/${encodeURIComponent(repo)}/languages`
|
2023-08-02 18:19:26 -04:00
|
|
|
)
|
2023-08-03 07:52:39 -04:00
|
|
|
if (languagesResp.ok) {
|
|
|
|
return getLanguagePercentages(await languagesResp.json())
|
|
|
|
}
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
|
2023-08-03 15:28:31 -04:00
|
|
|
const comicSansNames = new Set(['trixie'])
|
|
|
|
|
2023-08-03 07:52:39 -04:00
|
|
|
app.get('/:owner/:repo', async function (req, res) {
|
|
|
|
const [repoResp, commitsResp, languages] = await Promise.all([
|
|
|
|
fetch(
|
|
|
|
`${forgejoBaseUrl}/api/v1/repos/${encodeURIComponent(
|
|
|
|
req.params.owner
|
|
|
|
)}/${encodeURIComponent(req.params.repo)}`
|
|
|
|
),
|
|
|
|
fetch(
|
|
|
|
`${forgejoBaseUrl}/api/v1/repos/${encodeURIComponent(
|
|
|
|
req.params.owner
|
|
|
|
)}/${encodeURIComponent(req.params.repo)}/commits?limit=1`
|
|
|
|
),
|
|
|
|
getLanguages(req.params.owner, req.params.repo)
|
|
|
|
])
|
2023-08-02 23:53:23 -04:00
|
|
|
if (!repoResp.ok) {
|
|
|
|
res.status(repoResp.status)
|
|
|
|
res.end()
|
|
|
|
return
|
|
|
|
}
|
2023-08-02 18:19:26 -04:00
|
|
|
const repo = await repoResp.json()
|
2023-08-03 07:52:39 -04:00
|
|
|
repo.commits_count =
|
|
|
|
Number.parseInt(commitsResp.headers.get('x-total-count')) || 0
|
2023-08-02 18:51:06 -04:00
|
|
|
const html = await eta.renderAsync('repo', {
|
|
|
|
repo,
|
2023-08-02 22:00:54 -04:00
|
|
|
languages,
|
|
|
|
languageColors,
|
2023-08-02 18:51:06 -04:00
|
|
|
debug
|
|
|
|
})
|
|
|
|
if (debug) {
|
2023-08-02 20:00:50 -04:00
|
|
|
res.send(await renderHtml(html))
|
2023-08-02 18:51:06 -04:00
|
|
|
return
|
|
|
|
}
|
2023-08-03 15:28:31 -04:00
|
|
|
const options = {
|
|
|
|
fonts: comicSansNames.has(req.params.owner) ? ComicSans : DMSans,
|
|
|
|
format: req.query.format == 'svg' ? 'svg' : 'png'
|
|
|
|
}
|
|
|
|
res.type(options.format)
|
2023-08-02 18:37:47 -04:00
|
|
|
res.set('Content-Disposition', 'inline')
|
2023-08-03 15:28:31 -04:00
|
|
|
res.send(await render(html, options))
|
2023-08-02 18:09:44 -04:00
|
|
|
})
|
|
|
|
|
2023-08-03 05:40:18 -04:00
|
|
|
app.get('/:owner/:repo/commit/:hash', async function (req, res) {
|
2023-08-03 07:52:39 -04:00
|
|
|
const [commitResp, languages] = await Promise.all([
|
|
|
|
fetch(
|
|
|
|
`${forgejoBaseUrl}/api/v1/repos/${encodeURIComponent(
|
|
|
|
req.params.owner
|
|
|
|
)}/${encodeURIComponent(
|
|
|
|
req.params.repo
|
|
|
|
)}/git/commits/${encodeURIComponent(req.params.hash)}`
|
|
|
|
),
|
|
|
|
getLanguages(req.params.owner, req.params.repo)
|
|
|
|
])
|
2023-08-03 05:40:18 -04:00
|
|
|
if (!commitResp.ok) {
|
|
|
|
res.status(commitResp.status)
|
|
|
|
res.end()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const commit = await commitResp.json()
|
|
|
|
const html = await eta.renderAsync('commit', {
|
|
|
|
commit: {
|
|
|
|
...commit.commit,
|
|
|
|
sha: commit.sha,
|
|
|
|
repository: {
|
|
|
|
full_name: `${req.params.owner}/${req.params.repo}`
|
|
|
|
},
|
2023-08-03 08:48:52 -04:00
|
|
|
committer: commit.committer ?? {
|
|
|
|
...commit.commit.author,
|
|
|
|
avatar_url: `${forgejoBaseUrl}/avatar/${hash(
|
|
|
|
commit.commit.author.email.toLowerCase()
|
|
|
|
)}`
|
|
|
|
},
|
2023-08-03 05:40:18 -04:00
|
|
|
stats: commit.stats,
|
|
|
|
created_at: commit.created
|
|
|
|
},
|
|
|
|
languages,
|
|
|
|
languageColors,
|
|
|
|
debug
|
|
|
|
})
|
|
|
|
if (debug) {
|
|
|
|
res.send(await renderHtml(html))
|
|
|
|
return
|
|
|
|
}
|
2023-08-03 15:28:31 -04:00
|
|
|
const options = {
|
|
|
|
fonts: comicSansNames.has(req.params.owner) ? ComicSans : DMSans,
|
|
|
|
format: req.query.format == 'svg' ? 'svg' : 'png'
|
|
|
|
}
|
|
|
|
res.type(options.format)
|
2023-08-03 05:40:18 -04:00
|
|
|
res.set('Content-Disposition', 'inline')
|
2023-08-03 15:28:31 -04:00
|
|
|
res.send(await render(html, options))
|
2023-08-03 05:40:18 -04:00
|
|
|
})
|
|
|
|
|
2023-08-03 07:52:39 -04:00
|
|
|
app.get('/:owner/:repo/:type/:num', (req, res, next) => {
|
|
|
|
if (req.params.type != 'pull' && req.params.type != 'issue') return next()
|
|
|
|
res.redirect(
|
|
|
|
`/${req.params.owner}/${req.params.repo}/${req.params.type}s/${req.params.num}`
|
2023-08-02 20:06:10 -04:00
|
|
|
)
|
2023-08-03 07:52:39 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
app.get('/:owner/:repo/issues/:num', async function (req, res) {
|
|
|
|
const [issueResp, languages] = await Promise.all([
|
|
|
|
fetch(
|
|
|
|
`${forgejoBaseUrl}/api/v1/repos/${encodeURIComponent(
|
|
|
|
req.params.owner
|
|
|
|
)}/${encodeURIComponent(req.params.repo)}/issues/${encodeURIComponent(
|
|
|
|
req.params.num
|
|
|
|
)}`
|
|
|
|
),
|
|
|
|
getLanguages(req.params.owner, req.params.repo)
|
|
|
|
])
|
2023-08-02 23:53:23 -04:00
|
|
|
if (!issueResp.ok) {
|
|
|
|
res.status(issueResp.status)
|
|
|
|
res.end()
|
|
|
|
return
|
|
|
|
}
|
2023-08-02 20:06:10 -04:00
|
|
|
const issue = await issueResp.json()
|
2023-08-03 07:52:39 -04:00
|
|
|
if (issue.pull_request) {
|
|
|
|
res.redirect(
|
|
|
|
`/${req.params.owner}/${req.params.repo}/pulls/${req.params.num}`
|
|
|
|
)
|
|
|
|
return
|
2023-08-02 23:50:41 -04:00
|
|
|
}
|
2023-08-02 20:06:10 -04:00
|
|
|
const html = await eta.renderAsync('issue', {
|
|
|
|
issue,
|
2023-08-02 22:00:54 -04:00
|
|
|
languages,
|
|
|
|
languageColors,
|
2023-08-02 20:06:10 -04:00
|
|
|
debug
|
|
|
|
})
|
|
|
|
if (debug) {
|
|
|
|
res.send(await renderHtml(html))
|
|
|
|
return
|
|
|
|
}
|
2023-08-03 15:28:31 -04:00
|
|
|
const options = {
|
|
|
|
fonts: comicSansNames.has(req.params.owner) ? ComicSans : DMSans,
|
|
|
|
format: req.query.format == 'svg' ? 'svg' : 'png'
|
|
|
|
}
|
|
|
|
res.type(options.format)
|
2023-08-02 20:06:10 -04:00
|
|
|
res.set('Content-Disposition', 'inline')
|
2023-08-03 15:28:31 -04:00
|
|
|
res.send(await render(html, options))
|
2023-08-02 20:06:10 -04:00
|
|
|
})
|
|
|
|
|
2023-08-03 07:52:39 -04:00
|
|
|
app.get('/:owner/:repo/pulls/:num', async function (req, res) {
|
|
|
|
const [pullResp, pullCommitsResp, languages] = await Promise.all([
|
|
|
|
fetch(
|
|
|
|
`${forgejoBaseUrl}/api/v1/repos/${encodeURIComponent(
|
|
|
|
req.params.owner
|
|
|
|
)}/${encodeURIComponent(req.params.repo)}/pulls/${encodeURIComponent(
|
|
|
|
req.params.num
|
|
|
|
)}`
|
|
|
|
),
|
|
|
|
fetch(
|
|
|
|
`${forgejoBaseUrl}/api/v1/repos/${encodeURIComponent(
|
|
|
|
req.params.owner
|
|
|
|
)}/${encodeURIComponent(req.params.repo)}/pulls/${encodeURIComponent(
|
|
|
|
req.params.num
|
|
|
|
)}/commits?limit=0`
|
|
|
|
),
|
|
|
|
getLanguages(req.params.owner, req.params.repo)
|
|
|
|
])
|
|
|
|
if (!pullResp.ok || !pullCommitsResp.ok) {
|
|
|
|
res.status(pullResp.status)
|
|
|
|
res.end()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const [pull, commits] = await Promise.all([
|
|
|
|
pullResp.json(),
|
|
|
|
pullCommitsResp.json()
|
|
|
|
])
|
|
|
|
pull.commits_count =
|
|
|
|
Number.parseInt(pullCommitsResp.headers.get('x-total-count')) || 0
|
|
|
|
const stats = { total: 0, additions: 0, deletions: 0 }
|
|
|
|
for (const commit of commits) {
|
|
|
|
stats.total += commit.stats.total
|
|
|
|
stats.additions += commit.stats.additions
|
|
|
|
stats.deletions += commit.stats.deletions
|
|
|
|
}
|
|
|
|
const html = await eta.renderAsync('issue', {
|
|
|
|
issue: pull,
|
|
|
|
prStats: stats,
|
|
|
|
languages,
|
|
|
|
languageColors,
|
|
|
|
debug
|
|
|
|
})
|
|
|
|
if (debug) {
|
|
|
|
res.send(await renderHtml(html))
|
|
|
|
return
|
|
|
|
}
|
2023-08-03 15:28:31 -04:00
|
|
|
const options = {
|
|
|
|
fonts: comicSansNames.has(req.params.owner) ? ComicSans : DMSans,
|
|
|
|
format: req.query.format == 'svg' ? 'svg' : 'png'
|
|
|
|
}
|
|
|
|
res.type(options.format)
|
2023-08-03 07:52:39 -04:00
|
|
|
res.set('Content-Disposition', 'inline')
|
2023-08-03 15:28:31 -04:00
|
|
|
res.send(await render(html, options))
|
2023-08-03 07:52:39 -04:00
|
|
|
})
|
|
|
|
|
2023-08-03 03:17:05 -04:00
|
|
|
const [port, host] = [process.env.PORT ?? 9054, process.env.HOST ?? '127.0.0.1']
|
|
|
|
|
|
|
|
app.listen(port, host, () => {
|
|
|
|
console.log(`Listening on http://${host}:${port}`)
|
|
|
|
})
|