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