From 14a14ecaeaf490ea5db312317378c56a4248371d Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Mon, 11 Nov 2024 15:31:35 +0100 Subject: [PATCH 1/6] Profile Page with update user information. --- app/(customer)/dashboard/profile/page.tsx | 42 +++++++++++------ package.json | 1 + .../ButtonWithConfirm/ButtonWithConfirm.tsx | 45 +++++++++++++++++++ .../ButtonDeleteAccount.tsx | 32 +++++++++++++ .../delete-account.action.ts | 42 +++++++++++++++++ src/features/layout/page.tsx | 17 +++---- yarn.lock | 5 +++ 7 files changed, 159 insertions(+), 25 deletions(-) create mode 100644 src/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm.tsx create mode 100644 src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount.tsx create mode 100644 src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action.ts diff --git a/app/(customer)/dashboard/profile/page.tsx b/app/(customer)/dashboard/profile/page.tsx index b74954db..7b86781f 100644 --- a/app/(customer)/dashboard/profile/page.tsx +++ b/app/(customer)/dashboard/profile/page.tsx @@ -6,6 +6,11 @@ import {requiredCurrentUser} from "@/auth/current-user"; import {UserForm} from "@/components/wrappers/Dashboard/Profile/UserForm/UserForm"; import {prisma} from "@/prisma"; import {Badge} from "@/components/ui/badge"; +import Link from "next/link"; +import {Button} from "@/components/ui/button"; +import {ButtonWithConfirm} from "@/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm"; +import {ButtonDeleteAccount} from "@/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount"; +import {useIsMobile} from "@/hooks/use-mobile"; export default async function RoutePage(props: PageParams<{}>) { @@ -22,23 +27,32 @@ export default async function RoutePage(props: PageParams<{}>) { console.log(userInfo) + const test = () => { + console.log("test") + } + return ( - - - - {user.name?.[0]} - {user.image ? ( - - ) : null} - - {user.name} - {userInfo.authMethod} - - + {/**/} +
+ + + {user.name?.[0]} + {user.image ? ( + + ) : null} + + {user.name} + {userInfo.authMethod} + + + + +
+ {/*
*/} - +
-) + ) } \ No newline at end of file diff --git a/package.json b/package.json index 699ba2f8..13aff3d0 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "sonner": "^1.6.1", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", + "uuid": "^11.0.3", "vaul": "^1.1.1", "zod": "^3.23.8" }, diff --git a/src/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm.tsx b/src/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm.tsx new file mode 100644 index 00000000..5798e0d2 --- /dev/null +++ b/src/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm.tsx @@ -0,0 +1,45 @@ +"use client" +import {Button} from "@/components/ui/button"; +import {useState} from "react"; +import {Loader2} from "lucide-react"; + +export type VariantButton = { + secondary: string + default: string + outline: string + ghost: string + link: string + destructive: string +} + +export type ButtonWithConfirmProps = { + icon?: any, + text: string, + variant?: keyof VariantButton , + className?: string, + onClick?: () => void, + isPending? : boolean +}; + +export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => { + const [isConfirming, setIsConfirming] = useState(false) + return( + + ) +} \ No newline at end of file diff --git a/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount.tsx b/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount.tsx new file mode 100644 index 00000000..98ef5170 --- /dev/null +++ b/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount.tsx @@ -0,0 +1,32 @@ +"use client" +import {useMutation} from "@tanstack/react-query"; +import {useRouter} from "next/navigation"; +import {signOutAction} from "@/features/auth/auth.action"; +import {ButtonWithConfirm} from "@/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm"; +import {deleteUserAction} from "@/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action"; +import {Trash2} from "lucide-react"; + +export type ButtonDeleteAccountProps = {} + +export const ButtonDeleteAccount = (props: ButtonDeleteAccountProps) => { + + const mutation = useMutation({ + mutationFn: () => deleteUserAction(""), + onSuccess: async () => { + await signOutAction(); + }, + }) + + return ( + { + mutation.mutate() + }} + variant={"destructive"} + isPending={mutation.isPending} + className="gap-2" + icon={} + /> + ) +} diff --git a/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action.ts b/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action.ts new file mode 100644 index 00000000..c6f54ed8 --- /dev/null +++ b/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action.ts @@ -0,0 +1,42 @@ +"use server" +import {userAction} from "@/safe-actions"; +import {prisma} from "@/prisma"; +import {z} from "zod"; +import {v4 as uuidv4} from "uuid"; + + +export const deleteUserAction = userAction + .schema(z.string()) + .action(async ({parsedInput, ctx}) => { + + const uuid = uuidv4() + + const user = await prisma.user.update({ + where: { + id: ctx.user.id, + }, + data: { + email: `${uuid}@portabase.com`, + name: `${uuid}`, + } + }) + + const account = await prisma.account.findFirst({ + where: { + userId: ctx.user.id, + } + }) + if(account){ + await prisma.account.delete({ + where: { + id: account.id + } + + }) + } + + + return { + data: user, + } + }); \ No newline at end of file diff --git a/src/features/layout/page.tsx b/src/features/layout/page.tsx index 944cfb09..d2b31514 100644 --- a/src/features/layout/page.tsx +++ b/src/features/layout/page.tsx @@ -8,12 +8,9 @@ export const Page = ({children}: PropsWithChildren<{}>) => { ); }; -export const PageHeader = ({children}: PropsWithChildren<{}>) => { - return ( -
{children}
- ); -}; - +export const PageHeader = twx.div((props)=>[ + cn(`flex justify-between`, props.className), +]) export const PageTitle = twx.h1((props)=>[ cn(`text-3xl font-bold mb-6`, props.className), @@ -26,11 +23,9 @@ export const PageDescription = twx.h2((props)=>[ ]) -export const PageActions = ({children}: PropsWithChildren<{}>) => { - return ( -

{children}

- ); -}; +export const PageActions = twx.h1((props)=>[ + cn(`flex gap-4`, props.className), +]) export const PageContent = ({children}: PropsWithChildren<{}>) => { diff --git a/yarn.lock b/yarn.lock index c5a6dcf7..2eda0a4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4276,6 +4276,11 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@^11.0.3: + version "11.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.3.tgz#248451cac9d1a4a4128033e765d137e2b2c49a3d" + integrity sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg== + vaul@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/vaul/-/vaul-1.1.1.tgz#93aceaad16f7c53aacf28a2609b2dd43b5a91fa0" From 1e500f35834d693387fdacaeed2bdcd74027e21d Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Mon, 11 Nov 2024 15:33:21 +0100 Subject: [PATCH 2/6] Profile Page with update user information. --- .../dashboard/agents/[agentId]/page.tsx | 81 ++++++------------- package.json | 1 + 2 files changed, 27 insertions(+), 55 deletions(-) diff --git a/app/(customer)/dashboard/agents/[agentId]/page.tsx b/app/(customer)/dashboard/agents/[agentId]/page.tsx index 99586eb7..da0a3bf6 100644 --- a/app/(customer)/dashboard/agents/[agentId]/page.tsx +++ b/app/(customer)/dashboard/agents/[agentId]/page.tsx @@ -3,35 +3,46 @@ import {Page, PageActions, PageContent, PageDescription, PageHeader, PageTitle} import {Button} from "@/components/ui/button"; import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"; import {Card, CardContent, CardHeader} from "@/components/ui/card"; -import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table"; -import {StatusBadge} from "@/components/wrappers/status-badge"; +import {TablePagination} from "@/components/wrappers/table/table-pagination"; +import {columns} from "@/features/backup/columns"; +import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination"; export default async function RoutePage(props: PageParams<{}>) { const backups = [ - {'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'waiting'}, + {'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'}, {'id': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'}, {'id': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'}, - {'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'ongoing'}, - {'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'waiting'}, + {'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'}, + {'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'}, {'id': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'}, {'id': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'}, - {'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'ongoing'}, - {'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'waiting'}, - {'id': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'} + {'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'}, + {'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'}, + {'id': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'}, + {'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'}, + {'id': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'}, + {'id': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'}, + {'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'}, + {'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'}, + {'id': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'}, + {'id': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'}, + {'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'}, + {'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'}, + {'id': 'backup-11', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'} ] const restores = [ - {'id': 'restore-1', 'backupId': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'waiting'}, + {'id': 'restore-1', 'backupId': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'}, {'id': 'restore-2', 'backupId': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'}, {'id': 'restore-3', 'backupId': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'}, - {'id': 'restore-4', 'backupId': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'ongoing'}, - {'id': 'restore-5', 'backupId': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'waiting'}, + {'id': 'restore-4', 'backupId': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'}, + {'id': 'restore-5', 'backupId': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'}, {'id': 'restore-6', 'backupId': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'}, {'id': 'restore-7', 'backupId': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'}, - {'id': 'restore-8', 'backupId': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'ongoing'}, - {'id': 'restore-9', 'backupId': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'waiting'}, + {'id': 'restore-8', 'backupId': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'}, + {'id': 'restore-9', 'backupId': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'}, {'id': 'restore-10', 'backupId': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'} ] @@ -77,51 +88,11 @@ export default async function RoutePage(props: PageParams<{}>) { - - - - Reference - Date - Status - - - - - {backups.map((backup) => ( - - {backup.id} - {backup.createdAt} - - - - - ))} - -
+
- - - - Backup reference - Date - Status - - - - - {restores.map((restore) => ( - - {restore.backupId} - {restore.createdAt} - - - - - ))} - -
+
diff --git a/package.json b/package.json index 13aff3d0..cfceb8b8 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@radix-ui/react-tooltip": "^1.1.3", "@t3-oss/env-nextjs": "^0.11.1", "@tanstack/react-query": "^5.59.18", + "@tanstack/react-table": "^8.20.5", "argon2": "^0.41.1", "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", From 5a34498ac57335547381f933c8e410dba112423f Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Mon, 11 Nov 2024 16:18:09 +0100 Subject: [PATCH 3/6] Profile Page with update user information. --- .../20241103104432_2024_11_03/migration.sql | 0 .../20241103192308_2024_11_03/migration.sql | 0 .../20241111091911_2024_11_11/migration.sql | 0 prisma/migrations/20241111110357_/migration.sql | 12 ------------ .../20241111112810_2024_11_11/migration.sql | 0 5 files changed, 12 deletions(-) mode change 100644 => 100755 prisma/migrations/20241103104432_2024_11_03/migration.sql mode change 100644 => 100755 prisma/migrations/20241103192308_2024_11_03/migration.sql mode change 100644 => 100755 prisma/migrations/20241111091911_2024_11_11/migration.sql delete mode 100644 prisma/migrations/20241111110357_/migration.sql mode change 100644 => 100755 prisma/migrations/20241111112810_2024_11_11/migration.sql diff --git a/prisma/migrations/20241103104432_2024_11_03/migration.sql b/prisma/migrations/20241103104432_2024_11_03/migration.sql old mode 100644 new mode 100755 diff --git a/prisma/migrations/20241103192308_2024_11_03/migration.sql b/prisma/migrations/20241103192308_2024_11_03/migration.sql old mode 100644 new mode 100755 diff --git a/prisma/migrations/20241111091911_2024_11_11/migration.sql b/prisma/migrations/20241111091911_2024_11_11/migration.sql old mode 100644 new mode 100755 diff --git a/prisma/migrations/20241111110357_/migration.sql b/prisma/migrations/20241111110357_/migration.sql deleted file mode 100644 index cab955a3..00000000 --- a/prisma/migrations/20241111110357_/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[slug]` on the table `Agent` will be added. If there are existing duplicate values, this will fail. - - Added the required column `slug` to the `Agent` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "Agent" ADD COLUMN "slug" TEXT NOT NULL; - --- CreateIndex -CREATE UNIQUE INDEX "Agent_slug_key" ON "Agent"("slug"); diff --git a/prisma/migrations/20241111112810_2024_11_11/migration.sql b/prisma/migrations/20241111112810_2024_11_11/migration.sql old mode 100644 new mode 100755 From 221d0c4b264c14a3db6faaabd36ebc9c592495d6 Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Mon, 11 Nov 2024 16:25:16 +0100 Subject: [PATCH 4/6] Migrations --- .../20241111091911_2024_11_11/migration.sql | 46 ------------------- .../20241111112810_2024_11_11/migration.sql | 2 - .../20241111152326_2024_11_11/migration.sql | 15 ++++++ 3 files changed, 15 insertions(+), 48 deletions(-) delete mode 100755 prisma/migrations/20241111091911_2024_11_11/migration.sql delete mode 100755 prisma/migrations/20241111112810_2024_11_11/migration.sql create mode 100644 prisma/migrations/20241111152326_2024_11_11/migration.sql diff --git a/prisma/migrations/20241111091911_2024_11_11/migration.sql b/prisma/migrations/20241111091911_2024_11_11/migration.sql deleted file mode 100755 index 37a8421c..00000000 --- a/prisma/migrations/20241111091911_2024_11_11/migration.sql +++ /dev/null @@ -1,46 +0,0 @@ --- CreateEnum -CREATE TYPE "Status" AS ENUM ('waiting', 'ongoing', 'failed', 'success'); - --- CreateTable -CREATE TABLE "Agent" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "description" TEXT, - "last_contact" TIMESTAMP(3), - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Agent_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Database" ( - "id" TEXT NOT NULL, - "agent_id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "description" TEXT, - "backup_policy" TEXT, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Database_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Backup" ( - "id" TEXT NOT NULL, - "database_id" TEXT NOT NULL, - "status" "Status" NOT NULL DEFAULT 'waiting', - "file" TEXT, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Backup_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Restore" ( - "id" TEXT NOT NULL, - "backup_id" TEXT NOT NULL, - "status" "Status" NOT NULL DEFAULT 'waiting', - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Restore_pkey" PRIMARY KEY ("id") -); diff --git a/prisma/migrations/20241111112810_2024_11_11/migration.sql b/prisma/migrations/20241111112810_2024_11_11/migration.sql deleted file mode 100755 index b4621b56..00000000 --- a/prisma/migrations/20241111112810_2024_11_11/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AddForeignKey -ALTER TABLE "Database" ADD CONSTRAINT "Database_agent_id_fkey" FOREIGN KEY ("agent_id") REFERENCES "Agent"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20241111152326_2024_11_11/migration.sql b/prisma/migrations/20241111152326_2024_11_11/migration.sql new file mode 100644 index 00000000..8a710941 --- /dev/null +++ b/prisma/migrations/20241111152326_2024_11_11/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - A unique constraint covering the columns `[slug]` on the table `Agent` will be added. If there are existing duplicate values, this will fail. + - Added the required column `slug` to the `Agent` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Agent" ADD COLUMN "slug" TEXT NOT NULL; + +-- CreateIndex +CREATE UNIQUE INDEX "Agent_slug_key" ON "Agent"("slug"); + +-- AddForeignKey +ALTER TABLE "Database" ADD CONSTRAINT "Database_agent_id_fkey" FOREIGN KEY ("agent_id") REFERENCES "Agent"("id") ON DELETE CASCADE ON UPDATE CASCADE; From 55c5a359180a3bfc0a028b387bd10c4504c8d015 Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Mon, 11 Nov 2024 18:27:20 +0100 Subject: [PATCH 5/6] Working on API route for status. --- app/api/agent/[agentId]/status/route.ts | 47 +++++++++++++++++++++++++ middleware.ts | 39 ++++++++++++++++++++ package.json | 2 +- src/middleware/errorHandler.ts | 8 +++++ src/middleware/loggingMiddleware.ts | 8 +++++ tsconfig.json | 2 +- 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 app/api/agent/[agentId]/status/route.ts create mode 100644 middleware.ts create mode 100644 src/middleware/errorHandler.ts create mode 100644 src/middleware/loggingMiddleware.ts diff --git a/app/api/agent/[agentId]/status/route.ts b/app/api/agent/[agentId]/status/route.ts new file mode 100644 index 00000000..e601e83d --- /dev/null +++ b/app/api/agent/[agentId]/status/route.ts @@ -0,0 +1,47 @@ +import {prisma} from "@/prisma"; +import {NextResponse} from "next/server"; + +export async function POST( + request: Request, + {params}: { params: Promise<{ agentId: string }> } +) { + const agentId = (await params).agentId + + const agent = await prisma.agent.findFirst({ + where: { + id: agentId + } + }) + + if(!agent){ + return NextResponse.json({error: "Agent not found"}, {status: 404}) + } + + await prisma.agent.update({ + where: { + id: agent.id, + }, + data: { + lastContact: new Date(), + } + }); + + const response = { + agent: { + id: agentId, + lastContact: agent.lastContact + }, + backup: { + action: false, + cron : "" + }, + restore: { + action: false, + file: "" + } + } + + return Response.json({ + message: response + }) +} \ No newline at end of file diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 00000000..ecb9ada5 --- /dev/null +++ b/middleware.ts @@ -0,0 +1,39 @@ +import {NextRequest, NextResponse} from 'next/server' +import {loggingMiddleware} from "@/middleware/loggingMiddleware"; +import {errorHandler} from "@/middleware/errorHandler"; + +export function middleware(request: NextRequest) { + const url = request.nextUrl.clone(); + if (url.pathname.startsWith('/api')) { + const routeExists = checkRouteExists(url.pathname); + // If the route does not exist, return a 404 JSON response + if (!routeExists) { + return new NextResponse( + JSON.stringify({ message: "This API route does not exist.", status: 404 }), + { status: 404, headers: { 'Content-Type': 'application/json' } } + ); + } + } + try{ + loggingMiddleware(request); + }catch(err){ + errorHandler(err) + } + + +} +// Function to check if the route exists (supports dynamic routes) +function checkRouteExists(pathname) { + // Define static and dynamic routes with patterns + const routePatterns = [ + //do not delete + // /^\/api\/auth\/\d+$/, // Dynamic route with a number as a parameter (e.g., /api/dynamic/123) + // /^\/api\/auth\/\w+$/, // Dynamic route with a number as a parameter (e.g., /api/dynamic/123) + // /^\/api\/agent\/healthcheck\/\w+$/, // Dynamic route with an alphanumeric parameter (e.g., /api/user/username) + /^\/api\/agent\/[^/]+\/status\/?$/, // Dynamic route for /api/agent/[id]/status + ]; + return routePatterns.some(pattern => pattern.test(pathname)); +} +export const config = { + matcher: ['/api/agent/:path*'], +}; \ No newline at end of file diff --git a/package.json b/package.json index cfceb8b8..b1b4e371 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbopack -p 80", + "dev": "next dev -p 80", "build": "next build", "start": "next start", "lint": "next lint" diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts new file mode 100644 index 00000000..227b22fb --- /dev/null +++ b/src/middleware/errorHandler.ts @@ -0,0 +1,8 @@ +import {NextResponse} from "next/server"; + +export const errorHandler = (error: any) => { + return new NextResponse( + JSON.stringify({ message: 'An error occurred while processing your request.', status: 500 }), + { status: 500, headers: { 'Content-Type': 'application/json' } } + ); +}; \ No newline at end of file diff --git a/src/middleware/loggingMiddleware.ts b/src/middleware/loggingMiddleware.ts new file mode 100644 index 00000000..d22c4a63 --- /dev/null +++ b/src/middleware/loggingMiddleware.ts @@ -0,0 +1,8 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export function loggingMiddleware(request: NextRequest) { + if (request.url.includes('/api')) { + console.log(`[API] Received ${request.method} request : ${request.url} at ${new Date()}`); + } + return NextResponse.next(); +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 12112c71..a34a8ca0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,7 +33,7 @@ "next-env.d.ts", "**/*.ts", "**/*.tsx", - ".next/types/**/*.ts" + ".next/types/**/*.ts", ], "exclude": [ "node_modules" From 09529fb701913c65167d3992e7a09ecce3b3c278 Mon Sep 17 00:00:00 2001 From: killian-larcher Date: Mon, 11 Nov 2024 18:50:42 +0100 Subject: [PATCH 6/6] wrapping some agent components. --- .../dashboard/agents/[agentId]/page.tsx | 80 +++++-------------- app/(customer)/dashboard/agents/page.tsx | 2 +- app/(customer)/dashboard/layout.tsx | 2 +- app/(customer)/dashboard/profile/page.tsx | 42 ++++++---- app/api/agent/[agentId]/status/route.ts | 47 +++++++++++ middleware.ts | 39 +++++++++ package.json | 3 +- prisma/schema.prisma | 53 +++++------- .../ButtonWithConfirm/ButtonWithConfirm.tsx | 45 +++++++++++ .../ButtonDeleteAccount.tsx | 32 ++++++++ .../delete-account.action.ts | 42 ++++++++++ .../wrappers/cards-with-pagination.tsx | 6 +- .../pagination/pagination-navigation.tsx | 2 +- .../table/data-table-with-pagination.tsx | 4 +- .../wrappers/table/table-pagination-size.tsx | 7 +- .../wrappers/table/table-pagination.tsx | 9 +-- src/features/backup/columns.tsx | 5 +- src/features/layout/page.tsx | 46 ++++++----- src/features/restore/columns.tsx | 5 +- src/middleware/errorHandler.ts | 8 ++ src/middleware/loggingMiddleware.ts | 8 ++ tsconfig.json | 2 +- yarn.lock | 5 ++ 23 files changed, 338 insertions(+), 156 deletions(-) create mode 100644 app/api/agent/[agentId]/status/route.ts create mode 100644 middleware.ts create mode 100644 src/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm.tsx create mode 100644 src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount.tsx create mode 100644 src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action.ts create mode 100644 src/middleware/errorHandler.ts create mode 100644 src/middleware/loggingMiddleware.ts diff --git a/app/(customer)/dashboard/agents/[agentId]/page.tsx b/app/(customer)/dashboard/agents/[agentId]/page.tsx index 69211bc6..da0a3bf6 100644 --- a/app/(customer)/dashboard/agents/[agentId]/page.tsx +++ b/app/(customer)/dashboard/agents/[agentId]/page.tsx @@ -3,28 +3,12 @@ import {Page, PageActions, PageContent, PageDescription, PageHeader, PageTitle} import {Button} from "@/components/ui/button"; import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"; import {Card, CardContent, CardHeader} from "@/components/ui/card"; -import {backupColumns} from "@/features/backup/columns"; -import {restoreColumns} from "@/features/restore/columns"; +import {TablePagination} from "@/components/wrappers/table/table-pagination"; +import {columns} from "@/features/backup/columns"; import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination"; -import {prisma} from "@/prisma"; -import {GearIcon} from "@radix-ui/react-icons"; -import Link from "next/link"; -export default async function RoutePage(props: PageParams<{ agentId: string }>) { - - // const agent = await prisma.agent.findUnique({ - // where: { - // id: props.params.agentId, - // }, - // }) - - const agent = { - "id": props.params.agentId, - "name": "Agent 1", - "description": "My beautiful project!", - "lastContact": null, - } +export default async function RoutePage(props: PageParams<{}>) { const backups = [ {'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'}, @@ -62,81 +46,55 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>) {'id': 'restore-10', 'backupId': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'} ] - const databaseId = 'db-123'; - - const totalBackups = await prisma.backup.count({ - where: { - databaseId: databaseId, - }, - }); - - const successfulBackups = await prisma.backup.count({ - where: { - databaseId: databaseId, - status: 'success', - }, - }); - - const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null - - return ( - {agent.name} - - - + Agent 1 - - {agent.description} + My beautiful project! - -
+ +
- + Backups - + Success rate - - {successRate ?? "Unavailable for now."} - + - + Last contact - - {agent.lastContact?.toDateString() ?? "Never connected"} - +
- - + Backup - Backup + Restore - - + + - - + + +
diff --git a/app/(customer)/dashboard/agents/page.tsx b/app/(customer)/dashboard/agents/page.tsx index 38c883d5..f4b25e03 100644 --- a/app/(customer)/dashboard/agents/page.tsx +++ b/app/(customer)/dashboard/agents/page.tsx @@ -36,7 +36,7 @@ export default async function RoutePage(props: PageParams<{}>) { - +
-
+
{children}
diff --git a/app/(customer)/dashboard/profile/page.tsx b/app/(customer)/dashboard/profile/page.tsx index b74954db..7b86781f 100644 --- a/app/(customer)/dashboard/profile/page.tsx +++ b/app/(customer)/dashboard/profile/page.tsx @@ -6,6 +6,11 @@ import {requiredCurrentUser} from "@/auth/current-user"; import {UserForm} from "@/components/wrappers/Dashboard/Profile/UserForm/UserForm"; import {prisma} from "@/prisma"; import {Badge} from "@/components/ui/badge"; +import Link from "next/link"; +import {Button} from "@/components/ui/button"; +import {ButtonWithConfirm} from "@/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm"; +import {ButtonDeleteAccount} from "@/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount"; +import {useIsMobile} from "@/hooks/use-mobile"; export default async function RoutePage(props: PageParams<{}>) { @@ -22,23 +27,32 @@ export default async function RoutePage(props: PageParams<{}>) { console.log(userInfo) + const test = () => { + console.log("test") + } + return ( - - - - {user.name?.[0]} - {user.image ? ( - - ) : null} - - {user.name} - {userInfo.authMethod} - - + {/**/} +
+ + + {user.name?.[0]} + {user.image ? ( + + ) : null} + + {user.name} + {userInfo.authMethod} + + + + +
+ {/*
*/} - +
-) + ) } \ No newline at end of file diff --git a/app/api/agent/[agentId]/status/route.ts b/app/api/agent/[agentId]/status/route.ts new file mode 100644 index 00000000..e601e83d --- /dev/null +++ b/app/api/agent/[agentId]/status/route.ts @@ -0,0 +1,47 @@ +import {prisma} from "@/prisma"; +import {NextResponse} from "next/server"; + +export async function POST( + request: Request, + {params}: { params: Promise<{ agentId: string }> } +) { + const agentId = (await params).agentId + + const agent = await prisma.agent.findFirst({ + where: { + id: agentId + } + }) + + if(!agent){ + return NextResponse.json({error: "Agent not found"}, {status: 404}) + } + + await prisma.agent.update({ + where: { + id: agent.id, + }, + data: { + lastContact: new Date(), + } + }); + + const response = { + agent: { + id: agentId, + lastContact: agent.lastContact + }, + backup: { + action: false, + cron : "" + }, + restore: { + action: false, + file: "" + } + } + + return Response.json({ + message: response + }) +} \ No newline at end of file diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 00000000..ecb9ada5 --- /dev/null +++ b/middleware.ts @@ -0,0 +1,39 @@ +import {NextRequest, NextResponse} from 'next/server' +import {loggingMiddleware} from "@/middleware/loggingMiddleware"; +import {errorHandler} from "@/middleware/errorHandler"; + +export function middleware(request: NextRequest) { + const url = request.nextUrl.clone(); + if (url.pathname.startsWith('/api')) { + const routeExists = checkRouteExists(url.pathname); + // If the route does not exist, return a 404 JSON response + if (!routeExists) { + return new NextResponse( + JSON.stringify({ message: "This API route does not exist.", status: 404 }), + { status: 404, headers: { 'Content-Type': 'application/json' } } + ); + } + } + try{ + loggingMiddleware(request); + }catch(err){ + errorHandler(err) + } + + +} +// Function to check if the route exists (supports dynamic routes) +function checkRouteExists(pathname) { + // Define static and dynamic routes with patterns + const routePatterns = [ + //do not delete + // /^\/api\/auth\/\d+$/, // Dynamic route with a number as a parameter (e.g., /api/dynamic/123) + // /^\/api\/auth\/\w+$/, // Dynamic route with a number as a parameter (e.g., /api/dynamic/123) + // /^\/api\/agent\/healthcheck\/\w+$/, // Dynamic route with an alphanumeric parameter (e.g., /api/user/username) + /^\/api\/agent\/[^/]+\/status\/?$/, // Dynamic route for /api/agent/[id]/status + ]; + return routePatterns.some(pattern => pattern.test(pathname)); +} +export const config = { + matcher: ['/api/agent/:path*'], +}; \ No newline at end of file diff --git a/package.json b/package.json index 9f02445a..b1b4e371 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbopack -p 80", + "dev": "next dev -p 80", "build": "next build", "start": "next start", "lint": "next lint" @@ -67,6 +67,7 @@ "sonner": "^1.6.1", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", + "uuid": "^11.0.3", "vaul": "^1.1.1", "zod": "^3.23.8" }, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f44dc634..de672dd0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -61,39 +61,35 @@ model User { image String? role String? password String? - authMethod String? @map("auth_method") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime? @updatedAt @map("updated_at") - accounts Account[] - sessions Session[] + accounts Account[] + sessions Session[] + authMethod String? + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime? @updatedAt @map("updated_at") @@map("users") } model Agent { - id String @id @default(cuid()) - slug String @unique + id String @id @default(cuid()) + slug String @unique name String description String? - lastContact DateTime? @map("last_contact") - createdAt DateTime @default(now()) @map("created_at") - - databases Database[] + lastContact DateTime? @map("last_contact") + createdAt DateTime @default(now()) @map("created_at") + databases Database[] } model Database { - id String @id @default(cuid()) + id String @id @default(cuid()) + agentId String @map("agent_id") + agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade) + name String description String? backupPolicy String? @map("backup_policy") createdAt DateTime @default(now()) @map("created_at") - - agentId String @map("agent_id") - agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade) - - backups Backup[] - restaurations Restauration[] } enum Status { @@ -104,25 +100,16 @@ enum Status { } model Backup { - id String @id @default(cuid()) - status Status @default(waiting) - file String? - createdAt DateTime @default(now()) @map("created_at") - + id String @id @default(cuid()) databaseId String @map("database_id") - database Database @relation(fields: [databaseId], references: [id], onDelete: Cascade) - - restaurations Restauration[] + status Status @default(waiting) + file String? + createdAt DateTime @default(now()) @map("created_at") } -model Restauration { +model Restore { id String @id @default(cuid()) + backupId String @map("backup_id") status Status @default(waiting) createdAt DateTime @default(now()) @map("created_at") - - backupId String @map("backup_id") - backup Backup @relation(fields: [backupId], references: [id], onDelete: Cascade) - - databaseId String? @map("database_id") - database Database? @relation(fields: [databaseId], references: [id], onDelete: Cascade) } diff --git a/src/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm.tsx b/src/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm.tsx new file mode 100644 index 00000000..5798e0d2 --- /dev/null +++ b/src/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm.tsx @@ -0,0 +1,45 @@ +"use client" +import {Button} from "@/components/ui/button"; +import {useState} from "react"; +import {Loader2} from "lucide-react"; + +export type VariantButton = { + secondary: string + default: string + outline: string + ghost: string + link: string + destructive: string +} + +export type ButtonWithConfirmProps = { + icon?: any, + text: string, + variant?: keyof VariantButton , + className?: string, + onClick?: () => void, + isPending? : boolean +}; + +export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => { + const [isConfirming, setIsConfirming] = useState(false) + return( + + ) +} \ No newline at end of file diff --git a/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount.tsx b/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount.tsx new file mode 100644 index 00000000..98ef5170 --- /dev/null +++ b/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/ButtonDeleteAccount.tsx @@ -0,0 +1,32 @@ +"use client" +import {useMutation} from "@tanstack/react-query"; +import {useRouter} from "next/navigation"; +import {signOutAction} from "@/features/auth/auth.action"; +import {ButtonWithConfirm} from "@/components/wrappers/Button/ButtonWithConfirm/ButtonWithConfirm"; +import {deleteUserAction} from "@/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action"; +import {Trash2} from "lucide-react"; + +export type ButtonDeleteAccountProps = {} + +export const ButtonDeleteAccount = (props: ButtonDeleteAccountProps) => { + + const mutation = useMutation({ + mutationFn: () => deleteUserAction(""), + onSuccess: async () => { + await signOutAction(); + }, + }) + + return ( + { + mutation.mutate() + }} + variant={"destructive"} + isPending={mutation.isPending} + className="gap-2" + icon={} + /> + ) +} diff --git a/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action.ts b/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action.ts new file mode 100644 index 00000000..c6f54ed8 --- /dev/null +++ b/src/components/wrappers/Dashboard/Profile/ButtonDeleteAccount/delete-account.action.ts @@ -0,0 +1,42 @@ +"use server" +import {userAction} from "@/safe-actions"; +import {prisma} from "@/prisma"; +import {z} from "zod"; +import {v4 as uuidv4} from "uuid"; + + +export const deleteUserAction = userAction + .schema(z.string()) + .action(async ({parsedInput, ctx}) => { + + const uuid = uuidv4() + + const user = await prisma.user.update({ + where: { + id: ctx.user.id, + }, + data: { + email: `${uuid}@portabase.com`, + name: `${uuid}`, + } + }) + + const account = await prisma.account.findFirst({ + where: { + userId: ctx.user.id, + } + }) + if(account){ + await prisma.account.delete({ + where: { + id: account.id + } + + }) + } + + + return { + data: user, + } + }); \ No newline at end of file diff --git a/src/components/wrappers/cards-with-pagination.tsx b/src/components/wrappers/cards-with-pagination.tsx index 4c78ed2a..e3c2e892 100644 --- a/src/components/wrappers/cards-with-pagination.tsx +++ b/src/components/wrappers/cards-with-pagination.tsx @@ -42,14 +42,14 @@ export const CardsWithPagination = (props: cardsWithPaginationProps) => { return ( -
-
+
+
{currentCards.map((card, key) => ( ))}
{ const {className, totalPages, currentPage, goToPage, goToPrevPage, goToNextPage, maxVisiblePages = 3} = props return ( - + diff --git a/src/components/wrappers/table/data-table-with-pagination.tsx b/src/components/wrappers/table/data-table-with-pagination.tsx index b1e68d38..b3f39fea 100644 --- a/src/components/wrappers/table/data-table-with-pagination.tsx +++ b/src/components/wrappers/table/data-table-with-pagination.tsx @@ -30,9 +30,9 @@ export function DataTableWithPagination({columns, data}: DataTabl }) return ( -
+
- +
) } diff --git a/src/components/wrappers/table/table-pagination-size.tsx b/src/components/wrappers/table/table-pagination-size.tsx index f9a20a7d..071ede60 100644 --- a/src/components/wrappers/table/table-pagination-size.tsx +++ b/src/components/wrappers/table/table-pagination-size.tsx @@ -1,4 +1,3 @@ -import {useEffect} from "react"; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"; import {cn} from "@/lib/utils"; @@ -13,12 +12,8 @@ export const TablePaginationSize = (props: tablePaginationSizeProps) => { const {className, table, pageSizeOptions = [10, 20, 30, 40, 50]} = props - useEffect(() => { - table.setPageSize(Number(pageSizeOptions[0])) - }, []); - return ( -
+

Rows per page