created create process from project to app

This commit is contained in:
biersoeckli
2024-10-24 11:57:48 +00:00
parent 4657b84f88
commit 55e297ce6b
18 changed files with 170 additions and 103 deletions

View File

@@ -0,0 +1,29 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_App" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"projectId" TEXT NOT NULL,
"sourceType" TEXT NOT NULL DEFAULT 'GIT',
"containerImageSource" TEXT,
"gitUrl" TEXT,
"gitBranch" TEXT,
"gitUsername" TEXT,
"gitToken" TEXT,
"dockerfilePath" TEXT NOT NULL DEFAULT './Dockerfile',
"replicas" INTEGER NOT NULL DEFAULT 1,
"envVars" TEXT NOT NULL DEFAULT '',
"memoryReservation" INTEGER,
"memoryLimit" INTEGER,
"cpuReservation" INTEGER,
"cpuLimit" INTEGER,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "App_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_App" ("containerImageSource", "cpuLimit", "cpuReservation", "createdAt", "dockerfilePath", "envVars", "gitBranch", "gitToken", "gitUrl", "gitUsername", "id", "memoryLimit", "memoryReservation", "name", "projectId", "replicas", "sourceType", "updatedAt") SELECT "containerImageSource", "cpuLimit", "cpuReservation", "createdAt", "dockerfilePath", "envVars", "gitBranch", "gitToken", "gitUrl", "gitUsername", "id", "memoryLimit", "memoryReservation", "name", "projectId", "replicas", "sourceType", "updatedAt" FROM "App";
DROP TABLE "App";
ALTER TABLE "new_App" RENAME TO "App";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -128,16 +128,16 @@ model App {
project Project @relation(fields: [projectId], references: [id])
sourceType String @default("GIT") // GIT, CONTAINER
containerImageSource String
containerImageSource String?
gitUrl String
gitBranch String
gitUrl String?
gitBranch String?
gitUsername String?
gitToken String?
dockerfilePath String @default("./Dockerfile")
replicas Int @default(1)
envVars String
envVars String @default("")
memoryReservation Int?
memoryLimit Int?

View File

@@ -1,29 +0,0 @@
'use server'
import { AppModel } from "@/model/generated-zod";
import { SuccessActionResult } from "@/model/server-action-error-return.model";
import appService from "@/server/services/app.service";
import { getAuthUserSession, saveFormAction, simpleAction } from "@/server/utils/action-wrapper.utils";
import { z } from "zod";
const createAppSchema = z.object({
projectName: z.string().min(1)
});
export const createApp = async (projectName: string) =>
saveFormAction({ projectName }, createAppSchema, async (validatedData) => {
await getAuthUserSession();
await appService.save({
name: validatedData.projectName
});
return new SuccessActionResult(undefined, "Project created successfully.");
});
export const deleteProject = async (projectId: string) =>
simpleAction(async () => {
await getAuthUserSession();
await appService.deleteById(projectId);
return new SuccessActionResult(undefined, "Project deleted successfully.");
});

View File

@@ -1,26 +0,0 @@
'use client'
import { InputDialog } from "@/components/custom/input-dialog"
import { Button } from "@/components/ui/button"
import { Toast } from "@/lib/toast.utils";
import { createProject } from "./actions";
export function CreateProjectDialog() {
const createProj = async (name: string | undefined) => {
if (!name) {
return true;
}
const result = await Toast.fromAction(() => createProject(name));
return result.status === "success";
};
return <InputDialog
title="Create Project"
description="Name your new project."
fieldName="Name"
onResult={createProj}>
<Button>Create Project</Button>
</InputDialog>
}

View File

@@ -1,24 +0,0 @@
'use server'
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { getAuthUserSession, getUserSession } from "@/server/utils/action-wrapper.utils";
import projectService from "@/server/services/project.service";
import ProjectsTable from "./apps-table";
import { CreateProjectDialog } from "./create-app-dialog";
export default async function ProjectPage() {
await getAuthUserSession();
const data = await projectService.getAllProjects();
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<div className="flex gap-4">
<h2 className="text-3xl font-bold tracking-tight flex-1">Projects</h2>
<CreateProjectDialog />
</div>
<ProjectsTable data={data} />
</div>
)
}

View File

@@ -1,5 +1,5 @@
import { Button } from "@/components/ui/button";
import ProjectPage from "./projects/page";
import ProjectPage from "./projects/project-page";
export default function Home() {
return <ProjectPage />;

View File

@@ -0,0 +1,29 @@
'use server'
import { SuccessActionResult } from "@/model/server-action-error-return.model";
import appService from "@/server/services/app.service";
import { getAuthUserSession, saveFormAction, simpleAction } from "@/server/utils/action-wrapper.utils";
import { z } from "zod";
const createAppSchema = z.object({
appName: z.string().min(1)
});
export const createApp = async (appName: string, projectId: string) =>
saveFormAction({ appName }, createAppSchema, async (validatedData) => {
await getAuthUserSession();
const returnData = await appService.save({
name: validatedData.appName,
projectId
});
return new SuccessActionResult(returnData, "App created successfully.");
});
export const deleteApp = async (appId: string) =>
simpleAction(async () => {
await getAuthUserSession();
await appService.deleteById(appId);
return new SuccessActionResult(undefined, "App deleted successfully.");
});

View File

@@ -14,11 +14,15 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Label } from "@/components/ui/label";
import { AppRateLimitsModel, appRateLimitsZodModel } from "@/model/app-rate-limits.model";
import { App } from "@prisma/client";
export default function GeneralAppRateLimits() {
export default function GeneralAppRateLimits({ app }: {
app: App
}) {
const form = useForm<AppRateLimitsModel>({
resolver: zodResolver(appRateLimitsZodModel)
resolver: zodResolver(appRateLimitsZodModel),
defaultValues: app
});
const appId = '123'; // todo get from url;

View File

@@ -14,10 +14,17 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Label } from "@/components/ui/label";
import { useEffect } from "react";
import { App } from "@prisma/client";
export default function GeneralAppSource() {
export default function GeneralAppSource({ app }: {
app: App
}) {
const form = useForm<AppSourceInfoInputModel>({
resolver: zodResolver(appSourceInfoInputZodModel)
resolver: zodResolver(appSourceInfoInputZodModel),
defaultValues: {
...app,
sourceType: app.sourceType as 'GIT' | 'CONTAINER'
}
});
const appId = '123'; // todo get from url;

View File

@@ -4,6 +4,7 @@ import { redirect } from "next/navigation";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import GeneralAppRateLimits from "./general/app-rate-limits";
import GeneralAppSource from "./general/app-source";
import appService from "@/server/services/app.service";
@@ -13,6 +14,11 @@ export default async function AppPage({
searchParams?: { [key: string]: string | undefined };
}) {
await getAuthUserSession();
const appId = searchParams?.appId;
if (!appId) {
return <p>Could not find app with id {appId}</p>
}
const app = await appService.getById(appId);
return (
<div className="flex-1 space-y-4 p-8 pt-6">
@@ -30,8 +36,8 @@ export default async function AppPage({
</TabsList>
<TabsContent value="overview">Domains, Logs, etc.</TabsContent>
<TabsContent value="general" className="space-y-4">
<GeneralAppSource />
<GeneralAppRateLimits />
<GeneralAppSource app={app} />
<GeneralAppRateLimits app={app} />
</TabsContent>
<TabsContent value="environment">environment</TabsContent>
<TabsContent value="domains">domains</TabsContent>

View File

@@ -9,11 +9,11 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel,
import { MoreHorizontal } from "lucide-react";
import { Toast } from "@/lib/toast.utils";
import { Project } from "@prisma/client";
import { deleteProject } from "./actions";
import { deleteApp } from "./actions";
export default function ProjectsTable({ data }: { data: Project[] }) {
export default function AppTable({ data }: { data: Project[] }) {
return <>
<SimpleDataTable columns={[
@@ -23,7 +23,7 @@ export default function ProjectsTable({ data }: { data: Project[] }) {
["updatedAt", "Updated At", false, (item) => formatDateTime(item.updatedAt)],
]}
data={data}
onItemClickLink={(item) => `/project?id=${item.id}`}
onItemClickLink={(item) => `/project/app?appId=${item.id}`}
actionCol={(item) =>
<>
<div className="flex">
@@ -37,14 +37,14 @@ export default function ProjectsTable({ data }: { data: Project[] }) {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<Link href={`/project?id=${item.id}`}>
<Link href={`/project/app?appId=${item.id}`}>
<DropdownMenuItem>
<span>Show Apps of Project</span>
<span>Show App Details</span>
</DropdownMenuItem>
</Link>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => Toast.fromAction(() => deleteProject(item.id))}>
<span className="text-red-500">Delete Project</span>
<DropdownMenuItem onClick={() => Toast.fromAction(() => deleteApp(item.id))}>
<span className="text-red-500">Delete App</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -0,0 +1,38 @@
'use client'
import { InputDialog } from "@/components/custom/input-dialog"
import { Button } from "@/components/ui/button"
import { Toast } from "@/lib/toast.utils";
import { createApp } from "./actions";
import { redirect } from "next/navigation";
import { useRouter } from "next/navigation";
export function CreateAppDialog({
projectId
}: {
projectId: string;
}) {
const router = useRouter();
const createAppFunc = async (name: string | undefined) => {
if (!name) {
return true;
}
const result = await Toast.fromAction(() => createApp(name, projectId));
if (result.status === "success") {
router.push(`/project/app?appId=${result.data.id}`);
return true;
}
return false;
};
return <InputDialog
title="Create App"
description="Name your new App."
fieldName="Name"
onResult={createAppFunc}>
<Button>Create App</Button>
</InputDialog>
}

33
src/app/project/page.tsx Normal file
View File

@@ -0,0 +1,33 @@
'use server'
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { getAuthUserSession, getUserSession } from "@/server/utils/action-wrapper.utils";
import projectService from "@/server/services/project.service";
import AppTable from "./apps-table";
import { CreateAppDialog } from "./create-app-dialog";
import appService from "@/server/services/app.service";
export default async function AppsPage({
searchParams,
}: {
searchParams?: { [key: string]: string | undefined };
}) {
await getAuthUserSession();
const projectId = searchParams?.projectId;
if (!projectId) {
return <p>Could not find project with id {projectId}</p>
}
const data = await appService.getAllAppsByProjectID(projectId);
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<div className="flex gap-4">
<h2 className="text-3xl font-bold tracking-tight flex-1">Apps</h2>
<CreateAppDialog projectId={projectId} />
</div>
<AppTable data={data} />
</div>
)
}

View File

@@ -23,7 +23,7 @@ export default function ProjectsTable({ data }: { data: Project[] }) {
["updatedAt", "Updated At", false, (item) => formatDateTime(item.updatedAt)],
]}
data={data}
onItemClickLink={(item) => `/project?id=${item.id}`}
onItemClickLink={(item) => `/project?projectId=${item.id}`}
actionCol={(item) =>
<>
<div className="flex">
@@ -37,7 +37,7 @@ export default function ProjectsTable({ data }: { data: Project[] }) {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<Link href={`/project?id=${item.id}`}>
<Link href={`/project?projectId=${item.id}`}>
<DropdownMenuItem>
<span>Show Apps of Project</span>
</DropdownMenuItem>

View File

@@ -7,9 +7,9 @@ export const AppModel = z.object({
name: z.string(),
projectId: z.string(),
sourceType: z.string(),
containerImageSource: z.string(),
gitUrl: z.string(),
gitBranch: z.string(),
containerImageSource: z.string().nullish(),
gitUrl: z.string().nullish(),
gitBranch: z.string().nullish(),
gitUsername: z.string().nullish(),
gitToken: z.string().nullish(),
dockerfilePath: z.string(),

View File

@@ -31,7 +31,7 @@ class AppService {
}
async getById(id: string) {
return dataAccess.client.app.findUnique({
return dataAccess.client.app.findFirstOrThrow({
where: {
id
}