From 7f01bec7e675788247ab25e6bbf6f5ac3a27aa38 Mon Sep 17 00:00:00 2001 From: lew Date: Sat, 31 Jan 2026 22:41:24 +0000 Subject: [PATCH] refactor: --- www/src/lib/auth.ts | 26 +++++++++++++++++++ www/src/lib/format.ts | 24 +++++++++++++++++ www/src/lib/{posts.ts => md.ts} | 2 +- www/src/lib/txt.ts | 11 +++----- www/src/pages/admin.astro | 20 +++------------ www/src/pages/bookmarks/index.astro | 4 +-- www/src/pages/draft/[slug].astro | 22 ++++------------ www/src/pages/draft/index.astro | 26 +++++-------------- www/src/pages/feed.xml.ts | 2 +- www/src/pages/guestbook/index.astro | 30 ++-------------------- www/src/pages/index.astro | 40 +++++------------------------ www/src/pages/md/[slug].astro | 2 +- www/src/pages/md/index.astro | 6 ++--- www/src/pages/random.ts | 2 +- www/src/pages/sitemap.txt.ts | 2 +- www/src/pages/txt/index.astro | 4 +-- www/src/scripts/guestbook-sign.ts | 30 ++++++++++++++++++++++ 17 files changed, 121 insertions(+), 132 deletions(-) rename www/src/lib/{posts.ts => md.ts} (95%) create mode 100644 www/src/scripts/guestbook-sign.ts diff --git a/www/src/lib/auth.ts b/www/src/lib/auth.ts index e2fe4e3..28b0d8b 100644 --- a/www/src/lib/auth.ts +++ b/www/src/lib/auth.ts @@ -1,3 +1,29 @@ +import { getSession } from 'auth-astro/server'; + +type Session = { user?: { id?: string; name?: string | null } }; + export function isAdmin(userId: string | undefined): boolean { return userId === import.meta.env.ADMIN_GITHUB_ID; } + +export async function requireAdminSession(request: Request): Promise< + | { session: Session; error: null } + | { session: null; error: Response | null } +> { + let session: Session | null; + try { + session = await getSession(request); + } catch { + return { session: null, error: new Response('Auth not configured', { status: 500 }) }; + } + + if (!session) { + return { session: null, error: null }; + } + + if (!isAdmin(session.user?.id)) { + return { session: null, error: new Response('Forbidden', { status: 403 }) }; + } + + return { session, error: null }; +} diff --git a/www/src/lib/format.ts b/www/src/lib/format.ts index 2ed9219..2fbd80c 100644 --- a/www/src/lib/format.ts +++ b/www/src/lib/format.ts @@ -13,3 +13,27 @@ export function extractDomain(url: string): string { return url; } } + +export function formatListItem( + date: Date, + url: string, + title: string, + options?: { pinned?: boolean; suffix?: string } +): string { + const pinnedBadge = options?.pinned ? ' [pinned]' : ''; + const suffix = options?.suffix ? ` ${options.suffix}` : ''; + return `${formatDate(date)} ${title}${pinnedBadge}${suffix}`; +} + +interface Sortable { + date: Date; + pinned?: boolean; +} + +export function sortByPinnedThenDate(items: T[]): T[] { + return items.slice().sort((a, b) => { + if (a.pinned && !b.pinned) return -1; + if (!a.pinned && b.pinned) return 1; + return b.date.getTime() - a.date.getTime(); + }); +} diff --git a/www/src/lib/posts.ts b/www/src/lib/md.ts similarity index 95% rename from www/src/lib/posts.ts rename to www/src/lib/md.ts index d3fc859..7026087 100644 --- a/www/src/lib/posts.ts +++ b/www/src/lib/md.ts @@ -7,7 +7,7 @@ export function getSlug(postId: string): string { return parts[parts.length - 1]; } -export function sortPosts(posts: Post[]): Post[] { +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; diff --git a/www/src/lib/txt.ts b/www/src/lib/txt.ts index 233d24a..802ba33 100644 --- a/www/src/lib/txt.ts +++ b/www/src/lib/txt.ts @@ -2,6 +2,7 @@ import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import yaml from 'js-yaml'; +import { sortByPinnedThenDate } from './format'; function getGitDate(filePath: string): Date { try { @@ -40,18 +41,14 @@ export function getTxtFiles(): TxtFile[] { const config = loadTxtConfig(); const pinnedSet = new Set(config.pinned || []); - return fs.readdirSync(txtDir) + const files = 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(); - }); + })); + return sortByPinnedThenDate(files); } export function getTxtFileNames(): string[] { diff --git a/www/src/pages/admin.astro b/www/src/pages/admin.astro index d634e79..f540c6c 100644 --- a/www/src/pages/admin.astro +++ b/www/src/pages/admin.astro @@ -1,26 +1,14 @@ --- export const prerender = false; -import { getSession } from 'auth-astro/server'; import { getPendingEntries, type GuestbookEntry } from '../lib/db'; -import { isAdmin } from '../lib/auth'; +import { requireAdminSession } from '../lib/auth'; import Layout from '../layouts/Layout.astro'; import { formatDate } from '../lib/format'; -let session; -try { - session = await getSession(Astro.request); -} catch { - return new Response('Auth not configured', { status: 500 }); -} - -if (!session) { - return Astro.redirect('/api/auth/signin'); -} - -if (!isAdmin(session.user?.id)) { - return new Response('Forbidden', { status: 403 }); -} +const { session, error } = await requireAdminSession(Astro.request); +if (error) return error; +if (!session) return Astro.redirect('/api/auth/signin'); let entries: GuestbookEntry[] = []; try { diff --git a/www/src/pages/bookmarks/index.astro b/www/src/pages/bookmarks/index.astro index 4c37d9f..73f8438 100644 --- a/www/src/pages/bookmarks/index.astro +++ b/www/src/pages/bookmarks/index.astro @@ -1,7 +1,7 @@ --- import { getCollection } from 'astro:content'; import Layout from '../../layouts/Layout.astro'; -import { formatDate, extractDomain } from '../../lib/format'; +import { formatListItem, extractDomain } from '../../lib/format'; const bookmarksCollection = await getCollection('bookmarks'); const bookmarks = bookmarksCollection @@ -11,6 +11,6 @@ const bookmarks = bookmarksCollection
bookmarks -
 `${formatDate(b.data.date)}    ${b.data.title} (${extractDomain(b.data.url)})`).join('\n')} />
+
 formatListItem(b.data.date, b.data.url, b.data.title, { suffix: `(${extractDomain(b.data.url)})` })).join('\n')} />
 
diff --git a/www/src/pages/draft/[slug].astro b/www/src/pages/draft/[slug].astro index 78c919f..62302af 100644 --- a/www/src/pages/draft/[slug].astro +++ b/www/src/pages/draft/[slug].astro @@ -1,27 +1,15 @@ --- export const prerender = false; -import { getSession } from 'auth-astro/server'; import { getCollection, render } from 'astro:content'; -import { isAdmin } from '../../lib/auth'; +import { requireAdminSession } from '../../lib/auth'; import Layout from '../../layouts/Layout.astro'; import { formatDate } from '../../lib/format'; -import { getSlug } from '../../lib/posts'; +import { getSlug } from '../../lib/md'; -let session; -try { - session = await getSession(Astro.request); -} catch { - return new Response('Auth not configured', { status: 500 }); -} - -if (!session) { - return Astro.redirect('/api/auth/signin'); -} - -if (!isAdmin(session.user?.id)) { - return new Response('Forbidden', { status: 403 }); -} +const { session, error } = await requireAdminSession(Astro.request); +if (error) return error; +if (!session) return Astro.redirect('/api/auth/signin'); const slug = Astro.params.slug; const posts = await getCollection('md', ({ data }) => data.draft === true); diff --git a/www/src/pages/draft/index.astro b/www/src/pages/draft/index.astro index 4fb6cf6..eb8ca14 100644 --- a/www/src/pages/draft/index.astro +++ b/www/src/pages/draft/index.astro @@ -1,27 +1,15 @@ --- export const prerender = false; -import { getSession } from 'auth-astro/server'; import { getCollection } from 'astro:content'; -import { isAdmin } from '../../lib/auth'; +import { requireAdminSession } from '../../lib/auth'; import Layout from '../../layouts/Layout.astro'; -import { formatDate } from '../../lib/format'; -import { organizePostsByCategory, getSlug } from '../../lib/posts'; +import { formatListItem } from '../../lib/format'; +import { organizePostsByCategory, getSlug } from '../../lib/md'; -let session; -try { - session = await getSession(Astro.request); -} catch { - return new Response('Auth not configured', { status: 500 }); -} - -if (!session) { - return Astro.redirect('/api/auth/signin'); -} - -if (!isAdmin(session.user?.id)) { - return new Response('Forbidden', { status: 403 }); -} +const { session, error } = await requireAdminSession(Astro.request); +if (error) return error; +if (!session) return Astro.redirect('/api/auth/signin'); const posts = await getCollection('md', ({ data }) => data.draft === true); const { grouped, categories: sortedCategories } = organizePostsByCategory(posts); @@ -36,7 +24,7 @@ const { grouped, categories: sortedCategories } = organizePostsByCategory(posts) sortedCategories.map(category => (
{category} -
 `${formatDate(post.data.date)}    ${post.data.title}${post.data.pinned ? ' [pinned]' : ''}`).join('\n')} />
+      
 formatListItem(post.data.date, `/draft/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })).join('\n')} />
     
)) )} diff --git a/www/src/pages/feed.xml.ts b/www/src/pages/feed.xml.ts index 606e013..aa58a25 100644 --- a/www/src/pages/feed.xml.ts +++ b/www/src/pages/feed.xml.ts @@ -1,7 +1,7 @@ import rss from '@astrojs/rss'; import { getCollection } from 'astro:content'; import type { APIContext } from 'astro'; -import { getSlug } from '../lib/posts'; +import { getSlug } from '../lib/md'; import { getTxtFiles } from '../lib/txt'; export async function GET(context: APIContext) { diff --git a/www/src/pages/guestbook/index.astro b/www/src/pages/guestbook/index.astro index 5e33851..92ecdbf 100644 --- a/www/src/pages/guestbook/index.astro +++ b/www/src/pages/guestbook/index.astro @@ -29,33 +29,7 @@ try { diff --git a/www/src/pages/index.astro b/www/src/pages/index.astro index 03fc3de..2ae6848 100644 --- a/www/src/pages/index.astro +++ b/www/src/pages/index.astro @@ -2,8 +2,8 @@ import { getCollection } from 'astro:content'; import Layout from '../layouts/Layout.astro'; import { getApprovedEntries, type GuestbookEntry } from '../lib/db'; -import { formatDate, extractDomain } from '../lib/format'; -import { organizePostsByCategory, getSlug } from '../lib/posts'; +import { formatDate, extractDomain, formatListItem } from '../lib/format'; +import { organizePostsByCategory, getSlug } from '../lib/md'; import { getTxtFiles } from '../lib/txt'; const posts = await getCollection('md', ({ data }) => data.draft !== true); @@ -30,7 +30,7 @@ try {
{category}
 `${formatDate(post.data.date)}    ${post.data.title}${post.data.pinned ? ' [pinned]' : ''}`),
+  ...categoryPosts.slice(0, 10).map(post => formatListItem(post.data.date, `/md/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })),
   ...(categoryPosts.length > 10 ? [`+${categoryPosts.length - 10} more`] : [])
 ].join('\n')} />
 
@@ -40,7 +40,7 @@ try {
txt
 `${formatDate(f.date)}    ${f.name}${f.pinned ? ' [pinned]' : ''}`),
+  ...txtFiles.slice(0, 10).map(f => formatListItem(f.date, `/txt/${f.name}`, f.name, { pinned: f.pinned })),
   ...(txtFiles.length > 10 ? [`+${txtFiles.length - 10} more`] : [])
 ].join('\n')} />
 
@@ -48,7 +48,7 @@ try {
bookmarks
 `${formatDate(b.data.date)}    ${b.data.title} (${extractDomain(b.data.url)})`),
+  ...bookmarks.slice(0, 10).map(b => formatListItem(b.data.date, b.data.url, b.data.title, { suffix: `(${extractDomain(b.data.url)})` })),
   ...(bookmarks.length > 10 ? [`+${bookmarks.length - 10} more`] : [])
 ].join('\n')} />
 
@@ -70,33 +70,7 @@ try { diff --git a/www/src/pages/md/[slug].astro b/www/src/pages/md/[slug].astro index 615a11d..b91ceb4 100644 --- a/www/src/pages/md/[slug].astro +++ b/www/src/pages/md/[slug].astro @@ -2,7 +2,7 @@ import { getCollection, render } from 'astro:content'; import Layout from '../../layouts/Layout.astro'; import { formatDate } from '../../lib/format'; -import { getSlug } from '../../lib/posts'; +import { getSlug } from '../../lib/md'; export async function getStaticPaths() { const posts = await getCollection('md', ({ data }) => data.draft !== true); diff --git a/www/src/pages/md/index.astro b/www/src/pages/md/index.astro index 9382df8..7e58533 100644 --- a/www/src/pages/md/index.astro +++ b/www/src/pages/md/index.astro @@ -1,8 +1,8 @@ --- import { getCollection } from 'astro:content'; import Layout from '../../layouts/Layout.astro'; -import { formatDate } from '../../lib/format'; -import { organizePostsByCategory, getSlug } from '../../lib/posts'; +import { formatListItem } from '../../lib/format'; +import { organizePostsByCategory, getSlug } from '../../lib/md'; const posts = await getCollection('md', ({ data }) => data.draft !== true); const { grouped, categories: sortedCategories } = organizePostsByCategory(posts); @@ -12,7 +12,7 @@ const { grouped, categories: sortedCategories } = organizePostsByCategory(posts) {sortedCategories.map(category => (
{category} -
 `${formatDate(post.data.date)}    ${post.data.title}${post.data.pinned ? ' [pinned]' : ''}`).join('\n')} />
+
 formatListItem(post.data.date, `/md/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })).join('\n')} />
 
))} diff --git a/www/src/pages/random.ts b/www/src/pages/random.ts index 24b036b..5effc90 100644 --- a/www/src/pages/random.ts +++ b/www/src/pages/random.ts @@ -1,6 +1,6 @@ import { getCollection } from 'astro:content'; import type { APIContext } from 'astro'; -import { getSlug } from '../lib/posts'; +import { getSlug } from '../lib/md'; import { getTxtFileNames } from '../lib/txt'; export const prerender = false; diff --git a/www/src/pages/sitemap.txt.ts b/www/src/pages/sitemap.txt.ts index c0857ba..ac8cfa8 100644 --- a/www/src/pages/sitemap.txt.ts +++ b/www/src/pages/sitemap.txt.ts @@ -1,6 +1,6 @@ import { getCollection } from 'astro:content'; import type { APIContext } from 'astro'; -import { getSlug } from '../lib/posts'; +import { getSlug } from '../lib/md'; import { getTxtFileNames } from '../lib/txt'; const SUBDOMAINS = [ diff --git a/www/src/pages/txt/index.astro b/www/src/pages/txt/index.astro index 42d655c..fd98e29 100644 --- a/www/src/pages/txt/index.astro +++ b/www/src/pages/txt/index.astro @@ -1,6 +1,6 @@ --- import Layout from '../../layouts/Layout.astro'; -import { formatDate } from '../../lib/format'; +import { formatListItem } from '../../lib/format'; import { getTxtFiles } from '../../lib/txt'; const txtFiles = getTxtFiles(); @@ -9,6 +9,6 @@ const txtFiles = getTxtFiles();
txt -
 `${formatDate(f.date)}    ${f.name}${f.pinned ? ' [pinned]' : ''}`).join('\n')} />
+
 formatListItem(f.date, `/txt/${f.name}`, f.name, { pinned: f.pinned })).join('\n')} />
 
diff --git a/www/src/scripts/guestbook-sign.ts b/www/src/scripts/guestbook-sign.ts new file mode 100644 index 0000000..b9cdf47 --- /dev/null +++ b/www/src/scripts/guestbook-sign.ts @@ -0,0 +1,30 @@ +export function initGuestbookSign() { + document.getElementById('sign-guestbook')?.addEventListener('click', async (e) => { + e.preventDefault(); + const status = document.getElementById('guestbook-status')!; + + const name = prompt('name:'); + if (!name) return; + + const message = prompt('message:'); + if (!message) return; + + const url = prompt('url (optional):'); + + try { + const res = await fetch('/api/guestbook', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, message, url: url || null }), + }); + + if (res.ok) { + status.textContent = ' thanks! pending approval.'; + } else { + status.textContent = ' error'; + } + } catch { + status.textContent = ' failed'; + } + }); +}