You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
3.2 KiB
121 lines
3.2 KiB
import Koa from 'koa'
|
|
import Router from '@koa/router'
|
|
import serve from 'koa-static'
|
|
import bodyParser from 'koa-bodyparser'
|
|
import fetch from 'isomorphic-unfetch'
|
|
import { randomBytes } from 'crypto'
|
|
import parse from "mdast-util-from-markdown"
|
|
|
|
const app = new Koa()
|
|
const router = new Router()
|
|
|
|
const gitea = {
|
|
apiBase: process.env.GITEA_API_BASE,
|
|
user: process.env.GITEA_USER,
|
|
repo: process.env.GITEA_REPO,
|
|
token: process.env.GITEA_TOKEN,
|
|
}
|
|
|
|
async function createNote(name, contentPlain) {
|
|
const content = Buffer.from(contentPlain).toString('base64')
|
|
const body = JSON.stringify({ content })
|
|
const url = `${gitea.apiBase}/repos/${gitea.user}/${gitea.repo}/contents/${name}.md`
|
|
const resp = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
Authorization: `token ${gitea.token}`,
|
|
},
|
|
body,
|
|
})
|
|
}
|
|
|
|
router.post('/micropub', async (ctx, next) => {
|
|
const resp = await fetch('https://tokens.indieauth.com/token', {
|
|
method: 'GET',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: ctx.headers['authorization']
|
|
}
|
|
})
|
|
const data = await resp.json()
|
|
if (!resp.ok) {
|
|
next()
|
|
return
|
|
}
|
|
const name = randomBytes(12).toString('hex')
|
|
await createNote(name, ctx.request.body.content)
|
|
ctx.set('Location', `https://${name}.benatkin.com/`)
|
|
ctx.status = 201
|
|
ctx.body = {}
|
|
})
|
|
|
|
function readMarkdown(md) {
|
|
const parsed = parse(value);
|
|
const codeBlocks = parsed.children.filter((block) => block.type === "code");
|
|
const blocks = {
|
|
html: codeBlocks.filter((block) => block.lang === "html"),
|
|
js: codeBlocks.filter((block) => ["js", "javascript"].includes(block.lang)),
|
|
css: codeBlocks.filter((block) => block.lang === "css"),
|
|
}
|
|
const code = {
|
|
html: blocks.html.length === 1 ? blocks.html[0].value : undefined,
|
|
js: blocks.html.length === 1 ? blocks.js[0].value : undefined,
|
|
css: blocks.html.length === 1 ? blocks.css[0].value : undefined,
|
|
}
|
|
}
|
|
|
|
function renderSandbox({html, css, js}) {
|
|
return `<html>
|
|
<head>
|
|
<title>Sandbox</title>
|
|
${css ? `<style type="text/css">
|
|
${css}
|
|
</style>` : ''}
|
|
</head>
|
|
<body>
|
|
${html || ''}
|
|
${js ? `<script>
|
|
${js}
|
|
</script>` : ''}
|
|
</body>
|
|
</html>`
|
|
}
|
|
|
|
router.get('/', async (ctx, next) => {
|
|
const host = (ctx.headers['host'] || '').replace(/\.[^.]+\.[^.*]+$/, '')
|
|
if (host === 'notes') {
|
|
return await next()
|
|
}
|
|
const url = `${gitea.apiBase}/repos/${gitea.user}/${gitea.repo}/contents/${encodeURIComponent(host)}.txt`
|
|
const resp = await fetch(url, {
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `token ${gitea.token}`,
|
|
},
|
|
})
|
|
if (resp.ok) {
|
|
const { content } = await resp.json()
|
|
const contentPlain = Buffer.from(content, 'base64').toString('utf8')
|
|
const markdownContent = readMarkdown(contentPlain)
|
|
if (markdownContent) {
|
|
ctx.body = renderSandbox(markdownContent)
|
|
ctx.set('Content-Security-Policy', "default-src 'self'")
|
|
ctx.set('Content-Type', 'text/html')
|
|
} else {
|
|
ctx.body = contentPlain
|
|
}
|
|
} else {
|
|
ctx.status = 404
|
|
ctx.body = `${host} not found`
|
|
}
|
|
})
|
|
|
|
app
|
|
.use(bodyParser())
|
|
.use(router.routes())
|
|
.use(router.allowedMethods())
|
|
.use(serve('static'))
|
|
|
|
app.listen(3000)
|