diff --git a/actions/repositories.ts b/actions/repositories.ts index 9dbe498..8d5ae92 100644 --- a/actions/repositories.ts +++ b/actions/repositories.ts @@ -560,21 +560,19 @@ export type FileEntry = { lastCommit: { message: string; timestamp: number } | null; }; -async function fetchGitData(userId: string, repoName: string, defaultBranch: string) { +async function fetchFileTree(userId: string, repoName: string, defaultBranch: string) { const repoPrefix = getRepoPrefix(userId, `${repoName}.git`); const fs = createR2Fs(repoPrefix); let files: FileEntry[] = []; let isEmpty = true; - let readmeContent: string | null = null; let branches: string[] = []; - let commitCount = 0; + let readmeOid: string | null = null; try { - const [branchList, commits] = await Promise.all([git.listBranches({ fs, gitdir: "/" }), git.log({ fs, gitdir: "/", ref: defaultBranch })]); + const [branchList, commits] = await Promise.all([git.listBranches({ fs, gitdir: "/" }), git.log({ fs, gitdir: "/", ref: defaultBranch, depth: 1 })]); branches = branchList; - commitCount = commits.length; if (commits.length > 0) { isEmpty = false; @@ -614,22 +612,57 @@ async function fetchGitData(userId: string, repoName: string, defaultBranch: str const readmeEntry = tree.find((e) => e.path.toLowerCase() === "readme.md" && e.type === "blob"); if (readmeEntry) { - const { blob } = await git.readBlob({ fs, gitdir: "/", oid: readmeEntry.oid }); - readmeContent = new TextDecoder("utf-8").decode(blob); + readmeOid = readmeEntry.oid; } } } catch (err: unknown) { const error = err as { code?: string }; if (error.code !== "NotFoundError") { - console.error("fetchGitData error:", err); + console.error("fetchFileTree error:", err); } } - return { files, isEmpty, readmeContent, branches, commitCount }; + return { files, isEmpty, branches, readmeOid }; } -const getCachedGitData = (owner: string, repoName: string, userId: string, defaultBranch: string) => - unstable_cache(() => fetchGitData(userId, repoName, defaultBranch), [`git-data`, owner, repoName], { +async function fetchReadme(userId: string, repoName: string, readmeOid: string) { + const repoPrefix = getRepoPrefix(userId, `${repoName}.git`); + const fs = createR2Fs(repoPrefix); + + try { + const { blob } = await git.readBlob({ fs, gitdir: "/", oid: readmeOid }); + return new TextDecoder("utf-8").decode(blob); + } catch { + return null; + } +} + +async function fetchCommitCount(userId: string, repoName: string, defaultBranch: string) { + const repoPrefix = getRepoPrefix(userId, `${repoName}.git`); + const fs = createR2Fs(repoPrefix); + + try { + const commits = await git.log({ fs, gitdir: "/", ref: defaultBranch }); + return commits.length; + } catch { + return 0; + } +} + +const getCachedFileTree = (owner: string, repoName: string, userId: string, defaultBranch: string) => + unstable_cache(() => fetchFileTree(userId, repoName, defaultBranch), [`file-tree`, owner, repoName], { + tags: [`repo:${owner}/${repoName}`], + revalidate: 3600, + })(); + +const getCachedReadme = (owner: string, repoName: string, userId: string, readmeOid: string) => + unstable_cache(() => fetchReadme(userId, repoName, readmeOid), [`readme`, owner, repoName, readmeOid], { + tags: [`repo:${owner}/${repoName}`], + revalidate: 3600, + })(); + +const getCachedCommitCount = (owner: string, repoName: string, userId: string, defaultBranch: string) => + unstable_cache(() => fetchCommitCount(userId, repoName, defaultBranch), [`commit-count`, owner, repoName], { tags: [`repo:${owner}/${repoName}`], revalidate: 3600, })(); @@ -649,10 +682,10 @@ export async function getRepoPageData(owner: string, repoName: string) { return null; } - const [starCountResult, starredResult, gitData] = await Promise.all([ + const [starCountResult, starredResult, fileTreeData] = await Promise.all([ db.select({ count: count() }).from(stars).where(eq(stars.repositoryId, repo.id)), session?.user ? db.query.stars.findFirst({ where: and(eq(stars.userId, session.user.id), eq(stars.repositoryId, repo.id)) }) : Promise.resolve(null), - getCachedGitData(owner, repoName, user.id, repo.defaultBranch), + getCachedFileTree(owner, repoName, user.id, repo.defaultBranch), ]); const starCount = starCountResult[0]?.count ?? 0; @@ -666,7 +699,25 @@ export async function getRepoPageData(owner: string, repoName: string) { starCount, starred, }, - ...gitData, + ...fileTreeData, isOwner, }; } + +export async function getRepoReadme(owner: string, repoName: string, readmeOid: string) { + const user = await db.query.users.findFirst({ where: eq(users.username, owner) }); + if (!user) return null; + return getCachedReadme(owner, repoName, user.id, readmeOid); +} + +export async function getRepoCommitCountCached(owner: string, repoName: string) { + const user = await db.query.users.findFirst({ where: eq(users.username, owner) }); + if (!user) return 0; + + const repo = await db.query.repositories.findFirst({ + where: and(eq(repositories.ownerId, user.id), eq(repositories.name, repoName)), + }); + if (!repo) return 0; + + return getCachedCommitCount(owner, repoName, user.id, repo.defaultBranch); +} diff --git a/app/(main)/[username]/[repo]/page.tsx b/app/(main)/[username]/[repo]/page.tsx index 78e1b88..36f0be1 100644 --- a/app/(main)/[username]/[repo]/page.tsx +++ b/app/(main)/[username]/[repo]/page.tsx @@ -1,5 +1,6 @@ +import { Suspense } from "react"; import { notFound } from "next/navigation"; -import { getRepoPageData } from "@/actions/repositories"; +import { getRepoPageData, getRepoReadme, getRepoCommitCountCached } from "@/actions/repositories"; import { FileTree } from "@/components/file-tree"; import { CodeViewer } from "@/components/code-viewer"; import { CloneUrl } from "@/components/clone-url"; @@ -7,10 +8,68 @@ import { StarButton } from "@/components/star-button"; import { BranchSelector } from "@/components/branch-selector"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Lock, Globe, FileCode, Settings, GitCommit, GitBranch } from "lucide-react"; +import { Lock, Globe, FileCode, Settings, GitCommit, GitBranch, Loader2 } from "lucide-react"; import Link from "next/link"; import { getPublicServerUrl } from "@/lib/utils"; +async function CommitCount({ username, repoName, branch }: { username: string; repoName: string; branch: string }) { + const commitCount = await getRepoCommitCountCached(username, repoName); + + if (commitCount === 0) return null; + + return ( + + + {commitCount} + commits + + ); +} + +async function ReadmeSection({ username, repoName, readmeOid }: { username: string; repoName: string; readmeOid: string }) { + const content = await getRepoReadme(username, repoName, readmeOid); + + if (!content) return null; + + return ( +
+
+ + README.md +
+
+ +
+
+ ); +} + +function ReadmeSkeleton() { + return ( +
+
+ + README.md +
+
+ +
+
+ ); +} + +function CommitCountSkeleton() { + return ( +
+ + +
+ ); +} + export default async function RepoPage({ params }: { params: Promise<{ username: string; repo: string }> }) { const { username, repo: repoName } = await params; @@ -20,7 +79,7 @@ export default async function RepoPage({ params }: { params: Promise<{ username: notFound(); } - const { repo, files, isEmpty, readmeContent, branches, commitCount, isOwner } = data; + const { repo, files, isEmpty, branches, readmeOid, isOwner } = data; return (
@@ -67,16 +126,9 @@ export default async function RepoPage({ params }: { params: Promise<{ username:
- {commitCount > 0 && ( - - - {commitCount} - commits - - )} + }> + +
{isEmpty ? ( @@ -86,16 +138,10 @@ export default async function RepoPage({ params }: { params: Promise<{ username: )}
- {readmeContent && ( -
-
- - README.md -
-
- -
-
+ {readmeOid && ( + }> + + )}