your personal website on atproto - mirror blento.app
at main 186 lines 4.9 kB view raw
1import { getDetailedProfile, listRecords, resolveHandle, parseUri } from '$lib/atproto'; 2import { CardDefinitionsByType } from '$lib/cards'; 3import type { Item, UserCache, WebsiteData } from '$lib/types'; 4import { compactItems, fixAllCollisions } from '$lib/helper'; 5import { error } from '@sveltejs/kit'; 6import type { Handle } from '@atcute/lexicons'; 7 8const CURRENT_CACHE_VERSION = 1; 9 10export async function getCache(handle: string, page: string, cache?: UserCache) { 11 try { 12 const cachedResult = await cache?.get?.(handle); 13 14 if (!cachedResult) return; 15 const result = JSON.parse(cachedResult); 16 const update = result.updatedAt; 17 const timePassed = (Date.now() - update) / 1000; 18 19 const ONE_DAY = 60 * 60 * 24; 20 21 if (!result.version || result.version !== CURRENT_CACHE_VERSION) { 22 console.log('skipping cache because of version mismatch'); 23 return; 24 } 25 26 if (timePassed > ONE_DAY) { 27 console.log('skipping cache because of age'); 28 return; 29 } 30 31 result.page = 'blento.' + page; 32 33 result.publication = (result.publications as Awaited<ReturnType<typeof listRecords>>).find( 34 (v) => parseUri(v.uri).rkey === result.page 35 )?.value; 36 37 delete result['publications']; 38 39 console.log('using cached result for handle', handle, 'last update', timePassed, 'seconds ago'); 40 return checkData(result); 41 } catch (error) { 42 console.log('getting cached result failed', error); 43 } 44} 45 46export async function loadData( 47 handle: Handle, 48 cache: UserCache | undefined, 49 forceUpdate: boolean = false, 50 page: string = 'self' 51): Promise<WebsiteData> { 52 if (!handle) throw error(404); 53 54 if (!forceUpdate) { 55 const cachedResult = await getCache(handle, page, cache); 56 57 if (cachedResult) return cachedResult; 58 } 59 60 if (handle === 'favicon.ico') throw error(404); 61 62 console.log('resolving', handle); 63 const did = await resolveHandle({ handle }); 64 65 const cards = await listRecords({ did, collection: 'app.blento.card' }).catch(() => { 66 console.error('error getting records for collection app.blento.card'); 67 return [] as Awaited<ReturnType<typeof listRecords>>; 68 }); 69 70 const publications = await listRecords({ did, collection: 'site.standard.publication' }).catch( 71 () => { 72 console.error('error getting records for collection site.standard.publication'); 73 return [] as Awaited<ReturnType<typeof listRecords>>; 74 } 75 ); 76 77 const profile = await getDetailedProfile({ did }); 78 79 const cardTypes = new Set(cards.map((v) => v.value.cardType ?? '') as string[]); 80 const cardTypesArray = Array.from(cardTypes); 81 82 const additionDataPromises: Record<string, Promise<unknown>> = {}; 83 84 const loadOptions = { did, handle, cache }; 85 86 for (const cardType of cardTypesArray) { 87 const cardDef = CardDefinitionsByType[cardType]; 88 89 if (!cardDef?.loadData) continue; 90 91 try { 92 additionDataPromises[cardType] = cardDef.loadData( 93 cards.filter((v) => cardType === v.value.cardType).map((v) => v.value) as Item[], 94 loadOptions 95 ); 96 } catch { 97 console.error('error getting additional data for', cardType); 98 } 99 } 100 101 await Promise.all(Object.values(additionDataPromises)); 102 103 const additionalData: Record<string, unknown> = {}; 104 for (const [key, value] of Object.entries(additionDataPromises)) { 105 try { 106 additionalData[key] = await value; 107 } catch (error) { 108 console.log('error loading', key, error); 109 } 110 } 111 112 const result = { 113 page: 'blento.' + page, 114 handle, 115 did, 116 cards: (cards.map((v) => { 117 return { ...v.value }; 118 }) ?? []) as Item[], 119 publications: publications, 120 additionalData, 121 profile, 122 updatedAt: Date.now(), 123 version: CURRENT_CACHE_VERSION 124 }; 125 126 const stringifiedResult = JSON.stringify(result); 127 await cache?.put?.(handle, stringifiedResult); 128 129 const parsedResult = JSON.parse(stringifiedResult); 130 parsedResult.publication = ( 131 parsedResult.publications as Awaited<ReturnType<typeof listRecords>> 132 ).find((v) => parseUri(v.uri).rkey === parsedResult.page)?.value; 133 134 delete parsedResult['publications']; 135 136 return checkData(parsedResult); 137} 138 139function migrateFromV0ToV1(data: WebsiteData): WebsiteData { 140 for (const card of data.cards) { 141 if (card.version) continue; 142 card.x *= 2; 143 card.y *= 2; 144 card.h *= 2; 145 card.w *= 2; 146 card.mobileX *= 2; 147 card.mobileY *= 2; 148 card.mobileH *= 2; 149 card.mobileW *= 2; 150 card.version = 1; 151 } 152 153 return data; 154} 155 156function migrateFromV1ToV2(data: WebsiteData): WebsiteData { 157 for (const card of data.cards) { 158 if (!card.version || card.version < 2) { 159 card.page = 'blento.self'; 160 card.version = 2; 161 } 162 } 163 return data; 164} 165 166function checkData(data: WebsiteData): WebsiteData { 167 data = migrateData(data); 168 169 const cards = data.cards.filter((v) => v.page === data.page); 170 171 if (cards.length > 0) { 172 fixAllCollisions(cards); 173 fixAllCollisions(cards, true); 174 175 compactItems(cards); 176 compactItems(cards, true); 177 } 178 179 data.cards = cards; 180 181 return data; 182} 183 184function migrateData(data: WebsiteData): WebsiteData { 185 return migrateFromV1ToV2(migrateFromV0ToV1(data)); 186}