.
This commit is contained in:
parent
f77b6adb99
commit
d405ce40d6
@ -15,18 +15,18 @@ import {
|
||||
} from "@/ui";
|
||||
import { joiResolver } from "@hookform/resolvers/joi";
|
||||
import { ILogin_DTO } from "@shared/contexts";
|
||||
import { t } from "i18next";
|
||||
import Joi from "joi";
|
||||
import { AlertCircleIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Trans } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import SpanishJoiMessages from "../../spanish-joi-messages.json";
|
||||
|
||||
type LoginDataForm = ILogin_DTO;
|
||||
|
||||
export const LoginPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { mutate: login } = useLogin({
|
||||
onSuccess: (data) => {
|
||||
@ -121,7 +121,7 @@ export const LoginPage = () => {
|
||||
<Alert variant='destructive'>
|
||||
<AlertCircleIcon className='w-4 h-4' />
|
||||
<AlertTitle>
|
||||
<Trans i18nKey='login_page.error' />
|
||||
<Trans i18nKey='common.error' />
|
||||
</AlertTitle>
|
||||
<AlertDescription>{form.formState.errors.root?.message}</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
51
client/src/app/settings/SettingsActions.ts
Normal file
51
client/src/app/settings/SettingsActions.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { useOne } from '@/lib/hooks/useDataSource';
|
||||
import { IDataSource } from '@/lib/hooks/useDataSource/DataSource';
|
||||
|
||||
export type SuccessNotificationResponse = {
|
||||
message: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type PermissionResponse = unknown;
|
||||
|
||||
export type IdentityResponse = unknown;
|
||||
|
||||
export type CatalogActionCheckResponse = {
|
||||
authenticated: boolean;
|
||||
redirectTo?: string;
|
||||
logout?: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
export type CatalogActionOnErrorResponse = {
|
||||
redirectTo?: string;
|
||||
logout?: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
export type CatalogActionResponse = {
|
||||
success: boolean;
|
||||
redirectTo?: string;
|
||||
error?: Error;
|
||||
[key: string]: unknown;
|
||||
successNotification?: SuccessNotificationResponse;
|
||||
};
|
||||
|
||||
export interface ISettingsActions {
|
||||
getSettings: (
|
||||
dataSource: IDataSource,
|
||||
): Promise<IGetProfileResponse_DTO> => {
|
||||
|
||||
return useOne(
|
||||
|
||||
)
|
||||
return dataProvider.getList({
|
||||
resource: "invoices",
|
||||
quickSearchTerm,
|
||||
pagination,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
9
client/src/app/settings/SettingsContext.tsx
Normal file
9
client/src/app/settings/SettingsContext.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { PropsWithChildren, createContext } from "react";
|
||||
|
||||
export interface ISettingsContextState {}
|
||||
|
||||
export const SettingsContext = createContext<ISettingsContextState | null>(null);
|
||||
|
||||
export const SettingsProvider = ({ children }: PropsWithChildren) => {
|
||||
return <SettingsContext.Provider value={{}}>{children}</SettingsContext.Provider>;
|
||||
};
|
||||
@ -1,4 +1,8 @@
|
||||
import { FormTextAreaField } from "@/components";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
@ -6,67 +10,227 @@ import {
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Form,
|
||||
} from "@/ui";
|
||||
|
||||
import { Checkbox } from "@radix-ui/react-checkbox";
|
||||
import { t } from "i18next";
|
||||
import { AlertCircleIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
import { useSettings } from "./hooks";
|
||||
|
||||
type SettingsDataForm = {
|
||||
contact_information: string;
|
||||
default_payment_method: string;
|
||||
default_notes: string;
|
||||
default_legal_terms: string;
|
||||
default_quote_validity: string;
|
||||
};
|
||||
|
||||
export const SettingsEditor = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const {
|
||||
query: { data },
|
||||
mutation: { mutate },
|
||||
} = useSettings();
|
||||
|
||||
console.log(data);
|
||||
|
||||
const form = useForm<SettingsDataForm>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
contact_information: data?.contact_information ?? "",
|
||||
default_payment_method: data?.default_payment_method ?? "",
|
||||
default_notes: data?.default_notes ?? "",
|
||||
default_legal_terms: data?.default_legal_terms ?? "",
|
||||
default_quote_validity: data?.default_quote_validity ?? "",
|
||||
},
|
||||
/*resolver: joiResolver(
|
||||
Joi.object({
|
||||
email: Joi.string()
|
||||
.email({ tlds: { allow: false } })
|
||||
.required(),
|
||||
password: Joi.string().min(4).alphanum().required(),
|
||||
}),
|
||||
{
|
||||
messages: SpanishJoiMessages,
|
||||
}
|
||||
),*/
|
||||
});
|
||||
|
||||
const onSubmit: SubmitHandler<SettingsDataForm> = async (data) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log(data);
|
||||
mutate(data);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='mx-auto grid w-full max-w-6xl items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]'>
|
||||
<nav className='grid gap-4 text-sm text-muted-foreground' x-chunk='dashboard-04-chunk-0'>
|
||||
<Link to='#' className='font-semibold text-primary'>
|
||||
General
|
||||
</Link>
|
||||
<Link to='#'>Security</Link>
|
||||
<Link to='#'>Integrations</Link>
|
||||
<Link to='#'>Support</Link>
|
||||
<Link to='#'>Organizations</Link>
|
||||
<Link to='#'>Advanced</Link>
|
||||
</nav>
|
||||
<div className='grid gap-6'>
|
||||
<Card x-chunk='dashboard-04-chunk-1'>
|
||||
<CardHeader>
|
||||
<CardTitle>Store Name</CardTitle>
|
||||
<CardDescription>Used to identify your store in the marketplace.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form>
|
||||
<Input placeholder='Store Name' />
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>Save</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card x-chunk='dashboard-04-chunk-2'>
|
||||
<CardHeader>
|
||||
<CardTitle>Plugins Directory</CardTitle>
|
||||
<CardDescription>
|
||||
The directory within your project, in which your plugins are located.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form className='flex flex-col gap-4'>
|
||||
<Input placeholder='Project Name' defaultValue='/content/plugins' />
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Checkbox id='include' defaultChecked />
|
||||
<label
|
||||
htmlFor='include'
|
||||
className='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
||||
>
|
||||
Allow administrators to change the directory.
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>Save</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div className='mx-auto grid w-full max-w-6xl items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]'>
|
||||
{form.formState.errors.root?.message && (
|
||||
<Alert variant='destructive'>
|
||||
<AlertCircleIcon className='w-4 h-4' />
|
||||
<AlertTitle>
|
||||
<Trans i18nKey='common.error' />
|
||||
</AlertTitle>
|
||||
<AlertDescription>{form.formState.errors.root?.message}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<nav className='grid gap-4 text-sm text-muted-foreground'>
|
||||
<Link to='#' className='font-semibold text-primary'>
|
||||
<Trans i18nKey='settings.quotes.title' />
|
||||
</Link>
|
||||
<Link to='#'>
|
||||
<Trans i18nKey='settings.quotes.general' />
|
||||
</Link>
|
||||
<Link to='#'>Integrations</Link>
|
||||
<Link to='#'>Support</Link>
|
||||
<Link to='#'>Organizations</Link>
|
||||
<Link to='#'>Advanced</Link>
|
||||
</nav>
|
||||
<div className='grid gap-6'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey='settings.quotes.contact_information.label' />
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<Trans i18nKey='settings.quotes.contact_information.desc' />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FormTextAreaField
|
||||
disabled={loading}
|
||||
placeholder={t("settings.quotes.contact_information.placeholder")}
|
||||
{...form.register("contact_information", {
|
||||
required: true,
|
||||
})}
|
||||
errors={form.formState.errors}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>
|
||||
<Trans i18nKey='common.save' />
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey='settings.quotes.default_payment_method.label' />
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<Trans i18nKey='settings.quotes.default_payment_method.desc' />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FormTextAreaField
|
||||
disabled={loading}
|
||||
placeholder={t("settings.quotes.default_payment_method.placeholder")}
|
||||
{...form.register("default_payment_method", {
|
||||
required: true,
|
||||
})}
|
||||
errors={form.formState.errors}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>
|
||||
<Trans i18nKey='common.save' />
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey='settings.quotes.default_quote_validity.label' />
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<Trans i18nKey='settings.quotes.default_quote_validity.desc' />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FormTextAreaField
|
||||
disabled={loading}
|
||||
placeholder={t("settings.quotes.default_quote_validity.placeholder")}
|
||||
{...form.register("default_quote_validity", {
|
||||
required: true,
|
||||
})}
|
||||
errors={form.formState.errors}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>
|
||||
<Trans i18nKey='common.save' />
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey='settings.quotes.default_notes.label' />
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<Trans i18nKey='settings.quotes.default_notes.desc' />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FormTextAreaField
|
||||
disabled={loading}
|
||||
placeholder={t("settings.quotes.default_notes.placeholder")}
|
||||
{...form.register("default_notes", {
|
||||
required: true,
|
||||
})}
|
||||
errors={form.formState.errors}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>
|
||||
<Trans i18nKey='common.save' />
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey='settings.quotes.default_legal_terms.label' />
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<Trans i18nKey='settings.quotes.default_legal_terms.desc' />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FormTextAreaField
|
||||
disabled={loading}
|
||||
placeholder={t("settings.quotes.default_legal_terms.placeholder")}
|
||||
{...form.register("default_legal_terms", {
|
||||
required: true,
|
||||
})}
|
||||
errors={form.formState.errors}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>
|
||||
<Trans i18nKey='common.save' />
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
1
client/src/app/settings/hooks/index.ts
Normal file
1
client/src/app/settings/hooks/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./useSettings";
|
||||
35
client/src/app/settings/hooks/useSettings.tsx
Normal file
35
client/src/app/settings/hooks/useSettings.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { useOne, useSave } from "@/lib/hooks/useDataSource";
|
||||
import { useDataSource } from "@/lib/hooks/useDataSource/useDataSource";
|
||||
import { useQueryKey } from "@/lib/hooks/useQueryKey";
|
||||
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
||||
|
||||
export type UseSettingsGetParamsType = {
|
||||
enabled?: boolean;
|
||||
queryOptions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export const useSettings = (params?: UseSettingsGetParamsType) => {
|
||||
const dataSource = useDataSource();
|
||||
const keys = useQueryKey();
|
||||
|
||||
return {
|
||||
query: useOne<IGetProfileResponse_DTO>({
|
||||
queryKey: keys().data().resource("settings").action("one").id("").params().get(),
|
||||
queryFn: () =>
|
||||
dataSource.getOne({
|
||||
resource: "profile",
|
||||
id: "",
|
||||
}),
|
||||
...params,
|
||||
}),
|
||||
mutation: useSave({
|
||||
mutationKey: keys().data().resource("settings").action("one").id("").params().get(),
|
||||
mutationFn: (data) =>
|
||||
dataSource.updateOne({
|
||||
resource: "profile",
|
||||
data,
|
||||
id: "",
|
||||
}),
|
||||
}),
|
||||
};
|
||||
};
|
||||
9
client/src/app/settings/useSettingsContext.tsx
Normal file
9
client/src/app/settings/useSettingsContext.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { useContext } from "react";
|
||||
import { SettingsContext } from "./SettingsContext";
|
||||
|
||||
export const useSettingsContext = () => {
|
||||
const context = useContext(SettingsContext);
|
||||
if (context === null)
|
||||
throw new Error("useSettingsContext must be used within a SettingsProvider");
|
||||
return context;
|
||||
};
|
||||
@ -21,12 +21,14 @@ export type UseOneQueryOptions<TUseOneQueryData, TUseOneQueryError> = {
|
||||
queryOptions?: Record<string, unknown>;
|
||||
} & UseLoadingOvertimeOptionsProps;
|
||||
|
||||
export type UseOneQueryResult<TUseOneQueryData, TUseOneQueryError> =
|
||||
UseQueryResult<TUseOneQueryData, TUseOneQueryError> & {
|
||||
isEmpty: boolean;
|
||||
} & UseLoadingOvertimeReturnType;
|
||||
export type UseOneQueryResult<TUseOneQueryData, TUseOneQueryError> = UseQueryResult<
|
||||
TUseOneQueryData,
|
||||
TUseOneQueryError
|
||||
> & {
|
||||
isEmpty: boolean;
|
||||
} & UseLoadingOvertimeReturnType;
|
||||
|
||||
export function useOne<TUseOneQueryData, TUseOneQueryError>({
|
||||
export function useOne<TUseOneQueryData, TUseOneQueryError = Error>({
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled,
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"cancel": "Cancelar",
|
||||
"no": "No",
|
||||
"yes": "Sí",
|
||||
"save": "Guardar",
|
||||
"accept": "Aceptar",
|
||||
"hide": "Ocultar",
|
||||
"sort_asc": "Asc",
|
||||
@ -18,7 +19,8 @@
|
||||
"go_to_prev_page": "Ir a la página anterior",
|
||||
"go_to_next_page": "Ir a la página siguiente",
|
||||
"go_to_last_page": "Ir a la última página",
|
||||
"reset_filter": "Quitar el filtro"
|
||||
"reset_filter": "Quitar el filtro",
|
||||
"error": "Error"
|
||||
},
|
||||
"main_menu": {
|
||||
"home": "Inicio",
|
||||
@ -46,8 +48,7 @@
|
||||
"forgotten_password": "¿Has olvidado tu contraseña?",
|
||||
"become_dealer": "¿Quieres ser distribuidor de Uecko?",
|
||||
"contact_us": "Contacta con nosotros",
|
||||
"login": "Entrar",
|
||||
"error": "Error"
|
||||
"login": "Entrar"
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Bienvenido"
|
||||
@ -64,7 +65,35 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ajustes"
|
||||
"title": "Ajustes",
|
||||
"quotes": {
|
||||
"title": "Cotizaciones",
|
||||
"contact_information": {
|
||||
"label": "Información de contacto",
|
||||
"placeholder": "placeholder",
|
||||
"desc": "Información de contacto"
|
||||
},
|
||||
"default_payment_method": {
|
||||
"label": "Forma de pago",
|
||||
"placeholder": "placeholder",
|
||||
"desc": "desc"
|
||||
},
|
||||
"default_notes": {
|
||||
"label": "Notas",
|
||||
"placeholder": "",
|
||||
"desc": "desc"
|
||||
},
|
||||
"default_legal_terms": {
|
||||
"label": "Cláusulas legales",
|
||||
"placeholder": "",
|
||||
"desc": "desc"
|
||||
},
|
||||
"default_quote_validity": {
|
||||
"label": "Validez por defecto",
|
||||
"placeholder": "",
|
||||
"desc": "desc"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,9 +28,6 @@
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"../shared/lib/contexts/common/domain/entities/QueryCriteria/Pagination/defaults.ts"
|
||||
],
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
} from "@/contexts/common/application/useCases";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Result, UniqueID } from "@shared/contexts";
|
||||
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { IProfileRepository, Profile } from "../domain";
|
||||
|
||||
export interface IGetProfileUseCaseRequest extends IUseCaseRequest {
|
||||
userId: UniqueID;
|
||||
}
|
||||
|
||||
export type GetProfileResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<Profile, never>; // Success!
|
||||
|
||||
export class GetProfileUseCase
|
||||
implements IUseCase<IGetProfileUseCaseRequest, Promise<GetProfileResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(request: IGetProfileUseCaseRequest): Promise<GetProfileResponseOrError> {
|
||||
const { userId } = request;
|
||||
|
||||
// Validación de datos
|
||||
// No hay en este caso
|
||||
|
||||
return await this._getProfileDealer(userId);
|
||||
}
|
||||
|
||||
private async _getProfileDealer(userId: UniqueID) {
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const dealerRepoBuilder = this.getRepositoryByName<IProfileRepository>("Profile");
|
||||
|
||||
let profile: Profile | null = null;
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const dealerRepo = dealerRepoBuilder({ transaction: t });
|
||||
profile = await dealerRepo.getById(userId);
|
||||
});
|
||||
|
||||
if (!profile) {
|
||||
return Result.fail(UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, "Profile not found"));
|
||||
}
|
||||
|
||||
return Result.ok<Profile>(profile!);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(
|
||||
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, "Error al consultar el usuario", _error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _getProfileRepository() {
|
||||
return this._repositoryManager.getRepository<IProfileRepository>("Profile");
|
||||
}
|
||||
}
|
||||
106
server/src/contexts/profile/application/UpdateProfile.useCase.ts
Normal file
106
server/src/contexts/profile/application/UpdateProfile.useCase.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
} from "@/contexts/common/application";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { DomainError, IUpdateProfile_Request_DTO, Result, UniqueID } from "@shared/contexts";
|
||||
import { IProfileRepository, Profile } from "../domain";
|
||||
|
||||
export interface IUpdateProfileUseCaseRequest extends IUseCaseRequest {
|
||||
id: UniqueID;
|
||||
profileDTO: IUpdateProfile_Request_DTO;
|
||||
}
|
||||
|
||||
export type UpdateProfileResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<Profile, never>; // Success!
|
||||
|
||||
export class UpdateProfileUseCase
|
||||
implements IUseCase<IUpdateProfileUseCaseRequest, Promise<UpdateProfileResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
async execute(request: IUpdateProfileUseCaseRequest): Promise<UpdateProfileResponseOrError> {
|
||||
const { id, profileDTO } = request;
|
||||
const profileRepository = this._getProfileRepository();
|
||||
|
||||
// Comprobar que existe el profile
|
||||
const idExists = await profileRepository().exists(id);
|
||||
if (!idExists) {
|
||||
const message = `Profile not found`;
|
||||
return Result.fail(
|
||||
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, message, {
|
||||
path: "id",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Crear perfil con datos actualizados
|
||||
const profileOrError = Profile.create(
|
||||
{
|
||||
contactInformation: profileDTO.contact_information,
|
||||
defaultPaymentMethod: profileDTO.default_payment_method,
|
||||
defaultLegalTerms: profileDTO.default_legal_terms,
|
||||
defaultNotes: profileDTO.default_notes,
|
||||
defaultQuoteValidity: profileDTO.default_quote_validity,
|
||||
},
|
||||
id
|
||||
);
|
||||
|
||||
if (profileOrError.isFailure) {
|
||||
const { error: domainError } = profileOrError;
|
||||
let errorCode = "";
|
||||
let message = "";
|
||||
|
||||
switch (domainError.code) {
|
||||
// Errores manuales
|
||||
case DomainError.INVALID_INPUT_DATA:
|
||||
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||
message = "The profile has some incorrect data";
|
||||
break;
|
||||
|
||||
default:
|
||||
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||
message = domainError.message;
|
||||
break;
|
||||
}
|
||||
|
||||
return Result.fail(UseCaseError.create(errorCode, message, domainError));
|
||||
}
|
||||
|
||||
// Guardar los cambios
|
||||
return this._saveProfile(profileOrError.object);
|
||||
}
|
||||
|
||||
private async _saveProfile(updatedProfile: Profile) {
|
||||
// Guardar el contacto
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const profileRepository = this._getProfileRepository();
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const profileRepo = profileRepository({ transaction: t });
|
||||
await profileRepo.update(updatedProfile);
|
||||
});
|
||||
|
||||
return Result.ok<Profile>(updatedProfile);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
|
||||
}
|
||||
}
|
||||
|
||||
private _getProfileRepository() {
|
||||
return this._repositoryManager.getRepository<IProfileRepository>("Profile");
|
||||
}
|
||||
}
|
||||
1
server/src/contexts/profile/application/index.ts
Normal file
1
server/src/contexts/profile/application/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./UpdateProfile.useCase";
|
||||
13
server/src/contexts/profile/application/profileServices.ts
Normal file
13
server/src/contexts/profile/application/profileServices.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { IProfileRepository, Profile } from "../domain";
|
||||
|
||||
export const findProfileByID = async (
|
||||
id: UniqueID,
|
||||
adapter: IAdapter,
|
||||
repository: RepositoryBuilder<IProfileRepository>
|
||||
): Promise<Profile | null> => {
|
||||
return await adapter
|
||||
.startTransaction()
|
||||
.complete(async (t) => repository({ transaction: t }).getById(id));
|
||||
};
|
||||
45
server/src/contexts/profile/domain/entities/Profile.ts
Normal file
45
server/src/contexts/profile/domain/entities/Profile.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { AggregateRoot, IDomainError, Result, UniqueID } from "@shared/contexts";
|
||||
|
||||
export interface IProfileProps {
|
||||
contactInformation: string;
|
||||
defaultPaymentMethod: string;
|
||||
defaultNotes: string;
|
||||
defaultLegalTerms: string;
|
||||
defaultQuoteValidity: string;
|
||||
}
|
||||
|
||||
export interface IProfile {
|
||||
id: UniqueID;
|
||||
contactInformation: string;
|
||||
defaultPaymentMethod: string;
|
||||
defaultNotes: string;
|
||||
defaultLegalTerms: string;
|
||||
defaultQuoteValidity: string;
|
||||
}
|
||||
|
||||
export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
|
||||
public static create(props: IProfileProps, id?: UniqueID): Result<Profile, IDomainError> {
|
||||
const profile = new Profile(props, id);
|
||||
return Result.ok<Profile>(profile);
|
||||
}
|
||||
|
||||
get contactInformation(): string {
|
||||
return this.props.contactInformation;
|
||||
}
|
||||
|
||||
get defaultPaymentMethod(): string {
|
||||
return this.props.defaultPaymentMethod;
|
||||
}
|
||||
|
||||
get defaultNotes(): string {
|
||||
return this.props.defaultNotes;
|
||||
}
|
||||
|
||||
get defaultLegalTerms(): string {
|
||||
return this.props.defaultLegalTerms;
|
||||
}
|
||||
|
||||
get defaultQuoteValidity(): string {
|
||||
return this.props.defaultQuoteValidity;
|
||||
}
|
||||
}
|
||||
1
server/src/contexts/profile/domain/entities/index.ts
Normal file
1
server/src/contexts/profile/domain/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./Profile";
|
||||
2
server/src/contexts/profile/domain/index.ts
Normal file
2
server/src/contexts/profile/domain/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./entities";
|
||||
export * from "./repository";
|
||||
@ -0,0 +1,9 @@
|
||||
import { IRepository } from "@/contexts/common/domain";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { Profile } from "../entities";
|
||||
|
||||
export interface IProfileRepository extends IRepository<any> {
|
||||
exists(id: UniqueID): Promise<boolean>;
|
||||
getById(id: UniqueID): Promise<Profile | null>;
|
||||
update(profile: Profile): Promise<void>;
|
||||
}
|
||||
1
server/src/contexts/profile/domain/repository/index.ts
Normal file
1
server/src/contexts/profile/domain/repository/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./ProfileRepository.interface";
|
||||
@ -0,0 +1,57 @@
|
||||
import { ISequelizeAdapter, SequelizeRepository } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { Transaction } from "sequelize";
|
||||
import { IProfileContext } from ".";
|
||||
import { IProfileRepository, Profile } from "../domain";
|
||||
import { IProfileMapper, createProfileMapper } from "./mappers";
|
||||
|
||||
export class ProfileRepository extends SequelizeRepository<Profile> implements IProfileRepository {
|
||||
protected mapper: IProfileMapper;
|
||||
|
||||
public constructor(props: {
|
||||
mapper: IProfileMapper;
|
||||
adapter: ISequelizeAdapter;
|
||||
transaction: Transaction;
|
||||
}) {
|
||||
const { adapter, mapper, transaction } = props;
|
||||
super({ adapter, transaction });
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async exists(id: UniqueID): Promise<boolean> {
|
||||
return this._exists("Profile_Model", "id", id.toPrimitive());
|
||||
}
|
||||
|
||||
public async getById(id: UniqueID): Promise<Profile | null> {
|
||||
const rawProfile: any = await this._getById("Profile_Model", id);
|
||||
|
||||
if (!rawProfile === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawProfile);
|
||||
}
|
||||
|
||||
public async update(profile: Profile): Promise<void> {
|
||||
const userData = this.mapper.mapToPersistence(profile);
|
||||
|
||||
// borrando y luego creando
|
||||
// await this.removeById(user.id, true);
|
||||
await this._save("Dealer_Model", profile.id, userData, {});
|
||||
}
|
||||
}
|
||||
|
||||
export const registerProfileRepository = (context: IProfileContext) => {
|
||||
const adapter = context.adapter;
|
||||
const repoManager = context.repositoryManager;
|
||||
|
||||
repoManager.registerRepository("Profile", (params = { transaction: null }) => {
|
||||
const { transaction } = params;
|
||||
|
||||
return new ProfileRepository({
|
||||
transaction,
|
||||
adapter,
|
||||
mapper: createProfileMapper(context),
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -1,23 +1,22 @@
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application/useCases";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import { User } from "@/contexts/users/domain/entities/User";
|
||||
import { IGetUserResponse_DTO } from "@shared/contexts";
|
||||
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
||||
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { GetDealerByUserUseCase } from "@/contexts/sales/application";
|
||||
import { Dealer } from "@/contexts/sales/domain";
|
||||
import { GetProfileUseCase } from "@/contexts/profile/application/GetProfile.useCase";
|
||||
import { IProfileContext } from "../../../Profile.context";
|
||||
import { IGetProfilePresenter } from "./presenter";
|
||||
|
||||
export class GetProfileController extends ExpressController {
|
||||
private useCase: GetDealerByUserUseCase;
|
||||
private useCase: GetProfileUseCase;
|
||||
private presenter: IGetProfilePresenter;
|
||||
private context: IProfileContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: GetDealerByUserUseCase;
|
||||
useCase: GetProfileUseCase;
|
||||
presenter: IGetProfilePresenter;
|
||||
},
|
||||
context: IProfileContext
|
||||
@ -51,9 +50,9 @@ export class GetProfileController extends ExpressController {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
const dealer = <Dealer>result.object;
|
||||
const profile = result.object;
|
||||
|
||||
return this.ok<IGetUserResponse_DTO>(this.presenter.map(user, dealer, this.context));
|
||||
return this.ok<IGetProfileResponse_DTO>(this.presenter.map(profile, this.context));
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { GetDealerByUserUseCase } from "@/contexts/sales/application";
|
||||
import { registerDealerRepository } from "@/contexts/sales/infrastructure/Dealer.repository";
|
||||
import { GetProfileUseCase } from "@/contexts/profile/application/GetProfile.useCase";
|
||||
import { IProfileContext } from "../../../Profile.context";
|
||||
import { registerProfileRepository } from "../../../Profile.repository";
|
||||
import { GetProfileController } from "./GetProfile.controller";
|
||||
import { GetUserPresenter } from "./presenter";
|
||||
import { GetProfilePresenter } from "./presenter";
|
||||
|
||||
export const createGetProfileController = (context: IProfileContext) => {
|
||||
registerDealerRepository(context);
|
||||
registerProfileRepository(context);
|
||||
|
||||
return new GetProfileController(
|
||||
{
|
||||
useCase: new GetDealerByUserUseCase(context),
|
||||
presenter: GetUserPresenter,
|
||||
useCase: new GetProfileUseCase(context),
|
||||
presenter: GetProfilePresenter,
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import { Profile } from "@/contexts/profile/domain";
|
||||
import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context";
|
||||
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
||||
|
||||
export interface IGetProfilePresenter {
|
||||
map: (profile: Profile, context: IProfileContext) => IGetProfileResponse_DTO;
|
||||
}
|
||||
|
||||
export const GetProfilePresenter: IGetProfilePresenter = {
|
||||
map: (profile: Profile, context: IProfileContext): IGetProfileResponse_DTO => {
|
||||
return {
|
||||
id: profile.id.toString(),
|
||||
contact_information: profile.contactInformation,
|
||||
default_payment_method: profile.defaultPaymentMethod,
|
||||
default_notes: profile.defaultNotes,
|
||||
default_legal_terms: profile.defaultLegalTerms,
|
||||
default_quote_validity: profile.defaultQuoteValidity,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -1,26 +0,0 @@
|
||||
import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context";
|
||||
import { Dealer } from "@/contexts/sales/domain";
|
||||
import { User } from "@/contexts/users/domain";
|
||||
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
||||
|
||||
export interface IGetProfilePresenter {
|
||||
map: (user: User, dealer: Dealer, context: IProfileContext) => IGetProfileResponse_DTO;
|
||||
}
|
||||
|
||||
export const GetUserPresenter: IGetProfilePresenter = {
|
||||
map: (user: User, dealer: Dealer, context: IProfileContext): IGetProfileResponse_DTO => {
|
||||
return {
|
||||
id: user.id.toString(),
|
||||
name: user.name.toString(),
|
||||
email: user.email.toString(),
|
||||
language: "es",
|
||||
roles: user.getRoles().map((rol) => rol.toString()),
|
||||
contact_information: "",
|
||||
default_payment_method: "",
|
||||
default_notes: "",
|
||||
default_legal_terms: "",
|
||||
default_quote_validity: "",
|
||||
status: "active",
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -1 +1 @@
|
||||
export * from "./GetUser.presenter";
|
||||
export * from "./GetProfile.presenter";
|
||||
|
||||
@ -0,0 +1,135 @@
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import { UpdateProfileUseCase } from "@/contexts/profile/application";
|
||||
import { User } from "@/contexts/users/domain";
|
||||
import {
|
||||
IUpdateProfileResponse_DTO,
|
||||
IUpdateProfile_Request_DTO,
|
||||
ensureUpdateProfile_Request_DTOIsValid,
|
||||
} from "@shared/contexts";
|
||||
import { IProfileContext } from "../../../Profile.context";
|
||||
import { IUpdateProfilePresenter } from "./presenter";
|
||||
|
||||
export class UpdateProfileController extends ExpressController {
|
||||
private useCase: UpdateProfileUseCase;
|
||||
private presenter: IUpdateProfilePresenter;
|
||||
private context: IProfileContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: UpdateProfileUseCase;
|
||||
presenter: IUpdateProfilePresenter;
|
||||
},
|
||||
context: IProfileContext
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl() {
|
||||
const user = <User | undefined>this.req.user;
|
||||
|
||||
if (!user) {
|
||||
const errorMessage = "Unexpected missing Profile data";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage
|
||||
);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
try {
|
||||
const profileDTO: IUpdateProfile_Request_DTO = this.req.body;
|
||||
|
||||
// Validar DTO de datos
|
||||
const ProfileDTOOrError = ensureUpdateProfile_Request_DTOIsValid(profileDTO);
|
||||
|
||||
if (ProfileDTOOrError.isFailure) {
|
||||
const errorMessage = "Profile data not valid";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
ProfileDTOOrError.error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
// Llamar al caso de uso
|
||||
const result = await this.useCase.execute({
|
||||
id: user.id,
|
||||
profileDTO,
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
const profile = result.object;
|
||||
|
||||
return this.ok<IUpdateProfileResponse_DTO>(this.presenter.map(profile, this.context));
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleExecuteError(error: IUseCaseError) {
|
||||
let errorMessage: string;
|
||||
let infraError: IInfrastructureError;
|
||||
|
||||
switch (error.code) {
|
||||
case UseCaseError.NOT_FOUND_ERROR:
|
||||
errorMessage = "Profile has no associated profile";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
|
||||
return this.notFoundError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.INVALID_INPUT_DATA:
|
||||
errorMessage = "Profile data not valid";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
"Datos a actualizar erróneos",
|
||||
error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.REPOSITORY_ERROR:
|
||||
errorMessage = "Error updating profile";
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.conflictError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
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,17 @@
|
||||
import { UpdateProfileUseCase } from "@/contexts/profile/application";
|
||||
import { registerDealerRepository } from "@/contexts/sales/infrastructure/Dealer.repository";
|
||||
import { IProfileContext } from "../../../Profile.context";
|
||||
import { UpdateProfileController } from "./UpdateProfile.controller";
|
||||
import { UpdateProfilePresenter } from "./presenter";
|
||||
|
||||
export const createUpdateProfileController = (context: IProfileContext) => {
|
||||
registerDealerRepository(context);
|
||||
|
||||
return new UpdateProfileController(
|
||||
{
|
||||
useCase: new UpdateProfileUseCase(context),
|
||||
presenter: UpdateProfilePresenter,
|
||||
},
|
||||
context
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,20 @@
|
||||
import { Profile } from "@/contexts/profile/domain";
|
||||
import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context";
|
||||
import { IUpdateProfileResponse_DTO } from "@shared/contexts";
|
||||
|
||||
export interface IUpdateProfilePresenter {
|
||||
map: (profile: Profile, context: IProfileContext) => IUpdateProfileResponse_DTO;
|
||||
}
|
||||
|
||||
export const UpdateProfilePresenter: IUpdateProfilePresenter = {
|
||||
map: (profile: Profile, context: IProfileContext): IUpdateProfileResponse_DTO => {
|
||||
return {
|
||||
id: profile.id.toString(),
|
||||
contact_information: profile.contactInformation,
|
||||
default_payment_method: profile.defaultPaymentMethod,
|
||||
default_notes: profile.defaultNotes,
|
||||
default_legal_terms: profile.defaultLegalTerms,
|
||||
default_quote_validity: profile.defaultQuoteValidity,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./UpdateUser.presenter";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./profile.mapper";
|
||||
@ -0,0 +1,57 @@
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { IProfileProps, Profile } from "../../domain";
|
||||
import { IProfileContext } from "../Profile.context";
|
||||
import { ProfileCreationAttributes, Profile_Model } from "../sequelize";
|
||||
|
||||
export interface IProfileMapper
|
||||
extends ISequelizeMapper<Profile_Model, ProfileCreationAttributes, Profile> {}
|
||||
|
||||
class ProfileMapper
|
||||
extends SequelizeMapper<Profile_Model, ProfileCreationAttributes, Profile>
|
||||
implements IProfileMapper
|
||||
{
|
||||
public constructor(props: { context: IProfileContext }) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected toDomainMappingImpl(source: Profile_Model, params: any): Profile {
|
||||
const props: IProfileProps = {
|
||||
contactInformation: source.contact_information,
|
||||
defaultPaymentMethod: source.default_payment_method,
|
||||
defaultNotes: source.default_notes,
|
||||
defaultLegalTerms: source.default_legal_terms,
|
||||
defaultQuoteValidity: source.default_quote_validity,
|
||||
};
|
||||
|
||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||
const userOrError = Profile.create(props, id);
|
||||
|
||||
if (userOrError.isFailure) {
|
||||
throw userOrError.error;
|
||||
}
|
||||
|
||||
return userOrError.object;
|
||||
}
|
||||
|
||||
protected toPersistenceMappingImpl(source: Profile, params?: MapperParamsType | undefined) {
|
||||
return {
|
||||
id: source.id.toPrimitive(),
|
||||
|
||||
contact_information: source.contactInformation,
|
||||
default_payment_method: source.defaultPaymentMethod,
|
||||
default_notes: source.defaultNotes,
|
||||
default_legal_terms: source.defaultLegalTerms,
|
||||
default_quote_validity: source.defaultQuoteValidity,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const createProfileMapper = (context: IProfileContext): IProfileMapper =>
|
||||
new ProfileMapper({
|
||||
context,
|
||||
});
|
||||
@ -0,0 +1 @@
|
||||
export * from "./profile.model";
|
||||
@ -0,0 +1,54 @@
|
||||
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
|
||||
|
||||
export type ProfileCreationAttributes = InferCreationAttributes<Profile_Model>;
|
||||
|
||||
export class Profile_Model extends Model<
|
||||
InferAttributes<Profile_Model>,
|
||||
InferCreationAttributes<Profile_Model>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
static associate(connection: Sequelize) {}
|
||||
|
||||
declare id: string;
|
||||
declare contact_information: string;
|
||||
declare default_payment_method: string;
|
||||
declare default_notes: string;
|
||||
declare default_legal_terms: string;
|
||||
declare default_quote_validity: string;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
Profile_Model.init(
|
||||
{
|
||||
id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
contact_information: DataTypes.STRING,
|
||||
default_payment_method: DataTypes.STRING,
|
||||
default_notes: DataTypes.STRING,
|
||||
default_legal_terms: DataTypes.STRING,
|
||||
default_quote_validity: DataTypes.STRING,
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "dealers",
|
||||
|
||||
paranoid: true, // softs deletes
|
||||
timestamps: true,
|
||||
//version: true,
|
||||
|
||||
createdAt: false,
|
||||
updatedAt: "updated_at",
|
||||
deletedAt: false,
|
||||
}
|
||||
);
|
||||
|
||||
return Profile_Model;
|
||||
};
|
||||
@ -7,10 +7,9 @@ import {
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Result, UniqueID } from "@shared/contexts";
|
||||
import { IDealerRepository } from "../../domain";
|
||||
import { Dealer, IDealerRepository } from "../../domain";
|
||||
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { Dealer } from "../../domain/entities/Dealer";
|
||||
|
||||
export interface IGetDealerUseCaseRequest extends IUseCaseRequest {
|
||||
id: UniqueID;
|
||||
@ -31,28 +30,24 @@ export class GetDealerUseCase
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(request: IGetDealerUseCaseRequest): Promise<GetDealerResponseOrError> {
|
||||
const { id } = request;
|
||||
|
||||
// Validación de datos
|
||||
// No hay en este caso
|
||||
|
||||
return await this.findDealer(id);
|
||||
return await this._findDealer(id);
|
||||
}
|
||||
|
||||
private async findDealer(id: UniqueID) {
|
||||
private async _findDealer(id: UniqueID) {
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const dealerRepoBuilder = this.getRepositoryByName<IDealerRepository>("Dealer");
|
||||
const dealerRepository = this._getDealerRepository();
|
||||
|
||||
let dealer: Dealer | null = null;
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const dealerRepo = dealerRepoBuilder({ transaction: t });
|
||||
const dealerRepo = dealerRepository({ transaction: t });
|
||||
dealer = await dealerRepo.getById(id);
|
||||
});
|
||||
|
||||
@ -68,4 +63,8 @@ export class GetDealerUseCase
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _getDealerRepository() {
|
||||
return this._repositoryManager.getRepository<IDealerRepository>("Dealer");
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ import {
|
||||
DomainError,
|
||||
IDomainError,
|
||||
IUpdateDealer_Request_DTO,
|
||||
KeyValueMap,
|
||||
Language,
|
||||
Name,
|
||||
Result,
|
||||
UniqueID,
|
||||
@ -107,9 +109,24 @@ export class UpdateDealerUseCase
|
||||
return Result.fail(nameOrError.error);
|
||||
}
|
||||
|
||||
const languageOrError = Language.createFromCode(dealerDTO.language);
|
||||
if (languageOrError.isFailure) {
|
||||
return Result.fail(languageOrError.error);
|
||||
}
|
||||
|
||||
return Dealer.create(
|
||||
{
|
||||
name: nameOrError.object,
|
||||
logo: "",
|
||||
language: languageOrError.object,
|
||||
|
||||
additionalInfo: KeyValueMap.create([
|
||||
["contact_information", dealerDTO.contact_information],
|
||||
["default_payment_method", dealerDTO.default_payment_method],
|
||||
["default_notes", dealerDTO.default_notes],
|
||||
["default_legal_terms", dealerDTO.default_legal_terms],
|
||||
["default_quote_validity", dealerDTO.default_quote_validity],
|
||||
]).object,
|
||||
},
|
||||
dealerId
|
||||
);
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||
import { Dealer, IDealerRepository } from "@/contexts/sales/domain";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
|
||||
export const existsDealerByID = async (
|
||||
id: UniqueID,
|
||||
adapter: IAdapter,
|
||||
repository: RepositoryBuilder<IDealerRepository>
|
||||
): Promise<boolean> => {
|
||||
return await adapter
|
||||
.startTransaction()
|
||||
.complete(async (t) => repository({ transaction: t }).exists(id));
|
||||
};
|
||||
|
||||
export const findDealerByID = async (
|
||||
id: UniqueID,
|
||||
adapter: IAdapter,
|
||||
repository: RepositoryBuilder<IDealerRepository>
|
||||
): Promise<Dealer | null> => {
|
||||
return await adapter
|
||||
.startTransaction()
|
||||
.complete(async (t) => repository({ transaction: t }).getById(id));
|
||||
};
|
||||
@ -4,3 +4,5 @@ export * from "./GetDealer.useCase";
|
||||
export * from "./GetDealerByUser.useCase";
|
||||
export * from "./ListDealers.useCase";
|
||||
export * from "./UpdateDealer.useCase";
|
||||
|
||||
export * from "./dealerServices";
|
||||
|
||||
@ -1,14 +1,34 @@
|
||||
import { AggregateRoot, IDomainError, Name, Result, UniqueID } from "@shared/contexts";
|
||||
import {
|
||||
AggregateRoot,
|
||||
IDomainError,
|
||||
KeyValueMap,
|
||||
Language,
|
||||
Name,
|
||||
Result,
|
||||
UniqueID,
|
||||
} from "@shared/contexts";
|
||||
import { DealerRole } from "./DealerRole";
|
||||
import { DealerStatus } from "./DealerStatus";
|
||||
|
||||
export interface IDealerProps {
|
||||
name: Name;
|
||||
user_id: UniqueID;
|
||||
name: Name;
|
||||
logo: string;
|
||||
language: Language;
|
||||
roles: DealerRole[];
|
||||
additionalInfo: KeyValueMap;
|
||||
status: DealerStatus;
|
||||
}
|
||||
|
||||
export interface IDealer {
|
||||
id: UniqueID;
|
||||
name: Name;
|
||||
user_id: UniqueID;
|
||||
name: Name;
|
||||
language: Language;
|
||||
|
||||
additionalInfo: KeyValueMap;
|
||||
status: DealerStatus;
|
||||
getRoles: () => DealerRole[];
|
||||
}
|
||||
|
||||
export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
|
||||
@ -17,11 +37,47 @@ export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
|
||||
return Result.ok<Dealer>(user);
|
||||
}
|
||||
|
||||
get name(): Name {
|
||||
return this.props.name;
|
||||
private roles: DealerRole[];
|
||||
|
||||
constructor(props: IDealerProps, id?: UniqueID) {
|
||||
const { roles } = props;
|
||||
super(props, id);
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
get user_id(): UniqueID {
|
||||
return this.props.user_id;
|
||||
}
|
||||
|
||||
get name(): Name {
|
||||
return this.props.name;
|
||||
}
|
||||
|
||||
get language(): Language {
|
||||
return this.props.language;
|
||||
}
|
||||
|
||||
get status(): DealerStatus {
|
||||
return this.props.status;
|
||||
}
|
||||
|
||||
get additionalInfo(): KeyValueMap {
|
||||
return this.props.additionalInfo;
|
||||
}
|
||||
|
||||
get isUser(): boolean {
|
||||
return this._hasRole(DealerRole.ROLE_USER);
|
||||
}
|
||||
|
||||
get isAdmin(): boolean {
|
||||
return this._hasRole(DealerRole.ROLE_ADMIN);
|
||||
}
|
||||
|
||||
public getRoles(): DealerRole[] {
|
||||
return this.roles;
|
||||
}
|
||||
|
||||
private _hasRole(role: DealerRole): boolean {
|
||||
return (this.roles || []).some((r) => r.equals(role));
|
||||
}
|
||||
}
|
||||
|
||||
3
server/src/contexts/sales/domain/entities/DealerRole.ts
Normal file
3
server/src/contexts/sales/domain/entities/DealerRole.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { AuthUserRole } from "@/contexts/auth/domain";
|
||||
|
||||
export class DealerRole extends AuthUserRole {}
|
||||
88
server/src/contexts/sales/domain/entities/DealerStatus.ts
Normal file
88
server/src/contexts/sales/domain/entities/DealerStatus.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import {
|
||||
DomainError,
|
||||
IValueObjectOptions,
|
||||
Result,
|
||||
RuleValidator,
|
||||
ValueObject,
|
||||
handleDomainError,
|
||||
} from "@shared/contexts";
|
||||
import Joi from "joi";
|
||||
|
||||
export enum DEALER_STATUS {
|
||||
ACTIVE = "active",
|
||||
BLOQUED = "bloqued",
|
||||
DISABLED = "disabled",
|
||||
}
|
||||
export interface IDealerStatusOptions extends IValueObjectOptions {}
|
||||
|
||||
export class DealerStatus extends ValueObject<string> {
|
||||
public static readonly DEALER_STATUS = DEALER_STATUS;
|
||||
|
||||
protected static validate(value: string, options: IValueObjectOptions) {
|
||||
const rule = Joi.string()
|
||||
.valid(DEALER_STATUS.ACTIVE, DEALER_STATUS.BLOQUED, DEALER_STATUS.DISABLED)
|
||||
.label(options.label ? options.label : "status");
|
||||
|
||||
return RuleValidator.validate<string>(rule, value);
|
||||
}
|
||||
|
||||
private static sanitize(status: string): string {
|
||||
return String(status).trim().toLowerCase();
|
||||
}
|
||||
|
||||
public static createActive(): DealerStatus {
|
||||
return new DealerStatus(DEALER_STATUS.ACTIVE);
|
||||
}
|
||||
|
||||
public static createBloqued(): DealerStatus {
|
||||
return new DealerStatus(DEALER_STATUS.BLOQUED);
|
||||
}
|
||||
|
||||
public static createDisabled(): DealerStatus {
|
||||
return new DealerStatus(DEALER_STATUS.DISABLED);
|
||||
}
|
||||
|
||||
public static create(status: string, options: IDealerStatusOptions = {}) {
|
||||
const _options = {
|
||||
label: "status",
|
||||
...options,
|
||||
};
|
||||
const validationResult = DealerStatus.validate(status, _options);
|
||||
|
||||
if (validationResult.isFailure) {
|
||||
return Result.fail(
|
||||
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(new DealerStatus(DealerStatus.sanitize(validationResult.object)));
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return String(this.props);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return String(this.props);
|
||||
}
|
||||
|
||||
public toPrimitive(): string {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
public isActive(): boolean {
|
||||
return this.equals(DealerStatus.createActive());
|
||||
}
|
||||
|
||||
public isBloqued(): boolean {
|
||||
return this.equals(DealerStatus.createBloqued());
|
||||
}
|
||||
|
||||
public isDisabled(): boolean {
|
||||
return this.equals(DealerStatus.createDisabled());
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return this.props === "undefined";
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./Dealer";
|
||||
export * from "./DealerStatus";
|
||||
export * from "./Quote";
|
||||
export * from "./QuoteItem";
|
||||
export * from "./QuoteStatus";
|
||||
|
||||
@ -3,10 +3,11 @@ import {
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { Name, UniqueID } from "@shared/contexts";
|
||||
import { Dealer, IDealerProps } from "../../domain/entities";
|
||||
import { KeyValueMap, Language, Name, UniqueID } from "@shared/contexts";
|
||||
import { Dealer, DealerStatus, IDealerProps } from "../../domain/entities";
|
||||
import { DealerRole } from "../../domain/entities/DealerRole";
|
||||
import { ISalesContext } from "../Sales.context";
|
||||
import { DEALER_STATUS, DealerCreationAttributes, Dealer_Model } from "../sequelize";
|
||||
import { DealerCreationAttributes, Dealer_Model } from "../sequelize";
|
||||
|
||||
export interface IDealerMapper
|
||||
extends ISequelizeMapper<Dealer_Model, DealerCreationAttributes, Dealer> {}
|
||||
@ -21,8 +22,19 @@ class DealerMapper
|
||||
|
||||
protected toDomainMappingImpl(source: Dealer_Model, params: any): Dealer {
|
||||
const props: IDealerProps = {
|
||||
name: this.mapsValue(source, "name", Name.create),
|
||||
user_id: this.mapsValue(source, "user_id", UniqueID.create),
|
||||
logo: "",
|
||||
name: this.mapsValue(source, "name", Name.create),
|
||||
status: this.mapsValue(source, "status", DealerStatus.create),
|
||||
roles: this.mapsValue(source, "roles", DealerRole.create),
|
||||
language: this.mapsValue(source, "language", Language.createFromCode),
|
||||
additionalInfo: KeyValueMap.create([
|
||||
["contact_information", source.contact_information],
|
||||
["default_payment_method", source.default_payment_method],
|
||||
["default_notes", source.default_notes],
|
||||
["default_legal_terms", source.default_legal_terms],
|
||||
["default_quote_validity", source.default_quote_validity],
|
||||
]).object,
|
||||
};
|
||||
|
||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||
@ -39,15 +51,16 @@ class DealerMapper
|
||||
return {
|
||||
id: source.id.toPrimitive(),
|
||||
user_id: source.user_id.toPrimitive(),
|
||||
contact_id: undefined,
|
||||
//contact_id: undefined,
|
||||
logo: "",
|
||||
name: source.name.toPrimitive(),
|
||||
contact_information: "",
|
||||
default_payment_method: "",
|
||||
default_notes: "",
|
||||
default_legal_terms: "",
|
||||
default_quote_validity: "",
|
||||
status: DEALER_STATUS.STATUS_ACTIVE,
|
||||
language: "",
|
||||
status: source.status.toPrimitive(),
|
||||
language: source.language.toPrimitive(),
|
||||
contact_information: source.additionalInfo.get("contact_information")?.toString() ?? "",
|
||||
default_payment_method: source.additionalInfo.get("default_payment_method")?.toString() ?? "",
|
||||
default_notes: source.additionalInfo.get("default_notes")?.toString() ?? "",
|
||||
default_legal_terms: source.additionalInfo.get("default_legal_terms")?.toString() ?? "",
|
||||
default_quote_validity: source.additionalInfo.get("default_quote_validity")?.toString() ?? "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,11 +10,6 @@ import {
|
||||
} from "sequelize";
|
||||
import { Quote_Model } from "./quote.model";
|
||||
|
||||
export enum DEALER_STATUS {
|
||||
STATUS_ACTIVE = "active",
|
||||
STATUS_BLOCKED = "blocked",
|
||||
}
|
||||
|
||||
export type DealerCreationAttributes = InferCreationAttributes<
|
||||
Dealer_Model,
|
||||
{ omit: "user" | "quotes" }
|
||||
@ -54,7 +49,7 @@ export class Dealer_Model extends Model<
|
||||
declare default_notes: string;
|
||||
declare default_legal_terms: string;
|
||||
declare default_quote_validity: string;
|
||||
declare status: DEALER_STATUS;
|
||||
declare status: string;
|
||||
declare language: string;
|
||||
|
||||
declare user?: NonAttribute<User_Model>;
|
||||
@ -87,7 +82,7 @@ export default (sequelize: Sequelize) => {
|
||||
language: DataTypes.STRING,
|
||||
|
||||
status: {
|
||||
type: DataTypes.ENUM(...Object.values(DEALER_STATUS)),
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { checkUser } from "@/contexts/auth";
|
||||
import { createGetProfileController } from "@/contexts/profile/infrastructure";
|
||||
import { createUpdateUserController } from "@/contexts/users";
|
||||
import Express from "express";
|
||||
|
||||
export const profileRouter = (appRouter: Express.Router) => {
|
||||
@ -17,7 +16,7 @@ export const profileRouter = (appRouter: Express.Router) => {
|
||||
"/",
|
||||
checkUser,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createUpdateUserController(res.locals["context"]).execute(req, res, next)
|
||||
createUpdateProfileController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
appRouter.use("/profile", profileRoutes);
|
||||
|
||||
35
shared/lib/contexts/common/domain/entities/KeyValueMap.ts
Normal file
35
shared/lib/contexts/common/domain/entities/KeyValueMap.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Result } from "./Result";
|
||||
import { Primitive, ValueObject } from "./ValueObject";
|
||||
|
||||
type KeyValueMapProps = Map<string, Primitive>;
|
||||
|
||||
export class KeyValueMap extends ValueObject<KeyValueMapProps> {
|
||||
public static create(entries?: [string, Primitive][]) {
|
||||
const map = new Map<string, Primitive>(entries);
|
||||
return Result.ok(new KeyValueMap(map));
|
||||
}
|
||||
|
||||
get(key: string): Primitive | undefined {
|
||||
return this.props.get(key);
|
||||
}
|
||||
|
||||
set(key: string, value: Primitive): KeyValueMap {
|
||||
const newMap = new Map(this.props);
|
||||
newMap.set(key, value);
|
||||
return new KeyValueMap(newMap);
|
||||
}
|
||||
|
||||
delete(key: string): KeyValueMap {
|
||||
const newMap = new Map(this.props);
|
||||
newMap.delete(key);
|
||||
return new KeyValueMap(newMap);
|
||||
}
|
||||
|
||||
public toPrimitive(): string {
|
||||
return JSON.stringify(Object.fromEntries(this.props.entries()));
|
||||
}
|
||||
|
||||
public entries(): [string, Primitive][] {
|
||||
return Array.from(this.props.entries());
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { shallowEqual } from "shallow-equal-object";
|
||||
|
||||
type Primitive = string | boolean | number;
|
||||
export type Primitive = string | boolean | number;
|
||||
|
||||
export interface IValueObjectOptions {
|
||||
label?: string;
|
||||
|
||||
@ -5,6 +5,7 @@ export * from "./Currency";
|
||||
export * from "./Description";
|
||||
export * from "./Email";
|
||||
export * from "./Entity";
|
||||
export * from "./KeyValueMap";
|
||||
export * from "./Language";
|
||||
export * from "./Measure";
|
||||
export * from "./MoneyValue";
|
||||
|
||||
@ -1,13 +1,8 @@
|
||||
export interface IGetProfileResponse_DTO {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
language: string;
|
||||
roles: string[];
|
||||
contact_information: string;
|
||||
default_payment_method: string;
|
||||
default_notes: string;
|
||||
default_legal_terms: string;
|
||||
default_quote_validity: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../../../common";
|
||||
|
||||
export interface IUpdateProfile_Request_DTO {
|
||||
contact_information: string;
|
||||
default_payment_method: string;
|
||||
default_notes: string;
|
||||
default_legal_terms: string;
|
||||
default_quote_validity: string;
|
||||
}
|
||||
|
||||
export function ensureUpdateProfile_Request_DTOIsValid(userDTO: IUpdateProfile_Request_DTO) {
|
||||
const schema = Joi.object({
|
||||
contact_information: Joi.string(),
|
||||
default_payment_method: Joi.string(),
|
||||
default_notes: Joi.string(),
|
||||
default_legal_terms: Joi.string(),
|
||||
default_quote_validity: Joi.string(),
|
||||
}).unknown(true);
|
||||
|
||||
const result = RuleValidator.validate<IUpdateProfile_Request_DTO>(schema, userDTO);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return Result.ok(true);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export interface IUpdateProfileResponse_DTO {
|
||||
id: string;
|
||||
contact_information: string;
|
||||
default_payment_method: string;
|
||||
default_notes: string;
|
||||
default_legal_terms: string;
|
||||
default_quote_validity: string;
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./IUpdateProfile_Request.dto";
|
||||
export * from "./IUpdateProfile_Response.dto";
|
||||
@ -1 +1,2 @@
|
||||
export * from "./GetProfile.dto";
|
||||
export * from "./UpdateProfile.dto";
|
||||
|
||||
@ -3,11 +3,23 @@ import { Result, RuleValidator } from "../../../../../common";
|
||||
|
||||
export interface IUpdateDealer_Request_DTO {
|
||||
name: string;
|
||||
language: string;
|
||||
contact_information: string;
|
||||
default_payment_method: string;
|
||||
default_notes: string;
|
||||
default_legal_terms: string;
|
||||
default_quote_validity: string;
|
||||
}
|
||||
|
||||
export function ensureUpdateDealer_Request_DTOIsValid(dealerDTO: IUpdateDealer_Request_DTO) {
|
||||
const schema = Joi.object({
|
||||
name: Joi.string(),
|
||||
language: Joi.string(),
|
||||
contact_information: Joi.string(),
|
||||
default_payment_method: Joi.string(),
|
||||
default_notes: Joi.string(),
|
||||
default_legal_terms: Joi.string(),
|
||||
default_quote_validity: Joi.string(),
|
||||
}).unknown(true);
|
||||
|
||||
const result = RuleValidator.validate<IUpdateDealer_Request_DTO>(schema, dealerDTO);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user