From 9359ee7a04a9cd979a324a094060aa3168e08b60 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 23 Mar 2025 03:27:19 -0600 Subject: [PATCH] refactor: Update Gitea provider components and API handling - Adjusted GiteaProviderSchema to ensure watchPaths are correctly initialized and validated. - Refactored SaveGiteaProvider and SaveGiteaProviderCompose components for improved state management and UI consistency. - Simplified API router methods for Gitea, enhancing readability and error handling. - Updated database schema and service functions for better clarity and maintainability. - Removed unnecessary comments and improved logging for better debugging. --- .../general/generic/save-gitea-provider.tsx | 14 +- .../generic/save-gitea-provider-compose.tsx | 33 +- apps/dokploy/server/api/routers/gitea.ts | 307 +++++------- packages/server/src/db/schema/gitea.ts | 12 +- packages/server/src/services/gitea.ts | 19 +- packages/server/src/utils/providers/gitea.ts | 470 ++++++------------ 6 files changed, 316 insertions(+), 539 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx index 90de6b6b..fadbc4bb 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx @@ -67,13 +67,13 @@ const GiteaProviderSchema = z.object({ .object({ repo: z.string().min(1, "Repo is required"), owner: z.string().min(1, "Owner is required"), - giteaPathNamespace: z.string().min(1), + giteaPathNamespace: z.string(), id: z.number().nullable(), - watchPaths: z.array(z.string()).default([]), }) .required(), branch: z.string().min(1, "Branch is required"), giteaId: z.string().min(1, "Gitea Provider is required"), + watchPaths: z.array(z.string()).default([]), }); type GiteaProvider = z.infer; @@ -97,10 +97,10 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { repo: "", giteaPathNamespace: "", id: null, - watchPaths: [], }, giteaId: "", branch: "", + watchPaths: [], }, resolver: zodResolver(GiteaProviderSchema), }); @@ -146,10 +146,10 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { owner: data.giteaOwner || "", giteaPathNamespace: data.giteaPathNamespace || "", id: data.giteaProjectId, - watchPaths: data.watchPaths || [], }, buildPath: data.giteaBuildPath || "/", giteaId: data.giteaId || "", + watchPaths: data.watchPaths || [], }); } }, [form.reset, data, form]); @@ -164,7 +164,7 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { applicationId, giteaProjectId: data.repository.id, giteaPathNamespace: data.repository.giteaPathNamespace, - watchPaths: data.repository.watchPaths, + watchPaths: data.watchPaths, }) .then(async () => { toast.success("Service Provider Saved"); @@ -198,7 +198,6 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { repo: "", id: null, giteaPathNamespace: "", - watchPaths: [], }); form.setValue("branch", ""); }} @@ -285,7 +284,6 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { repo: repo.name, id: repo.id, giteaPathNamespace: repo.name, - watchPaths: [], }); form.setValue("branch", ""); }} @@ -413,7 +411,7 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { /> (
diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx index dce6e4f7..0f47e8a2 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx @@ -41,7 +41,7 @@ import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import type { Branch, Repository } from "@/utils/gitea-utils"; import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import { CheckIcon, ChevronsUpDown, Plus, X } from "lucide-react"; import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; @@ -55,7 +55,7 @@ const GiteaProviderSchema = z.object({ repo: z.string().min(1, "Repo is required"), owner: z.string().min(1, "Owner is required"), id: z.number().nullable(), - giteaPathNamespace: z.string().min(1), + giteaPathNamespace: z.string(), }) .required(), branch: z.string().min(1, "Branch is required"), @@ -95,6 +95,8 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => { const repository = form.watch("repository"); const giteaId = form.watch("giteaId"); + console.log(repository); + const { data: repositories, isLoading: isLoadingRepositories, @@ -126,6 +128,7 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => { useEffect(() => { if (data) { + console.log(data.giteaRepository); form.reset({ branch: data.giteaBranch || "", repository: { @@ -452,20 +455,20 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => { />
@@ -475,13 +478,11 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => { /> - +
+ +
diff --git a/apps/dokploy/server/api/routers/gitea.ts b/apps/dokploy/server/api/routers/gitea.ts index 99140774..88ad04d7 100644 --- a/apps/dokploy/server/api/routers/gitea.ts +++ b/apps/dokploy/server/api/routers/gitea.ts @@ -22,49 +22,36 @@ import { import { TRPCError } from "@trpc/server"; export const giteaRouter = createTRPCRouter({ - // Create a new Gitea provider create: protectedProcedure .input(apiCreateGitea) - .mutation( - async ({ - input, - ctx, - }: { input: typeof apiCreateGitea._input; ctx: any }) => { - try { - return await createGitea(input, ctx.session.activeOrganizationId); - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error creating this Gitea provider", - cause: error, - }); - } - }, - ), + .mutation(async ({ input, ctx }) => { + try { + return await createGitea(input, ctx.session.activeOrganizationId); + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error creating this Gitea provider", + cause: error, + }); + } + }), - // Fetch a specific Gitea provider by ID one: protectedProcedure .input(apiFindOneGitea) - .query( - async ({ - input, - ctx, - }: { input: typeof apiFindOneGitea._input; ctx: any }) => { - const giteaProvider = await findGiteaById(input.giteaId); - if ( - giteaProvider.gitProvider.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to access this Gitea provider", - }); - } - return giteaProvider; - }, - ), + .query(async ({ input, ctx }) => { + const giteaProvider = await findGiteaById(input.giteaId); + if ( + giteaProvider.gitProvider.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this Gitea provider", + }); + } + return giteaProvider; + }), - // Fetch all Gitea providers for the active organization giteaProviders: protectedProcedure.query(async ({ ctx }: { ctx: any }) => { let result = await db.query.gitea.findMany({ with: { @@ -72,14 +59,12 @@ export const giteaRouter = createTRPCRouter({ }, }); - // Filter by organization ID result = result.filter( (provider) => provider.gitProvider.organizationId === ctx.session.activeOrganizationId, ); - // Filter providers that meet the requirements const filtered = result .filter((provider) => haveGiteaRequirements(provider)) .map((provider) => { @@ -94,64 +79,88 @@ export const giteaRouter = createTRPCRouter({ return filtered; }), - // Fetch repositories from Gitea provider getGiteaRepositories: protectedProcedure .input(apiFindOneGitea) - .query( - async ({ - input, - ctx, - }: { input: typeof apiFindOneGitea._input; ctx: any }) => { - const { giteaId } = input; + .query(async ({ input, ctx }) => { + const { giteaId } = input; - if (!giteaId) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Gitea provider ID is required.", - }); - } + if (!giteaId) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Gitea provider ID is required.", + }); + } - const giteaProvider = await findGiteaById(giteaId); - if ( - giteaProvider.gitProvider.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to access this Gitea provider", - }); - } + const giteaProvider = await findGiteaById(giteaId); + if ( + giteaProvider.gitProvider.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this Gitea provider", + }); + } - try { - return await getGiteaRepositories(giteaId); - } catch (error) { - console.error("Error fetching Gitea repositories:", error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: error instanceof Error ? error.message : String(error), - }); - } - }, - ), + try { + const repositories = await getGiteaRepositories(giteaId); + console.log(repositories); + return repositories; + } catch (error) { + console.error("Error fetching Gitea repositories:", error); + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error.message : String(error), + }); + } + }), - // Fetch branches of a specific Gitea repository getGiteaBranches: protectedProcedure .input(apiFindGiteaBranches) - .query( - async ({ - input, - ctx, - }: { input: typeof apiFindGiteaBranches._input; ctx: any }) => { - const { giteaId, owner, repositoryName } = input; + .query(async ({ input, ctx }) => { + const { giteaId, owner, repositoryName } = input; - if (!giteaId || !owner || !repositoryName) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: - "Gitea provider ID, owner, and repository name are required.", - }); - } + if (!giteaId || !owner || !repositoryName) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + "Gitea provider ID, owner, and repository name are required.", + }); + } + const giteaProvider = await findGiteaById(giteaId); + if ( + giteaProvider.gitProvider.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this Gitea provider", + }); + } + + try { + return await getGiteaBranches({ + giteaId, + owner, + repo: repositoryName, + id: 0, + }); + } catch (error) { + console.error("Error fetching Gitea branches:", error); + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error.message : String(error), + }); + } + }), + + testConnection: protectedProcedure + .input(apiGiteaTestConnection) + .mutation(async ({ input, ctx }) => { + const giteaId = input.giteaId ?? ""; + + try { const giteaProvider = await findGiteaById(giteaId); if ( giteaProvider.gitProvider.organizationId !== @@ -163,97 +172,49 @@ export const giteaRouter = createTRPCRouter({ }); } - try { - return await getGiteaBranches({ - giteaId, - owner, - repo: repositoryName, - id: 0, // Provide a default value for the optional id - }); - } catch (error) { - console.error("Error fetching Gitea branches:", error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: error instanceof Error ? error.message : String(error), - }); - } - }, - ), + const result = await testGiteaConnection({ + giteaId, + }); - // Test connection to Gitea provider - testConnection: protectedProcedure - .input(apiGiteaTestConnection) - .mutation( - async ({ - input, - ctx, - }: { input: typeof apiGiteaTestConnection._input; ctx: any }) => { - const giteaId = input.giteaId ?? ""; + return `Found ${result} repositories`; + } catch (error) { + console.error("Gitea connection test error:", error); + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error.message : String(error), + }); + } + }), - try { - const giteaProvider = await findGiteaById(giteaId); - if ( - giteaProvider.gitProvider.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to access this Gitea provider", - }); - } - - const result = await testGiteaConnection({ - giteaId, - }); - - return `Found ${result} repositories`; - } catch (error) { - console.error("Gitea connection test error:", error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: error instanceof Error ? error.message : String(error), - }); - } - }, - ), - - // Update an existing Gitea provider update: protectedProcedure .input(apiUpdateGitea) - .mutation( - async ({ - input, - ctx, - }: { input: typeof apiUpdateGitea._input; ctx: any }) => { - const giteaProvider = await findGiteaById(input.giteaId); - if ( - giteaProvider.gitProvider.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to access this Gitea provider", - }); - } + .mutation(async ({ input, ctx }) => { + const giteaProvider = await findGiteaById(input.giteaId); + if ( + giteaProvider.gitProvider.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this Gitea provider", + }); + } - console.log("Updating Gitea provider:", input); + if (input.name) { + await updateGitProvider(input.gitProviderId, { + name: input.name, + organizationId: ctx.session.activeOrganizationId, + }); - if (input.name) { - await updateGitProvider(input.gitProviderId, { - name: input.name, - organizationId: ctx.session.activeOrganizationId, - }); + await updateGitea(input.giteaId, { + ...input, + }); + } else { + await updateGitea(input.giteaId, { + ...input, + }); + } - await updateGitea(input.giteaId, { - ...input, - }); - } else { - await updateGitea(input.giteaId, { - ...input, - }); - } - - return { success: true }; - }, - ), + return { success: true }; + }), }); diff --git a/packages/server/src/db/schema/gitea.ts b/packages/server/src/db/schema/gitea.ts index 5022167b..db7454e9 100644 --- a/packages/server/src/db/schema/gitea.ts +++ b/packages/server/src/db/schema/gitea.ts @@ -5,13 +5,12 @@ import { nanoid } from "nanoid"; import { z } from "zod"; import { gitProvider } from "./git-provider"; -// Gitea table definition export const gitea = pgTable("gitea", { giteaId: text("giteaId") .notNull() .primaryKey() - .$defaultFn(() => nanoid()), // Using nanoid for unique ID - giteaUrl: text("giteaUrl").default("https://gitea.com").notNull(), // Default URL for Gitea + .$defaultFn(() => nanoid()), + giteaUrl: text("giteaUrl").default("https://gitea.com").notNull(), redirectUri: text("redirect_uri"), clientId: text("client_id"), clientSecret: text("client_secret"), @@ -26,7 +25,6 @@ export const gitea = pgTable("gitea", { lastAuthenticatedAt: integer("last_authenticated_at"), }); -// Gitea relations with gitProvider export const giteaProviderRelations = relations(gitea, ({ one }) => ({ gitProvider: one(gitProvider, { fields: [gitea.gitProviderId], @@ -34,10 +32,8 @@ export const giteaProviderRelations = relations(gitea, ({ one }) => ({ }), })); -// Create schema for Gitea const createSchema = createInsertSchema(gitea); -// API schema for creating a Gitea instance export const apiCreateGitea = createSchema.extend({ clientId: z.string().optional(), clientSecret: z.string().optional(), @@ -54,14 +50,12 @@ export const apiCreateGitea = createSchema.extend({ lastAuthenticatedAt: z.number().optional(), }); -// API schema for finding one Gitea instance export const apiFindOneGitea = createSchema .extend({ giteaId: z.string().min(1), }) .pick({ giteaId: true }); -// API schema for testing Gitea connection export const apiGiteaTestConnection = createSchema .extend({ organizationName: z.string().optional(), @@ -70,7 +64,6 @@ export const apiGiteaTestConnection = createSchema export type ApiGiteaTestConnection = z.infer; -// API schema for finding branches in Gitea export const apiFindGiteaBranches = z.object({ id: z.number().optional(), owner: z.string().min(1), @@ -78,7 +71,6 @@ export const apiFindGiteaBranches = z.object({ giteaId: z.string().optional(), }); -// API schema for updating Gitea instance export const apiUpdateGitea = createSchema.extend({ clientId: z.string().optional(), clientSecret: z.string().optional(), diff --git a/packages/server/src/services/gitea.ts b/packages/server/src/services/gitea.ts index 9ad7c987..45447f45 100644 --- a/packages/server/src/services/gitea.ts +++ b/packages/server/src/services/gitea.ts @@ -1,6 +1,4 @@ -// @ts-ignore: Cannot find module errors import { db } from "@dokploy/server/db"; -// @ts-ignore: Cannot find module errors import { type apiCreateGitea, gitProvider, @@ -15,7 +13,6 @@ export const createGitea = async ( input: typeof apiCreateGitea._type, organizationId: string, ) => { - // @ts-ignore - Complex transaction type - Added because proper typing in Drizzle in not sufficient return await db.transaction(async (tx) => { const newGitProvider = await tx .insert(gitProvider) @@ -25,7 +22,7 @@ export const createGitea = async ( name: input.name, }) .returning() - .then((response: (typeof gitProvider.$inferSelect)[]) => response[0]); + .then((response) => response[0]); if (!newGitProvider) { throw new TRPCError({ @@ -50,7 +47,6 @@ export const createGitea = async ( }); } - // Return just the essential data needed by the frontend return { giteaId: giteaProvider.giteaId, clientId: giteaProvider.clientId, @@ -69,7 +65,6 @@ export const findGiteaById = async (giteaId: string) => { }); if (!giteaProviderResult) { - console.error("No Gitea Provider found:", { giteaId }); throw new TRPCError({ code: "NOT_FOUND", message: "Gitea Provider not found", @@ -78,21 +73,11 @@ export const findGiteaById = async (giteaId: string) => { return giteaProviderResult; } catch (error) { - console.error("Error finding Gitea Provider:", error); throw error; } }; export const updateGitea = async (giteaId: string, input: Partial) => { - console.log("Updating Gitea Provider:", { - giteaId, - updateData: { - accessTokenPresent: !!input.accessToken, - refreshTokenPresent: !!input.refreshToken, - expiresAt: input.expiresAt, - }, - }); - try { const updateResult = await db .update(gitea) @@ -103,13 +88,11 @@ export const updateGitea = async (giteaId: string, input: Partial) => { const result = updateResult[0] as Gitea | undefined; if (!result) { - console.error("No rows were updated", { giteaId, input }); throw new Error(`Failed to update Gitea provider with ID ${giteaId}`); } return result; } catch (error) { - console.error("Error updating Gitea provider:", error); throw error; } }; diff --git a/packages/server/src/utils/providers/gitea.ts b/packages/server/src/utils/providers/gitea.ts index c4d1e7fd..da8ab9d9 100644 --- a/packages/server/src/utils/providers/gitea.ts +++ b/packages/server/src/utils/providers/gitea.ts @@ -1,46 +1,18 @@ import { createWriteStream } from "node:fs"; -import * as fs from "node:fs/promises"; import { join } from "node:path"; -// @ts-ignore: Cannot find module errors import { paths } from "@dokploy/server/constants"; -// @ts-ignore: Cannot find module errors -import { findGiteaById, updateGitea } from "@dokploy/server/services/gitea"; +import { + findGiteaById, + type Gitea, + updateGitea, +} from "@dokploy/server/services/gitea"; import { TRPCError } from "@trpc/server"; import { recreateDirectory } from "../filesystem/directory"; import { execAsyncRemote } from "../process/execAsync"; import { spawnAsync } from "../process/spawnAsync"; +import type { Compose } from "@dokploy/server/services/compose"; +import type { InferResultType } from "@dokploy/server/types/with"; -/** - * Wrapper function to maintain compatibility with the existing implementation - */ -export const fetchGiteaBranches = async ( - giteaId: string, - repoFullName: string, -) => { - // Ensure owner and repo are non-empty strings - const parts = repoFullName.split("/"); - - // Validate that we have exactly two parts - if (parts.length !== 2 || !parts[0] || !parts[1]) { - throw new Error( - `Invalid repository name format: ${repoFullName}. Expected format: owner/repo`, - ); - } - - const [owner, repo] = parts; - - // Call the existing getGiteaBranches function with the correct object structure - return await getGiteaBranches({ - giteaId, - owner, - repo, - id: 0, // Provide a default value for optional id - }); -}; - -/** - * Helper function to check if the required fields are filled for Gitea repository operations - */ export const getErrorCloneRequirements = (entity: { giteaRepository?: string | null; giteaOwner?: string | null; @@ -138,11 +110,15 @@ export const refreshGiteaToken = async (giteaProviderId: string) => { } }; -/** - * Generate a secure Git clone command with proper validation - */ +export type ApplicationWithGitea = InferResultType< + "applications", + { gitea: true } +>; + +export type ComposeWithGitea = InferResultType<"compose", { gitea: true }>; + export const getGiteaCloneCommand = async ( - entity: any, + entity: ApplicationWithGitea | ComposeWithGitea, logPath: string, isCompose = false, ) => { @@ -153,6 +129,7 @@ export const getGiteaCloneCommand = async ( giteaOwner, giteaRepository, serverId, + gitea, } = entity; if (!serverId) { @@ -163,6 +140,12 @@ export const getGiteaCloneCommand = async ( } if (!giteaId) { + const command = ` + echo "Error: ❌ Gitlab Provider not found" >> ${logPath}; + exit 1; + `; + + await execAsyncRemote(serverId, command); throw new TRPCError({ code: "NOT_FOUND", message: "Gitea Provider not found", @@ -171,282 +154,189 @@ export const getGiteaCloneCommand = async ( // Use paths(true) for remote operations const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true); + await refreshGiteaToken(giteaId); const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; const outputPath = join(basePath, appName, "code"); - const giteaProvider = await findGiteaById(giteaId); - const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ""); + const baseUrl = gitea?.giteaUrl.replace(/^https?:\/\//, ""); const repoClone = `${giteaOwner}/${giteaRepository}.git`; - const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; + const cloneUrl = `https://oauth2:${gitea?.accessToken}@${baseUrl}/${repoClone}`; const cloneCommand = ` - # Ensure output directory exists and is empty rm -rf ${outputPath}; mkdir -p ${outputPath}; - - # Clone with detailed logging - echo "Cloning repository to ${outputPath}" >> ${logPath}; - echo "Repository: ${repoClone}" >> ${logPath}; if ! git clone --branch ${giteaBranch} --depth 1 --recurse-submodules ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then echo "❌ [ERROR] Failed to clone the repository ${repoClone}" >> ${logPath}; exit 1; fi - # Verify clone - CLONE_COUNT=$(find ${outputPath} -type f | wc -l) - echo "Files cloned: $CLONE_COUNT" >> ${logPath}; - - if [ "$CLONE_COUNT" -eq 0 ]; then - echo "⚠️ WARNING: No files cloned" >> ${logPath}; - exit 1; - fi - echo "Cloned ${repoClone} to ${outputPath}: ✅" >> ${logPath}; `; return cloneCommand; }; -/** - * Main function to clone a Gitea repository with improved validation and robust directory handling - */ +interface CloneGiteaRepository { + appName: string; + giteaBranch: string; + giteaId: string; + giteaOwner: string; + giteaRepository: string; +} export const cloneGiteaRepository = async ( - entity: any, - logPath?: string, + entity: CloneGiteaRepository, + logPath: string, isCompose = false, ) => { - // If logPath is not provided, generate a default log path - const actualLogPath = - logPath || - join( - paths()[isCompose ? "COMPOSE_PATH" : "APPLICATIONS_PATH"], - entity.appName, - "clone.log", - ); + const { APPLICATIONS_PATH, COMPOSE_PATH } = paths(); - const writeStream = createWriteStream(actualLogPath, { flags: "a" }); + const writeStream = createWriteStream(logPath, { flags: "a" }); const { appName, giteaBranch, giteaId, giteaOwner, giteaRepository } = entity; + if (!giteaId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea Provider not found", + }); + } + + await refreshGiteaToken(giteaId); + const giteaProvider = await findGiteaById(giteaId); + if (!giteaProvider) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea provider not found in the database", + }); + } + + const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; + const outputPath = join(basePath, appName, "code"); + await recreateDirectory(outputPath); + + const repoClone = `${giteaOwner}/${giteaRepository}.git`; + const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ""); + const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; + + writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`); + try { - if (!giteaId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitea Provider not found", - }); - } - - // Refresh the access token - await refreshGiteaToken(giteaId); - - // Fetch the Gitea provider - const giteaProvider = await findGiteaById(giteaId); - if (!giteaProvider) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitea provider not found in the database", - }); - } - - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - - // Log path information - writeStream.write("\nPath Information:\n"); - writeStream.write(`Base Path: ${basePath}\n`); - writeStream.write(`Output Path: ${outputPath}\n`); - - writeStream.write(`\nRecreating directory: ${outputPath}\n`); - await recreateDirectory(outputPath); - - // Additional step - verify directory exists and is empty - try { - const filesCheck = await fs.readdir(outputPath); - writeStream.write( - `Directory after cleanup - files: ${filesCheck.length}\n`, - ); - - if (filesCheck.length > 0) { - writeStream.write("WARNING: Directory not empty after cleanup!\n"); - - // Force remove with shell command if recreateDirectory didn't work - if (entity.serverId) { - writeStream.write("Attempting forceful cleanup via shell command\n"); - await execAsyncRemote( - entity.serverId, - `rm -rf "${outputPath}" && mkdir -p "${outputPath}"`, - (data) => writeStream.write(`Cleanup output: ${data}\n`), - ); - } else { - // Fallback to direct fs operations if serverId not available - writeStream.write("Attempting direct fs removal\n"); - await fs.rm(outputPath, { recursive: true, force: true }); - await fs.mkdir(outputPath, { recursive: true }); + await spawnAsync( + "git", + [ + "clone", + "--branch", + giteaBranch, + "--depth", + "1", + "--recurse-submodules", + cloneUrl, + outputPath, + "--progress", + ], + (data) => { + if (writeStream.writable) { + writeStream.write(data); } - } - } catch (verifyError) { - writeStream.write(`Error verifying directory: ${verifyError}\n`); - // Continue anyway - the clone operation might handle this - } - - const repoClone = `${giteaOwner}/${giteaRepository}.git`; - const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ""); - const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; - - writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`); - writeStream.write( - `Clone URL (masked): https://oauth2:***@${baseUrl}/${repoClone}\n`, + }, ); - - // First try standard git clone - try { - await spawnAsync( - "git", - [ - "clone", - "--branch", - giteaBranch, - "--depth", - "1", - "--recurse-submodules", - cloneUrl, - outputPath, - "--progress", - ], - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }, - ); - writeStream.write("\nStandard git clone succeeded\n"); - } catch (cloneError) { - writeStream.write(`\nStandard git clone failed: ${cloneError}\n`); - writeStream.write("Falling back to git init + fetch approach...\n"); - - // Retry cleanup one more time - if (entity.serverId) { - await execAsyncRemote( - entity.serverId, - `rm -rf "${outputPath}" && mkdir -p "${outputPath}"`, - (data) => writeStream.write(`Cleanup retry: ${data}\n`), - ); - } else { - await fs.rm(outputPath, { recursive: true, force: true }); - await fs.mkdir(outputPath, { recursive: true }); - } - - // Initialize git repo - writeStream.write("Initializing git repository...\n"); - await spawnAsync("git", ["init", outputPath], (data) => - writeStream.write(data), - ); - - // Set remote origin - writeStream.write("Setting remote origin...\n"); - await spawnAsync( - "git", - ["-C", outputPath, "remote", "add", "origin", cloneUrl], - (data) => writeStream.write(data), - ); - - // Fetch branch - writeStream.write(`Fetching branch: ${giteaBranch}...\n`); - await spawnAsync( - "git", - ["-C", outputPath, "fetch", "--depth", "1", "origin", giteaBranch], - (data) => writeStream.write(data), - ); - - // Checkout branch - writeStream.write(`Checking out branch: ${giteaBranch}...\n`); - await spawnAsync( - "git", - ["-C", outputPath, "checkout", "FETCH_HEAD"], - (data) => writeStream.write(data), - ); - - writeStream.write("Git init and fetch completed successfully\n"); - } - - // Verify clone - const files = await fs.readdir(outputPath); - writeStream.write("\nClone Verification:\n"); - writeStream.write(`Files found: ${files.length}\n`); - if (files.length > 0) { - // Using a for loop instead of forEach - for (let i = 0; i < Math.min(files.length, 10); i++) { - writeStream.write(`- ${files[i]}\n`); - } - } - - if (files.length === 0) { - throw new Error("Repository clone failed - directory is empty"); - } - - writeStream.write(`\nCloned ${repoClone} successfully: ✅\n`); + writeStream.write(`\nCloned ${repoClone}: ✅\n`); } catch (error) { - writeStream.write(`\nClone Error: ${error}\n`); + writeStream.write(`ERROR Clonning: ${error}: ❌`); throw error; } finally { writeStream.end(); } }; -/** - * Clone a Gitea repository locally for a Compose configuration - * Leverages the existing comprehensive cloneGiteaRepository function - */ -export const cloneRawGiteaRepository = async (entity: any) => { - // Merge the existing entity with compose-specific properties - const composeEntity = { - ...entity, - sourceType: "compose", - isCompose: true, - }; +export const cloneRawGiteaRepository = async (entity: Compose) => { + const { appName, giteaRepository, giteaOwner, giteaBranch, giteaId } = entity; + const { COMPOSE_PATH } = paths(); - // Call cloneGiteaRepository with the modified entity - await cloneGiteaRepository(composeEntity); + if (!giteaId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea Provider not found", + }); + } + await refreshGiteaToken(giteaId); + const giteaProvider = await findGiteaById(giteaId); + if (!giteaProvider) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea provider not found in the database", + }); + } + + const basePath = COMPOSE_PATH; + const outputPath = join(basePath, appName, "code"); + await recreateDirectory(outputPath); + + const repoClone = `${giteaOwner}/${giteaRepository}.git`; + const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ""); + const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; + + try { + await spawnAsync("git", [ + "clone", + "--branch", + giteaBranch!, + "--depth", + "1", + "--recurse-submodules", + cloneUrl, + outputPath, + "--progress", + ]); + } catch (error) { + throw error; + } }; -/** - * Clone a Gitea repository remotely for a Compose configuration - * Uses the existing getGiteaCloneCommand function for remote cloning - */ -export const cloneRawGiteaRepositoryRemote = async (compose: any) => { - const { COMPOSE_PATH } = paths(true); - const logPath = join(COMPOSE_PATH, compose.appName, "clone.log"); +export const cloneRawGiteaRepositoryRemote = async (compose: Compose) => { + const { + appName, + giteaRepository, + giteaOwner, + giteaBranch, + giteaId, + serverId, + } = compose; - // Reuse the existing getGiteaCloneCommand function - const command = await getGiteaCloneCommand( - { - ...compose, - isCompose: true, - }, - logPath, - true, - ); - - if (!compose.serverId) { + if (!serverId) { throw new TRPCError({ code: "NOT_FOUND", message: "Server not found", }); } - - // Execute the clone command on the remote server - await execAsyncRemote(compose.serverId, command); + if (!giteaId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea Provider not found", + }); + } + const { COMPOSE_PATH } = paths(true); + const giteaProvider = await findGiteaById(giteaId); + const basePath = COMPOSE_PATH; + const outputPath = join(basePath, appName, "code"); + const repoClone = `${giteaOwner}/${giteaRepository}.git`; + const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ""); + const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; + try { + const command = ` + rm -rf ${outputPath}; + git clone --branch ${giteaBranch} --depth 1 ${cloneUrl} ${outputPath} + `; + await execAsyncRemote(serverId, command); + } catch (error) { + throw error; + } }; -// Helper function to check if a Gitea provider meets the necessary requirements -export const haveGiteaRequirements = (giteaProvider: any) => { +export const haveGiteaRequirements = (giteaProvider: Gitea) => { return !!(giteaProvider?.clientId && giteaProvider?.clientSecret); }; -/** - * Function to test the connection to a Gitea provider - */ export const testGiteaConnection = async (input: { giteaId: string }) => { try { const { giteaId } = input; @@ -455,7 +345,6 @@ export const testGiteaConnection = async (input: { giteaId: string }) => { throw new Error("Gitea provider not found"); } - // Fetch the Gitea provider from the database const giteaProvider = await findGiteaById(giteaId); if (!giteaProvider) { throw new TRPCError({ @@ -464,16 +353,8 @@ export const testGiteaConnection = async (input: { giteaId: string }) => { }); } - console.log("Gitea Provider Found:", { - id: giteaProvider.giteaId, - url: giteaProvider.giteaUrl, - hasAccessToken: !!giteaProvider.accessToken, - }); - - // Refresh the token if needed await refreshGiteaToken(giteaId); - // Fetch the provider again in case the token was refreshed const provider = await findGiteaById(giteaId); if (!provider || !provider.accessToken) { throw new TRPCError({ @@ -482,15 +363,9 @@ export const testGiteaConnection = async (input: { giteaId: string }) => { }); } - // Make API request to test connection - console.log("Making API request to test connection..."); - - // Construct proper URL for the API request - const baseUrl = provider.giteaUrl.replace(/\/+$/, ""); // Remove trailing slashes + const baseUrl = provider.giteaUrl.replace(/\/+$/, ""); const url = `${baseUrl}/api/v1/user/repos`; - console.log(`Testing connection to: ${url}`); - const response = await fetch(url, { headers: { Accept: "application/json", @@ -499,45 +374,31 @@ export const testGiteaConnection = async (input: { giteaId: string }) => { }); if (!response.ok) { - const errorText = await response.text(); - console.error("Repository API failed:", errorText); throw new Error( `Failed to connect to Gitea API: ${response.status} ${response.statusText}`, ); } const repos = await response.json(); - console.log( - `Successfully connected to Gitea API. Found ${repos.length} repositories.`, - ); - - // Update lastAuthenticatedAt await updateGitea(giteaId, { lastAuthenticatedAt: Math.floor(Date.now() / 1000), }); return repos.length; } catch (error) { - console.error("Gitea Connection Test Error:", error); throw error; } }; -/** - * Function to fetch repositories from a Gitea provider - */ export const getGiteaRepositories = async (giteaId?: string) => { if (!giteaId) { return []; } - // Refresh the token await refreshGiteaToken(giteaId); - // Fetch the Gitea provider const giteaProvider = await findGiteaById(giteaId); - // Construct the URL for fetching repositories const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, ""); const url = `${baseUrl}/api/v1/user/repos`; @@ -557,7 +418,6 @@ export const getGiteaRepositories = async (giteaId?: string) => { const repositories = await response.json(); - // Map repositories to a consistent format const mappedRepositories = repositories.map((repo: any) => ({ id: repo.id, name: repo.name, @@ -570,9 +430,6 @@ export const getGiteaRepositories = async (giteaId?: string) => { return mappedRepositories; }; -/** - * Function to fetch branches for a specific Gitea repository - */ export const getGiteaBranches = async (input: { id?: number; giteaId?: string; @@ -583,10 +440,8 @@ export const getGiteaBranches = async (input: { return []; } - // Fetch the Gitea provider const giteaProvider = await findGiteaById(input.giteaId); - // Construct the URL for fetching branches const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, ""); const url = `${baseUrl}/api/v1/repos/${input.owner}/${input.repo}/branches`; @@ -603,7 +458,6 @@ export const getGiteaBranches = async (input: { const branches = await response.json(); - // Map branches to a consistent format return branches.map((branch: any) => ({ id: branch.name, name: branch.name, @@ -612,15 +466,3 @@ export const getGiteaBranches = async (input: { }, })); }; - -export default { - cloneGiteaRepository, - cloneRawGiteaRepository, - cloneRawGiteaRepositoryRemote, - refreshGiteaToken, - haveGiteaRequirements, - testGiteaConnection, - getGiteaRepositories, - getGiteaBranches, - fetchGiteaBranches, -};