Working on notification system.

This commit is contained in:
charlesgauthereau
2025-12-06 23:14:59 +01:00
parent 9458b12d69
commit fde7688485
64 changed files with 792 additions and 551 deletions
@@ -1,6 +1,6 @@
"use server";
import { z } from "zod";
import { EmailFormSchema } from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.schema";
import { EmailFormSchema } from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.schema";
import { eq } from "drizzle-orm";
import { db } from "@/db";
import * as drizzleDb from "@/db";
@@ -19,11 +19,11 @@ import {TooltipProvider} from "@/components/ui/tooltip";
import {
EmailFormSchema,
EmailFormType
} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.schema";
} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.schema";
import {PasswordInput} from "@/components/ui/password-input";
import {
updateEmailSettingsAction
} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.action";
} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.action";
import {toast} from "sonner";
import {useRouter} from "next/navigation";
@@ -1,7 +1,7 @@
"use client"
import {EmailForm} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form";
import {EmailForm} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form";
import {Setting} from "@/db/schema/01_setting";
import {EmailFormType} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.schema";
import {EmailFormType} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.schema";
export type SettingsEmailSectionProps = {
@@ -0,0 +1,49 @@
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card";
import {Button} from "@/components/ui/button";
import {Download} from "lucide-react";
import {getFileUrlPresignedLocal} from "@/features/upload/private/upload.action";
import {toast} from "sonner";
export type AdminSettingsTabProps = {};
export const AdminSettingsSection = (props: AdminSettingsTabProps) => {
const handleDownloadKey = async () => {
let url: string = "";
const data = await getFileUrlPresignedLocal({dir: "private/keys/", fileName: "server_public.pem"})
if (data?.data?.success) {
url = data.data.value ?? "";
} else {
// @ts-ignore
const errorMessage = data?.data?.actionError?.message || "Failed to get file!";
toast.error(errorMessage);
}
window.open(url, "_self");
};
return (
<div className="flex flex-col gap-y-4 h-full py-4">
<Card>
<CardHeader>
<CardTitle>Instance settings</CardTitle>
<CardDescription>Manage portabase settings</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium">Download Public Key</p>
<p className="text-xs text-muted-foreground">
Used for encrypting communications with this instance.
</p>
</div>
<Button onClick={handleDownloadKey} variant="outline" size="sm">
<Download className="h-4 w-4 mr-2"/>
Download
</Button>
</div>
</CardContent>
</Card>
</div>
);
};
@@ -0,0 +1,107 @@
"use client"
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
import {Info, ShieldCheck} from "lucide-react";
import {Switch} from "@/components/ui/switch";
import {Label} from "@/components/ui/label";
import {StorageS3Form} from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/storage-s3-form";
import {useState} from "react";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import {useMutation} from "@tanstack/react-query";
import {checkConnexionToS3} from "@/features/upload/public/upload.action";
import {toast} from "sonner";
import {useRouter} from "next/navigation";
import {
updateStorageSettingsAction
} from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.action";
import {Setting} from "@/db/schema/01_setting";
import {S3FormType} from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.schema";
export type SettingsStorageSectionProps = {
settings: Setting;
};
export const SettingsStorageSection = (props: SettingsStorageSectionProps) => {
const router = useRouter();
const mutation = useMutation({
mutationFn: async () => {
const result = await checkConnexionToS3();
if (result.error) {
toast.error("An error occured during the connexion !");
} else {
toast.success("Connexion succeed!");
}
},
});
const [isSwitched, setIsSwitched] = useState<boolean>(props.settings.storage !== "local");
const updateMutation = useMutation({
mutationFn: () => updateStorageSettingsAction({name: "system", data: {storage: isSwitched ? "s3" : "local"}}),
onSuccess: () => {
toast.success(`Settings updated successfully.`);
router.refresh();
},
onError: () => {
toast.error(`An error occurred while updating settings information.`);
},
});
const HandleSwitchStorage = async () => {
setIsSwitched(!isSwitched);
await updateMutation.mutateAsync();
};
const extractS3FormValues = (settings: Setting): S3FormType | undefined => {
if (!settings.s3EndPointUrl) return undefined;
return {
s3EndPointUrl: settings.s3EndPointUrl,
s3AccessKeyId: settings.s3AccessKeyId!,
s3SecretAccessKey: settings.s3SecretAccessKey!,
S3BucketName: settings.S3BucketName!,
};
};
return (
<div className="flex flex-col h-full">
<Alert className="mt-3">
<Info className="h-4 w-4"/>
<AlertTitle>Informations</AlertTitle>
<AlertDescription>
Actually you can only store you data in one place : s3 compatible or in local. For exemple you
cannot choose to store images in one place
and backups files in another.
</AlertDescription>
</Alert>
<div className="flex flex-col h-full py-4 ">
<div className="flex items-center justify-between space-x-2">
<div className="flex items-center space-x-2">
<Label htmlFor="storage-mode">Storage Mode (Local/s3 compatible)</Label>
<Switch
checked={isSwitched}
onCheckedChange={async () => {
await HandleSwitchStorage();
}}
id="storage-mode"
/>
</div>
<div>
<ButtonWithLoading
size={"default"}
disabled={!isSwitched}
isPending={mutation.isPending}
onClick={async () => {
await mutation.mutateAsync();
}}
icon={<ShieldCheck/>}>Test connexion</ButtonWithLoading>
</div>
</div>
{isSwitched && (
<div className="mt-5">
<StorageS3Form defaultValues={extractS3FormValues(props.settings)}/>
</div>
)}
</div>
</div>
);
};
@@ -0,0 +1,50 @@
"use server";
import { z } from "zod";
import { db } from "@/db";
import { S3FormSchema, StorageSwitchSchema } from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.schema";
import { eq } from "drizzle-orm";
import * as drizzleDb from "@/db";
import {userAction} from "@/lib/safe-actions/actions";
export const updateS3SettingsAction = userAction
.schema(
z.object({
name: z.string(),
data: S3FormSchema,
})
)
.action(async ({ parsedInput }) => {
const { name, data } = parsedInput;
const [updatedSettings] = await db
.update(drizzleDb.schemas.setting)
.set({ ...data })
.where(eq(drizzleDb.schemas.setting.name, name))
.returning();
return {
data: updatedSettings,
};
});
export const updateStorageSettingsAction = userAction
.schema(
z.object({
name: z.string(),
data: StorageSwitchSchema,
})
)
.action(async ({ parsedInput }) => {
const { name, data } = parsedInput;
const [updatedSettings] = await db
.update(drizzleDb.schemas.setting)
.set({ ...data })
.where(eq(drizzleDb.schemas.setting.name, name))
.returning();
return {
data: updatedSettings,
};
});
@@ -0,0 +1,16 @@
import { z } from "zod";
export const S3FormSchema = z.object({
s3EndPointUrl: z.string(),
s3AccessKeyId: z.string(),
s3SecretAccessKey: z.string(),
S3BucketName: z.string(),
});
export type S3FormType = z.infer<typeof S3FormSchema>;
export const StorageSwitchSchema = z.object({
storage: z.enum(["local", "s3"]),
});
export type StorageType = z.infer<typeof StorageSwitchSchema>;
@@ -0,0 +1,119 @@
"use client";
import { Card, CardContent } from "@/components/ui/card";
import { FormControl, FormDescription, 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 { useMutation } from "@tanstack/react-query";
import { TooltipProvider } from "@/components/ui/tooltip";
import { S3FormSchema, S3FormType } from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.schema";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { updateS3SettingsAction } from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.action";
import {PasswordInput} from "@/components/ui/password-input";
export type S3FormProps = {
defaultValues?: S3FormType;
};
export const StorageS3Form = (props: S3FormProps) => {
const form = useZodForm({
schema: S3FormSchema,
defaultValues: props.defaultValues,
});
const router = useRouter();
const mutation = useMutation({
mutationFn: async (values: S3FormType) => {
console.log(values);
const updateS3Settings = await updateS3SettingsAction({ name: "system", data: values });
const data = updateS3Settings?.data?.data;
if (updateS3Settings?.serverError || !data) {
console.log(updateS3Settings?.serverError);
toast.error(updateS3Settings?.serverError);
return;
}
toast.success(`Success updating storage informations`);
router.refresh();
},
});
return (
<TooltipProvider>
<Card>
<CardContent>
<Form
form={form}
className="flex flex-col gap-4 mt-3"
onSubmit={async (values) => {
await mutation.mutateAsync(values);
}}
>
<FormField
control={form.control}
name="s3EndPointUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Endpoint Url *</FormLabel>
<FormControl>
<Input placeholder={"s3.eu-west-3.amazonaws.com"} {...field} />
</FormControl>
<FormDescription>{"Your s3 compatible url"}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="s3AccessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>Access Key *</FormLabel>
<FormControl>
<Input placeholder={"The access key token"} {...field} />
</FormControl>
<FormDescription>{"Add your access key"}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="s3SecretAccessKey"
render={({ field }) => (
<FormItem>
<FormLabel>Secret Key *</FormLabel>
<FormControl>
<PasswordInput placeholder={"The secret key token"} {...field} />
</FormControl>
<FormDescription>{"Add your secret key"}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="S3BucketName"
render={({ field }) => (
<FormItem>
<FormLabel>Bucket name *</FormLabel>
<FormControl>
<Input placeholder={"my-bucket"} {...field} />
</FormControl>
<FormDescription>{"The bucket name where you want to store your data"}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-4">
<Button>Save</Button>
</div>
</Form>
</CardContent>
</Card>
</TooltipProvider>
);
};