yarmo.eu/server/util.js
2020-12-08 16:07:10 +01:00

258 lines
7.1 KiB
JavaScript

const fs = require('fs')
const { readdir } = require('fs').promises
const path = require('path')
const { resolve } = require('path')
const bent = require('bent')
const getJSON = bent('json')
const _ = require('lodash')
const md = require('markdown-it')({ typographer: true })
const yamlFront = require('yaml-front-matter')
const { DateTime } = require('luxon')
const Feed = require('feed').Feed
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const adapter = new FileSync('db.json')
const db = low(adapter)
db.defaults({ posts: [] })
.write()
md.use(require("markdown-it-anchor"), {
"level": 2,
"permalink": true,
"permalinkClass": 'header-anchor',
"permalinkSymbol": '¶',
"permalinkBefore": false
})
async function* getFiles(dir) {
const dirents = await readdir(dir, { withFileTypes: true })
for (const dirent of dirents) {
const res = resolve(dir, dirent.name)
if (dirent.isDirectory()) {
yield* getFiles(res)
} else {
yield res
}
}
}
const getBlogPosts = async (opts) => {
if (!opts) { opts = {} }
opts.publishedOnly = 'publishedOnly' in opts ? opts.publishedOnly : true
return db._.reverse(db.get('posts')
.filter({ type: 'blog', published: opts.publishedOnly })
.sortBy('date')
.value())
}
const getBlogPostsFromFiles = async (opts) => {
if (!opts) { opts = {} }
opts.publishedOnly = 'publishedOnly' in opts ? opts.publishedOnly : true
let data = []
for await (const f of getFiles(path.join(__dirname, '../', 'content', 'blog'))) {
const rawContent = fs.readFileSync(f, 'utf8')
const fm = yamlFront.loadFront(rawContent)
if (opts.publishedOnly && !fm.published) { continue }
data.push({
type: 'blog',
title: fm.title,
author: fm.author,
urlrel: `/post/${fm.slug}`,
url: `https://yarmo.eu/post/${fm.slug}`,
slug: fm.slug,
date: fm.date,
date_formatted: DateTime.fromFormat(fm.date, 'yyyy-LL-dd hh:mm:ss').setLocale("en").toLocaleString(DateTime.DATE_MED),
published: fm.published,
discussion: fm.discussion,
content: md.render(fm.__content)
})
}
return data.reverse()
}
const getNotes = async (opts) => {
if (!opts) { opts = {} }
opts.publishedOnly = 'publishedOnly' in opts ? opts.publishedOnly : true
return db._.reverse(db.get('posts')
.filter({ type: 'note', published: opts.publishedOnly })
.sortBy('date')
.value())
}
const getNotesFromFiles = async (opts) => {
if (!opts) { opts = {} }
opts.publishedOnly = 'publishedOnly' in opts ? opts.publishedOnly : true
let data = []
for await (const f of getFiles(path.join(__dirname, '../', 'content', 'notes'))) {
const rawContent = fs.readFileSync(f, 'utf8')
const fm = yamlFront.loadFront(rawContent)
if (opts.publishedOnly && !fm.published) { continue }
data.push({
type: 'note',
title: fm.title,
author: fm.author,
urlrel: `/post/${fm.slug}`,
url: `https://yarmo.eu/post/${fm.slug}`,
slug: fm.slug,
date: fm.date,
date_formatted: DateTime.fromFormat(fm.date, 'yyyy-LL-dd hh:mm:ss').setLocale("en").setZone('utc').toLocaleString(DateTime.DATE_MED),
published: fm.published,
discussion: fm.discussion,
content: md.render(fm.__content)
})
}
return data.reverse()
}
const getPost = async (slug) => {
let post = db.get('posts')
.find({ slug: slug })
.value()
return post
}
const getWebmentions = async (url) => {
let data
try {
const data_1 = await getJSON(`https://webm.yarmo.eu/get?target=${url}`)
const data_2 = await getJSON(`https://webm.yarmo.eu/get?target=${url.replace('/post/', '/blog/')}`)
const data_3 = await getJSON(`https://webm.yarmo.eu/get?target=${url.replace('/post/', '/notes/')}`)
const dataRaw = data_1.concat(data_2).concat(data_3)
data = _.map(dataRaw, (x) => {
x.date = DateTime.fromISO(x.created_at).setLocale("en").setZone('utc').toLocaleString(DateTime.DATE_MED),
x.time = DateTime.fromISO(x.created_at).setLocale("en").setZone('utc').toLocaleString(DateTime.TIME_24_WITH_SHORT_OFFSET)
return x
})
} catch(e) {
data = []
}
return data
}
const getRSS = async (opts) => {
if (!opts) { opts = {} }
opts.include = 'include' in opts ? opts.include : ['blog', 'notes']
opts.channel = 'channel' in opts ? opts.channel : 'all'
opts.format = 'format' in opts ? opts.format : 'rss'
const feed = new Feed({
title: "Yarmo's blog and notes",
description: "Blog posts and notes feed for yarmo.eu discussing open source, privacy and selfhosted stuff",
id: "https://yarmo.eu",
link: "https://yarmo.eu",
language: "en",
favicon: "https://yarmo.eu/favicon.png",
copyright: "All rights reserved 2020 Yarmo Mackenbach",
updated: new Date(Date.now()),
feedLinks: {
rss: `https://yarmo.eu/feed/${opts.channel}`,
json: `https://yarmo.eu/feed/${opts.channel}.json`,
atom: `https://yarmo.eu/feed/${opts.channel}.atom`
},
author: {
name: "Yarmo Mackenbach",
email: "yarmo@yarmo.eu",
link: "https://yarmo.eu"
}
})
feed.addCategory("Technology")
feed.addCategory("FOSS")
feed.addCategory("Decentralization")
feed.addCategory("Privacy")
feed.addCategory("Identity")
feed.addCategory("Cryptography")
feed.addCategory("Selfhosted")
let posts = []
if (opts.include.includes('blog')) {
posts = posts.concat(await getBlogPosts())
}
if (opts.include.includes('notes')) {
posts = posts.concat(await getNotes())
}
posts = _.sortBy(posts, ['date']).reverse()
posts.forEach((item, i) => {
feed.addItem({
title: item.title,
id: item.url,
link: item.url,
description: item.content,
content: item.content,
author: [
{
name: "Yarmo Mackenbach",
email: "yarmo@yarmo.eu",
link: "https://yarmo.eu"
}
],
date: new Date(item.date)
})
})
let response
switch (opts.format) {
case 'rss':
case 'xml':
default:
response = feed.rss2()
break;
case 'atom':
response = feed.atom1()
break;
case 'json':
response = feed.json1()
break;
}
return response
}
const updateDB = async () => {
db.read()
db.set('posts', [])
.write()
let data
data = await getBlogPostsFromFiles({ publishedOnly: false })
data = data.concat(await getNotesFromFiles({ publishedOnly: false }))
data.forEach((item, i) => {
if (db.get('posts').find({ slug: item.slug }).size().value() == 0) {
db.get('posts')
.push(item)
.write()
} else {
db.get('posts')
.find({ slug: item.slug })
.assign(item)
.write()
}
})
}
module.exports = {
getFiles: getFiles,
getBlogPosts: getBlogPosts,
getBlogPostsFromFiles: getBlogPostsFromFiles,
getNotes: getNotes,
getNotesFromFiles: getNotesFromFiles,
getPost: getPost,
getWebmentions: getWebmentions,
getRSS: getRSS,
updateDB: updateDB
}