Merge branch 'main' into fix/large-files-storage-backend-v3

# Conflicts:
#	src/features/storages/providers/s3.ts
This commit is contained in:
charlesgauthereau
2026-02-11 11:54:09 +01:00
11 changed files with 51 additions and 23 deletions
@@ -85,7 +85,7 @@ export const LoginForm = (props: loginFormProps) => {
<FormItem> <FormItem>
<FormLabel>Email Address</FormLabel> <FormLabel>Email Address</FormLabel>
<FormControl> <FormControl>
<Input autoComplete="email" autoFocus placeholder="exemple@portabase.io" {...field} /> <Input autoComplete="email" autoFocus placeholder="example@portabase.io" {...field} />
</FormControl> </FormControl>
<FormMessage/> <FormMessage/>
</FormItem> </FormItem>
@@ -88,7 +88,7 @@ export const RegisterForm = (props: registerFormProps) => {
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel>Email</FormLabel>
<FormControl> <FormControl>
<Input placeholder="exemple@portabase.io" {...field} /> <Input placeholder="example@portabase.io" {...field} />
</FormControl> </FormControl>
<FormMessage/> <FormMessage/>
</FormItem> </FormItem>
@@ -18,6 +18,7 @@ import {
ChannelOrganisationForm ChannelOrganisationForm
} from "@/components/wrappers/dashboard/admin/channels/organization/channels-organization-form"; } from "@/components/wrappers/dashboard/admin/channels/organization/channels-organization-form";
type ChannelAddModalProps = { type ChannelAddModalProps = {
channel?: NotificationChannelWith | StorageChannelWith channel?: NotificationChannelWith | StorageChannelWith
organization?: OrganizationWithMembers; organization?: OrganizationWithMembers;
@@ -34,6 +34,7 @@ import {
addNotificationChannelAction, updateNotificationChannelAction addNotificationChannelAction, updateNotificationChannelAction
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/action"; } from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/action";
type NotifierFormProps = { type NotifierFormProps = {
onSuccessAction?: () => void; onSuccessAction?: () => void;
organization?: OrganizationWithMembers; organization?: OrganizationWithMembers;
@@ -54,7 +55,7 @@ export const ChannelForm = ({onSuccessAction, organization, defaultValues, kind}
}); });
useEffect(() => { useEffect(() => {
form.reset(defaultValues ? {...defaultValues} : {}); form.reset(defaultValues ? {...defaultValues} : {enabled: true});
}, [defaultValues]); }, [defaultValues]);
const mutationAddNotificationChannel = useMutation({ const mutationAddNotificationChannel = useMutation({
@@ -118,7 +119,7 @@ export const ChannelForm = ({onSuccessAction, organization, defaultValues, kind}
}} }}
> >
{type.preview && ( {type.preview && (
<div <div
className="absolute bottom-0 right-0 overflow-hidden w-20 h-20 pointer-events-none"> className="absolute bottom-0 right-0 overflow-hidden w-20 h-20 pointer-events-none">
<div <div
@@ -223,11 +224,11 @@ export const ChannelForm = ({onSuccessAction, organization, defaultValues, kind}
<div className="flex justify-between mt-4"> <div className="flex justify-between mt-4">
<div> <div>
{defaultValues && ( {kind == "storage" && (
<ChannelTestButton <ChannelTestButton
kind={kind} kind={kind}
organizationId={organization?.id} organizationId={organization?.id}
channel={defaultValues} channel={form.getValues()}
/> />
)} )}
</div> </div>
@@ -1,18 +1,21 @@
"use client" "use client"
import {Button} from "@/components/ui/button";
import {NotificationChannel} from "@/db/schema/09_notification-channel";
import {useMutation} from "@tanstack/react-query"; import {useMutation} from "@tanstack/react-query";
import {dispatchNotification} from "@/features/notifications/dispatch";
import {EventPayload} from "@/features/notifications/types";
import {Send, ShieldCheck} from "lucide-react"; import {Send, ShieldCheck} from "lucide-react";
import {toast} from "sonner"; import {toast} from "sonner";
import {Button} from "@/components/ui/button";
import {NotificationChannel} from "@/db/schema/09_notification-channel";
import {dispatchNotification} from "@/features/notifications/dispatch";
import {EventPayload} from "@/features/notifications/types";
import {useIsMobile} from "@/hooks/use-mobile"; import {useIsMobile} from "@/hooks/use-mobile";
import {cn} from "@/lib/utils"; import {cn} from "@/lib/utils";
import {StorageChannel} from "@/db/schema/12_storage-channel"; import {StorageChannel} from "@/db/schema/12_storage-channel";
import {ChannelKind, getChannelTextBasedOnKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common"; import {ChannelKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
import type {StorageInput} from "@/features/storages/types"; import type {StorageInput} from "@/features/storages/types";
import {dispatchStorage} from "@/features/storages/dispatch"; import {dispatchStorage} from "@/features/storages/dispatch";
type NotifierTestChannelButtonProps = { type NotifierTestChannelButtonProps = {
channel: NotificationChannel | StorageChannel; channel: NotificationChannel | StorageChannel;
organizationId?: string; organizationId?: string;
@@ -20,8 +23,9 @@ type NotifierTestChannelButtonProps = {
} }
export const ChannelTestButton = ({channel, organizationId, kind}: NotifierTestChannelButtonProps) => { export const ChannelTestButton = ({channel, organizationId, kind}: NotifierTestChannelButtonProps) => {
const channelText = getChannelTextBasedOnKind(kind)
const isMobile = useIsMobile() const isMobile = useIsMobile()
const mutation = useMutation({ const mutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
if (kind === "notification") { if (kind === "notification") {
@@ -30,7 +34,7 @@ export const ChannelTestButton = ({channel, organizationId, kind}: NotifierTestC
message: `We are testing channel ${channel.name}`, message: `We are testing channel ${channel.name}`,
level: 'info', level: 'info',
}; };
const result = await dispatchNotification(payload, undefined, channel.id, organizationId); const result = await dispatchNotification(payload, undefined, channel, organizationId);
if (result.success) { if (result.success) {
toast.success(result.message); toast.success(result.message);
@@ -41,7 +45,7 @@ export const ChannelTestButton = ({channel, organizationId, kind}: NotifierTestC
const input: StorageInput = { const input: StorageInput = {
action: "ping", action: "ping",
}; };
const result = await dispatchStorage(input, undefined, channel.id); const result = await dispatchStorage(input, undefined, undefined, channel);
if (result.success) { if (result.success) {
toast.success("Successfully connected to storage channel"); toast.success("Successfully connected to storage channel");
@@ -47,7 +47,7 @@ export const NotifierSmtpForm = ({form}: NotifierSmtpFormProps) => {
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>
<FormControl> <FormControl>
<Input placeholder="contact@exemple.com" {...field} value={field.value ?? ""}/> <Input placeholder="contact@example.com" {...field} value={field.value ?? ""}/>
</FormControl> </FormControl>
<FormMessage/> <FormMessage/>
</FormItem> </FormItem>
@@ -77,7 +77,7 @@ export const NotifierSmtpForm = ({form}: NotifierSmtpFormProps) => {
<FormItem> <FormItem>
<FormLabel>From Email</FormLabel> <FormLabel>From Email</FormLabel>
<FormControl> <FormControl>
<Input placeholder={`"Portabase" <exemple@portabase.io>`} {...field} <Input placeholder={`"Portabase" <example@portabase.io>`} {...field}
value={field.value ?? ""}/> value={field.value ?? ""}/>
</FormControl> </FormControl>
<FormMessage/> <FormMessage/>
@@ -19,9 +19,22 @@ export const StorageS3Form = ({form}: StorageS3FormProps) => {
name="config.endPointUrl" name="config.endPointUrl"
render={({field}) => ( render={({field}) => (
<FormItem> <FormItem>
<FormLabel>Endpoind URL</FormLabel> <FormLabel>Endpoint URL</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="s3.exemple.com"/> <Input {...field} placeholder="s3.amazonaws.com"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.region"
render={({field}) => (
<FormItem>
<FormLabel>Region</FormLabel>
<FormControl>
<Input {...field} placeholder="us-east-1"/>
</FormControl> </FormControl>
<FormMessage/> <FormMessage/>
</FormItem> </FormItem>
@@ -110,7 +110,7 @@ export const EmailForm = (props: EmailFormProps) => {
<FormItem> <FormItem>
<FormLabel>From Email *</FormLabel> <FormLabel>From Email *</FormLabel>
<FormControl> <FormControl>
<Input placeholder={"exemple@portabase.com"} {...field} /> <Input placeholder={"example@portabase.com"} {...field} />
</FormControl> </FormControl>
<FormDescription>{"The email from where the email will be send"}</FormDescription> <FormDescription>{"The email from where the email will be send"}</FormDescription>
<FormMessage/> <FormMessage/>
@@ -172,7 +172,7 @@ export const EmailForm = (props: EmailFormProps) => {
<FormItem> <FormItem>
<FormLabel>User Email *</FormLabel> <FormLabel>User Email *</FormLabel>
<FormControl> <FormControl>
<Input placeholder={"exemple@portabase.com"} {...field} /> <Input placeholder={"example@portabase.com"} {...field} />
</FormControl> </FormControl>
<FormDescription>{"The email server user"}</FormDescription> <FormDescription>{"The email server user"}</FormDescription>
<FormMessage/> <FormMessage/>
@@ -94,7 +94,7 @@ export const AgentForm = (props: agentFormProps) => {
<FormItem> <FormItem>
<FormLabel>Description</FormLabel> <FormLabel>Description</FormLabel>
<FormControl> <FormControl>
<Input placeholder="This agent is for the client exemple.com" {...field} <Input placeholder="This agent is for the client example.com" {...field}
value={field.value ?? ""}/> value={field.value ?? ""}/>
</FormControl> </FormControl>
<FormDescription>Enter your project agent description</FormDescription> <FormDescription>Enter your project agent description</FormDescription>
+9 -2
View File
@@ -1,17 +1,23 @@
"use server"; "use server";
import {eq} from 'drizzle-orm'; import {eq} from 'drizzle-orm';
import {Json} from "drizzle-zod";
import * as drizzleDb from '@/db'; import * as drizzleDb from '@/db';
import {db} from '@/db'; import {db} from '@/db';
import type {StorageInput, StorageProviderKind, StorageResult,} from './types'; import type {StorageInput, StorageProviderKind, StorageResult,} from './types';
import {dispatchViaProvider} from "@/features/storages/providers"; import {dispatchViaProvider} from "@/features/storages/providers";
import {StorageChannel} from "@/db/schema/12_storage-channel"; import {StorageChannel} from "@/db/schema/12_storage-channel";
import {Json} from "drizzle-zod"; import {
StorageChannelFormType
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-form.schema";
export async function dispatchStorage( export async function dispatchStorage(
input: StorageInput, input: StorageInput,
policyId?: string, policyId?: string,
channelId?: string, channelId?: string,
channelData?: StorageChannelFormType,
organizationId?: string organizationId?: string
): Promise<StorageResult> { ): Promise<StorageResult> {
try { try {
@@ -68,6 +74,8 @@ export async function dispatchStorage(
}; };
} }
if (channelData) channel = {...channelData, config: channelData.config as Json};
if (!channel) { if (!channel) {
return { return {
success: false, success: false,
@@ -90,7 +98,6 @@ export async function dispatchStorage(
channel.provider as StorageProviderKind, channel.provider as StorageProviderKind,
channel.config, channel.config,
input, input,
); );
+2
View File
@@ -4,6 +4,7 @@ import {Readable} from "node:stream";
type S3Config = { type S3Config = {
endPointUrl: string; endPointUrl: string;
region?: string;
accessKey: string; accessKey: string;
secretKey: string; secretKey: string;
bucketName: string; bucketName: string;
@@ -14,6 +15,7 @@ type S3Config = {
async function getS3Client(config: S3Config) { async function getS3Client(config: S3Config) {
return new Minio.Client({ return new Minio.Client({
endPoint: config.endPointUrl, endPoint: config.endPointUrl,
region: config.region ?? "us-east-1",
accessKey: config.accessKey, accessKey: config.accessKey,
secretKey: config.secretKey, secretKey: config.secretKey,
port: config.port ?? 443, port: config.port ?? 443,