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