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