Enviar una incidencia
This commit is contained in:
parent
ab9f7ea401
commit
d69c8b3831
@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
@ -68,7 +68,6 @@
|
||||
"react-resizable-panels": "^2.0.23",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-secure-storage": "^1.3.2",
|
||||
"react-toastify": "^10.0.5",
|
||||
"react-wrap-balancer": "^1.1.1",
|
||||
"recharts": "^2.12.7",
|
||||
"slugify": "^1.6.6",
|
||||
|
||||
@ -3,7 +3,6 @@ import { Toaster, TooltipProvider } from "@/ui";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import { Suspense } from "react";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { Routes } from "./Routes";
|
||||
import { LoadingOverlay, TailwindIndicator } from "./components";
|
||||
import { createAxiosAuthActions, createAxiosDataProvider } from "./lib/axios";
|
||||
@ -20,25 +19,26 @@ function App() {
|
||||
});
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<DataSourceProvider dataSource={createAxiosDataProvider(import.meta.env.VITE_API_URL)}>
|
||||
<AuthProvider authActions={createAxiosAuthActions(import.meta.env.VITE_API_URL)}>
|
||||
<ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<UnsavedWarnProvider>
|
||||
<Suspense fallback={<LoadingOverlay />}>
|
||||
<Routes />
|
||||
|
||||
<Toaster />
|
||||
</Suspense>
|
||||
</UnsavedWarnProvider>
|
||||
</TooltipProvider>
|
||||
<TailwindIndicator />
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</DataSourceProvider>
|
||||
</QueryClientProvider>
|
||||
<>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<DataSourceProvider dataSource={createAxiosDataProvider(import.meta.env.VITE_API_URL)}>
|
||||
<AuthProvider authActions={createAxiosAuthActions(import.meta.env.VITE_API_URL)}>
|
||||
<ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<UnsavedWarnProvider>
|
||||
<Suspense fallback={<LoadingOverlay />}>
|
||||
<Routes />
|
||||
</Suspense>
|
||||
</UnsavedWarnProvider>
|
||||
</TooltipProvider>
|
||||
<Toaster />
|
||||
<TailwindIndicator />
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</DataSourceProvider>
|
||||
</QueryClientProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
} from "./app";
|
||||
import { CatalogLayout, CatalogList } from "./app/catalog";
|
||||
import { DashboardPage } from "./app/dashboard";
|
||||
import { QuotesLayout } from "./app/quotes/layout";
|
||||
import { QuotesLayout } from "./app/quotes";
|
||||
import { QuotesList } from "./app/quotes/list";
|
||||
import { ProtectedRoute } from "./components";
|
||||
|
||||
@ -46,11 +46,9 @@ export const Routes = () => {
|
||||
{
|
||||
path: "/catalog",
|
||||
element: (
|
||||
<ProtectedRoute>
|
||||
<CatalogLayout>
|
||||
<Outlet />
|
||||
</CatalogLayout>
|
||||
</ProtectedRoute>
|
||||
<CatalogLayout>
|
||||
<Outlet />
|
||||
</CatalogLayout>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
@ -62,11 +60,9 @@ export const Routes = () => {
|
||||
{
|
||||
path: "/dealers",
|
||||
element: (
|
||||
<ProtectedRoute>
|
||||
<DealerLayout>
|
||||
<Outlet />
|
||||
</DealerLayout>
|
||||
</ProtectedRoute>
|
||||
<DealerLayout>
|
||||
<Outlet />
|
||||
</DealerLayout>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
@ -77,7 +73,11 @@ export const Routes = () => {
|
||||
},
|
||||
{
|
||||
path: "/quotes",
|
||||
element: <QuotesLayout />,
|
||||
element: (
|
||||
<QuotesLayout>
|
||||
<Outlet />
|
||||
</QuotesLayout>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@ -96,11 +96,9 @@ export const Routes = () => {
|
||||
{
|
||||
path: "/settings",
|
||||
element: (
|
||||
<ProtectedRoute>
|
||||
<SettingsLayout>
|
||||
<Outlet />
|
||||
</SettingsLayout>
|
||||
</ProtectedRoute>
|
||||
<SettingsLayout>
|
||||
<Outlet />
|
||||
</SettingsLayout>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import { Layout, LayoutContent, LayoutHeader } from "@/components";
|
||||
import { Layout, LayoutContent, LayoutHeader, ProtectedRoute } from "@/components";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { CatalogProvider } from "./CatalogContext";
|
||||
|
||||
export const CatalogLayout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<CatalogProvider>
|
||||
<Layout>
|
||||
<LayoutHeader />
|
||||
<LayoutContent>{children}</LayoutContent>
|
||||
</Layout>
|
||||
</CatalogProvider>
|
||||
<ProtectedRoute>
|
||||
<CatalogProvider>
|
||||
<Layout className='catalog-layout'>
|
||||
<LayoutHeader />
|
||||
<LayoutContent>{children}</LayoutContent>
|
||||
</Layout>
|
||||
</CatalogProvider>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { Layout, LayoutContent, LayoutHeader } from "@/components";
|
||||
import { Layout, LayoutContent, LayoutHeader, ProtectedRoute } from "@/components";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export const DealerLayout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<Layout>
|
||||
<LayoutHeader />
|
||||
<LayoutContent>{children}</LayoutContent>
|
||||
</Layout>
|
||||
<ProtectedRoute>
|
||||
<Layout className='dealers-layout'>
|
||||
<LayoutHeader />
|
||||
<LayoutContent>{children}</LayoutContent>
|
||||
</Layout>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
};
|
||||
|
||||
@ -11,13 +11,13 @@ import { t } from "i18next";
|
||||
import { SubmitButton } from "@/components";
|
||||
import { useUnsavedChangesNotifier } from "@/lib/hooks";
|
||||
import { Button, Form, Separator } from "@/ui";
|
||||
import { useToast } from "@/ui/use-toast";
|
||||
import { joiResolver } from "@hookform/resolvers/joi";
|
||||
import { ICreateQuote_Request_DTO } from "@shared/contexts";
|
||||
import Joi from "joi";
|
||||
import { useMemo } from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import SpanishJoiMessages from "../../spanish-joi-messages.json";
|
||||
import { useQuotes } from "./hooks";
|
||||
|
||||
@ -25,6 +25,7 @@ interface QuoteDataForm extends ICreateQuote_Request_DTO {}
|
||||
|
||||
export const QuoteCreate = () => {
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const { useCreate } = useQuotes();
|
||||
const { mutate, isPending } = useCreate();
|
||||
|
||||
@ -67,11 +68,18 @@ export const QuoteCreate = () => {
|
||||
mutate(formData, {
|
||||
onError: (error) => {
|
||||
console.debug(error);
|
||||
toast.error(error.message);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
reset(getValues());
|
||||
toast.success("Cotización guardada");
|
||||
toast({
|
||||
title: "Cotización guardada",
|
||||
className: "bg-green-300",
|
||||
});
|
||||
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from "./create";
|
||||
export * from "./edit";
|
||||
export * from "./layout";
|
||||
export * from "./list";
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
import { Layout, LayoutContent, LayoutHeader, ProtectedRoute } from "@/components";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { QuotesProvider } from "./QuotesContext";
|
||||
|
||||
export const QuotesLayout = () => {
|
||||
export const QuotesLayout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<QuotesProvider>
|
||||
<Layout>
|
||||
<Layout className='quotes-layout'>
|
||||
<LayoutHeader />
|
||||
<LayoutContent>
|
||||
<Outlet />
|
||||
</LayoutContent>
|
||||
<LayoutContent>{children}</LayoutContent>
|
||||
</Layout>
|
||||
</QuotesProvider>
|
||||
</ProtectedRoute>
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
import { Layout, LayoutContent, LayoutHeader } from "@/components";
|
||||
import { Layout, LayoutContent, LayoutHeader, ProtectedRoute } from "@/components";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { SettingsProvider } from "./SettingsContext";
|
||||
|
||||
export const SettingsLayout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<SettingsProvider>
|
||||
<Layout>
|
||||
<LayoutHeader />
|
||||
<LayoutContent>
|
||||
<div className='grid w-full max-w-6xl gap-2 mx-auto'>
|
||||
<h1 className='text-2xl font-semibold md:text-3xl'>
|
||||
<Trans i18nKey='settings.edit.title' />
|
||||
</h1>
|
||||
</div>
|
||||
{children}
|
||||
</LayoutContent>
|
||||
</Layout>
|
||||
</SettingsProvider>
|
||||
<ProtectedRoute>
|
||||
<SettingsProvider>
|
||||
<Layout className='settings-layout'>
|
||||
<LayoutHeader />
|
||||
<LayoutContent>
|
||||
<div className='grid w-full max-w-6xl gap-2 mx-auto'>
|
||||
<h1 className='text-2xl font-semibold md:text-3xl'>
|
||||
<Trans i18nKey='settings.edit.title' />
|
||||
</h1>
|
||||
</div>
|
||||
{children}
|
||||
</LayoutContent>
|
||||
</Layout>
|
||||
</SettingsProvider>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
};
|
||||
|
||||
160
client/src/app/support/components/SupportModal.tsx
Normal file
160
client/src/app/support/components/SupportModal.tsx
Normal file
@ -0,0 +1,160 @@
|
||||
import { FormTextAreaField } from "@/components";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Form,
|
||||
} from "@/ui";
|
||||
import { useState } from "react";
|
||||
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
|
||||
import { useToast } from "@/ui/use-toast";
|
||||
import { joiResolver } from "@hookform/resolvers/joi";
|
||||
import { ISendIncidence_Request_DTO } from "@shared/contexts";
|
||||
import { t } from "i18next";
|
||||
import Joi from "joi";
|
||||
import { HelpCircleIcon } from "lucide-react";
|
||||
import { useSupport } from "../hooks";
|
||||
|
||||
const formSchema = Joi.object({
|
||||
incidence: Joi.string().min(10).required().messages({
|
||||
"string.empty": "Debe escribir algo antes de enviar",
|
||||
"string.min": "El texto es demasiado corto. Debe tener al menos 10 caracteres",
|
||||
"string.max": "El texto es demasiado largo.",
|
||||
"any.required": "La descripción es requerida",
|
||||
}),
|
||||
});
|
||||
|
||||
type SupportDataForm = ISendIncidence_Request_DTO;
|
||||
|
||||
export default function SupportModal() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const { useSubmitIncidence } = useSupport();
|
||||
|
||||
const form = useForm<SupportDataForm>({
|
||||
mode: "onBlur",
|
||||
resolver: joiResolver(formSchema),
|
||||
defaultValues: {
|
||||
incidence: "",
|
||||
},
|
||||
});
|
||||
|
||||
const { handleSubmit, watch, reset } = form;
|
||||
|
||||
const incidenceValue = watch("incidence");
|
||||
|
||||
const { mutate } = useSubmitIncidence({
|
||||
mutateOptions: {
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: "Incidencia enviada",
|
||||
description: "La incidencia se ha enviado correctamente",
|
||||
variant: "success",
|
||||
});
|
||||
setIsOpen(false);
|
||||
reset();
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: "Error en el envío",
|
||||
description:
|
||||
"No se ha podido enviar la incidencia correctamente. Por favor, inténtalo de nuevo.",
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit: SubmitHandler<SupportDataForm> = async (data) => {
|
||||
mutate(data);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
console.log("handleClose", incidenceValue.trim());
|
||||
if (incidenceValue.trim()) {
|
||||
setShowConfirmDialog(true);
|
||||
} else {
|
||||
setIsOpen(false);
|
||||
reset();
|
||||
}
|
||||
};
|
||||
|
||||
const confirmClose = () => {
|
||||
setShowConfirmDialog(false);
|
||||
setIsOpen(false);
|
||||
reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='icon'
|
||||
className='overflow-hidden rounded-full bg-primary text-primary-foreground'
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<HelpCircleIcon className='w-5 h-5' />
|
||||
<span className='sr-only'>Abrir ventana de soporte</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className='sm:max-w-xl'>
|
||||
<DialogHeader className='mb-2'>
|
||||
<DialogTitle>{t("support.modal.title")}</DialogTitle>
|
||||
<DialogDescription>{t("support.modal.subtitle")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='space-y-4'>
|
||||
<FormTextAreaField
|
||||
name='incidence'
|
||||
placeholder='Describe la incidencia aquí...'
|
||||
className='min-h-96'
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button type='button' variant='outline' onClick={handleClose}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type='submit'>Enviar incidencia</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>¿Estás seguro de que quieres cancelar?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Has escrito texto en el campo de descripción. Si cierras la ventana, perderás los
|
||||
cambios no guardados.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => setShowConfirmDialog(false)}>
|
||||
Volver al formulario
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={confirmClose}>Sí, cerrar</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
client/src/app/support/components/index.tsx
Normal file
1
client/src/app/support/components/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from "./SupportModal";
|
||||
1
client/src/app/support/hooks/index.ts
Normal file
1
client/src/app/support/hooks/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./useSupport";
|
||||
28
client/src/app/support/hooks/useSupport.tsx
Normal file
28
client/src/app/support/hooks/useSupport.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { TDataSourceError } from "@/lib/hooks/useDataSource/types";
|
||||
import { useDataSource } from "@/lib/hooks/useDataSource/useDataSource";
|
||||
import { ISendIncidence_Request_DTO } from "@shared/contexts";
|
||||
import { useMutation, UseMutationOptions } from "@tanstack/react-query";
|
||||
|
||||
export type UseSupportGetParamsType = {
|
||||
mutateOptions?: UseMutationOptions<void, TDataSourceError, ISendIncidence_Request_DTO>;
|
||||
};
|
||||
|
||||
export const useSupport = () => {
|
||||
const dataSource = useDataSource();
|
||||
|
||||
return {
|
||||
useSubmitIncidence: (params?: UseSupportGetParamsType) => {
|
||||
const { mutateOptions = {} } = params || {};
|
||||
|
||||
return useMutation<void, TDataSourceError, ISendIncidence_Request_DTO>({
|
||||
mutationFn: (data) => {
|
||||
return dataSource.createOne({
|
||||
resource: "support",
|
||||
data,
|
||||
});
|
||||
},
|
||||
...mutateOptions,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
2
client/src/app/support/index.tsx
Normal file
2
client/src/app/support/index.tsx
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./components";
|
||||
export * from "./hooks";
|
||||
@ -1,9 +1,15 @@
|
||||
import { UnsavedWarnProvider } from "@/lib/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export const Layout = ({ children }: PropsWithChildren) => (
|
||||
export const Layout = ({
|
||||
className,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
className?: string;
|
||||
}>) => (
|
||||
<UnsavedWarnProvider>
|
||||
<div className='flex flex-col w-full min-h-screen'>{children}</div>
|
||||
<div className={cn("flex flex-col w-full min-h-screen", className)}>{children}</div>
|
||||
</UnsavedWarnProvider>
|
||||
);
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Button, Sheet, SheetContent, SheetTrigger } from "@/ui";
|
||||
|
||||
import SupportModal from "@/app/support/components/SupportModal";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MenuIcon, Package2Icon } from "lucide-react";
|
||||
import { useCallback } from "react";
|
||||
@ -105,6 +106,7 @@ export const LayoutHeader = () => {
|
||||
</Link>
|
||||
<div className='flex items-center justify-end w-full gap-4 md:ml-auto md:gap-2 lg:gap-4'>
|
||||
<UserButton />
|
||||
<SupportModal />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@ -23,16 +23,16 @@ export const ProtectedRoute = ({ children }: ProctectRouteProps) => {
|
||||
}
|
||||
}, [profile, profileStatus, i18n]);*/
|
||||
|
||||
if (isPending) {
|
||||
/*if (isPending) {
|
||||
return null;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (isSuccess && !authenticated) {
|
||||
// Redirect them to the /login page, but save the current location they were
|
||||
// trying to go to when they were redirected. This allows us to send them
|
||||
// along to that page after they login, which is a nicer user experience
|
||||
// than dropping them off on the home page.
|
||||
return <Navigate to={redirectTo ?? "/login"} state={{ from: location }} />;
|
||||
return <Navigate to={redirectTo ?? "/login"} />;
|
||||
}
|
||||
|
||||
return <>{children ?? null}</>;
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
IUpdateOneDataProviderParams,
|
||||
IUploadFileDataProviderParam,
|
||||
} from "../hooks/useDataSource/DataSource";
|
||||
import { createAxiosInstance } from "./axiosInstance";
|
||||
import { createAxiosInstance, defaultAxiosRequestConfig } from "./axiosInstance";
|
||||
|
||||
export const createAxiosDataProvider = (
|
||||
apiUrl: string,
|
||||
@ -170,62 +170,54 @@ export const createAxiosDataProvider = (
|
||||
},
|
||||
|
||||
custom: async <R>(params: ICustomDataProviderParam): Promise<R> => {
|
||||
const { url, method, responseType, headers, signal, data, ...payload } = params;
|
||||
const requestUrl = `${url}?`;
|
||||
const { url, path, method, responseType, headers, signal, data, ...payload } = params;
|
||||
let requestUrl: string;
|
||||
|
||||
/*if (sort) {
|
||||
const generatedSort = extractSortParams(sort);
|
||||
if (generatedSort) {
|
||||
const { _sort, _order } = generatedSort;
|
||||
const sortQuery = {
|
||||
_sort: _sort.join(","),
|
||||
_order: _order.join(","),
|
||||
};
|
||||
requestUrl = `${requestUrl}&${queryString.stringify(sortQuery)}`;
|
||||
}
|
||||
if (path) {
|
||||
requestUrl = `${apiUrl}/${path}`;
|
||||
} else if (url) {
|
||||
requestUrl = url;
|
||||
} else {
|
||||
throw new Error('"url" or "path" param is missing');
|
||||
}
|
||||
|
||||
if (filters) {
|
||||
const filterQuery = extractFilterParams(filters);
|
||||
requestUrl = `${requestUrl}&${queryString.stringify(filterQuery)}`;
|
||||
}*/
|
||||
|
||||
/*if (query) {
|
||||
requestUrl = `${requestUrl}&${queryString.stringify(query)}`;
|
||||
}*/
|
||||
|
||||
if (headers) {
|
||||
httpClient.defaults.headers = {
|
||||
...httpClient.defaults.headers,
|
||||
...headers,
|
||||
};
|
||||
}
|
||||
console.log(apiUrl, path, url, requestUrl.toString());
|
||||
|
||||
// Preparar la respuesta personalizada
|
||||
let customResponse;
|
||||
|
||||
// Configurar opciones comunes para la petición
|
||||
const config = {
|
||||
url: requestUrl.toString(),
|
||||
method,
|
||||
responseType,
|
||||
signal,
|
||||
...payload,
|
||||
...defaultAxiosRequestConfig,
|
||||
};
|
||||
|
||||
switch (method) {
|
||||
case "put":
|
||||
case "post":
|
||||
case "patch":
|
||||
customResponse = await httpClient.request<R>({
|
||||
url,
|
||||
method,
|
||||
responseType,
|
||||
headers,
|
||||
...config,
|
||||
data,
|
||||
...payload,
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
customResponse = await httpClient.delete<R>(url, {
|
||||
customResponse = await httpClient.delete<R>(requestUrl.toString(), {
|
||||
responseType,
|
||||
headers,
|
||||
...payload,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
customResponse = await httpClient.get<R>(requestUrl, {
|
||||
customResponse = await httpClient.get<R>(requestUrl.toString(), {
|
||||
responseType,
|
||||
signal,
|
||||
headers,
|
||||
...payload,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { AuthActionResponse, useAuth } from "@/lib/hooks";
|
||||
import { UseMutationOptions, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import { useToast } from "@/ui/use-toast";
|
||||
import { useQueryKey } from "../useQueryKey";
|
||||
|
||||
export const useLogout = (params?: UseMutationOptions<AuthActionResponse, Error>) => {
|
||||
@ -11,6 +11,7 @@ export const useLogout = (params?: UseMutationOptions<AuthActionResponse, Error>
|
||||
const keys = useQueryKey();
|
||||
const { logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: keys().auth().action("logout").get(),
|
||||
@ -29,7 +30,11 @@ export const useLogout = (params?: UseMutationOptions<AuthActionResponse, Error>
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
const { message } = error;
|
||||
toast.error(message);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: message,
|
||||
variant: "destructive",
|
||||
});
|
||||
|
||||
if (onError) {
|
||||
onError(error, variables, context);
|
||||
|
||||
@ -74,7 +74,8 @@ export interface IUploadFileDataProviderParam {
|
||||
}
|
||||
|
||||
export interface ICustomDataProviderParam {
|
||||
url: string;
|
||||
url?: string;
|
||||
path?: string;
|
||||
method: "get" | "delete" | "head" | "options" | "post" | "put" | "patch";
|
||||
signal?: AbortSignal;
|
||||
responseType?: ResponseType;
|
||||
|
||||
@ -433,6 +433,13 @@
|
||||
"desc": "Texto para indicar el tiempo de validez de la cotización"
|
||||
}
|
||||
}
|
||||
},
|
||||
"support": {
|
||||
"modal": {
|
||||
"title": "Enviar una incidencia",
|
||||
"subtitle": "Utiliza este formulario para informar sobre cualquier problema que hayas encontrado mientras usabas la aplicación. Nuestro equipo de desarrollo revisará tu incidencia y tratará de resolverla."
|
||||
},
|
||||
"form_fields": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import * as ToastPrimitives from "@radix-ui/react-toast";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { X } from "lucide-react";
|
||||
@ -14,7 +16,7 @@ const ToastViewport = React.forwardRef<
|
||||
<ToastPrimitives.Viewport
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px] space-y-6",
|
||||
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -30,6 +32,7 @@ const toastVariants = cva(
|
||||
default: "border bg-background text-foreground",
|
||||
destructive:
|
||||
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
||||
success: "group border-green-400 bg-green-300 text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Toast,
|
||||
ToastClose,
|
||||
@ -12,7 +14,7 @@ export function Toaster() {
|
||||
const { toasts } = useToast();
|
||||
|
||||
return (
|
||||
<ToastProvider swipeDirection='right'>
|
||||
<ToastProvider>
|
||||
{toasts.map(function ({ id, title, description, action, ...props }) {
|
||||
return (
|
||||
<Toast key={id} {...props}>
|
||||
@ -25,7 +27,7 @@ export function Toaster() {
|
||||
</Toast>
|
||||
);
|
||||
})}
|
||||
<ToastViewport />
|
||||
<ToastViewport className='sm:mx-auto sm:right-0 sm:left-0' />
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
"use client";
|
||||
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from "react";
|
||||
|
||||
import type { ToastActionElement, ToastProps } from "./toast";
|
||||
|
||||
const TOAST_LIMIT = 5;
|
||||
const TOAST_REMOVE_DELAY = 10000;
|
||||
const TOAST_LIMIT = 3;
|
||||
const TOAST_REMOVE_DELAY = 1000000;
|
||||
|
||||
type ToasterToast = ToastProps & {
|
||||
id: string;
|
||||
|
||||
@ -2593,7 +2593,7 @@ clsx@2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b"
|
||||
integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==
|
||||
|
||||
clsx@^2.0.0, clsx@^2.1.0, clsx@^2.1.1:
|
||||
clsx@^2.0.0, clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
@ -5094,13 +5094,6 @@ react-style-singleton@^2.2.1:
|
||||
invariant "^2.2.4"
|
||||
tslib "^2.0.0"
|
||||
|
||||
react-toastify@^10.0.5:
|
||||
version "10.0.5"
|
||||
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-10.0.5.tgz#6b8f8386060c5c856239f3036d1e76874ce3bd1e"
|
||||
integrity sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==
|
||||
dependencies:
|
||||
clsx "^2.1.0"
|
||||
|
||||
react-transition-group@^4.4.5:
|
||||
version "4.4.5"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
|
||||
|
||||
@ -58,6 +58,7 @@
|
||||
"@joi/date": "^2.1.0",
|
||||
"@reis/joi-luxon": "^3.0.0",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/nodemailer": "^6.4.16",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cls-rtracer": "^2.6.3",
|
||||
"cors": "^2.8.5",
|
||||
@ -79,6 +80,7 @@
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^3.6.0",
|
||||
"nodemailer": "^6.9.15",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
|
||||
@ -41,6 +41,11 @@ module.exports = {
|
||||
"/home/rodax/Documentos/uecko-presupuestos/server/uploads/dealer-logos",
|
||||
dealer_logo_placeholder:
|
||||
"/home/rodax/Documentos/uecko-presupuestos/server/uploads/images/logo-placeholder-200x100.png",
|
||||
|
||||
support: {
|
||||
from: "noreply@presupuestos.uecko.com",
|
||||
subject: "Nueva incidencia Presupuestador Uecko",
|
||||
},
|
||||
},
|
||||
|
||||
admin: {
|
||||
@ -56,4 +61,6 @@ module.exports = {
|
||||
password: "123456",
|
||||
language: "en",
|
||||
},
|
||||
|
||||
nodemailer: {},
|
||||
};
|
||||
|
||||
@ -24,6 +24,10 @@ module.exports = {
|
||||
defaults: {
|
||||
dealer_logos_upload_path: "/api/uploads/dealer-logos",
|
||||
dealer_logo_placeholder: "/api/uploads/images/logo-placeholder-200x100.png",
|
||||
|
||||
support: {
|
||||
from: "noreply@presupuestos.uecko.com",
|
||||
},
|
||||
},
|
||||
|
||||
admin: {
|
||||
@ -39,4 +43,16 @@ module.exports = {
|
||||
password: "123456",
|
||||
language: "en",
|
||||
},
|
||||
|
||||
nodemailer: {
|
||||
brevo: {
|
||||
host: "smtp-relay.brevo.com",
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: "7d0c4e001@smtp-brevo.com",
|
||||
pass: "xsmtpsib-42ff61d359e148710fce8376854330891677a38172fd4217a0dc220551cce210-Wxm4DQwItYgTUcF6",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -10,11 +10,10 @@ const extension = isProduction ? ".js" : ".ts";
|
||||
|
||||
const environmentConfig = require(path.resolve(__dirname, "environments", environment + extension));
|
||||
|
||||
export const config = Object.assign(
|
||||
{
|
||||
environment,
|
||||
isProduction,
|
||||
isDevelopment,
|
||||
},
|
||||
environmentConfig
|
||||
);
|
||||
export const config = {
|
||||
environment,
|
||||
isProduction,
|
||||
isDevelopment,
|
||||
|
||||
...environmentConfig,
|
||||
};
|
||||
|
||||
@ -1,35 +1,3 @@
|
||||
import {
|
||||
IRepositoryManager,
|
||||
RepositoryManager,
|
||||
} from "@/contexts/common/domain";
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
createSequelizeAdapter,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { ICommonContext } from "@/contexts/common/infrastructure";
|
||||
|
||||
export interface IAuthContext {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
//services: IApplicationService;
|
||||
}
|
||||
|
||||
export class AuthContext {
|
||||
private static instance: AuthContext | null = null;
|
||||
|
||||
public static getInstance(): IAuthContext {
|
||||
if (!AuthContext.instance) {
|
||||
AuthContext.instance = new AuthContext({
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
});
|
||||
}
|
||||
|
||||
return AuthContext.instance.context;
|
||||
}
|
||||
|
||||
private context: IAuthContext;
|
||||
|
||||
private constructor(context: IAuthContext) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
export interface IAuthContext extends ICommonContext {}
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import { IAuthContext } from "./Auth.context";
|
||||
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
SequelizeRepository,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { ISequelizeAdapter, SequelizeRepository } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Email, ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||
import { Transaction } from "sequelize";
|
||||
import { AuthUser } from "../domain/entities";
|
||||
import { IAuthRepository } from "../domain/repository/AuthRepository.interface";
|
||||
import { IAuthContext } from "./Auth.context";
|
||||
import { IUserMapper, createUserMapper } from "./mappers/authuser.mapper";
|
||||
|
||||
export type QueryParams = {
|
||||
@ -15,10 +11,7 @@ export type QueryParams = {
|
||||
filters: Record<string, any>;
|
||||
};
|
||||
|
||||
export class AuthRepository
|
||||
extends SequelizeRepository<AuthUser>
|
||||
implements IAuthRepository
|
||||
{
|
||||
export class AuthRepository extends SequelizeRepository<AuthUser> implements IAuthRepository {
|
||||
protected mapper: IUserMapper;
|
||||
|
||||
public constructor(props: {
|
||||
@ -42,11 +35,7 @@ export class AuthRepository
|
||||
}
|
||||
|
||||
public async findUserByEmail(email: Email): Promise<AuthUser | null> {
|
||||
const rawUser: any = await this._getBy(
|
||||
"AuthUser_Model",
|
||||
"email",
|
||||
email.toPrimitive(),
|
||||
);
|
||||
const rawUser: any = await this._getBy("AuthUser_Model", "email", email.toPrimitive());
|
||||
|
||||
if (!rawUser === true) {
|
||||
return null;
|
||||
@ -55,12 +44,10 @@ export class AuthRepository
|
||||
return this.mapper.mapToDomain(rawUser);
|
||||
}
|
||||
|
||||
public async findAll(
|
||||
queryCriteria?: IQueryCriteria,
|
||||
): Promise<ICollection<any>> {
|
||||
public async findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<any>> {
|
||||
const { rows, count } = await this._findAll(
|
||||
"AuthUser_Model",
|
||||
queryCriteria,
|
||||
queryCriteria
|
||||
/*{
|
||||
include: [], // esto es para quitar las asociaciones al hacer la consulta
|
||||
}*/
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
// Import the necessary packages and modules
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import passport from "passport";
|
||||
|
||||
export class AuthenticateController extends ExpressController {
|
||||
async executeImpl() {
|
||||
try {
|
||||
return passport.authenticate(
|
||||
"local-jwt",
|
||||
{ session: false },
|
||||
(
|
||||
err: any,
|
||||
user?: AuthUser | false | null,
|
||||
info?: object | string | Array<string | undefined>,
|
||||
status?: number | Array<number | undefined>
|
||||
) => {
|
||||
if (err) {
|
||||
return this.next(err);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return this.unauthorizedError("Unauthorized access. No token provided.");
|
||||
}
|
||||
|
||||
// If the user is authenticated, attach the user object to the request and move on to the next middleware
|
||||
this.req["user"] = user;
|
||||
return this.next();
|
||||
}
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { IAuthUser } from "@/contexts/auth/domain";
|
||||
import { IAuthContext } from "@/contexts/auth/infrastructure/Auth.context";
|
||||
import { IAuthContext } from "@/contexts/auth/infrastructure";
|
||||
import { IIdentity_Response_DTO } from "@shared/contexts";
|
||||
|
||||
export interface IIdentityUser extends IAuthUser {}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { IAuthUser } from "@/contexts/auth/domain";
|
||||
import { IAuthContext } from "@/contexts/auth/infrastructure/Auth.context";
|
||||
import { IAuthContext } from "@/contexts/auth/infrastructure";
|
||||
import { ILogin_Response_DTO } from "@shared/contexts";
|
||||
|
||||
export interface ILoginUser {
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { generateExpressError } from "@/contexts/common/infrastructure/express";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import httpStatus from "http-status";
|
||||
|
||||
interface AuthenticatedRequest extends Request {
|
||||
user?: AuthUser;
|
||||
}
|
||||
|
||||
const profileMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
const _req = req as AuthenticatedRequest;
|
||||
const user = <AuthUser>_req.user;
|
||||
|
||||
if (!user || !user.isAdmin) {
|
||||
generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
}
|
||||
next();
|
||||
};
|
||||
@ -0,0 +1,69 @@
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { ICommonContext } from "@/contexts/common/infrastructure";
|
||||
import { generateExpressError } from "@/contexts/common/infrastructure/express";
|
||||
import { ensureIdIsValid } from "@shared/contexts";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import httpStatus from "http-status";
|
||||
import passport from "passport";
|
||||
|
||||
// Extender el Request de Express para incluir el usuario autenticado optionalmente
|
||||
interface AuthenticatedRequest extends Request {
|
||||
user?: AuthUser;
|
||||
}
|
||||
|
||||
// Middleware para autenticar usando passport con el local-jwt strategy
|
||||
const authenticateJwt = passport.authenticate("local-jwt", { session: false });
|
||||
|
||||
// Para establecer el contexto de autenticación
|
||||
const setAuthContext = (req: AuthenticatedRequest, res: Response, user: AuthUser) => {
|
||||
const { context } = res.locals || {};
|
||||
res.locals.context = {
|
||||
...context,
|
||||
user,
|
||||
} as ICommonContext;
|
||||
};
|
||||
|
||||
// Comprueba el rol del usuario
|
||||
const authorizeUser = (condition: (user: AuthUser) => boolean) => {
|
||||
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
|
||||
const user = req.user as AuthUser;
|
||||
if (!user || !condition(user)) {
|
||||
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
setAuthContext(req, res, user);
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// Verifica que el usuario esté autenticado
|
||||
export const checkUser = [authenticateJwt, authorizeUser((user) => user.isUser)];
|
||||
|
||||
// Verifica que el usuario sea administrador
|
||||
export const checkIsAdmin = [authenticateJwt, authorizeUser((user) => user.isAdmin)];
|
||||
|
||||
// Middleware para verificar que el usuario sea administrador o el dueño de los datos (self)
|
||||
export const checkAdminOrSelf = [
|
||||
authenticateJwt,
|
||||
(req: AuthenticatedRequest, res: Response, next: NextFunction) => {
|
||||
const user = req.user as AuthUser;
|
||||
const { userId } = req.params;
|
||||
|
||||
// Si el usuario es admin, está autorizado
|
||||
if (user.isAdmin) {
|
||||
setAuthContext(req, res, user);
|
||||
next();
|
||||
}
|
||||
|
||||
// Si el usuario es sí mismo
|
||||
if (user.isUser && userId) {
|
||||
const paramIdOrError = ensureIdIsValid(userId);
|
||||
if (paramIdOrError.isSuccess && user.id.equals(paramIdOrError.object)) {
|
||||
setAuthContext(req, res, user);
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
},
|
||||
];
|
||||
@ -1,65 +0,0 @@
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { composeMiddleware, generateExpressError } from "@/contexts/common/infrastructure/express";
|
||||
import { ensureIdIsValid } from "@shared/contexts";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import httpStatus from "http-status";
|
||||
import passport from "passport";
|
||||
|
||||
interface AuthenticatedRequest extends Request {
|
||||
user?: AuthUser;
|
||||
}
|
||||
|
||||
export const checkUser = composeMiddleware([
|
||||
passport.authenticate("local-jwt", {
|
||||
session: false,
|
||||
}),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const _req = req as AuthenticatedRequest;
|
||||
const user = <AuthUser>_req.user;
|
||||
|
||||
if (!user || !user.isUser) {
|
||||
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
}
|
||||
return next();
|
||||
},
|
||||
]);
|
||||
|
||||
export const checkisAdmin = composeMiddleware([
|
||||
passport.authenticate("local-jwt", {
|
||||
session: false,
|
||||
}),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const _req = req as AuthenticatedRequest;
|
||||
const user = <AuthUser>_req.user;
|
||||
|
||||
if (!user || !user.isAdmin) {
|
||||
generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
}
|
||||
return next();
|
||||
},
|
||||
]);
|
||||
|
||||
export const checkAdminOrSelf = composeMiddleware([
|
||||
passport.authenticate("local-jwt", {
|
||||
session: false,
|
||||
}),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const _req = req as AuthenticatedRequest;
|
||||
const user = <AuthUser>_req.user;
|
||||
|
||||
const { userId } = req.params;
|
||||
|
||||
if (user && user.isAdmin) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (user && user.isUser && userId) {
|
||||
const paramIdOrError = ensureIdIsValid(userId);
|
||||
if (paramIdOrError.isSuccess && user.id.equals(paramIdOrError.object)) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
},
|
||||
]);
|
||||
@ -1,10 +1,11 @@
|
||||
import { createCommonContext } from "@/contexts/common/infrastructure";
|
||||
import { PassportStatic } from "passport";
|
||||
import { AuthContext } from "../../Auth.context";
|
||||
import { initEmailStrategy } from "./emailStrategy";
|
||||
import { initJWTStrategy } from "./jwtStrategy";
|
||||
|
||||
// Export a function that will be used to configure Passport authentication
|
||||
export const configurePassportAuth = (passport: PassportStatic) => {
|
||||
passport.use("local-email", initEmailStrategy(AuthContext.getInstance()));
|
||||
passport.use("local-jwt", initJWTStrategy(AuthContext.getInstance()));
|
||||
const context = createCommonContext();
|
||||
passport.use("local-email", initEmailStrategy(context));
|
||||
passport.use("local-jwt", initJWTStrategy(context));
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ import { Strategy as EmailStrategy, IVerifyOptions } from "passport-local";
|
||||
|
||||
import { LoginUseCase } from "@/contexts/auth/application";
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { ICommonContext } from "@/contexts/common/infrastructure";
|
||||
import { IAuthContext } from "../../Auth.context";
|
||||
import { registerAuthRepository } from "../../Auth.repository";
|
||||
|
||||
@ -54,9 +55,9 @@ class EmailStrategyController extends PassportStrategyController {
|
||||
}
|
||||
}
|
||||
|
||||
export const initEmailStrategy = (context: IAuthContext) =>
|
||||
export const initEmailStrategy = (context: ICommonContext) =>
|
||||
new EmailStrategy(strategyOpts, async (username, password, done) => {
|
||||
registerAuthRepository(context);
|
||||
registerAuthRepository(context as IAuthContext);
|
||||
return new EmailStrategyController(
|
||||
{
|
||||
useCase: new LoginUseCase(context),
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "./authMiddleware";
|
||||
export * from "./Auth.middleware";
|
||||
export * from "./configurePassportAuth";
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { config } from "@/config";
|
||||
import { FindUserByEmailUseCase } from "@/contexts/auth/application/FindUserByEmail.useCase";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { createCommonContext, ICommonContext } from "@/contexts/common/infrastructure";
|
||||
import { PassportStrategyController } from "@/contexts/common/infrastructure/express";
|
||||
import { ExtractJwt, Strategy as JWTStrategy, VerifiedCallback } from "passport-jwt";
|
||||
import { IAuthContext } from "../../Auth.context";
|
||||
@ -44,9 +45,10 @@ class JWTStrategyController extends PassportStrategyController {
|
||||
}
|
||||
}
|
||||
|
||||
export const initJWTStrategy = (context: IAuthContext) =>
|
||||
export const initJWTStrategy = (context: ICommonContext) =>
|
||||
new JWTStrategy(strategyOpts, async (payload, done) => {
|
||||
registerAuthRepository(context);
|
||||
const context = createCommonContext();
|
||||
registerAuthRepository(context as IAuthContext);
|
||||
return new JWTStrategyController(
|
||||
{
|
||||
useCase: new FindUserByEmailUseCase(context),
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export * from "./Auth.context";
|
||||
export * from "./Auth.repository";
|
||||
export * from "./express";
|
||||
export * from "./sequelize";
|
||||
|
||||
@ -13,10 +13,6 @@ class AuthUserMapper
|
||||
extends SequelizeMapper<AuthUser_Model, AuthUserCreationAttributes, AuthUser>
|
||||
implements IUserMapper
|
||||
{
|
||||
public constructor(props: { context: IAuthContext }) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected toDomainMappingImpl(source: AuthUser_Model, params: any): AuthUser {
|
||||
const props: IAuthUserProps = {
|
||||
name: this.mapsValue(source, "name", Name.create),
|
||||
|
||||
@ -1,32 +1,3 @@
|
||||
import { IRepositoryManager, RepositoryManager } from "@/contexts/common/domain";
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
createSequelizeAdapter,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { ICommonContext } from "@/contexts/common/infrastructure";
|
||||
|
||||
export interface ICatalogContext {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
//services: IApplicationService;
|
||||
}
|
||||
|
||||
export class CatalogContext {
|
||||
private static instance: CatalogContext | null = null;
|
||||
|
||||
public static getInstance(): ICatalogContext {
|
||||
if (!CatalogContext.instance) {
|
||||
CatalogContext.instance = new CatalogContext({
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
});
|
||||
}
|
||||
|
||||
return CatalogContext.instance.context;
|
||||
}
|
||||
|
||||
private context: ICatalogContext;
|
||||
|
||||
private constructor(context: ICatalogContext) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
export interface ICatalogContext extends ICommonContext {}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
export interface ISendEmailAddress {
|
||||
name: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface ISendEmailOptions {
|
||||
from: string | ISendEmailAddress | undefined;
|
||||
to: string | ISendEmailAddress;
|
||||
subject?: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
cc?: string | ISendEmailAddress | Array<string | ISendEmailAddress> | undefined;
|
||||
bcc?: string | ISendEmailAddress | Array<string | ISendEmailAddress> | undefined;
|
||||
replyTo?: string | ISendEmailAddress | Array<string | ISendEmailAddress> | undefined;
|
||||
attachments?: Array<{ filename: string; path: string }>;
|
||||
}
|
||||
|
||||
export interface IEmailService {
|
||||
sendMail(mailOptions: ISendEmailOptions, dry?: boolean): Promise<void>;
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./ApplicationService";
|
||||
export * from "./Email.service";
|
||||
export * from "./QueryCriteriaService";
|
||||
|
||||
22
server/src/contexts/common/infrastructure/Common.context.ts
Normal file
22
server/src/contexts/common/infrastructure/Common.context.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { config } from "@/config";
|
||||
import { RepositoryManager } from "@/contexts/common/domain";
|
||||
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { IRepositoryManager } from "../domain";
|
||||
import { ISequelizeAdapter } from "./sequelize";
|
||||
|
||||
export interface IContext {}
|
||||
11111111;
|
||||
export interface ICommonContext extends IContext {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
defaults: Record<string, any>;
|
||||
user?: AuthUser;
|
||||
}
|
||||
|
||||
export const createCommonContext = () => ({
|
||||
defaults: config.defaults,
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
});
|
||||
@ -1,31 +0,0 @@
|
||||
import { config } from "../../../config";
|
||||
import { IRepositoryManager, RepositoryManager } from "../domain";
|
||||
import { ISequelizeAdapter, createSequelizeAdapter } from "./sequelize";
|
||||
|
||||
export interface IContext {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
defaults: Record<string, any>;
|
||||
}
|
||||
|
||||
export class ContextFactory {
|
||||
protected static instance: ContextFactory | null = null;
|
||||
|
||||
public static getInstance(): IContext {
|
||||
if (!ContextFactory.instance) {
|
||||
ContextFactory.instance = new ContextFactory({
|
||||
defaults: config.defaults, // Agregamos los valores específicos de ProfileContext
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
});
|
||||
}
|
||||
|
||||
return ContextFactory.instance.context;
|
||||
}
|
||||
|
||||
protected context: IContext;
|
||||
|
||||
protected constructor(context: IContext) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
@ -4,21 +4,18 @@ import { IServerError, ServerError } from "../domain/errors";
|
||||
|
||||
export interface IInfrastructureError extends IServerError {}
|
||||
|
||||
export class InfrastructureError
|
||||
extends ServerError
|
||||
implements IInfrastructureError
|
||||
{
|
||||
export class InfrastructureError extends ServerError implements IInfrastructureError {
|
||||
public static readonly BAD_REQUEST = "BAD_REQUEST";
|
||||
public static readonly UNEXCEPTED_ERROR = "UNEXCEPTED_ERROR";
|
||||
public static readonly INVALID_INPUT_DATA = "INVALID_INPUT_DATA";
|
||||
public static readonly RESOURCE_NOT_READY = "RESOURCE_NOT_READY";
|
||||
public static readonly RESOURCE_NOT_FOUND_ERROR = "RESOURCE_NOT_FOUND_ERROR";
|
||||
public static readonly RESOURCE_ALREADY_REGISTERED =
|
||||
"RESOURCE_ALREADY_REGISTERED";
|
||||
public static readonly RESOURCE_ALREADY_REGISTERED = "RESOURCE_ALREADY_REGISTERED";
|
||||
|
||||
public static create(
|
||||
code: string,
|
||||
message: string,
|
||||
error?: UseCaseError | ValidationError,
|
||||
error?: UseCaseError | ValidationError
|
||||
): InfrastructureError {
|
||||
let payload = {};
|
||||
|
||||
@ -29,9 +26,7 @@ export class InfrastructureError
|
||||
} else {
|
||||
// UseCaseError
|
||||
const _error = <UseCaseError>error;
|
||||
const _payload = Array.isArray(_error.payload)
|
||||
? _error.payload
|
||||
: [_error.payload];
|
||||
const _payload = Array.isArray(_error.payload) ? _error.payload : [_error.payload];
|
||||
|
||||
payload = _payload.map((item: Record<string, any>) => ({
|
||||
path: item.path,
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { IContext } from "../ContextFactory";
|
||||
import { ExpressController } from "./ExpressController";
|
||||
|
||||
export type ControllerFactory<T extends IContext> = (context: T) => ExpressController;
|
||||
|
||||
export const handleRequest =
|
||||
<T extends IContext>(controllerFactory: ControllerFactory<T>) =>
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const context: T = res.locals["context"];
|
||||
(controllerFactory: any) => (req: Request, res: Response, next: NextFunction) => {
|
||||
const context = res.locals["context"];
|
||||
return controllerFactory(context).execute(req, res, next);
|
||||
};
|
||||
|
||||
@ -40,7 +40,7 @@ function composeMiddleware(middlewareArray: any[]) {
|
||||
|
||||
return function (req: Request, res: Response, next: NextFunction) {
|
||||
head(req, res, function (err: unknown) {
|
||||
if (err) return next(err);
|
||||
if (err) next(err);
|
||||
composeMiddleware(tail)(req, res, next);
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export * from "./ContextFactory";
|
||||
export * from "./Common.context";
|
||||
export * from "./Controller.interface";
|
||||
export * from "./InfrastructureError";
|
||||
export * from "./mappers";
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { config } from "@/config";
|
||||
import nodemailer from "nodemailer";
|
||||
import { ApplicationService, IEmailService, ISendEmailOptions } from "../../application";
|
||||
import { LoggerMailService } from "./LoggerMailService";
|
||||
|
||||
export class BrevoMailService extends ApplicationService implements IEmailService {
|
||||
private transporter: any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.transporter = nodemailer.createTransport(config.nodemailer.brevo);
|
||||
}
|
||||
|
||||
async sendMail(mailOptions: ISendEmailOptions, dry?: boolean): Promise<void> {
|
||||
if (dry) {
|
||||
// No enviar el email
|
||||
new LoggerMailService().sendMail(mailOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.transporter.sendMail({
|
||||
...mailOptions,
|
||||
attachments: mailOptions.attachments?.map((att) => ({
|
||||
filename: att.filename,
|
||||
path: att.path,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import { logger } from "@/infrastructure/logger";
|
||||
import { ApplicationService, IEmailService, ISendEmailOptions } from "../../application";
|
||||
|
||||
export class LoggerMailService extends ApplicationService implements IEmailService {
|
||||
async sendMail(mailOptions: ISendEmailOptions): Promise<void> {
|
||||
await logger().debug(
|
||||
`Email no enviado (modo desarrollo):\n${JSON.stringify(mailOptions, null, 2)}\n\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./BrevoMailService";
|
||||
export * from "./LoggerMailService";
|
||||
@ -1,25 +1,3 @@
|
||||
import { ContextFactory, IContext } from "@/contexts/common/infrastructure";
|
||||
import { ICommonContext } from "@/contexts/common/infrastructure";
|
||||
|
||||
export interface IProfileContext extends IContext {}
|
||||
|
||||
export class ProfileContext extends ContextFactory {
|
||||
protected static instance: ProfileContext | null = null;
|
||||
|
||||
public static getInstance(): IProfileContext {
|
||||
if (!ProfileContext.instance) {
|
||||
try {
|
||||
ProfileContext.instance = new ProfileContext({
|
||||
...ContextFactory.getInstance(), // Reutilizamos el contexto de la clase base
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
throw new Error(`Error initializing ProfileContext: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return ProfileContext.instance.context as IProfileContext;
|
||||
}
|
||||
|
||||
private constructor(context: IProfileContext) {
|
||||
super(context); // Llamamos al constructor de la clase base
|
||||
}
|
||||
}
|
||||
export interface IProfileContext extends ICommonContext {}
|
||||
|
||||
@ -1,31 +1,9 @@
|
||||
import { ContextFactory, IContext } from "@/contexts/common/infrastructure";
|
||||
import { ICommonContext } from "@/contexts/common/infrastructure";
|
||||
import { Dealer, IQuoteReferenceGeneratorService } from "../domain";
|
||||
|
||||
export interface ISalesContext extends IContext {
|
||||
export interface ISalesContext extends ICommonContext {
|
||||
services?: {
|
||||
QuoteReferenceGeneratorService: IQuoteReferenceGeneratorService;
|
||||
};
|
||||
dealer?: Dealer;
|
||||
}
|
||||
|
||||
export class SalesContext extends ContextFactory {
|
||||
protected static instance: SalesContext | null = null;
|
||||
|
||||
public static getInstance(): ISalesContext {
|
||||
if (!SalesContext.instance) {
|
||||
try {
|
||||
SalesContext.instance = new SalesContext({
|
||||
...ContextFactory.getInstance(), // Reutilizamos el contexto de la clase base
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
throw new Error(`Error initializing SalesContext: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return SalesContext.instance.context;
|
||||
}
|
||||
|
||||
private constructor(context: ISalesContext) {
|
||||
super(context); // Llamamos al constructor de la clase base
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,53 +82,33 @@ export class UpdateQuoteController extends ExpressController {
|
||||
}
|
||||
|
||||
private _handleExecuteError(error: IUseCaseError) {
|
||||
const createInfraError = (infrastructureCode: string, message: string) => {
|
||||
return InfrastructureError.create(infrastructureCode, message, error);
|
||||
};
|
||||
|
||||
let errorMessage: string;
|
||||
let infraError: IInfrastructureError;
|
||||
|
||||
switch (error.code) {
|
||||
case UseCaseError.NOT_FOUND_ERROR:
|
||||
errorMessage = "Quote not found";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
|
||||
infraError = createInfraError(InfrastructureError.RESOURCE_NOT_FOUND_ERROR, errorMessage);
|
||||
return this.notFoundError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.INVALID_INPUT_DATA:
|
||||
errorMessage = "Quote data not valid";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
"Datos del cliente a actulizar erróneos",
|
||||
error
|
||||
);
|
||||
infraError = createInfraError(InfrastructureError.INVALID_INPUT_DATA, errorMessage);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.REPOSITORY_ERROR:
|
||||
errorMessage = "Error updating quote";
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
infraError = createInfraError(InfrastructureError.UNEXCEPTED_ERROR, errorMessage);
|
||||
return this.conflictError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.UNEXCEPTED_ERROR:
|
||||
errorMessage = error.message;
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
infraError = createInfraError(InfrastructureError.UNEXCEPTED_ERROR, errorMessage);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorMessage = error.message;
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application";
|
||||
import { ICommonContext, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { generateExpressError } from "@/contexts/common/infrastructure/express";
|
||||
import { GetDealerByUserUseCase } from "@/contexts/sales/application";
|
||||
import * as express from "express";
|
||||
import httpStatus from "http-status";
|
||||
import { registerDealerRepository } from "../../Dealer.repository";
|
||||
|
||||
interface AuthenticatedRequest extends express.Request {
|
||||
user?: AuthUser;
|
||||
}
|
||||
|
||||
export const getDealerMiddleware = async (
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction
|
||||
) => {
|
||||
const _req = req as AuthenticatedRequest;
|
||||
const user = _req.user as AuthUser;
|
||||
const { context } = res.locals as { context: ICommonContext };
|
||||
|
||||
if (!user || !user.id) {
|
||||
return handleError(
|
||||
req,
|
||||
res,
|
||||
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, "User not found")
|
||||
);
|
||||
}
|
||||
|
||||
registerDealerRepository(context);
|
||||
|
||||
try {
|
||||
const dealerOrError = await new GetDealerByUserUseCase({
|
||||
adapter: context.adapter,
|
||||
repositoryManager: context.repositoryManager,
|
||||
}).execute({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (dealerOrError.isFailure) {
|
||||
return handleError(req, res, dealerOrError.error);
|
||||
}
|
||||
|
||||
res.locals.context = {
|
||||
...context,
|
||||
dealer: dealerOrError.object,
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (e: unknown) {
|
||||
console.error("Error in getDealerMiddleware:", e as Error);
|
||||
return handleError(req, res, e as UseCaseError);
|
||||
}
|
||||
};
|
||||
|
||||
function handleError(req: express.Request, res: express.Response, error?: IUseCaseError) {
|
||||
const errorMappings: {
|
||||
[key: string]: { message: string; status: number; infraErrorCode: string };
|
||||
} = {
|
||||
[UseCaseError.NOT_FOUND_ERROR]: {
|
||||
message: "Dealer not found",
|
||||
status: httpStatus.NOT_FOUND,
|
||||
infraErrorCode: InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
|
||||
},
|
||||
[UseCaseError.INVALID_INPUT_DATA]: {
|
||||
message: "Dealer data not valid",
|
||||
status: httpStatus.UNPROCESSABLE_ENTITY,
|
||||
infraErrorCode: InfrastructureError.INVALID_INPUT_DATA,
|
||||
},
|
||||
[UseCaseError.REPOSITORY_ERROR]: {
|
||||
message: "Unexpected error",
|
||||
status: httpStatus.CONFLICT,
|
||||
infraErrorCode: InfrastructureError.UNEXCEPTED_ERROR,
|
||||
},
|
||||
[UseCaseError.UNEXCEPTED_ERROR]: {
|
||||
message: error?.message ?? "Unexcepted error",
|
||||
status: httpStatus.INTERNAL_SERVER_ERROR,
|
||||
infraErrorCode: InfrastructureError.UNEXCEPTED_ERROR,
|
||||
},
|
||||
};
|
||||
|
||||
const { message, status, infraErrorCode } = error
|
||||
? errorMappings[error.code]
|
||||
: {
|
||||
message: "Bad request",
|
||||
status: httpStatus.BAD_REQUEST,
|
||||
infraErrorCode: InfrastructureError.BAD_REQUEST,
|
||||
};
|
||||
|
||||
const infraError = InfrastructureError.create(infraErrorCode, message, error);
|
||||
|
||||
return generateExpressError(req, res, status, message, infraError);
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { GetDealerByUserUseCase } from "@/contexts/sales/application";
|
||||
import * as express from "express";
|
||||
import { registerDealerRepository } from "../../Dealer.repository";
|
||||
import { ISalesContext } from "../../Sales.context";
|
||||
|
||||
interface AuthenticatedRequest extends express.Request {
|
||||
user?: AuthUser;
|
||||
}
|
||||
|
||||
export const getDealerMiddleware = async (
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction
|
||||
) => {
|
||||
const _req = req as AuthenticatedRequest;
|
||||
const user = <AuthUser>_req.user;
|
||||
const context: ISalesContext = res.locals.context;
|
||||
|
||||
registerDealerRepository(context);
|
||||
|
||||
try {
|
||||
const dealerOrError = await new GetDealerByUserUseCase(context).execute({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (dealerOrError.isFailure) {
|
||||
return res.status(500).json().send();
|
||||
//return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
context.dealer = dealerOrError.object;
|
||||
|
||||
return next();
|
||||
} catch (e: unknown) {
|
||||
//return this.fail(e as IServerError);
|
||||
return res.status(500).json().send();
|
||||
}
|
||||
};
|
||||
119
server/src/contexts/support/application/SendIncidence.useCase.ts
Normal file
119
server/src/contexts/support/application/SendIncidence.useCase.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
} from "@/contexts/common/application";
|
||||
import {
|
||||
DomainError,
|
||||
IDomainError,
|
||||
ISendIncidence_Request_DTO,
|
||||
Result,
|
||||
TextValueObject,
|
||||
UTCDateValue,
|
||||
UniqueID,
|
||||
} from "@shared/contexts";
|
||||
|
||||
import { Dealer } from "@/contexts/sales/domain";
|
||||
import { Incidence } from "../domain";
|
||||
import { ISupportContext } from "../infrastructure";
|
||||
|
||||
export interface ISendIncidenceUseCaseRequest extends IUseCaseRequest {
|
||||
incidenceDTO: ISendIncidence_Request_DTO;
|
||||
}
|
||||
|
||||
export type SendIncidenceResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<void, never>; // Success!
|
||||
|
||||
export class SendIncidenceUseCase
|
||||
implements IUseCase<ISendIncidenceUseCaseRequest, Promise<SendIncidenceResponseOrError>>
|
||||
{
|
||||
private _context: ISupportContext;
|
||||
|
||||
constructor(context: ISupportContext) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
async execute(request: ISendIncidenceUseCaseRequest): Promise<SendIncidenceResponseOrError> {
|
||||
const { incidenceDTO } = request;
|
||||
//const QuoteRepository = this._getQuoteRepository();
|
||||
|
||||
// Validaciones de datos
|
||||
if (!this._context.user) {
|
||||
const message = "Error. Missing User";
|
||||
return Result.fail(UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message));
|
||||
}
|
||||
|
||||
// Validaciones de datos
|
||||
if (!this._context.dealer) {
|
||||
const message = "Error. Missing Dealer";
|
||||
return Result.fail(UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message));
|
||||
}
|
||||
|
||||
const dealer = this._context.dealer;
|
||||
const user = this._context.user;
|
||||
|
||||
// Send incidence
|
||||
const incidenceOrError = this._tryIncidenceInstance(incidenceDTO, dealer);
|
||||
|
||||
if (incidenceOrError.isFailure) {
|
||||
const { error: domainError } = incidenceOrError;
|
||||
let errorCode = "";
|
||||
let message = "";
|
||||
|
||||
switch (domainError.code) {
|
||||
// Errores manuales
|
||||
case DomainError.INVALID_INPUT_DATA:
|
||||
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||
message = "The issue has some erroneous data.";
|
||||
break;
|
||||
|
||||
default:
|
||||
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||
message = domainError.message;
|
||||
break;
|
||||
}
|
||||
|
||||
return Result.fail(UseCaseError.create(errorCode, message, domainError));
|
||||
}
|
||||
|
||||
const incidence = incidenceOrError.object;
|
||||
|
||||
this._context.services?.emailService.sendMail({
|
||||
from: {
|
||||
name: user.name.toString(),
|
||||
address: user.email.toString(),
|
||||
},
|
||||
to: this._context.defaults.support.from,
|
||||
subject: this._context.defaults.support.subject,
|
||||
html: incidence.description.toString(),
|
||||
});
|
||||
|
||||
return Result.ok<void>();
|
||||
}
|
||||
|
||||
private _tryIncidenceInstance(
|
||||
incidenceDTO: ISendIncidence_Request_DTO,
|
||||
dealer: Dealer
|
||||
): Result<Incidence, IDomainError> {
|
||||
const dateOrError = UTCDateValue.createCurrentDate();
|
||||
if (dateOrError.isFailure) {
|
||||
return Result.fail(dateOrError.error);
|
||||
}
|
||||
|
||||
const descriptionOrError = TextValueObject.create(incidenceDTO.incidence);
|
||||
if (descriptionOrError.isFailure) {
|
||||
return Result.fail(descriptionOrError.error);
|
||||
}
|
||||
|
||||
return Incidence.create(
|
||||
{
|
||||
date: dateOrError.object,
|
||||
description: descriptionOrError.object,
|
||||
dealerId: dealer.id,
|
||||
},
|
||||
UniqueID.generateNewID().object
|
||||
);
|
||||
}
|
||||
}
|
||||
1
server/src/contexts/support/application/index.ts
Normal file
1
server/src/contexts/support/application/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./SendIncidence.useCase";
|
||||
45
server/src/contexts/support/domain/Incidence.ts
Normal file
45
server/src/contexts/support/domain/Incidence.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {
|
||||
AggregateRoot,
|
||||
IDomainError,
|
||||
Note,
|
||||
Result,
|
||||
UTCDateValue,
|
||||
UniqueID,
|
||||
} from "@shared/contexts";
|
||||
|
||||
export interface IIncidenceProps {
|
||||
dealerId: UniqueID;
|
||||
date: UTCDateValue;
|
||||
description: Note;
|
||||
}
|
||||
|
||||
export interface IIncidence {
|
||||
id: UniqueID;
|
||||
|
||||
dealerId: UniqueID;
|
||||
date: UTCDateValue;
|
||||
description: Note;
|
||||
}
|
||||
|
||||
export class Incidence extends AggregateRoot<IIncidenceProps> implements IIncidence {
|
||||
public static create(props: IIncidenceProps, id?: UniqueID): Result<Incidence, IDomainError> {
|
||||
const incidence = new Incidence(props, id);
|
||||
return Result.ok<Incidence>(incidence);
|
||||
}
|
||||
|
||||
get id(): UniqueID {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get dealerId() {
|
||||
return this.props.dealerId;
|
||||
}
|
||||
|
||||
get date() {
|
||||
return this.props.date;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.props.description;
|
||||
}
|
||||
}
|
||||
1
server/src/contexts/support/domain/index.ts
Normal file
1
server/src/contexts/support/domain/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./Incidence";
|
||||
@ -0,0 +1,10 @@
|
||||
import { IEmailService } from "@/contexts/common/application";
|
||||
import { ICommonContext } from "@/contexts/common/infrastructure";
|
||||
import { Dealer } from "@/contexts/sales/domain";
|
||||
|
||||
export interface ISupportContext extends ICommonContext {
|
||||
services?: {
|
||||
emailService: IEmailService;
|
||||
};
|
||||
dealer?: Dealer;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./sendIncidence";
|
||||
@ -0,0 +1,76 @@
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application/useCases";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import { SendIncidenceUseCase } from "@/contexts/support/application/SendIncidence.useCase";
|
||||
import {
|
||||
ensureSendIncidence_Request_DTOIsValid,
|
||||
ISendIncidence_Request_DTO,
|
||||
} from "@shared/contexts";
|
||||
import { ISupportContext } from "../../../Support.context";
|
||||
|
||||
export class SendIncidenceController extends ExpressController {
|
||||
private useCase: SendIncidenceUseCase;
|
||||
private context: ISupportContext;
|
||||
|
||||
constructor(props: { useCase: SendIncidenceUseCase }, context: ISupportContext) {
|
||||
super();
|
||||
|
||||
const { useCase } = props;
|
||||
this.useCase = useCase;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl(): Promise<any> {
|
||||
try {
|
||||
const incidenceDTO: ISendIncidence_Request_DTO = this.req.body;
|
||||
|
||||
// Validar DTO de datos
|
||||
const incidenceDTOOrError = ensureSendIncidence_Request_DTOIsValid(incidenceDTO);
|
||||
|
||||
if (incidenceDTOOrError.isFailure) {
|
||||
const errorMessage = "Incidence data not valid";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
incidenceDTOOrError.error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
// Llamar al caso de uso
|
||||
const result = await this.useCase.execute({
|
||||
incidenceDTO: incidenceDTOOrError.object,
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
return this.noContent();
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleExecuteError(error: IUseCaseError) {
|
||||
let errorMessage: string;
|
||||
let infraError: IInfrastructureError;
|
||||
|
||||
switch (error.code) {
|
||||
case UseCaseError.UNEXCEPTED_ERROR:
|
||||
errorMessage = error.message;
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorMessage = error.message;
|
||||
return this.clientError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { SendIncidenceUseCase } from "@/contexts/support/application/SendIncidence.useCase";
|
||||
import { ISupportContext } from "../../../Support.context";
|
||||
import { SendIncidenceController } from "./SendIncidence.controller";
|
||||
|
||||
export const createSendIncidenceController = (context: ISupportContext) => {
|
||||
if (!context) {
|
||||
throw new Error("Support context is required");
|
||||
}
|
||||
|
||||
return new SendIncidenceController(
|
||||
{
|
||||
useCase: new SendIncidenceUseCase(context),
|
||||
},
|
||||
context
|
||||
);
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./controllers";
|
||||
2
server/src/contexts/support/infrastructure/index.ts
Normal file
2
server/src/contexts/support/infrastructure/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./express";
|
||||
export * from "./Support.context";
|
||||
@ -1,35 +1,3 @@
|
||||
import {
|
||||
IRepositoryManager,
|
||||
RepositoryManager,
|
||||
} from "@/contexts/common/domain";
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
createSequelizeAdapter,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { ICommonContext } from "@/contexts/common/infrastructure";
|
||||
|
||||
export interface IUserContext {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
//services: IApplicationService;
|
||||
}
|
||||
|
||||
export class UserContext {
|
||||
private static instance: UserContext | null = null;
|
||||
|
||||
public static getInstance(): IUserContext {
|
||||
if (!UserContext.instance) {
|
||||
UserContext.instance = new UserContext({
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
});
|
||||
}
|
||||
|
||||
return UserContext.instance.context;
|
||||
}
|
||||
|
||||
private context: IUserContext;
|
||||
|
||||
private constructor(context: IUserContext) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
export interface IUserContext extends ICommonContext {}
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import { createCommonContext } from "@/contexts/common/infrastructure";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
|
||||
export const commonContextMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
// Almacenar el contexto en res.locals
|
||||
res.locals.context = createCommonContext();
|
||||
next();
|
||||
};
|
||||
@ -1,3 +0,0 @@
|
||||
import { ContextFactory } from "@/contexts/common/infrastructure";
|
||||
|
||||
export const createContextMiddleware = () => ContextFactory.getInstance();
|
||||
@ -1,6 +1,6 @@
|
||||
import { checkUser } from "@/contexts/auth";
|
||||
import { listArticlesController } from "@/contexts/catalog";
|
||||
import { NextFunction, Request, Response, Router } from "express";
|
||||
import { listArticlesController } from "../../../../contexts/catalog/infrastructure/express/controllers";
|
||||
|
||||
export const catalogRouter = (appRouter: Router) => {
|
||||
const catalogRoutes: Router = Router({ mergeParams: true });
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { checkUser, checkisAdmin } from "@/contexts/auth";
|
||||
import { checkIsAdmin, checkUser } from "@/contexts/auth";
|
||||
import {
|
||||
getDealerController,
|
||||
listDealersController,
|
||||
} from "@/contexts/sales/infrastructure/express/controllers/dealers";
|
||||
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/dealerMiddleware";
|
||||
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/Dealer.middleware";
|
||||
import { Router } from "express";
|
||||
|
||||
export const DealerRouter = (appRouter: Router) => {
|
||||
export const dealerRouter = (appRouter: Router) => {
|
||||
const dealerRoutes: Router = Router({ mergeParams: true });
|
||||
|
||||
dealerRoutes.get("/", checkisAdmin, listDealersController);
|
||||
dealerRoutes.get("/", checkIsAdmin, listDealersController);
|
||||
dealerRoutes.get("/:dealerId", checkUser, getDealerMiddleware, getDealerController);
|
||||
///dealerRoutes.post("/", checkisAdmin, createDealerController);
|
||||
//dealerRoutes.put("/:dealerId", checkisAdmin, updateDealerController);
|
||||
|
||||
@ -3,12 +3,11 @@ import { handleRequest } from "@/contexts/common/infrastructure/express";
|
||||
import {
|
||||
createGetProfileController,
|
||||
createUpdateProfileController,
|
||||
ProfileContext,
|
||||
} from "@/contexts/profile/infrastructure";
|
||||
import { createGetProfileLogoController } from "@/contexts/profile/infrastructure/express/controllers/getProfileLogo";
|
||||
import { createUploadProfileLogoController } from "@/contexts/profile/infrastructure/express/controllers/uploadProfileLogo";
|
||||
import { NextFunction, Request, Response, Router } from "express";
|
||||
import { createMulterMiddleware } from "../upload.middleware";
|
||||
import { Router } from "express";
|
||||
import { createMulterMiddleware } from "../Upload.middleware";
|
||||
|
||||
const uploadProfileLogo = createMulterMiddleware({
|
||||
uploadFolder: "uploads/dealer-logos",
|
||||
@ -19,13 +18,6 @@ const uploadProfileLogo = createMulterMiddleware({
|
||||
export const profileRouter = (appRouter: Router) => {
|
||||
const profileRoutes: Router = Router({ mergeParams: true });
|
||||
|
||||
const profileContextMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
res.locals["context"] = ProfileContext.getInstance();
|
||||
next();
|
||||
};
|
||||
|
||||
profileRoutes.use(profileContextMiddleware);
|
||||
|
||||
profileRoutes.get("/", checkUser, handleRequest(createGetProfileController));
|
||||
profileRoutes.put("/", checkUser, handleRequest(createUpdateProfileController));
|
||||
profileRoutes.get("/logo", checkUser, handleRequest(createGetProfileLogoController));
|
||||
|
||||
@ -7,10 +7,10 @@ import {
|
||||
setStatusQuoteController,
|
||||
updateQuoteController,
|
||||
} from "@/contexts/sales/infrastructure/express/controllers";
|
||||
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/dealerMiddleware";
|
||||
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/Dealer.middleware";
|
||||
import { Router } from "express";
|
||||
|
||||
export const QuoteRouter = (appRouter: Router) => {
|
||||
export const quoteRouter = (appRouter: Router): void => {
|
||||
const quoteRoutes: Router = Router({ mergeParams: true });
|
||||
|
||||
// Users CRUD
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import { NextFunction, Request, Response, Router } from "express";
|
||||
|
||||
import { config } from "@/config";
|
||||
import { checkUser } from "@/contexts/auth";
|
||||
import { handleRequest } from "@/contexts/common/infrastructure/express";
|
||||
import { BrevoMailService, LoggerMailService } from "@/contexts/common/infrastructure/nodemailer";
|
||||
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/Dealer.middleware";
|
||||
import { createSendIncidenceController, ISupportContext } from "@/contexts/support/infrastructure";
|
||||
|
||||
export const supportRouter = (appRouter: Router): void => {
|
||||
const supportRoutes: Router = Router({ mergeParams: true });
|
||||
|
||||
supportRoutes.use((req: Request, res: Response, next: NextFunction) => {
|
||||
const context = res.locals["context"];
|
||||
res.locals["context"] = {
|
||||
...context,
|
||||
services: {
|
||||
emailService: config.isProduction ? new BrevoMailService() : new LoggerMailService(),
|
||||
},
|
||||
} as ISupportContext;
|
||||
next();
|
||||
});
|
||||
|
||||
supportRoutes.post(
|
||||
"/",
|
||||
checkUser,
|
||||
getDealerMiddleware,
|
||||
handleRequest(createSendIncidenceController)
|
||||
);
|
||||
|
||||
appRouter.use("/support", supportRoutes);
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import { checkAdminOrSelf, checkisAdmin } from "@/contexts/auth";
|
||||
import { checkAdminOrSelf, checkIsAdmin } from "@/contexts/auth";
|
||||
import { NextFunction, Request, Response, Router } from "express";
|
||||
import {
|
||||
createCreateUserController,
|
||||
@ -11,7 +11,7 @@ import {
|
||||
export const usersRouter = (appRouter: Router) => {
|
||||
const userRoutes: Router = Router({ mergeParams: true });
|
||||
|
||||
userRoutes.get("/", checkisAdmin, (req: Request, res: Response, next: NextFunction) =>
|
||||
userRoutes.get("/", checkIsAdmin, (req: Request, res: Response, next: NextFunction) =>
|
||||
createListUsersController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
@ -19,15 +19,15 @@ export const usersRouter = (appRouter: Router) => {
|
||||
createGetUserController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
userRoutes.post("/", checkisAdmin, (req: Request, res: Response, next: NextFunction) =>
|
||||
userRoutes.post("/", checkIsAdmin, (req: Request, res: Response, next: NextFunction) =>
|
||||
createCreateUserController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
userRoutes.put("/:userId", checkisAdmin, (req: Request, res: Response, next: NextFunction) =>
|
||||
userRoutes.put("/:userId", checkIsAdmin, (req: Request, res: Response, next: NextFunction) =>
|
||||
createUpdateUserController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
userRoutes.delete("/:userId", checkisAdmin, (req: Request, res: Response, next: NextFunction) =>
|
||||
userRoutes.delete("/:userId", checkIsAdmin, (req: Request, res: Response, next: NextFunction) =>
|
||||
createDeleteUserController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { NextFunction, Request, Response, Router } from "express";
|
||||
import { createContextMiddleware } from "./context.middleware";
|
||||
import { Router } from "express";
|
||||
import { commonContextMiddleware } from "./CommonContext.middleware";
|
||||
import {
|
||||
DealerRouter,
|
||||
QuoteRouter,
|
||||
authRouter,
|
||||
catalogRouter,
|
||||
dealerRouter,
|
||||
profileRouter,
|
||||
quoteRouter,
|
||||
usersRouter,
|
||||
} from "./routes";
|
||||
import { supportRouter } from "./routes/support.routes";
|
||||
|
||||
export const v1Routes = () => {
|
||||
const routes = Router({ mergeParams: true });
|
||||
@ -16,24 +17,22 @@ export const v1Routes = () => {
|
||||
res.send("Hello world!");
|
||||
});
|
||||
|
||||
routes.use((req: Request, res: Response, next: NextFunction) => {
|
||||
res.locals["context"] = createContextMiddleware();
|
||||
//res.locals["middlewares"] = createMiddlewareMap();
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
routes.use((req, res, next) => {
|
||||
console.log(`[${new Date().toLocaleTimeString()}] Incoming request to ${req.path}`);
|
||||
console.log(
|
||||
`[${new Date().toLocaleTimeString()}] Incoming request ${req.method} to ${req.path}`
|
||||
);
|
||||
next();
|
||||
});
|
||||
|
||||
routes.use(commonContextMiddleware);
|
||||
|
||||
authRouter(routes);
|
||||
profileRouter(routes);
|
||||
usersRouter(routes);
|
||||
catalogRouter(routes);
|
||||
DealerRouter(routes);
|
||||
QuoteRouter(routes);
|
||||
dealerRouter(routes);
|
||||
quoteRouter(routes);
|
||||
supportRouter(routes);
|
||||
|
||||
return routes;
|
||||
};
|
||||
|
||||
@ -63,10 +63,6 @@ app.use(helmet());
|
||||
//app.use(morgan('common'));
|
||||
app.use(morgan("dev"));
|
||||
|
||||
// Autentication
|
||||
app.use(passport.initialize());
|
||||
configurePassportAuth(passport);
|
||||
|
||||
// Express configuration
|
||||
app.set("port", process.env.PORT ?? 3001);
|
||||
|
||||
@ -85,6 +81,10 @@ app.use((err: any, req: any, res: any, next: any) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// Autentication
|
||||
app.use(passport.initialize());
|
||||
configurePassportAuth(passport);
|
||||
|
||||
// API
|
||||
app.use("/api/v1", v1Routes());
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { createLogger, format, transports } from "winston";
|
||||
import DailyRotateFile from "winston-daily-rotate-file";
|
||||
import { config } from "../../config";
|
||||
|
||||
function initLogger(rTracer) {
|
||||
export const initLogger = (rTracer) => {
|
||||
// a custom format that outputs request id
|
||||
|
||||
const consoleFormat = format.combine(
|
||||
@ -83,9 +83,7 @@ function initLogger(rTracer) {
|
||||
//}
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
export { initLogger };
|
||||
};
|
||||
|
||||
export const logger = () => {
|
||||
return initLogger(rTracer);
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { SalesContext } from "@/contexts/sales/infrastructure";
|
||||
import { config } from "@/config";
|
||||
import { RepositoryManager } from "@/contexts/common/domain";
|
||||
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
|
||||
import { registerDealerRepository } from "@/contexts/sales/infrastructure/Dealer.repository";
|
||||
import {
|
||||
initializeAdmin,
|
||||
@ -8,7 +11,11 @@ import {
|
||||
import { registerUserRepository } from "@/contexts/users/infrastructure/User.repository";
|
||||
|
||||
export const insertUsers = async () => {
|
||||
const context = SalesContext.getInstance();
|
||||
const context = {
|
||||
defaults: config.defaults,
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
} as any;
|
||||
|
||||
registerUserRepository(context);
|
||||
registerDealerRepository(context);
|
||||
|
||||
@ -1038,6 +1038,13 @@
|
||||
dependencies:
|
||||
undici-types "~6.19.2"
|
||||
|
||||
"@types/nodemailer@^6.4.16":
|
||||
version "6.4.16"
|
||||
resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.16.tgz#db006abcb1e1c8e6ea2fb53b27fefec3c03eaa6c"
|
||||
integrity sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/passport-jwt@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-4.0.1.tgz#080fbe934fb9f6954fb88ec4cdf4bb2cc7c4d435"
|
||||
@ -4598,6 +4605,11 @@ node-releases@^2.0.14:
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
|
||||
integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
|
||||
|
||||
nodemailer@^6.9.15:
|
||||
version "6.9.15"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.15.tgz#57b79dc522be27e0e47ac16cc860aa0673e62e04"
|
||||
integrity sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==
|
||||
|
||||
nopt@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
|
||||
|
||||
@ -3,4 +3,5 @@ export * from "./catalog";
|
||||
export * from "./common";
|
||||
export * from "./profile";
|
||||
export * from "./sales";
|
||||
export * from "./support";
|
||||
export * from "./users";
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../../common";
|
||||
|
||||
export interface ISendIncidence_Request_DTO {
|
||||
incidence: string;
|
||||
}
|
||||
|
||||
export function ensureSendIncidence_Request_DTOIsValid(SupportDTO: ISendIncidence_Request_DTO) {
|
||||
const schema = Joi.object({
|
||||
incidence: Joi.string().min(10).max(1000).required(),
|
||||
});
|
||||
|
||||
const result = RuleValidator.validate<ISendIncidence_Request_DTO>(schema, SupportDTO);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return Result.ok(result.object);
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./ISendIncidence_Request.dto";
|
||||
1
shared/lib/contexts/support/dto/index.ts
Normal file
1
shared/lib/contexts/support/dto/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./SendIncidence.dto";
|
||||
1
shared/lib/contexts/support/index.ts
Normal file
1
shared/lib/contexts/support/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./dto";
|
||||
Loading…
Reference in New Issue
Block a user