192 lines
5.8 KiB
JavaScript
192 lines
5.8 KiB
JavaScript
import { Router } from "express";
|
||
import * as searchService from "../services/search.service.js";
|
||
import * as entryService from "../services/entry.service.js";
|
||
import * as tagService from "../services/tag.service.js";
|
||
import * as commentService from "../services/comment.service.js";
|
||
import * as sourceService from "../services/source.service.js";
|
||
import * as userService from "../services/user.service.js";
|
||
import { ENTRY_TYPES } from "../services/entry.service.js";
|
||
export const pagesRouter = Router();
|
||
|
||
function csv(v) {
|
||
return String(v || "")
|
||
.split(",")
|
||
.map((s) => s.trim())
|
||
.filter(Boolean);
|
||
}
|
||
|
||
pagesRouter.get("/healthz", (_req, res) => {
|
||
res.json({ ok: true, uptime: process.uptime() });
|
||
});
|
||
|
||
pagesRouter.get("/", async (_req, res, next) => {
|
||
try {
|
||
const [stats, recent, hero] = await Promise.all([
|
||
searchService.stats(),
|
||
entryService.listRecent(4),
|
||
(async () => {
|
||
const rand = await searchService.randomActive();
|
||
return rand ? entryService.getById(rand.id) : null;
|
||
})(),
|
||
]);
|
||
res.render("index", {
|
||
title: "Awesome Index · 把网上牛逼的东西收进一个库",
|
||
stats,
|
||
recent,
|
||
hero,
|
||
});
|
||
} catch (e) {
|
||
next(e);
|
||
}
|
||
});
|
||
|
||
pagesRouter.get("/entries", async (req, res, next) => {
|
||
try {
|
||
const filters = {
|
||
q: req.query.q ?? "",
|
||
types: csv(req.query.type),
|
||
tagIds: csv(req.query.tags).map(Number).filter(Number.isFinite),
|
||
sort: req.query.sort ?? "updated",
|
||
page: Math.max(1, Number(req.query.page) || 1),
|
||
status: "active",
|
||
};
|
||
const [result, tagTree, typeCounts] = await Promise.all([
|
||
searchService.searchEntries({ ...filters, perPage: 10 }),
|
||
tagService.attachCounts(await tagService.buildTree()),
|
||
searchService.typeCounts(),
|
||
]);
|
||
const totalPages = Math.max(1, Math.ceil(result.total / result.perPage));
|
||
res.render("list", {
|
||
title: `搜索${filters.q ? `:${filters.q}` : "全部"} · Awesome Index`,
|
||
result,
|
||
filters,
|
||
totalPages,
|
||
types: ENTRY_TYPES,
|
||
tagTree,
|
||
typeCounts,
|
||
});
|
||
} catch (e) {
|
||
next(e);
|
||
}
|
||
});
|
||
|
||
pagesRouter.get("/entries/:slug", async (req, res, next) => {
|
||
try {
|
||
const entry = await entryService.getBySlug(req.params.slug);
|
||
if (!entry || entry.status === "greyed") {
|
||
return res.status(404).send("条目不存在或已下架");
|
||
}
|
||
const comments = await commentService.listForEntry(entry.id);
|
||
const machine = {
|
||
id: entry.slug,
|
||
type: entry.type,
|
||
name: entry.title,
|
||
summary: entry.summary,
|
||
description: entry.description_md,
|
||
url: entry.url,
|
||
tags: entry.tags,
|
||
license: entry.license,
|
||
stars: Number(entry.stars),
|
||
updated_at: entry.updated_at,
|
||
status: entry.status,
|
||
};
|
||
res.render("detail", {
|
||
title: `${entry.title} · Awesome Index`,
|
||
entry,
|
||
comments,
|
||
machineJson: JSON.stringify(machine, null, 2),
|
||
});
|
||
} catch (e) {
|
||
next(e);
|
||
}
|
||
});
|
||
|
||
pagesRouter.get("/random", async (_req, res, next) => {
|
||
try {
|
||
const entry = await searchService.randomActive();
|
||
if (!entry) return res.redirect("/entries");
|
||
res.redirect(`/entries/${entry.slug}`);
|
||
} catch (e) {
|
||
next(e);
|
||
}
|
||
});
|
||
|
||
pagesRouter.get("/login", (_req, res) => {
|
||
res.render("login", { title: "管理台登录 · Awesome Index" });
|
||
});
|
||
|
||
pagesRouter.get("/admin", async (req, res, next) => {
|
||
try {
|
||
if (!req.session.userId) return res.redirect("/login");
|
||
const isAdmin = req.session.role === "admin";
|
||
const [sources, users, comments, entries, tagTreeAdmin] = await Promise.all([
|
||
sourceService.listSources(),
|
||
isAdmin ? userService.listUsers() : Promise.resolve([]),
|
||
commentService.listAll({}),
|
||
searchService.searchEntries({ status: "all", perPage: 50 }),
|
||
tagService.attachCounts(await tagService.buildTree()),
|
||
]);
|
||
res.render("admin", {
|
||
title: "管理台 · Awesome Index",
|
||
user: { username: req.session.username, role: req.session.role },
|
||
sources,
|
||
users,
|
||
comments,
|
||
entries: entries.items,
|
||
isAdmin,
|
||
tagTreeAdmin,
|
||
});
|
||
} catch (e) {
|
||
next(e);
|
||
}
|
||
});
|
||
|
||
pagesRouter.get("/admin/entries/:id/edit", async (req, res, next) => {
|
||
try {
|
||
if (!req.session.userId) return res.redirect("/login");
|
||
const entry = await entryService.getById(Number(req.params.id));
|
||
if (!entry) throw Object.assign(new Error("条目不存在"), { status: 404 });
|
||
const tagTreeAdmin = await tagService.buildTree();
|
||
res.render("admin-entry-edit", {
|
||
title: `编辑 · ${entry.title} · Awesome Index`,
|
||
user: { username: req.session.username, role: req.session.role },
|
||
isAdmin: req.session.role === "admin",
|
||
entry,
|
||
tagTreeAdmin,
|
||
types: ENTRY_TYPES,
|
||
});
|
||
} catch (e) {
|
||
next(e);
|
||
}
|
||
});
|
||
|
||
pagesRouter.post("/admin/entries/:id/edit", async (req, res, next) => {
|
||
try {
|
||
if (!req.session.userId) return res.redirect("/login");
|
||
const id = Number(req.params.id);
|
||
const tags = Array.isArray(req.body.tags) ? req.body.tags : req.body.tags ? [req.body.tags] : [];
|
||
const newTags = String(req.body.new_tags || "")
|
||
.split(",")
|
||
.map((s) => s.trim())
|
||
.filter(Boolean);
|
||
const patch = {
|
||
title: String(req.body.title || "").trim(),
|
||
type: req.body.type,
|
||
summary: String(req.body.summary || "").trim(),
|
||
description_md: String(req.body.description_md || ""),
|
||
url: String(req.body.url || "").trim(),
|
||
license: String(req.body.license || "").trim(),
|
||
stars: Number(req.body.stars) || 0,
|
||
tags: [...new Set([...tags, ...newTags])],
|
||
};
|
||
await entryService.updateEntry(id, patch, { isHumanEditor: true });
|
||
const status = req.body.status;
|
||
if (status && entryService.STATUSES.includes(status)) {
|
||
await entryService.setStatusDirect(id, status);
|
||
}
|
||
res.redirect("/admin#sec-content");
|
||
} catch (e) {
|
||
next(e);
|
||
}
|
||
});
|