feat: add providers to compose

This commit is contained in:
Mauricio Siu 2024-09-01 17:35:44 -06:00
parent 4c99e6000a
commit d57bbff87c
31 changed files with 18759 additions and 82 deletions

View File

@ -16,16 +16,9 @@ const baseAdmin: Admin = {
createdAt: "", createdAt: "",
authId: "", authId: "",
adminId: "string", adminId: "string",
githubAppId: null,
githubAppName: null,
serverIp: null, serverIp: null,
certificateType: "none", certificateType: "none",
host: null, host: null,
githubClientId: null,
githubClientSecret: null,
githubInstallationId: null,
githubPrivateKey: null,
githubWebhookSecret: null,
letsEncryptEmail: null, letsEncryptEmail: null,
sshPrivateKey: null, sshPrivateKey: null,
enableDockerCleanup: false, enableDockerCleanup: false,

View File

@ -13,6 +13,18 @@ const baseApp: ApplicationNested = {
buildArgs: null, buildArgs: null,
buildPath: "/", buildPath: "/",
buildType: "nixpacks", buildType: "nixpacks",
bitbucketBranch: "",
bitbucketBuildPath: "",
bitbucketId: "",
bitbucketRepository: "",
bitbucketOwner: "",
githubId: "",
gitlabProjectId: 0,
gitlabBranch: "",
gitlabBuildPath: "",
gitlabId: "",
gitlabRepository: "",
gitlabOwner: "",
command: null, command: null,
cpuLimit: null, cpuLimit: null,
cpuReservation: null, cpuReservation: null,

View File

@ -44,6 +44,7 @@ const GitlabProviderSchema = z.object({
.object({ .object({
repo: z.string().min(1, "Repo is required"), repo: z.string().min(1, "Repo is required"),
owner: z.string().min(1, "Owner is required"), owner: z.string().min(1, "Owner is required"),
gitlabPathNamespace: z.string().min(1),
id: z.number().nullable(), id: z.number().nullable(),
}) })
.required(), .required(),
@ -70,6 +71,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
repository: { repository: {
owner: "", owner: "",
repo: "", repo: "",
gitlabPathNamespace: "",
id: null, id: null,
}, },
gitlabId: "", gitlabId: "",
@ -112,6 +114,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
repository: { repository: {
repo: data.gitlabRepository || "", repo: data.gitlabRepository || "",
owner: data.gitlabOwner || "", owner: data.gitlabOwner || "",
gitlabPathNamespace: data.gitlabPathNamespace || "",
id: data.gitlabProjectId, id: data.gitlabProjectId,
}, },
buildPath: data.gitlabBuildPath || "/", buildPath: data.gitlabBuildPath || "/",
@ -129,6 +132,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
gitlabId: data.gitlabId, gitlabId: data.gitlabId,
applicationId, applicationId,
gitlabProjectId: data.repository.id, gitlabProjectId: data.repository.id,
gitlabPathNamespace: data.repository.gitlabPathNamespace,
}) })
.then(async () => { .then(async () => {
toast.success("Service Provided Saved"); toast.success("Service Provided Saved");
@ -161,6 +165,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
owner: "", owner: "",
repo: "", repo: "",
id: null, id: null,
gitlabPathNamespace: "",
}); });
form.setValue("branch", ""); form.setValue("branch", "");
}} }}
@ -246,6 +251,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
owner: repo.owner.username as string, owner: repo.owner.username as string,
repo: repo.name, repo: repo.name,
id: repo.id, id: repo.id,
gitlabPathNamespace: repo.url,
}); });
form.setValue("branch", ""); form.setValue("branch", "");
}} }}

View File

@ -0,0 +1,375 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const BitbucketProviderSchema = z.object({
composePath: z.string().min(1),
repository: z
.object({
repo: z.string().min(1, "Repo is required"),
owner: z.string().min(1, "Owner is required"),
})
.required(),
branch: z.string().min(1, "Branch is required"),
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
});
type BitbucketProvider = z.infer<typeof BitbucketProviderSchema>;
interface Props {
composeId: string;
}
export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
const { data: bitbucketProviders } =
api.gitProvider.bitbucketProviders.useQuery();
const { data, refetch } = api.compose.one.useQuery({ composeId });
const { mutateAsync, isLoading: isSavingBitbucketProvider } =
api.compose.update.useMutation();
const form = useForm<BitbucketProvider>({
defaultValues: {
composePath: "./docker-compose.yml",
repository: {
owner: "",
repo: "",
},
bitbucketId: "",
branch: "",
},
resolver: zodResolver(BitbucketProviderSchema),
});
const repository = form.watch("repository");
const bitbucketId = form.watch("bitbucketId");
const {
data: repositories,
isLoading: isLoadingRepositories,
error,
isError,
} = api.gitProvider.getBitbucketRepositories.useQuery({
bitbucketId,
});
const {
data: branches,
fetchStatus,
status,
} = api.gitProvider.getBitbucketBranches.useQuery(
{
owner: repository?.owner,
repo: repository?.repo,
bitbucketId,
},
{
enabled: !!repository?.owner && !!repository?.repo && !!bitbucketId,
},
);
useEffect(() => {
if (data) {
form.reset({
branch: data.bitbucketBranch || "",
repository: {
repo: data.bitbucketRepository || "",
owner: data.bitbucketOwner || "",
},
composePath: data.composePath,
bitbucketId: data.bitbucketId || "",
});
}
}, [form.reset, data, form]);
const onSubmit = async (data: BitbucketProvider) => {
await mutateAsync({
bitbucketBranch: data.branch,
bitbucketRepository: data.repository.repo,
bitbucketOwner: data.repository.owner,
bitbucketId: data.bitbucketId,
composePath: data.composePath,
composeId,
sourceType: "bitbucket",
composeStatus: "idle",
})
.then(async () => {
toast.success("Service Provided Saved");
await refetch();
})
.catch(() => {
toast.error("Error to save the Bitbucket provider");
});
};
return (
<div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4 py-3"
>
{error && (
<AlertBlock type="error">Repositories: {error.message}</AlertBlock>
)}
<div className="grid md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="bitbucketId"
render={({ field }) => (
<FormItem className="md:col-span-2 flex flex-col">
<FormLabel>Bitbucket Account</FormLabel>
<Select
onValueChange={(value) => {
field.onChange(value);
form.setValue("repository", {
owner: "",
repo: "",
});
form.setValue("branch", "");
}}
defaultValue={field.value}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a Bitbucket Account" />
</SelectTrigger>
</FormControl>
<SelectContent>
{bitbucketProviders?.map((bitbucketProvider) => (
<SelectItem
key={bitbucketProvider.bitbucketId}
value={bitbucketProvider.bitbucketId}
>
{bitbucketProvider.gitProvider.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="repository"
render={({ field }) => (
<FormItem className="md:col-span-2 flex flex-col">
<FormLabel>Repository</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
)}
>
{isLoadingRepositories
? "Loading...."
: field.value.owner
? repositories?.find(
(repo) => repo.name === field.value.repo,
)?.name
: "Select repository"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput
placeholder="Search repository..."
className="h-9"
/>
{isLoadingRepositories && (
<span className="py-6 text-center text-sm">
Loading Repositories....
</span>
)}
<CommandEmpty>No repositories found.</CommandEmpty>
<ScrollArea className="h-96">
<CommandGroup>
{repositories?.map((repo) => (
<CommandItem
value={repo.url}
key={repo.url}
onSelect={() => {
form.setValue("repository", {
owner: repo.owner.username as string,
repo: repo.name,
});
form.setValue("branch", "");
}}
>
{repo.name}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
repo.name === field.value.repo
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
</Command>
</PopoverContent>
</Popover>
{form.formState.errors.repository && (
<p className={cn("text-sm font-medium text-destructive")}>
Repository is required
</p>
)}
</FormItem>
)}
/>
<FormField
control={form.control}
name="branch"
render={({ field }) => (
<FormItem className="block w-full">
<FormLabel>Branch</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
" w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
)}
>
{status === "loading" && fetchStatus === "fetching"
? "Loading...."
: field.value
? branches?.find(
(branch) => branch.name === field.value,
)?.name
: "Select branch"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput
placeholder="Search branch..."
className="h-9"
/>
{status === "loading" && fetchStatus === "fetching" && (
<span className="py-6 text-center text-sm text-muted-foreground">
Loading Branches....
</span>
)}
{!repository?.owner && (
<span className="py-6 text-center text-sm text-muted-foreground">
Select a repository
</span>
)}
<ScrollArea className="h-96">
<CommandEmpty>No branch found.</CommandEmpty>
<CommandGroup>
{branches?.map((branch) => (
<CommandItem
value={branch.name}
key={branch.commit.sha}
onSelect={() => {
form.setValue("branch", branch.name);
}}
>
{branch.name}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
branch.name === field.value
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
</Command>
</PopoverContent>
<FormMessage />
</Popover>
</FormItem>
)}
/>
<FormField
control={form.control}
name="composePath"
render={({ field }) => (
<FormItem>
<FormLabel>Compose Path</FormLabel>
<FormControl>
<Input placeholder="docker-compose.yml" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex w-full justify-end">
<Button
isLoading={isSavingBitbucketProvider}
type="submit"
className="w-fit"
>
Save
</Button>
</div>
</form>
</Form>
</div>
);
};

View File

@ -21,6 +21,13 @@ import {
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
@ -39,6 +46,7 @@ const GithubProviderSchema = z.object({
}) })
.required(), .required(),
branch: z.string().min(1, "Branch is required"), branch: z.string().min(1, "Branch is required"),
githubId: z.string().min(1, "Github Provider is required"),
}); });
type GithubProvider = z.infer<typeof GithubProviderSchema>; type GithubProvider = z.infer<typeof GithubProviderSchema>;
@ -48,6 +56,7 @@ interface Props {
} }
export const SaveGithubProviderCompose = ({ composeId }: Props) => { export const SaveGithubProviderCompose = ({ composeId }: Props) => {
const { data: githubProviders } = api.gitProvider.githubProviders.useQuery();
const { data, refetch } = api.compose.one.useQuery({ composeId }); const { data, refetch } = api.compose.one.useQuery({ composeId });
const { mutateAsync, isLoading: isSavingGithubProvider } = const { mutateAsync, isLoading: isSavingGithubProvider } =
@ -60,26 +69,33 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
owner: "", owner: "",
repo: "", repo: "",
}, },
githubId: "",
branch: "", branch: "",
}, },
resolver: zodResolver(GithubProviderSchema), resolver: zodResolver(GithubProviderSchema),
}); });
const repository = form.watch("repository"); const repository = form.watch("repository");
const githubId = form.watch("githubId");
const { data: repositories, isLoading: isLoadingRepositories } = const { data: repositories, isLoading: isLoadingRepositories } =
api.admin.getRepositories.useQuery(); api.gitProvider.getRepositories.useQuery({
githubId,
});
const { const {
data: branches, data: branches,
fetchStatus, fetchStatus,
status, status,
} = api.admin.getBranches.useQuery( } = api.gitProvider.getBranches.useQuery(
{ {
owner: repository?.owner, owner: repository?.owner,
repo: repository?.repo, repo: repository?.repo,
githubId,
},
{
enabled: !!repository?.owner && !!repository?.repo && !!githubId,
}, },
{ enabled: !!repository?.owner && !!repository?.repo },
); );
useEffect(() => { useEffect(() => {
@ -91,19 +107,21 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
owner: data.owner || "", owner: data.owner || "",
}, },
composePath: data.composePath, composePath: data.composePath,
githubId: data.githubId || "",
}); });
} }
}, [form.reset, data, form]); }, [form.reset, data, form]);
const onSubmit = async (data: GithubProvider) => { const onSubmit = async (data: GithubProvider) => {
console.log(data);
await mutateAsync({ await mutateAsync({
branch: data.branch, branch: data.branch,
repository: data.repository.repo, repository: data.repository.repo,
composeId: composeId, composeId,
owner: data.repository.owner, owner: data.repository.owner,
sourceType: "github",
composePath: data.composePath, composePath: data.composePath,
githubId: data.githubId,
sourceType: "github",
composeStatus: "idle",
}) })
.then(async () => { .then(async () => {
toast.success("Service Provided Saved"); toast.success("Service Provided Saved");
@ -122,6 +140,45 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
className="grid w-full gap-4 py-3" className="grid w-full gap-4 py-3"
> >
<div className="grid md:grid-cols-2 gap-4"> <div className="grid md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="githubId"
render={({ field }) => (
<FormItem className="md:col-span-2 flex flex-col">
<FormLabel>Github Account</FormLabel>
<Select
onValueChange={(value) => {
field.onChange(value);
form.setValue("repository", {
owner: "",
repo: "",
});
form.setValue("branch", "");
}}
defaultValue={field.value}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a Github Account" />
</SelectTrigger>
</FormControl>
<SelectContent>
{githubProviders?.map((githubProvider) => (
<SelectItem
key={githubProvider.githubId}
value={githubProvider.githubId}
>
{githubProvider.gitProvider.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="repository" name="repository"
@ -278,7 +335,6 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="composePath" name="composePath"

View File

@ -0,0 +1,391 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const GitlabProviderSchema = z.object({
composePath: z.string().min(1),
repository: z
.object({
repo: z.string().min(1, "Repo is required"),
owner: z.string().min(1, "Owner is required"),
id: z.number().nullable(),
gitlabPathNamespace: z.string().min(1),
})
.required(),
branch: z.string().min(1, "Branch is required"),
gitlabId: z.string().min(1, "Gitlab Provider is required"),
});
type GitlabProvider = z.infer<typeof GitlabProviderSchema>;
interface Props {
composeId: string;
}
export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery();
const { data, refetch } = api.compose.one.useQuery({ composeId });
const { mutateAsync, isLoading: isSavingGitlabProvider } =
api.compose.update.useMutation();
const form = useForm<GitlabProvider>({
defaultValues: {
composePath: "./docker-compose.yml",
repository: {
owner: "",
repo: "",
gitlabPathNamespace: "",
id: null,
},
gitlabId: "",
branch: "",
},
resolver: zodResolver(GitlabProviderSchema),
});
const repository = form.watch("repository");
const gitlabId = form.watch("gitlabId");
const {
data: repositories,
isLoading: isLoadingRepositories,
error,
} = api.gitProvider.getGitlabRepositories.useQuery({
gitlabId,
});
const {
data: branches,
fetchStatus,
status,
} = api.gitProvider.getGitlabBranches.useQuery(
{
owner: repository?.owner,
repo: repository?.repo,
id: repository?.id,
gitlabId: gitlabId,
},
{
enabled: !!repository?.owner && !!repository?.repo && !!gitlabId,
},
);
useEffect(() => {
if (data) {
form.reset({
branch: data.gitlabBranch || "",
repository: {
repo: data.gitlabRepository || "",
owner: data.gitlabOwner || "",
id: data.gitlabProjectId,
gitlabPathNamespace: data.gitlabPathNamespace || "",
},
composePath: data.composePath,
gitlabId: data.gitlabId || "",
});
}
}, [form.reset, data, form]);
const onSubmit = async (data: GitlabProvider) => {
await mutateAsync({
gitlabBranch: data.branch,
gitlabRepository: data.repository.repo,
gitlabOwner: data.repository.owner,
composePath: data.composePath,
gitlabId: data.gitlabId,
composeId,
gitlabProjectId: data.repository.id,
gitlabPathNamespace: data.repository.gitlabPathNamespace,
sourceType: "gitlab",
composeStatus: "idle",
})
.then(async () => {
toast.success("Service Provided Saved");
await refetch();
})
.catch(() => {
toast.error("Error to save the gitlab provider");
});
};
return (
<div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4 py-3"
>
{error && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="gitlabId"
render={({ field }) => (
<FormItem className="md:col-span-2 flex flex-col">
<FormLabel>Gitlab Account</FormLabel>
<Select
onValueChange={(value) => {
field.onChange(value);
form.setValue("repository", {
owner: "",
repo: "",
gitlabPathNamespace: "",
id: null,
});
form.setValue("branch", "");
}}
defaultValue={field.value}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a Gitlab Account" />
</SelectTrigger>
</FormControl>
<SelectContent>
{gitlabProviders?.map((gitlabProvider) => (
<SelectItem
key={gitlabProvider.gitlabId}
value={gitlabProvider.gitlabId}
>
{gitlabProvider.gitProvider.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="repository"
render={({ field }) => (
<FormItem className="md:col-span-2 flex flex-col">
<FormLabel>Repository</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
)}
>
{isLoadingRepositories
? "Loading...."
: field.value.owner
? repositories?.find(
(repo) => repo.name === field.value.repo,
)?.name
: "Select repository"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput
placeholder="Search repository..."
className="h-9"
/>
{isLoadingRepositories && (
<span className="py-6 text-center text-sm">
Loading Repositories....
</span>
)}
<CommandEmpty>No repositories found.</CommandEmpty>
<ScrollArea className="h-96">
<CommandGroup>
{repositories && repositories.length === 0 && (
<CommandEmpty>
No repositories found.
</CommandEmpty>
)}
{repositories?.map((repo) => {
return (
<CommandItem
value={repo.url}
key={repo.url}
onSelect={() => {
form.setValue("repository", {
owner: repo.owner.username as string,
repo: repo.name,
id: repo.id,
gitlabPathNamespace: repo.url,
});
form.setValue("branch", "");
}}
>
{repo.name}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
repo.name === field.value.repo
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
);
})}
</CommandGroup>
</ScrollArea>
</Command>
</PopoverContent>
</Popover>
{form.formState.errors.repository && (
<p className={cn("text-sm font-medium text-destructive")}>
Repository is required
</p>
)}
</FormItem>
)}
/>
<FormField
control={form.control}
name="branch"
render={({ field }) => (
<FormItem className="block w-full">
<FormLabel>Branch</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
" w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
)}
>
{status === "loading" && fetchStatus === "fetching"
? "Loading...."
: field.value
? branches?.find(
(branch) => branch.name === field.value,
)?.name
: "Select branch"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput
placeholder="Search branch..."
className="h-9"
/>
{status === "loading" && fetchStatus === "fetching" && (
<span className="py-6 text-center text-sm text-muted-foreground">
Loading Branches....
</span>
)}
{!repository?.owner && (
<span className="py-6 text-center text-sm text-muted-foreground">
Select a repository
</span>
)}
<ScrollArea className="h-96">
<CommandEmpty>No branch found.</CommandEmpty>
<CommandGroup>
{branches?.map((branch) => (
<CommandItem
value={branch.name}
key={branch.commit.id}
onSelect={() => {
form.setValue("branch", branch.name);
}}
>
{branch.name}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
branch.name === field.value
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
</Command>
</PopoverContent>
<FormMessage />
</Popover>
</FormItem>
)}
/>
<FormField
control={form.control}
name="composePath"
render={({ field }) => (
<FormItem>
<FormLabel>Compose Path</FormLabel>
<FormControl>
<Input placeholder="docker-compose.yml" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex w-full justify-end">
<Button
isLoading={isSavingGitlabProvider}
type="submit"
className="w-fit"
>
Save
</Button>
</div>
</form>
</Form>
</div>
);
};

View File

@ -8,15 +8,25 @@ import { ComposeFileEditor } from "../compose-file-editor";
import { ShowConvertedCompose } from "../show-converted-compose"; import { ShowConvertedCompose } from "../show-converted-compose";
import { SaveGitProviderCompose } from "./save-git-provider-compose"; import { SaveGitProviderCompose } from "./save-git-provider-compose";
import { SaveGithubProviderCompose } from "./save-github-provider-compose"; import { SaveGithubProviderCompose } from "./save-github-provider-compose";
import {
BitbucketIcon,
GithubIcon,
GitIcon,
GitlabIcon,
} from "@/components/icons/data-tools-icons";
import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose";
import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose";
type TabState = "github" | "git" | "raw"; type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket";
interface Props { interface Props {
composeId: string; composeId: string;
} }
export const ShowProviderFormCompose = ({ composeId }: Props) => { export const ShowProviderFormCompose = ({ composeId }: Props) => {
const { data: haveGithubConfigured } = const { data: githubProviders } = api.gitProvider.githubProviders.useQuery();
api.admin.haveGithubConfigured.useQuery(); const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery();
const { data: bitbucketProviders } =
api.gitProvider.bitbucketProviders.useQuery();
const { data: compose } = api.compose.one.useQuery({ composeId }); const { data: compose } = api.compose.one.useQuery({ composeId });
const [tab, setSab] = useState<TabState>(compose?.sourceType || "github"); const [tab, setSab] = useState<TabState>(compose?.sourceType || "github");
@ -44,38 +54,96 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => {
setSab(e as TabState); setSab(e as TabState);
}} }}
> >
<TabsList className="grid w-fit grid-cols-4 bg-transparent"> <div className="flex flex-row items-center justify-between w-full gap-4">
<TabsTrigger <TabsList className="md:grid md:w-fit md:grid-cols-5 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
value="github" <TabsTrigger
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border" value="github"
> className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
Github >
</TabsTrigger> <GithubIcon className="size-4 text-current fill-current" />
Github
</TabsTrigger>
<TabsTrigger
value="gitlab"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<GitlabIcon className="size-4 text-current fill-current" />
Gitlab
</TabsTrigger>
<TabsTrigger
value="bitbucket"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<BitbucketIcon className="size-4 text-current fill-current" />
Bitbucket
</TabsTrigger>
<TabsTrigger <TabsTrigger
value="git" value="git"
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border" className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
> >
Git <GitIcon />
</TabsTrigger> Git
<TabsTrigger </TabsTrigger>
value="raw" <TabsTrigger
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border" value="raw"
> className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
Raw >
</TabsTrigger> Raw
</TabsList> </TabsTrigger>
</TabsList>
</div>
<TabsContent value="github" className="w-full p-2"> <TabsContent value="github" className="w-full p-2">
{haveGithubConfigured ? ( {githubProviders && githubProviders?.length > 0 ? (
<SaveGithubProviderCompose composeId={composeId} /> <SaveGithubProviderCompose composeId={composeId} />
) : ( ) : (
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<LockIcon className="size-8 text-muted-foreground" /> <GithubIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground"> <span className="text-base text-muted-foreground">
To deploy using GitHub, you need to configure your account To deploy using GitHub, you need to configure your account
first. Please, go to{" "} first. Please, go to{" "}
<Link <Link
href="/dashboard/settings/server" href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="gitlab" className="w-full p-2">
{gitlabProviders && gitlabProviders?.length > 0 ? (
<SaveGitlabProviderCompose composeId={composeId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GitlabIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using GitLab, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="bitbucket" className="w-full p-2">
{bitbucketProviders && bitbucketProviders?.length > 0 ? (
<SaveBitbucketProviderCompose composeId={composeId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<BitbucketIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using Bitbucket, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground" className="text-foreground"
> >
Settings Settings

View File

@ -256,8 +256,8 @@ export const DockerIcon = ({ className }: Props) => {
export const GitIcon = ({ className }: Props) => { export const GitIcon = ({ className }: Props) => {
return ( return (
<svg <svg
width="24" width="20"
height="24" height="20"
viewBox="0 0 256 256" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMinYMin meet" preserveAspectRatio="xMinYMin meet"

View File

@ -0,0 +1,31 @@
ALTER TYPE "sourceTypeCompose" ADD VALUE 'gitlab';--> statement-breakpoint
ALTER TYPE "sourceTypeCompose" ADD VALUE 'bitbucket';--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "gitlabProjectId" integer;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "gitlabRepository" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "gitlabOwner" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "gitlabBranch" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "gitlabBuildPath" text DEFAULT '/';--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "bitbucketRepository" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "bitbucketOwner" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "bitbucketBranch" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "bitbucketBuildPath" text DEFAULT '/';--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "githubId" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "gitlabId" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "bitbucketId" text;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "compose" ADD CONSTRAINT "compose_githubId_github_provider_githubId_fk" FOREIGN KEY ("githubId") REFERENCES "public"."github_provider"("githubId") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "compose" ADD CONSTRAINT "compose_gitlabId_gitlab_provider_gitlabId_fk" FOREIGN KEY ("gitlabId") REFERENCES "public"."gitlab_provider"("gitlabId") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "compose" ADD CONSTRAINT "compose_bitbucketId_bitbucket_provider_bitbucketId_fk" FOREIGN KEY ("bitbucketId") REFERENCES "public"."bitbucket_provider"("bitbucketId") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@ -0,0 +1,2 @@
ALTER TABLE "compose" DROP COLUMN IF EXISTS "gitlabBuildPath";--> statement-breakpoint
ALTER TABLE "compose" DROP COLUMN IF EXISTS "bitbucketBuildPath";

View File

@ -0,0 +1 @@
ALTER TABLE "compose" ADD COLUMN "gitlabHttpUrl" text;

View File

@ -0,0 +1 @@
ALTER TABLE "compose" RENAME COLUMN "gitlabHttpUrl" TO "gitlabPath";

View File

@ -0,0 +1,2 @@
ALTER TABLE "compose" RENAME COLUMN "gitlabPath" TO "gitlabPathNamespace";--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "gitlabPathNamespace" text;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -344,6 +344,41 @@
"when": 1725206119154, "when": 1725206119154,
"tag": "0048_unknown_radioactive_man", "tag": "0048_unknown_radioactive_man",
"breakpoints": true "breakpoints": true
},
{
"idx": 49,
"version": "6",
"when": 1725225552834,
"tag": "0049_futuristic_vampiro",
"breakpoints": true
},
{
"idx": 50,
"version": "6",
"when": 1725226430293,
"tag": "0050_faithful_brood",
"breakpoints": true
},
{
"idx": 51,
"version": "6",
"when": 1725231466293,
"tag": "0051_powerful_ironclad",
"breakpoints": true
},
{
"idx": 52,
"version": "6",
"when": 1725231697187,
"tag": "0052_condemned_khan",
"breakpoints": true
},
{
"idx": 53,
"version": "6",
"when": 1725232936525,
"tag": "0053_fearless_electro",
"breakpoints": true
} }
] ]
} }

View File

@ -8,7 +8,6 @@ export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse, res: NextApiResponse,
) { ) {
console.log(req.body);
const { code, gitlabId } = req.query; const { code, gitlabId } = req.query;
if (!code || Array.isArray(code)) { if (!code || Array.isArray(code)) {

View File

@ -5,7 +5,6 @@ import { type apiCreateCompose, compose } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils"; import { generateAppName } from "@/server/db/schema/utils";
import { buildCompose } from "@/server/utils/builders/compose"; import { buildCompose } from "@/server/utils/builders/compose";
import { cloneCompose, loadDockerCompose } from "@/server/utils/docker/domain"; import { cloneCompose, loadDockerCompose } from "@/server/utils/docker/domain";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error"; import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success"; import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
import { execAsync } from "@/server/utils/process/execAsync"; import { execAsync } from "@/server/utils/process/execAsync";
@ -15,9 +14,11 @@ import { createComposeFile } from "@/server/utils/providers/raw";
import { generatePassword } from "@/templates/utils"; import { generatePassword } from "@/templates/utils";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { findAdmin, getDokployUrl } from "./admin"; import { getDokployUrl } from "./admin";
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment"; import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
import { validUniqueServerAppName } from "./project"; import { validUniqueServerAppName } from "./project";
import { cloneBitbucketRepository } from "@/server/utils/providers/bitbucket";
import { cloneGitlabRepository } from "@/server/utils/providers/gitlab";
export type Compose = typeof compose.$inferSelect; export type Compose = typeof compose.$inferSelect;
@ -92,6 +93,9 @@ export const findComposeById = async (composeId: string) => {
deployments: true, deployments: true,
mounts: true, mounts: true,
domains: true, domains: true,
githubProvider: true,
gitlabProvider: true,
bitbucketProvider: true,
}, },
}); });
if (!result) { if (!result) {
@ -151,7 +155,6 @@ export const deployCompose = async ({
descriptionLog: string; descriptionLog: string;
}) => { }) => {
const compose = await findComposeById(composeId); const compose = await findComposeById(composeId);
const admin = await findAdmin();
const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`; const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`;
const deployment = await createDeploymentCompose({ const deployment = await createDeploymentCompose({
composeId: composeId, composeId: composeId,
@ -161,7 +164,11 @@ export const deployCompose = async ({
try { try {
if (compose.sourceType === "github") { if (compose.sourceType === "github") {
await cloneGithubRepository(admin, compose, deployment.logPath, true); await cloneGithubRepository(compose, deployment.logPath);
} else if (compose.sourceType === "gitlab") {
await cloneGitlabRepository(compose, deployment.logPath);
} else if (compose.sourceType === "bitbucket") {
await cloneBitbucketRepository(compose, deployment.logPath);
} else if (compose.sourceType === "git") { } else if (compose.sourceType === "git") {
await cloneGitRepository(compose, deployment.logPath, true); await cloneGitRepository(compose, deployment.logPath, true);
} else if (compose.sourceType === "raw") { } else if (compose.sourceType === "raw") {

View File

@ -138,6 +138,7 @@ export const applications = pgTable("application", {
gitlabOwner: text("gitlabOwner"), gitlabOwner: text("gitlabOwner"),
gitlabBranch: text("gitlabBranch"), gitlabBranch: text("gitlabBranch"),
gitlabBuildPath: text("gitlabBuildPath").default("/"), gitlabBuildPath: text("gitlabBuildPath").default("/"),
gitlabPathNamespace: text("gitlabPathNamespace"),
// Bitbucket // Bitbucket
bitbucketRepository: text("bitbucketRepository"), bitbucketRepository: text("bitbucketRepository"),
bitbucketOwner: text("bitbucketOwner"), bitbucketOwner: text("bitbucketOwner"),
@ -423,6 +424,7 @@ export const apiSaveGitlabProvider = createSchema
gitlabRepository: true, gitlabRepository: true,
gitlabId: true, gitlabId: true,
gitlabProjectId: true, gitlabProjectId: true,
gitlabPathNamespace: true,
}) })
.required(); .required();

View File

@ -1,6 +1,6 @@
import { sshKeys } from "@/server/db/schema/ssh-key"; import { sshKeys } from "@/server/db/schema/ssh-key";
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core"; import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@ -10,10 +10,17 @@ import { mounts } from "./mount";
import { projects } from "./project"; import { projects } from "./project";
import { applicationStatus } from "./shared"; import { applicationStatus } from "./shared";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
import {
bitbucketProvider,
githubProvider,
gitlabProvider,
} from "./git-provider";
export const sourceTypeCompose = pgEnum("sourceTypeCompose", [ export const sourceTypeCompose = pgEnum("sourceTypeCompose", [
"git", "git",
"github", "github",
"gitlab",
"bitbucket",
"raw", "raw",
]); ]);
@ -39,6 +46,16 @@ export const compose = pgTable("compose", {
owner: text("owner"), owner: text("owner"),
branch: text("branch"), branch: text("branch"),
autoDeploy: boolean("autoDeploy").$defaultFn(() => true), autoDeploy: boolean("autoDeploy").$defaultFn(() => true),
// Gitlab
gitlabProjectId: integer("gitlabProjectId"),
gitlabRepository: text("gitlabRepository"),
gitlabOwner: text("gitlabOwner"),
gitlabBranch: text("gitlabBranch"),
gitlabPathNamespace: text("gitlabPathNamespace"),
// Bitbucket
bitbucketRepository: text("bitbucketRepository"),
bitbucketOwner: text("bitbucketOwner"),
bitbucketBranch: text("bitbucketBranch"),
// Git // Git
customGitUrl: text("customGitUrl"), customGitUrl: text("customGitUrl"),
customGitBranch: text("customGitBranch"), customGitBranch: text("customGitBranch"),
@ -48,7 +65,6 @@ export const compose = pgTable("compose", {
onDelete: "set null", onDelete: "set null",
}, },
), ),
//
command: text("command").notNull().default(""), command: text("command").notNull().default(""),
// //
composePath: text("composePath").notNull().default("./docker-compose.yml"), composePath: text("composePath").notNull().default("./docker-compose.yml"),
@ -59,6 +75,19 @@ export const compose = pgTable("compose", {
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
githubId: text("githubId").references(() => githubProvider.githubId, {
onDelete: "set null",
}),
gitlabId: text("gitlabId").references(() => gitlabProvider.gitlabId, {
onDelete: "set null",
}),
bitbucketId: text("bitbucketId").references(
() => bitbucketProvider.bitbucketId,
{
onDelete: "set null",
},
),
}); });
export const composeRelations = relations(compose, ({ one, many }) => ({ export const composeRelations = relations(compose, ({ one, many }) => ({
@ -73,6 +102,18 @@ export const composeRelations = relations(compose, ({ one, many }) => ({
references: [sshKeys.sshKeyId], references: [sshKeys.sshKeyId],
}), }),
domains: many(domains), domains: many(domains),
githubProvider: one(githubProvider, {
fields: [compose.githubId],
references: [githubProvider.githubId],
}),
gitlabProvider: one(gitlabProvider, {
fields: [compose.gitlabId],
references: [gitlabProvider.gitlabId],
}),
bitbucketProvider: one(bitbucketProvider, {
fields: [compose.bitbucketId],
references: [bitbucketProvider.bitbucketId],
}),
})); }));
const createSchema = createInsertSchema(compose, { const createSchema = createInsertSchema(compose, {

View File

@ -64,7 +64,7 @@ export const githubProvider = pgTable("github_provider", {
export const githubProviderRelations = relations( export const githubProviderRelations = relations(
githubProvider, githubProvider,
({ one, many }) => ({ ({ one, }) => ({
gitProvider: one(gitProvider, { gitProvider: one(gitProvider, {
fields: [githubProvider.gitProviderId], fields: [githubProvider.gitProviderId],
references: [gitProvider.gitProviderId], references: [gitProvider.gitProviderId],
@ -91,7 +91,7 @@ export const gitlabProvider = pgTable("gitlab_provider", {
export const gitlabProviderRelations = relations( export const gitlabProviderRelations = relations(
gitlabProvider, gitlabProvider,
({ one, many }) => ({ ({ one}) => ({
gitProvider: one(gitProvider, { gitProvider: one(gitProvider, {
fields: [gitlabProvider.gitProviderId], fields: [gitlabProvider.gitProviderId],
references: [gitProvider.gitProviderId], references: [gitProvider.gitProviderId],
@ -114,7 +114,7 @@ export const bitbucketProvider = pgTable("bitbucket_provider", {
export const bitbucketProviderRelations = relations( export const bitbucketProviderRelations = relations(
bitbucketProvider, bitbucketProvider,
({ one, many }) => ({ ({ one }) => ({
gitProvider: one(gitProvider, { gitProvider: one(gitProvider, {
fields: [bitbucketProvider.gitProviderId], fields: [bitbucketProvider.gitProviderId],
references: [gitProvider.gitProviderId], references: [gitProvider.gitProviderId],

View File

@ -18,15 +18,7 @@ export type ComposeNested = InferResultType<
>; >;
export const buildCompose = async (compose: ComposeNested, logPath: string) => { export const buildCompose = async (compose: ComposeNested, logPath: string) => {
const writeStream = createWriteStream(logPath, { flags: "a" }); const writeStream = createWriteStream(logPath, { flags: "a" });
const { const { sourceType, appName, mounts, composeType, domains } = compose;
sourceType,
appName,
mounts,
composeType,
env,
composePath,
domains,
} = compose;
try { try {
const command = createCommand(compose); const command = createCommand(compose);
await writeDomainsToCompose(compose, domains); await writeDomainsToCompose(compose, domains);

View File

@ -1,4 +1,4 @@
import fs, { existsSync, readFileSync, writeSync } from "node:fs"; import fs, { existsSync, readFileSync } from "node:fs";
import { writeFile } from "node:fs/promises"; import { writeFile } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import type { Compose } from "@/server/api/services/compose"; import type { Compose } from "@/server/api/services/compose";
@ -13,10 +13,16 @@ import type {
DefinitionsService, DefinitionsService,
PropertiesNetworks, PropertiesNetworks,
} from "./types"; } from "./types";
import { cloneRawGitlabRepository } from "../providers/gitlab";
import { cloneRawBitbucketRepository } from "../providers/bitbucket";
export const cloneCompose = async (compose: Compose) => { export const cloneCompose = async (compose: Compose) => {
if (compose.sourceType === "github") { if (compose.sourceType === "github") {
await cloneRawGithubRepository(compose); await cloneRawGithubRepository(compose);
} else if (compose.sourceType === "gitlab") {
await cloneRawGitlabRepository(compose);
} else if (compose.sourceType === "bitbucket") {
await cloneRawBitbucketRepository(compose);
} else if (compose.sourceType === "git") { } else if (compose.sourceType === "git") {
await cloneGitRawRepository(compose); await cloneGitRawRepository(compose);
} else if (compose.sourceType === "raw") { } else if (compose.sourceType === "raw") {

View File

@ -72,6 +72,10 @@ export const getBuildAppDirectory = (application: Application) => {
if (sourceType === "github") { if (sourceType === "github") {
buildPath = application?.buildPath || ""; buildPath = application?.buildPath || "";
} else if (sourceType === "gitlab") {
buildPath = application?.gitlabBuildPath || "";
} else if (sourceType === "bitbucket") {
buildPath = application?.bitbucketBuildPath || "";
} else if (sourceType === "drop") { } else if (sourceType === "drop") {
buildPath = application?.dropBuildPath || ""; buildPath = application?.dropBuildPath || "";
} else if (sourceType === "git") { } else if (sourceType === "git") {

View File

@ -6,14 +6,20 @@ import { recreateDirectory } from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync"; import { spawnAsync } from "../process/spawnAsync";
import type { InferResultType } from "@/server/types/with"; import type { InferResultType } from "@/server/types/with";
import { getBitbucketProvider } from "@/server/api/services/git-provider"; import { getBitbucketProvider } from "@/server/api/services/git-provider";
import type { Compose } from "@/server/api/services/compose";
export type ApplicationWithBitbucket = InferResultType< export type ApplicationWithBitbucket = InferResultType<
"applications", "applications",
{ bitbucketProvider: true } { bitbucketProvider: true }
>; >;
export type ComposeWithBitbucket = InferResultType<
"compose",
{ bitbucketProvider: true }
>;
export const cloneBitbucketRepository = async ( export const cloneBitbucketRepository = async (
entity: ApplicationWithBitbucket, entity: ApplicationWithBitbucket | ComposeWithBitbucket,
logPath: string, logPath: string,
isCompose = false, isCompose = false,
) => { ) => {
@ -68,16 +74,13 @@ export const cloneBitbucketRepository = async (
} }
}; };
export const cloneRawBitbucketRepository = async ( export const cloneRawBitbucketRepository = async (entity: Compose) => {
entity: ApplicationWithBitbucket,
) => {
const { const {
appName, appName,
bitbucketRepository, bitbucketRepository,
bitbucketOwner, bitbucketOwner,
bitbucketBranch, bitbucketBranch,
bitbucketId, bitbucketId,
bitbucketProvider,
} = entity; } = entity;
if (!bitbucketId) { if (!bitbucketId) {
@ -87,6 +90,7 @@ export const cloneRawBitbucketRepository = async (
}); });
} }
const bitbucketProvider = await getBitbucketProvider(bitbucketId);
const basePath = COMPOSE_PATH; const basePath = COMPOSE_PATH;
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
await recreateDirectory(outputPath); await recreateDirectory(outputPath);

View File

@ -11,6 +11,7 @@ import {
getGithubProvider, getGithubProvider,
type GithubProvider, type GithubProvider,
} from "@/server/api/services/git-provider"; } from "@/server/api/services/git-provider";
import type { Compose } from "@/server/api/services/compose";
export const authGithub = (githubProvider: GithubProvider) => { export const authGithub = (githubProvider: GithubProvider) => {
if (!haveGithubRequirements(githubProvider)) { if (!haveGithubRequirements(githubProvider)) {
@ -71,8 +72,13 @@ export type ApplicationWithGithub = InferResultType<
"applications", "applications",
{ githubProvider: true } { githubProvider: true }
>; >;
export type ComposeWithGithub = InferResultType<
"compose",
{ githubProvider: true }
>;
export const cloneGithubRepository = async ( export const cloneGithubRepository = async (
entity: ApplicationWithGithub, entity: ApplicationWithGithub | ComposeWithGithub,
logPath: string, logPath: string,
isCompose = false, isCompose = false,
) => { ) => {
@ -140,9 +146,7 @@ export const cloneGithubRepository = async (
} }
}; };
export const cloneRawGithubRepository = async ( export const cloneRawGithubRepository = async (entity: Compose) => {
entity: ApplicationWithGithub,
) => {
const { appName, repository, owner, branch, githubId } = entity; const { appName, repository, owner, branch, githubId } = entity;
if (!githubId) { if (!githubId) {

View File

@ -10,6 +10,7 @@ import {
updateGitlabProvider, updateGitlabProvider,
} from "@/server/api/services/git-provider"; } from "@/server/api/services/git-provider";
import type { InferResultType } from "@/server/types/with"; import type { InferResultType } from "@/server/types/with";
import type { Compose } from "@/server/api/services/compose";
export const refreshGitlabToken = async (gitlabProviderId: string) => { export const refreshGitlabToken = async (gitlabProviderId: string) => {
const gitlabProvider = await getGitlabProvider(gitlabProviderId); const gitlabProvider = await getGitlabProvider(gitlabProviderId);
@ -79,8 +80,13 @@ export type ApplicationWithGitlab = InferResultType<
{ gitlabProvider: true } { gitlabProvider: true }
>; >;
export type ComposeWithGitlab = InferResultType<
"compose",
{ gitlabProvider: true }
>;
export const cloneGitlabRepository = async ( export const cloneGitlabRepository = async (
entity: ApplicationWithGitlab, entity: ApplicationWithGitlab | ComposeWithGitlab,
logPath: string, logPath: string,
isCompose = false, isCompose = false,
) => { ) => {
@ -92,6 +98,7 @@ export const cloneGitlabRepository = async (
gitlabBranch, gitlabBranch,
gitlabId, gitlabId,
gitlabProvider, gitlabProvider,
gitlabPathNamespace,
} = entity; } = entity;
if (!gitlabId) { if (!gitlabId) {
@ -121,7 +128,7 @@ export const cloneGitlabRepository = async (
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
await recreateDirectory(outputPath); await recreateDirectory(outputPath);
const repoclone = `gitlab.com/${gitlabOwner}/${gitlabRepository}.git`; const repoclone = `gitlab.com/${gitlabPathNamespace}.git`;
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`; const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
try { try {
@ -191,12 +198,11 @@ export const getGitlabRepositories = async (input: {
} }
return kind === "user"; return kind === "user";
}); });
const mappedRepositories = filteredRepos.map((repo: any) => { const mappedRepositories = filteredRepos.map((repo: any) => {
return { return {
id: repo.id, id: repo.id,
name: repo.name, name: repo.name,
url: repo.web_url, url: repo.path_with_namespace,
owner: { owner: {
username: repo.namespace.path, username: repo.namespace.path,
}, },
@ -249,16 +255,14 @@ export const getGitlabBranches = async (input: {
}[]; }[];
}; };
export const cloneRawGitlabRepository = async ( export const cloneRawGitlabRepository = async (entity: Compose) => {
entity: ApplicationWithGitlab,
) => {
const { const {
appName, appName,
gitlabRepository, gitlabRepository,
gitlabOwner, gitlabOwner,
gitlabBranch, gitlabBranch,
gitlabId, gitlabId,
gitlabProvider, gitlabPathNamespace,
} = entity; } = entity;
if (!gitlabId) { if (!gitlabId) {
@ -268,12 +272,15 @@ export const cloneRawGitlabRepository = async (
}); });
} }
const gitlabProvider = await getGitlabProvider(gitlabId);
await refreshGitlabToken(gitlabId); await refreshGitlabToken(gitlabId);
const basePath = COMPOSE_PATH; const basePath = COMPOSE_PATH;
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
await recreateDirectory(outputPath); await recreateDirectory(outputPath);
const repoclone = `gitlab.com/${gitlabOwner}/${gitlabRepository}.git`; const repoclone = `gitlab.com/${gitlabPathNamespace}.git`;
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`; const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
try { try {
await spawnAsync("git", [ await spawnAsync("git", [
"clone", "clone",

File diff suppressed because one or more lines are too long