- 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>
308 lines
7.1 KiB
Markdown
308 lines
7.1 KiB
Markdown
# 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
|
|
|
|
```env
|
|
# 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
|
|
|
|
```bash
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```json
|
|
{
|
|
"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)
|
|
|
|
```yaml
|
|
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](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/s3-example-creating-buckets.html)
|
|
- [MinIO Docs](https://min.io/docs/minio/linux/index.html)
|
|
- [MII-004](../01-epicas/MII-004-captura-video.md) - Captura de video
|
|
|
|
---
|
|
|
|
**Ultima Actualizacion:** 2026-01-10
|