feat: stats page
This commit is contained in:
parent
4095158e7b
commit
0fa86f4a81
3 changed files with 113 additions and 0 deletions
11
apps/blog/public/txt/stats.txt
Normal file
11
apps/blog/public/txt/stats.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
this site consists of [WORDS] words across [PAGES] pages
|
||||
|
||||
there are [POSTS] blog posts
|
||||
[TXT] txt files
|
||||
and [BOOKMARKS] bookmarks
|
||||
|
||||
[GUESTBOOK] people have signed the guestbook
|
||||
|
||||
this file was generated automatically on deploy
|
||||
cheers,
|
||||
lewis
|
||||
85
apps/blog/scripts/generate-stats.js
Normal file
85
apps/blog/scripts/generate-stats.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import yaml from 'js-yaml';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.join(__dirname, '..');
|
||||
|
||||
function countWords(text) {
|
||||
return text.split(/\s+/).filter(w => w.length > 0).length;
|
||||
}
|
||||
|
||||
// Count blog posts and their words
|
||||
const postsDir = path.join(root, 'src/content/posts');
|
||||
const posts = fs.existsSync(postsDir)
|
||||
? fs.readdirSync(postsDir).filter(f => f.endsWith('.md'))
|
||||
: [];
|
||||
let postWords = 0;
|
||||
for (const post of posts) {
|
||||
const content = fs.readFileSync(path.join(postsDir, post), 'utf-8');
|
||||
// Remove frontmatter
|
||||
const body = content.replace(/^---[\s\S]*?---/, '');
|
||||
postWords += countWords(body);
|
||||
}
|
||||
|
||||
// Count txt files and their words (excluding stats.txt which we're generating)
|
||||
const txtDir = path.join(root, 'public/txt');
|
||||
const txtFiles = fs.existsSync(txtDir)
|
||||
? fs.readdirSync(txtDir).filter(f => f.endsWith('.txt') && f !== 'stats.txt')
|
||||
: [];
|
||||
let txtWords = 0;
|
||||
for (const txt of txtFiles) {
|
||||
const content = fs.readFileSync(path.join(txtDir, txt), 'utf-8');
|
||||
txtWords += countWords(content);
|
||||
}
|
||||
|
||||
// Count bookmarks
|
||||
const bookmarksFile = path.join(root, 'src/data/bookmarks.yaml');
|
||||
const bookmarks = fs.existsSync(bookmarksFile)
|
||||
? yaml.load(fs.readFileSync(bookmarksFile, 'utf-8')) || []
|
||||
: [];
|
||||
|
||||
// Guestbook count - read from built JSON file
|
||||
const guestbookJsonFile = path.join(root, '.vercel/output/static/guestbook-count.json');
|
||||
let guestbookCount = 0;
|
||||
if (fs.existsSync(guestbookJsonFile)) {
|
||||
const data = JSON.parse(fs.readFileSync(guestbookJsonFile, 'utf-8'));
|
||||
guestbookCount = data.count;
|
||||
}
|
||||
|
||||
// Calculate totals (excluding stats.txt words for now, we'll add them after generating)
|
||||
const totalPages = 1 + 1 + posts.length + 1 + txtFiles.length + 1 + 1; // home, blog index, posts, txt index, txts, bookmarks, guestbook
|
||||
|
||||
// Read template from public/txt/stats.txt and replace placeholders
|
||||
const template = fs.readFileSync(path.join(root, 'public/txt/stats.txt'), 'utf-8');
|
||||
|
||||
// First pass: generate stats without stats.txt word count
|
||||
let stats = template
|
||||
.replace('[PAGES]', totalPages.toString())
|
||||
.replace('[POSTS]', posts.length.toString())
|
||||
.replace('[TXT]', txtFiles.length.toString())
|
||||
.replace('[BOOKMARKS]', bookmarks.length.toString())
|
||||
.replace('[GUESTBOOK]', guestbookCount.toString());
|
||||
|
||||
// Count words in the stats file itself (before adding [WORDS])
|
||||
const statsWords = countWords(stats.replace('[WORDS]', '0'));
|
||||
const totalWords = postWords + txtWords + statsWords;
|
||||
|
||||
// Final pass: replace [WORDS] with actual total
|
||||
stats = stats.replace('[WORDS]', totalWords.toString());
|
||||
|
||||
// Write to Vercel output
|
||||
const outputDir = path.join(root, '.vercel/output/static/txt');
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(path.join(outputDir, 'stats.txt'), stats);
|
||||
|
||||
console.log('Generated stats.txt');
|
||||
console.log(` Words: ${totalWords}`);
|
||||
console.log(` Pages: ${totalPages}`);
|
||||
console.log(` Posts: ${posts.length}`);
|
||||
console.log(` Txt files: ${txtFiles.length}`);
|
||||
console.log(` Bookmarks: ${bookmarks.length}`);
|
||||
console.log(` Guestbook: ${guestbookCount}`);
|
||||
17
apps/blog/src/pages/guestbook-count.json.ts
Normal file
17
apps/blog/src/pages/guestbook-count.json.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import type { APIRoute } from 'astro';
|
||||
import { getApprovedEntries } from '../lib/db';
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
let count = 0;
|
||||
try {
|
||||
const entries = await getApprovedEntries();
|
||||
count = entries.length;
|
||||
} catch {
|
||||
// DB not available
|
||||
}
|
||||
return new Response(JSON.stringify({ count }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue