From 08831481b8e2d66797eb2642c81cdce679deada2 Mon Sep 17 00:00:00 2001 From: ngc2207 Date: Sat, 14 Dec 2024 17:52:15 +0800 Subject: [PATCH] feat(playground): integrate Gitea API for file tree retrieval and update PlaygroundSidebar component --- .gitignore | 3 + package.json | 1 + src/app/actions/index.ts | 22 ++++ .../components/playground-sidebar.tsx | 123 +++++++++++------- src/lib/gitea.ts | 13 ++ 5 files changed, 116 insertions(+), 46 deletions(-) create mode 100644 src/app/actions/index.ts create mode 100644 src/lib/gitea.ts diff --git a/.gitignore b/.gitignore index a840f95..8b99f98 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ cache # bun bun.lockb + +# vscode +.vscode diff --git a/package.json b/package.json index 017ea2a..97d8984 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@radix-ui/react-tooltip": "^1.1.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cross-fetch": "^4.0.0", "gitea-js": "^1.22.0", "jotai": "^2.10.3", "lucide-react": "^0.468.0", diff --git a/src/app/actions/index.ts b/src/app/actions/index.ts new file mode 100644 index 0000000..171e1f0 --- /dev/null +++ b/src/app/actions/index.ts @@ -0,0 +1,22 @@ +import api from "@/lib/gitea"; +import { GitEntry, APIError } from "gitea-js"; + +export async function retrieveTreeStructure( + owner: string, + repo: string, + sha: string +): Promise { + try { + const response = await api.repos.getTree(owner, repo, sha, { + recursive: true, + }); + return response.data.tree || []; + } catch (error) { + if ((error as APIError).message) { + console.error("Gitea API Error", error); + } else { + console.error("Unexpected Error", error); + } + throw new Error("Failed to retrieve tree structure"); + } +} diff --git a/src/app/playground/components/playground-sidebar.tsx b/src/app/playground/components/playground-sidebar.tsx index 852dace..4cee51f 100644 --- a/src/app/playground/components/playground-sidebar.tsx +++ b/src/app/playground/components/playground-sidebar.tsx @@ -1,3 +1,4 @@ +import { auth } from "@/auth"; import * as React from "react"; import { Sidebar, @@ -11,45 +12,78 @@ import { SidebarMenuSub, SidebarRail, } from "@/components/ui/sidebar"; +import { GitEntry } from "gitea-js"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; +import { retrieveTreeStructure } from "@/app/actions"; import { ChevronRight, File, Folder } from "lucide-react"; -const data = { - tree: [ - [ - "app", - [ - "api", - ["hello", ["route.ts"]], - "page.tsx", - "layout.tsx", - ["blog", ["page.tsx"]], - ], - ], - [ - "components", - ["ui", "button.tsx", "card.tsx"], - "header.tsx", - "footer.tsx", - ], - ["lib", ["util.ts"]], - ["public", "favicon.ico", "vercel.svg"], - ".eslintrc.json", - ".gitignore", - "next.config.js", - "tailwind.config.js", - "package.json", - "README.md", - ], -}; +interface FileTree { + name: string; + type: "blob" | "tree"; + children?: { [key: string]: FileTree }; +} -export function PlaygroundSidebar({ +export function buildFileTree(tree: GitEntry[]): FileTree { + const root: FileTree = { name: "", type: "tree", children: {} }; + + tree.forEach((entry: GitEntry) => { + if (!entry.path) return; + + const pathParts = entry.path.split("/"); + let currentLevel = root; + + pathParts.forEach((part: string) => { + if (!currentLevel.children) { + currentLevel.children = {}; + } + + if (!currentLevel.children[part]) { + currentLevel.children[part] = { + name: part, + type: entry.type as "blob" | "tree", + children: {}, + }; + } + + currentLevel = currentLevel.children[part]; + }); + }); + + if (root.children) { + const sortedChildren = Object.values(root.children).sort((a, b) => { + if (a.type === "tree" && b.type === "blob") return -1; + if (a.type === "blob" && b.type === "tree") return 1; + return a.name.localeCompare(b.name); + }); + + root.children = sortedChildren.reduce((acc, child) => { + acc[child.name] = child; + return acc; + }, {} as { [key: string]: FileTree }); + } + + return root; +} + +export async function PlaygroundSidebar({ ...props }: React.ComponentProps) { + const session = await auth(); + const username = session?.user?.name ?? ""; + let fileTree: FileTree = { name: "", type: "tree", children: {} }; + if (username) { + const tree: GitEntry[] = await retrieveTreeStructure( + username, + "playground", + "main" + ); + fileTree = buildFileTree(tree); + } + return ( @@ -57,9 +91,10 @@ export function PlaygroundSidebar({ Files - {data.tree.map((item, index) => ( - - ))} + {fileTree.children && + Object.values(fileTree.children).map((item, index) => ( + + ))} @@ -69,25 +104,20 @@ export function PlaygroundSidebar({ ); } -function Tree({ item }: { item: string | any[] }) { - const [name, ...items] = Array.isArray(item) ? item : [item]; - if (!items.length) { +function Tree({ item }: { item: FileTree }) { + const { name, type, children } = item; + if (type === "blob") { return ( - + {name} ); } + return ( - + @@ -97,9 +127,10 @@ function Tree({ item }: { item: string | any[] }) { - {items.map((subItem, index) => ( - - ))} + {children && + Object.values(children).map((subItem, index) => ( + + ))} diff --git a/src/lib/gitea.ts b/src/lib/gitea.ts new file mode 100644 index 0000000..fe95069 --- /dev/null +++ b/src/lib/gitea.ts @@ -0,0 +1,13 @@ +import fetch from "cross-fetch"; +import { giteaApi } from "gitea-js"; + +if (!process.env.GITEA_URL) { + throw new Error("GITEA_URL environment variable is not defined"); +} + +const api = giteaApi(process.env.GITEA_URL, { + token: process.env.GITEA_TOKEN, + customFetch: fetch, +}); + +export default api;