Simplify Gitea authorization flow with shared utilities

This commit is contained in:
Jason Parks
2025-03-20 16:48:59 -06:00
parent a4e4d1c467
commit 530ad31aaa
9 changed files with 558 additions and 388 deletions
@@ -1,4 +1,4 @@
import { GiteaIcon } from "@/components/icons/data-tools-icons"; // Use GiteaIcon for Gitea
import { GiteaIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import { CardContent } from "@/components/ui/card";
@@ -19,9 +19,12 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import {
type GiteaProviderResponse,
getGiteaOAuthUrl,
} from "@/utils/gitea-utils";
import { useUrl } from "@/utils/hooks/use-url";
import { zodResolver } from "@hookform/resolvers/zod";
import { ExternalLink } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
@@ -51,11 +54,14 @@ const Schema = z.object({
type Schema = z.infer<typeof Schema>;
export const AddGiteaProvider = () => {
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const url = useUrl();
const urlObj = useUrl();
const baseUrl =
typeof urlObj === "string" ? urlObj : (urlObj as any)?.url || "";
const { mutateAsync, error, isError } = api.gitea.create.useMutation();
const webhookUrl = `${url}/api/providers/gitea/callback`;
const webhookUrl = `${baseUrl}/api/providers/gitea/callback`;
const form = useForm<Schema>({
defaultValues: {
@@ -78,24 +84,52 @@ export const AddGiteaProvider = () => {
name: "",
giteaUrl: "https://gitea.com",
});
}, [form, isOpen]);
}, [form, webhookUrl, isOpen]);
const onSubmit = async (data: Schema) => {
await mutateAsync({
clientId: data.clientId || "",
clientSecret: data.clientSecret || "",
name: data.name || "",
redirectUri: data.redirectUri || "",
giteaUrl: data.giteaUrl || "https://gitea.com",
})
.then(async () => {
await utils.gitProvider.getAll.invalidate();
toast.success("Gitea provider created successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error configuring Gitea");
});
try {
// Send the form data to create the Gitea provider
const result = (await mutateAsync({
clientId: data.clientId,
clientSecret: data.clientSecret,
name: data.name,
redirectUri: data.redirectUri,
giteaUrl: data.giteaUrl,
organizationName: data.organizationName,
})) as unknown as GiteaProviderResponse;
// Check if we have a giteaId from the response
if (!result || !result.giteaId) {
toast.error("Failed to get Gitea ID from response");
return;
}
// Generate OAuth URL using the shared utility
const authUrl = getGiteaOAuthUrl(
result.giteaId,
data.clientId,
data.giteaUrl,
baseUrl,
);
// Open the Gitea OAuth URL
if (authUrl !== "#") {
window.open(authUrl, "_blank");
} else {
toast.error("Configuration Incomplete", {
description: "Please fill in Client ID and Gitea URL first.",
});
}
toast.success("Gitea provider created successfully");
setIsOpen(false);
} catch (error: unknown) {
if (error instanceof Error) {
toast.error(`Error configuring Gitea: ${error.message}`);
} else {
toast.error("An unknown error occurred.");
}
}
};
return (
@@ -109,7 +143,7 @@ export const AddGiteaProvider = () => {
<span>Gitea</span>
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen ">
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
Gitea Provider <GiteaIcon className="size-5" />
@@ -17,6 +17,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { getGiteaOAuthUrl } from "@/utils/gitea-utils";
import { useUrl } from "@/utils/hooks/use-url";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon } from "lucide-react";
@@ -55,7 +56,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
useEffect(() => {
const { connected, error } = router.query;
// Only process if router is ready and query parameters exist
if (!router.isReady) return;
if (connected) {
@@ -64,7 +64,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
id: "gitea-connection-success",
});
refetch();
// Clear the query parameters to prevent re-triggering
router.replace(
{
pathname: router.pathname,
@@ -80,7 +79,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
description: decodeURIComponent(error as string),
id: "gitea-connection-error",
});
// Clear the query parameters to prevent re-triggering
router.replace(
{
pathname: router.pathname,
@@ -102,7 +100,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
},
});
// Update form values when data is loaded
useEffect(() => {
if (gitea) {
form.reset({
@@ -141,7 +138,15 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
description: result,
});
} catch (error: any) {
const authUrl = error.authorizationUrl || getGiteaOAuthUrl();
const formValues = form.getValues();
const authUrl =
error.authorizationUrl ||
getGiteaOAuthUrl(
giteaId,
formValues.clientId,
formValues.giteaUrl,
typeof url === "string" ? url : (url as any).url || "",
);
toast.error("Gitea Not Connected", {
description:
@@ -157,32 +162,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
}
};
// Generate Gitea OAuth URL with state parameter
const getGiteaOAuthUrl = () => {
const clientId = form.getValues().clientId;
const giteaUrl = form.getValues().giteaUrl;
if (!clientId || !giteaUrl) {
toast.error("Configuration Incomplete", {
description: "Please fill in Client ID and Gitea URL first.",
});
return "#";
}
const redirectUri = `${url}/api/providers/gitea/callback`;
// Use the scopes from the gitea data (if available), else fallback to default scopes
const scopes =
gitea?.scopes?.split(",").join(" ") ||
"repo repo:status read:user read:org";
//const scopes = gitea?.scopes || 'repo,repo:status,read:user,read:org';
const state = giteaId;
return `${giteaUrl}/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scopes)}&state=${encodeURIComponent(state)}`;
};
// Show loading state if data is being fetched
if (isLoading) {
return (
<Button variant="ghost" size="icon" disabled>
@@ -282,7 +261,13 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
type="button"
variant="outline"
onClick={() => {
const authUrl = getGiteaOAuthUrl();
const formValues = form.getValues();
const authUrl = getGiteaOAuthUrl(
giteaId,
formValues.clientId,
formValues.giteaUrl,
typeof url === "string" ? url : (url as any).url || "",
);
if (authUrl !== "#") {
window.open(authUrl, "_blank");
}