From 7c7c259d0a516d9ad1f787a900997b906ba63612 Mon Sep 17 00:00:00 2001 From: ARUNAVO RAY Date: Wed, 18 Mar 2026 04:36:14 +0530 Subject: [PATCH] fix repo links to use external gitea url (#233) --- src/components/dashboard/RepositoryList.tsx | 13 +----- .../organizations/OrganizationsList.tsx | 13 +----- .../repositories/RepositoryTable.tsx | 12 +---- src/lib/gitea-url.test.ts | 45 +++++++++++++++++++ src/lib/gitea-url.ts | 28 ++++++++++++ 5 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 src/lib/gitea-url.test.ts create mode 100644 src/lib/gitea-url.ts diff --git a/src/components/dashboard/RepositoryList.tsx b/src/components/dashboard/RepositoryList.tsx index f7591be..cb1f615 100644 --- a/src/components/dashboard/RepositoryList.tsx +++ b/src/components/dashboard/RepositoryList.tsx @@ -4,6 +4,7 @@ import { GitFork } from "lucide-react"; import { SiGithub, SiGitea } from "react-icons/si"; import type { Repository } from "@/lib/db/schema"; import { getStatusColor } from "@/lib/utils"; +import { buildGiteaWebUrl } from "@/lib/gitea-url"; import { useGiteaConfig } from "@/hooks/useGiteaConfig"; interface RepositoryListProps { @@ -15,11 +16,6 @@ export function RepositoryList({ repositories }: RepositoryListProps) { // Helper function to construct Gitea repository URL const getGiteaRepoUrl = (repository: Repository): string | null => { - const rawBaseUrl = giteaConfig?.externalUrl || giteaConfig?.url; - if (!rawBaseUrl) { - return null; - } - // Only provide Gitea links for repositories that have been or are being mirrored const validStatuses = ['mirroring', 'mirrored', 'syncing', 'synced']; if (!validStatuses.includes(repository.status)) { @@ -38,12 +34,7 @@ export function RepositoryList({ repositories }: RepositoryListProps) { repoPath = `${owner}/${repository.name}`; } - // Ensure the base URL doesn't have a trailing slash - const baseUrl = rawBaseUrl.endsWith("/") - ? rawBaseUrl.slice(0, -1) - : rawBaseUrl; - - return `${baseUrl}/${repoPath}`; + return buildGiteaWebUrl(giteaConfig, repoPath); }; return ( diff --git a/src/components/organizations/OrganizationsList.tsx b/src/components/organizations/OrganizationsList.tsx index 84691d7..5301b7b 100644 --- a/src/components/organizations/OrganizationsList.tsx +++ b/src/components/organizations/OrganizationsList.tsx @@ -9,6 +9,7 @@ import type { FilterParams } from "@/types/filter"; import Fuse from "fuse.js"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; +import { buildGiteaWebUrl } from "@/lib/gitea-url"; import { MirrorDestinationEditor } from "./MirrorDestinationEditor"; import { useGiteaConfig } from "@/hooks/useGiteaConfig"; import { @@ -67,11 +68,6 @@ export function OrganizationList({ // Helper function to construct Gitea organization URL const getGiteaOrgUrl = (organization: Organization): string | null => { - const rawBaseUrl = giteaConfig?.externalUrl || giteaConfig?.url; - if (!rawBaseUrl) { - return null; - } - // Only provide Gitea links for organizations that have been mirrored const validStatuses = ['mirroring', 'mirrored']; if (!validStatuses.includes(organization.status || '')) { @@ -84,12 +80,7 @@ export function OrganizationList({ return null; } - // Ensure the base URL doesn't have a trailing slash - const baseUrl = rawBaseUrl.endsWith("/") - ? rawBaseUrl.slice(0, -1) - : rawBaseUrl; - - return `${baseUrl}/${orgName}`; + return buildGiteaWebUrl(giteaConfig, orgName); }; const handleUpdateDestination = async (orgId: string, newDestination: string | null) => { diff --git a/src/components/repositories/RepositoryTable.tsx b/src/components/repositories/RepositoryTable.tsx index 266cce6..de0df4f 100644 --- a/src/components/repositories/RepositoryTable.tsx +++ b/src/components/repositories/RepositoryTable.tsx @@ -14,6 +14,7 @@ import { SiGithub, SiGitea } from "react-icons/si"; import type { Repository } from "@/lib/db/schema"; import { Button } from "@/components/ui/button"; import { formatLastSyncTime } from "@/lib/utils"; +import { buildGiteaWebUrl } from "@/lib/gitea-url"; import type { FilterParams } from "@/types/filter"; import { Skeleton } from "@/components/ui/skeleton"; import { useGiteaConfig } from "@/hooks/useGiteaConfig"; @@ -124,10 +125,6 @@ export default function RepositoryTable({ // Helper function to construct Gitea repository URL const getGiteaRepoUrl = (repository: Repository): string | null => { - if (!giteaConfig?.url) { - return null; - } - // Only provide Gitea links for repositories that have been or are being mirrored const validStatuses = ['mirroring', 'mirrored', 'syncing', 'synced', 'archived']; if (!validStatuses.includes(repository.status)) { @@ -144,12 +141,7 @@ export default function RepositoryTable({ repoPath = `${owner}/${repository.name}`; } - // Ensure the base URL doesn't have a trailing slash - const baseUrl = giteaConfig.url.endsWith('/') - ? giteaConfig.url.slice(0, -1) - : giteaConfig.url; - - return `${baseUrl}/${repoPath}`; + return buildGiteaWebUrl(giteaConfig, repoPath); }; const hasAnyFilter = [ diff --git a/src/lib/gitea-url.test.ts b/src/lib/gitea-url.test.ts new file mode 100644 index 0000000..6716f7a --- /dev/null +++ b/src/lib/gitea-url.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "bun:test"; +import { buildGiteaWebUrl, getGiteaWebBaseUrl } from "@/lib/gitea-url"; + +describe("getGiteaWebBaseUrl", () => { + it("prefers externalUrl when both urls are present", () => { + const baseUrl = getGiteaWebBaseUrl({ + url: "http://gitea:3000", + externalUrl: "https://git.example.com", + }); + + expect(baseUrl).toBe("https://git.example.com"); + }); + + it("falls back to url when externalUrl is missing", () => { + const baseUrl = getGiteaWebBaseUrl({ + url: "http://gitea:3000", + }); + + expect(baseUrl).toBe("http://gitea:3000"); + }); + + it("trims a trailing slash", () => { + const baseUrl = getGiteaWebBaseUrl({ + externalUrl: "https://git.example.com/", + }); + + expect(baseUrl).toBe("https://git.example.com"); + }); +}); + +describe("buildGiteaWebUrl", () => { + it("builds a full repository url and removes leading path slashes", () => { + const url = buildGiteaWebUrl( + { externalUrl: "https://git.example.com/" }, + "/org/repo" + ); + + expect(url).toBe("https://git.example.com/org/repo"); + }); + + it("returns null when no gitea url is configured", () => { + const url = buildGiteaWebUrl({}, "org/repo"); + expect(url).toBeNull(); + }); +}); diff --git a/src/lib/gitea-url.ts b/src/lib/gitea-url.ts new file mode 100644 index 0000000..70bc7a5 --- /dev/null +++ b/src/lib/gitea-url.ts @@ -0,0 +1,28 @@ +interface GiteaUrlConfig { + url?: string | null; + externalUrl?: string | null; +} + +export function getGiteaWebBaseUrl( + config?: GiteaUrlConfig | null +): string | null { + const rawBaseUrl = config?.externalUrl || config?.url; + if (!rawBaseUrl) { + return null; + } + + return rawBaseUrl.endsWith("/") ? rawBaseUrl.slice(0, -1) : rawBaseUrl; +} + +export function buildGiteaWebUrl( + config: GiteaUrlConfig | null | undefined, + path: string +): string | null { + const baseUrl = getGiteaWebBaseUrl(config); + if (!baseUrl) { + return null; + } + + const normalizedPath = path.replace(/^\/+/, ""); + return normalizedPath ? `${baseUrl}/${normalizedPath}` : baseUrl; +}