-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
155 lines (124 loc) · 5.61 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import fs from "node:fs";
import { Elysia } from "elysia";
import type { Article, EightyEightByThirtyOne } from "./types";
import { articlesToRss, initializeBlogPosts } from "./misc/articles";
import { load88x31s, load88x31sHtml } from "./misc/88x31";
import { mdToHtml, mdToLiteHtml } from "./misc/markdown";
import { addArticleView, getAllArticleViews, getUserIP } from "./misc/analytics";
let blogPosts: Article[] = initializeBlogPosts();
let eightyEightByThirtyOnes: EightyEightByThirtyOne[] = load88x31s();
let eightyEightByThirtyOnesHtml: string = load88x31sHtml();
let imagesCache: { [key: string]: Buffer } = {};
let pageIpCache: { [key: string]: string[] } = {};
let homeHtml = fs.readFileSync("./html/home.html", "utf-8");
let baseHtml = fs.readFileSync("./html/base.html", "utf-8");
let statisticsHtml = fs.readFileSync("./html/stats.html", "utf-8");
let notFoundHtml = fs.readFileSync("./html/404.html", "utf-8");
const cliArgs = process.argv.slice(2);
const router = new Elysia();
/* Home */
router.get("/", ({ set }) => {
set.headers["Content-Type"] = "text/html";
const articles = blogPosts.map((article) =>
`
<a href="/article/${article.url_name}" style="text-decoration: none;">
<div class="article" published="${article.published}">
<div class="preview-image" style="background: url(${article.meta_image});"></div>
<div class="article-information">
<div class="title-container">
<h4>${article.title}</h4>
<span>(<span class="extended-date">published </span>${new Date(article.published).toLocaleDateString()})</span>
</div>
<p>${mdToLiteHtml(article.content.slice(0, 256))}...</p>
</div>
</div>
</a>
`
).join("");
return homeHtml.replace("{{articles}}", articles).replace("{{88x31s}}", eightyEightByThirtyOnesHtml);
});
/* 88x31 management */
router.get("/88x31", ({ }) => eightyEightByThirtyOnes);
router.get("/88x31/:name", ({ set, params }) => {
const banner = eightyEightByThirtyOnes.find((banner) => banner.name === params.name);
if (!banner) { set.status = 404; return null; }
set.headers["Content-Type"] = `image/${banner.path.split(".").pop()}`;
if (imagesCache[banner.path]) return imagesCache[banner.path];
imagesCache[banner.path] = fs.readFileSync(banner.path);
return imagesCache[banner.path];
});
/* Article management */
router.get("/api/articles", ({ }) => blogPosts);
router.get("/article/:name", ({ set, params, request }) => {
set.headers["Content-Type"] = "text/html";
const article = blogPosts.find((article) => article.url_name === params.name);
if (!article) { set.status = 404; return notFoundHtml; }
if (!pageIpCache[params.name]) pageIpCache[params.name] = [];
if (!pageIpCache[params.name].includes(getUserIP(request))) {
pageIpCache[params.name].push(getUserIP(request));
addArticleView(params.name);
}
return baseHtml
.replace("{{title}}", article.title)
.replace("{{meta_title}}", article.title)
.replace(/{{meta_image}}/g, `https://blog.northernsi.de${article.meta_image}` ?? "https://blog.northernsi.de/media/meta.webp")
.replace("{{content}}", mdToHtml(article));
});
router.get("/media/:name", ({ set, params }) => {
const folder = Bun.env.MEDIA_DIR ?? "./media";
const filePath = `${folder}/${params.name}`;
if (!fs.existsSync(filePath)) { set.status = 404; set.headers["Content-Type"] = "text/html"; return notFoundHtml; }
set.headers["Content-Type"] = `image/${filePath.split(".").pop()}`;
if (imagesCache[filePath]) return imagesCache[filePath];
imagesCache[filePath] = fs.readFileSync(filePath);
return imagesCache[filePath];
});
router.get("/statistics", ({ set }) => {
set.headers["Content-Type"] = "text/html";
const stats = Object.keys(getAllArticleViews()).sort((a, b) => getAllArticleViews()[b] - getAllArticleViews()[a]).map((articleId) => {
const article = blogPosts.find((article) => article.url_name === articleId);
return `<div><p>${getAllArticleViews()[articleId]} views - <a href="/article/${articleId}">${article?.title}</a></p></div>`;
}).join("");
return statisticsHtml
.replace("{{stats}}", stats)
.replace("{{88x31s}}", eightyEightByThirtyOnesHtml);
});
router.get("/rss.xml", ({ set }) => {
set.headers["Content-Type"] = "application/xml";
return articlesToRss(blogPosts);
});
router.get("/*", ({ set }) => {
set.status = 404;
set.headers["Content-Type"] = "text/html";
return notFoundHtml;
});
router.listen({
hostname: Bun.env.HOSTNAME ?? "127.0.0.1",
port: Bun.env.PORT ?? 3000,
});
if (cliArgs.includes("--watch")) {
console.log("Watching for changes...");
fs.watch("./articles", () => {
console.log("Reloading articles...");
blogPosts = initializeBlogPosts();
});
fs.watch("./media", () => {
console.log("Reloading media...");
imagesCache = {};
});
fs.watch("./html", () => {
console.log("Reloading HTML...");
homeHtml = fs.readFileSync("./html/home.html", "utf-8");
baseHtml = fs.readFileSync("./html/base.html", "utf-8");
statisticsHtml = fs.readFileSync("./html/stats.html", "utf-8");
notFoundHtml = fs.readFileSync("./html/404.html", "utf-8");
});
fs.watch("./88x31", () => {
console.log("Reloading 88x31s...");
eightyEightByThirtyOnes = load88x31s();
eightyEightByThirtyOnesHtml = load88x31sHtml();
});
}
setInterval(() => {
pageIpCache = {};
}, 5 * 60 * 1000);