refactor(git-provider): update UnauthorizedGitProvider to use service prop and enhance access handling

- Changed the prop name from 'application' to 'service' in the UnauthorizedGitProvider component for clarity.
- Updated the logic to check for unauthorized access to the git provider in the compose router, returning a new field 'hasGitProviderAccess'.
- Implemented a disconnect functionality for git providers in the ShowProviderFormCompose component, providing user feedback on success or failure.
This commit is contained in:
Mauricio Siu
2025-06-21 21:03:31 -06:00
parent 9686848090
commit d97461d820
4 changed files with 173 additions and 27 deletions

View File

@@ -122,7 +122,7 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<UnauthorizedGitProvider <UnauthorizedGitProvider
application={application} service={application}
onDisconnect={handleDisconnect} onDisconnect={handleDisconnect}
/> />
</CardContent> </CardContent>

View File

@@ -9,15 +9,17 @@ import { DialogAction } from "@/components/shared/dialog-action";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import type { RouterOutputs } from "@/utils/api";
import { AlertCircle, GitBranch, Unlink } from "lucide-react"; import { AlertCircle, GitBranch, Unlink } from "lucide-react";
export const UnauthorizedGitProvider = ({ interface Props {
application, service:
onDisconnect, | RouterOutputs["application"]["one"]
}: { | RouterOutputs["compose"]["one"];
application: any;
onDisconnect: () => void; onDisconnect: () => void;
}) => { }
export const UnauthorizedGitProvider = ({ service, onDisconnect }: Props) => {
const getProviderIcon = (sourceType: string) => { const getProviderIcon = (sourceType: string) => {
switch (sourceType) { switch (sourceType) {
case "github": case "github":
@@ -36,35 +38,35 @@ export const UnauthorizedGitProvider = ({
}; };
const getRepositoryInfo = () => { const getRepositoryInfo = () => {
switch (application.sourceType) { switch (service.sourceType) {
case "github": case "github":
return { return {
repo: application.repository, repo: service.repository,
branch: application.branch, branch: service.branch,
owner: application.owner, owner: service.owner,
}; };
case "gitlab": case "gitlab":
return { return {
repo: application.gitlabRepository, repo: service.gitlabRepository,
branch: application.gitlabBranch, branch: service.gitlabBranch,
owner: application.gitlabOwner, owner: service.gitlabOwner,
}; };
case "bitbucket": case "bitbucket":
return { return {
repo: application.bitbucketRepository, repo: service.bitbucketRepository,
branch: application.bitbucketBranch, branch: service.bitbucketBranch,
owner: application.bitbucketOwner, owner: service.bitbucketOwner,
}; };
case "gitea": case "gitea":
return { return {
repo: application.giteaRepository, repo: service.giteaRepository,
branch: application.giteaBranch, branch: service.giteaBranch,
owner: application.giteaOwner, owner: service.giteaOwner,
}; };
case "git": case "git":
return { return {
repo: application.customGitUrl, repo: service.customGitUrl,
branch: application.customGitBranch, branch: service.customGitBranch,
owner: null, owner: null,
}; };
default: default:
@@ -79,7 +81,7 @@ export const UnauthorizedGitProvider = ({
<Alert> <Alert>
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertDescription> <AlertDescription>
This application is connected to a {application.sourceType} repository This application is connected to a {service.sourceType} repository
through a git provider that you don't have access to. You can see through a git provider that you don't have access to. You can see
basic repository information below, but cannot modify the basic repository information below, but cannot modify the
configuration. configuration.
@@ -89,9 +91,9 @@ export const UnauthorizedGitProvider = ({
<Card className="border-dashed border-2 border-muted-foreground/20 bg-transparent"> <Card className="border-dashed border-2 border-muted-foreground/20 bg-transparent">
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2">
{getProviderIcon(application.sourceType)} {getProviderIcon(service.sourceType)}
<span className="capitalize text-sm font-medium"> <span className="capitalize text-sm font-medium">
{application.sourceType} Repository {service.sourceType} Repository
</span> </span>
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>

View File

@@ -18,6 +18,8 @@ import { SaveGitProviderCompose } from "./save-git-provider-compose";
import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose"; import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose";
import { SaveGithubProviderCompose } from "./save-github-provider-compose"; import { SaveGithubProviderCompose } from "./save-github-provider-compose";
import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose"; import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose";
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
import { toast } from "sonner";
type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea"; type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea";
interface Props { interface Props {
@@ -34,12 +36,29 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => {
const { data: giteaProviders, isLoading: isLoadingGitea } = const { data: giteaProviders, isLoading: isLoadingGitea } =
api.gitea.giteaProviders.useQuery(); api.gitea.giteaProviders.useQuery();
const { data: compose } = api.compose.one.useQuery({ composeId }); const { mutateAsync: disconnectGitProvider } =
api.compose.disconnectGitProvider.useMutation();
const { data: compose, refetch } = api.compose.one.useQuery({ composeId });
const [tab, setSab] = useState<TabState>(compose?.sourceType || "github"); const [tab, setSab] = useState<TabState>(compose?.sourceType || "github");
const isLoading = const isLoading =
isLoadingGithub || isLoadingGitlab || isLoadingBitbucket || isLoadingGitea; isLoadingGithub || isLoadingGitlab || isLoadingBitbucket || isLoadingGitea;
const handleDisconnect = async () => {
try {
await disconnectGitProvider({ composeId });
toast.success("Repository disconnected successfully");
await refetch();
} catch (error) {
toast.error(
`Failed to disconnect repository: ${
error instanceof Error ? error.message : "Unknown error"
}`,
);
}
};
if (isLoading) { if (isLoading) {
return ( return (
<Card className="group relative w-full bg-transparent"> <Card className="group relative w-full bg-transparent">
@@ -68,6 +87,37 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => {
); );
} }
// Check if user doesn't have access to the current git provider
if (
compose &&
!compose.hasGitProviderAccess &&
compose.sourceType !== "raw"
) {
return (
<Card className="group relative w-full bg-transparent">
<CardHeader>
<CardTitle className="flex items-start justify-between">
<div className="flex flex-col gap-2">
<span className="flex flex-col space-y-0.5">Provider</span>
<p className="flex items-center text-sm font-normal text-muted-foreground">
Repository connection through unauthorized provider
</p>
</div>
<div className="hidden space-y-1 text-sm font-normal md:block">
<GitBranch className="size-6 text-muted-foreground" />
</div>
</CardTitle>
</CardHeader>
<CardContent>
<UnauthorizedGitProvider
service={compose}
onDisconnect={handleDisconnect}
/>
</CardContent>
</Card>
);
}
return ( return (
<Card className="group relative w-full bg-transparent"> <Card className="group relative w-full bg-transparent">
<CardHeader> <CardHeader>

View File

@@ -28,6 +28,7 @@ import {
deleteMount, deleteMount,
findComposeById, findComposeById,
findDomainsByComposeId, findDomainsByComposeId,
findGitProviderById,
findProjectById, findProjectById,
findServerById, findServerById,
findUserById, findUserById,
@@ -119,7 +120,45 @@ export const composeRouter = createTRPCRouter({
message: "You are not authorized to access this compose", message: "You are not authorized to access this compose",
}); });
} }
return compose;
let hasGitProviderAccess = true;
let unauthorizedProvider: string | null = null;
const getGitProviderId = () => {
switch (compose.sourceType) {
case "github":
return compose.github?.gitProviderId;
case "gitlab":
return compose.gitlab?.gitProviderId;
case "bitbucket":
return compose.bitbucket?.gitProviderId;
case "gitea":
return compose.gitea?.gitProviderId;
default:
return null;
}
};
const gitProviderId = getGitProviderId();
if (gitProviderId) {
try {
const gitProvider = await findGitProviderById(gitProviderId);
if (gitProvider.userId !== ctx.session.userId) {
hasGitProviderAccess = false;
unauthorizedProvider = compose.sourceType;
}
} catch {
hasGitProviderAccess = false;
unauthorizedProvider = compose.sourceType;
}
}
return {
...compose,
hasGitProviderAccess,
unauthorizedProvider,
};
}), }),
update: protectedProcedure update: protectedProcedure
@@ -526,6 +565,61 @@ export const composeRouter = createTRPCRouter({
const uniqueTags = _.uniq(allTags); const uniqueTags = _.uniq(allTags);
return uniqueTags; return uniqueTags;
}), }),
disconnectGitProvider: protectedProcedure
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to disconnect this git provider",
});
}
// Reset all git provider related fields
await updateCompose(input.composeId, {
// GitHub fields
repository: null,
branch: null,
owner: null,
composePath: undefined,
githubId: null,
triggerType: "push",
// GitLab fields
gitlabRepository: null,
gitlabOwner: null,
gitlabBranch: null,
gitlabId: null,
gitlabProjectId: null,
gitlabPathNamespace: null,
// Bitbucket fields
bitbucketRepository: null,
bitbucketOwner: null,
bitbucketBranch: null,
bitbucketId: null,
// Gitea fields
giteaRepository: null,
giteaOwner: null,
giteaBranch: null,
giteaId: null,
// Custom Git fields
customGitBranch: null,
customGitUrl: null,
customGitSSHKeyId: null,
// Common fields
sourceType: "github", // Reset to default
composeStatus: "idle",
watchPaths: null,
enableSubmodules: false,
});
return true;
}),
move: protectedProcedure move: protectedProcedure
.input( .input(