og/server.js

270 lines
6.7 KiB
JavaScript
Raw Permalink Normal View History

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-12-21 09:21:41 -05:00
import render 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-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-12-21 09:21:41 -05:00
app.get('/', function (_, res) {
2023-08-02 18:17:21 -04:00
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-12-21 11:02:37 -05:00
const assets = express.Router()
assets.use('/', (_, res, next) => {
res.set('Cache-Control', 'public, max-age=31536000')
res.set('Access-Control-Allow-Origin', '*')
next()
})
assets.use('/', express.static('assets'))
app.use('/assets', assets)
2023-12-21 09:21:41 -05:00
const defaultFormat = debug ? 'html' : 'png'
2023-12-21 07:47:43 -05:00
app.use('/', function (req, res, next) {
2023-12-21 09:21:41 -05:00
app.locals.options = req.query.debug
? {
format: 'html'
}
: {
format: defaultFormat
}
res.type(app.locals.options.format)
2023-12-21 07:47:43 -05:00
next()
})
2023-08-03 15:28:31 -04:00
2023-12-16 22:36:07 -05:00
app.get('/:user', async function (req, res) {
const [userResp, reposResp] = await Promise.all([
await fetch(
`${forgejoBaseUrl}/api/v1/users/${encodeURIComponent(req.params.user)}`
),
await fetch(
`${forgejoBaseUrl}/api/v1/users/${encodeURIComponent(
req.params.user
)}/repos`
)
])
if (!userResp.ok) {
res.status(userResp.status)
res.end()
return
}
const [user, repos] = await Promise.all([
await userResp.json(),
await reposResp.json()
])
user.repo_count = repos.length
2023-12-21 13:12:51 -05:00
user.description = user.description?.split('---\r\n')[0]
2023-12-16 22:36:07 -05:00
const html = await eta.renderAsync('user', {
user,
debug
})
res.set('Content-Disposition', 'inline')
2023-12-21 07:47:43 -05:00
res.send(await render(html, app.locals.options))
2023-12-16 22:36:07 -05:00
})
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)
])
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
})
2023-08-02 18:37:47 -04:00
res.set('Content-Disposition', 'inline')
2023-12-21 07:47:43 -05:00
res.send(await render(html, app.locals.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
})
res.set('Content-Disposition', 'inline')
2023-12-21 07:47:43 -05:00
res.send(await render(html, app.locals.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-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)
])
if (!issueResp.ok) {
res.status(issueResp.status)
res.end()
return
}
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
}
const html = await eta.renderAsync('issue', {
issue,
2023-08-02 22:00:54 -04:00
languages,
languageColors,
debug
})
res.set('Content-Disposition', 'inline')
2023-12-21 07:47:43 -05:00
res.send(await render(html, app.locals.options))
})
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
})
res.set('Content-Disposition', 'inline')
2023-12-21 07:47:43 -05:00
res.send(await render(html, app.locals.options))
2023-08-03 07:52:39 -04:00
})
const [port, host] = [process.env.PORT ?? 8000, process.env.HOST ?? '0.0.0.0']
export const BASE_URL = `http://${host}:${port}`
app.listen(port, host, () => {
console.log(`Listening on http://${host}:${port}`)
})