my website at ewancroft.uk
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(blog): implement platform prioritisation and smarter redirects

- Reworked /blog root route to dynamically redirect based on configuration:
- Priority 1: Leaflet base path
- Priority 2: Leaflet publication page
- Fallback: WhiteWind (if enabled)
- Updated [rkey] route to prioritise Leaflet detection and only check WhiteWind when enabled
- Improved 404 responses with contextual notes and platform hints
- Enhanced RSS feed logic:
- Generates WhiteWind feed only when enabled
- Redirects to Leaflet RSS otherwise
- Added PUBLIC_ENABLE_WHITEWIND support across blog routes
- Improved logging and error handling for missing publications and records

+111 -56
+59 -11
src/routes/blog/+server.ts
··· 1 1 import type { RequestHandler } from '@sveltejs/kit'; 2 + import { 3 + PUBLIC_ATPROTO_DID, 4 + PUBLIC_LEAFLET_BASE_PATH, 5 + PUBLIC_LEAFLET_BLOG_PUBLICATION, 6 + PUBLIC_ENABLE_WHITEWIND 7 + } from '$env/static/public'; 8 + import { fetchLeafletPublications } from '$lib/services/atproto'; 2 9 3 10 /** 4 11 * Blog root redirect handler 5 12 * 6 - * This redirects /blog to a helpful page explaining the blog setup. 13 + * Redirects /blog to the appropriate blog platform: 14 + * - Priority 1: Leaflet blog (if PUBLIC_LEAFLET_BASE_PATH is configured) 15 + * - Priority 2: Leaflet publication page (if PUBLIC_LEAFLET_BLOG_PUBLICATION is set) 16 + * - Fallback: WhiteWind blog page (if PUBLIC_ENABLE_WHITEWIND is true) 17 + * 7 18 * Individual posts are handled by the [rkey] route which detects 8 - * whether the post is from WhiteWind or Leaflet. 19 + * whether the post is from Leaflet or WhiteWind (if enabled). 9 20 */ 10 - export const GET: RequestHandler = ({ url }) => { 21 + export const GET: RequestHandler = async ({ url }) => { 11 22 // If there's a path after /blog, let it fall through to other routes 12 23 const blogPath = url.pathname.replace(/^\/blog\/?/, ''); 13 24 ··· 21 32 }); 22 33 } 23 34 24 - // For /blog root, return a helpful message 35 + // For /blog root, redirect to the blog platform 25 36 if (!blogPath || blogPath === '') { 26 - return new Response( 27 - `Blog posts are distributed across multiple platforms: 37 + let redirectUrl: string | null = null; 28 38 29 - - WhiteWind: https://whtwnd.com 30 - - Leaflet: https://leaflet.pub 39 + // Priority 1: Use Leaflet base path if configured 40 + if (PUBLIC_LEAFLET_BASE_PATH) { 41 + redirectUrl = PUBLIC_LEAFLET_BASE_PATH; 42 + } 43 + // Priority 2: Use Leaflet publication page if configured 44 + else if (PUBLIC_LEAFLET_BLOG_PUBLICATION) { 45 + try { 46 + const { publications } = await fetchLeafletPublications(); 47 + const publication = publications.find(p => p.rkey === PUBLIC_LEAFLET_BLOG_PUBLICATION); 48 + 49 + if (publication?.basePath) { 50 + // Ensure basePath is a complete URL 51 + redirectUrl = publication.basePath.startsWith('http') 52 + ? publication.basePath 53 + : `https://${publication.basePath}`; 54 + } else { 55 + // Use Leaflet /lish format 56 + redirectUrl = `https://leaflet.pub/lish/${PUBLIC_ATPROTO_DID}/${PUBLIC_LEAFLET_BLOG_PUBLICATION}`; 57 + } 58 + } catch (error) { 59 + console.error('Error fetching Leaflet publication:', error); 60 + // Will check WhiteWind fallback below 61 + } 62 + } 63 + 64 + // Fallback: WhiteWind blog page (if enabled) 65 + if (!redirectUrl && PUBLIC_ENABLE_WHITEWIND === 'true') { 66 + redirectUrl = `https://whtwnd.com/${PUBLIC_ATPROTO_DID}`; 67 + } 31 68 32 - Individual post URLs like /blog/{rkey} will automatically redirect to the correct platform. 69 + // If we have a redirect URL, use it 70 + if (redirectUrl) { 71 + return new Response(null, { 72 + status: 301, 73 + headers: { 74 + Location: redirectUrl, 75 + 'Cache-Control': 'public, max-age=3600' 76 + } 77 + }); 78 + } 33 79 34 - For RSS feeds, visit: /blog/rss`, 80 + // No blog configured 81 + return new Response( 82 + 'Blog not configured. Please set PUBLIC_LEAFLET_BASE_PATH or PUBLIC_LEAFLET_BLOG_PUBLICATION in your environment variables.', 35 83 { 36 - status: 200, 84 + status: 404, 37 85 headers: { 38 86 'Content-Type': 'text/plain; charset=utf-8' 39 87 }
+44 -38
src/routes/blog/[rkey]/+server.ts
··· 3 3 PUBLIC_ATPROTO_DID, 4 4 PUBLIC_LEAFLET_BASE_PATH, 5 5 PUBLIC_LEAFLET_BLOG_PUBLICATION, 6 - PUBLIC_BLOG_FALLBACK_URL 6 + PUBLIC_BLOG_FALLBACK_URL, 7 + PUBLIC_ENABLE_WHITEWIND 7 8 } from '$env/static/public'; 8 9 import { withFallback } from '$lib/services/atproto'; 9 10 import { fetchLeafletPublications } from '$lib/services/atproto'; ··· 11 12 /** 12 13 * Smart blog post redirect handler 13 14 * 14 - * Automatically detects whether the post is from WhiteWind or Leaflet 15 + * Automatically detects whether the post is from Leaflet or WhiteWind (if enabled) 15 16 * and redirects to the appropriate URL. 16 17 * 17 - * WhiteWind: https://whtwnd.com/{DID}/{rkey} 18 - * Leaflet: {LEAFLET_BASE_PATH}/{rkey} or https://leaflet.pub/{DID}/{rkey} 18 + * Priority order: 19 + * 1. Leaflet: {LEAFLET_BASE_PATH}/{rkey} or https://leaflet.pub/{DID}/{rkey} 20 + * 2. WhiteWind: https://whtwnd.com/{DID}/{rkey} (only if PUBLIC_ENABLE_WHITEWIND is true) 19 21 * 20 22 * If detection fails, falls back to PUBLIC_BLOG_FALLBACK_URL or returns 404. 21 23 * ··· 28 30 rkey: string 29 31 ): Promise<{ platform: 'whitewind' | 'leaflet' | 'unknown'; url?: string }> { 30 32 try { 31 - // Check WhiteWind first using atproto services 32 - const whiteWindRecord = await withFallback( 33 - PUBLIC_ATPROTO_DID, 34 - async (agent) => { 35 - try { 36 - const response = await agent.com.atproto.repo.getRecord({ 37 - repo: PUBLIC_ATPROTO_DID, 38 - collection: 'com.whtwnd.blog.entry', 39 - rkey 40 - }); 41 - return response.data; 42 - } catch (err) { 43 - // Record not found 44 - return null; 45 - } 46 - }, 47 - true // Use PDS first for custom collections 48 - ); 49 - 50 - if (whiteWindRecord) { 51 - const value = whiteWindRecord.value as any; 52 - // Skip drafts and non-public posts 53 - if (!value?.isDraft && (!value?.visibility || value.visibility === 'public')) { 54 - return { 55 - platform: 'whitewind', 56 - url: `https://whtwnd.com/${PUBLIC_ATPROTO_DID}/${rkey}` 57 - }; 58 - } 59 - } 60 - 61 - // Check Leaflet using atproto services 33 + // Check Leaflet FIRST (prioritized) using atproto services 62 34 const leafletRecord = await withFallback( 63 35 PUBLIC_ATPROTO_DID, 64 36 async (agent) => { ··· 119 91 }; 120 92 } 121 93 94 + // Check WhiteWind as fallback (only if enabled) 95 + if (PUBLIC_ENABLE_WHITEWIND === 'true') { 96 + const whiteWindRecord = await withFallback( 97 + PUBLIC_ATPROTO_DID, 98 + async (agent) => { 99 + try { 100 + const response = await agent.com.atproto.repo.getRecord({ 101 + repo: PUBLIC_ATPROTO_DID, 102 + collection: 'com.whtwnd.blog.entry', 103 + rkey 104 + }); 105 + return response.data; 106 + } catch (err) { 107 + // Record not found 108 + return null; 109 + } 110 + }, 111 + true // Use PDS first for custom collections 112 + ); 113 + 114 + if (whiteWindRecord) { 115 + const value = whiteWindRecord.value as any; 116 + // Skip drafts and non-public posts 117 + if (!value?.isDraft && (!value?.visibility || value.visibility === 'public')) { 118 + return { 119 + platform: 'whitewind', 120 + url: `https://whtwnd.com/${PUBLIC_ATPROTO_DID}/${rkey}` 121 + }; 122 + } 123 + } 124 + } 125 + 122 126 return { platform: 'unknown' }; 123 127 } catch (error) { 124 128 console.error('Error detecting post platform:', error); ··· 158 162 const blogPublicationNote = PUBLIC_LEAFLET_BLOG_PUBLICATION 159 163 ? `\n\nNote: Only checking Leaflet publication: ${PUBLIC_LEAFLET_BLOG_PUBLICATION}` 160 164 : ''; 165 + const whiteWindNote = PUBLIC_ENABLE_WHITEWIND === 'true' 166 + ? '\n- WhiteWind: https://whtwnd.com' 167 + : ''; 161 168 162 169 return new Response( 163 170 `Blog post not found: ${rkey} 164 171 165 - This post could not be found on WhiteWind or Leaflet platforms.${blogPublicationNote} 172 + This post could not be found on Leaflet${PUBLIC_ENABLE_WHITEWIND === 'true' ? ' or WhiteWind' : ''} platform${PUBLIC_ENABLE_WHITEWIND === 'true' ? 's' : ''}.${blogPublicationNote} 166 173 167 174 Please check: 168 - - WhiteWind: https://whtwnd.com 169 - - Leaflet: https://leaflet.pub`, 175 + - Leaflet: https://leaflet.pub${whiteWindNote}`, 170 176 { 171 177 status: 404, 172 178 headers: {
+8 -7
src/routes/blog/rss/+server.ts
··· 5 5 PUBLIC_SITE_DESCRIPTION, 6 6 PUBLIC_SITE_URL, 7 7 PUBLIC_LEAFLET_BASE_PATH, 8 - PUBLIC_LEAFLET_BLOG_PUBLICATION 8 + PUBLIC_LEAFLET_BLOG_PUBLICATION, 9 + PUBLIC_ENABLE_WHITEWIND 9 10 } from '$env/static/public'; 10 11 import { fetchBlogPosts, fetchLeafletPublications } from '$lib/services/atproto'; 11 12 ··· 13 14 * RSS 2.0 feed for blog posts 14 15 * 15 16 * Strategy: 16 - * 1. If only Leaflet posts exist and there's a single publication, redirect to its RSS feed 17 - * 2. If WhiteWind posts exist, generate RSS with WhiteWind posts (linking to them) 18 - * 3. If mixed content, prioritize WhiteWind and generate RSS for those 17 + * 1. If WhiteWind is disabled or no WhiteWind posts exist, redirect to Leaflet RSS feed 18 + * 2. If WhiteWind is enabled and WhiteWind posts exist, generate RSS with WhiteWind posts 19 + * 3. If mixed content and WhiteWind is enabled, prioritize WhiteWind and generate RSS for those 19 20 */ 20 21 export const GET: RequestHandler = async () => { 21 22 try { ··· 25 26 const whiteWindPosts = posts.filter((p) => p.platform === 'WhiteWind'); 26 27 const leafletPosts = posts.filter((p) => p.platform === 'leaflet'); 27 28 28 - // If we have WhiteWind posts, generate RSS for them 29 - if (whiteWindPosts.length > 0) { 29 + // If WhiteWind is enabled and we have WhiteWind posts, generate RSS for them 30 + if (PUBLIC_ENABLE_WHITEWIND === 'true' && whiteWindPosts.length > 0) { 30 31 return generateWhiteWindRSS(whiteWindPosts); 31 32 } 32 33 33 - // If only Leaflet posts, redirect to Leaflet RSS feed 34 + // If WhiteWind is disabled or only Leaflet posts exist, redirect to Leaflet RSS feed 34 35 if (leafletPosts.length > 0) { 35 36 return await redirectToLeafletRSS(); 36 37 }