Skip to content

Commit bb03ed0

Browse files
committed
Use content collections for Docker and GitHub projects
1 parent 3f08da3 commit bb03ed0

File tree

4 files changed

+153
-113
lines changed

4 files changed

+153
-113
lines changed

src/components/DockerHubCard.astro

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,25 @@
22
import { shortenNumberWithK } from "../utils/shortenNumber";
33
44
const { image } = Astro.props;
5+
const { owner, repo, github_url, hub_details } = image.data;
56
---
67

78
<div
8-
id={`image-${image.repo}`}
9+
id={`image-${repo}`}
910
class="grid grid-rows-[1fr_auto] rounded-2xl border border-[var(--accent-main)] bg-[var(--accent-background)] p-4 text-gray-400 shadow-lg"
1011
>
1112
<div class="">
1213
<div class="grid gap-1">
1314
<div class="flex items-center justify-between">
1415
<a
15-
href=`https://hub.docker.com/r/${image.owner}/${image.repo}`
16+
href=`https://hub.docker.com/r/${owner}/${repo}`
1617
target="_blank"
1718
class="passing-underline flex-none text-base font-bold text-gray-200 hover:text-gray-200"
1819
>
19-
{image.owner}/{image.repo}
20+
{owner}/{repo}
2021
</a>
2122
</div>
22-
<p class="text-sm">{image.details.description}</p>
23+
<p class="text-sm">{hub_details.description}</p>
2324
</div>
2425
</div>
2526
<div class="">
@@ -31,16 +32,16 @@ const { image } = Astro.props;
3132
d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"
3233
></path>
3334
</svg>
34-
<p>{shortenNumberWithK(image.details.star_count)} stars</p>
35+
<p>{shortenNumberWithK(hub_details.star_count)} stars</p>
3536
</div>
3637
<div class="inline-flex items-center gap-1">
3738
<svg fill="none" aria-hidden="true" viewBox="0 0 24 24" class="size-5"
3839
><path fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></svg
3940
>
40-
<p>{shortenNumberWithK(image.details.pull_count)} pulls</p>
41+
<p>{shortenNumberWithK(hub_details.pull_count)} pulls</p>
4142
</div>
4243
<div class="inline-flex items-center">
43-
<span>View source on <a href={image.github_url} target="_blank">GitHub</a></span>
44+
<span>View source on <a href={github_url} target="_blank">GitHub</a></span>
4445
</div>
4546
</div>
4647
</div>

src/components/GitHubRepoCard.astro

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,28 @@
22
import { shortenNumberWithK } from "../utils/shortenNumber";
33
44
const { repo } = Astro.props;
5+
const repoData = repo.data;
56
---
67

78
<div
8-
id={`project-${repo.id}`}
9+
id={`project-${repoData.id}`}
910
class="grid grid-rows-[1fr_auto] rounded-2xl border border-[var(--accent-main)] bg-[var(--accent-background)] p-4 shadow-lg"
1011
>
1112
<div class="">
1213
<div class="grid gap-1">
1314
<div class="flex items-center justify-between">
1415
<a
15-
href={repo.html_url}
16+
href={repoData.html_url}
1617
target="_blank"
1718
class="passing-underline flex-none text-base font-bold text-gray-200 hover:text-gray-200"
1819
>
19-
{repo.name}
20+
{repoData.name}
2021
</a>
2122
</div>
22-
<p class="text-sm text-gray-400">{repo.description}</p>
23+
<p class="text-sm text-gray-400">{repoData.description}</p>
2324
<div class="flex flex-wrap gap-x-2 py-1">
2425
{
25-
repo.topics.map((topic: string) => (
26+
repoData.topics.map((topic: string) => (
2627
<a
2728
href={`https://github.com/topics/${topic}`}
2829
target="_blank"
@@ -44,10 +45,10 @@ const { repo } = Astro.props;
4445
d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"
4546
></path>
4647
</svg>
47-
<p>{shortenNumberWithK(repo.stars)} stars</p>
48+
<p>{shortenNumberWithK(repoData.stars)} stars</p>
4849
</div>
49-
<span class="empty:hidden">{repo.language}</span>
50-
{repo.license && <span class="empty:hidden">{repo.license.spdx_id}</span>}
50+
<span class="empty:hidden">{repoData.language}</span>
51+
{repoData.license && <span class="empty:hidden">{repoData.license.spdx_id}</span>}
5152
</div>
5253
</div>
5354
</div>

src/content.config.ts

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { glob } from "astro/loaders";
22
import { defineCollection, z } from "astro:content";
33
import { rssSchema } from "@astrojs/rss";
44
import raycastExtensionsList from "./data/raycastExtensions.json";
5+
import dockerHubImagesList from "./data/dockerHubImages.json";
6+
import githubReposList from "./data/githubRepos.json";
7+
import { Octokit } from "@octokit/rest";
8+
import { GH_API } from "astro:env/server";
59

610
const blog = defineCollection({
711
loader: glob({ base: "./src/blog", pattern: "**/*.{md,mdx}" }),
@@ -180,4 +184,126 @@ const raycastExtensions = defineCollection({
180184
}),
181185
});
182186

183-
export const collections = { blog, raycastExtensions };
187+
const dockerHubImages = defineCollection({
188+
loader: {
189+
name: "docker-hub-images-loader",
190+
load: async ({ store, logger }) => {
191+
logger.info("Loading Docker Hub images");
192+
store.clear();
193+
194+
for (const image of dockerHubImagesList) {
195+
const res = await fetch(
196+
`https://hub.docker.com/v2/repositories/${image.owner}/${image.repo}`,
197+
);
198+
const json = await res.json();
199+
200+
store.set({
201+
id: `${image.owner}/${image.repo}`,
202+
data: {
203+
owner: image.owner,
204+
repo: image.repo,
205+
github_url: image.github_url,
206+
hub_details: json,
207+
},
208+
});
209+
}
210+
},
211+
},
212+
schema: z.object({
213+
owner: z.string(),
214+
repo: z.string(),
215+
github_url: z.string().url(),
216+
hub_details: z.object({
217+
user: z.string(),
218+
name: z.string(),
219+
namespace: z.string(),
220+
description: z.string(),
221+
pull_count: z.number(),
222+
star_count: z.number(),
223+
last_updated: z.string(),
224+
date_registered: z.string(),
225+
}),
226+
}),
227+
});
228+
229+
const githubRepos = defineCollection({
230+
loader: {
231+
name: "github-repos-loader",
232+
load: async ({ store, logger }) => {
233+
logger.info("Loading GitHub repositories");
234+
store.clear();
235+
236+
const octokit = new Octokit({
237+
auth: GH_API,
238+
userAgent: "sebdanielsson/sebdanielsson.dev",
239+
});
240+
241+
for (const repo of githubReposList) {
242+
try {
243+
const response = await octokit.repos.get({
244+
owner: repo.owner,
245+
repo: repo.repo,
246+
});
247+
248+
const repoData = response.data;
249+
250+
store.set({
251+
id: `${repo.owner}/${repo.repo}`,
252+
data: {
253+
id: repoData.id,
254+
owner: repoData.owner.login,
255+
owner_url: repoData.owner.html_url,
256+
name: repoData.name,
257+
full_name: repoData.full_name,
258+
html_url: repoData.html_url,
259+
description: repoData.description,
260+
created_at: repoData.created_at,
261+
updated_at: repoData.updated_at,
262+
pushed_at: repoData.pushed_at,
263+
stars: repoData.stargazers_count,
264+
language: repoData.language,
265+
topics: repoData.topics ?? [],
266+
license: repoData.license
267+
? {
268+
key: repoData.license.key,
269+
name: repoData.license.name,
270+
url: repoData.license.url,
271+
spdx_id: repoData.license.spdx_id,
272+
node_id: repoData.license.node_id,
273+
}
274+
: null,
275+
},
276+
});
277+
} catch (error) {
278+
logger.error(`Error fetching repository ${repo.owner}/${repo.repo}: ${error}`);
279+
}
280+
}
281+
},
282+
},
283+
schema: z.object({
284+
id: z.number(),
285+
owner: z.string(),
286+
owner_url: z.string().url(),
287+
name: z.string(),
288+
full_name: z.string(),
289+
html_url: z.string().url(),
290+
description: z.string().nullable(),
291+
created_at: z.string(),
292+
updated_at: z.string(),
293+
pushed_at: z.string(),
294+
stars: z.number(),
295+
language: z.string().nullable(),
296+
topics: z.array(z.string()),
297+
license: z
298+
.object({
299+
key: z.string(),
300+
name: z.string(),
301+
url: z.string().url().nullable(),
302+
spdx_id: z.string().nullable(),
303+
node_id: z.string(),
304+
})
305+
.nullable(),
306+
}),
307+
});
308+
309+
export const collections = { blog, raycastExtensions, dockerHubImages, githubRepos };

src/pages/projects.astro

Lines changed: 9 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -3,125 +3,37 @@ import Layout from "@/layouts/Layout.astro";
33
import GitHubRepoCard from "@/components/GitHubRepoCard.astro";
44
import RaycastStoreCard from "@/components/RaycastStoreCard.astro";
55
import DockerHubCard from "@/components/DockerHubCard.astro";
6-
import { Octokit } from "@octokit/rest";
76
import { getCollection } from "astro:content";
8-
import githubRepos from "@/data/githubRepos.json";
9-
import dockerHubImages from "@/data/dockerHubImages.json";
10-
import type GitHubRepoData from "@/interfaces/gitHubRepoData";
11-
import type GitHubRepo from "@/interfaces/gitHubRepo";
12-
import type DockerHubRepoData from "@/interfaces/dockerHubRepoData";
13-
import { GH_API } from "astro:env/server";
147
158
// Fetch Raycast extensions from collection
169
const raycastExtensions = await getCollection("raycastExtensions");
1710
18-
// Fetch repo details
19-
const octokit = new Octokit({
20-
auth: GH_API,
21-
userAgent: "sebdanielsson/sebdanielsson.dev",
22-
});
23-
24-
// Function to fetch and transform repo details to match the GitHubRepoData interface
25-
async function getRepoDetails(owner: string, repo: string): Promise<GitHubRepoData | null> {
26-
try {
27-
const response = await octokit.repos.get({
28-
owner,
29-
repo,
30-
});
31-
32-
const repoData = response.data;
33-
34-
// Transform the fetched data to match the GitHubRepoData interface
35-
return {
36-
id: repoData.id,
37-
owner: repoData.owner.login,
38-
owner_url: repoData.owner.html_url,
39-
name: repoData.name,
40-
full_name: repoData.full_name,
41-
html_url: repoData.html_url,
42-
description: repoData.description,
43-
created_at: repoData.created_at,
44-
updated_at: repoData.updated_at,
45-
pushed_at: repoData.pushed_at,
46-
stars: repoData.stargazers_count,
47-
language: repoData.language,
48-
topics: repoData.topics ?? [],
49-
license: repoData.license
50-
? {
51-
key: repoData.license.key,
52-
name: repoData.license.name,
53-
url: repoData.license.url,
54-
spdx_id: repoData.license.spdx_id,
55-
node_id: repoData.license.node_id,
56-
}
57-
: null,
58-
};
59-
} catch (error) {
60-
console.error("Error fetching repository details:", error);
61-
return null;
62-
}
63-
}
11+
// Fetch Docker Hub images from collection
12+
const dockerHubImages = await getCollection("dockerHubImages");
6413
65-
// Fetch Docker Hub repo details
66-
async function getDockerHubRepoDetails(
67-
dockerHubUser: string,
68-
dockerHubRepo: string,
69-
): Promise<DockerHubRepoData | null> {
70-
try {
71-
const response = await fetch(
72-
`https://hub.docker.com/v2/repositories/${dockerHubUser}/${dockerHubRepo}`,
73-
);
74-
const repoData = (await response.json()) as DockerHubRepoData;
14+
// Fetch GitHub repos from collection
15+
const githubRepos = await getCollection("githubRepos");
7516
76-
return repoData;
77-
} catch (error) {
78-
console.error("Error fetching Docker Hub repository details:", error);
79-
return null;
80-
}
81-
}
82-
83-
// List of GitHub repos to fetch
84-
const typedGithubRepos: GitHubRepo[] = githubRepos as GitHubRepo[];
85-
86-
// Fetch repo details
87-
const repoDetails: (GitHubRepoData | null)[] = await Promise.all(
88-
typedGithubRepos.map((repo) => getRepoDetails(repo.owner, repo.repo)),
89-
);
90-
91-
// Sort by stars
92-
repoDetails.sort((a, b) => {
93-
if (a && b) {
94-
return b.stars - a.stars;
95-
}
96-
return 0;
17+
// Sort GitHub repos by stars
18+
const sortedGithubRepos = githubRepos.sort((a, b) => {
19+
return b.data.stars - a.data.stars;
9720
});
98-
99-
// Fetch all details for Docker Hub images
100-
const dockerHubImagesWithDetails = await Promise.all(
101-
dockerHubImages.map(async (image) => {
102-
const details = await getDockerHubRepoDetails(image.owner, image.repo);
103-
return {
104-
...image,
105-
details,
106-
};
107-
}),
108-
);
10921
---
11022

11123
<Layout title="Projects">
11224
<main class="main-width py-12">
11325
<h1 class="my-0">Projects</h1>
11426
<h2>Docker Images</h2>
11527
<div class="grid grid-cols-1 gap-3 lg:grid-cols-2">
116-
{dockerHubImagesWithDetails.map((image) => <DockerHubCard image={image} />)}
28+
{dockerHubImages.map((image) => <DockerHubCard image={image} />)}
11729
</div>
11830
<h2>Raycast Extensions</h2>
11931
<div class="grid grid-cols-1 gap-3 lg:grid-cols-2">
12032
{raycastExtensions.map((extension) => <RaycastStoreCard extension={extension} />)}
12133
</div>
12234
<h2>GitHub Repos</h2>
12335
<div class="grid grid-cols-1 gap-3 lg:grid-cols-2">
124-
{repoDetails.map((repo) => <GitHubRepoCard repo={repo} type="repo" />)}
36+
{sortedGithubRepos.map((repo) => <GitHubRepoCard repo={repo} type="repo" />)}
12537
</div>
12638
</main>
12739
</Layout>

0 commit comments

Comments
 (0)