.
This commit is contained in:
parent
f39dbe95cc
commit
82fdc6de13
@ -1,5 +1,13 @@
|
||||
import { Outlet, RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||
import { DealerLayout, DealersList, LoginPage, LogoutPage, SettingsPage, StartPage } from "./app";
|
||||
import {
|
||||
DealerLayout,
|
||||
DealersList,
|
||||
LoginPage,
|
||||
LogoutPage,
|
||||
SettingsEditor,
|
||||
SettingsLayout,
|
||||
StartPage,
|
||||
} from "./app";
|
||||
import { CatalogLayout, CatalogList } from "./app/catalog";
|
||||
import { DashboardPage } from "./app/dashboard";
|
||||
import { QuotesList } from "./app/quotes/list";
|
||||
@ -68,9 +76,17 @@ export const Routes = () => {
|
||||
path: "/settings",
|
||||
element: (
|
||||
<ProtectedRoute>
|
||||
<SettingsPage />
|
||||
<SettingsLayout>
|
||||
<Outlet />
|
||||
</SettingsLayout>
|
||||
</ProtectedRoute>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <SettingsEditor />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/logout",
|
||||
|
||||
@ -15,7 +15,6 @@ import { useCatalogList } from "../hooks";
|
||||
export const CatalogDataTable = () => {
|
||||
const navigate = useNavigate();
|
||||
const { pagination, globalFilter, isFiltered } = useDataTableContext();
|
||||
console.log("pagination PADRE => ", pagination);
|
||||
|
||||
const { data, isPending, isError, error } = useCatalogList({
|
||||
pagination: {
|
||||
@ -27,12 +26,30 @@ export const CatalogDataTable = () => {
|
||||
|
||||
const columns = useMemo<ColumnDef<IListArticles_Response_DTO, any>[]>(
|
||||
() => [
|
||||
{
|
||||
id: "id" as const,
|
||||
accessorKey: "id",
|
||||
enableResizing: false,
|
||||
size: 10,
|
||||
},
|
||||
{
|
||||
id: "article_id" as const,
|
||||
accessorKey: "id_article",
|
||||
enableResizing: false,
|
||||
size: 10,
|
||||
},
|
||||
{
|
||||
id: "catalog_name" as const,
|
||||
accessorKey: "catalog_name",
|
||||
enableResizing: false,
|
||||
size: 10,
|
||||
},
|
||||
{
|
||||
id: "description" as const,
|
||||
accessorKey: "description",
|
||||
header: () => <>{t("catalog.list.columns.description")}</>,
|
||||
enableResizing: false,
|
||||
size: 300,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
id: "points" as const,
|
||||
|
||||
@ -16,7 +16,6 @@ export const CatalogLayout = ({ children }: PropsWithChildren) => {
|
||||
</div>
|
||||
{children}
|
||||
</LayoutContent>
|
||||
1
|
||||
</Layout>
|
||||
</CatalogProvider>
|
||||
);
|
||||
|
||||
72
client/src/app/settings/edit.tsx
Normal file
72
client/src/app/settings/edit.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
} from "@/ui";
|
||||
|
||||
import { Checkbox } from "@radix-ui/react-checkbox";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export const SettingsEditor = () => {
|
||||
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>
|
||||
);
|
||||
};
|
||||
@ -1,81 +1,2 @@
|
||||
import { Layout, LayoutHeader } from "@/components";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
} from "@/ui";
|
||||
|
||||
import { Checkbox } from "@radix-ui/react-checkbox";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export const SettingsPage = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<LayoutHeader />
|
||||
<main className='flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col gap-4 bg-muted/40 p-4 md:gap-8 md:p-10'>
|
||||
<div className='grid w-full max-w-6xl gap-2 mx-auto'>
|
||||
<h1 className='text-3xl font-semibold'>Settings</h1>
|
||||
</div>
|
||||
<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>
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
export * from "./edit";
|
||||
export * from "./layout";
|
||||
|
||||
19
client/src/app/settings/layout.tsx
Normal file
19
client/src/app/settings/layout.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { Layout, LayoutContent, LayoutHeader } from "@/components";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
export const SettingsLayout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<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.title' />
|
||||
</h1>
|
||||
</div>
|
||||
{children}
|
||||
</LayoutContent>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
import { DEFAULT_PAGE_SIZES, INITIAL_PAGE_INDEX } from "@/lib/hooks";
|
||||
import { DEFAULT_PAGE_SIZES } from "@/lib/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui";
|
||||
import { INITIAL_PAGE_INDEX } from "@shared/contexts";
|
||||
import { Table } from "@tanstack/react-table";
|
||||
import { t } from "i18next";
|
||||
import {
|
||||
|
||||
@ -9,7 +9,7 @@ import { UserButton } from "./components/UserButton";
|
||||
|
||||
export const LayoutHeader = () => {
|
||||
return (
|
||||
<header className='sticky top-0 flex items-center h-16 gap-8 px-4 border-b bg-background md:px-6'>
|
||||
<header className='sticky top-0 z-10 flex items-center h-16 gap-8 px-4 border-b bg-background md:px-6'>
|
||||
<nav className='flex-col hidden gap-6 text-lg font-medium md:flex md:flex-row md:items-center md:gap-5 md:text-sm lg:gap-6'>
|
||||
<Link to='/' className='flex items-center font-semibold'>
|
||||
<UeckoLogo className='w-24' />
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { IListResponse_DTO } from "@shared/contexts";
|
||||
import { IListResponse_DTO, INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@shared/contexts";
|
||||
import {
|
||||
ICreateOneDataProviderParams,
|
||||
IDataSource,
|
||||
@ -10,7 +10,6 @@ import {
|
||||
ISortItemDataProviderParam,
|
||||
IUpdateOneDataProviderParams,
|
||||
} from "../hooks/useDataSource/DataSource";
|
||||
import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "../hooks/usePagination";
|
||||
import { createAxiosInstance } from "./axiosInstance";
|
||||
|
||||
export const createAxiosDataProvider = (
|
||||
|
||||
@ -125,7 +125,7 @@ export function useDataTable<TData, TValue>({
|
||||
enableHiding = false,
|
||||
enableRowSelection = false,
|
||||
}: UseDataTableProps<TData, TValue>) {
|
||||
const { pagination, setPagination, sorting, setSorting } = useDataTableContext();
|
||||
const { pagination, setPagination, sorting } = useDataTableContext();
|
||||
|
||||
// Table states
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
@ -142,7 +142,7 @@ export function useDataTable<TData, TValue>({
|
||||
if (typeof updater === "function") {
|
||||
const newSorting = updater(sorting);
|
||||
console.log(newSorting);
|
||||
setSorting(newSorting);
|
||||
//setSorting(newSorting);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { act, renderHook } from "@testing-library/react-hooks";
|
||||
import {
|
||||
INITIAL_PAGE_INDEX,
|
||||
INITIAL_PAGE_SIZE,
|
||||
MAX_PAGE_SIZE,
|
||||
MIN_PAGE_SIZE,
|
||||
PaginationState,
|
||||
usePagination,
|
||||
} from "./usePagination";
|
||||
} from "@shared/contexts";
|
||||
import { act, renderHook } from "@testing-library/react-hooks";
|
||||
import { PaginationState, usePagination } from "./usePagination";
|
||||
|
||||
describe("usePagination", () => {
|
||||
it("should initialize with default values", () => {
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import {
|
||||
INITIAL_PAGE_INDEX,
|
||||
INITIAL_PAGE_SIZE,
|
||||
MAX_PAGE_SIZE,
|
||||
MIN_PAGE_SIZE,
|
||||
} from "@shared/contexts";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
export const INITIAL_PAGE_INDEX = 0;
|
||||
export const INITIAL_PAGE_SIZE = 10;
|
||||
|
||||
export const MIN_PAGE_INDEX = 0;
|
||||
export const MIN_PAGE_SIZE = 1;
|
||||
|
||||
export const MAX_PAGE_SIZE = 100; //Number.MAX_SAFE_INTEGER;
|
||||
|
||||
export const DEFAULT_PAGE_SIZES = [10, 25, 50, 100];
|
||||
export const DEFAULT_PAGE_SIZES = [15, 30, 50, 75, 100];
|
||||
|
||||
export interface PaginationState {
|
||||
pageIndex: number;
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { PaginationState } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import {
|
||||
INITIAL_PAGE_INDEX,
|
||||
INITIAL_PAGE_SIZE,
|
||||
MAX_PAGE_SIZE,
|
||||
MIN_PAGE_SIZE,
|
||||
usePagination,
|
||||
} from "./usePagination";
|
||||
} from "@shared/contexts";
|
||||
import { PaginationState } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { usePagination } from "./usePagination";
|
||||
|
||||
export const usePaginationParams = (
|
||||
initialPageIndex: number = INITIAL_PAGE_INDEX,
|
||||
|
||||
@ -62,6 +62,9 @@
|
||||
"retail_price": "PVP"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ajustes"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,9 @@
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": [
|
||||
"src",
|
||||
"../shared/lib/contexts/common/domain/entities/QueryCriteria/Pagination/defaults.ts"
|
||||
],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
@ -43,6 +43,13 @@ module.exports = {
|
||||
language: "es",
|
||||
},
|
||||
|
||||
sample_dealer: {
|
||||
name: "Roberto",
|
||||
email: "rblanco@rodax-software.com",
|
||||
password: "123456",
|
||||
language: "en",
|
||||
},
|
||||
|
||||
uploads: {
|
||||
imports: process.env.UPLOAD_PATH || "/home/rodax/Documentos/BBDD/server/uploads/imports",
|
||||
documents: process.env.UPLOAD_PATH || "/home/rodax/Documentos/BBDD/server/uploads/documents",
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
export * from "./controllers";
|
||||
export * from "./passport";
|
||||
export * from "./routes";
|
||||
|
||||
@ -1,21 +1,16 @@
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { composeMiddleware, generateExpressError } from "@/contexts/common/infrastructure/express";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { ensureIdIsValid } from "@shared/contexts";
|
||||
import Express from "express";
|
||||
import httpStatus from "http-status";
|
||||
import passport from "passport";
|
||||
|
||||
export const isUser = composeMiddleware([
|
||||
export const checkUser = composeMiddleware([
|
||||
passport.authenticate("local-jwt", {
|
||||
session: false,
|
||||
}),
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||
if (req.isAuthenticated()) {
|
||||
console.log(<AuthUser>req.user);
|
||||
/*const user = <AuthUser>req.user;
|
||||
if (!user.isUser) {
|
||||
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
}*/
|
||||
return next();
|
||||
}
|
||||
|
||||
@ -23,25 +18,34 @@ export const isUser = composeMiddleware([
|
||||
},
|
||||
]);
|
||||
|
||||
export const isAdmin = composeMiddleware([
|
||||
isUser,
|
||||
export const checkisAdmin = composeMiddleware([
|
||||
checkUser,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||
const user = <AuthUser>req.user;
|
||||
if (!user.isAdmin) {
|
||||
generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
}
|
||||
next();
|
||||
return next();
|
||||
},
|
||||
]);
|
||||
|
||||
export const isAdminOrMe = composeMiddleware([
|
||||
isUser,
|
||||
export const checkAdminOrSelf = composeMiddleware([
|
||||
checkUser,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||
const user = <AuthUser>req.user;
|
||||
const { userId } = req.params;
|
||||
|
||||
if (user.isAdmin || user.id.equals(UniqueID.create(userId).object)) {
|
||||
next();
|
||||
} else generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
if (user.isAdmin) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
const paramIdOrError = ensureIdIsValid(userId);
|
||||
if (paramIdOrError.isSuccess && user.id.equals(paramIdOrError.object)) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
},
|
||||
]);
|
||||
|
||||
@ -14,8 +14,6 @@ export interface IArticleProps {
|
||||
catalog_name: Slug;
|
||||
id_article: ArticleIdentifier;
|
||||
reference: Slug;
|
||||
//family: Description;
|
||||
//subfamily: Description;
|
||||
description: Description;
|
||||
points: Quantity;
|
||||
retail_price: UnitPrice;
|
||||
@ -26,8 +24,6 @@ export interface IArticle {
|
||||
catalog_name: Slug;
|
||||
id_article: ArticleIdentifier;
|
||||
reference: Slug;
|
||||
//family: Description;
|
||||
//subfamily: Description;
|
||||
description: Description;
|
||||
points: Quantity;
|
||||
retail_price: UnitPrice;
|
||||
@ -67,14 +63,6 @@ export class Article extends AggregateRoot<IArticleProps> implements IArticle {
|
||||
return this.props.reference;
|
||||
}
|
||||
|
||||
/*get family(): Description {
|
||||
return this.props.family;
|
||||
}
|
||||
|
||||
get subfamily(): Description {
|
||||
return this.props.subfamily;
|
||||
}*/
|
||||
|
||||
get description(): Description {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import Joi from "joi";
|
||||
|
||||
import {
|
||||
ListArticlesResult,
|
||||
ListArticlesUseCase,
|
||||
} from "@/contexts/catalog/application";
|
||||
import { ListArticlesResult, ListArticlesUseCase } from "@/contexts/catalog/application";
|
||||
import { Article } from "@/contexts/catalog/domain";
|
||||
import { QueryCriteriaService } from "@/contexts/common/application/services";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
@ -29,7 +26,7 @@ export class ListArticlesController extends ExpressController {
|
||||
useCase: ListArticlesUseCase;
|
||||
presenter: IListArticlesPresenter;
|
||||
},
|
||||
context: ICatalogContext,
|
||||
context: ICatalogContext
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -60,10 +57,7 @@ export class ListArticlesController extends ExpressController {
|
||||
const queryParams = queryOrError.object;
|
||||
|
||||
try {
|
||||
const queryCriteria: IQueryCriteria =
|
||||
QueryCriteriaService.parse(queryParams);
|
||||
|
||||
console.log(queryCriteria);
|
||||
const queryCriteria: IQueryCriteria = QueryCriteriaService.parse(queryParams);
|
||||
|
||||
const result: ListArticlesResult = await this.useCase.execute({
|
||||
queryCriteria,
|
||||
@ -79,7 +73,7 @@ export class ListArticlesController extends ExpressController {
|
||||
this.presenter.mapArray(customers, this.context, {
|
||||
page: queryCriteria.pagination.offset,
|
||||
limit: queryCriteria.pagination.limit,
|
||||
}),
|
||||
})
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
|
||||
@ -23,8 +23,6 @@ export const listArticlesPresenter: IListArticlesPresenter = {
|
||||
id_article: article.id_article.toString(),
|
||||
reference: article.reference.toString(),
|
||||
description: article.description.toString(),
|
||||
//family: article.family.toString(),
|
||||
//subfamily: article.subfamily.toString(),
|
||||
points: article.points.toNumber(),
|
||||
retail_price: article.retail_price.toObject(),
|
||||
};
|
||||
|
||||
@ -1,2 +1 @@
|
||||
export * from "./controllers";
|
||||
export * from "./routes";
|
||||
|
||||
@ -20,8 +20,6 @@ class ArticleMapper
|
||||
catalog_name: this.mapsValue(source, "catalog_name", Slug.create),
|
||||
id_article: this.mapsValue(source, "id_article", ArticleIdentifier.create),
|
||||
reference: this.mapsValue(source, "reference", Slug.create),
|
||||
//family: this.mapsValue(source, "family", Description.create),
|
||||
//subfamily: this.mapsValue(source, "subfamily", Description.create),
|
||||
description: this.mapsValue(source, "description", Description.create),
|
||||
points: this.mapsValue(source, "points", Quantity.create),
|
||||
retail_price: this.mapsValue(source, "retail_price", (value: any) =>
|
||||
|
||||
@ -26,8 +26,6 @@ export class Article_Model extends Model<
|
||||
declare catalog_name: string;
|
||||
declare id_article: string; // number ??
|
||||
declare reference: CreationOptional<string>;
|
||||
//declare family: CreationOptional<string>;
|
||||
//declare subfamily: CreationOptional<string>;
|
||||
declare description: CreationOptional<string>;
|
||||
declare points: CreationOptional<number>;
|
||||
declare retail_price: CreationOptional<number>;
|
||||
@ -50,8 +48,6 @@ export default (sequelize: Sequelize) => {
|
||||
allowNull: false,
|
||||
},
|
||||
reference: DataTypes.STRING(),
|
||||
//family: DataTypes.STRING(),
|
||||
//subfamily: DataTypes.STRING(),
|
||||
description: DataTypes.STRING(),
|
||||
points: {
|
||||
type: DataTypes.SMALLINT().UNSIGNED,
|
||||
@ -74,8 +70,6 @@ export default (sequelize: Sequelize) => {
|
||||
indexes: [
|
||||
{ name: "catalog_name_idx", fields: ["catalog_name"] },
|
||||
{ name: "id_article_idx", fields: ["id_article"] },
|
||||
//{ name: "family_idx", fields: ["family"] },
|
||||
//{ name: "family_subfamily_idx", fields: ["family", "subfamily"] },
|
||||
{ name: "updated_at_idx", fields: ["updated_at"] },
|
||||
],
|
||||
|
||||
@ -88,12 +82,6 @@ export default (sequelize: Sequelize) => {
|
||||
reference: {
|
||||
[Op.like]: `%${value}%`,
|
||||
},
|
||||
/*family: {
|
||||
[Op.like]: `%${value}%`,
|
||||
},
|
||||
subfamily: {
|
||||
[Op.like]: `%${value}%`,
|
||||
},*/
|
||||
description: {
|
||||
[Op.like]: `%${value}%`,
|
||||
},
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
import { IRepositoryManager, RepositoryManager } from "@/contexts/common/domain";
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
createSequelizeAdapter,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
|
||||
export interface IProfileContext {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
}
|
||||
|
||||
export class ProfileContext {
|
||||
private static instance: ProfileContext | null = null;
|
||||
|
||||
public static getInstance(): IProfileContext {
|
||||
if (!ProfileContext.instance) {
|
||||
ProfileContext.instance = new ProfileContext({
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
});
|
||||
}
|
||||
|
||||
return ProfileContext.instance.context;
|
||||
}
|
||||
|
||||
private context: IProfileContext;
|
||||
|
||||
private constructor(context: IProfileContext) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
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 { 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 { IProfileContext } from "../../../Profile.context";
|
||||
import { IGetProfilePresenter } from "./presenter";
|
||||
|
||||
export class GetProfileController extends ExpressController {
|
||||
private useCase: GetDealerByUserUseCase;
|
||||
private presenter: IGetProfilePresenter;
|
||||
private context: IProfileContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: GetDealerByUserUseCase;
|
||||
presenter: IGetProfilePresenter;
|
||||
},
|
||||
context: IProfileContext
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl(): Promise<any> {
|
||||
const user = <User | undefined>this.req.user;
|
||||
|
||||
if (!user) {
|
||||
const errorMessage = "Unexpected missing user data";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage
|
||||
);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.useCase.execute({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
const dealer = <Dealer>result.object;
|
||||
|
||||
return this.ok<IGetUserResponse_DTO>(this.presenter.map(user, dealer, 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 = "User has no associated profile";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.RESOURCE_NOT_READY,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
|
||||
return this.notFoundError(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 { GetDealerByUserUseCase } from "@/contexts/sales/application";
|
||||
import { registerDealerRepository } from "@/contexts/sales/infrastructure/Dealer.repository";
|
||||
import { IProfileContext } from "../../../Profile.context";
|
||||
import { GetProfileController } from "./GetProfile.controller";
|
||||
import { GetUserPresenter } from "./presenter";
|
||||
|
||||
export const createGetProfileController = (context: IProfileContext) => {
|
||||
registerDealerRepository(context);
|
||||
|
||||
return new GetProfileController(
|
||||
{
|
||||
useCase: new GetDealerByUserUseCase(context),
|
||||
presenter: GetUserPresenter,
|
||||
},
|
||||
context
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
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",
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./GetUser.presenter";
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./getProfile";
|
||||
//export * from "./updateProfile";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./controllers";
|
||||
2
server/src/contexts/profile/infrastructure/index.ts
Normal file
2
server/src/contexts/profile/infrastructure/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./Profile.context";
|
||||
export * from "./express";
|
||||
@ -2,11 +2,13 @@ import { AggregateRoot, IDomainError, Name, Result, UniqueID } from "@shared/con
|
||||
|
||||
export interface IDealerProps {
|
||||
name: Name;
|
||||
user_id: UniqueID;
|
||||
}
|
||||
|
||||
export interface IDealer {
|
||||
id: UniqueID;
|
||||
name: Name;
|
||||
user_id: UniqueID;
|
||||
}
|
||||
|
||||
export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
|
||||
@ -18,4 +20,8 @@ export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
|
||||
get name(): Name {
|
||||
return this.props.name;
|
||||
}
|
||||
|
||||
get user_id(): UniqueID {
|
||||
return this.props.user_id;
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,20 +53,7 @@ export class DealerRepository extends SequelizeRepository<Dealer> implements IDe
|
||||
}
|
||||
|
||||
public async getByUserId(userId: UniqueID): Promise<Dealer | null> {
|
||||
const _dealer_model = this._adapter.getModel("Dealer_Model");
|
||||
const _user_model = this._adapter.getModel("User_Model");
|
||||
|
||||
const rawDealer: any = await _dealer_model.findOne({
|
||||
include: [
|
||||
{
|
||||
attributes: [],
|
||||
model: _user_model,
|
||||
as: "users",
|
||||
required: true,
|
||||
where: { id: userId.toPrimitive() },
|
||||
},
|
||||
],
|
||||
});
|
||||
const rawDealer: any = await this._getBy("Dealer_Model", "user_id", userId.toPrimitive());
|
||||
|
||||
if (!rawDealer === true) {
|
||||
return null;
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./dealers";
|
||||
export * from "./quotes";
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
import { isAdmin, isUser } from "@/contexts/auth";
|
||||
import Express from "express";
|
||||
import {
|
||||
createDealerController,
|
||||
deleteDealerController,
|
||||
getDealerController,
|
||||
updateDealerController,
|
||||
} from "./controllers/dealers";
|
||||
import { listDealersController } from "./controllers/dealers/listDealers";
|
||||
import { getDealerMiddleware } from "./middlewares/dealerMiddleware";
|
||||
import { quoteRoutes } from "./quote.routes";
|
||||
|
||||
export const DealerRouter = (appRouter: Express.Router) => {
|
||||
const dealerRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
dealerRoutes.get("/", isAdmin, listDealersController);
|
||||
dealerRoutes.get("/:dealerId", isUser, getDealerMiddleware, getDealerController);
|
||||
dealerRoutes.post("/", isAdmin, createDealerController);
|
||||
dealerRoutes.put("/:dealerId", isAdmin, updateDealerController);
|
||||
dealerRoutes.delete("/:dealerId", isAdmin, deleteDealerController);
|
||||
|
||||
// Anidar quotes en /dealers/:dealerId
|
||||
dealerRoutes.use("/:dealerId/quotes", quoteRoutes);
|
||||
|
||||
appRouter.use("/dealers", dealerRoutes);
|
||||
};
|
||||
@ -1 +1 @@
|
||||
export * from "./routes";
|
||||
export * from "../../../../infrastructure/express/api/routes/sales.routes";
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./Sales.context";
|
||||
export * from "./express";
|
||||
|
||||
@ -22,6 +22,7 @@ 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),
|
||||
};
|
||||
|
||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||
@ -37,7 +38,8 @@ class DealerMapper
|
||||
protected toPersistenceMappingImpl(source: Dealer, params?: MapperParamsType | undefined) {
|
||||
return {
|
||||
id: source.id.toPrimitive(),
|
||||
id_contact: undefined,
|
||||
user_id: source.user_id.toPrimitive(),
|
||||
contact_id: undefined,
|
||||
name: source.name.toPrimitive(),
|
||||
contact_information: "",
|
||||
default_payment_method: "",
|
||||
@ -45,6 +47,7 @@ class DealerMapper
|
||||
default_legal_terms: "",
|
||||
default_quote_validity: "",
|
||||
status: DEALER_STATUS.STATUS_ACTIVE,
|
||||
language: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { User_Model } from "@/contexts/users";
|
||||
import {
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Op,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
@ -13,11 +15,14 @@ export enum DEALER_STATUS {
|
||||
STATUS_BLOCKED = "blocked",
|
||||
}
|
||||
|
||||
export type DealerCreationAttributes = InferCreationAttributes<Dealer_Model>;
|
||||
export type DealerCreationAttributes = InferCreationAttributes<
|
||||
Dealer_Model,
|
||||
{ omit: "user" | "quotes" }
|
||||
>;
|
||||
|
||||
export class Dealer_Model extends Model<
|
||||
InferAttributes<Dealer_Model>,
|
||||
InferCreationAttributes<Dealer_Model>
|
||||
InferAttributes<Dealer_Model, { omit: "user" | "quotes" }>,
|
||||
InferCreationAttributes<Dealer_Model, { omit: "user" | "quotes" }>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
@ -28,9 +33,9 @@ export class Dealer_Model extends Model<
|
||||
static associate(connection: Sequelize) {
|
||||
const { Dealer_Model, User_Model } = connection.models;
|
||||
|
||||
Dealer_Model.hasMany(User_Model, {
|
||||
as: "users",
|
||||
foreignKey: "dealer_id",
|
||||
Dealer_Model.belongsTo(User_Model, {
|
||||
as: "user",
|
||||
foreignKey: "user_id",
|
||||
onDelete: "RESTRICT",
|
||||
});
|
||||
|
||||
@ -42,7 +47,7 @@ export class Dealer_Model extends Model<
|
||||
}
|
||||
|
||||
declare id: string;
|
||||
declare id_contact?: string; // number ??
|
||||
declare contact_id?: string; // number ??
|
||||
declare name: string;
|
||||
declare contact_information: string;
|
||||
declare default_payment_method: string;
|
||||
@ -51,6 +56,9 @@ export class Dealer_Model extends Model<
|
||||
declare default_quote_validity: string;
|
||||
declare status: DEALER_STATUS;
|
||||
declare language: string;
|
||||
|
||||
declare user?: NonAttribute<User_Model>;
|
||||
declare quotes?: NonAttribute<Quote_Model>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
@ -61,7 +69,7 @@ export default (sequelize: Sequelize) => {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
id_contact: {
|
||||
contact_id: {
|
||||
type: DataTypes.BIGINT().UNSIGNED,
|
||||
allowNull: true,
|
||||
},
|
||||
@ -96,7 +104,7 @@ export default (sequelize: Sequelize) => {
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [
|
||||
{ name: "id_contact_idx", fields: ["id_contact"] },
|
||||
{ name: "contact_id_idx", fields: ["contact_id"] },
|
||||
{ name: "status_idx", fields: ["status"] },
|
||||
],
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { config } from "@/config";
|
||||
import { IAdapter, Password, RepositoryBuilder } from "@/contexts/common/domain";
|
||||
import { Dealer, IDealerRepository } from "@/contexts/sales/domain";
|
||||
import { Email, Language, Name, UniqueID } from "@shared/contexts";
|
||||
import { IUserRepository, User, UserRole } from "../domain";
|
||||
|
||||
@ -76,3 +77,51 @@ export const initializeAdmin = async (
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const initializeSampleUser = async (
|
||||
adapter: IAdapter,
|
||||
repository: RepositoryBuilder<IUserRepository>
|
||||
) => {
|
||||
return await adapter.startTransaction().complete(async (t) => {
|
||||
const email = Email.create(config.sample_dealer.email).object;
|
||||
const userExists = await repository({ transaction: t }).existsUserWithEmail(email);
|
||||
|
||||
if (!userExists) {
|
||||
const user = User.create(
|
||||
{
|
||||
name: Name.create(config.sample_dealer.name).object,
|
||||
email,
|
||||
password: Password.createFromPlainTextPassword(config.sample_dealer.password).object,
|
||||
language: Language.createFromCode(config.sample_dealer.language).object,
|
||||
roles: [UserRole.ROLE_USER],
|
||||
},
|
||||
UniqueID.generateNewID().object
|
||||
).object;
|
||||
await repository({ transaction: t }).create(user);
|
||||
console.log("Usuario creado");
|
||||
return user;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const initializeSampleDealer = async (
|
||||
user: User,
|
||||
adapter: IAdapter,
|
||||
repository: RepositoryBuilder<IDealerRepository>
|
||||
) => {
|
||||
return await adapter.startTransaction().complete(async (t) => {
|
||||
const dealerExists = await repository({ transaction: t }).getByUserId(user.id);
|
||||
|
||||
if (!dealerExists) {
|
||||
const dealer = Dealer.create(
|
||||
{
|
||||
name: Name.create(config.sample_dealer.name).object,
|
||||
user_id: user.id,
|
||||
},
|
||||
UniqueID.generateNewID().object
|
||||
).object;
|
||||
await repository({ transaction: t }).create(dealer);
|
||||
console.log("Dealer creado");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "../../../../infrastructure/express/api/routes/users.routes";
|
||||
export * from "./controllers";
|
||||
export * from "./routes";
|
||||
|
||||
@ -27,9 +27,9 @@ export class User_Model extends Model<
|
||||
static associate(connection: Sequelize) {
|
||||
const { User_Model, Dealer_Model } = connection.models;
|
||||
|
||||
User_Model.belongsTo(Dealer_Model, {
|
||||
User_Model.hasOne(Dealer_Model, {
|
||||
as: "dealer",
|
||||
foreignKey: "dealer_id",
|
||||
foreignKey: "user_id",
|
||||
onDelete: "RESTRICT",
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import Express from "express";
|
||||
import passport from "passport";
|
||||
import { createLoginController } from "./controllers";
|
||||
import { createIdentityController } from "./controllers/identity";
|
||||
import { isUser } from "./passport";
|
||||
import { createLoginController } from "../../../../contexts/auth/infrastructure/express/controllers";
|
||||
import { createIdentityController } from "../../../../contexts/auth/infrastructure/express/controllers/identity";
|
||||
import { checkUser } from "../../../../contexts/auth/infrastructure/express/passport";
|
||||
|
||||
export const AuthRouter = (appRouter: Express.Router) => {
|
||||
export const authRouter = (appRouter: Express.Router) => {
|
||||
const authRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
//appRouter.use(registerMiddleware("isUser", isUser));
|
||||
@ -17,16 +17,20 @@ export const AuthRouter = (appRouter: Express.Router) => {
|
||||
createLoginController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
authRoutes.post("/logout", isUser, (req: Express.Request, res: Express.Response) => {
|
||||
//req.logout(); <-- ??
|
||||
return res.status(200).json();
|
||||
authRoutes.post("/logout", checkUser, (req: Express.Request, res: Express.Response) => {
|
||||
req.logout(function (err) {
|
||||
if (err) {
|
||||
return res.status(500).json();
|
||||
}
|
||||
return res.status(200).json();
|
||||
});
|
||||
});
|
||||
|
||||
authRoutes.post("/register");
|
||||
|
||||
authRoutes.get(
|
||||
"/identity",
|
||||
isUser,
|
||||
checkUser,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createIdentityController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
@ -1,19 +1,13 @@
|
||||
import { isUser } from "@/contexts/auth";
|
||||
import { checkUser } from "@/contexts/auth";
|
||||
import Express from "express";
|
||||
import { listArticlesController } from "./controllers";
|
||||
import { listArticlesController } from "../../../../contexts/catalog/infrastructure/express/controllers";
|
||||
|
||||
/*catalogRoutes.get(
|
||||
"/:articleId",
|
||||
(req: Request, res: Response, next: NextFunction) =>
|
||||
createGetCustomerController(res.locals["context"]).execute(req, res, next)
|
||||
);*/
|
||||
|
||||
export const CatalogRouter = (appRouter: Express.Router) => {
|
||||
export const catalogRouter = (appRouter: Express.Router) => {
|
||||
const catalogRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
catalogRoutes.get(
|
||||
"/",
|
||||
isUser,
|
||||
checkUser,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
listArticlesController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
@ -0,0 +1,27 @@
|
||||
import { checkUser, checkisAdmin } from "@/contexts/auth";
|
||||
import {
|
||||
createDealerController,
|
||||
deleteDealerController,
|
||||
getDealerController,
|
||||
listDealersController,
|
||||
updateDealerController,
|
||||
} from "@/contexts/sales/infrastructure/express/controllers/dealers";
|
||||
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/dealerMiddleware";
|
||||
import Express from "express";
|
||||
import { quoteRoutes } from "./quote.routes";
|
||||
5;
|
||||
|
||||
export const DealerRouter = (appRouter: Express.Router) => {
|
||||
const dealerRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
dealerRoutes.get("/", checkisAdmin, listDealersController);
|
||||
dealerRoutes.get("/:dealerId", checkUser, getDealerMiddleware, getDealerController);
|
||||
dealerRoutes.post("/", checkisAdmin, createDealerController);
|
||||
dealerRoutes.put("/:dealerId", checkisAdmin, updateDealerController);
|
||||
dealerRoutes.delete("/:dealerId", checkisAdmin, deleteDealerController);
|
||||
|
||||
// Anidar quotes en /dealers/:dealerId
|
||||
dealerRoutes.use("/:dealerId/quotes", quoteRoutes);
|
||||
|
||||
appRouter.use("/dealers", dealerRoutes);
|
||||
};
|
||||
7
server/src/infrastructure/express/api/routes/index.ts
Normal file
7
server/src/infrastructure/express/api/routes/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export * from "./auth.routes";
|
||||
export * from "./catalog.routes";
|
||||
export * from "./dealers.routes";
|
||||
export * from "./profile.routes";
|
||||
export * from "./quote.routes";
|
||||
export * from "./sales.routes";
|
||||
export * from "./users.routes";
|
||||
@ -0,0 +1,24 @@
|
||||
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) => {
|
||||
const profileRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
profileRoutes.get(
|
||||
"/",
|
||||
checkUser,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createGetProfileController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
profileRoutes.put(
|
||||
"/",
|
||||
checkUser,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createUpdateUserController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
appRouter.use("/profile", profileRoutes);
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import { isAdmin } from "@/contexts/auth";
|
||||
import { checkisAdmin } from "@/contexts/auth";
|
||||
import Express from "express";
|
||||
|
||||
export const quoteRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
@ -9,7 +9,7 @@ quoteRoutes.post("/", isAdmin, createQuoteController);
|
||||
quoteRoutes.put("/:quoteId", isAdmin, updateQuoteController);
|
||||
quoteRoutes.delete("/:quoteId", isAdmin, deleteQuoteController);*/
|
||||
|
||||
quoteRoutes.get("/", isAdmin, (req, res) => {
|
||||
quoteRoutes.get("/", checkisAdmin, (req, res) => {
|
||||
console.log(req.params);
|
||||
res.status(200).json();
|
||||
});
|
||||
@ -2,7 +2,7 @@ import Express from "express";
|
||||
import { DealerRouter } from "./dealers.routes";
|
||||
import { QuoteRouter } from "./quote.routes";
|
||||
|
||||
export const SalesRouter = (appRouter: Express.Router) => {
|
||||
export const salesRouter = (appRouter: Express.Router) => {
|
||||
DealerRouter(appRouter);
|
||||
QuoteRouter(appRouter);
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import { isAdmin, isAdminOrMe } from "@/contexts/auth";
|
||||
import { checkAdminOrSelf, checkisAdmin } from "@/contexts/auth";
|
||||
import Express from "express";
|
||||
import {
|
||||
createCreateUserController,
|
||||
@ -6,42 +6,42 @@ import {
|
||||
createGetUserController,
|
||||
createListUsersController,
|
||||
createUpdateUserController,
|
||||
} from "./controllers";
|
||||
} from "../../../../contexts/users/infrastructure/express/controllers";
|
||||
|
||||
export const UserRouter = (appRouter: Express.Router) => {
|
||||
export const usersRouter = (appRouter: Express.Router) => {
|
||||
const userRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
userRoutes.get(
|
||||
"/",
|
||||
isAdmin,
|
||||
checkisAdmin,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createListUsersController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
userRoutes.get(
|
||||
"/:userId",
|
||||
isAdminOrMe,
|
||||
checkAdminOrSelf,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createGetUserController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
userRoutes.post(
|
||||
"/",
|
||||
isAdmin,
|
||||
checkisAdmin,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createCreateUserController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
userRoutes.put(
|
||||
"/:userId",
|
||||
isAdmin,
|
||||
checkisAdmin,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createUpdateUserController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
userRoutes.delete(
|
||||
"/:userId",
|
||||
isAdmin,
|
||||
checkisAdmin,
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createDeleteUserController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
@ -1,9 +1,7 @@
|
||||
import { AuthRouter } from "@/contexts/auth";
|
||||
import { CatalogRouter } from "@/contexts/catalog";
|
||||
import { SalesRouter } from "@/contexts/sales/infrastructure/express";
|
||||
import { UserRouter } from "@/contexts/users";
|
||||
import { salesRouter } from "@/contexts/sales/infrastructure/express";
|
||||
import Express from "express";
|
||||
import { createContextMiddleware } from "./context.middleware";
|
||||
import { authRouter, catalogRouter, profileRouter, usersRouter } from "./routes";
|
||||
|
||||
export const v1Routes = () => {
|
||||
const routes = Express.Router({ mergeParams: true });
|
||||
@ -23,10 +21,83 @@ export const v1Routes = () => {
|
||||
next();
|
||||
});
|
||||
|
||||
AuthRouter(routes);
|
||||
UserRouter(routes);
|
||||
CatalogRouter(routes);
|
||||
SalesRouter(routes);
|
||||
authRouter(routes);
|
||||
profileRouter(routes);
|
||||
usersRouter(routes);
|
||||
catalogRouter(routes);
|
||||
salesRouter(routes);
|
||||
|
||||
return routes;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
Comentarios: Cada usuario que no sea administrador tiene un registro asociado en "dealers" a modo de perfil
|
||||
|
||||
* Endpoints Públicos
|
||||
/auth
|
||||
|
||||
POST /auth/login: Permite a los usuarios autenticarse y recibir un token JWT.
|
||||
|
||||
* Endpoints para Usuarios Registrados
|
||||
/auth
|
||||
|
||||
POST /auth/logout: Permite a los usuarios cerrar sesión (podría implicar invalidar el token JWT).
|
||||
GET /auth/identity: Devuelve la identidad del usuario autenticado, incluyendo id, name, email, language y roles.
|
||||
|
||||
/profile
|
||||
|
||||
GET /profile: Obtiene el perfil del usuario autenticado.
|
||||
POST /profile: Actualiza el perfil del usuario autenticado.
|
||||
|
||||
/catalog
|
||||
|
||||
GET /catalog: Devuelve la lista de artículos del catálogo.
|
||||
|
||||
/quotes
|
||||
|
||||
GET /quotes: Devuelve las cotizaciones del usuario autenticado.
|
||||
POST /quotes: Permite al usuario crear una nueva cotización.
|
||||
PUT /quotes/
|
||||
: Permite al usuario actualizar una cotización existente.
|
||||
DELETE /quotes/
|
||||
: Permite al usuario eliminar una cotización existente.
|
||||
|
||||
* Endpoints para Administradores
|
||||
/auth
|
||||
|
||||
POST /auth/register: Permite al administrador registrar nuevos usuarios.
|
||||
|
||||
/users
|
||||
|
||||
GET /users: Devuelve la lista de todos los usuarios.
|
||||
POST /users: Permite crear un nuevo usuario.
|
||||
GET /users/
|
||||
: Devuelve los detalles de un usuario específico.
|
||||
PUT /users/
|
||||
: Permite actualizar los detalles de un usuario específico.
|
||||
DELETE /users/
|
||||
: Permite eliminar un usuario específico.
|
||||
|
||||
/dealers
|
||||
|
||||
GET /dealers: Devuelve la lista de todos los distribuidores.
|
||||
POST /dealers: Permite crear un nuevo distribuidor.
|
||||
GET /dealers/
|
||||
: Devuelve los detalles de un distribuidor específico.
|
||||
PUT /dealers/
|
||||
: Permite actualizar los detalles de un distribuidor específico.
|
||||
DELETE /dealers/
|
||||
: Permite eliminar un distribuidor específico.
|
||||
|
||||
/quotes
|
||||
|
||||
GET /quotes: Devuelve la lista de todas las cotizaciones.
|
||||
POST /quotes: Permite crear una nueva cotización.
|
||||
PUT /quotes/
|
||||
: Permite actualizar una cotización existente.
|
||||
DELETE /quotes/
|
||||
: Permite eliminar una cotización existente.
|
||||
|
||||
*/
|
||||
|
||||
@ -9,7 +9,7 @@ import { trace } from "console";
|
||||
import { config } from "../../config";
|
||||
import { app } from "../express/app";
|
||||
import { initLogger } from "../logger";
|
||||
import { initializeAdminUser } from "../sequelize/initializeAdminUser";
|
||||
import { insertUsers } from "../sequelize/initData";
|
||||
|
||||
process.env.TZ = "UTC";
|
||||
Settings.defaultLocale = "es-ES";
|
||||
@ -109,7 +109,7 @@ try {
|
||||
sequelizeConn.sync({ force: false, alter: true }).then(() => {
|
||||
//
|
||||
|
||||
initializeAdminUser();
|
||||
insertUsers();
|
||||
|
||||
// Launch server
|
||||
server.listen(currentState.server.port, () => {
|
||||
|
||||
28
server/src/infrastructure/sequelize/initData.ts
Normal file
28
server/src/infrastructure/sequelize/initData.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { registerDealerRepository } from "@/contexts/sales/infrastructure/Dealer.repository";
|
||||
import {
|
||||
initializeAdmin,
|
||||
initializeSampleDealer,
|
||||
initializeSampleUser,
|
||||
} from "@/contexts/users/application/userServices";
|
||||
import { UserContext } from "@/contexts/users/infrastructure/User.context";
|
||||
import { registerUserRepository } from "@/contexts/users/infrastructure/User.repository";
|
||||
|
||||
export const insertUsers = async () => {
|
||||
const context = UserContext.getInstance();
|
||||
registerUserRepository(context);
|
||||
registerDealerRepository(context);
|
||||
|
||||
initializeAdmin(context.adapter, context.repositoryManager.getRepository("User"));
|
||||
const user = await initializeSampleUser(
|
||||
context.adapter,
|
||||
context.repositoryManager.getRepository("User")
|
||||
);
|
||||
|
||||
if (user) {
|
||||
initializeSampleDealer(
|
||||
user,
|
||||
context.adapter,
|
||||
context.repositoryManager.getRepository("Dealer")
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
import { initializeAdmin } from "@/contexts/users/application/userServices";
|
||||
import { UserContext } from "@/contexts/users/infrastructure/User.context";
|
||||
import { registerUserRepository } from "@/contexts/users/infrastructure/User.repository";
|
||||
|
||||
export const initializeAdminUser = () => {
|
||||
const context = UserContext.getInstance();
|
||||
registerUserRepository(context);
|
||||
|
||||
initializeAdmin(context.adapter, context.repositoryManager.getRepository("User"));
|
||||
};
|
||||
@ -2,6 +2,13 @@ import Joi from "joi";
|
||||
import { RuleValidator } from "../../../RuleValidator";
|
||||
import { Result } from "../../Result";
|
||||
import { ValueObject } from "../../ValueObject";
|
||||
import {
|
||||
INITIAL_PAGE_INDEX,
|
||||
INITIAL_PAGE_SIZE,
|
||||
MAX_PAGE_SIZE,
|
||||
MIN_PAGE_INDEX,
|
||||
MIN_PAGE_SIZE,
|
||||
} from "./defaults";
|
||||
|
||||
export interface IOffsetPagingProps {
|
||||
offset: number | string | undefined;
|
||||
@ -14,12 +21,12 @@ export interface IOffsetPaging {
|
||||
}
|
||||
|
||||
export class OffsetPaging extends ValueObject<IOffsetPaging> {
|
||||
public static readonly LIMIT_DEFAULT_VALUE: number = 10;
|
||||
public static readonly LIMIT_MINIMAL_VALUE: number = 1;
|
||||
public static readonly LIMIT_MAXIMAL_VALUE: number = 100;
|
||||
public static readonly LIMIT_DEFAULT_VALUE: number = INITIAL_PAGE_SIZE;
|
||||
public static readonly LIMIT_MINIMAL_VALUE: number = MIN_PAGE_SIZE;
|
||||
public static readonly LIMIT_MAXIMAL_VALUE: number = MAX_PAGE_SIZE;
|
||||
|
||||
public static readonly OFFSET_DEFAULT_VALUE: number = 0;
|
||||
public static readonly OFFSET_MINIMAL_VALUE: number = 0;
|
||||
public static readonly OFFSET_DEFAULT_VALUE: number = INITIAL_PAGE_INDEX;
|
||||
public static readonly OFFSET_MINIMAL_VALUE: number = MIN_PAGE_INDEX;
|
||||
public static readonly OFFSET_MAXIMAL_VALUE: number = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
public static createWithMaxLimit(): Result<OffsetPaging> {
|
||||
@ -52,10 +59,7 @@ export class OffsetPaging extends ValueObject<IOffsetPaging> {
|
||||
}
|
||||
|
||||
private static validate(offset: string | number, limit: string | number) {
|
||||
const numberOrError = RuleValidator.validate(
|
||||
RuleValidator.RULE_IS_TYPE_NUMBER,
|
||||
offset,
|
||||
);
|
||||
const numberOrError = RuleValidator.validate(RuleValidator.RULE_IS_TYPE_NUMBER, offset);
|
||||
|
||||
if (numberOrError.isFailure) {
|
||||
return numberOrError;
|
||||
@ -64,25 +68,18 @@ export class OffsetPaging extends ValueObject<IOffsetPaging> {
|
||||
const _offset = typeof offset === "string" ? parseInt(offset, 10) : offset;
|
||||
|
||||
const offsetValidate = RuleValidator.validate(
|
||||
Joi.number()
|
||||
.min(OffsetPaging.OFFSET_MINIMAL_VALUE)
|
||||
.max(OffsetPaging.OFFSET_MAXIMAL_VALUE),
|
||||
offset,
|
||||
Joi.number().min(OffsetPaging.OFFSET_MINIMAL_VALUE).max(OffsetPaging.OFFSET_MAXIMAL_VALUE),
|
||||
offset
|
||||
);
|
||||
|
||||
if (offsetValidate.isFailure) {
|
||||
return Result.fail(
|
||||
new Error(
|
||||
`Page need to be larger than or equal to ${OffsetPaging.OFFSET_MINIMAL_VALUE}.`,
|
||||
),
|
||||
new Error(`Page need to be larger than or equal to ${OffsetPaging.OFFSET_MINIMAL_VALUE}.`)
|
||||
);
|
||||
}
|
||||
|
||||
// limit
|
||||
const limitNumberOrError = RuleValidator.validate(
|
||||
RuleValidator.RULE_IS_TYPE_NUMBER,
|
||||
limit,
|
||||
);
|
||||
const limitNumberOrError = RuleValidator.validate(RuleValidator.RULE_IS_TYPE_NUMBER, limit);
|
||||
|
||||
if (limitNumberOrError.isFailure) {
|
||||
return limitNumberOrError;
|
||||
@ -92,14 +89,12 @@ export class OffsetPaging extends ValueObject<IOffsetPaging> {
|
||||
|
||||
const limitValidate = RuleValidator.validate(
|
||||
Joi.number().min(0).max(OffsetPaging.LIMIT_MAXIMAL_VALUE),
|
||||
offset,
|
||||
offset
|
||||
);
|
||||
|
||||
if (limitValidate.isFailure) {
|
||||
return Result.fail(
|
||||
new Error(
|
||||
`Page size need to be smaller than ${OffsetPaging.LIMIT_MAXIMAL_VALUE}`,
|
||||
),
|
||||
new Error(`Page size need to be smaller than ${OffsetPaging.LIMIT_MAXIMAL_VALUE}`)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
export const INITIAL_PAGE_INDEX = 0;
|
||||
export const INITIAL_PAGE_SIZE = 15;
|
||||
|
||||
export const MIN_PAGE_INDEX = 0;
|
||||
export const MIN_PAGE_SIZE = 1;
|
||||
|
||||
export const MAX_PAGE_SIZE = 100; //Number.MAX_SAFE_INTEGER;
|
||||
@ -1 +1,2 @@
|
||||
export * from './OffsetPaging';
|
||||
export * from "./OffsetPaging";
|
||||
export * from "./defaults";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export * from "./common";
|
||||
|
||||
export * from "./auth";
|
||||
export * from "./catalog";
|
||||
export * from "./common";
|
||||
export * from "./profile";
|
||||
export * from "./sales";
|
||||
export * from "./users";
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
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 @@
|
||||
export * from "./IGetProfile_Response.dto";
|
||||
1
shared/lib/contexts/profile/application/dto/index.ts
Normal file
1
shared/lib/contexts/profile/application/dto/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./GetProfile.dto";
|
||||
1
shared/lib/contexts/profile/application/index.ts
Normal file
1
shared/lib/contexts/profile/application/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./dto";
|
||||
1
shared/lib/contexts/profile/index.ts
Normal file
1
shared/lib/contexts/profile/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./application";
|
||||
Loading…
Reference in New Issue
Block a user