Total rearrangement of files into a sane structure with multiple entry points instead of single process handling everything.
3
.gitignore
vendored
@ -1,3 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
config.js
|
config.js
|
||||||
package-lock.json
|
|
109
backup.sql
@ -1,109 +0,0 @@
|
|||||||
INSERT INTO `blog` (`date`, `category`, `title`, `body`)
|
|
||||||
VALUES
|
|
||||||
(
|
|
||||||
"2020-07-28 16:51:00", 1, "Banners.",
|
|
||||||
"Konnichiwa. I made banners for my websites of 88x31 and 240x60 sizes."),
|
|
||||||
(
|
|
||||||
"2020-07-29 00:00:00", 1, "Quick rundown on what happened since the december of the last year.",
|
|
||||||
"At first I moved off to aravs.ru domain in december 2019 that I was gonna change with, maybe, old arav.icu if it'll be available for cheap because renewal prices are too high for me. But then I found a new registrar with dirt cheap renewal price and now I have arav.top domain I've payed for 2 years and if nothing change I'll renew it again and again. So, yes, I finally found my domain that I'm not gonna switch if only I will be forced to.n"
|
|
||||||
"And mostly because of my lazyness I wasn't updating arav.neocities.org for fucking 3 months. That's also because I was trying to reduce its about page.n"
|
|
||||||
"Then at July 13th my RPi suddenly stopped working. It didn't take much time to realise that the problem was a dying HDD. Hopefully, fsck fixed filesystem and I successfully moved all the stuff onto another drive I took from a laptop that was holding my data. And at July 16th a new SSD arrivedto replace old laptop's system drive that now holds all my stuff and I came back online."),
|
|
||||||
(
|
|
||||||
"2020-11-07 00:40:00", 1, "Looks like I'm back to work on this website.",
|
|
||||||
"And maybe I'll finally do something with my neocities' website. Maybe..."),
|
|
||||||
(
|
|
||||||
"2020-11-10 14:54:00", 1, "New banners.",
|
|
||||||
"I made new <a href=""https://arav.top/about.html#banners"">banners</a>. They're pretty much the same as was before. I fixed frames order and logo now takes all width."),
|
|
||||||
(
|
|
||||||
"2020-11-14 00:50:00", 1, "Oh, PGP key wasn't accessible... Shite.",
|
|
||||||
"I just found that my PGP key isn't available. Now it is. I apologise to those who wanted it. According to logs this was a problem since the second half of september. :/"),
|
|
||||||
(
|
|
||||||
"2020-11-16 00:10:00", 1, "Dark theme flickering fixed.",
|
|
||||||
"Now a check for a current theme is inside a DOMContentLoaded event listener and script itself is now in a <head>.\n"
|
|
||||||
"I also added an async attribute to a guestbook's script so page won't wait for guestbook posts to load and won't flicker light theme because of that.\n"
|
|
||||||
"And I remade 88x31 banner and made banner's dark counterparts as well. So you have what to choose from. :)"),
|
|
||||||
(
|
|
||||||
"2020-11-16 11:28:00", 1, "Maintanance.",
|
|
||||||
"It's time to replace a thermal compound in the laptop, so radio, FTP and Gitea would be down for a while today."),
|
|
||||||
(
|
|
||||||
"2020-11-16 13:40:00", 1, "Maintanance complete.",
|
|
||||||
"Thermal compound replaced, not much dust there was. It used to work at 62°C. Will see later. :) Now it runs on 49°C after 11 minutes. That only means I did everything right.\n"
|
|
||||||
"P.S.: Now it runs on 49-52°C. Nice."),
|
|
||||||
(
|
|
||||||
"2020-11-16 15:59:00", 1, "Uuhh, this motherfucker still flickers...",
|
|
||||||
"Is there a cure for it ... ? At least, I managed to reduce this soykaf."),
|
|
||||||
(
|
|
||||||
"2020-11-17 18:13:00", 1, "Dark theme is default from now.",
|
|
||||||
"Ok, in order to make flickering less painful I made dark theme default."),
|
|
||||||
(
|
|
||||||
"2020-11-17 21:57:00", 1, "Yay, fuck JS!",
|
|
||||||
"I rewrote the guestbook without JS. Hooray!"),
|
|
||||||
(
|
|
||||||
"2020-11-19 00:12:00", 1, "ScrapTheChan ver. 0.4.0 released.",
|
|
||||||
"Today I fixed most of noticed problems and released it. Changelog is behind the <a href=""https://git.arav.top/Arav/ScrapTheChan/releases/tag/0.4.0"">link</a>."),
|
|
||||||
(
|
|
||||||
"2020-11-19 22:51:00", 2, "Yesterday I was replacing a thermal compound on my PC.",
|
|
||||||
"Yesterday (19 Nov for me is it :)) I replaced a thermal compound on my PC, and, of course, it just couldn't be an ordinary procedure.\n"
|
|
||||||
"When I assembled everything back and turned on the PC it started swearing at me with 26 beeps and rebooting. I knew that it could be a problem with a RAM stick, but I didn't believe it. And I dissassembled it again and found that a few pins were bended and one missing (!). I fixed them all (around 5 didn't count) and was thinking what to do with that missing pin. I thought it was causing problem but searching led me to a pinout of the socket 1150 and that pin was doing something called VDDQ, and I found that it isn't a problem at all. There was a questionable pin SA_DQ57 (whatever it means), but I bended it a little and it's fine.\n"
|
|
||||||
"Oh, why I even took it from the socket? To clean it from an old thermal compound.\n"
|
|
||||||
"Well, I almost give in to a thought of taking a motherboard to a service and then I tried to put a RAM stick to the second slot and ... it worked! Shit, I don't know what happened. I've vacuumed the motherboard and was absolutely careful. No doubt I didn't break anything, yet, fuck. That vacuum cleaner isn't even powerful enough to suck in anything soldered.\n"
|
|
||||||
"But there's one breakage for sure. One of fixators of a cooler has broken. Shit. Now radiator isn't attached well to the CPU. Hopefully it led to the same temperatures as they was before at least. xD Don't want to whip up it with <s>shit and bricks</s> a zip tie, so I'd better get a new one. And I already ordered it, a little smaller, but supports socket AM4, so I will reuse it in the future, great.\n"
|
|
||||||
"What's next after all the stress I got? I'm going to replace a compound on my graphics card! Ahahahahaha! xDD\n"
|
|
||||||
"P.S.: I made a mindflow.html after I wrote this post.\n"
|
|
||||||
"Update: Okay, SA_DQ is a data bus for memory, so by bending that pin I lost first ram slot. Well, that's not because of cleaning at least.\n"),
|
|
||||||
(
|
|
||||||
"2020-11-23 00:30:00", 1, "Recent updates.",
|
|
||||||
"I did a good work at this website. Especially I love that heading with a name of a site's section under the nav menu. And I finally made this section. I keep moving on. Next major step is to make a links page. My banners are ready, but no one would add me if I won't have a place for theirs banners. :)\n"
|
|
||||||
"Yeah, still no updates to Neocities' website. Now I'm working on a design of this place, and once I'll be satisfied that place will get an update. I work on it too, especially on an about page's content. Last not noticeable update was changing link of a guestbook to a JS-free new one."),
|
|
||||||
(
|
|
||||||
"2020-11-26 23:54:00", 1, "Article on userdir in NGiNX.",
|
|
||||||
"I thought why not to make <a href=""articles/nginx_recipes_and_tips.html"">an article</a> on how to implement a userdir functionality in NGiNX. Maybe, someone will want to implement this on their server.\n"
|
|
||||||
"And, an article on e-mail server is hard to finish, because every time I sit on it I find flaws in e-mail server's configuration, LMAO."),
|
|
||||||
(
|
|
||||||
"2020-11-27 15:07:00", 1, "Tor and I2P access for this site and files.",
|
|
||||||
"I was thinking why not to make my place available through Tor and I2P. So after minor links fixing I made this place work like a charm through darkweb. Currently only main website and file share are available.\n"
|
|
||||||
"I2P site: <a href=""http://t42fkp6zp5dfqywantq3zp427ig3q2onrmfv246tyaztpg4ckb5a.b32.i2p"">t42fkp6zp5dfqywantq3zp427ig3q2onrmfv246tyaztpg4ckb5a.b32.i2p</a>\n"
|
|
||||||
"I2P radio: <a href=""http://plkybcgxt4cdanot75cy3pbnqlbqcsrib2fmrpsnug4bqphqvfda.b32.i2p"">plkybcgxt4cdanot75cy3pbnqlbqcsrib2fmrpsnug4bqphqvfda.b32.i2p</a>\n"
|
|
||||||
"I2P files: <a href=""http://gajftpr47ze6ao7e3q2tb5xmcqneyaczu4edzvwwg2qzmnernpka.b32.i2p"">gajftpr47ze6ao7e3q2tb5xmcqneyaczu4edzvwwg2qzmnernpka.b32.i2p</a>\n"
|
|
||||||
"Tor site: <a href=""http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion"">moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion</a>\n"
|
|
||||||
"Tor radio: <a href=""http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion"">wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion</a>\n"
|
|
||||||
"Tor files: <a href=""http://qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion"">qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion</a>\n"
|
|
||||||
"Updated on 2021-02-10."),
|
|
||||||
(
|
|
||||||
"2020-12-01 20:30:00", 1, "Laptop's RAM update.",
|
|
||||||
"Today I replaced old 1333MHz 2GB stick with a new 8GB 1600MHz. Now laptop has 10 gigs. I am to figure out what to fill it with. :)"),
|
|
||||||
(
|
|
||||||
"2020-12-04 22:27:00", 1, "Article on userdir in NGiNX rewrote to be an article with recipes for it.",
|
|
||||||
"Yep, I think whole article for such a small thing is too much, so now <a href=""https://arav.top/articles/nginx_recipes_and_tips.html"">there is an article</a> with recipes and tips for NGiNX.\n"
|
|
||||||
"Now there is info on how to implement userdir functionality and a note about how NGiNX works with HTTP headers."),
|
|
||||||
(
|
|
||||||
"2021-02-08 22:19:00", 1, "New year. Big changes.",
|
|
||||||
"All this time I was working on a look of a website. And just for a last week I said to myself that I'm tired of editing every single thingy across all pages. So I came to conclusion that it's time to try NodeJS. So I took Koa and Pug and ported my website to it. Well, it's really amazing. Code's so clean now (if you only saw a code of a PHP version of guestbook... xD). Template engines are an invaluably great invention. :)\n"
|
|
||||||
"Yeah, I got rid of Javascript on the outside, but instead, bring it inside. xD Anyway, I was always thinking that Javascript itself never was a problem. Problem is that it is often used where absolutely unnecessary. And browser's APIs brings many convenient tools for us alongside with the ways of doing malicious deeds as well (like fingerprinting, crypto-mining, viruses, etc.). Alas, this is one of the laws of life, everything could be used for evil.\n"
|
|
||||||
"There's one project I'm working on (now developing database). It's a service for file uploading, but files could be downloaded just one time. Yeah, I called it "One-Time File". It'll be available by otf.arav.top link. A file would be kept for 7 days until it won't be downloaded, and size limit is 120MB. That'll be a really interesting experience, since I'll've to figure out how to be sure that file was fully downloaded before it can be deleted. :)\n"
|
|
||||||
"And speaking of my Neocities' site. Well, it looks like that I move out of there, but I definitely don't. That place would be my outpost with links to my website and services. :/"),
|
|
||||||
(
|
|
||||||
"2021-03-04 17:03:00", 1, "Radio playlist update #1",
|
|
||||||
"From now on I will post what music I've added to the radio. And what I've added are: André Rieu - The Second Waltz, Cake - Hem Of Your Garment and Dernière Volonté - L'Appel."),
|
|
||||||
(
|
|
||||||
"2021-03-05 18:26:51", 1, "Radio playlist update #2",
|
|
||||||
"Beborn Beton - New Born King and 相対性理論 - チャイナアドバイス were added."),
|
|
||||||
(
|
|
||||||
"2021-03-11 14:31:55", 1, "Now git is available as a hidden service.",
|
|
||||||
"Now you can browse my Gitea instance via Tor and I2P. Alas, it requires you to have a fixed domain and root URL specified in a config. So some links are pointing to git.arav.top, like files in releases and a cloning URL.\n"
|
|
||||||
"Tor: <a href=""http://qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion"">qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion</a>\n"
|
|
||||||
"I2P: <a href=""http://p5nkflgogwv4esy3ainup6mgurpugmdyufizhuufauznreyvprsa.b32.i2p"">p5nkflgogwv4esy3ainup6mgurpugmdyufizhuufauznreyvprsa.b32.i2p</a>");
|
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO `guestbook`
|
|
||||||
VALUES
|
|
||||||
(1, "2020-09-01 00:15:22", "Serana", "", "", "hey whats up! i recently found that out neocities is actually really cool and have spent the past few hours looking through whatever sites i can find (yours included!). figured i'd drop a message and say to keep up what you're doing! this is all really cool and i'm feeling all motivated to make something becuase of it. hope you have a nice day <3", 1, 1),
|
|
||||||
(2, "2020-11-09 04:18:27", "duli", "", "", "your website is super cool! hope you're having a good day <3", 1, 1),
|
|
||||||
(3, "2021-02-15 18:44:39", "Anonymous", "", "", "Cool website design, dig the .onion address, enjoying the radio station!", 1, 1);
|
|
||||||
|
|
||||||
INSERT INTO `guestbook_feedback`
|
|
||||||
VALUES
|
|
||||||
(1, "2020-11-10 00:17:31", "Well, better late than never. :) Hope feedbacks now work fine. Dunno when and if you ever read it again, but still, hope you're doing good as well. :) I'm so glad to read that I motivate others. Personal website is indeed a great thing that allows us to show who we are immeasuarably better than soulless social media. <3"),
|
|
||||||
(2, "2020-11-10 00:17:31", "Hi! Yeah, hope and you do so! <3 Great, that means I'm defenitely moving in a right direction. :) Need to dedicate more time for the websites, especially neocities' one literally begs to be updated, heh. But I have to rethink this first."),
|
|
||||||
(3, "2021-02-16 23:21:34", "Thank you! Glad to read that! :)", 1, 1);
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
const base_host = "127.0.0.1";
|
|
||||||
|
|
||||||
exports.dwelling = {}
|
|
||||||
|
|
||||||
exports.dwelling.port = 3200;
|
|
||||||
exports.dwelling.host = base_host;
|
|
||||||
|
|
||||||
exports.dwelling.database = {
|
|
||||||
host: "192.168.0.1",
|
|
||||||
database: "db",
|
|
||||||
user: "user",
|
|
||||||
password: "password",
|
|
||||||
timezone: "Z" };
|
|
||||||
|
|
||||||
exports.dwelling.guestbook = {}
|
|
||||||
|
|
||||||
exports.dwelling.guestbook.owner = "Owner";
|
|
||||||
exports.dwelling.guestbook.pageSize = 30;
|
|
||||||
|
|
||||||
exports.radio = {}
|
|
||||||
|
|
||||||
exports.radio.port = 3201;
|
|
||||||
exports.radio.host = base_host;
|
|
||||||
|
|
||||||
exports.files = {}
|
|
||||||
|
|
||||||
exports.files.port = 3202;
|
|
||||||
exports.files.host = base_host;
|
|
||||||
|
|
||||||
exports.files.file_path = "/srv/ftp";
|
|
@ -1,49 +0,0 @@
|
|||||||
const mysql = require("mysql");
|
|
||||||
|
|
||||||
const config = require("../config");
|
|
||||||
const util = require("../util");
|
|
||||||
|
|
||||||
|
|
||||||
let connection = mysql.createConnection(config.dwelling.database);
|
|
||||||
|
|
||||||
|
|
||||||
connection.config.queryFormat = util.mysqlQueryFormat;
|
|
||||||
|
|
||||||
exports.closeConnection = () => connection.end();
|
|
||||||
|
|
||||||
exports.getPosts = category => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let query = "\
|
|
||||||
SELECT\
|
|
||||||
`blog`.`entry_id` AS `post_id`,\
|
|
||||||
`blog`.`date`,\
|
|
||||||
`blog`.`title`,\
|
|
||||||
`blog`.`body`\
|
|
||||||
FROM `blog`\
|
|
||||||
LEFT JOIN `blog_categories` ON `blog_categories`.`category_id` = `blog`.`category`\
|
|
||||||
WHERE `blog_categories`.`category` = :category\
|
|
||||||
ORDER BY `blog`.`date` DESC;";
|
|
||||||
|
|
||||||
connection.query(query, { category: category }, (err, results) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
resolve(results); });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getPostsForRSS = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let query = "\
|
|
||||||
SELECT\
|
|
||||||
`blog`.`date`,\
|
|
||||||
`blog_categories`.`category`,\
|
|
||||||
`blog`.`title`,\
|
|
||||||
`blog`.`body`\
|
|
||||||
FROM `blog`\
|
|
||||||
LEFT JOIN `blog_categories` ON `blog_categories`.`category_id` = `blog`.`category`\
|
|
||||||
ORDER BY `blog`.`date` DESC;";
|
|
||||||
|
|
||||||
connection.query(query, (err, results) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
resolve(results); });
|
|
||||||
});
|
|
||||||
};
|
|
119
files.js
@ -1,119 +0,0 @@
|
|||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
const Koa = require("koa");
|
|
||||||
const koaPug = require("koa-pug");
|
|
||||||
const koaRouter = require("koa-router");
|
|
||||||
const koaRange = require("koa-range");
|
|
||||||
|
|
||||||
const config = require("./config");
|
|
||||||
const util = require("./util");
|
|
||||||
const { ENOENT, ECONNRESET } = require("constants");
|
|
||||||
|
|
||||||
const SIZE_UNITS = [ "B", "KiB", "MiB", "GiB" ];
|
|
||||||
|
|
||||||
function makeCurrentPathField(path) {
|
|
||||||
let current = `<a href="/">root</a>`;
|
|
||||||
if (path.endsWith("/"))
|
|
||||||
path = path.slice(1, path.length-1);
|
|
||||||
const path_parts = path.split("/");
|
|
||||||
|
|
||||||
for (i = 0; i < path_parts.length; ++i) {
|
|
||||||
let lnk = "";
|
|
||||||
for (j = 0; j < i+1; ++j) {
|
|
||||||
lnk += `/${path_parts[j]}`;
|
|
||||||
}
|
|
||||||
current += `/<a href="${encodeURI(lnk)}/">${path_parts[i]}</a>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertSize(size) {
|
|
||||||
let i = 0;
|
|
||||||
for (; size > 1024; size /= 1024) { ++i; }
|
|
||||||
return [i > 0 ? size.toFixed(2) : size, SIZE_UNITS[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortByNameField(a, b) {
|
|
||||||
if (a.name > b.name)
|
|
||||||
return 1;
|
|
||||||
else if (a.name < b.name)
|
|
||||||
return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDirectoryList(dir_path, orig_url) {
|
|
||||||
let dirs = [];
|
|
||||||
let files = [];
|
|
||||||
let total_files_size = 0;
|
|
||||||
let d = await fs.promises.opendir(dir_path);
|
|
||||||
for await (const dirent of d) {
|
|
||||||
const stat = fs.lstatSync(path.join(dir_path, dirent.name));
|
|
||||||
const [s, u] = convertSize(stat.size);
|
|
||||||
if (stat.isDirectory())
|
|
||||||
dirs.push({
|
|
||||||
name: dirent.name,
|
|
||||||
link: `${encodeURIComponent(dirent.name)}/`,
|
|
||||||
datetime: util.datetime(stat.mtime, util.formats.file_date),
|
|
||||||
size: "DIR",
|
|
||||||
size_unit: '' });
|
|
||||||
else {
|
|
||||||
total_files_size += stat.size;
|
|
||||||
files.push({
|
|
||||||
name: dirent.name,
|
|
||||||
link: `/files${orig_url}${encodeURIComponent(dirent.name)}`,
|
|
||||||
datetime: util.datetime(stat.mtime, util.formats.file_date),
|
|
||||||
size: +s,
|
|
||||||
size_unit: u });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dirs.sort(sortByNameField);
|
|
||||||
files.sort(sortByNameField);
|
|
||||||
|
|
||||||
return [dirs.concat(files), dirs.length, files.length,
|
|
||||||
convertSize(total_files_size).join(' ')];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRoutes(router) {
|
|
||||||
router.get('/(.*)?', async ctx => {
|
|
||||||
const file_path = path.join(config.files.file_path, decodeURI(ctx.originalUrl));
|
|
||||||
let stat = fs.lstatSync(file_path);
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
const [items, total_directories, total_files, total_files_size]
|
|
||||||
= await getDirectoryList(file_path, decodeURI(ctx.originalUrl));
|
|
||||||
await ctx.render('index', {
|
|
||||||
title: "/ Files",
|
|
||||||
description: "File share.",
|
|
||||||
main_site: util.getServiceByHost(ctx.header.host),
|
|
||||||
current_path: makeCurrentPathField(decodeURI(ctx.originalUrl)),
|
|
||||||
total_files: total_files,
|
|
||||||
total_files_size: total_files_size,
|
|
||||||
total_directories: total_directories,
|
|
||||||
items: items });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
const app = new Koa();
|
|
||||||
const pug = new koaPug({
|
|
||||||
viewPath: path.join(__dirname, "views", "files"),
|
|
||||||
locals: {
|
|
||||||
moment: date => util.datetime(date, util.formats.file_date) },
|
|
||||||
app: app
|
|
||||||
});
|
|
||||||
|
|
||||||
const radio_router = koaRouter();
|
|
||||||
setRoutes(radio_router);
|
|
||||||
|
|
||||||
app.proxy = true;
|
|
||||||
app
|
|
||||||
.use(koaRange)
|
|
||||||
.use(radio_router.routes())
|
|
||||||
.on("error", err => { if (!err.code == ECONNRESET || !err.code == ENOENT) console.log(err.code); })
|
|
||||||
.listen(config.files.port, config.files.host);
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
3
files/config.example.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
exports.host = "0.0.0.0";
|
||||||
|
exports.port = 32202;
|
||||||
|
exports.share_path = "/srv/ftp";
|
119
files/index.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { ENOENT, ECONNRESET } = require("constants");
|
||||||
|
|
||||||
|
const Koa = require("koa");
|
||||||
|
const koaPug = require("koa-pug");
|
||||||
|
const koaRouter = require("koa-router");
|
||||||
|
|
||||||
|
const config = require("./config");
|
||||||
|
const util = require("../shared/util");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const SIZE_UNITS = [ "B", "KiB", "MiB", "GiB" ];
|
||||||
|
|
||||||
|
function addFileSizeUnit(size) {
|
||||||
|
let i = 0;
|
||||||
|
for (; size > 1024; size /= 1024) ++i;
|
||||||
|
return [i > 0 ? size.toFixed(2) : size, SIZE_UNITS[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function buildCurrentPathLink(path) {
|
||||||
|
let currentPath = `<a href="/">root</a>`;
|
||||||
|
|
||||||
|
if (path.endsWith("/"))
|
||||||
|
path = path.slice(1, path.length-1);
|
||||||
|
|
||||||
|
const path_parts = path.split("/");
|
||||||
|
|
||||||
|
for (let i = 0; i < path_parts.length; ++i) {
|
||||||
|
let lnk = "";
|
||||||
|
|
||||||
|
for (let j = 0; j < i+1; ++j)
|
||||||
|
lnk += `/${path_parts[j]}`;
|
||||||
|
|
||||||
|
currentPath += `/<a href="${encodeURI(lnk)}/">${path_parts[i]}</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortFuncByNameField(a, b) {
|
||||||
|
return (a.name > b.name) ? 1 : (a.name < b.name) ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDirectoryList(dir_path, orig_url) {
|
||||||
|
let directories = [];
|
||||||
|
let files = [];
|
||||||
|
let total_files_size = 0;
|
||||||
|
let directory = await fs.promises.opendir(dir_path);
|
||||||
|
|
||||||
|
for await (const dirent of directory) {
|
||||||
|
const stat = await fs.promises.stat(path.join(dir_path, dirent.name));
|
||||||
|
const [s, u] = addFileSizeUnit(stat.size);
|
||||||
|
|
||||||
|
if (stat.isDirectory())
|
||||||
|
directories.push({
|
||||||
|
name: dirent.name,
|
||||||
|
link: `${encodeURIComponent(dirent.name)}/`,
|
||||||
|
datetime: stat.mtime,
|
||||||
|
size: "DIR" });
|
||||||
|
else {
|
||||||
|
total_files_size += stat.size;
|
||||||
|
files.push({
|
||||||
|
name: dirent.name,
|
||||||
|
link: `/files${orig_url}${encodeURIComponent(dirent.name)}`,
|
||||||
|
datetime: stat.mtime,
|
||||||
|
size: `${s} ${u}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
directories.sort(sortFuncByNameField);
|
||||||
|
files.sort(sortFuncByNameField);
|
||||||
|
|
||||||
|
return [directories.concat(files), directories.length, files.length,
|
||||||
|
addFileSizeUnit(total_files_size).join(' ')];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRoutes() {
|
||||||
|
return koaRouter().get('/(.*)?', async ctx => {
|
||||||
|
const file_path = path.join(config.share_path, decodeURI(ctx.originalUrl));
|
||||||
|
|
||||||
|
let stat = await fs.promises.stat(file_path);
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const [items, total_directories, total_files, total_files_size]
|
||||||
|
= await getDirectoryList(file_path, decodeURI(ctx.originalUrl));
|
||||||
|
|
||||||
|
await ctx.render('index', {
|
||||||
|
title: "/ Files",
|
||||||
|
description: "File share.",
|
||||||
|
clientTZ: util.getClientTimezone(ctx),
|
||||||
|
main_site: util.getServiceByHost(ctx.header.host),
|
||||||
|
current_path: buildCurrentPathLink(decodeURI(ctx.originalUrl)),
|
||||||
|
total_files: total_files,
|
||||||
|
total_files_size: total_files_size,
|
||||||
|
total_directories: total_directories,
|
||||||
|
items: items });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const app = new Koa();
|
||||||
|
const pug = new koaPug({
|
||||||
|
viewPath: path.join(__dirname, "views"),
|
||||||
|
locals: {
|
||||||
|
moment: (date, tz) => util.datetime(date, util.formats.file_date, tz), },
|
||||||
|
app: app
|
||||||
|
});
|
||||||
|
|
||||||
|
app.proxy = true;
|
||||||
|
app
|
||||||
|
.use(setRoutes().routes())
|
||||||
|
// .on("error", err => { if (!err.code == ECONNRESET || !err.code == ENOENT) console.log(err.code); })
|
||||||
|
.listen(config.port, config.host);
|
||||||
|
|
||||||
|
console.log("Arav's dwelling / Files is up.");
|
2
files/static/robots.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /assets/
|
36
files/views/index.pug
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
doctype html
|
||||||
|
html(lang='en')
|
||||||
|
head
|
||||||
|
title Arav's dwelling #{title}
|
||||||
|
meta(charset='utf-8')
|
||||||
|
meta(http-equiv='X-UA-Compatible' content='IE=edge')
|
||||||
|
meta(name='viewport' content='width=device-width, initial-scale=1.0')
|
||||||
|
meta(name='theme-color' content='#cd2682')
|
||||||
|
meta(name='description' content=description)
|
||||||
|
link(rel='icon' type='image/svg+xml' href='/shared/img/favicon.svg' sizes='any')
|
||||||
|
link(href='/assets/css/main.css' rel='stylesheet')
|
||||||
|
body
|
||||||
|
header
|
||||||
|
svg#logo(viewBox='0 -25 216 40')
|
||||||
|
text.logo Arav's dwelling
|
||||||
|
text.under(y='11') Welcome to my sacred place, wanderer
|
||||||
|
nav
|
||||||
|
a(href=main_site) Back to main website
|
||||||
|
h1 Files
|
||||||
|
section#files
|
||||||
|
span#current-path!= current_path
|
||||||
|
p Files: #{total_files} (#{total_files_size}); Directories: #{total_directories}.
|
||||||
|
table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th Name
|
||||||
|
th Date
|
||||||
|
th Size
|
||||||
|
tbody
|
||||||
|
tr
|
||||||
|
td #[a(href="../") ../]
|
||||||
|
each item in items
|
||||||
|
tr
|
||||||
|
td #[a(href=item.link)= item.name]
|
||||||
|
td!= moment(item.datetime, clientTZ)
|
||||||
|
td= item.size
|
14
homepage/config.example.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
exports.port = 32250;
|
||||||
|
exports.host = "127.0.0.1";
|
||||||
|
|
||||||
|
exports.database = {
|
||||||
|
host: "127.0.0.1",
|
||||||
|
database: "db",
|
||||||
|
user: "db",
|
||||||
|
password: "dbpass",
|
||||||
|
timezone: "Z" };
|
||||||
|
|
||||||
|
exports.guestbook = {
|
||||||
|
owner: "Owner",
|
||||||
|
pageSize: 30
|
||||||
|
}
|
@ -1,44 +1,44 @@
|
|||||||
const mysql = require("mysql");
|
const mysql = require("mysql");
|
||||||
|
|
||||||
const config = require("../config");
|
const config = require("./config");
|
||||||
const util = require("../util");
|
const util = require("../shared/util");
|
||||||
|
|
||||||
|
|
||||||
let connection = mysql.createConnection(config.dwelling.database);
|
let connection = mysql.createConnection(config.database);
|
||||||
|
|
||||||
|
|
||||||
connection.config.queryFormat = util.mysqlQueryFormat;
|
connection.config.queryFormat = util.mysqlQueryFormat;
|
||||||
|
|
||||||
exports.pageSize = config.dwelling.guestbook.pageSize !== undefined
|
exports.pageSize = config.guestbook.pageSize !== undefined
|
||||||
? config.dwelling.guestbook.pageSize : 20;
|
? config.guestbook.pageSize : 20;
|
||||||
|
|
||||||
exports.closeConnection = () => connection.end();
|
exports.closeConnection = () => connection.end();
|
||||||
|
|
||||||
exports.getPosts = async (page = 1, page_size = exports.pageSize) => {
|
exports.getPosts = async (page = 1, page_size = exports.pageSize) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let query = "\
|
let query = `
|
||||||
SELECT \
|
SELECT
|
||||||
`guestbook`.`post_id`, \
|
guestbook.post_id,
|
||||||
`guestbook`.`created`, \
|
guestbook.created,
|
||||||
`guestbook`.`name`, \
|
guestbook.name,
|
||||||
(CASE \
|
(CASE
|
||||||
WHEN `guestbook`.`hide_email` IS false \
|
WHEN guestbook.hide_email IS false
|
||||||
THEN `guestbook`.`email` \
|
THEN guestbook.email
|
||||||
ELSE NULL \
|
ELSE NULL
|
||||||
END) AS `email`, \
|
END) AS email,
|
||||||
(CASE \
|
(CASE
|
||||||
WHEN `guestbook`.`hide_website` IS false \
|
WHEN guestbook.hide_website IS false
|
||||||
THEN `guestbook`.`website` \
|
THEN guestbook.website
|
||||||
ELSE NULL \
|
ELSE NULL
|
||||||
END) AS `website`, \
|
END) AS website,
|
||||||
`guestbook`.`message`, \
|
guestbook.message,
|
||||||
`guestbook_feedback`.`comment` AS `feedback`, \
|
guestbook_feedback.comment AS feedback,
|
||||||
`guestbook_feedback`.`created` AS `feedback_created` \
|
guestbook_feedback.created AS feedback_created
|
||||||
FROM `guestbook` \
|
FROM guestbook
|
||||||
LEFT JOIN `guestbook_feedback` ON `guestbook`.`post_id` = `guestbook_feedback`.`post_id` \
|
LEFT JOIN guestbook_feedback ON guestbook.post_id = guestbook_feedback.post_id
|
||||||
ORDER BY `guestbook`.`created` DESC \
|
ORDER BY guestbook.created DESC
|
||||||
LIMIT :page_size \
|
LIMIT :page_size
|
||||||
OFFSET :page_offset;";
|
OFFSET :page_offset;`;
|
||||||
|
|
||||||
connection.query(query, { page_size: page_size, page_offset: (page-1) * page_size },
|
connection.query(query, { page_size: page_size, page_offset: (page-1) * page_size },
|
||||||
(err, results) => {
|
(err, results) => {
|
||||||
@ -63,10 +63,10 @@ exports.addPost = async (post) => {
|
|||||||
VALUES (:name, :email, :website, :message, :hide_website, :hide_email);";
|
VALUES (:name, :email, :website, :message, :hide_website, :hide_email);";
|
||||||
|
|
||||||
if (post.message === undefined || post.message === "")
|
if (post.message === undefined || post.message === "")
|
||||||
reject("empty message");
|
return reject("empty message");
|
||||||
|
|
||||||
if (post.message.includes("://"))
|
if (post.message.includes("</a>"))
|
||||||
reject("spam");
|
return reject("spam");
|
||||||
|
|
||||||
if (post.name === undefined || post.name === "")
|
if (post.name === undefined || post.name === "")
|
||||||
post.name = "Anonymous";
|
post.name = "Anonymous";
|
||||||
@ -76,7 +76,7 @@ exports.addPost = async (post) => {
|
|||||||
post.message = post.message.replace(/(?:\n\n|\n \n)/g, "\n");
|
post.message = post.message.replace(/(?:\n\n|\n \n)/g, "\n");
|
||||||
|
|
||||||
connection.query(query, post, (err, results, fields) => {
|
connection.query(query, post, (err, results, fields) => {
|
||||||
if (err) reject(err);
|
if (err) return reject(err);
|
||||||
resolve(true); });
|
return resolve(true); });
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -1,16 +1,16 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
|
const bodyParser = require("koa-body")({multipart: true});
|
||||||
|
const fetch = require("node-fetch");
|
||||||
const Koa = require("koa");
|
const Koa = require("koa");
|
||||||
const koaPug = require("koa-pug");
|
const koaPug = require("koa-pug");
|
||||||
const koaRouter = require("koa-router");
|
const koaRouter = require("koa-router");
|
||||||
const bodyParser = require("koa-body")({multipart: true});
|
const { MysqlError } = require("mysql");
|
||||||
const mysql = require("mysql");
|
|
||||||
const fetch = require("node-fetch");
|
|
||||||
|
|
||||||
const config = require("./config");
|
const config = require("./config");
|
||||||
const util = require("./util");
|
const guestbook = require("./guestbook");
|
||||||
const guestbook = require("./dwelling/guestbook");
|
const mindflow = require("./mindflow");
|
||||||
const mindflow = require("./dwelling/mindflow");
|
const util = require("../shared/util");
|
||||||
|
|
||||||
|
|
||||||
const articles_meta = {
|
const articles_meta = {
|
||||||
@ -38,8 +38,8 @@ async function getProcesses() {
|
|||||||
return Object.fromEntries(Object.entries(reimu).concat(Object.entries(sakuya)));
|
return Object.fromEntries(Object.entries(reimu).concat(Object.entries(sakuya)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRoutes(router) {
|
function setRoutes() {
|
||||||
router.get('/', async ctx => {
|
return koaRouter().get('/', async ctx => {
|
||||||
await ctx.render('index', {
|
await ctx.render('index', {
|
||||||
description: "A homepage of a russian guy Arav. Not just homepage, but FTP, radio, and smth more as well."
|
description: "A homepage of a russian guy Arav. Not just homepage, but FTP, radio, and smth more as well."
|
||||||
});
|
});
|
||||||
@ -65,7 +65,7 @@ function setRoutes(router) {
|
|||||||
title: "/ Mindflow",
|
title: "/ Mindflow",
|
||||||
description: "Here I will post updates on my infrastructure, my very important opinions and thoughts.",
|
description: "Here I will post updates on my infrastructure, my very important opinions and thoughts.",
|
||||||
files_site: util.getServiceByHost(ctx.request.host, "files"),
|
files_site: util.getServiceByHost(ctx.request.host, "files"),
|
||||||
tz: util.getTimezone(ctx),
|
tz: util.getClientTimezone(ctx),
|
||||||
diary: await mindflow.getPosts("Diary"),
|
diary: await mindflow.getPosts("Diary"),
|
||||||
updates: await mindflow.getPosts("Update")
|
updates: await mindflow.getPosts("Update")
|
||||||
})
|
})
|
||||||
@ -103,8 +103,8 @@ function setRoutes(router) {
|
|||||||
return await ctx.render('guestbook', {
|
return await ctx.render('guestbook', {
|
||||||
title: "/ Guestbook",
|
title: "/ Guestbook",
|
||||||
description: "This is my guestbook. Welcome.",
|
description: "This is my guestbook. Welcome.",
|
||||||
tz: util.getTimezone(ctx),
|
tz: util.getClientTimezone(ctx),
|
||||||
owner: config.dwelling.guestbook.owner,
|
owner: config.guestbook.owner,
|
||||||
posts: posts,
|
posts: posts,
|
||||||
error: posts === null,
|
error: posts === null,
|
||||||
pages_count: Math.ceil(await guestbook.getPostsCount() / page_size) });
|
pages_count: Math.ceil(await guestbook.getPostsCount() / page_size) });
|
||||||
@ -120,7 +120,7 @@ function setRoutes(router) {
|
|||||||
ctx.redirect("/guestbook");
|
ctx.redirect("/guestbook");
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
ctx.type = "text/plain";
|
ctx.type = "text/plain";
|
||||||
if (err instanceof mysql.MysqlError)
|
if (err instanceof MysqlError)
|
||||||
ctx.body = `Database failed so your post wasn't added. So your time wasn't wasted here's your message:\n${post.message}`;
|
ctx.body = `Database failed so your post wasn't added. So your time wasn't wasted here's your message:\n${post.message}`;
|
||||||
else
|
else
|
||||||
ctx.body = `Your post was rejected because of "${err}". So your time wasn't wasted here's your message:\n${post.message}`;
|
ctx.body = `Your post was rejected because of "${err}". So your time wasn't wasted here's your message:\n${post.message}`;
|
||||||
@ -129,35 +129,31 @@ function setRoutes(router) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = () => {
|
const app = new Koa();
|
||||||
const app = new Koa();
|
const pug = new koaPug({
|
||||||
const pug = new koaPug({
|
viewPath: path.join(__dirname, "views"),
|
||||||
viewPath: path.join(__dirname, "views", "dwelling"),
|
locals: {
|
||||||
locals: {
|
date_: (date, tz) => util.datetime(date, util.formats.post_date, tz),
|
||||||
date_: (date, tz) => util.datetime(date, util.formats.post_date, tz),
|
mindflowDateToId: (date, tz) => util.datetime(date, util.formats.id_date, tz),
|
||||||
mindflowDateToId: (date, tz) => util.datetime(date, util.formats.id_date, tz),
|
rssLink: util.rssLink },
|
||||||
rssLink: util.rssLink },
|
app: app
|
||||||
app: app
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const dwelling_router = koaRouter();
|
|
||||||
setRoutes(dwelling_router);
|
|
||||||
|
|
||||||
app.proxy = true;
|
app.proxy = true;
|
||||||
app
|
app
|
||||||
.use(async (ctx, next) => {
|
.use(async (ctx, next) => {
|
||||||
try {
|
try {
|
||||||
await next();
|
await next();
|
||||||
if (ctx.status === 404) ctx.throw(404);
|
if (ctx.status === 404) ctx.throw(404);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (ctx.status === 404)
|
if (ctx.status === 404)
|
||||||
await ctx.render('404');
|
await ctx.render('404');
|
||||||
else
|
else
|
||||||
await ctx.render('500');
|
await ctx.render('500');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.use(dwelling_router.routes())
|
.use(setRoutes().routes())
|
||||||
.listen(config.dwelling.port, config.dwelling.host);
|
.listen(config.port, config.host);
|
||||||
|
|
||||||
return app;
|
console.log("Arav's dwelling / Homepage is up.");
|
||||||
};
|
|
49
homepage/mindflow.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
const mysql = require("mysql");
|
||||||
|
|
||||||
|
const config = require("./config");
|
||||||
|
const util = require("../shared/util");
|
||||||
|
|
||||||
|
|
||||||
|
let connection = mysql.createConnection(config.database);
|
||||||
|
|
||||||
|
|
||||||
|
connection.config.queryFormat = util.mysqlQueryFormat;
|
||||||
|
|
||||||
|
exports.closeConnection = () => connection.end();
|
||||||
|
|
||||||
|
exports.getPosts = category => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let query = `
|
||||||
|
SELECT
|
||||||
|
blog.entry_id AS post_id,
|
||||||
|
blog.date,
|
||||||
|
blog.title,
|
||||||
|
blog.body
|
||||||
|
FROM blog
|
||||||
|
LEFT JOIN blog_categories ON blog_categories.category_id = blog.category
|
||||||
|
WHERE blog_categories.category = :category
|
||||||
|
ORDER BY blog.date DESC;`;
|
||||||
|
|
||||||
|
connection.query(query, { category: category }, (err, results) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(results); });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getPostsForRSS = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let query = `
|
||||||
|
SELECT
|
||||||
|
blog.date,
|
||||||
|
blog_categories.category,
|
||||||
|
blog.title,
|
||||||
|
blog.body
|
||||||
|
FROM blog
|
||||||
|
LEFT JOIN blog_categories ON blog_categories.category_id = blog.category
|
||||||
|
ORDER BY blog.date DESC;`;
|
||||||
|
|
||||||
|
connection.query(query, (err, results) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(results); });
|
||||||
|
});
|
||||||
|
};
|
Before Width: | Height: | Size: 478 KiB After Width: | Height: | Size: 478 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 218 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 380 KiB After Width: | Height: | Size: 380 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
9
index.js
@ -1,9 +0,0 @@
|
|||||||
|
|
||||||
const app_dwelling = require("./dwelling")();
|
|
||||||
console.log("Arav's dwelling / Main website is up.");
|
|
||||||
|
|
||||||
const app_radio = require("./radio")();
|
|
||||||
console.log("Arav's dwelling / Radio is up.");
|
|
||||||
|
|
||||||
const app_files = require("./files")();
|
|
||||||
console.log("Arav's dwelling / Files is up.");
|
|
53
install.sql
@ -1,53 +0,0 @@
|
|||||||
-- Was meant to work on MySQL/MariaDB
|
|
||||||
|
|
||||||
CREATE DATABASE `dwelling`
|
|
||||||
COLLATE utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
USE `dwelling`;
|
|
||||||
|
|
||||||
CREATE USER `dweller`@`%`
|
|
||||||
IDENTIFIED BY 'password';
|
|
||||||
|
|
||||||
GRANT ALL PRIVILEGES
|
|
||||||
ON `dwelling`.*
|
|
||||||
TO `dweller`@`%`;
|
|
||||||
|
|
||||||
CREATE TABLE `blog_categories` (
|
|
||||||
`category_id` MEDIUMINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
`category` VARCHAR(40) NOT NULL UNIQUE );
|
|
||||||
|
|
||||||
INSERT INTO `blog_categories` (`category`)
|
|
||||||
VALUES ('Update'), ('Diary');
|
|
||||||
|
|
||||||
CREATE INDEX `blog_categories_ix_category`
|
|
||||||
ON `blog_categories` (`category`);
|
|
||||||
|
|
||||||
CREATE TABLE `blog` (
|
|
||||||
`entry_id` SERIAL PRIMARY KEY,
|
|
||||||
`date` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
||||||
`category` MEDIUMINT NOT NULL,
|
|
||||||
`title` VARCHAR(255) NOT NULL,
|
|
||||||
`body` MEDIUMTEXT NOT NULL,
|
|
||||||
FOREIGN KEY (`category`) REFERENCES `blog_categories` (`category_id`)
|
|
||||||
ON UPDATE CASCADE );
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `guestbook` (
|
|
||||||
`post_id` SERIAL PRIMARY KEY,
|
|
||||||
`created` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
||||||
`name` VARCHAR(80) NOT NULL,
|
|
||||||
`email` VARCHAR(255),
|
|
||||||
`website` VARCHAR(255),
|
|
||||||
`message` VARCHAR(4096) NOT NULL,
|
|
||||||
`hide_website` BOOLEAN DEFAULT TRUE NOT NULL,
|
|
||||||
`hide_email` BOOLEAN DEFAULT TRUE NOT NULL);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS `guestbook_ix_created`
|
|
||||||
ON `guestbook` (`created`);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `guestbook_feedback` (
|
|
||||||
`post_id` SERIAL PRIMARY KEY,
|
|
||||||
`created` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
||||||
`comment` VARCHAR(4096) NOT NULL,
|
|
||||||
FOREIGN KEY (`post_id`) REFERENCES `guestbook` (`post_id`)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE );
|
|
2330
package-lock.json
generated
Normal file
60
package.json
@ -1,33 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "arav-dwelling",
|
"name": "arav-dwelling",
|
||||||
"version": "21.10.0",
|
"description": "Arav's dwelling",
|
||||||
"description": "Arav's dwelling",
|
"version": "21.22.0",
|
||||||
"homepage": "https://git.arav.top/Arav/Dwelling",
|
"author": "Alexander \"Arav\" Andreev <me@arav.top> (https://arav.top)",
|
||||||
"repository": {
|
"license": "GPLv3",
|
||||||
"type": "git",
|
"homepage": "https://git.arav.top/Arav/Dwelling",
|
||||||
"url": "https://git.arav.top/Arav/Dwelling"
|
"repository": {
|
||||||
},
|
"type": "git",
|
||||||
"main": "index.js",
|
"url": "https://git.arav.top/Arav/Dwelling.git"
|
||||||
"scripts": {
|
},
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"private": true,
|
||||||
},
|
"main": "./homepage/index.js",
|
||||||
"keywords": [
|
"exports": {
|
||||||
"homepage",
|
"./homepage": "./homepage/index.js",
|
||||||
"dwelling",
|
"./files": "./files/index.js",
|
||||||
"Arav"
|
"./radio": "./radio/index.js"
|
||||||
],
|
},
|
||||||
"author": "Alexander \"Arav\" Andreev <me@arav.top> (https://arav.top)",
|
"dependencies": {
|
||||||
"license": "GPLv3",
|
"koa": "^2.13.1",
|
||||||
"dependencies": {
|
"koa-body": "^4.2.0",
|
||||||
"koa": "^2.13.1",
|
"koa-pug": "^5.0.0",
|
||||||
"koa-body": "^4.2.0",
|
"koa-range": "^0.3.0",
|
||||||
"koa-pug": "^4.0.4",
|
"koa-router": "^10.0.0",
|
||||||
"koa-range": "^0.3.0",
|
"koa-static": "^5.0.0",
|
||||||
"koa-router": "^10.0.0",
|
"moment": "^2.29.1",
|
||||||
"koa-static": "^5.0.0",
|
"moment-timezone": "^0.5.33",
|
||||||
"moment": "^2.29.1",
|
"mysql": "^2.18.1",
|
||||||
"moment-timezone": "^0.5.33",
|
"node-fetch": "^2.6.1"
|
||||||
"mysql": "^2.18.1",
|
}
|
||||||
"node-fetch": "^2.6.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
2
radio/config.example.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
exports.port = 32251;
|
||||||
|
exports.host = "127.0.0.1";
|
@ -7,7 +7,7 @@ const koaRouter = require("koa-router");
|
|||||||
const fetch = require("node-fetch");
|
const fetch = require("node-fetch");
|
||||||
|
|
||||||
const config = require("./config");
|
const config = require("./config");
|
||||||
const util = require("./util");
|
const util = require("../shared/util");
|
||||||
|
|
||||||
async function getRadioStatus() {
|
async function getRadioStatus() {
|
||||||
try {
|
try {
|
||||||
@ -30,8 +30,8 @@ async function getRadioStatus() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRoutes(router) {
|
function setRoutes() {
|
||||||
router.get('/', async ctx => {
|
return koaRouter().get('/', async ctx => {
|
||||||
await ctx.render('index', {
|
await ctx.render('index', {
|
||||||
title: "/ Radio",
|
title: "/ Radio",
|
||||||
description: "Internet-radio broadcasting from under my desk.",
|
description: "Internet-radio broadcasting from under my desk.",
|
||||||
@ -42,34 +42,27 @@ function setRoutes(router) {
|
|||||||
.get('/filelist', async ctx => {
|
.get('/filelist', async ctx => {
|
||||||
ctx.type = 'text/html';
|
ctx.type = 'text/html';
|
||||||
ctx.body = fs.readFileSync(
|
ctx.body = fs.readFileSync(
|
||||||
path.join(__dirname, '/static/radio/assets/files/radio_filelist.html'));
|
path.join(__dirname, '/static/radio_filelist.html'));
|
||||||
})
|
})
|
||||||
.get('/playlist', async ctx => {
|
.get('/playlist', async ctx => {
|
||||||
ctx.attachment('radio.arav.top.m3u');
|
ctx.attachment('radio.arav.top.m3u');
|
||||||
ctx.body = fs.readFileSync(
|
ctx.body = fs.readFileSync(
|
||||||
path.join(__dirname, '/static/radio/assets/files/radio.arav.top.m3u'));
|
path.join(__dirname, '/static/assets/files/radio.arav.top.m3u'));
|
||||||
})
|
})
|
||||||
.get('/stats', async ctx => {
|
.get('/stats', async ctx => {
|
||||||
ctx.body = await getRadioStatus();
|
ctx.body = await getRadioStatus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = () => {
|
const app = new Koa();
|
||||||
const app = new Koa();
|
const pug = new koaPug({
|
||||||
const pug = new koaPug({
|
viewPath: path.join(__dirname, "views"),
|
||||||
viewPath: path.join(__dirname, "views", "radio"),
|
locals: {
|
||||||
locals: {
|
moment: util.datetime },
|
||||||
moment: util.datetime },
|
app: app
|
||||||
app: app
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const radio_router = koaRouter();
|
app.proxy = true;
|
||||||
setRoutes(radio_router);
|
app
|
||||||
|
.use(setRoutes().routes())
|
||||||
app.proxy = true;
|
.listen(config.port, config.host);
|
||||||
app
|
|
||||||
.use(radio_router.routes())
|
|
||||||
.listen(config.radio.port, config.radio.host);
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
3
radio/static/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /.well-known/
|
||||||
|
Disallow: /assets/
|
54
radio/views/index.pug
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
mixin radioStatus(date, iso)
|
||||||
|
if (date != "n/a")
|
||||||
|
p #[span#radio-status On-air since #[time(datetime=iso)= date]]
|
||||||
|
else
|
||||||
|
p #[span#radio-status Radio is offline.]
|
||||||
|
|
||||||
|
doctype html
|
||||||
|
html(lang='en')
|
||||||
|
head
|
||||||
|
title Arav's dwelling #{title}
|
||||||
|
meta(charset='utf-8')
|
||||||
|
meta(http-equiv='X-UA-Compatible' content='IE=edge')
|
||||||
|
meta(name='viewport' content='width=device-width, initial-scale=1.0')
|
||||||
|
meta(name='theme-color' content='#cd2682')
|
||||||
|
meta(name='description' content=description)
|
||||||
|
link(rel='icon' href='/shared/img/favicon.svg' sizes='any' type='image/svg+xml')
|
||||||
|
link(href='/assets/css/main.css' rel='stylesheet')
|
||||||
|
script(src='/assets/js/main.js' defer)
|
||||||
|
body
|
||||||
|
header
|
||||||
|
svg#logo(viewBox='0 -25 216 40')
|
||||||
|
text.logo Arav's dwelling
|
||||||
|
text.under(y='11') Welcome to my sacred place, wanderer
|
||||||
|
nav
|
||||||
|
a(href=main_site) Back to main website
|
||||||
|
h1 Radio
|
||||||
|
section
|
||||||
|
small.player-links
|
||||||
|
a(href="/filelist") file list
|
||||||
|
a(href="/playlist") playlist (.m3u)
|
||||||
|
a(href="/live/stream.ogg") direct link
|
||||||
|
a(href="http://radio.arav.top:8000/stream.ogg") direct link (http)
|
||||||
|
a(href="http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion/radio/live/stream.ogg") direct link (Tor)
|
||||||
|
a(href="http://t42fkp6zp5dfqywantq3zp427ig3q2onrmfv246tyaztpg4ckb5a.b32.i2p/radio/live/stream.ogg") direct link (I2P)
|
||||||
|
| OGG 128 Kb/s
|
||||||
|
audio(preload='none' controls)
|
||||||
|
source(src="/live/stream.ogg" type="audio/ogg")
|
||||||
|
| Your browser doesn't support an audio element, it's sad... But you always can take the #[a(href="/playlist") playlist]!
|
||||||
|
+radioStatus(radio_status.server_start_date, radio_status.server_start_iso8601)
|
||||||
|
p Now playing: #[span#radio-song= radio_status.song]
|
||||||
|
p Current/peak listeners: #[span#radio-listeners= radio_status.listeners] / #[span#radio-listener-peak= radio_status.listener_peak]
|
||||||
|
p
|
||||||
|
small Notice: information updates every 45 seconds. But you can #[a(href="javascript:getRadioStats()") update] it forcibly.
|
||||||
|
section
|
||||||
|
h2 About the radio
|
||||||
|
p This project grew from MPD I used to stream music over LAN for myself. Then I let it out and placed a player on my neocities website. Then I added Icecast to see if there are listeners. I wasn't happy on how MPD was nearly overloading CPU, and later on, when I moved radio off to a laptop, I found a great tool for streams called Liquidsoap.
|
||||||
|
p Radio is also available through HTTP port 8000, but browsers may try to redirect to HTTPS. Any other applications should work fine.
|
||||||
|
p Radio is listed in #[a(href="https://dir.xiph.org/search?q=arav's+dwelling") Xiph directory].
|
||||||
|
section
|
||||||
|
h2 Privacy statements
|
||||||
|
p I collect access logs that include access date and time, IP-address, User-Agent, referer URL that tells me where have you came from, request that you sent to me. In addition there are GeoIP information added based on your IP-address that includes country, region, and city for my convenience.
|
||||||
|
p This website makes use of JavaScript to show you information on a radio.
|
||||||
|
footer
|
||||||
|
| 2017—2021 Arav <#[a(href='mailto:me@arav.top') me@arav.top]>
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 975 B |
Before Width: | Height: | Size: 854 B After Width: | Height: | Size: 854 B |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@ -5,10 +5,21 @@ const formats = Object.freeze({
|
|||||||
file_date: "YYYY-MM-DD HH:mm:ss z",
|
file_date: "YYYY-MM-DD HH:mm:ss z",
|
||||||
id_date: "YYYYMMDD-HHmm" });
|
id_date: "YYYYMMDD-HHmm" });
|
||||||
|
|
||||||
|
exports.formats = formats;
|
||||||
|
|
||||||
function datetime(date, format=formats.post_date, timezone="UTC") {
|
function datetime(date, format=formats.post_date, timezone="UTC") {
|
||||||
return moment.utc(date).tz(timezone).format(format);
|
return moment.utc(date).tz(timezone).format(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.datetime = datetime;
|
||||||
|
|
||||||
|
exports.getClientTimezone = ctx => {
|
||||||
|
if (ctx.header['x-client-timezone'] !== undefined)
|
||||||
|
return ctx.header['x-client-timezone'];
|
||||||
|
else
|
||||||
|
return "UTC";
|
||||||
|
};
|
||||||
|
|
||||||
exports.getServiceByHost = (host, service="") => {
|
exports.getServiceByHost = (host, service="") => {
|
||||||
if (host.endsWith("onion"))
|
if (host.endsWith("onion"))
|
||||||
switch (service) {
|
switch (service) {
|
||||||
@ -30,16 +41,6 @@ exports.getServiceByHost = (host, service="") => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getTimezone = ctx => {
|
|
||||||
if (ctx.header['x-client-timezone'] !== undefined)
|
|
||||||
return ctx.header['x-client-timezone'];
|
|
||||||
else
|
|
||||||
return "UTC";
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.formats = formats;
|
|
||||||
exports.datetime = datetime;
|
|
||||||
|
|
||||||
exports.rssLink = (proto, host, date, category, timezone="UTC") =>
|
exports.rssLink = (proto, host, date, category, timezone="UTC") =>
|
||||||
`${proto}://${host}/mindflow#${category}-${datetime(date, formats.id_date, timezone)}`;
|
`${proto}://${host}/mindflow#${category}-${datetime(date, formats.id_date, timezone)}`;
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Disallow: /assets/
|
|
||||||
Disallow: /files/
|
|
@ -1,3 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Disallow: /assets/
|
|
||||||
Disallow: /stream.ogg
|
|
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 854 B |
@ -1,21 +0,0 @@
|
|||||||
doctype html
|
|
||||||
html(lang='en')
|
|
||||||
head
|
|
||||||
title Arav's dwelling #{title}
|
|
||||||
meta(charset='utf-8')
|
|
||||||
meta(http-equiv='X-UA-Compatible' content='IE=edge')
|
|
||||||
meta(name='viewport' content='width=device-width, initial-scale=1.0')
|
|
||||||
meta(name='theme-color' content='#cd2682')
|
|
||||||
meta(name='description' content=description)
|
|
||||||
link(rel='icon' href='/shared/img/favicon.svg' sizes='any' type='image/svg+xml')
|
|
||||||
link(href='/assets/css/main.css' rel='stylesheet')
|
|
||||||
block head
|
|
||||||
body
|
|
||||||
header
|
|
||||||
svg#logo(viewBox='0 -25 216 40')
|
|
||||||
text.logo Arav's dwelling
|
|
||||||
text.under(y='11') Welcome to my sacred place, wanderer
|
|
||||||
block nav
|
|
||||||
block content
|
|
||||||
footer
|
|
||||||
| 2017—2021 Arav <#[a(href='mailto:me@arav.top') me@arav.top]>
|
|
@ -1,25 +0,0 @@
|
|||||||
extends base.pug
|
|
||||||
|
|
||||||
block nav
|
|
||||||
nav
|
|
||||||
a(href=main_site) Back to main website
|
|
||||||
h1 Files
|
|
||||||
|
|
||||||
block content
|
|
||||||
section#files
|
|
||||||
span#current-path!= current_path
|
|
||||||
p Files: #{total_files} (#{total_files_size}); Directories: #{total_directories}.
|
|
||||||
table
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
th Name
|
|
||||||
th Date
|
|
||||||
th Size
|
|
||||||
tbody
|
|
||||||
tr
|
|
||||||
td #[a(href="../") ../]
|
|
||||||
each item in items
|
|
||||||
tr
|
|
||||||
td #[a(href=item.link)= item.name]
|
|
||||||
td!= item.datetime
|
|
||||||
td= item.size + ' ' + item.size_unit
|
|
@ -1,21 +0,0 @@
|
|||||||
doctype html
|
|
||||||
html(lang='en')
|
|
||||||
head
|
|
||||||
title Arav's dwelling #{title}
|
|
||||||
meta(charset='utf-8')
|
|
||||||
meta(http-equiv='X-UA-Compatible' content='IE=edge')
|
|
||||||
meta(name='viewport' content='width=device-width, initial-scale=1.0')
|
|
||||||
meta(name='theme-color' content='#cd2682')
|
|
||||||
meta(name='description' content=description)
|
|
||||||
link(rel='icon' href='/shared/img/favicon.svg' sizes='any' type='image/svg+xml')
|
|
||||||
link(href='/assets/css/main.css' rel='stylesheet')
|
|
||||||
block head
|
|
||||||
body
|
|
||||||
header
|
|
||||||
svg#logo(viewBox='0 -25 216 40')
|
|
||||||
text.logo Arav's dwelling
|
|
||||||
text.under(y='11') Welcome to my sacred place, wanderer
|
|
||||||
block nav
|
|
||||||
block content
|
|
||||||
footer
|
|
||||||
| 2017—2021 Arav <#[a(href='mailto:me@arav.top') me@arav.top]>
|
|
@ -1,43 +0,0 @@
|
|||||||
extends base.pug
|
|
||||||
|
|
||||||
block head
|
|
||||||
script(src='/assets/js/main.js' defer)
|
|
||||||
|
|
||||||
block nav
|
|
||||||
nav
|
|
||||||
a(href=main_site) Back to main website
|
|
||||||
h1 Radio
|
|
||||||
|
|
||||||
mixin radioStatus(date, iso)
|
|
||||||
if (date != "n/a")
|
|
||||||
p #[span#radio-status On-air since #[time(datetime=iso)= date]]
|
|
||||||
else
|
|
||||||
p #[span#radio-status Radio is offline.]
|
|
||||||
|
|
||||||
block content
|
|
||||||
section
|
|
||||||
small.player-links
|
|
||||||
a(href="/filelist") file list
|
|
||||||
a(href="/playlist") playlist (.m3u)
|
|
||||||
a(href="/live/stream.ogg") direct link
|
|
||||||
a(href="http://radio.arav.top:8000/stream.ogg") direct link (http)
|
|
||||||
a(href="http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion/radio/live/stream.ogg") direct link (Tor)
|
|
||||||
a(href="http://t42fkp6zp5dfqywantq3zp427ig3q2onrmfv246tyaztpg4ckb5a.b32.i2p/radio/live/stream.ogg") direct link (I2P)
|
|
||||||
| OGG 128 Kb/s
|
|
||||||
audio(preload='none' controls)
|
|
||||||
source(src="/live/stream.ogg" type="audio/ogg")
|
|
||||||
| Your browser doesn't support an audio element, it's sad... But you always can take the #[a(href="/playlist") playlist]!
|
|
||||||
+radioStatus(radio_status.server_start_date, radio_status.server_start_iso8601)
|
|
||||||
p Now playing: #[span#radio-song= radio_status.song]
|
|
||||||
p Current/peak listeners: #[span#radio-listeners= radio_status.listeners] / #[span#radio-listener-peak= radio_status.listener_peak]
|
|
||||||
p
|
|
||||||
small Notice: information updates every 45 seconds. But you can #[a(href="javascript:getRadioStats()") update] it forcibly.
|
|
||||||
section
|
|
||||||
h2 About the radio
|
|
||||||
p This project grew from MPD I used to stream music over LAN for myself. Then I let it out and placed a player on my neocities website. Then I added Icecast to see if there are listeners. I wasn't happy on how MPD was nearly overloading CPU, and later on, when I moved radio off to a laptop, I found a great tool for streams called Liquidsoap.
|
|
||||||
p Radio is also available through HTTP port 8000, but browsers may try to redirect to HTTPS. Any other applications should work fine.
|
|
||||||
p Radio is listed in #[a(href="https://dir.xiph.org/search?q=arav's+dwelling") Xiph directory].
|
|
||||||
section
|
|
||||||
h2 Privacy statements
|
|
||||||
p I collect access logs that include access date and time, IP-address, User-Agent, referer URL that tells me where have you came from, request that you sent to me. In addition there are GeoIP information added based on your IP-address that includes country, region, and city for my convenience.
|
|
||||||
p This website makes use of JavaScript to show you information on a radio.
|
|
6
yandex_83cb7680b026e750.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>Verification: 83cb7680b026e750</body>
|
||||||
|
</html>
|