Finish migration prisma to drizzle.

This commit is contained in:
charlesgauthereau
2025-07-16 18:15:28 +02:00
parent 385777535f
commit de9fe33194
21 changed files with 278 additions and 293 deletions
@@ -1,16 +1,21 @@
import { PageParams } from "@/types/next"; import { PageParams } from "@/types/next";
import * as drizzleDb from "@/db";
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page"; import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
import { AdminTabs } from "@/components/wrappers/dashboard/admin/admin-tabs"; import { AdminTabs } from "@/components/wrappers/dashboard/admin/admin-tabs";
import { currentUser } from "@/lib/auth/current-user"; import { currentUser } from "@/lib/auth/current-user";
import { db } from "@/db"; import { db } from "@/db";
import {eq, isNotNull, isNull} from "drizzle-orm";
export default async function RoutePage(props: PageParams<{}>) { export default async function RoutePage(props: PageParams<{}>) {
const user = await currentUser()!; const user = await currentUser();
const users = await db.query.user.findMany({ const users = await db.query.user.findMany({
where: (fields, { isNull, not }) => not(isNull(fields.deletedAt)), where: (fields) => isNull(fields.deletedAt)
}); });
// const users = await db.query.user.findMany();
console.log("users",users);
const settings = await db.query.setting.findFirst({ const settings = await db.query.setting.findFirst({
where: (fields, { eq }) => eq(fields.name, "system"), where: (fields, { eq }) => eq(fields.name, "system"),
@@ -22,7 +27,7 @@ export default async function RoutePage(props: PageParams<{}>) {
<PageTitle>Administration Panel</PageTitle> <PageTitle>Administration Panel</PageTitle>
</PageHeader> </PageHeader>
<PageContent className="mt-10"> <PageContent className="mt-10">
<AdminTabs settings={settings!} currentUser={user} users={users} /> <AdminTabs settings={settings!} users={users} />
</PageContent> </PageContent>
</Page> </Page>
); );
@@ -1,6 +1,5 @@
import Link from "next/link"; import Link from "next/link";
import {GearIcon} from "@radix-ui/react-icons"; import {GearIcon} from "@radix-ui/react-icons";
import {prisma} from "@/prisma";
import {PageParams} from "@/types/next"; import {PageParams} from "@/types/next";
import {Page, PageContent, PageDescription, PageTitle} from "@/features/layout/page"; import {Page, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
import {formatDateLastContact} from "@/utils/date-formatting"; import {formatDateLastContact} from "@/utils/date-formatting";
@@ -9,38 +8,55 @@ import {Card, CardContent, CardHeader} from "@/components/ui/card";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination"; import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
import {DatabaseCard} from "@/components/wrappers/dashboard/projects/ProjectCard/ProjectDatabaseCard"; import {DatabaseCard} from "@/components/wrappers/dashboard/projects/ProjectCard/ProjectDatabaseCard";
import {AgentCardKey} from "@/components/wrappers/dashboard/agent/AgentCardKey/AgentCardKey"; import {AgentCardKey} from "@/components/wrappers/dashboard/agent/AgentCardKey/AgentCardKey";
import { db } from "@/db";
import * as drizzleDb from "@/db";
import {and, eq} from "drizzle-orm";
import {notFound} from "next/navigation";
export default async function RoutePage(props: PageParams<{ agentId: string }>) { export default async function RoutePage(props: PageParams<{ agentId: string }>) {
const {agentId} = await props.params const {agentId} = await props.params
const agent = await prisma.agent.findUnique({ const agent = await db.query.agent.findFirst({
where: { where: eq(drizzleDb.schemas.agent.id, agentId),
id: agentId, with: {
}, databases: true
include: {
databases: {}
} }
}) })
//
console.log(agent)
const databaseId = 'db-123'; if (!agent) {
notFound()
const totalBackups = await prisma.backup.count({ }
where: { //
databaseId: databaseId, // const databaseId = 'db-123';
}, //
}); // const totalBackupsResult = await db
// .select({ count: drizzleDb.schemas.backup.id })
const successfulBackups = await prisma.backup.count({ // .from(drizzleDb.schemas.backup)
where: { // .where(eq(drizzleDb.schemas.backup.databaseId, databaseId))
databaseId: databaseId, // .execute();
status: 'success', //
}, // const totalBackups = totalBackupsResult.length;
}); //
// const successfulBackupsResult = await db
const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null // .select({ count: drizzleDb.schemas.backup.id })
// .from(drizzleDb.schemas.backup)
// .where(
// and(
// eq(drizzleDb.schemas.backup.databaseId, databaseId),
// eq(drizzleDb.schemas.backup.status, "success")
// )
// )
// .execute();
//
//
// const successfulBackups = successfulBackupsResult.length;
//
// const successRate =
// totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null;
return ( return (
<Page> <Page>
@@ -73,7 +89,7 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>)
Success rate Success rate
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{successRate ?? "Unavailable for now."} {/*{successRate ?? "Unavailable for now."}*/}
</CardContent> </CardContent>
</Card> </Card>
<Card className="w-full sm:w-auto flex-1"> <Card className="w-full sm:w-auto flex-1">
@@ -6,7 +6,6 @@ import Link from "next/link";
import { Page, PageActions, PageContent, PageHeader, PageTitle } from "@/features/layout/page"; import { Page, PageActions, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { db } from "@/db"; import { db } from "@/db";
export const dynamic = "force-dynamic";
export default async function RoutePage(props: PageParams<{}>) { export default async function RoutePage(props: PageParams<{}>) {
const agents = await db.query.agent.findMany(); const agents = await db.query.agent.findMany();
@@ -22,7 +22,7 @@ export default async function RoutePage(props: PageParams<{ projectId: string }>
notFound(); notFound();
} }
//
const org = await db.query.organization.findFirst({ const org = await db.query.organization.findFirst({
where: eq(drizzleDb.schemas.organization.slug, "default"), where: eq(drizzleDb.schemas.organization.slug, "default"),
}); });
@@ -46,7 +46,7 @@ export default async function RoutePage(props: PageParams<{
<div className="justify-between gap-2 sm:flex"> <div className="justify-between gap-2 sm:flex">
<PageTitle className="flex items-center"> <PageTitle className="flex items-center">
{proj.name} {proj.name}
<Link className={buttonVariants({ variant: "outline" })} href={`/dashboard/${organization.slug}/projects/${proj.id}/edit`}> <Link className={buttonVariants({ variant: "outline" })} href={`/dashboard/projects/${proj.id}/edit`}>
<GearIcon className="w-7 h-7" /> <GearIcon className="w-7 h-7" />
</Link> </Link>
</PageTitle> </PageTitle>
@@ -1,26 +1,19 @@
import {PageParams} from "@/types/next"; import {PageParams} from "@/types/next";
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page"; import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {getCurrentOrganizationSlug} from "@/features/dashboard/organization-cookie";
import {prisma} from "@/prisma";
import {requiredCurrentUser} from "@/auth/current-user";
import {notFound} from "next/navigation"; import {notFound} from "next/navigation";
import {OrganizationForm} from "@/components/wrappers/dashboard/organization/OrganizationForm/OrganizationForm"; import {OrganizationForm} from "@/components/wrappers/dashboard/organization/OrganizationForm/OrganizationForm";
import {getOrganization} from "@/lib/auth/auth"; import {getOrganization} from "@/lib/auth/auth";
export default async function RoutePage(props: PageParams<{ export default async function RoutePage(props: PageParams<{
slug: string;
}>) { }>) {
const organization = await getOrganization({});
const {slug: organizationSlug} = await props.params;
const organization = await getOrganization({organizationSlug});
if (!organization) { if (!organization) {
notFound() notFound();
} }
return( return(
<Page> <Page>
<PageHeader> <PageHeader>
@@ -3,9 +3,6 @@ import {Page, PageActions, PageContent, PageDescription, PageHeader, PageTitle}
import {currentUser} from "@/lib/auth/current-user"; import {currentUser} from "@/lib/auth/current-user";
import {getOrganization} from "@/lib/auth/auth"; import {getOrganization} from "@/lib/auth/auth";
import {notFound} from "next/navigation"; import {notFound} from "next/navigation";
import {requiredCurrentUser} from "@/auth/current-user";
import {SettingsTabs} from "@/components/wrappers/dashboard/settings/SettingsTabs/SettingsTabs";
import {getCurrentOrganizationSlug} from "@/features/dashboard/organization-cookie";
import { import {
DeleteOrganizationButton DeleteOrganizationButton
} from "@/components/wrappers/dashboard/organization/DeleteOrganization/DeleteOrganizationButton"; } from "@/components/wrappers/dashboard/organization/DeleteOrganization/DeleteOrganizationButton";
@@ -13,50 +10,13 @@ import {EditButtonSettings} from "@/components/wrappers/dashboard/settings/EditB
export default async function RoutePage(props: PageParams<{ slug: string }>) { export default async function RoutePage(props: PageParams<{ slug: string }>) {
const {slug: organizationSlug} = await props.params; const organization = await getOrganization({});
const user = await currentUser(); const user = await currentUser();
const organization = await getOrganization({organizationSlug}); if (!organization) {
if (!organization || organization?.slug !== organizationSlug) {
notFound(); notFound();
} }
// const organization = await prisma.organization.findUnique({
// where: {
// slug: currentOrganizationSlug,
// },
// include: {
// users:{
// include:{
// user: {}
// }
// }
// }
// })
//
// const currentOrganizationUser = await prisma.userOrganization.findFirst({
// where:{
// userId: user.id,
// organization:{
// slug: currentOrganizationSlug != "" ? currentOrganizationSlug : "default",
// }
// }
// })
// if (currentOrganizationUser.role != "admin") {
// notFound()
// }
//
//
// const settings = await prisma.settings.findUnique({
// where: {
// name: "system"
// }
// })
return ( return (
<Page> <Page>
<PageHeader> <PageHeader>
@@ -76,7 +36,7 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
Manage your organization settings. Manage your organization settings.
</PageDescription> </PageDescription>
<PageContent> <PageContent>
{/*<SettingsTabs settings={settings} currentUser={user} users={organization.users}/>*/} {/* TODO add the list of organisation members (add, remove, edit) */}
</PageContent> </PageContent>
</Page> </Page>
) )
@@ -8,17 +8,17 @@ import { notFound } from "next/navigation";
import { db } from "@/db"; import { db } from "@/db";
import { asc, count, eq, inArray } from "drizzle-orm"; import { asc, count, eq, inArray } from "drizzle-orm";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {getOrganization} from "@/lib/auth/auth";
export default async function RoutePage(props: PageParams<{ slug: string }>) { export default async function RoutePage(props: PageParams<{ }>) {
const { slug: organizationSlug } = await props.params; const organization = await getOrganization({});
const currentOrganizationSlug = await getCurrentOrganizationSlug(); if (!organization) {
if (currentOrganizationSlug !== organizationSlug) {
notFound(); notFound();
} }
const org = await db.query.organization.findFirst({ const org = await db.query.organization.findFirst({
where: eq(drizzleDb.schemas.organization.slug, currentOrganizationSlug), where: eq(drizzleDb.schemas.organization.slug, organization.slug),
}); });
if (!org) notFound(); if (!org) notFound();
+60 -52
View File
@@ -1,25 +1,27 @@
import {NextResponse} from "next/server"; import {NextResponse} from "next/server";
import {prisma} from "@/prisma";
import {isUuidv4} from "@/utils/verify-uuid"; import {isUuidv4} from "@/utils/verify-uuid";
import {uploadLocalPrivate} from "@/features/upload/private/upload.action"; import {uploadLocalPrivate} from "@/features/upload/private/upload.action";
import {v4 as uuidv4} from "uuid"; import {v4 as uuidv4} from "uuid";
import {Backup, Database} from "@prisma/client";
import {eventEmitter} from "../../../events/route"; import {eventEmitter} from "../../../events/route";
import {db} from "@/db";
import {Backup} from "@/db/schema/06_database";
import {and, eq} from "drizzle-orm";
import * as drizzleDb from "@/db";
export async function POST( export async function POST(
request: Request, request: Request,
{ params }: { params: Promise<{ agentId: string }> } {params}: { params: Promise<{ agentId: string }> }
) { ) {
try { try {
const contentType = request.headers.get("Content-Type"); const contentType = request.headers.get("Content-Type");
if (!contentType || !contentType.includes("multipart/form-data")) { if (!contentType || !contentType.includes("multipart/form-data")) {
return NextResponse.json( return NextResponse.json(
{ error: "Unsupported or missing Content-Type" }, {error: "Unsupported or missing Content-Type"},
{ status: 400 } {status: 400}
); );
} }
eventEmitter.emit('modification', { update: true }); eventEmitter.emit('modification', {update: true});
const agentId = (await params).agentId; const agentId = (await params).agentId;
const formData = await request.formData(); const formData = await request.formData();
@@ -28,61 +30,64 @@ export async function POST(
if (!generatedId || !isUuidv4(generatedId)) { if (!generatedId || !isUuidv4(generatedId)) {
return NextResponse.json( return NextResponse.json(
{ error: "generatedId is not a valid UUID" }, {error: "generatedId is not a valid UUID"},
{ status: 400 } {status: 400}
); );
} }
const agent = await prisma.agent.findFirst({ const agent = await db.query.agent.findFirst({
where: { id: agentId }, where: eq(drizzleDb.schemas.agent.id, agentId),
}); });
if (!agent) { if (!agent) {
return NextResponse.json( return NextResponse.json(
{ error: "Agent not found" }, {error: "Agent not found"},
{ status: 404 } {status: 404}
); );
} }
const database = await prisma.database.findFirst({ const database = await db.query.database.findFirst({
where: { generatedId }, where: eq(drizzleDb.schemas.database.agentDatabaseId, generatedId),
}); });
if (!database) { if (!database) {
return NextResponse.json( return NextResponse.json(
{ error: "Database associated with generatedId not found" }, {error: "Database associated with generatedId not found"},
{ status: 404 } {status: 404}
); );
} }
let backup: Backup | null = null; let backup: Backup | null | undefined = null;
if (method === "automatic") { if (method === "automatic") {
backup = await prisma.backup.create({ [backup] = await db
data: { .insert(drizzleDb.schemas.backup)
status: "ongoing", .values({
status: 'ongoing',
databaseId: database.id, databaseId: database.id,
}, })
}); .returning();
if (!backup) { if (!backup) {
return NextResponse.json( return NextResponse.json(
{ error: "Unable to create an automatic backup" }, {error: "Unable to create an automatic backup"},
{ status: 500 } {status: 500}
); );
} }
} else { } else {
backup = await prisma.backup.findFirst({ backup = await db.query.backup.findFirst({
where: { where: and(
status: "ongoing", eq(drizzleDb.schemas.backup.status, 'ongoing'),
databaseId: database.id, eq(drizzleDb.schemas.backup.databaseId, database.id),
}, ),
}); });
if (!backup) { if (!backup) {
return NextResponse.json( return NextResponse.json(
{ error: "Unable to find the corresponding backup" }, {error: "Unable to find the corresponding backup"},
{ status: 404 } {status: 404}
); );
} }
} }
@@ -94,8 +99,8 @@ export async function POST(
if (!file) { if (!file) {
return NextResponse.json( return NextResponse.json(
{ error: "File is required for successful backup" }, {error: "File is required for successful backup"},
{ status: 400 } {status: 400}
); );
} }
@@ -103,49 +108,52 @@ export async function POST(
const fileName = `${uuid}.dump`; const fileName = `${uuid}.dump`;
const buffer = Buffer.from(await file.arrayBuffer()); const buffer = Buffer.from(await file.arrayBuffer());
const { success, message, filePath } = await uploadLocalPrivate(fileName, buffer); const {success, message, filePath} = await uploadLocalPrivate(fileName, buffer);
if (!success) { if (!success) {
return NextResponse.json( return NextResponse.json(
{ error: message }, {error: message},
{ status: 500 } {status: 500}
); );
} }
await prisma.backup.update({ await db
where: { id: backup.id }, .update(drizzleDb.schemas.backup)
data: { .set({
file: fileName, file: fileName,
status: "success", status: 'success',
}, })
}); .where(eq(drizzleDb.schemas.backup.id, backup.id));
eventEmitter.emit('modification', { update: true });
eventEmitter.emit('modification', {update: true});
return NextResponse.json( return NextResponse.json(
{ {
message: "Backup successfully uploaded", message: "Backup successfully uploaded",
}, },
{ status: 200 } {status: 200}
); );
} else { } else {
await prisma.backup.update({
where: { id: backup.id }, await db
data: { status: "failed" }, .update(drizzleDb.schemas.backup)
}); .set({status: 'failed'})
eventEmitter.emit('modification', { update: true }); .where(eq(drizzleDb.schemas.backup.id, backup.id));
eventEmitter.emit('modification', {update: true});
return NextResponse.json( return NextResponse.json(
{ {
message: "Backup successfully updated with status failed", message: "Backup successfully updated with status failed",
}, },
{ status: 200 } {status: 200}
); );
} }
} catch (error) { } catch (error) {
console.error("Error in POST handler:", error); console.error("Error in POST handler:", error);
return NextResponse.json( return NextResponse.json(
{ error: "Internal server error" }, {error: "Internal server error"},
{ status: 500 } {status: 500}
); );
} }
} }
+20 -26
View File
@@ -1,14 +1,17 @@
import {NextResponse} from "next/server"; import {NextResponse} from "next/server";
import {isUuidv4} from "@/utils/verify-uuid"; import {isUuidv4} from "@/utils/verify-uuid";
import {prisma} from "@/prisma";
import {eventEmitter} from "../../../events/route"; import {eventEmitter} from "../../../events/route";
import * as drizzleDb from "@/db";
import {db} from "@/db";
import {and, eq} from "drizzle-orm";
export type BodyResultRestore = { export type BodyResultRestore = {
generatedId: string generatedId: string
status: string status: string
} }
type RestorationStatus = 'waiting' | 'ongoing' | 'failed' | 'success';
export async function POST( export async function POST(
@@ -17,7 +20,7 @@ export async function POST(
) { ) {
try { try {
eventEmitter.emit('modification', { update: true }); eventEmitter.emit('modification', {update: true});
const agentId = (await params).agentId const agentId = (await params).agentId
const body: BodyResultRestore = await request.json(); const body: BodyResultRestore = await request.json();
@@ -33,53 +36,44 @@ export async function POST(
); );
} }
const agent = await prisma.agent.findFirst({ const agent = await db.query.agent.findFirst({
where: { where: eq(drizzleDb.schemas.agent.id, agentId)
id: agentId
}
}) })
if (!agent) { if (!agent) {
return NextResponse.json({error: "Agent not found"}, {status: 404}) return NextResponse.json({error: "Agent not found"}, {status: 404})
} }
const database = await prisma.database.findFirst({ const database = await db.query.database.findFirst({
where: { where: eq(drizzleDb.schemas.database.agentDatabaseId, body.generatedId)
generatedId: body.generatedId
}
}) })
if (!database) { if (!database) {
return NextResponse.json({error: "Database associated with generatedId provided not found"}, {status: 404}) return NextResponse.json({error: "Database associated with generatedId provided not found"}, {status: 404})
} }
const restoration = await prisma.restoration.findFirst({ const restoration = await db.query.restoration.findFirst({
where: { where: and(eq(drizzleDb.schemas.restoration.status, "ongoing"), eq(drizzleDb.schemas.restoration.databaseId, database.id),)
status : "ongoing",
databaseId: database.id
}
}) })
if (!restoration) { if (!restoration) {
return NextResponse.json({error: "Unable to fin the corresponding restoration"}, {status: 404}) return NextResponse.json({error: "Unable to fin the corresponding restoration"}, {status: 404})
} }
await prisma.restoration.update({ await db
where:{ .update(drizzleDb.schemas.restoration)
id : restoration.id .set({ status: body.status as RestorationStatus })
}, .where(eq(drizzleDb.schemas.restoration.id, restoration.id));
data: {
status: body.status
}
})
const response = { const response = {
message: true, message: true,
details: "Restoration successfully updated" details: "Restoration successfully updated"
} }
eventEmitter.emit('modification', { update: true }); eventEmitter.emit('modification', {update: true});
return Response.json(response , {status: 200}) return Response.json(response, {status: 200})
} catch (error) { } catch (error) {
+50 -54
View File
@@ -1,16 +1,18 @@
import {Agent, Database} from "@prisma/client";
import {prisma} from "@/prisma";
import {NextResponse} from "next/server"; import {NextResponse} from "next/server";
import {Body} from "./route"; import {Body} from "./route";
import {isUuidv4} from "@/utils/verify-uuid"; import {isUuidv4} from "@/utils/verify-uuid";
import {getFileUrlPresignedLocal} from "@/features/upload/private/upload.action"; import {getFileUrlPresignedLocal} from "@/features/upload/private/upload.action";
import {Agent} from "@/db/schema/07_agent";
import {Database} from "@/db/schema/06_database";
import * as drizzleDb from "@/db";
import {db as dbClient} from "@/db";
import {and, eq} from "drizzle-orm";
export async function handleDatabases(body: Body, agent: Agent, lastContact: Date) { export async function handleDatabases(body: Body, agent: Agent, lastContact: Date) {
const databasesResponse = []; const databasesResponse = [];
const formatDatabase = (database: Database, backupAction: boolean, restoreAction: boolean, UrlBackup: string) => ({ const formatDatabase = (database: Database, backupAction: boolean, restoreAction: boolean, UrlBackup: string) => ({
generatedId: database.generatedId, generatedId: database.agentDatabaseId,
dbms: database.dbms, dbms: database.dbms,
data: { data: {
backup: { backup: {
@@ -25,15 +27,14 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
}); });
for (const db of body.databases) { for (const db of body.databases) {
const existingDatabase = await prisma.database.findFirst({
where: { const existingDatabase = await dbClient.query.database.findFirst({
generatedId: db.generatedId, where: eq(drizzleDb.schemas.database.agentDatabaseId, db.generatedId)
},
}); });
let backupAction: boolean = false let backupAction: boolean = false
let restoreAction: boolean = false let restoreAction: boolean = false
let UrlBackup: string = null let UrlBackup: string = ""
if (!existingDatabase) { if (!existingDatabase) {
if (!isUuidv4(db.generatedId)) { if (!isUuidv4(db.generatedId)) {
@@ -42,74 +43,69 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
{ status: 500 } { status: 500 }
); );
} }
const databaseCreated = await prisma.database.create({
data: { const [databaseCreated] = await dbClient
.insert(drizzleDb.schemas.database)
.values({
agentId: agent.id, agentId: agent.id,
name: db.name, name: db.name,
dbms: db.dbms, dbms: db.dbms,
generatedId: db.generatedId, agentDatabaseId: db.generatedId,
lastContact: lastContact, lastContact: lastContact,
}, })
}); .returning();
if (databaseCreated) { if (databaseCreated) {
databasesResponse.push(formatDatabase(databaseCreated, backupAction,restoreAction, UrlBackup)); databasesResponse.push(formatDatabase(databaseCreated, backupAction,restoreAction, UrlBackup));
} }
} else { } else {
const databaseUpdated = await prisma.database.update({
where: {
id: existingDatabase.id,
},
data: {
lastContact: lastContact,
},
});
const backup = await prisma.backup.findFirst({
where: {
databaseId: databaseUpdated.id, const [databaseUpdated] = await dbClient
status: "waiting" .update(drizzleDb.schemas.database)
} .set({ lastContact: lastContact })
.where(eq(drizzleDb.schemas.database.id, existingDatabase.id))
.returning();
const backup = await dbClient.query.backup.findFirst({
where: and(eq(drizzleDb.schemas.backup.databaseId, databaseUpdated.id), eq(drizzleDb.schemas.backup.status,"waiting"))
}) })
const restoration = await prisma.restoration.findFirst({
where:{ const restoration = await dbClient.query.restoration.findFirst({
databaseId: databaseUpdated.id, where: and(eq(drizzleDb.schemas.restoration.databaseId, databaseUpdated.id), eq(drizzleDb.schemas.restoration.status,"waiting"))
status: "waiting"
}
}) })
if(backup){ if(backup){
backupAction = true backupAction = true
await prisma.backup.update({
where:{ await dbClient
id: backup.id .update(drizzleDb.schemas.backup)
}, .set({ status: "ongoing" })
data: { .where(eq(drizzleDb.schemas.backup.id, backup.id));
status: "ongoing"
}
})
} }
if(restoration){ if(restoration){
restoreAction = true restoreAction = true
const backupToRestore = await prisma.backup.findFirst({
where:{ const backupToRestore = await dbClient.query.backup.findFirst({
id: restoration.backupId where: eq(drizzleDb.schemas.backup.id, restoration.backupId),
}
})
const fileName = backupToRestore.file
UrlBackup = await getFileUrlPresignedLocal(fileName)
await prisma.restoration.update({
where: {
id: restoration.id
},
data:{
status: "ongoing"
}
}) })
const fileName = backupToRestore?.file
UrlBackup = await getFileUrlPresignedLocal(fileName ?? "")
await dbClient
.update(drizzleDb.schemas.restoration)
.set({ status: "ongoing" })
.where(eq(drizzleDb.schemas.restoration.id, restoration.id));
} }
+16 -17
View File
@@ -1,14 +1,15 @@
import {prisma} from "@/prisma";
import {NextResponse} from "next/server"; import {NextResponse} from "next/server";
import {Dbms} from "@prisma/client";
import {getFileUrlPresignedLocal} from "@/features/upload/private/upload.action"; import {getFileUrlPresignedLocal} from "@/features/upload/private/upload.action";
import {handleDatabases} from "./helpers"; import {handleDatabases} from "./helpers";
import {eventEmitter} from "../../../events/route"; import {eventEmitter} from "../../../events/route";
import * as drizzleDb from "@/db";
import {db} from "@/db";
import {EDbmsSchema} from "@/db/schema/types";
import {eq} from "drizzle-orm";
export type databaseAgent = { export type databaseAgent = {
name: string, name: string,
dbms: Dbms, dbms: EDbmsSchema,
generatedId: string generatedId: string
} }
@@ -33,25 +34,23 @@ export async function POST(
const body: Body = await request.json(); const body: Body = await request.json();
const lastContact = new Date(); const lastContact = new Date();
const agent = await prisma.agent.findFirst({
where: { const agent = await db.query.agent.findFirst({
id: agentId where: eq(drizzleDb.schemas.agent.id, agentId),
}
}) })
if (!agent) { if (!agent) {
return NextResponse.json({error: "Agent not found"}, {status: 404}) return NextResponse.json({error: "Agent not found"}, {status: 404})
} }
const databasesResponse = await handleDatabases(body, agent, lastContact) const databasesResponse = await handleDatabases(body, agent, lastContact)
await prisma.agent.update({
where:{
id: agentId,
},
data:{
lastContact: lastContact,
}
})
eventEmitter.emit('modification', { update: true });
await db
.update(drizzleDb.schemas.agent)
.set({ lastContact: lastContact })
.where(eq(drizzleDb.schemas.agent.id, agentId));
eventEmitter.emit('modification', {update: true});
const response = { const response = {
agent: { agent: {
@@ -1,26 +1,26 @@
"use client"; "use client"
import {ColumnDef} from "@tanstack/react-table";
import { ColumnDef } from "@tanstack/react-table"; import {Badge} from "@/components/ui/badge";
import { Badge } from "@/components/ui/badge"; import {updateUserAction} from "@/components/wrappers/dashboard/profile/UserForm/user-form.action";
import { User } from "@prisma/client"; import {useMutation} from "@tanstack/react-query";
import { updateUserAction } from "@/components/wrappers/dashboard/profile/UserForm/user-form.action"; import {toast} from "sonner";
import { useMutation } from "@tanstack/react-query"; import {useRouter} from "next/navigation";
import { toast } from "sonner"; import {useState} from "react";
import { useRouter } from "next/navigation"; import {Trash2} from "lucide-react";
import { useState } from "react"; import {deleteUserAction} from "@/components/wrappers/dashboard/profile/ButtonDeleteAccount/delete-account.action";
import { Trash2 } from "lucide-react"; import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import { deleteUserAction } from "@/components/wrappers/dashboard/profile/ButtonDeleteAccount/delete-account.action"; import {User} from "@/db/schema/01_user";
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading"; import {useSession} from "@/lib/auth/auth-client";
export const usersColumnsAdmin: ColumnDef<User>[] = [ export const usersColumnsAdmin: ColumnDef<User>[] = [
{ {
accessorKey: "role", accessorKey: "role",
header: "Role", header: "Role",
cell: ({ row }) => { cell: ({row}) => {
const [role, setRole] = useState<string>(row.getValue("role")); const [role, setRole] = useState<string>(row.getValue("role"));
const updateMutation = useMutation({ const updateMutation = useMutation({
mutationFn: () => updateUserAction({ id: row.original.id, data: { role: role } }), mutationFn: () => updateUserAction({id: row.original.id, data: {role: role}}),
onSuccess: () => { onSuccess: () => {
toast.success(`User updated successfully.`); toast.success(`User updated successfully.`);
}, },
@@ -53,22 +53,19 @@ export const usersColumnsAdmin: ColumnDef<User>[] = [
{ {
accessorKey: "updatedAt", accessorKey: "updatedAt",
header: "Updated At", header: "Updated At",
cell: ({ row }) => { cell: ({row}) => {
return new Date(row.getValue("updatedAt")).toLocaleString("fr-FR"); return new Date(row.getValue("updatedAt")).toLocaleString("fr-FR", {
}, timeZone: "Europe/Paris",
}, });
{
accessorKey: "authMethod",
header: "Method",
cell: ({ row }) => {
return <Badge variant="outline">{row.getValue("authMethod")}</Badge>;
}, },
}, },
{ {
header: "Action", header: "Action",
id: "actions", id: "actions",
cell: ({ row }) => { cell: ({row}) => {
const router = useRouter(); const router = useRouter();
const {data: session, isPending} = useSession();
const mutation = useMutation({ const mutation = useMutation({
mutationFn: () => deleteUserAction(row.original.id), mutationFn: () => deleteUserAction(row.original.id),
onSuccess: async () => { onSuccess: async () => {
@@ -77,17 +74,23 @@ export const usersColumnsAdmin: ColumnDef<User>[] = [
}, },
}); });
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ButtonWithLoading {!session || session?.user.email === row.original.email ? null :
variant="outline" <ButtonWithLoading
text="" variant="outline"
icon={<Trash2 color="red" size={15} />} text=""
onClick={async () => { icon={<Trash2 color="red" size={15}/>}
await mutation.mutateAsync(); onClick={async () => {
}} await mutation.mutateAsync();
size="icon" }}
/> size="icon"
/>
}
</div> </div>
); );
}, },
@@ -1,8 +1,8 @@
"use client"; "use client";
import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { ProjectWith } from "@/db/schema";
import Link from "next/link"; import Link from "next/link";
import {ProjectWith} from "@/db/schema/05_project";
export type projectCardProps = { export type projectCardProps = {
data: ProjectWith; data: ProjectWith;
@@ -13,7 +13,7 @@ export const ProjectCard = (props: projectCardProps) => {
const { data: project, organizationSlug } = props; const { data: project, organizationSlug } = props;
return ( return (
<Link href={`/dashboard/${organizationSlug}/projects/${project.id}`}> <Link href={`/dashboard/projects/${project.id}`}>
<Card className="flex flex-row justify-between"> <Card className="flex flex-row justify-between">
<div className=""> <div className="">
<CardHeader>{project.name}</CardHeader> <CardHeader>{project.name}</CardHeader>
@@ -17,7 +17,7 @@ export const ProjectDatabaseCard = (props: projectDatabaseCardProps) => {
const { organizationSlug, data: database, extendedProps: extendedProps } = props; const { organizationSlug, data: database, extendedProps: extendedProps } = props;
return ( return (
<Link href={`/dashboard/${organizationSlug}/projects/${extendedProps.id}/database/${database.id}`}> <Link href={`/dashboard/projects/${extendedProps.id}/database/${database.id}`}>
<DatabaseCard data={database} /> <DatabaseCard data={database} />
</Link> </Link>
); );
@@ -66,7 +66,7 @@ export const ProjectForm = (props: projectFormProps) => {
if (project && project.data) { if (project && project.data) {
if (project.data.success) { if (project.data.success) {
project.data.actionSuccess && toast.success(project.data.actionSuccess.message); project.data.actionSuccess && toast.success(project.data.actionSuccess.message);
router.push(`/dashboard/${props.organization.slug}/projects/${project.data.value!.id}`); router.push(`/dashboard/projects/${project.data.value!.id}`);
router.refresh(); router.refresh();
} else { } else {
project.data.actionError && toast.error(project.data.actionError.message || "Unknown error occurred."); project.data.actionError && toast.error(project.data.actionError.message || "Unknown error occurred.");
@@ -2,20 +2,14 @@ import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
SidebarFooter, SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader, SidebarHeader,
SidebarMenu as SM, SidebarMenu as SM,
SidebarMenuItem, SidebarMenuItem,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import {SidebarItem, SidebarMenu} from "@/components/wrappers/dashboard/sideBar/SideBarMenu/SideBarMenu";
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox"; import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
import {SideBarLogo} from "@/components/wrappers/dashboard/sideBar/SideBarLogo/SideBarLogo"; import {SideBarLogo} from "@/components/wrappers/dashboard/sideBar/SideBarLogo/SideBarLogo";
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/sideBar/SideBarFooterCredit/SideBarFooterCredit"; import {SideBarFooterCredit} from "@/components/wrappers/dashboard/sideBar/SideBarFooterCredit/SideBarFooterCredit";
import {LoggedInButton} from "@/components/wrappers/dashboard/loggedInButton/LoggedInButton"; import {LoggedInButton} from "@/components/wrappers/dashboard/loggedInButton/LoggedInButton";
import {Layers, ChartArea, Settings, ShieldHalf} from "lucide-react";
import {authClient} from "@/lib/auth/auth-client";
import {SidebarContentA} from "./sidebar-content"; import {SidebarContentA} from "./sidebar-content";
import {getActiveMember, getOrganization} from "@/lib/auth/auth"; import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {notFound} from "next/navigation"; import {notFound} from "next/navigation";
@@ -31,7 +25,6 @@ export async function AppSidebar() {
const organization = await getOrganization({organizationId: member.organizationId}); const organization = await getOrganization({organizationId: member.organizationId});
//todo: à revoir
console.log("organization", organization); console.log("organization", organization);
@@ -41,6 +41,8 @@ export const SidebarContentA = () => {
}); });
} }
const adminItems: SidebarItem[] = [ const adminItems: SidebarItem[] = [
{ {
type: "list", type: "list",
@@ -62,13 +64,24 @@ export const SidebarContentA = () => {
return ( return (
activeOrganization && ( activeOrganization && (
<>
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu items={appItems} baseUrl={`/dashboard`} />
</SidebarGroupContent>
</SidebarGroup>
{(session && (session.user.role === "admin" || session.user.role === "superadmin")) && (
<SidebarGroup>
<SidebarGroupLabel>Administration</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu items={adminItems} baseUrl={`/dashboard`} />
</SidebarGroupContent>
</SidebarGroup>
)}
</>
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu items={appItems} baseUrl={`/dashboard/${activeOrganization.slug}`} />
</SidebarGroupContent>
</SidebarGroup>
) )
); );
}; };
+2 -3
View File
@@ -11,7 +11,7 @@ export const database = pgTable("databases", {
agentDatabaseId: uuid("agent_database_id").notNull().defaultRandom(), agentDatabaseId: uuid("agent_database_id").notNull().defaultRandom(),
name: text("name").notNull(), name: text("name").notNull(),
dbms: dbmsEnum("dbms").notNull(), dbms: dbmsEnum("dbms").notNull(),
description: text("description").notNull(), description: text("description"),
backupPolicy: text("backup_policy"), backupPolicy: text("backup_policy"),
isWaitingForBackup: boolean("is_waiting_for_backup").default(false).notNull(), isWaitingForBackup: boolean("is_waiting_for_backup").default(false).notNull(),
backupToRestore: text("backup_to_restore"), backupToRestore: text("backup_to_restore"),
@@ -23,8 +23,7 @@ export const database = pgTable("databases", {
lastContact: timestamp("last_contact"), lastContact: timestamp("last_contact"),
projectId: uuid("project_id") projectId: uuid("project_id")
.references(() => project.id) .references(() => project.id),
.notNull(),
}); });
export const backup = pgTable( export const backup = pgTable(
+7
View File
@@ -1,6 +1,8 @@
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { createSelectSchema } from "drizzle-zod"; import { createSelectSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
import {database} from "@/db/schema/06_database";
import {relations} from "drizzle-orm";
export const agent = pgTable("agents", { export const agent = pgTable("agents", {
id: uuid("id").primaryKey().defaultRandom(), id: uuid("id").primaryKey().defaultRandom(),
@@ -14,3 +16,8 @@ export const agent = pgTable("agents", {
export const agentSchema = createSelectSchema(agent); export const agentSchema = createSelectSchema(agent);
export type Agent = z.infer<typeof agentSchema>; export type Agent = z.infer<typeof agentSchema>;
export const agentRelations = relations(agent, ({ many }) => ({
databases: many(database),
}));
+1 -1
View File
@@ -97,7 +97,7 @@ export const auth = betterAuth({
await db.insert(drizzleDb.schemas.member).values({ await db.insert(drizzleDb.schemas.member).values({
userId: user.id, userId: user.id,
organizationId: defaultOrg.id, organizationId: defaultOrg.id,
role: "orgOwner", role: "owner",
}); });
} else { } else {
console.warn("Default organization not found. Cannot assign member."); console.warn("Default organization not found. Cannot assign member.");