- Backend NestJS con módulos de autenticación, inventario, créditos - Frontend React con dashboard y componentes UI - Base de datos PostgreSQL con migraciones - Tests E2E configurados - Configuración de Docker y deployment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
7.1 KiB
7.1 KiB
INT-005: Integracion S3/MinIO
id: INT-005 type: Integration status: Pendiente version: "1.0.0" created_date: 2026-01-10 updated_date: 2026-01-10 simco_version: "4.0.0"
Metadata
| Campo | Valor |
|---|---|
| ID | INT-005 |
| Servicio | AWS S3 / MinIO |
| Proposito | Almacenamiento de videos y artefactos |
| Criticidad | P0 |
| Estado | Pendiente |
1. Descripcion
Integracion con almacenamiento compatible con S3 para guardar videos de inventario, frames extraidos, y otros artefactos del procesamiento.
2. Informacion del Servicio
| Campo | Valor |
|---|---|
| Desarrollo | MinIO (local) |
| Produccion | AWS S3 o DigitalOcean Spaces |
| SDK | @aws-sdk/client-s3, @aws-sdk/s3-request-presigner |
3. Configuracion
Variables de Entorno
# Desarrollo (MinIO)
S3_ENDPOINT=http://localhost:9002
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_BUCKET=miinventario
S3_REGION=us-east-1
# Produccion (AWS S3)
S3_ENDPOINT=https://s3.amazonaws.com
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=...
S3_BUCKET=miinventario-prod
S3_REGION=us-east-1
Instalacion
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
4. Estructura de Buckets
miinventario/
├── videos/
│ └── {userId}/{sessionId}/
│ └── video.mp4
├── frames/
│ └── {sessionId}/
│ ├── frame_001.jpg
│ ├── frame_002.jpg
│ └── ...
├── thumbnails/
│ └── {sessionId}/
│ └── thumb.jpg
├── evidence/
│ └── {sessionId}/
│ └── closeup_{itemId}.jpg
└── products/
└── {productId}/
└── image.jpg
5. Implementacion Backend
Servicio S3
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
@Injectable()
export class S3Service {
private client: S3Client;
private bucket: string;
constructor() {
this.client = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
},
forcePathStyle: true, // Necesario para MinIO
});
this.bucket = process.env.S3_BUCKET;
}
async upload(key: string, body: Buffer, contentType: string) {
await this.client.send(new PutObjectCommand({
Bucket: this.bucket,
Key: key,
Body: body,
ContentType: contentType,
}));
return `${process.env.S3_ENDPOINT}/${this.bucket}/${key}`;
}
async getSignedUploadUrl(key: string, contentType: string, expiresIn = 3600) {
const command = new PutObjectCommand({
Bucket: this.bucket,
Key: key,
ContentType: contentType,
});
return getSignedUrl(this.client, command, { expiresIn });
}
async getSignedDownloadUrl(key: string, expiresIn = 3600) {
const command = new GetObjectCommand({
Bucket: this.bucket,
Key: key,
});
return getSignedUrl(this.client, command, { expiresIn });
}
async delete(key: string) {
await this.client.send(new DeleteObjectCommand({
Bucket: this.bucket,
Key: key,
}));
}
}
Upload Multipart
import { CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand } from '@aws-sdk/client-s3';
async initMultipartUpload(key: string) {
const { UploadId } = await this.client.send(
new CreateMultipartUploadCommand({
Bucket: this.bucket,
Key: key,
})
);
return UploadId;
}
async uploadPart(key: string, uploadId: string, partNumber: number, body: Buffer) {
const { ETag } = await this.client.send(
new UploadPartCommand({
Bucket: this.bucket,
Key: key,
UploadId: uploadId,
PartNumber: partNumber,
Body: body,
})
);
return { ETag, PartNumber: partNumber };
}
async completeMultipartUpload(key: string, uploadId: string, parts: any[]) {
await this.client.send(
new CompleteMultipartUploadCommand({
Bucket: this.bucket,
Key: key,
UploadId: uploadId,
MultipartUpload: { Parts: parts },
})
);
}
6. Flujo de Upload de Video
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Mobile │───▶│ Backend │───▶│ S3 │
└──────────┘ └──────────┘ └──────────┘
│ │ │
│ 1. Init │ │
│ upload │ │
│──────────────▶│ │
│ │ 2. Create │
│ │ multipart │
│ │──────────────▶│
│◀──────────────│ uploadId │
│ │ │
│ 3. Upload │ │
│ parts │ │
│───────────────────────────────▶
│ (presigned) │ │
│ │ │
│ 4. Complete │ │
│──────────────▶│ │
│ │ 5. Complete │
│ │ multipart │
│ │──────────────▶│
│◀──────────────│ │
7. Lifecycle Rules
Configuracion de Expiracion
{
"Rules": [
{
"ID": "DeleteOldVideos",
"Status": "Enabled",
"Filter": {
"Prefix": "videos/"
},
"Expiration": {
"Days": 30
}
},
{
"ID": "DeleteOldFrames",
"Status": "Enabled",
"Filter": {
"Prefix": "frames/"
},
"Expiration": {
"Days": 7
}
}
]
}
8. Docker Compose (MinIO)
minio:
image: minio/minio
container_name: mii_minio
ports:
- "9002:9000"
- "9003:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
9. Consideraciones de Seguridad
| Aspecto | Implementacion |
|---|---|
| URLs firmadas | Expiracion corta (1h) |
| Bucket privado | No acceso publico |
| CORS | Solo dominios permitidos |
| Encriptacion | SSE-S3 en reposo |
10. Referencias
- AWS S3 SDK
- MinIO Docs
- MII-004 - Captura de video
Ultima Actualizacion: 2026-01-10