tangled
alpha
login
or
join now
flo-bit.dev
/
blento
your personal website on atproto - mirror
blento.app
20
fork
atom
overview
issues
pulls
pipelines
delete api routes
Florian
1 day ago
cb5f3127
7bcda16d
+32
-243
10 changed files
expand all
collapse all
unified
split
src
lib
cards
media
LastFMCard
LastFMProfileCard
LastFMProfileCard.svelte
LastFMRecentTracksCard
LastFMRecentTracksCard.svelte
LastFMTopAlbumsCard
LastFMTopAlbumsCard.svelte
LastFMTopTracksCard
LastFMTopTracksCard.svelte
social
GitHubContributorsCard
GitHubContributorsCard.svelte
GitHubProfileCard
GitHubProfileCard.svelte
routes
api
github
+server.ts
contributors
+server.ts
lastfm
+server.ts
npmx-leaderboard
+server.ts
+6
-5
src/lib/cards/media/LastFMCard/LastFMProfileCard/LastFMProfileCard.svelte
···
4
4
import { getAdditionalUserData } from '$lib/website/context';
5
5
import type { ContentComponentProps } from '../../../types';
6
6
import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte';
7
7
+
import { fetchLastFM } from '../api.remote';
7
8
8
9
interface UserInfo {
9
10
name: string;
···
27
28
if (!item.cardData.lastfmUsername) return;
28
29
29
30
try {
30
30
-
const response = await fetch(
31
31
-
`/api/lastfm?method=user.getInfo&user=${encodeURIComponent(item.cardData.lastfmUsername)}`
32
32
-
);
33
33
-
if (response.ok) {
34
34
-
const result = await response.json();
31
31
+
const result = await fetchLastFM({
32
32
+
method: 'user.getInfo',
33
33
+
user: item.cardData.lastfmUsername
34
34
+
});
35
35
+
if (result) {
35
36
userInfo = result?.user;
36
37
data[cacheKey] = userInfo;
37
38
}
+6
-5
src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/LastFMRecentTracksCard.svelte
···
4
4
import type { ContentComponentProps } from '../../../types';
5
5
import LastFMAlbumArt from '../LastFMAlbumArt.svelte';
6
6
import { RelativeTime } from '@foxui/time';
7
7
+
import { fetchLastFM } from '../api.remote';
7
8
8
9
interface Track {
9
10
name: string;
···
29
30
if (!item.cardData.lastfmUsername) return;
30
31
31
32
try {
32
32
-
const response = await fetch(
33
33
-
`/api/lastfm?method=user.getRecentTracks&user=${encodeURIComponent(item.cardData.lastfmUsername)}&limit=50`
34
34
-
);
35
35
-
if (response.ok) {
36
36
-
const result = await response.json();
33
33
+
const result = await fetchLastFM({
34
34
+
method: 'user.getRecentTracks',
35
35
+
user: item.cardData.lastfmUsername
36
36
+
});
37
37
+
if (result) {
37
38
tracks = result?.recenttracks?.track ?? [];
38
39
data[cacheKey] = tracks;
39
40
} else {
+7
-5
src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/LastFMTopAlbumsCard.svelte
···
3
3
import type { ContentComponentProps } from '../../../types';
4
4
import { getAdditionalUserData } from '$lib/website/context';
5
5
import ImageGrid from '$lib/components/ImageGrid.svelte';
6
6
+
import { fetchLastFM } from '../api.remote';
6
7
7
8
interface Album {
8
9
name: string;
···
30
31
loading = true;
31
32
32
33
try {
33
33
-
const response = await fetch(
34
34
-
`/api/lastfm?method=user.getTopAlbums&user=${encodeURIComponent(item.cardData.lastfmUsername)}&period=${period}&limit=50`
35
35
-
);
36
36
-
if (response.ok) {
37
37
-
const result = await response.json();
34
34
+
const result = await fetchLastFM({
35
35
+
method: 'user.getTopAlbums',
36
36
+
user: item.cardData.lastfmUsername,
37
37
+
period
38
38
+
});
39
39
+
if (result) {
38
40
albums = result?.topalbums?.album ?? [];
39
41
data[cacheKey] = albums;
40
42
} else {
+7
-5
src/lib/cards/media/LastFMCard/LastFMTopTracksCard/LastFMTopTracksCard.svelte
···
3
3
import { getAdditionalUserData } from '$lib/website/context';
4
4
import type { ContentComponentProps } from '../../../types';
5
5
import LastFMAlbumArt from '../LastFMAlbumArt.svelte';
6
6
+
import { fetchLastFM } from '../api.remote';
6
7
7
8
interface Track {
8
9
name: string;
···
29
30
loading = true;
30
31
31
32
try {
32
32
-
const response = await fetch(
33
33
-
`/api/lastfm?method=user.getTopTracks&user=${encodeURIComponent(item.cardData.lastfmUsername)}&period=${period}&limit=50`
34
34
-
);
35
35
-
if (response.ok) {
36
36
-
const result = await response.json();
33
33
+
const result = await fetchLastFM({
34
34
+
method: 'user.getTopTracks',
35
35
+
user: item.cardData.lastfmUsername,
36
36
+
period
37
37
+
});
38
38
+
if (result) {
37
39
tracks = result?.toptracks?.track ?? [];
38
40
data[cacheKey] = tracks;
39
41
} else {
+3
-7
src/lib/cards/social/GitHubContributorsCard/GitHubContributorsCard.svelte
···
4
4
import { getAdditionalUserData, getCanEdit } from '$lib/website/context';
5
5
import type { GitHubContributor, GitHubContributorsLoadedData } from '.';
6
6
import ImageGrid from '$lib/components/ImageGrid.svelte';
7
7
+
import { fetchGitHubContributors } from './api.remote';
7
8
8
9
let { item }: ContentComponentProps = $props();
9
10
···
41
42
async function loadContributors() {
42
43
if (!owner || !repo) return;
43
44
try {
44
44
-
const response = await fetch(
45
45
-
`/api/github/contributors?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}`
46
46
-
);
47
47
-
if (response.ok) {
48
48
-
const data = await response.json();
49
49
-
clientContributors = data;
50
50
-
}
45
45
+
const data = await fetchGitHubContributors({ owner, repo });
46
46
+
if (data) clientContributors = data;
51
47
} catch (error) {
52
48
console.error('Failed to fetch GitHub contributors:', error);
53
49
}
+3
-3
src/lib/cards/social/GitHubProfileCard/GitHubProfileCard.svelte
···
8
8
import { Button } from '@foxui/core';
9
9
import { browser } from '$app/environment';
10
10
import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte';
11
11
+
import { fetchGitHubContributions } from './api.remote';
11
12
12
13
let { item, isEditing }: ContentComponentProps = $props();
13
14
···
23
24
onMount(async () => {
24
25
if (!contributionsData && item.cardData?.user) {
25
26
try {
26
26
-
const response = await fetch(`/api/github?user=${encodeURIComponent(item.cardData.user)}`);
27
27
-
if (response.ok) {
28
28
-
contributionsData = await response.json();
27
27
+
contributionsData = await fetchGitHubContributions(item.cardData.user);
28
28
+
if (contributionsData) {
29
29
data[item.cardType] ??= {};
30
30
(data[item.cardType] as GithubProfileLoadedData)[item.cardData.user] = contributionsData;
31
31
}
-48
src/routes/api/github/+server.ts
···
1
1
-
import { json } from '@sveltejs/kit';
2
2
-
import type { RequestHandler } from './$types';
3
3
-
import type { GitHubContributionsData } from '$lib/cards/social/GitHubProfileCard/types';
4
4
-
import { createCache } from '$lib/cache';
5
5
-
6
6
-
const GithubAPIURL = 'https://edge-function-github-contribution.vercel.app/api/github-data?user=';
7
7
-
8
8
-
export const GET: RequestHandler = async ({ url, platform }) => {
9
9
-
const user = url.searchParams.get('user');
10
10
-
11
11
-
if (!user) {
12
12
-
return json({ error: 'No user provided' }, { status: 400 });
13
13
-
}
14
14
-
15
15
-
const cache = createCache(platform);
16
16
-
const cachedData = await cache?.get('github', user);
17
17
-
18
18
-
if (cachedData) {
19
19
-
return json(JSON.parse(cachedData));
20
20
-
}
21
21
-
22
22
-
try {
23
23
-
const response = await fetch(GithubAPIURL + user);
24
24
-
25
25
-
if (!response.ok) {
26
26
-
console.error('error', response.statusText);
27
27
-
return json(
28
28
-
{ error: 'Failed to fetch GitHub data ' + response.statusText },
29
29
-
{ status: response.status }
30
30
-
);
31
31
-
}
32
32
-
33
33
-
const data = await response.json();
34
34
-
35
35
-
if (!data?.user) {
36
36
-
return json({ error: 'User not found' }, { status: 404 });
37
37
-
}
38
38
-
39
39
-
const result = data.user as GitHubContributionsData;
40
40
-
41
41
-
await cache?.put('github', user, JSON.stringify(result));
42
42
-
43
43
-
return json(result);
44
44
-
} catch (error) {
45
45
-
console.error('Error fetching GitHub contributions:', error);
46
46
-
return json({ error: 'Failed to fetch GitHub data' }, { status: 500 });
47
47
-
}
48
48
-
};
-45
src/routes/api/github/contributors/+server.ts
···
1
1
-
import { json } from '@sveltejs/kit';
2
2
-
import type { RequestHandler } from './$types';
3
3
-
import { createCache } from '$lib/cache';
4
4
-
5
5
-
const GithubContributorsAPIURL =
6
6
-
'https://edge-function-github-contribution.vercel.app/api/github-contributors';
7
7
-
8
8
-
export const GET: RequestHandler = async ({ url, platform }) => {
9
9
-
const owner = url.searchParams.get('owner');
10
10
-
const repo = url.searchParams.get('repo');
11
11
-
12
12
-
if (!owner || !repo) {
13
13
-
return json({ error: 'Missing owner or repo parameter' }, { status: 400 });
14
14
-
}
15
15
-
16
16
-
const cache = createCache(platform);
17
17
-
const cacheKey = `${owner}/${repo}`;
18
18
-
const cachedData = await cache?.get('gh-contrib', cacheKey);
19
19
-
20
20
-
if (cachedData) {
21
21
-
return json(JSON.parse(cachedData));
22
22
-
}
23
23
-
24
24
-
try {
25
25
-
const response = await fetch(
26
26
-
`${GithubContributorsAPIURL}?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}`
27
27
-
);
28
28
-
29
29
-
if (!response.ok) {
30
30
-
return json(
31
31
-
{ error: 'Failed to fetch GitHub contributors ' + response.statusText },
32
32
-
{ status: response.status }
33
33
-
);
34
34
-
}
35
35
-
36
36
-
const data = await response.json();
37
37
-
38
38
-
await cache?.put('gh-contrib', cacheKey, JSON.stringify(data));
39
39
-
40
40
-
return json(data);
41
41
-
} catch (error) {
42
42
-
console.error('Error fetching GitHub contributors:', error);
43
43
-
return json({ error: 'Failed to fetch GitHub contributors' }, { status: 500 });
44
44
-
}
45
45
-
};
-85
src/routes/api/lastfm/+server.ts
···
1
1
-
import { json } from '@sveltejs/kit';
2
2
-
import type { RequestHandler } from './$types';
3
3
-
import { env } from '$env/dynamic/private';
4
4
-
import { createCache } from '$lib/cache';
5
5
-
6
6
-
const LASTFM_API_URL = 'https://ws.audioscrobbler.com/2.0/';
7
7
-
8
8
-
const ALLOWED_METHODS = [
9
9
-
'user.getRecentTracks',
10
10
-
'user.getTopTracks',
11
11
-
'user.getTopAlbums',
12
12
-
'user.getInfo'
13
13
-
];
14
14
-
15
15
-
const CACHE_TTL: Record<string, number> = {
16
16
-
'user.getRecentTracks': 15 * 60,
17
17
-
'user.getTopTracks': 60 * 60,
18
18
-
'user.getTopAlbums': 60 * 60,
19
19
-
'user.getInfo': 12 * 60 * 60
20
20
-
};
21
21
-
22
22
-
export const GET: RequestHandler = async ({ url, platform }) => {
23
23
-
const method = url.searchParams.get('method');
24
24
-
const user = url.searchParams.get('user');
25
25
-
const period = url.searchParams.get('period') || '7day';
26
26
-
const limit = url.searchParams.get('limit') || '50';
27
27
-
28
28
-
if (!method || !user) {
29
29
-
return json({ error: 'Missing method or user parameter' }, { status: 400 });
30
30
-
}
31
31
-
32
32
-
if (!ALLOWED_METHODS.includes(method)) {
33
33
-
return json({ error: 'Method not allowed' }, { status: 400 });
34
34
-
}
35
35
-
36
36
-
const cache = createCache(platform);
37
37
-
const cacheKey = `${method}:${user}:${period}:${limit}`;
38
38
-
const cachedData = await cache?.get('lastfm', cacheKey);
39
39
-
40
40
-
if (cachedData) {
41
41
-
return json(JSON.parse(cachedData));
42
42
-
}
43
43
-
44
44
-
const apiKey = env?.LASTFM_API_KEY;
45
45
-
if (!apiKey) {
46
46
-
return json({ error: 'Last.fm API key not configured' }, { status: 500 });
47
47
-
}
48
48
-
49
49
-
try {
50
50
-
const params = new URLSearchParams({
51
51
-
method,
52
52
-
user,
53
53
-
api_key: apiKey,
54
54
-
format: 'json',
55
55
-
limit
56
56
-
});
57
57
-
58
58
-
if (method === 'user.getTopTracks' || method === 'user.getTopAlbums') {
59
59
-
params.set('period', period);
60
60
-
}
61
61
-
62
62
-
const response = await fetch(`${LASTFM_API_URL}?${params}`);
63
63
-
64
64
-
if (!response.ok) {
65
65
-
return json(
66
66
-
{ error: 'Failed to fetch Last.fm data: ' + response.statusText },
67
67
-
{ status: response.status }
68
68
-
);
69
69
-
}
70
70
-
71
71
-
const data = await response.json();
72
72
-
73
73
-
if (data.error) {
74
74
-
return json({ error: data.message || 'Last.fm API error' }, { status: 400 });
75
75
-
}
76
76
-
77
77
-
const ttl = CACHE_TTL[method] || 60 * 60;
78
78
-
await cache?.put('lastfm', cacheKey, JSON.stringify(data), ttl);
79
79
-
80
80
-
return json(data);
81
81
-
} catch (error) {
82
82
-
console.error('Error fetching Last.fm data:', error);
83
83
-
return json({ error: 'Failed to fetch Last.fm data' }, { status: 500 });
84
84
-
}
85
85
-
};
-35
src/routes/api/npmx-leaderboard/+server.ts
···
1
1
-
import { json } from '@sveltejs/kit';
2
2
-
import type { RequestHandler } from './$types';
3
3
-
import { createCache } from '$lib/cache';
4
4
-
5
5
-
const LEADERBOARD_API_URL =
6
6
-
'https://npmx-likes-leaderboard-api-production.up.railway.app/api/leaderboard/likes?limit=20';
7
7
-
8
8
-
export const GET: RequestHandler = async ({ platform }) => {
9
9
-
const cache = createCache(platform);
10
10
-
const cachedData = await cache?.get('npmx', 'likes');
11
11
-
12
12
-
if (cachedData) {
13
13
-
return json(JSON.parse(cachedData));
14
14
-
}
15
15
-
16
16
-
try {
17
17
-
const response = await fetch(LEADERBOARD_API_URL);
18
18
-
19
19
-
if (!response.ok) {
20
20
-
return json(
21
21
-
{ error: 'Failed to fetch npmx leaderboard ' + response.statusText },
22
22
-
{ status: response.status }
23
23
-
);
24
24
-
}
25
25
-
26
26
-
const data = await response.json();
27
27
-
28
28
-
await cache?.put('npmx', 'likes', JSON.stringify(data));
29
29
-
30
30
-
return json(data);
31
31
-
} catch (error) {
32
32
-
console.error('Error fetching npmx leaderboard:', error);
33
33
-
return json({ error: 'Failed to fetch npmx leaderboard' }, { status: 500 });
34
34
-
}
35
35
-
};