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