Dwelling/homepage/index.js

198 lines
6.0 KiB
JavaScript

const path = require("path");
const bodyParser = require("koa-body")({multipart: true});
const fetch = require("node-fetch");
const Koa = require("koa");
const koaPug = require("koa-pug");
const koaRouter = require("koa-router");
const { MysqlError } = require("mysql");
const config = require("./config");
const guestbook = require("./guestbook");
const mindflow = require("./mindflow");
const util = require("../shared/util");
const { URLSearchParams } = require("url");
const articles_meta = {
"rpi_root_on_external_drive": {
title: "How to move a root from SD card to external drive on Raspberry Pi",
description: "How to move a root partition to an ext. drive on Raspberry Pi."
},
"setting_up_a_tor_proxy_relay_hiddenserv": {
title: "Setting up a Tor proxy, relay and hidden service",
description: "How to setup a Tor proxy, relay and hidden service."
},
"setting_up_a_mail_server": {
title: "Setting up a mail server",
description: "How to create your own mail server using Postfix and Dovecot."
},
"nginx_recipes_and_tips": {
title: "NGiNX's recipes & tips",
description: "Tips and recipes for NGiNX webserver."
},
"hardening_mikrotik": {
title: "Hardening Mikrotik",
description: "How to harden security of your Mikrotik router."
},
};
async function getProcesses() {
try {
let reimu = await fetch("http://reimu.arav.home.arpa:14882/processes").then(r => r.json());
let sakuya = await fetch("http://sakuya.arav.home.arpa:14882/processes").then(r => r.json());
return Object.fromEntries(Object.entries(reimu).concat(Object.entries(sakuya)));
} catch {
return [];
}
}
// 127.0.0.1:19322
async function getNewCaptcha() {
try {
return await fetch("http://arav.home.arpa/captcha/", {method: "POST"}).then(r => r.text());
} catch {
return null;
}
}
async function solveCaptcha(id, answer) {
try {
let body = new URLSearchParams();
body.append('answer', answer);
let result = fetch(`http://startpage.arav.home.arpa/captcha/${id}`, { method: "POST", body: body } );
return await result.then(r => {
return r.status == 202;
});
} catch {
return false;
}
}
function setRoutes() {
return koaRouter().get('/', async ctx => {
await ctx.render('index', {
description: "A homepage of a russian guy Arav. Not just homepage, but FTP, radio, and smth more as well."
});
})
.get('/rss.xml', async ctx => {
ctx.type = 'xml';
await ctx.render('rss', {
doctype: 'xml',
schemahost: `${ctx.header.schema}://${ctx.header.host}`,
author: "me@arav.su (Alexander \"Arav\" Andreev)",
items: await mindflow.getPosts()
})
})
.get('/stuff', async ctx => {
await ctx.render('stuff', {
title: "/ Stuff",
description: "Here I share my programs, scripts, articles, may be other stuff.",
git_site: util.getServiceByHost(ctx.request.host, "git"),
files_site: util.getServiceByHost(ctx.request.host, "files")
})
})
.get('/mindflow', async ctx => {
await ctx.render('mindflow', {
title: "/ Mindflow",
description: "Here I will post updates on my infrastructure, my very important opinions and thoughts.",
files_site: util.getServiceByHost(ctx.request.host, "files"),
tz: util.getClientTimezone(ctx),
posts: await mindflow.getPosts()
})
})
.get('/stuff/article/:article', async ctx => {
if (ctx.params.article) {
return await ctx.render("articles/" + ctx.params.article, {
title: "/ Article / " + articles_meta[ctx.params.article].title,
description: articles_meta[ctx.params.article].description
});
} else {
return ctx.throw(404);
}
})
.get('/about', async ctx => {
await ctx.render('about', {
title: "/ About",
description: "This is a page where I'm telling about me and my home server.",
files_site: util.getServiceByHost(ctx.request.host, "files"),
services: await getProcesses()
})
})
.get('/guestbook', async ctx => {
const page = ctx.request.query.p !== undefined ? ctx.request.query.p : 1;
const page_size = ctx.request.query.ps !== undefined ? ctx.request.query.ps : guestbook.pageSize;
let posts;
try {
posts = await guestbook.getPosts(page, page_size);
} catch(err) {
posts = null;
}
return await ctx.render('guestbook', {
title: "/ Guestbook",
description: "This is my guestbook. Welcome.",
tz: util.getClientTimezone(ctx),
owner: config.guestbook.owner,
posts: posts,
pages_count: Math.ceil(await guestbook.getPostsCount() / page_size),
captcha_id: await getNewCaptcha() });
})
.post('/guestbook', bodyParser, async ctx => {
const post = ctx.request.body;
post.hide_website = post.hide_website !== undefined;
try {
let check = await solveCaptcha(post.captcha_id, post.captcha_answer);
if (!check)
throw "CAPTCHA is wrong or expired";
if (await guestbook.addPost(post))
ctx.redirect("/guestbook");
} catch(err) {
if (typeof err == 'object' && err instanceof MysqlError) {
ctx.response.status = 500;
ctx.response.body = { error: `Database failed so your post wasn't added. Here's your message:`, message: post.message };
} else if (typeof err == 'string') {
ctx.response.status = 403;
ctx.response.body = { error: `Reason why your post was rejected is "${err}". Here's your message:`, message: post.message };
}
}
});
}
const app = new Koa();
const pug = new koaPug({
viewPath: path.join(__dirname, "views"),
locals: {
date_: (date, tz) => util.datetime(date, util.date_formats.post_date, tz),
date_id: (date, tz) => util.datetime(date, util.date_formats.id_date, tz),
date_rss: date => util.datetime(date, util.date_formats.rss_date, "UTC"),
rssLink: util.rssLink },
app: app
});
app.proxy = true;
app
.use(async (ctx, next) => {
try {
await next();
if (ctx.status !== 200) ctx.throw(ctx.status);
} catch (err) {
ctx.status = err.status || 500;
if (ctx.status === 404)
await ctx.render('404');
else if (ctx.status === 403)
await ctx.render('403', { error: ctx.body });
else
await ctx.render('500', { error: ctx.body });
}
})
.use(setRoutes().routes())
.listen(config.port, config.host);