diff --git a/src/lib/cards/index.ts b/src/lib/cards/index.ts --- a/src/lib/cards/index.ts +++ b/src/lib/cards/index.ts @@ -40,6 +40,7 @@ import { GitHubContributorsCardDefinition } from './social/GitHubContributorsCard'; import { ProductHuntCardDefinition } from './social/ProductHuntCard'; import { KickstarterCardDefinition } from './social/KickstarterCard'; +import { NpmxLikesCardDefinition } from './social/NpmxLikesCard'; // import { Model3DCardDefinition } from './visual/Model3DCard'; export const AllCardDefinitions = [ @@ -84,7 +85,8 @@ FriendsCardDefinition, GitHubContributorsCardDefinition, ProductHuntCardDefinition, - KickstarterCardDefinition + KickstarterCardDefinition, + NpmxLikesCardDefinition ] as const; export const CardDefinitionsByType = AllCardDefinitions.reduce( diff --git a/src/routes/api/npmx-leaderboard/+server.ts b/src/routes/api/npmx-leaderboard/+server.ts new file mode 100644 --- /dev/null +++ b/src/routes/api/npmx-leaderboard/+server.ts @@ -0,0 +1,44 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; + +const LEADERBOARD_API_URL = + 'https://npmx-likes-leaderboard-api-production.up.railway.app/api/leaderboard/likes'; + +export const GET: RequestHandler = async ({ platform }) => { + const cacheKey = '#npmx-leaderboard:likes'; + const cachedData = await platform?.env?.USER_DATA_CACHE?.get(cacheKey); + + if (cachedData) { + const parsedCache = JSON.parse(cachedData); + + const TWELVE_HOURS = 12 * 60 * 60 * 1000; + const now = Date.now(); + + if (now - (parsedCache.updatedAt || 0) < TWELVE_HOURS) { + return json(parsedCache.data); + } + } + + try { + const response = await fetch(LEADERBOARD_API_URL); + + if (!response.ok) { + return json( + { error: 'Failed to fetch npmx leaderboard ' + response.statusText }, + { status: response.status } + ); + } + + const data = await response.json(); + + await platform?.env?.USER_DATA_CACHE?.put( + cacheKey, + JSON.stringify({ data, updatedAt: Date.now() }) + ); + + return json(data); + } catch (error) { + console.error('Error fetching npmx leaderboard:', error); + return json({ error: 'Failed to fetch npmx leaderboard' }, { status: 500 }); + } +}; diff --git a/src/lib/cards/social/NpmxLikesCard/NpmxLikesCard.svelte b/src/lib/cards/social/NpmxLikesCard/NpmxLikesCard.svelte new file mode 100644 --- /dev/null +++ b/src/lib/cards/social/NpmxLikesCard/NpmxLikesCard.svelte @@ -0,0 +1,103 @@ + + +{#snippet likeItem(like: NpmxLike)} +
+
+ + + +
+
+
+
+ {getPackageName(like)} +
+ {#if like.value.createdAt} +
+ ago +
+ {/if} +
+
+
+{/snippet} + +
+ {#if feed && feed.length > 0} + {#each feed as like (like.uri)} + + {@render likeItem(like)} + + {/each} + {:else if feed} +
+ No liked packages found. +
+ {:else} +
+ Loading likes... +
+ {/if} +
diff --git a/src/lib/cards/social/NpmxLikesCard/index.ts b/src/lib/cards/social/NpmxLikesCard/index.ts new file mode 100644 --- /dev/null +++ b/src/lib/cards/social/NpmxLikesCard/index.ts @@ -0,0 +1,31 @@ +import type { CardDefinition } from '../../types'; +import { listRecords } from '$lib/atproto'; +import NpmxLikesCard from './NpmxLikesCard.svelte'; + +export const NpmxLikesCardDefinition = { + type: 'npmxLikes', + contentComponent: NpmxLikesCard, + createNew: (card) => { + card.w = 4; + card.mobileW = 8; + card.h = 3; + card.mobileH = 6; + }, + loadData: async (items, { did }) => { + const data = await listRecords({ + did, + collection: 'dev.npmx.feed.like', + limit: 99 + }); + + return data; + }, + minW: 4, + canHaveLabel: true, + + keywords: ['npm', 'package', 'npmx', 'likes'], + name: 'npmx Likes', + + groups: ['Social'], + icon: `` +} as CardDefinition & { type: 'npmxLikes' }; diff --git a/src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte b/src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte --- a/src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte +++ b/src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte @@ -1773,7 +1773,11 @@ ? 'bg-base-50 dark:bg-base-900' : 'bg-black'}" > - - + +