Some refactoring in database and projects side.

This commit is contained in:
charlesgauthereau
2025-11-29 22:31:32 +01:00
parent 934e635cdb
commit ac951378e8
7 changed files with 102 additions and 43 deletions
@@ -1,8 +1,8 @@
"use client";
import { TablePaginationNavigation } from "@/components/wrappers/common/table/table-pagination-navigation";
import { TablePaginationSize } from "@/components/wrappers/common/table/table-pagination-size";
import { cn } from "@/lib/utils";
import {TablePaginationNavigation} from "@/components/wrappers/common/table/table-pagination-navigation";
import {TablePaginationSize} from "@/components/wrappers/common/table/table-pagination-size";
import {cn} from "@/lib/utils";
interface tablePaginationProps {
className?: string;
@@ -12,12 +12,14 @@ interface tablePaginationProps {
}
export function TablePagination(props: tablePaginationProps) {
const { className, table, maxVisiblePages = 3, pageSizeOptions = [10, 20, 30, 40, 50] } = props;
const {className, table, maxVisiblePages = 3, pageSizeOptions = [10, 20, 30, 40, 50]} = props;
return (
<div className={cn("flex gap-x-4 flex-row w-full", className)}>
<TablePaginationSize table={table} pageSizeOptions={pageSizeOptions} />
<TablePaginationNavigation table={table} maxVisiblePages={maxVisiblePages} className="justify-end mt-0" />
<div
className={cn("flex gap-x-4", className)}
>
<TablePaginationSize table={table} pageSizeOptions={pageSizeOptions}/>
<TablePaginationNavigation table={table} maxVisiblePages={maxVisiblePages} className="justify-end mt-0"/>
</div>
);
}
@@ -1,9 +1,8 @@
import {Form, FormControl, FormField, FormItem, FormMessage, useZodForm} from "@/components/ui/form";
import {Plus, Trash2} from "lucide-react";
import {InfoIcon, Plus, Trash2} from "lucide-react";
import {useFieldArray} from "react-hook-form";
import {
AlertPoliciesSchema, AlertPoliciesType, AlertPolicySchema, AlertPolicyType,
AlertPoliciesSchema, AlertPoliciesType, AlertPolicyType,
EVENT_KIND_OPTIONS
} from "@/components/wrappers/dashboard/database/alert-policy/alert-policy.schema";
import {DatabaseWith} from "@/db/schema/07_database";
@@ -16,13 +15,14 @@ import {Separator} from "@/components/ui/separator";
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select";
import {MultiSelect} from "@/components/wrappers/common/multiselect/multi-select";
import {useMutation} from "@tanstack/react-query";
import {deleteBackupAction} from "@/features/dashboard/restore/restore.action";
import {toast} from "sonner";
import {
createAlertPoliciesAction, deleteAlertPoliciesAction,
updateAlertPoliciesAction
} from "@/components/wrappers/dashboard/database/alert-policy/alert-policy.action";
import {useRouter} from "next/navigation";
import {Switch} from "@/components/ui/switch";
import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";
type AlertPolicyFormProps = {
onSuccess?: () => void;
@@ -33,13 +33,13 @@ type AlertPolicyFormProps = {
export const AlertPolicyForm = ({database, notificationChannels, organizationId, onSuccess}: AlertPolicyFormProps) => {
const router = useRouter()
console.log("database",database)
const organizationNotificationChannels = notificationChannels.map(channel => channel.id) ?? [];
const formattedAlertPoliciesList = (alertPolicies: AlertPolicy[]) => {
return alertPolicies.map((alertPolicy) => ({
notificationChannelId: alertPolicy.notificationChannelId,
eventKinds: alertPolicy.eventKinds
eventKinds: alertPolicy.eventKinds,
enabled: alertPolicy.enabled
}));
};
@@ -49,6 +49,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
alertPolicies: database.alertPolicies && database.alertPolicies.length > 0 ? formattedAlertPoliciesList(database.alertPolicies) : [{
notificationChannelId: "",
eventKinds: [],
enabled: true,
}],
},
});
@@ -60,7 +61,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
const addAlertPolicy = () => {
append({id: "", eventKinds: []})
append({id: "", eventKinds: [], enabled: true});
}
const removeAlertPolicy = (index: number) => {
@@ -76,6 +77,9 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
const mutation = useMutation({
mutationFn: async ({alertPolicies}: AlertPoliciesType) => {
console.log(alertPolicies)
const defaultFormatedAlertPolicies = formattedAlertPoliciesList(database?.alertPolicies ?? []);
@@ -138,7 +142,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
);
console.log("failedActions",failedActions);
console.log("failedActions", failedActions);
if (failedActions.length > 0) {
const firstError = failedActions[0].data.actionError;
const message = firstError?.message || "One or more operations failed";
@@ -149,7 +153,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
},
onSuccess: () => {
toast.success("Alert policies saved successfully");
onSuccess?.();
// onSuccess?.();
router.refresh();
},
onError: (error: any) => {
@@ -199,8 +203,6 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
control={form.control}
name={`alertPolicies.${index}.notificationChannelId`}
render={({field}) => {
const selectedIds = form
.watch("alertPolicies")
.map((a: AlertPolicyType) => a.notificationChannelId)
@@ -219,7 +221,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
>
<FormControl>
<SelectTrigger className="h-10 w-full">
<SelectTrigger className="h-10 w-full ">
<SelectValue
placeholder="Select notification channel"/>
</SelectTrigger>
@@ -239,6 +241,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
}}
/>
<FormField
control={form.control}
name={`alertPolicies.${index}.eventKinds`}
@@ -251,19 +254,58 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
defaultValue={field.value ?? []}
placeholder="Select event kinds"
variant="inverted"
animation={2}
animation={0}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div>
<div className="flex flex-col gap-3 justify-between items-center ">
<Button type="button" variant="outline"
onClick={() => removeAlertPolicy(index)}>
<Trash2 className="w-4 h-4"/>
</Button>
<div className="flex h-full justify-center items-center">
<Tooltip>
<TooltipTrigger asChild>
<div tabIndex={0} className="inline-flex rounded-md">
{/*<Switch*/}
{/* checked={true}*/}
{/* onCheckedChange={async () => {*/}
{/* }}*/}
{/*/>*/}
<FormField
control={form.control}
name={`alertPolicies.${index}.enabled`}
render={({field}) => (
<FormItem>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
</TooltipTrigger>
<TooltipContent className="max-w-64 text-pretty">
<div className="flex items-center gap-1.5">
<InfoIcon className="size-4"/>
<p>This is for activating the alert policy</p>
</div>
</TooltipContent>
</Tooltip>
</div>
</div>
</div>
@@ -279,7 +321,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
Cancel
</ButtonWithLoading>
<ButtonWithLoading isPending={false}>
Submit
Save
</ButtonWithLoading>
</div>
</Form>
@@ -27,6 +27,7 @@ export const createAlertPoliciesAction = userAction
databaseId: parsedInput.databaseId,
notificationChannelId: policy.notificationChannelId,
eventKinds: policy.eventKinds,
enabled: policy.enabled,
}));
const insertedPolicies = await db
@@ -5,6 +5,7 @@ export const AlertPolicySchema =
z.object({
notificationChannelId: z.string().min(1, "Please select a notification channel"),
eventKinds: z.enum(['error_backup', 'error_restore', 'success_restore', 'success_backup', 'weekly_report']).array().nonempty(),
enabled: z.boolean().default(true),
}
)
@@ -4,19 +4,28 @@ import {userAction} from "@/lib/safe-actions/actions";
import {z} from "zod";
import {v4 as uuidv4} from "uuid";
import {ServerActionResult} from "@/types/action-type";
import {and, eq} from "drizzle-orm";
import {and, eq, inArray} from "drizzle-orm";
import {db} from "@/db";
import * as drizzleDb from "@/db";
export const deleteProjectAction = userAction.schema(z.string()).action(async ({parsedInput}): Promise<ServerActionResult<typeof drizzleDb.schemas.project.$inferSelect>> => {
try {
const uuid = uuidv4();
await db
const databasesUpdated = await db
.update(drizzleDb.schemas.database)
.set({
projectId: null,
backupPolicy: null
})
.where(eq(drizzleDb.schemas.database.projectId, parsedInput));
.where(eq(drizzleDb.schemas.database.projectId, parsedInput)).returning();
const databasesToRemove = databasesUpdated.map((db) => db.id);
console.log(databasesUpdated);
console.log(databasesToRemove);
await db.delete(drizzleDb.schemas.retentionPolicy)
.where(inArray(drizzleDb.schemas.retentionPolicy.databaseId, databasesToRemove)).execute();
const updatedProjects = await db
.update(drizzleDb.schemas.project)
@@ -109,7 +109,6 @@ export const DatabaseBackupList = (props: DatabaseBackupListProps) => {
enablePagination
selectedActions={(rows) => (
<>
<div className="flex justify-start md:justify-between gap-3 md:gap-0 items-center w-full ml-0">
<div className="flex gap-2">
{!isMember && (
@@ -1,10 +1,10 @@
"use server";
import {userAction} from "@/lib/safe-actions/actions";
import { ProjectSchema } from "@/components/wrappers/dashboard/projects/project-form/project-form.schema";
import { z } from "zod";
import { ServerActionResult } from "@/types/action-type";
import { db } from "@/db";
import {ProjectSchema} from "@/components/wrappers/dashboard/projects/project-form/project-form.schema";
import {z} from "zod";
import {ServerActionResult} from "@/types/action-type";
import {db} from "@/db";
import {and, eq, inArray} from "drizzle-orm";
import {Project} from "@/db/schema/06_project";
import * as drizzleDb from "@/db";
@@ -18,12 +18,12 @@ export const createProjectAction = userAction
organizationId: z.string(),
})
)
.action(async ({ parsedInput }): Promise<ServerActionResult<Project>> => {
.action(async ({parsedInput}): Promise<ServerActionResult<Project>> => {
try {
const slug = slugify(parsedInput.data.name);
const existingProject = await db.query.project.findFirst({
where: and(eq(drizzleDb.schemas.project.name, parsedInput.data.name) ),
where: and(eq(drizzleDb.schemas.project.name, parsedInput.data.name)),
})
if (existingProject) {
@@ -32,7 +32,7 @@ export const createProjectAction = userAction
actionError: {
message: "A project with this name already exists.",
status: 400,
messageParams: { projectName: parsedInput.data.name },
messageParams: {projectName: parsedInput.data.name},
},
};
}
@@ -49,7 +49,7 @@ export const createProjectAction = userAction
if (parsedInput.data.databases.length > 0) {
await db
.update(drizzleDb.schemas.database)
.set({ projectId: createdProject.id })
.set({projectId: createdProject.id})
.where(inArray(drizzleDb.schemas.database.id, parsedInput.data.databases));
}
@@ -58,7 +58,7 @@ export const createProjectAction = userAction
value: createdProject,
actionSuccess: {
message: "Project has been successfully created.",
messageParams: { projectName: parsedInput.data.name },
messageParams: {projectName: parsedInput.data.name},
},
};
} catch (error) {
@@ -69,15 +69,13 @@ export const createProjectAction = userAction
message: "Failed to create project.",
status: 500,
cause: error instanceof Error ? error.message : "Unknown error",
messageParams: { projectName: parsedInput.data.name },
messageParams: {projectName: parsedInput.data.name},
},
};
}
});
export const updateProjectAction = userAction
.schema(
z.object({
@@ -86,7 +84,7 @@ export const updateProjectAction = userAction
projectId: z.string(),
})
)
.action(async ({ parsedInput }): Promise<ServerActionResult<Project>> => {
.action(async ({parsedInput}): Promise<ServerActionResult<Project>> => {
try {
const existing = await db.query.project.findFirst({
where: eq(drizzleDb.schemas.project.id, parsedInput.projectId),
@@ -106,11 +104,18 @@ export const updateProjectAction = userAction
const databasesToRemove = existingDbIds.filter((id: string) => !newDbIds.includes(id));
if (databasesToAdd.length > 0) {
await db.update(drizzleDb.schemas.database).set({ projectId: parsedInput.projectId }).where(inArray(drizzleDb.schemas.database.id, databasesToAdd));
await db.update(drizzleDb.schemas.database).set({projectId: parsedInput.projectId}).where(inArray(drizzleDb.schemas.database.id, databasesToAdd));
}
if (databasesToRemove.length > 0) {
await db.update(drizzleDb.schemas.database).set({ projectId: null }).where(inArray(drizzleDb.schemas.database.id, databasesToRemove));
await db.update(drizzleDb.schemas.database).set({
projectId: null,
backupPolicy: null
}).where(inArray(drizzleDb.schemas.database.id, databasesToRemove));
await db.delete(drizzleDb.schemas.retentionPolicy)
.where(inArray(drizzleDb.schemas.retentionPolicy.databaseId, databasesToRemove)).execute();
}
// const slug = slugify(parsedInput.data.name);
@@ -128,7 +133,7 @@ export const updateProjectAction = userAction
value: updatedProject,
actionSuccess: {
message: "Project has been successfully updated.",
messageParams: { projectName: parsedInput.data.name },
messageParams: {projectName: parsedInput.data.name},
},
};
} catch (error) {
@@ -138,7 +143,7 @@ export const updateProjectAction = userAction
message: "Failed to update project.",
status: 500,
cause: error instanceof Error ? error.message : "Unknown error",
messageParams: { projectName: parsedInput.data.name },
messageParams: {projectName: parsedInput.data.name},
},
};
}