your personal website on atproto - mirror blento.app
at remove-extra-buttons 220 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 = await listRecords({ did, collection: 'app.blento.card' }).catch(() => { 77 console.error('error getting records for collection app.blento.card'); 78 return [] as Awaited<ReturnType<typeof listRecords>>; 79 }); 80 81 const mainPublication = await 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 90 const pages = await listRecords({ did, collection: 'app.blento.page' }).catch(() => { 91 console.error('error getting records for collection app.blento.page'); 92 return [] as Awaited<ReturnType<typeof listRecords>>; 93 }); 94 95 const profile = await getDetailedProfile({ did }); 96 97 const cardTypes = new Set(cards.map((v) => v.value.cardType ?? '') as string[]); 98 const cardTypesArray = Array.from(cardTypes); 99 100 const additionDataPromises: Record<string, Promise<unknown>> = {}; 101 102 const loadOptions = { did, handle, cache }; 103 104 for (const cardType of cardTypesArray) { 105 const cardDef = CardDefinitionsByType[cardType]; 106 107 if (!cardDef?.loadData) continue; 108 109 try { 110 additionDataPromises[cardType] = cardDef.loadData( 111 cards.filter((v) => cardType === v.value.cardType).map((v) => v.value) as Item[], 112 loadOptions 113 ); 114 } catch { 115 console.error('error getting additional data for', cardType); 116 } 117 } 118 119 await Promise.all(Object.values(additionDataPromises)); 120 121 const additionalData: Record<string, unknown> = {}; 122 for (const [key, value] of Object.entries(additionDataPromises)) { 123 try { 124 additionalData[key] = await value; 125 } catch (error) { 126 console.log('error loading', key, error); 127 } 128 } 129 130 const result = { 131 page: 'blento.' + page, 132 handle, 133 did, 134 cards: (cards.map((v) => { 135 return { ...v.value }; 136 }) ?? []) as Item[], 137 publications: [mainPublication, ...pages].filter((v) => v), 138 additionalData, 139 profile, 140 updatedAt: Date.now(), 141 version: CURRENT_CACHE_VERSION 142 }; 143 144 const stringifiedResult = JSON.stringify(result); 145 await cache?.put?.(handle, stringifiedResult); 146 147 const parsedResult = JSON.parse(stringifiedResult); 148 149 parsedResult.publication = ( 150 parsedResult.publications as Awaited<ReturnType<typeof listRecords>> 151 ).find((v) => parseUri(v.uri)?.rkey === parsedResult.page)?.value; 152 parsedResult.publication ??= { 153 name: profile?.displayName || profile?.handle, 154 description: profile?.description 155 }; 156 157 delete parsedResult['publications']; 158 159 return checkData(parsedResult); 160} 161 162function migrateFromV0ToV1(data: WebsiteData): WebsiteData { 163 for (const card of data.cards) { 164 if (card.version) continue; 165 card.x *= 2; 166 card.y *= 2; 167 card.h *= 2; 168 card.w *= 2; 169 card.mobileX *= 2; 170 card.mobileY *= 2; 171 card.mobileH *= 2; 172 card.mobileW *= 2; 173 card.version = 1; 174 } 175 176 return data; 177} 178 179function migrateFromV1ToV2(data: WebsiteData): WebsiteData { 180 for (const card of data.cards) { 181 if (!card.version || card.version < 2) { 182 card.page = 'blento.self'; 183 card.version = 2; 184 } 185 } 186 return data; 187} 188 189function migrateCards(data: WebsiteData): WebsiteData { 190 for (const card of data.cards) { 191 const cardDef = CardDefinitionsByType[card.cardType]; 192 193 if (!cardDef?.migrate) continue; 194 195 cardDef.migrate(card); 196 } 197 return data; 198} 199 200function checkData(data: WebsiteData): WebsiteData { 201 data = migrateData(data); 202 203 const cards = data.cards.filter((v) => v.page === data.page); 204 205 if (cards.length > 0) { 206 fixAllCollisions(cards); 207 fixAllCollisions(cards, true); 208 209 compactItems(cards); 210 compactItems(cards, true); 211 } 212 213 data.cards = cards; 214 215 return data; 216} 217 218function migrateData(data: WebsiteData): WebsiteData { 219 return migrateCards(migrateFromV1ToV2(migrateFromV0ToV1(data))); 220}