App service implemented

This commit is contained in:
stefan.meyer
2024-10-24 09:47:31 +00:00
parent 71ec020965
commit a97cd08217
10 changed files with 239 additions and 0 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -0,0 +1,35 @@
/*
Warnings:
- Added the required column `containerImageSource` to the `App` table without a default value. This is not possible if the table is not empty.
*/
-- 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 NOT NULL,
"gitUrl" TEXT NOT NULL,
"gitBranch" TEXT NOT NULL,
"gitUsername" TEXT,
"gitToken" TEXT,
"dockerfilePath" TEXT NOT NULL DEFAULT './Dockerfile',
"replicas" INTEGER NOT NULL DEFAULT 1,
"envVars" TEXT NOT NULL,
"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" ("cpuLimit", "cpuReservation", "createdAt", "dockerfilePath", "envVars", "gitBranch", "gitToken", "gitUrl", "gitUsername", "id", "memoryLimit", "memoryReservation", "name", "projectId", "replicas", "updatedAt") SELECT "cpuLimit", "cpuReservation", "createdAt", "dockerfilePath", "envVars", "gitBranch", "gitToken", "gitUrl", "gitUsername", "id", "memoryLimit", "memoryReservation", "name", "projectId", "replicas", "updatedAt" FROM "App";
DROP TABLE "App";
ALTER TABLE "new_App" RENAME TO "App";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

29
src/app/apps/actions.ts Normal file
View File

@@ -0,0 +1,29 @@
'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

@@ -0,0 +1,55 @@
'use client'
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { SimpleDataTable } from "@/components/custom/simple-data-table";
import { formatDateTime } from "@/lib/format.utils";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { MoreHorizontal } from "lucide-react";
import { Toast } from "@/lib/toast.utils";
import { Project } from "@prisma/client";
import { deleteProject } from "./actions";
export default function ProjectsTable({ data }: { data: Project[] }) {
return <>
<SimpleDataTable columns={[
['id', 'ID', false],
['name', 'Name', true],
["createdAt", "Created At", true, (item) => formatDateTime(item.createdAt)],
["updatedAt", "Updated At", false, (item) => formatDateTime(item.updatedAt)],
]}
data={data}
onItemClickLink={(item) => `/project?id=${item.id}`}
actionCol={(item) =>
<>
<div className="flex">
<div className="flex-1"></div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<Link href={`/project?id=${item.id}`}>
<DropdownMenuItem>
<span>Show Apps of Project</span>
</DropdownMenuItem>
</Link>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => Toast.fromAction(() => deleteProject(item.id))}>
<span className="text-red-500">Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</>}
/>
</>
}

View File

@@ -0,0 +1,26 @@
'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>
}

24
src/app/apps/page.tsx Normal file
View File

@@ -0,0 +1,24 @@
'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,3 +1,5 @@
'use server'
import userService from "@/server/services/user.service";
import UserRegistrationForm from "./register-from";
import UserLoginForm from "./login-from";

View File

@@ -6,6 +6,8 @@ export const AppModel = z.object({
id: z.string(),
name: z.string(),
projectId: z.string(),
sourceType: z.string(),
containerImageSource: z.string(),
gitUrl: z.string(),
gitBranch: z.string(),
gitUsername: z.string().nullish(),

View File

@@ -0,0 +1,62 @@
import { revalidateTag, unstable_cache } from "next/cache";
import dataAccess from "../adapter/db.client";
import { Tags } from "../utils/cache-tag-generator.utils";
import { App, Prisma, Project } from "@prisma/client";
import { DefaultArgs } from "@prisma/client/runtime/library";
class AppService {
async deleteById(id: string) {
const existingItem = await this.getById(id);
if (!existingItem) {
return;
}
await dataAccess.client.app.delete({
where: {
id
}
});
revalidateTag(Tags.apps(existingItem.projectId));
}
async getAllAppsByProjectID(projectId: string) {
return await unstable_cache(async (projectId:string) => await dataAccess.client.app.findMany({
where: {
projectId
}
}),
[Tags.apps(projectId)], {
tags: [Tags.apps(projectId)]
})(projectId as string);
}
async getById(id: string) {
return dataAccess.client.app.findUnique({
where: {
id
}
});
}
async save(item: Prisma.AppUncheckedCreateInput | Prisma.AppUncheckedUpdateInput) {
let savedItem: Prisma.Prisma__AppClient<App, never, DefaultArgs>;
if (item.id) {
savedItem = dataAccess.client.app.update({
where: {
id: item.id as string
},
data: item
});
} else {
savedItem = dataAccess.client.app.create({
data: item as Prisma.AppUncheckedCreateInput
});
}
revalidateTag(Tags.apps(item.projectId as string));
return savedItem;
}
}
const appService = new AppService();
export default appService;

View File

@@ -7,4 +7,8 @@ export class Tags {
static projects() {
return `projects`;
}
static apps(projectId: string) {
return `apps-${projectId}`;
}
}