.
This commit is contained in:
parent
3a881c554f
commit
f47a97bfa0
@ -21,6 +21,7 @@
|
|||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@repo/typescript-config": "workspace:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
"@types/dinero.js": "^1.9.4",
|
"@types/dinero.js": "^1.9.4",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/glob": "^8.1.0",
|
"@types/glob": "^8.1.0",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
//import { initPackages } from "@/core/package-loader";
|
//import { initPackages } from "@/core/package-loader";
|
||||||
|
import cors from "cors";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import express, { Application } from "express";
|
import express, { Application } from "express";
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
@ -21,6 +22,27 @@ export function createApp(): Application {
|
|||||||
|
|
||||||
app.use(responseTime()); // set up the response-time middleware
|
app.use(responseTime()); // set up the response-time middleware
|
||||||
|
|
||||||
|
// enable CORS - Cross Origin Resource Sharing
|
||||||
|
app.use(
|
||||||
|
cors({
|
||||||
|
origin: process.env.FRONTEND_URL || "http://localhost:5173",
|
||||||
|
methods: "GET,POST,PUT,DELETE,OPTIONS",
|
||||||
|
credentials: true,
|
||||||
|
|
||||||
|
exposedHeaders: [
|
||||||
|
"Access-Control-Allow-Headers",
|
||||||
|
"Access-Control-Allow-Origin",
|
||||||
|
"Content-Disposition",
|
||||||
|
"Content-Type",
|
||||||
|
"Content-Length",
|
||||||
|
"X-Total-Count",
|
||||||
|
"Pagination-Count",
|
||||||
|
"Pagination-Page",
|
||||||
|
"Pagination-Limit",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// secure apps by setting various HTTP headers
|
// secure apps by setting various HTTP headers
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export const App = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const axiosInstance = createAxiosInstance({
|
const axiosInstance = createAxiosInstance({
|
||||||
baseURL: import.meta.env.VITE_API_URL,
|
baseURL: import.meta.env.VITE_API_SERVER_URL,
|
||||||
getAccessToken,
|
getAccessToken,
|
||||||
onAuthError: () => {
|
onAuthError: () => {
|
||||||
clearAccessToken();
|
clearAccessToken();
|
||||||
@ -32,8 +32,6 @@ export const App = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(axiosInstance.defaults.env);
|
|
||||||
|
|
||||||
const dataSource = createAxiosDataSource(axiosInstance);
|
const dataSource = createAxiosDataSource(axiosInstance);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
83
docs/prompt-auth.md
Normal file
83
docs/prompt-auth.md
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
📌 CONTEXTO DEL PROYECTO:
|
||||||
|
Estoy desarrollando una API en **Node.js** con **TypeScript**, **Express.js**, **Sequelize**, y **Arquitectura Hexagonal (Ports & Adapters)** bajo los principios de **DDD (Domain-Driven Design)** y **SOLID**.
|
||||||
|
La API maneja **autenticación con PassportJS**, soporte para **JWT y LocalStrategy**, gestión segura de contraseñas con **bcrypt**, y transformación de respuestas con **DTOs (Data Transfer Objects)**.
|
||||||
|
|
||||||
|
📌 PRINCIPIOS Y PATRONES A APLICAR:
|
||||||
|
✅ **DDD (Domain-Driven Design):**
|
||||||
|
- Los **agregados** encapsulan su estado y exponen solo lo necesario.
|
||||||
|
- Cada **agregado** tiene su propio **mapper** para conversión entre persistencia y dominio.
|
||||||
|
- **Repositorios** manejan **agregados** y deben desacoplarse de Sequelize.
|
||||||
|
|
||||||
|
✅ **SOLID (Principios de Diseño):**
|
||||||
|
- **SRP:** Cada clase tiene una única responsabilidad.
|
||||||
|
- **OCP:** El código debe ser fácil de extender sin modificarlo.
|
||||||
|
- **LSP:** Los objetos derivados deben poder sustituir a sus clases base.
|
||||||
|
- **ISP:** Usar interfaces específicas en lugar de dependencias directas.
|
||||||
|
- **DIP:** Usar **interfaces** (`IAuthProvider`, `ITransactionManager`) en lugar de acoplarse a implementaciones concretas.
|
||||||
|
|
||||||
|
📌 GESTIÓN DE DEPENDENCIAS:
|
||||||
|
✅ **Factory Pattern (`createAuthService`)**
|
||||||
|
- `AuthService` debe crearse con un **Factory** (`createAuthService()`), permitiendo cambiar su implementación sin afectar la API.
|
||||||
|
- `AuthService` depende de `IAuthenticatedUserRepository`, `ITransactionManager` y `IAuthProvider`.
|
||||||
|
|
||||||
|
📌 AUTENTICACIÓN CON `PassportJS`:
|
||||||
|
✅ **Soporte para JWT y LocalStrategy**
|
||||||
|
- `PassportAuthProvider` maneja **JWTStrategy** para autenticación con tokens y **LocalStrategy** para validación con email y contraseña.
|
||||||
|
- Se debe usar `bcrypt` para cifrar contraseñas antes de almacenarlas.
|
||||||
|
- `validateUser()` en `PassportAuthProvider` verifica credenciales con `LocalStrategy`.
|
||||||
|
|
||||||
|
📌 GESTIÓN SEGURA DE CONTRASEÑAS:
|
||||||
|
✅ **Uso de `bcrypt` para cifrado de contraseñas**
|
||||||
|
- `PasswordHash.create()` valida la fuerza de la contraseña antes de cifrarla.
|
||||||
|
- `bcrypt.compare()` se usa para validar la contraseña al hacer login.
|
||||||
|
- Las contraseñas **nunca deben almacenarse en texto plano en la base de datos**.
|
||||||
|
|
||||||
|
📌 GESTIÓN DE ERRORES:
|
||||||
|
✅ **Uso de `ApiError` para respuestas de error estructuradas**
|
||||||
|
- `ExpressController` debe devolver errores con `ApiError` (`status`, `title`, `detail`, `timestamp`).
|
||||||
|
- `RegisterController` y `LoginController` deben usar `ApiError` en caso de errores (`409 Conflict`, `401 Unauthorized`, `500 Internal Server Error`).
|
||||||
|
- Se deben capturar errores de **Sequelize** en `BaseRepository` (`UniqueConstraintError`, `ConnectionError`, `TimeoutError`).
|
||||||
|
- **Errores de unicidad (`SequelizeUniqueConstraintError`) deben ser personalizados según el contexto usando `errorMapper` en los repositorios.**
|
||||||
|
|
||||||
|
📌 FLUJO DE LA API `/register`:
|
||||||
|
1️⃣ **Request llega a `RegisterController`**.
|
||||||
|
2️⃣ **Valida `email`, `username`, `password` como Value Objects**.
|
||||||
|
3️⃣ **Llama a `AuthService.registerUser()`**.
|
||||||
|
4️⃣ **`AuthService` usa `validateUser()` para evitar usuarios duplicados**.
|
||||||
|
5️⃣ **Si el usuario es nuevo, se cifra la contraseña con `bcrypt.hash()`**.
|
||||||
|
6️⃣ **Se almacena en la BD con `AuthenticatedUserRepository.create()`**.
|
||||||
|
7️⃣ **Se generan `accessToken` y `refreshToken` con `PassportAuthProvider`**.
|
||||||
|
8️⃣ **Se responde con los tokens y el `userId`**.
|
||||||
|
|
||||||
|
📌 FLUJO DE LA API `/login`:
|
||||||
|
1️⃣ **Request llega a `LoginController`**.
|
||||||
|
2️⃣ **Llama a `AuthService.loginUser()` con `email` y `password`**.
|
||||||
|
3️⃣ **`AuthService` busca el usuario en `AuthenticatedUserRepository.findUserByEmail()`**.
|
||||||
|
4️⃣ **Si el usuario existe, compara contraseñas con `bcrypt.compare()`**.
|
||||||
|
5️⃣ **Si la contraseña es correcta, genera `accessToken` y `refreshToken`**.
|
||||||
|
6️⃣ **Devuelve los tokens en la respuesta**.
|
||||||
|
|
||||||
|
📌 TRANSFORMACIÓN DE RESPUESTAS:
|
||||||
|
✅ **Los Controladores son responsables de la transformación de datos antes de enviarlos.**
|
||||||
|
✅ **Se usan `Presenters` (`AuthResponsePresenter`) para mapear datos del dominio a DTOs (`AuthResponseDTO`).**
|
||||||
|
✅ **Se inyectan `Presenters` en los controladores para desacoplar la lógica de transformación.**
|
||||||
|
|
||||||
|
📌 SEGURIDAD Y MEJORES PRÁCTICAS:
|
||||||
|
✅ **Usar `TransactionManager.complete()` para manejar transacciones.**
|
||||||
|
✅ **Evitar acoplamientos directos entre servicios y repositorios.**
|
||||||
|
✅ **Usar `bcrypt` con `SALT_ROUNDS=12` configurables en `.env`.**
|
||||||
|
✅ **Si ocurre un error en la BD, `rollback()` debe ejecutarse automáticamente.**
|
||||||
|
✅ **Devolver códigos HTTP adecuados (`401 Unauthorized`, `409 Conflict`, `500 Internal Server Error`).**
|
||||||
|
|
||||||
|
📌 SOLICITUDES QUE PUEDO HACERTE:
|
||||||
|
- **Generar código** cumpliendo con estas reglas.
|
||||||
|
- **Revisar código y detectar violaciones de SOLID o DDD**.
|
||||||
|
- **Optimizar `AuthService` para mejorar la seguridad y escalabilidad**.
|
||||||
|
- **Implementar `/refresh` para renovar `accessTokens`**.
|
||||||
|
- **Escribir pruebas unitarias y de integración para `registerUser()` y `loginUser()`**.
|
||||||
|
- **Optimizar `AuthResponsePresenter` para soportar más formatos (`XML`, `CSV`).**
|
||||||
|
|
||||||
|
⚠️ **IMPORTANTE:**
|
||||||
|
- **NO generes código que acople dependencias directamente**.
|
||||||
|
- **NO uses Sequelize directamente en los servicios**.
|
||||||
|
- **SIEMPRE usa interfaces (`IAuthProvider`, `ITransactionManager`) en lugar de instancias concretas**.
|
||||||
93
docs/prompt-customer-invoices.md
Normal file
93
docs/prompt-customer-invoices.md
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
📌 CONTEXTO DEL PROYECTO:
|
||||||
|
Estoy desarrollando una API en **Node.js** con **TypeScript**, **Express.js**, **Sequelize**, y **Arquitectura Hexagonal (Ports & Adapters)** bajo los principios de **DDD (Domain-Driven Design)** y **SOLID**.
|
||||||
|
Este módulo es para **facturas de cliente (Customer Invoices)** y debe cumplir con los estándares definidos previamente para el contexto de autenticación, adaptados a los datos, reglas y operaciones de las facturas.
|
||||||
|
|
||||||
|
📌 PRINCIPIOS Y PATRONES A APLICAR:
|
||||||
|
✅ **DDD (Domain-Driven Design)**
|
||||||
|
- Las **facturas** serán agregados raíz (`CustomerInvoice`) con propiedades como `id`, `customerId`, `invoiceNumber`, `date`, `lines`, `totals`, etc.
|
||||||
|
- Las **líneas de factura** (`InvoiceLine`) se modelan como entidades o value objects dentro del agregado.
|
||||||
|
- Se usará un `Mapper` para convertir entre dominio y persistencia.
|
||||||
|
- Repositorios (`ICustomerInvoiceRepository`) solo manejan agregados.
|
||||||
|
- Operaciones como `createInvoice`, `updateInvoice`, `getInvoiceById` serán gestionadas en `CustomerInvoiceService`.
|
||||||
|
|
||||||
|
✅ **SOLID**
|
||||||
|
- Usar SRP: cada clase con una responsabilidad clara.
|
||||||
|
- Usar interfaces como `ICustomerInvoiceRepository`, `ITransactionManager`, `IInvoicePresenter`.
|
||||||
|
- Evitar acoplamientos entre capa de aplicación y capa de infraestructura.
|
||||||
|
- Controladores deben transformar los datos en DTOs antes de enviarlos.
|
||||||
|
|
||||||
|
📌 ESTRUCTURA DE DATOS:
|
||||||
|
|
||||||
|
La entidad `CustomerInvoice` tendrá:
|
||||||
|
- `id`: UUID (ValueObject `UniqueID`)
|
||||||
|
- `status`: string (ValueObject) (`Draft`, `Emitted`, `Sent`, `Rejected`)
|
||||||
|
- `invoiceNumber`: string (ValueObject )
|
||||||
|
- `invoiceSeries`: string (ValueObject )
|
||||||
|
- `customerId`: UUID (ValueObject)
|
||||||
|
- `issueDate`: UtcDate (ValueObject)
|
||||||
|
- `operationDate`: UtcDate (ValueObject)
|
||||||
|
- `lines`: array de `InvoiceLine`, cada una con:
|
||||||
|
- `productId`: (Optional) UUID (ValueObject `UniqueID`)
|
||||||
|
- `description`: string (ValueObject)
|
||||||
|
- `quantity`: Amount (number) and scale (number) (ValueObject Quantity)
|
||||||
|
- `unitPrice`: Amount (number), scale (number) and currency (string) (ValueObject MoneyValue)
|
||||||
|
- `totalLineAmount`: Amount (number), scale (number) and currency (string) (ValueObject MoneyValue)
|
||||||
|
- `subtotalPrice`: Amount (number), scale (number) and currency (string) (ValueObject MoneyValue)
|
||||||
|
- `discount`: Amount (number) and scale (number) (ValueObject Percentage)
|
||||||
|
- `tax`: Amount (number) and scale (number) (ValueObject Percentage)
|
||||||
|
- `totalAmount`: Amount (number), scale (number) and currency (string) (ValueObject MoneyValue)
|
||||||
|
|
||||||
|
|
||||||
|
📌 OPERACIONES PRINCIPALES:
|
||||||
|
- `createInvoice(data: CreateCustomerInvoiceDTO)`
|
||||||
|
- `getInvoiceById(id: string)`
|
||||||
|
- `updateInvoice(id: string, changes: UpdateCustomerInvoiceDTO)`
|
||||||
|
- `listInvoices(filter?)`
|
||||||
|
- `deleteInvoice(id: string)`
|
||||||
|
- `markAsPaid(id: string)`
|
||||||
|
|
||||||
|
📌 VALIDACIONES:
|
||||||
|
✅ Usar `Zod` para validaciones en Value Objects (`InvoiceNumber`, `InvoiceDate`, `InvoiceLine`).
|
||||||
|
✅ No se deben lanzar excepciones para errores de validación. Usar `Result<T, E>` como en autenticación.
|
||||||
|
|
||||||
|
📌 PRESENTACIÓN:
|
||||||
|
✅ Los controladores convierten el agregado `CustomerInvoice` a `CustomerInvoiceDTO`.
|
||||||
|
✅ Se usan `Presenters` (`CustomerInvoicePresenter`) para construir los DTOs.
|
||||||
|
✅ DTOs solo exponen campos necesarios: nada sensible, nada interno.
|
||||||
|
✅ Se puede extender con `CsvInvoicePresenter`, `PdfInvoicePresenter`, etc.
|
||||||
|
|
||||||
|
📌 AUTORIZACIÓN Y ACCESO:
|
||||||
|
✅ Se protegerán rutas con middleware JWT (`authenticateJWT`).
|
||||||
|
✅ Se validará que el usuario autenticado pueda acceder/modificar la factura (p. ej. pertenece a su empresa).
|
||||||
|
|
||||||
|
📌 PERSISTENCIA:
|
||||||
|
✅ `SequelizeCustomerInvoiceRepository` no accede directamente al modelo de Sequelize, lo hace a través de `Mapper`.
|
||||||
|
✅ La clase `CustomerInvoiceMapper` mapea de/agregado <-> persistencia.
|
||||||
|
✅ Soporta transacciones Sequelize (`TransactionManager.complete(...)`).
|
||||||
|
✅ Los nombres de los campos y las tablas en la base de datos seguirán la notación "snake_case".
|
||||||
|
✅ Los nombres de los campos en los modelos Sequelize seguirán la notación "snake_case".
|
||||||
|
✅ Los modelos Sequelize de los agregados tienen campos 'timestamp' con los nombres "created_at", "updated_at" y "deleted_at".
|
||||||
|
|
||||||
|
|
||||||
|
📌 ERRORES:
|
||||||
|
✅ Usar `ApiError` para errores en la API.
|
||||||
|
✅ Los repositorios deben capturar errores de Sequelize (`UniqueConstraintError`, etc.) y convertirlos a errores de dominio con mensajes claros y específicos (mediante `errorMapper`).
|
||||||
|
|
||||||
|
📌 TESTING:
|
||||||
|
✅ Los servicios (`CustomerInvoiceService`) serán testeados con mocks de repositorio.
|
||||||
|
✅ Las rutas serán testeadas con `supertest`.
|
||||||
|
✅ Las validaciones de ValueObjects tendrán pruebas unitarias.
|
||||||
|
|
||||||
|
📌 NOMENCLATURA:
|
||||||
|
- Agregado: `CustomerInvoice`
|
||||||
|
- Línea: `CustomerInvoiceLine`
|
||||||
|
- DTOs: `CustomerInvoiceDTO`, `InvoiceLineDTO`
|
||||||
|
- Presentadores: `CustomerInvoicePresenter`
|
||||||
|
- Repositorio: `ICustomerInvoiceRepository`, `SequelizeCustomerInvoiceRepository`
|
||||||
|
- ValueObjects: `CustomerInvoiceNumber`, `CustomerInvoiceDate`, `CustomerInvoiceTotal`, `CustomerInvoiceItemQuantity`, etc.
|
||||||
|
|
||||||
|
⚠️ IMPORTANTE:
|
||||||
|
- NO incluir `passwords`, `tokens`, o datos sensibles en respuestas.
|
||||||
|
- NO usar Sequelize directamente en servicios ni controladores.
|
||||||
|
- NO devolver el agregado como respuesta bruta. Usar DTOs.
|
||||||
|
- TODO debe estar tipado, incluso los métodos de los servicios (`Promise<Result<T, Error>>`).
|
||||||
@ -2,53 +2,78 @@ import { AxiosInstance } from "axios";
|
|||||||
import { ICustomParams, IDataSource } from "../datasource.interface";
|
import { ICustomParams, IDataSource } from "../datasource.interface";
|
||||||
import { defaultAxiosRequestConfig } from "./create-axios-instance";
|
import { defaultAxiosRequestConfig } from "./create-axios-instance";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea un DataSource basado en Axios.
|
||||||
|
* @param client Instancia de Axios que se utilizará para las peticiones HTTP.
|
||||||
|
* @returns Un objeto que implementa la interfaz IDataSource.
|
||||||
|
* @throws Error si la instancia de Axios no es válida o no tiene la configuración necesaria.
|
||||||
|
* @example
|
||||||
|
* const axiosInstance = createAxiosInstance({
|
||||||
|
* baseURL: "https://api.example.com",
|
||||||
|
* getAccessToken: () => localStorage.getItem("accessToken"),
|
||||||
|
* onAuthError: () => {
|
||||||
|
* console.error("Error de autenticación");
|
||||||
|
* // Manejar el error de autenticación, por ejemplo, redirigir al login
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* const dataSource = createAxiosDataSource(axiosInstance);
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
export const createAxiosDataSource = (client: AxiosInstance): IDataSource => {
|
export const createAxiosDataSource = (client: AxiosInstance): IDataSource => {
|
||||||
function getBaseUrlOrFail(): string {
|
// Validaciones de la instancia de Axios
|
||||||
const baseUrl = client.getUri();
|
if (!client) {
|
||||||
|
throw new Error("[Axios] Se esperaba una instancia de Axios.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!baseUrl) {
|
if (!(client as AxiosInstance).getUri) {
|
||||||
throw new Error("[Axios] baseURL no está definido en esta instancia.");
|
throw new Error("[Axios] La instancia proporcionada no es una instancia de Axios válida.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseUrl;
|
if (typeof (client as AxiosInstance).getUri !== "function") {
|
||||||
|
throw new Error("[Axios] La instancia proporcionada no tiene el método getUri.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (client as AxiosInstance).getUri() !== "string") {
|
||||||
|
throw new Error("[Axios] baseURL no está definido en esta instancia.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getBaseUrl: getBaseUrlOrFail,
|
getBaseUrl: () => (client as AxiosInstance).getUri(),
|
||||||
|
|
||||||
getList: async <T>(resource: string, params?: Record<string, any>) => {
|
getList: async <T>(resource: string, params?: Record<string, any>) => {
|
||||||
const res = await client.get<T[]>(resource, params);
|
const res = await (client as AxiosInstance).get<T[]>(resource, params);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
getOne: async <T>(resource: string, id: string | number) => {
|
getOne: async <T>(resource: string, id: string | number) => {
|
||||||
const res = await client.get<T>(`${resource}/${id}`);
|
const res = await (client as AxiosInstance).get<T>(`${resource}/${id}`);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
getMany: async <T>(resource: string, ids: Array<string | number>) => {
|
getMany: async <T>(resource: string, ids: Array<string | number>) => {
|
||||||
const res = await client.get<T[]>(`${resource}`, { params: { ids } });
|
const res = await (client as AxiosInstance).get<T[]>(`${resource}`, { params: { ids } });
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
createOne: async <T>(resource: string, data: Partial<T>) => {
|
createOne: async <T>(resource: string, data: Partial<T>) => {
|
||||||
const res = await client.post<T>(resource, data);
|
const res = await (client as AxiosInstance).post<T>(resource, data);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateOne: async <T>(resource: string, id: string | number, data: Partial<T>) => {
|
updateOne: async <T>(resource: string, id: string | number, data: Partial<T>) => {
|
||||||
const res = await client.put<T>(`${resource}/${id}`, data);
|
const res = await (client as AxiosInstance).put<T>(`${resource}/${id}`, data);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteOne: async <T>(resource: string, id: string | number) => {
|
deleteOne: async <T>(resource: string, id: string | number) => {
|
||||||
await client.delete(`${resource}/${id}`);
|
await (client as AxiosInstance).delete(`${resource}/${id}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
custom: async <T>(customParams: ICustomParams) => {
|
custom: async <T>(customParams: ICustomParams) => {
|
||||||
const { url, path, method, responseType, headers, signal, data, ...payload } = customParams;
|
const { url, path, method, responseType, headers, signal, data, ...payload } = customParams;
|
||||||
|
|
||||||
const requestUrl = path ? `${getBaseUrlOrFail()}/${path}` : url;
|
const requestUrl = path ? `${(client as AxiosInstance).getUri()}/${path}` : url;
|
||||||
if (!requestUrl) throw new Error('"url" or "path" param is missing');
|
if (!requestUrl) throw new Error('"url" or "path" param is missing');
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@ -67,14 +92,14 @@ export const createAxiosDataSource = (client: AxiosInstance): IDataSource => {
|
|||||||
case "put":
|
case "put":
|
||||||
case "post":
|
case "post":
|
||||||
case "patch":
|
case "patch":
|
||||||
customResponse = await client.request<T>({
|
customResponse = await (client as AxiosInstance).request<T>({
|
||||||
...config,
|
...config,
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "delete":
|
case "delete":
|
||||||
customResponse = await client.delete<T>(requestUrl, {
|
customResponse = await (client as AxiosInstance).delete<T>(requestUrl, {
|
||||||
responseType,
|
responseType,
|
||||||
headers,
|
headers,
|
||||||
...payload,
|
...payload,
|
||||||
@ -82,7 +107,7 @@ export const createAxiosDataSource = (client: AxiosInstance): IDataSource => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
customResponse = await client.get<T>(requestUrl, {
|
customResponse = await (client as AxiosInstance).get<T>(requestUrl, {
|
||||||
responseType,
|
responseType,
|
||||||
signal,
|
signal,
|
||||||
headers,
|
headers,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance, CreateAxiosDefaults } from "axios";
|
||||||
import { setupInterceptors } from "./setup-interceptors";
|
import { setupInterceptors } from "./setup-interceptors";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,7 +20,7 @@ export interface AxiosFactoryConfig {
|
|||||||
onAuthError?: () => void;
|
onAuthError?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultAxiosRequestConfig = {
|
export const defaultAxiosRequestConfig: CreateAxiosDefaults = {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
@ -40,10 +40,11 @@ export const createAxiosInstance = ({
|
|||||||
getAccessToken,
|
getAccessToken,
|
||||||
onAuthError,
|
onAuthError,
|
||||||
}: AxiosFactoryConfig): AxiosInstance => {
|
}: AxiosFactoryConfig): AxiosInstance => {
|
||||||
const instance = axios.create({
|
console.log({ baseURL, getAccessToken, onAuthError });
|
||||||
baseURL,
|
|
||||||
...defaultAxiosRequestConfig,
|
const instance = axios.create(defaultAxiosRequestConfig);
|
||||||
});
|
|
||||||
|
instance.defaults.baseURL = baseURL;
|
||||||
|
|
||||||
setupInterceptors(instance, getAccessToken, onAuthError);
|
setupInterceptors(instance, getAccessToken, onAuthError);
|
||||||
|
|
||||||
|
|||||||
@ -87,6 +87,8 @@ export const CustomerInvoicesGrid = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
console.log(isError, error);
|
||||||
|
|
||||||
// Container: Defines the grid's theme & dimensions.
|
// Container: Defines the grid's theme & dimensions.
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export const useCustomerInvoices = (params: any) => {
|
|||||||
return useQuery<any[]>({
|
return useQuery<any[]>({
|
||||||
queryKey: keys().data().resource("invoices").action("list").params(params).get(),
|
queryKey: keys().data().resource("invoices").action("list").params(params).get(),
|
||||||
queryFn: (context) => {
|
queryFn: (context) => {
|
||||||
console.log(context);
|
console.log(dataSource.getBaseUrl());
|
||||||
const { signal } = context;
|
const { signal } = context;
|
||||||
return dataSource.getList("customer-invoices", {
|
return dataSource.getList("customer-invoices", {
|
||||||
signal,
|
signal,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export const CustomerInvoicesList = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [status, setStatus] = useState("all");
|
const [status, setStatus] = useState("all");
|
||||||
|
|
||||||
const CustomerInvoiceStatuses = [
|
/*const CustomerInvoiceStatuses = [
|
||||||
{ value: "all", label: t("customerInvoices.list.tabs.all") },
|
{ value: "all", label: t("customerInvoices.list.tabs.all") },
|
||||||
{ value: "draft", label: t("customerInvoices.list.tabs.draft") },
|
{ value: "draft", label: t("customerInvoices.list.tabs.draft") },
|
||||||
{ value: "ready", label: t("customerInvoices.list.tabs.ready") },
|
{ value: "ready", label: t("customerInvoices.list.tabs.ready") },
|
||||||
@ -20,7 +20,7 @@ export const CustomerInvoicesList = () => {
|
|||||||
{ value: "accepted", label: t("customerInvoices.list.tabs.accepted") },
|
{ value: "accepted", label: t("customerInvoices.list.tabs.accepted") },
|
||||||
{ value: "rejected", label: t("customerInvoices.list.tabs.rejected") },
|
{ value: "rejected", label: t("customerInvoices.list.tabs.rejected") },
|
||||||
{ value: "archived", label: t("customerInvoices.list.tabs.archived") },
|
{ value: "archived", label: t("customerInvoices.list.tabs.archived") },
|
||||||
];
|
];*/
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -34,7 +34,7 @@ export const CustomerInvoicesList = () => {
|
|||||||
<p className='text-muted-foreground'>{t("customerInvoices.list.description")}</p>
|
<p className='text-muted-foreground'>{t("customerInvoices.list.description")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center space-x-2'>
|
<div className='flex items-center space-x-2'>
|
||||||
<Button onClick={() => navigate("/CustomerInvoices/add")}>
|
<Button onClick={() => navigate("/customer-invoices/add")}>
|
||||||
<PlusIcon className='w-4 h-4 mr-2' />
|
<PlusIcon className='w-4 h-4 mr-2' />
|
||||||
{t("customerInvoices.create.title")}
|
{t("customerInvoices.create.title")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
export * from "./components";
|
export * from "./components/index.tsx";
|
||||||
|
|||||||
@ -132,6 +132,9 @@ importers:
|
|||||||
'@types/bcrypt':
|
'@types/bcrypt':
|
||||||
specifier: ^5.0.2
|
specifier: ^5.0.2
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
|
'@types/cors':
|
||||||
|
specifier: ^2.8.19
|
||||||
|
version: 2.8.19
|
||||||
'@types/dinero.js':
|
'@types/dinero.js':
|
||||||
specifier: ^1.9.4
|
specifier: ^1.9.4
|
||||||
version: 1.9.4
|
version: 1.9.4
|
||||||
@ -2621,6 +2624,9 @@ packages:
|
|||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||||
|
|
||||||
|
'@types/cors@2.8.19':
|
||||||
|
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
|
||||||
|
|
||||||
'@types/d3-array@3.2.1':
|
'@types/d3-array@3.2.1':
|
||||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||||
|
|
||||||
@ -7961,6 +7967,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.24
|
'@types/node': 22.15.24
|
||||||
|
|
||||||
|
'@types/cors@2.8.19':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.15.24
|
||||||
|
|
||||||
'@types/d3-array@3.2.1': {}
|
'@types/d3-array@3.2.1': {}
|
||||||
|
|
||||||
'@types/d3-color@3.1.3': {}
|
'@types/d3-color@3.1.3': {}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user