your personal website on atproto - mirror blento.app

move to remote functions

+169 -180
+6 -29
src/lib/cards/media/LastFMCard/LastFMProfileCard/index.ts
··· 1 1 import type { CardDefinition } from '../../../types'; 2 2 import CreateLastFMCardModal from '../CreateLastFMCardModal.svelte'; 3 3 import LastFMProfileCard from './LastFMProfileCard.svelte'; 4 + import { fetchLastFM } from '../api.remote'; 4 5 5 6 export const LastFMProfileCardDefinition = { 6 7 type: 'lastfmProfile', ··· 18 19 const username = item.cardData.lastfmUsername; 19 20 if (!username) continue; 20 21 try { 21 - const response = await fetch( 22 - `https://blento.app/api/lastfm?method=user.getInfo&user=${encodeURIComponent(username)}` 23 - ); 24 - if (!response.ok) continue; 25 - const text = await response.text(); 26 - const result = JSON.parse(text); 27 - allData[`lastfmProfile:${username}`] = result?.user; 22 + const data = await fetchLastFM({ method: 'user.getInfo', user: username }); 23 + if (data) allData[`lastfmProfile:${username}`] = data?.user; 28 24 } catch (error) { 29 25 console.error('Failed to fetch Last.fm profile:', error); 30 26 } 31 27 } 32 28 return allData; 33 29 }, 34 - loadDataServer: async (items, { cache, env }) => { 35 - const apiKey = env?.LASTFM_API_KEY; 36 - if (!apiKey) return {}; 30 + loadDataServer: async (items) => { 37 31 const allData: Record<string, unknown> = {}; 38 32 for (const item of items) { 39 33 const username = item.cardData.lastfmUsername; 40 34 if (!username) continue; 41 35 try { 42 - const cacheKey = `user.getInfo:${username}:7day:50`; 43 - const cached = await cache?.get('lastfm', cacheKey); 44 - if (cached) { 45 - allData[`lastfmProfile:${username}`] = JSON.parse(cached)?.user; 46 - continue; 47 - } 48 - const params = new URLSearchParams({ 49 - method: 'user.getInfo', 50 - user: username, 51 - api_key: apiKey, 52 - format: 'json', 53 - limit: '50' 54 - }); 55 - const response = await fetch(`https://ws.audioscrobbler.com/2.0/?${params}`); 56 - if (!response.ok) continue; 57 - const data = await response.json(); 58 - if (data.error) continue; 59 - await cache?.put('lastfm', cacheKey, JSON.stringify(data), 12 * 60 * 60); 60 - allData[`lastfmProfile:${username}`] = data?.user; 36 + const data = await fetchLastFM({ method: 'user.getInfo', user: username }); 37 + if (data) allData[`lastfmProfile:${username}`] = data?.user; 61 38 } catch (error) { 62 39 console.error('Failed to fetch Last.fm profile:', error); 63 40 }
+6 -29
src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/index.ts
··· 1 1 import type { CardDefinition } from '../../../types'; 2 2 import CreateLastFMCardModal from '../CreateLastFMCardModal.svelte'; 3 3 import LastFMRecentTracksCard from './LastFMRecentTracksCard.svelte'; 4 + import { fetchLastFM } from '../api.remote'; 4 5 5 6 export const LastFMRecentTracksCardDefinition = { 6 7 type: 'lastfmRecentTracks', ··· 18 19 const username = item.cardData.lastfmUsername; 19 20 if (!username) continue; 20 21 try { 21 - const response = await fetch( 22 - `https://blento.app/api/lastfm?method=user.getRecentTracks&user=${encodeURIComponent(username)}&limit=50` 23 - ); 24 - if (!response.ok) continue; 25 - const text = await response.text(); 26 - const result = JSON.parse(text); 27 - allData[`lastfmRecentTracks:${username}`] = result?.recenttracks?.track ?? []; 22 + const data = await fetchLastFM({ method: 'user.getRecentTracks', user: username }); 23 + if (data) allData[`lastfmRecentTracks:${username}`] = data?.recenttracks?.track ?? []; 28 24 } catch (error) { 29 25 console.error('Failed to fetch Last.fm recent tracks:', error); 30 26 } 31 27 } 32 28 return allData; 33 29 }, 34 - loadDataServer: async (items, { cache, env }) => { 35 - const apiKey = env?.LASTFM_API_KEY; 36 - if (!apiKey) return {}; 30 + loadDataServer: async (items) => { 37 31 const allData: Record<string, unknown> = {}; 38 32 for (const item of items) { 39 33 const username = item.cardData.lastfmUsername; 40 34 if (!username) continue; 41 35 try { 42 - const cacheKey = `user.getRecentTracks:${username}:7day:50`; 43 - const cached = await cache?.get('lastfm', cacheKey); 44 - if (cached) { 45 - allData[`lastfmRecentTracks:${username}`] = JSON.parse(cached)?.recenttracks?.track ?? []; 46 - continue; 47 - } 48 - const params = new URLSearchParams({ 49 - method: 'user.getRecentTracks', 50 - user: username, 51 - api_key: apiKey, 52 - format: 'json', 53 - limit: '50' 54 - }); 55 - const response = await fetch(`https://ws.audioscrobbler.com/2.0/?${params}`); 56 - if (!response.ok) continue; 57 - const data = await response.json(); 58 - if (data.error) continue; 59 - await cache?.put('lastfm', cacheKey, JSON.stringify(data), 15 * 60); 60 - allData[`lastfmRecentTracks:${username}`] = data?.recenttracks?.track ?? []; 36 + const data = await fetchLastFM({ method: 'user.getRecentTracks', user: username }); 37 + if (data) allData[`lastfmRecentTracks:${username}`] = data?.recenttracks?.track ?? []; 61 38 } catch (error) { 62 39 console.error('Failed to fetch Last.fm recent tracks:', error); 63 40 }
+6 -31
src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/index.ts
··· 2 2 import CreateLastFMCardModal from '../CreateLastFMCardModal.svelte'; 3 3 import LastFMTopAlbumsCard from './LastFMTopAlbumsCard.svelte'; 4 4 import LastFMTopAlbumsCardSettings from './LastFMTopAlbumsCardSettings.svelte'; 5 + import { fetchLastFM } from '../api.remote'; 5 6 6 7 export const LastFMTopAlbumsCardDefinition = { 7 8 type: 'lastfmTopAlbums', ··· 22 23 const period = item.cardData.period ?? '7day'; 23 24 if (!username) continue; 24 25 try { 25 - const response = await fetch( 26 - `https://blento.app/api/lastfm?method=user.getTopAlbums&user=${encodeURIComponent(username)}&period=${period}&limit=50` 27 - ); 28 - if (!response.ok) continue; 29 - const text = await response.text(); 30 - const result = JSON.parse(text); 31 - allData[`lastfmTopAlbums:${username}:${period}`] = result?.topalbums?.album ?? []; 26 + const data = await fetchLastFM({ method: 'user.getTopAlbums', user: username, period }); 27 + if (data) allData[`lastfmTopAlbums:${username}:${period}`] = data?.topalbums?.album ?? []; 32 28 } catch (error) { 33 29 console.error('Failed to fetch Last.fm top albums:', error); 34 30 } 35 31 } 36 32 return allData; 37 33 }, 38 - loadDataServer: async (items, { cache, env }) => { 39 - const apiKey = env?.LASTFM_API_KEY; 40 - if (!apiKey) return {}; 34 + loadDataServer: async (items) => { 41 35 const allData: Record<string, unknown> = {}; 42 36 for (const item of items) { 43 37 const username = item.cardData.lastfmUsername; 44 38 const period = item.cardData.period ?? '7day'; 45 39 if (!username) continue; 46 40 try { 47 - const cacheKey = `user.getTopAlbums:${username}:${period}:50`; 48 - const cached = await cache?.get('lastfm', cacheKey); 49 - if (cached) { 50 - allData[`lastfmTopAlbums:${username}:${period}`] = 51 - JSON.parse(cached)?.topalbums?.album ?? []; 52 - continue; 53 - } 54 - const params = new URLSearchParams({ 55 - method: 'user.getTopAlbums', 56 - user: username, 57 - api_key: apiKey, 58 - format: 'json', 59 - limit: '50', 60 - period 61 - }); 62 - const response = await fetch(`https://ws.audioscrobbler.com/2.0/?${params}`); 63 - if (!response.ok) continue; 64 - const data = await response.json(); 65 - if (data.error) continue; 66 - await cache?.put('lastfm', cacheKey, JSON.stringify(data), 60 * 60); 67 - allData[`lastfmTopAlbums:${username}:${period}`] = data?.topalbums?.album ?? []; 41 + const data = await fetchLastFM({ method: 'user.getTopAlbums', user: username, period }); 42 + if (data) allData[`lastfmTopAlbums:${username}:${period}`] = data?.topalbums?.album ?? []; 68 43 } catch (error) { 69 44 console.error('Failed to fetch Last.fm top albums:', error); 70 45 }
+6 -31
src/lib/cards/media/LastFMCard/LastFMTopTracksCard/index.ts
··· 2 2 import CreateLastFMCardModal from '../CreateLastFMCardModal.svelte'; 3 3 import LastFMPeriodSettings from '../LastFMPeriodSettings.svelte'; 4 4 import LastFMTopTracksCard from './LastFMTopTracksCard.svelte'; 5 + import { fetchLastFM } from '../api.remote'; 5 6 6 7 export const LastFMTopTracksCardDefinition = { 7 8 type: 'lastfmTopTracks', ··· 22 23 const period = item.cardData.period ?? '7day'; 23 24 if (!username) continue; 24 25 try { 25 - const response = await fetch( 26 - `https://blento.app/api/lastfm?method=user.getTopTracks&user=${encodeURIComponent(username)}&period=${period}&limit=50` 27 - ); 28 - if (!response.ok) continue; 29 - const text = await response.text(); 30 - const result = JSON.parse(text); 31 - allData[`lastfmTopTracks:${username}:${period}`] = result?.toptracks?.track ?? []; 26 + const data = await fetchLastFM({ method: 'user.getTopTracks', user: username, period }); 27 + if (data) allData[`lastfmTopTracks:${username}:${period}`] = data?.toptracks?.track ?? []; 32 28 } catch (error) { 33 29 console.error('Failed to fetch Last.fm top tracks:', error); 34 30 } 35 31 } 36 32 return allData; 37 33 }, 38 - loadDataServer: async (items, { cache, env }) => { 39 - const apiKey = env?.LASTFM_API_KEY; 40 - if (!apiKey) return {}; 34 + loadDataServer: async (items) => { 41 35 const allData: Record<string, unknown> = {}; 42 36 for (const item of items) { 43 37 const username = item.cardData.lastfmUsername; 44 38 const period = item.cardData.period ?? '7day'; 45 39 if (!username) continue; 46 40 try { 47 - const cacheKey = `user.getTopTracks:${username}:${period}:50`; 48 - const cached = await cache?.get('lastfm', cacheKey); 49 - if (cached) { 50 - allData[`lastfmTopTracks:${username}:${period}`] = 51 - JSON.parse(cached)?.toptracks?.track ?? []; 52 - continue; 53 - } 54 - const params = new URLSearchParams({ 55 - method: 'user.getTopTracks', 56 - user: username, 57 - api_key: apiKey, 58 - format: 'json', 59 - limit: '50', 60 - period 61 - }); 62 - const response = await fetch(`https://ws.audioscrobbler.com/2.0/?${params}`); 63 - if (!response.ok) continue; 64 - const data = await response.json(); 65 - if (data.error) continue; 66 - await cache?.put('lastfm', cacheKey, JSON.stringify(data), 60 * 60); 67 - allData[`lastfmTopTracks:${username}:${period}`] = data?.toptracks?.track ?? []; 41 + const data = await fetchLastFM({ method: 'user.getTopTracks', user: username, period }); 42 + if (data) allData[`lastfmTopTracks:${username}:${period}`] = data?.toptracks?.track ?? []; 68 43 } catch (error) { 69 44 console.error('Failed to fetch Last.fm top tracks:', error); 70 45 }
+59
src/lib/cards/media/LastFMCard/api.remote.ts
··· 1 + import { query, getRequestEvent } from '$app/server'; 2 + import { env } from '$env/dynamic/private'; 3 + import { createCache } from '$lib/cache'; 4 + 5 + const LASTFM_API_URL = 'https://ws.audioscrobbler.com/2.0/'; 6 + 7 + const CACHE_TTL: Record<string, number> = { 8 + 'user.getRecentTracks': 15 * 60, 9 + 'user.getTopTracks': 60 * 60, 10 + 'user.getTopAlbums': 60 * 60, 11 + 'user.getInfo': 12 * 60 * 60 12 + }; 13 + 14 + export const fetchLastFM = query( 15 + 'unchecked', 16 + async ({ 17 + method, 18 + user, 19 + period = '7day', 20 + limit = '50' 21 + }: { 22 + method: string; 23 + user: string; 24 + period?: string; 25 + limit?: string; 26 + }) => { 27 + const apiKey = env?.LASTFM_API_KEY; 28 + if (!apiKey) return undefined; 29 + 30 + const { platform } = getRequestEvent(); 31 + const cache = createCache(platform); 32 + 33 + const cacheKey = `${method}:${user}:${period}:${limit}`; 34 + const cached = await cache?.get('lastfm', cacheKey); 35 + if (cached) return JSON.parse(cached); 36 + 37 + const params = new URLSearchParams({ 38 + method, 39 + user, 40 + api_key: apiKey, 41 + format: 'json', 42 + limit 43 + }); 44 + 45 + if (method === 'user.getTopTracks' || method === 'user.getTopAlbums') { 46 + params.set('period', period); 47 + } 48 + 49 + const response = await fetch(`${LASTFM_API_URL}?${params}`); 50 + if (!response.ok) return undefined; 51 + 52 + const data = await response.json(); 53 + if (data.error) return undefined; 54 + 55 + const ttl = CACHE_TTL[method] || 60 * 60; 56 + await cache?.put('lastfm', cacheKey, JSON.stringify(data), ttl); 57 + return data; 58 + } 59 + );
+26
src/lib/cards/social/GitHubContributorsCard/api.remote.ts
··· 1 + import { query, getRequestEvent } from '$app/server'; 2 + import { createCache } from '$lib/cache'; 3 + 4 + const GITHUB_CONTRIBUTORS_API_URL = 5 + 'https://edge-function-github-contribution.vercel.app/api/github-contributors'; 6 + 7 + export const fetchGitHubContributors = query( 8 + 'unchecked', 9 + async ({ owner, repo }: { owner: string; repo: string }) => { 10 + const { platform } = getRequestEvent(); 11 + const cache = createCache(platform); 12 + 13 + const key = `${owner}/${repo}`; 14 + const cached = await cache?.get('gh-contrib', key); 15 + if (cached) return JSON.parse(cached); 16 + 17 + const response = await fetch( 18 + `${GITHUB_CONTRIBUTORS_API_URL}?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}` 19 + ); 20 + if (!response.ok) return undefined; 21 + 22 + const data = await response.json(); 23 + await cache?.put('gh-contrib', key, JSON.stringify(data)); 24 + return data; 25 + } 26 + );
+6 -19
src/lib/cards/social/GitHubContributorsCard/index.ts
··· 2 2 import GitHubContributorsCard from './GitHubContributorsCard.svelte'; 3 3 import CreateGitHubContributorsCardModal from './CreateGitHubContributorsCardModal.svelte'; 4 4 import GitHubContributorsCardSettings from './GitHubContributorsCardSettings.svelte'; 5 + import { fetchGitHubContributors } from './api.remote'; 5 6 6 7 export type GitHubContributor = { 7 8 username: string; ··· 33 34 const key = `${owner}/${repo}`; 34 35 if (contributorsData[key]) continue; 35 36 try { 36 - const response = await fetch( 37 - `https://blento.app/api/github/contributors?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}` 38 - ); 39 - if (response.ok) { 40 - contributorsData[key] = await response.json(); 41 - } 37 + const data = await fetchGitHubContributors({ owner, repo }); 38 + if (data) contributorsData[key] = data; 42 39 } catch (error) { 43 40 console.error('Failed to fetch GitHub contributors:', error); 44 41 } 45 42 } 46 43 return contributorsData; 47 44 }, 48 - loadDataServer: async (items, { cache }) => { 45 + loadDataServer: async (items) => { 49 46 const contributorsData: GitHubContributorsLoadedData = {}; 50 47 for (const item of items) { 51 48 const { owner, repo } = item.cardData; ··· 53 50 const key = `${owner}/${repo}`; 54 51 if (contributorsData[key]) continue; 55 52 try { 56 - const cached = await cache?.get('gh-contrib', key); 57 - if (cached) { 58 - contributorsData[key] = JSON.parse(cached); 59 - continue; 60 - } 61 - const response = await fetch( 62 - `https://edge-function-github-contribution.vercel.app/api/github-contributors?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}` 63 - ); 64 - if (!response.ok) continue; 65 - const data = await response.json(); 66 - await cache?.put('gh-contrib', key, JSON.stringify(data)); 67 - contributorsData[key] = data; 53 + const data = await fetchGitHubContributors({ owner, repo }); 54 + if (data) contributorsData[key] = data; 68 55 } catch (error) { 69 56 console.error('Failed to fetch GitHub contributors:', error); 70 57 }
+21
src/lib/cards/social/GitHubProfileCard/api.remote.ts
··· 1 + import { query, getRequestEvent } from '$app/server'; 2 + import { createCache } from '$lib/cache'; 3 + 4 + const GITHUB_API_URL = 'https://edge-function-github-contribution.vercel.app/api/github-data?user='; 5 + 6 + export const fetchGitHubContributions = query('unchecked', async (user: string) => { 7 + const { platform } = getRequestEvent(); 8 + const cache = createCache(platform); 9 + 10 + const cached = await cache?.get('github', user); 11 + if (cached) return JSON.parse(cached); 12 + 13 + const response = await fetch(GITHUB_API_URL + encodeURIComponent(user)); 14 + if (!response.ok) return undefined; 15 + 16 + const data = await response.json(); 17 + if (!data?.user) return undefined; 18 + 19 + await cache?.put('github', user, JSON.stringify(data.user)); 20 + return data.user; 21 + });
+9 -23
src/lib/cards/social/GitHubProfileCard/index.ts
··· 1 1 import type { CardDefinition } from '../../types'; 2 2 import CreateGitHubProfileCardModal from './CreateGitHubProfileCardModal.svelte'; 3 - import type GithubContributionsGraph from './GithubContributionsGraph.svelte'; 4 3 import GitHubProfileCard from './GitHubProfileCard.svelte'; 5 4 import type { GitHubContributionsData } from './types'; 5 + import { fetchGitHubContributions } from './api.remote'; 6 6 7 7 export type GithubProfileLoadedData = Record<string, GitHubContributionsData | undefined>; 8 8 ··· 12 12 creationModalComponent: CreateGitHubProfileCardModal, 13 13 14 14 loadData: async (items) => { 15 - const githubData: Record<string, GithubContributionsGraph> = {}; 15 + const githubData: Record<string, GitHubContributionsData> = {}; 16 16 for (const item of items) { 17 + const user = item.cardData.user; 18 + if (!user) continue; 17 19 try { 18 - const response = await fetch( 19 - `https://blento.app/api/github?user=${encodeURIComponent(item.cardData.user)}` 20 - ); 21 - if (response.ok) { 22 - githubData[item.cardData.user] = await response.json(); 23 - } 20 + const data = await fetchGitHubContributions(user); 21 + if (data) githubData[user] = data; 24 22 } catch (error) { 25 23 console.error('Failed to fetch GitHub contributions:', error); 26 24 } 27 25 } 28 26 return githubData; 29 27 }, 30 - loadDataServer: async (items, { cache }) => { 28 + loadDataServer: async (items) => { 31 29 const githubData: Record<string, GitHubContributionsData> = {}; 32 30 for (const item of items) { 33 31 const user = item.cardData.user; 34 32 if (!user) continue; 35 33 try { 36 - const cached = await cache?.get('github', user); 37 - if (cached) { 38 - githubData[user] = JSON.parse(cached); 39 - continue; 40 - } 41 - const response = await fetch( 42 - `https://edge-function-github-contribution.vercel.app/api/github-data?user=${encodeURIComponent(user)}` 43 - ); 44 - if (!response.ok) continue; 45 - const data = await response.json(); 46 - if (!data?.user) continue; 47 - const result = data.user as GitHubContributionsData; 48 - await cache?.put('github', user, JSON.stringify(result)); 49 - githubData[user] = result; 34 + const data = await fetchGitHubContributions(user); 35 + if (data) githubData[user] = data; 50 36 } catch (error) { 51 37 console.error('Failed to fetch GitHub contributions:', error); 52 38 }
+20
src/lib/cards/social/NpmxLikesLeaderboardCard/api.remote.ts
··· 1 + import { query, getRequestEvent } from '$app/server'; 2 + import { createCache } from '$lib/cache'; 3 + 4 + const LEADERBOARD_API_URL = 5 + 'https://npmx-likes-leaderboard-api-production.up.railway.app/api/leaderboard/likes?limit=20'; 6 + 7 + export const fetchNpmxLeaderboard = query(async () => { 8 + const { platform } = getRequestEvent(); 9 + const cache = createCache(platform); 10 + 11 + const cached = await cache?.get('npmx', 'likes'); 12 + if (cached) return JSON.parse(cached); 13 + 14 + const response = await fetch(LEADERBOARD_API_URL); 15 + if (!response.ok) return undefined; 16 + 17 + const data = await response.json(); 18 + await cache?.put('npmx', 'likes', JSON.stringify(data)); 19 + return data; 20 + });
+4 -18
src/lib/cards/social/NpmxLikesLeaderboardCard/index.ts
··· 1 1 import type { CardDefinition } from '../../types'; 2 2 import NpmxLikesLeaderboardCard from './NpmxLikesLeaderboardCard.svelte'; 3 + import { fetchNpmxLeaderboard } from './api.remote'; 3 4 4 5 export const NpmxLikesLeaderboardCardDefinition = { 5 6 type: 'npmxLikesLeaderboard', ··· 11 12 card.mobileH = 6; 12 13 }, 13 14 loadData: async () => { 14 - const res = await fetch('https://blento.app/api/npmx-leaderboard'); 15 - const data = await res.json(); 16 - return data; 15 + return await fetchNpmxLeaderboard(); 17 16 }, 18 - loadDataServer: async (_items, { cache }) => { 19 - try { 20 - const cached = await cache?.get('npmx', 'likes'); 21 - if (cached) return JSON.parse(cached); 22 - const response = await fetch( 23 - 'https://npmx-likes-leaderboard-api-production.up.railway.app/api/leaderboard/likes?limit=20' 24 - ); 25 - if (!response.ok) return undefined; 26 - const data = await response.json(); 27 - await cache?.put('npmx', 'likes', JSON.stringify(data)); 28 - return data; 29 - } catch (error) { 30 - console.error('Error fetching npmx leaderboard:', error); 31 - return undefined; 32 - } 17 + loadDataServer: async () => { 18 + return await fetchNpmxLeaderboard(); 33 19 }, 34 20 minW: 3, 35 21 canHaveLabel: true,