feat: extracts some repeated logic out into lib/ files

This commit is contained in:
Lewis Wynne 2026-01-31 22:10:03 +00:00
parent 38653f2aa1
commit 99c1539aad
22 changed files with 200 additions and 336 deletions

19
www/src/lib/api.ts Normal file
View file

@ -0,0 +1,19 @@
import { isAdmin } from './auth';
export function jsonResponse(data: unknown, status = 200): Response {
return new Response(JSON.stringify(data), {
status,
headers: { 'Content-Type': 'application/json' },
});
}
export function errorResponse(message: string, status: number): Response {
return jsonResponse({ error: message }, status);
}
export function requireAdmin(session: { user?: { id?: string } } | null): Response | null {
if (!session?.user?.id || !isAdmin(session.user.id)) {
return errorResponse('Unauthorized', 403);
}
return null;
}

15
www/src/lib/format.ts Normal file
View file

@ -0,0 +1,15 @@
export function formatDate(date: Date): string {
const d = String(date.getDate()).padStart(2, '0');
const m = String(date.getMonth() + 1).padStart(2, '0');
const y = String(date.getFullYear()).slice(-2);
return `${d}/${m}/${y}`;
}
export function extractDomain(url: string): string {
try {
const parsed = new URL(url);
return parsed.hostname.replace(/^www\./, '');
} catch {
return url;
}
}

35
www/src/lib/posts.ts Normal file
View file

@ -0,0 +1,35 @@
import type { CollectionEntry } from 'astro:content';
type Post = CollectionEntry<'md'>;
export function sortPosts(posts: Post[]): Post[] {
return posts.slice().sort((a, b) => {
if (a.data.pinned && !b.data.pinned) return -1;
if (!a.data.pinned && b.data.pinned) return 1;
return b.data.date.getTime() - a.data.date.getTime();
});
}
export function organizePostsByCategory(posts: Post[]): {
grouped: Record<string, Post[]>;
categories: string[];
} {
const grouped = posts.reduce((acc, post) => {
const category = post.data.category ?? 'md';
if (!acc[category]) acc[category] = [];
acc[category].push(post);
return acc;
}, {} as Record<string, Post[]>);
const categories = Object.keys(grouped).sort((a, b) => {
if (a === 'md') return -1;
if (b === 'md') return 1;
return a.localeCompare(b);
});
for (const category of categories) {
grouped[category] = sortPosts(grouped[category]);
}
return { grouped, categories };
}

53
www/src/lib/txt.ts Normal file
View file

@ -0,0 +1,53 @@
import fs from 'node:fs';
import path from 'node:path';
import yaml from 'js-yaml';
import { getGitDate } from '../utils';
export interface TxtFile {
name: string;
date: Date;
pinned: boolean;
}
export interface TxtConfig {
pinned?: string[];
}
export function getTxtDir(): string {
return path.join(process.cwd(), 'public/txt');
}
export function loadTxtConfig(): TxtConfig {
const configPath = path.join(getTxtDir(), 'config.yaml');
return fs.existsSync(configPath)
? yaml.load(fs.readFileSync(configPath, 'utf8')) as TxtConfig
: {};
}
export function getTxtFiles(): TxtFile[] {
const txtDir = getTxtDir();
if (!fs.existsSync(txtDir)) return [];
const config = loadTxtConfig();
const pinnedSet = new Set(config.pinned || []);
return fs.readdirSync(txtDir)
.filter(file => file.endsWith('.txt'))
.map(name => ({
name,
date: getGitDate(path.join(txtDir, name)),
pinned: pinnedSet.has(name),
}))
.sort((a, b) => {
if (a.pinned && !b.pinned) return -1;
if (!a.pinned && b.pinned) return 1;
return b.date.getTime() - a.date.getTime();
});
}
export function getTxtFileNames(): string[] {
const txtDir = getTxtDir();
if (!fs.existsSync(txtDir)) return [];
return fs.readdirSync(txtDir).filter(file => file.endsWith('.txt'));
}