feat: StorageService S3/MinIO, POST /users, invoice upload, portal dashboard §08 statuses + notifications

This commit is contained in:
Lizandro Guarnizo
2026-06-01 20:55:30 -05:00
parent 98ab5a309c
commit a047a8b032
19 changed files with 1166 additions and 134 deletions
+17 -27
View File
@@ -3,27 +3,21 @@ import {
UseGuards, UseInterceptors, UploadedFiles,
} from "@nestjs/common";
import { FilesInterceptor } from "@nestjs/platform-express";
import { diskStorage } from "multer";
import { extname, join } from "path";
import { existsSync, mkdirSync } from "fs";
import { randomUUID } from "crypto";
import { memoryStorage } from "multer";
import { extname } from "path";
import { PackagesService } from "./packages.service";
import { StorageService } from "../storage/storage.service";
import { CreatePackageDto, UpdateStatusDto, VerifyPackageDto, SenaeDeclarationDto } from "./dto/package.dto";
import { JwtAuthGuard, RolesGuard, Roles } from "../auth/guards/auth.guard";
import { CurrentUser } from "../auth/decorators/current-user.decorator";
import { ConfigService } from "@nestjs/config";
@Controller("packages")
@UseGuards(JwtAuthGuard, RolesGuard)
export class PackagesController {
private readonly apiUrl: string;
constructor(
private svc: PackagesService,
private config: ConfigService,
) {
this.apiUrl = this.config.get("API_URL", "http://localhost:3001");
}
private storage: StorageService,
) {}
@Get()
findAll(
@@ -72,23 +66,13 @@ export class PackagesController {
return this.svc.verifyPackage(id, dto, user.id);
}
/** Bodega: upload photos via multipart form (doc §10 step 4) */
/** Bodega: upload photos via multipart form — stored via StorageService (doc §10 step 4) */
@Post(":id/photos")
@Roles("OPERADOR_BODEGA", "ADMIN_EMPRESA", "SUPER_ADMIN")
@UseInterceptors(
FilesInterceptor("photos", 10, {
storage: diskStorage({
destination: (req, file, cb) => {
const pkgId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
const dir = join(process.cwd(), "uploads", "packages", pkgId);
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
cb(null, dir);
},
filename: (req, file, cb) => {
cb(null, `${randomUUID()}${extname(file.originalname)}`);
},
}),
fileFilter: (req, file, cb) => {
storage: memoryStorage(),
fileFilter: (_req, file, cb) => {
const allowed = /jpg|jpeg|png|gif|webp/;
cb(null, allowed.test(extname(file.originalname).toLowerCase()));
},
@@ -100,9 +84,15 @@ export class PackagesController {
@UploadedFiles() files: Express.Multer.File[],
@CurrentUser() user: any,
): Promise<any> {
const baseUrl = this.apiUrl;
const photoUrls = (files || []).map(
f => `${baseUrl}/uploads/packages/${id}/${f.filename}`,
const photoUrls = await Promise.all(
(files || []).map(f =>
this.storage.saveFile(
`packages/${id}`,
f.originalname,
f.buffer,
f.mimetype,
),
),
);
return this.svc.addPhotos(id, photoUrls, user.id);
}
+2 -1
View File
@@ -4,9 +4,10 @@ import { PackagesController } from "./packages.controller";
import { PrismaModule } from "../prisma/prisma.module";
import { ConfigModule } from "@nestjs/config";
import { NotificationsModule } from "../notifications/notifications.module";
import { StorageModule } from "../storage/storage.module";
@Module({
imports: [PrismaModule, ConfigModule, NotificationsModule],
imports: [PrismaModule, ConfigModule, NotificationsModule, StorageModule],
providers: [PackagesService],
controllers: [PackagesController],
exports: [PackagesService],