Add Greatest of All Time manga dashboard

This commit is contained in:
Codex committed 2026-10-09 10:10:34 +02:00
1 parent c8dd46d0fb
commit 5df94db9a3
16 files changed
+2717 -1

No files matched your search

+36 -1
View File
@@ -14,6 +14,8 @@
const http = require('http'), fs = require('fs'), path = require('path'), crypto = require('crypto');
const { scanLibrary, scanChangedLibrary, libraryStamps, snapshotIndex, restoreIndex } = require('./library');
const { Catalog, CatalogError, collectionViews, TYPES, ROLES } = require('./catalog');
const { RATINGS: GREATEST_RATINGS, loadCatalog: loadGreatestCatalog, findOwned: findGreatestOwned,
countsFor: greatestCounts, scoreFor: greatestScore } = require('./greatest');
const { secret } = require('./runtime/config');
const {
applyPulse, applyVisit, chapterInsight, eligiblePages, emptyRecord, libraryInsight, mangaInsight, publicSummary,
@@ -40,8 +42,10 @@ const MIME = {
// ---------- storage: one JSON file, written atomically ----------
fs.mkdirSync(DATA, { recursive: true });
const catalog = new Catalog(DATA);
const greatestCatalog = loadGreatestCatalog();
const greatestById = new Map(greatestCatalog.entries.map(entry => [entry.id, entry]));
const DB_FILE = path.join(DATA, 'db.json'), CACHE_FILE = path.join(DATA, 'scan-cache.json'), INDEX_FILE = path.join(DATA, 'library-index.json');
const db = { users: {}, sessions: {}, progress: {}, reading: {}, follows: {}, notes: {}, votes: {}, known: {} };
const db = { users: {}, sessions: {}, progress: {}, reading: {}, follows: {}, notes: {}, votes: {}, greatestRatings: {}, known: {} };
// A corrupt db.json stops the server instead of silently starting empty and overwriting it.
if (fs.existsSync(DB_FILE)) Object.assign(db, JSON.parse(fs.readFileSync(DB_FILE, 'utf8')));
function writeAtomic(file, text) { fs.writeFileSync(file + '.tmp', text); fs.renameSync(file + '.tmp', file); }
@@ -364,6 +368,36 @@ const routes = {
};
},
'GET /api/greatest': ({ u }) => ({
title: greatestCatalog.title, updatedAt: greatestCatalog.updatedAt, periods: greatestCatalog.periods,
ratings: GREATEST_RATINGS,
entries: greatestCatalog.entries.map(entry => {
const owned = findGreatestOwned(entry, lib.mangas);
const counts = greatestCounts(entry.id, db.greatestRatings || {}, db.users);
return {
...entry,
cover: owned?.cover ? url(owned.cover) : entry.cover,
chapters: owned ? owned.chapters.length : entry.chapters,
status: owned?.meta?.status || entry.status,
library: owned ? { id: owned.id, chapters: owned.chapters.length } : null,
myRating: db.greatestRatings?.[u.name]?.[entry.id] || null,
votes: counts, score: greatestScore(counts),
};
}),
}),
'POST /api/greatest/rating': ({ u, b }) => {
const id = String(b.id || '');
if (!greatestById.has(id)) bad(404, 'Catalog entry not found');
const rating = b.rating === null || b.rating === '' ? null : String(b.rating || '');
if (rating !== null && !GREATEST_RATINGS.includes(rating)) bad(400, 'Unknown rating');
const all = (db.greatestRatings = db.greatestRatings || {});
const mine = (all[u.name] = all[u.name] || {});
if (rating === null) delete mine[id]; else mine[id] = rating;
save();
const votes = greatestCounts(id, all, db.users);
return { id, myRating: rating, votes, score: greatestScore(votes) };
},
'GET /api/manga': ({ u, q }) => {
const m = mangaOf(q.get('m')), prog = progressOf(u, m), showStory = settingsOf(u).readingStory;
const records = mangaReadingOf(u, m);
@@ -545,6 +579,7 @@ const routes = {
const name = String(b.name || '');
if (name === u.name) bad(400, "You can't delete your own account.");
delete db.users[name]; delete db.progress[name]; delete db.reading[name]; delete db.follows[name]; delete db.notes[name];
if (db.greatestRatings) delete db.greatestRatings[name];
for (const [k, s] of Object.entries(db.sessions)) if (s.u === name) delete db.sessions[k];
save();
return { ok: true };