diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..429121c3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+migrate:
+ docker compose run --rm app sh -c "npx prisma migrate dev"
\ No newline at end of file
diff --git a/app/(customer)/dashboard/agents/[agentId]/page.tsx b/app/(customer)/dashboard/agents/[agentId]/page.tsx
index f66fb57f..4a53ae19 100644
--- a/app/(customer)/dashboard/agents/[agentId]/page.tsx
+++ b/app/(customer)/dashboard/agents/[agentId]/page.tsx
@@ -13,6 +13,7 @@ import {AgentModalKey} from "@/components/wrappers/Agent/AgentModalKey/AgentModa
import {KeyRound} from "lucide-react";
import {BackupButton} from "@/components/wrappers/BackupButton/BackupButton";
import {backups, restaurations} from "@/utils/mock-data";
+import {formatDateLastContact} from "@/utils/date-formatting";
export default async function RoutePage(props: PageParams<{ agentId: string }>) {
@@ -84,7 +85,7 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>)
Last contact
- {agent.lastContact?.toDateString() ?? "Never connected."}
+ {formatDateLastContact(agent.lastContact)}
diff --git a/app/(customer)/dashboard/agents/page.tsx b/app/(customer)/dashboard/agents/page.tsx
index 258dc5d7..58a7c133 100644
--- a/app/(customer)/dashboard/agents/page.tsx
+++ b/app/(customer)/dashboard/agents/page.tsx
@@ -6,9 +6,11 @@ import {Button} from "@/components/ui/button";
import Link from 'next/link'
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
+
export default async function RoutePage(props: PageParams<{}>) {
const agents = await prisma.agent.findMany({})
+
return (
diff --git a/app/(customer)/dashboard/layout.tsx b/app/(customer)/dashboard/layout.tsx
index 05a98994..f23fddb7 100644
--- a/app/(customer)/dashboard/layout.tsx
+++ b/app/(customer)/dashboard/layout.tsx
@@ -6,6 +6,7 @@ import {AppSidebar} from "@/components/wrappers/Dashboard/SideBar/app-sidebar";
import {currentUser} from "@/auth/current-user";
import {Header} from "@/features/layout/Header";
import {prisma} from "@/prisma";
+import {GlobalStoreProvider} from "@/state-management/provider";
export default async function Layout({children}: { children: React.ReactNode }) {
@@ -22,16 +23,20 @@ export default async function Layout({children}: { children: React.ReactNode })
if (!user) redirect('/login')
return (
-
-
-
-
-
-
- {children}
-
-
-
-
+ //
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ //
)
}
\ No newline at end of file
diff --git a/app/(customer)/dashboard/projects/[projectId]/edit/page.tsx b/app/(customer)/dashboard/projects/[projectId]/edit/page.tsx
index b835010f..ed55ed63 100644
--- a/app/(customer)/dashboard/projects/[projectId]/edit/page.tsx
+++ b/app/(customer)/dashboard/projects/[projectId]/edit/page.tsx
@@ -32,10 +32,13 @@ export default async function RoutePage(props: PageParams<{
const availableDatabases = await prisma.database.findMany({
where: {
OR: [
- { projectId: null },
- { projectId: project.id },
+ {projectId: null},
+ {projectId: project.id},
],
},
+ include: {
+ agent: {}
+ },
orderBy: {
createdAt: 'desc',
},
@@ -50,7 +53,8 @@ export default async function RoutePage(props: PageParams<{
-
+
)
diff --git a/app/(customer)/dashboard/projects/new/page.tsx b/app/(customer)/dashboard/projects/new/page.tsx
index 17e1a304..33ef8647 100644
--- a/app/(customer)/dashboard/projects/new/page.tsx
+++ b/app/(customer)/dashboard/projects/new/page.tsx
@@ -10,6 +10,9 @@ export default async function RoutePage(props: PageParams<{}>) {
where: {
projectId: null
},
+ include: {
+ agent: {}
+ },
orderBy: {
createdAt: 'desc',
},
diff --git a/app/(customer)/dashboard/projects/page.tsx b/app/(customer)/dashboard/projects/page.tsx
index 518cc654..51999067 100644
--- a/app/(customer)/dashboard/projects/page.tsx
+++ b/app/(customer)/dashboard/projects/page.tsx
@@ -5,17 +5,28 @@ import {Button} from "@/components/ui/button";
import Link from 'next/link'
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {ProjectCard} from "@/components/wrappers/Dashboard/Projects/ProjectCard/ProjectCard";
+import {requiredCurrentUser} from "@/auth/current-user";
export default async function RoutePage(props: PageParams<{}>) {
+ // const {searchParams} = props
+ // const organizationId = searchParams?.organizationId || "default";
+
+ const user = await requiredCurrentUser()
const projects = await prisma.project.findMany({
where: {
organization: {
slug: "default",
+ // id: organizationId,
+ users: {
+ some: {
+ userId: user.id
+ },
+ },
}
},
- include:{
+ include: {
databases: {}
}
})
diff --git a/app/middleware.ts b/app/middleware.ts
new file mode 100644
index 00000000..7e54a7c4
--- /dev/null
+++ b/app/middleware.ts
@@ -0,0 +1,11 @@
+// import {NextResponse} from "next/server";
+// import {useStore} from "@/state-management/store";
+//
+// export function middleware(req) {
+// // const organizationId = req.cookies.get("organizationId") || "default"; // Retrieve from cookies
+// const url = req.nextUrl.clone();
+// const {organizationId, moveToAnotherOrganization} = useStore((state) => state);
+// console.log("middlewaressss", organizationId);
+// // url.searchParams.set("organizationId", organizationId); // Append to query string
+// return NextResponse.rewrite(url);
+// }
\ No newline at end of file
diff --git a/docker/dockerfile/Dockerfile b/docker/dockerfile/Dockerfile
index f08aa4dd..96518981 100644
--- a/docker/dockerfile/Dockerfile
+++ b/docker/dockerfile/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:20-alpine AS base
+FROM node:20.1.0-alpine AS base
ENV YARN_VERSION=4.2.2
diff --git a/package.json b/package.json
index 2c59cd6e..bf7defe1 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,6 @@
"start": "next start",
"lint": "next lint",
"email": "email dev"
-
},
"dependencies": {
"@auth/prisma-adapter": "^2.4.1",
@@ -76,7 +75,8 @@
"tailwindcss-animate": "^1.0.7",
"uuid": "^11.0.3",
"vaul": "^1.1.1",
- "zod": "^3.23.8"
+ "zod": "^3.23.8",
+ "zustand": "^5.0.2"
},
"devDependencies": {
"@types/node": "^20",
diff --git a/prisma/migrations/20241212103214_2024_12_12/migration.sql b/prisma/migrations/20241212103214_2024_12_12/migration.sql
new file mode 100644
index 00000000..ddea0021
--- /dev/null
+++ b/prisma/migrations/20241212103214_2024_12_12/migration.sql
@@ -0,0 +1,17 @@
+-- CreateTable
+CREATE TABLE "_OrganizationToUser" (
+ "A" TEXT NOT NULL,
+ "B" TEXT NOT NULL
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "_OrganizationToUser_AB_unique" ON "_OrganizationToUser"("A", "B");
+
+-- CreateIndex
+CREATE INDEX "_OrganizationToUser_B_index" ON "_OrganizationToUser"("B");
+
+-- AddForeignKey
+ALTER TABLE "_OrganizationToUser" ADD CONSTRAINT "_OrganizationToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "_OrganizationToUser" ADD CONSTRAINT "_OrganizationToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/migrations/20241212103831_2024_12_12/migration.sql b/prisma/migrations/20241212103831_2024_12_12/migration.sql
new file mode 100644
index 00000000..d0912c61
--- /dev/null
+++ b/prisma/migrations/20241212103831_2024_12_12/migration.sql
@@ -0,0 +1,32 @@
+/*
+ Warnings:
+
+ - You are about to drop the `_OrganizationToUser` table. If the table is not empty, all the data it contains will be lost.
+
+*/
+-- DropForeignKey
+ALTER TABLE "_OrganizationToUser" DROP CONSTRAINT "_OrganizationToUser_A_fkey";
+
+-- DropForeignKey
+ALTER TABLE "_OrganizationToUser" DROP CONSTRAINT "_OrganizationToUser_B_fkey";
+
+-- DropTable
+DROP TABLE "_OrganizationToUser";
+
+-- CreateTable
+CREATE TABLE "UserOrganization" (
+ "id" TEXT NOT NULL,
+ "userId" TEXT NOT NULL,
+ "organizationId" TEXT NOT NULL,
+
+ CONSTRAINT "UserOrganization_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "UserOrganization_userId_organizationId_key" ON "UserOrganization"("userId", "organizationId");
+
+-- AddForeignKey
+ALTER TABLE "UserOrganization" ADD CONSTRAINT "UserOrganization_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "UserOrganization" ADD CONSTRAINT "UserOrganization_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/prisma/migrations/20241212110141_2024_12_12/migration.sql b/prisma/migrations/20241212110141_2024_12_12/migration.sql
new file mode 100644
index 00000000..4521b67f
--- /dev/null
+++ b/prisma/migrations/20241212110141_2024_12_12/migration.sql
@@ -0,0 +1,32 @@
+/*
+ Warnings:
+
+ - You are about to drop the `UserOrganization` table. If the table is not empty, all the data it contains will be lost.
+
+*/
+-- DropForeignKey
+ALTER TABLE "UserOrganization" DROP CONSTRAINT "UserOrganization_organizationId_fkey";
+
+-- DropForeignKey
+ALTER TABLE "UserOrganization" DROP CONSTRAINT "UserOrganization_userId_fkey";
+
+-- DropTable
+DROP TABLE "UserOrganization";
+
+-- CreateTable
+CREATE TABLE "users_organisations" (
+ "id" TEXT NOT NULL,
+ "userId" TEXT NOT NULL,
+ "organizationId" TEXT NOT NULL,
+
+ CONSTRAINT "users_organisations_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "users_organisations_userId_organizationId_key" ON "users_organisations"("userId", "organizationId");
+
+-- AddForeignKey
+ALTER TABLE "users_organisations" ADD CONSTRAINT "users_organisations_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "users_organisations" ADD CONSTRAINT "users_organisations_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index c771cdae..1c69c23d 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -54,19 +54,20 @@ model VerificationToken {
}
model User {
- id String @id @default(cuid())
+ id String @id @default(cuid())
name String?
- email String? @unique
- emailVerified DateTime? @map("email_verified")
+ email String? @unique
+ emailVerified DateTime? @map("email_verified")
image String?
role Role
password String?
- authMethod String? @map("auth_method")
- createdAt DateTime @default(now()) @map("created_at")
- updatedAt DateTime? @updatedAt @map("updated_at")
- deleted Boolean? @default(false)
+ authMethod String? @map("auth_method")
+ createdAt DateTime @default(now()) @map("created_at")
+ updatedAt DateTime? @updatedAt @map("updated_at")
+ deleted Boolean? @default(false)
accounts Account[]
sessions Session[]
+ organizations UserOrganization[]
@@map("users")
}
@@ -84,10 +85,22 @@ model Organization {
createdAt DateTime @default(now()) @map("created_at")
projects Project[]
+ users UserOrganization[]
@@map("organizations")
}
+model UserOrganization {
+ id String @id @default(cuid())
+ userId String
+ organizationId String
+ user User @relation(fields: [userId], references: [id])
+ organization Organization @relation(fields: [organizationId], references: [id])
+
+ @@unique([userId, organizationId])
+ @@map("users_organisations")
+}
+
model Project {
id String @id @default(cuid())
slug String @unique
diff --git a/src/auth/auth.ts b/src/auth/auth.ts
index 3f225040..879cbc1a 100644
--- a/src/auth/auth.ts
+++ b/src/auth/auth.ts
@@ -6,7 +6,6 @@ import {env} from "@/env.mjs";
import GoogleProvider from "next-auth/providers/google";
-
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
adapter: PrismaAdapter(prisma),
theme: {
@@ -79,20 +78,20 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
// // session.user.authMethod = user.authMethod;
// return session
// },
- async jwt({ token, trigger, session, user }) {
+ async jwt({token, trigger, session, user}) {
if (trigger === "update" && session) {
- return { ...token, ...session?.user };
+ return {...token, ...session?.user};
}
- return { ...token, ...user };
+ return {...token, ...user};
},
- async session({ session, token, user }) {
+ async session({session, token, user}) {
session.user = token;
return session;
},
- async signIn({ account, user, profile }) {
+ async signIn({account, user, profile}) {
const existingUser = await prisma.user.findFirst({
- where: { email: user.email },
+ where: {email: user.email},
});
if (!existingUser) {
@@ -106,7 +105,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
})
const role = users.length > 0 ? "pending" : "admin"
- await prisma.user.create({
+ const newUser = await prisma.user.create({
data: {
email: user.email,
name: user.name,
@@ -115,6 +114,18 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
authMethod: account.provider,
},
});
+
+ const defaultOrganization = await prisma.organization.findUnique({
+ where: {slug: "default"},
+ });
+
+ await prisma.userOrganization.create({
+ data: {
+ userId: newUser.id,
+ organizationId: defaultOrganization.id
+ },
+ });
+
return role !== "pending";
}
@@ -125,7 +136,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
// Update auth method if user exists
await prisma.user.update({
- where: { id: existingUser.id },
+ where: {id: existingUser.id},
data: {
authMethod: account.provider,
},
diff --git a/src/components/wrappers/Agent/AgentCard/AgentCard.tsx b/src/components/wrappers/Agent/AgentCard/AgentCard.tsx
index f29c5224..b97b9c5e 100644
--- a/src/components/wrappers/Agent/AgentCard/AgentCard.tsx
+++ b/src/components/wrappers/Agent/AgentCard/AgentCard.tsx
@@ -5,6 +5,7 @@ import Link from "next/link";
import {ValueIcon} from "@radix-ui/react-icons";
import {Circle} from "lucide-react";
import {ConnectionCircle} from "@/components/wrappers/connection-circle";
+import {formatDateLastContact} from "@/utils/date-formatting";
export type agentCardProps = {
data: any
@@ -20,7 +21,7 @@ export const AgentCard = (props: agentCardProps) => {
{agent.name}
- Last contact : {agent.lastContact?.toDateString() ?? "Never connected."}
+ Last contact : {formatDateLastContact(agent.lastContact)}
diff --git a/src/components/wrappers/Auth/Register/RegisterForm/RegisterForm.tsx b/src/components/wrappers/Auth/Register/RegisterForm/RegisterForm.tsx
index 1758aabf..ffdcc861 100644
--- a/src/components/wrappers/Auth/Register/RegisterForm/RegisterForm.tsx
+++ b/src/components/wrappers/Auth/Register/RegisterForm/RegisterForm.tsx
@@ -1,19 +1,18 @@
"use client";
+import {useRouter} from "next/navigation";
+import {Info} from "lucide-react";
+import {useMutation} from "@tanstack/react-query";
+import {toast} from "sonner";
+
import {Card, CardContent, CardHeader} from "@/components/ui/card";
-import {
- FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm
-} from "@/components/ui/form";
+import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Form} from "@/components/ui/form"
import {Button} from "@/components/ui/button";
-import {toast} from "sonner";
-import {useRouter} from "next/navigation";
-import {useMutation} from "@tanstack/react-query";
import {TooltipProvider, TooltipTrigger, Tooltip, TooltipContent} from "@/components/ui/tooltip";
import {RegisterSchema, RegisterType} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.schema";
import {registerUserAction} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.action";
-import {Info} from "lucide-react";
import {PasswordInput} from "@/components/wrappers/Auth/PaswordInput/password-input";
export type registerFormProps = {
@@ -104,7 +103,8 @@ export const RegisterForm = (props: registerFormProps) => {
- Min. 8 characters, 1 uppercase (A-Z), 1 lowercase (a-z), 1 number (0-9), 1 special character (!, @, etc.)
+ Min. 8 characters, 1 uppercase (A-Z), 1 lowercase (a-z), 1
+ number (0-9), 1 special character (!, @, etc.)
diff --git a/src/components/wrappers/Auth/Register/RegisterForm/register-form.action.ts b/src/components/wrappers/Auth/Register/RegisterForm/register-form.action.ts
index a7e18805..6a0db2d8 100644
--- a/src/components/wrappers/Auth/Register/RegisterForm/register-form.action.ts
+++ b/src/components/wrappers/Auth/Register/RegisterForm/register-form.action.ts
@@ -8,18 +8,18 @@ import {hashPassword} from "@/utils/password";
export const registerUserAction = action
.schema(RegisterSchema)
.action(async ({parsedInput, ctx}) => {
- const user = await prisma.user.findUnique({ where: { email: parsedInput.email } });
+ const user = await prisma.user.findUnique({where: {email: parsedInput.email}});
console.log(user);
if (!user && parsedInput.password === parsedInput.confirmPassword) {
const users = await prisma.user.findMany({
- where:{
- deleted: { not: true },
+ where: {
+ deleted: {not: true},
}
})
const role = users.length > 0 ? "pending" : "admin"
- const new_user = await prisma.user.create({
+ const newUser = await prisma.user.create({
data: {
name: parsedInput.name,
email: parsedInput.email,
@@ -27,8 +27,20 @@ export const registerUserAction = action
role: role
},
});
+
+ const defaultOrganization = await prisma.organization.findUnique({
+ where: {slug: "default"},
+ });
+
+ await prisma.userOrganization.create({
+ data: {
+ userId: newUser.id,
+ organizationId: defaultOrganization.id
+ },
+ });
+
return {
- data: new_user,
+ data: newUser,
}
}
throw new Error('An error occured while creating user');
diff --git a/src/components/wrappers/Dashboard/Projects/ProjectsForm/ProjectForm.tsx b/src/components/wrappers/Dashboard/Projects/ProjectsForm/ProjectForm.tsx
index c0699ccd..703c2081 100644
--- a/src/components/wrappers/Dashboard/Projects/ProjectsForm/ProjectForm.tsx
+++ b/src/components/wrappers/Dashboard/Projects/ProjectsForm/ProjectForm.tsx
@@ -15,13 +15,14 @@ import {Form} from "@/components/ui/form"
import {Button} from "@/components/ui/button";
import {useMutation} from "@tanstack/react-query";
import {ProjectSchema, ProjectType} from "@/components/wrappers/Dashboard/Projects/ProjectsForm/ProjectForm.schema";
-import {createProjectAction, updateProjectAction} from "@/components/wrappers/Dashboard/Projects/ProjectsForm/project-form.action";
+import {
+ createProjectAction,
+ updateProjectAction
+} from "@/components/wrappers/Dashboard/Projects/ProjectsForm/project-form.action";
import {useRouter} from "next/navigation";
import {Database, Organization, Projects} from "@prisma/client"
import {MultiSelect} from "@/components/wrappers/MultiSelect/MultiSelect";
-import {ZodString} from "zod";
import {toast} from "sonner";
-import {ServerActionResult} from "@/types/action-type";
export type projectFormProps = {
@@ -41,7 +42,7 @@ export const ProjectForm = (props: projectFormProps) => {
const formatDatabasesList = (databases: Database[]) => {
return databases.map(database => ({
value: database.id,
- label: `${database.name} | ${database.generatedId}`,
+ label: `${database.name} (${database.generatedId}) | ${database.agent.name}`,
}));
};
@@ -64,14 +65,21 @@ export const ProjectForm = (props: projectFormProps) => {
const mutation = useMutation({
mutationFn: async (values: ProjectType) => {
console.log(values)
- const project: Projects = isCreate ? await createProjectAction({data: values, organizationId: props.organization.id}) : await updateProjectAction({data: values, organizationId: props.organization.id, projectId: props.projectId});
+ const project: Projects = isCreate ? await createProjectAction({
+ data: values,
+ organizationId: props.organization.id
+ }) : await updateProjectAction({
+ data: values,
+ organizationId: props.organization.id,
+ projectId: props.projectId
+ });
console.log(project)
if (project.data.success) {
toast.success(project.data.actionSuccess.message);
router.push(`/dashboard/projects/${project.data.value.id}`);
router.refresh()
- }else{
+ } else {
toast.success(project.data.actionError.message);
}
@@ -115,7 +123,11 @@ export const ProjectForm = (props: projectFormProps) => {
Slug
+ placeholder="project-1" {...field}
+ onChange={(e) => {
+ const value = e.target.value.replaceAll(" ", "-").toLowerCase()
+ field.onChange(value)
+ }}/>
@@ -125,24 +137,24 @@ export const ProjectForm = (props: projectFormProps) => {
control={form.control}
name="databases"
render={({field}) => (
-
- Databases
-
+
+ Databases
+
-
-
- Select databases you want to add to this project
-
-
- )
+
+
+ Select databases you want to add to this project
+
+
+ )
}
/>