Validacion y restriccion de archivos + almacenamiento en carpeta
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
DROP VIEW "public"."v_product_store";--> statement-breakpoint
|
||||
ALTER TABLE "products" ADD COLUMN "gallery" text[] DEFAULT '{}'::text[] NOT NULL;--> statement-breakpoint
|
||||
CREATE VIEW "public"."v_product_store" AS (
|
||||
select p.id as product_id, p.title, p.description, p.price, p.stock, p.url_img, p.gallery, p.address, p.status, p.user_id, u.fullname, u.email, u.phone
|
||||
from products p
|
||||
left join auth.users as u on u.id = p.user_id);
|
||||
1550
apps/api/src/database/migrations/meta/0007_snapshot.json
Normal file
1550
apps/api/src/database/migrations/meta/0007_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,13 @@
|
||||
"when": 1752507413748,
|
||||
"tag": "0006_real_tyger_tiger",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "7",
|
||||
"when": 1754420096323,
|
||||
"tag": "0007_curved_fantastic_four",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -16,7 +16,8 @@ export async function seedProducts(db: NodePgDatabase<typeof schema>) {
|
||||
address:"Calle 1",
|
||||
status:'PUBLICADO', // PUBLICADO, AGOTADO, BORRADOR
|
||||
urlImg:'apple.avif',
|
||||
userId:1
|
||||
userId:1,
|
||||
gallery: ["Pruebas"]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
import { Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
|
||||
import { Controller, Post, UploadedFiles, UseInterceptors, Body } from '@nestjs/common';
|
||||
import { FilesInterceptor } from '@nestjs/platform-express';
|
||||
import { PicturesService } from './pictures.service';
|
||||
|
||||
@@ -9,13 +8,14 @@ export class PicturesController {
|
||||
|
||||
@Post('upload')
|
||||
@UseInterceptors(FilesInterceptor('urlImg'))
|
||||
async uploadFile(@UploadedFiles() files: Express.Multer.File[]) {
|
||||
console.log(files);
|
||||
async uploadFile(@UploadedFiles() files: Express.Multer.File[], @Body() body: any) {
|
||||
// Aquí puedes acceder a los campos del formulario
|
||||
// console.log('Archivos:', files);
|
||||
// console.log('Otros campos del formulario:', body);
|
||||
|
||||
const result = await this.picturesService.saveImages(files);
|
||||
console.log(result);
|
||||
|
||||
// const result = await this.picturesService.saveImages(files);
|
||||
// console.log(result);
|
||||
|
||||
return {data: ["result"]}
|
||||
return { data: result };
|
||||
}
|
||||
}
|
||||
@@ -14,21 +14,20 @@ export class PicturesService {
|
||||
|
||||
const picturesPath = join(__dirname, '..', '..', '..', '..', 'uploads','pict');
|
||||
|
||||
console.log(picturesPath);
|
||||
|
||||
let images : string[] = [];
|
||||
|
||||
console.log(file);
|
||||
|
||||
|
||||
let count = 0;
|
||||
|
||||
// file.forEach(async (file) => {
|
||||
// // count++
|
||||
// // const fileName = `${Date.now()}-${count++}-${file.originalname}`;
|
||||
// // console.log(fileName);
|
||||
// // const filePath = join(picturesPath, fileName);
|
||||
// // await writeFile(filePath, file.buffer);
|
||||
// // images.push(fileName);
|
||||
// });
|
||||
file.forEach(async (file) => {
|
||||
count++
|
||||
const fileName = `${Date.now()}-${count}-${file.originalname}`;
|
||||
images.push(fileName);
|
||||
// console.log(fileName);
|
||||
const filePath = join(picturesPath, fileName);
|
||||
await writeFile(filePath, file.buffer);
|
||||
});
|
||||
// return [file[0].originalname]
|
||||
return images;
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ import { z } from 'zod'; // Asegúrate de importar Zod
|
||||
// Si tu EditInventory original no contempla FileList, crea un esquema para el formulario.
|
||||
|
||||
// Ejemplo de cómo podrías adaptar tu esquema para el formulario
|
||||
const formSchemaWithFiles = editInventory.extend({
|
||||
urlImg: z.custom<FileList | undefined | null>().optional(), // Ahora permite FileList para el input file
|
||||
});
|
||||
// const formSchemaWithFiles = editInventory.extend({
|
||||
// urlImg: z.custom<FileList | undefined | null>().optional(), // Ahora permite FileList para el input file
|
||||
// });
|
||||
|
||||
// Define un tipo para los datos del formulario que incluye el FileList
|
||||
type FormDataWithFiles = z.infer<typeof formSchemaWithFiles>;
|
||||
// type FormDataWithFiles = z.infer<typeof formSchemaWithFiles>;
|
||||
|
||||
interface UpdateFormProps {
|
||||
onSuccess?: () => void;
|
||||
@@ -59,7 +59,7 @@ export function UpdateForm({
|
||||
|
||||
const [sizeFile, setSizeFile] = useState('0 bytes');
|
||||
|
||||
const defaultformValues: FormDataWithFiles = { // Usamos el nuevo tipo aquí
|
||||
const defaultformValues: EditInventory = { // Usamos el nuevo tipo aquí
|
||||
id: defaultValues?.id,
|
||||
title: defaultValues?.title || '',
|
||||
description: defaultValues?.description || '',
|
||||
@@ -68,21 +68,19 @@ export function UpdateForm({
|
||||
status: defaultValues?.status || 'BORRADOR',
|
||||
stock: defaultValues?.stock ?? 0,
|
||||
urlImg: undefined, // Inicializamos como undefined o null para el FileList
|
||||
userId: defaultValues?.userId
|
||||
};
|
||||
|
||||
const form = useForm<FormDataWithFiles>({ // Usamos el nuevo tipo aquí
|
||||
resolver: zodResolver(formSchemaWithFiles), // Usamos el esquema extendido
|
||||
const form = useForm<EditInventory>({ // Usamos el nuevo tipo aquí
|
||||
resolver: zodResolver(editInventory), // Usamos el esquema extendido
|
||||
defaultValues: defaultformValues,
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
const onSubmit = async (data: FormDataWithFiles) => {
|
||||
const onSubmit = async (data: EditInventory) => {
|
||||
// --- MODIFICACIÓN CLAVE: Crear FormData ---
|
||||
const formData = new FormData();
|
||||
|
||||
// Añadir otros campos de texto al FormData
|
||||
// Asegúrate de que los nombres coincidan con lo que tu backend espera
|
||||
formData.append('id', data.id ? String(data.id) : ''); // Los IDs a menudo son numéricos, conviértelos a string
|
||||
formData.append('title', data.title);
|
||||
formData.append('description', data.description);
|
||||
@@ -90,7 +88,6 @@ export function UpdateForm({
|
||||
formData.append('address', data.address);
|
||||
formData.append('status', data.status);
|
||||
formData.append('stock', String(data.stock)); // Convertir a string
|
||||
formData.append('userId', data.userId ? String(data.userId) : '');
|
||||
|
||||
// --- MODIFICACIÓN AQUÍ: Asegurar que cada archivo sea un 'File' ---
|
||||
if (data.urlImg) { // Primero, verifica que FileList no sea null/undefined
|
||||
@@ -229,7 +226,7 @@ export function UpdateForm({
|
||||
render={({ field: { onChange, onBlur, name, ref } }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Imagen</FormLabel>
|
||||
<p>Peso máximo: 2MB / {sizeFile}</p>
|
||||
<p>Peso máximo: 5MB / {sizeFile}</p>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="file"
|
||||
|
||||
@@ -7,31 +7,9 @@ export type EditInventory = z.infer<typeof editInventory>;
|
||||
export type ProductApiResponseSchema = z.infer<typeof productApiResponseSchema>;
|
||||
export type allProducts = z.infer<typeof productDetails>;
|
||||
|
||||
// --- Esquemas de validación para archivos ---
|
||||
|
||||
// Esquema básico para un solo archivo
|
||||
const fileSchema = z.object({
|
||||
name: z.string().min(1, 'El nombre del archivo no puede estar vacío.'),
|
||||
size: z.number().int().positive('El tamaño del archivo debe ser positivo.'),
|
||||
type: z.string().refine(
|
||||
(type) => type.startsWith('image/'),
|
||||
'Solo se permiten archivos de imagen.'
|
||||
),
|
||||
// Puedes añadir más validaciones personalizadas aquí
|
||||
// Por ejemplo, limitar el tamaño máximo a 5MB (5 * 1024 * 1024 bytes)
|
||||
}).refine(
|
||||
(file) => file.size <= 5 * 1024 * 1024,
|
||||
'El tamaño del archivo no debe exceder los 5MB.'
|
||||
);
|
||||
|
||||
// Esquema para un array de archivos (cuando el input es multiple)
|
||||
const filesArraySchema = z.array(fileSchema).max(5, 'Solo se permiten hasta 5 archivos.');
|
||||
|
||||
// const formSchema = z.object({
|
||||
// file: z
|
||||
// .instanceof(FileList)
|
||||
// .refine((file) => file?.length == 1, 'File is required.')
|
||||
// });
|
||||
const MAX_FILE_SIZE = 5242880; // 5MB en bytes
|
||||
const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/jpg", "image/png", "image/webp"];
|
||||
const MAX_FILENAME_LENGTH = 50;
|
||||
|
||||
export const product = z.object({
|
||||
id: z.number().optional(),
|
||||
@@ -41,7 +19,7 @@ export const product = z.object({
|
||||
// category: z.string(),
|
||||
stock: z.number(),
|
||||
price: z.string(),
|
||||
urlImg: z.any(),
|
||||
urlImg: z.custom<FileList | undefined>().optional(),
|
||||
// urlImg: z.string(),
|
||||
status: z.string(),
|
||||
userId: z.number().optional()
|
||||
@@ -60,22 +38,23 @@ export const editInventory = z.object({
|
||||
stock: z.number(),
|
||||
address: z.string().min(5, { message: "Debe de tener 5 o más caracteres" }),
|
||||
price: z.string(),
|
||||
urlImg: z.any(),
|
||||
urlImg: z.custom<FileList | undefined>()
|
||||
.refine((files) => files && files.length > 0, "Se requiere al menos una imagen")
|
||||
.refine((files) => files && files.length <= 10, "Máximo 10 imágenes")
|
||||
.refine((files) =>
|
||||
files && Array.from(files).every(file => file.size <= MAX_FILE_SIZE),
|
||||
`El tamaño máximo de cada imagen es de 5MB`
|
||||
).refine((files) =>
|
||||
files && Array.from(files).every(file => ACCEPTED_IMAGE_TYPES.includes(file.type)),
|
||||
"Solo se aceptan archivos .jpg, .jpeg, .png y .webp"
|
||||
).refine((files) =>
|
||||
files && Array.from(files).every(file => file.name.length <= MAX_FILENAME_LENGTH),
|
||||
`El nombre de cada archivo no puede superar los ${MAX_FILENAME_LENGTH} caracteres`
|
||||
),
|
||||
status: z.string().min(1, { message: "Debe de seleccionar un valor" }),
|
||||
userId: z.number().optional(),
|
||||
})
|
||||
|
||||
// export const productDetails = z.object({
|
||||
// id: z.number().optional(),
|
||||
// title: z.string().min(5),
|
||||
// description: z.string().min(10),
|
||||
// stock: z.number(),
|
||||
// price: z.string(),
|
||||
// address: z.string(),
|
||||
// urlImg: z.string(),
|
||||
// userId: z.number(),
|
||||
// })
|
||||
|
||||
export const ApiResponseSchema = z.object({
|
||||
message: z.string(),
|
||||
data: z.array(product),
|
||||
|
||||
@@ -54,7 +54,7 @@ export const safeFetchApi = async <T extends z.ZodSchema<any>>(
|
||||
[{ type: string; message: string; details?: any } | null, z.infer<T> | null]
|
||||
> => {
|
||||
try {
|
||||
console.log(url,method,data);
|
||||
// console.log(url,method,data);
|
||||
|
||||
const response = await fetchApi({
|
||||
method,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverActions: {
|
||||
bodySizeLimit: '2mb',
|
||||
bodySizeLimit: '5mb',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user