didi-lot2-backend/backend/services/data-layer/didiStorage/INDEX.md
2026-07-10 03:39:53 -07:00

352 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# didiStorage - Index
Stocare fisiere media pentru platforma DIDI. Container MinIO (S3-compatibil) local pe masina de deployment. Nu contine cod custom -- doar configurare si script de initializare.
## Productie activa (LOCAL)
DIDI scrie **LOCAL** pe containerul MinIO `staging-dataLayer-minio:9000` (pe `didi-network`), intr-un singur bucket `didi-prod`. Decizie: stabilitate + zero dependinte externe. Clusterul MinIO managed extern (4 noduri, erasure coding EC:2, HAProxy + keepalived VRRP) ramane configurat ca **fallback de urgenta pentru HA**, activabil cu `minio-switch.sh cluster`, dar nu este folosit operational acum.
| Mediu | Endpoint | Bucket | Credentiale |
|-------|----------|--------|-------------|
| **Productie (LOCAL — activ)** | `staging-dataLayer-minio:9000` (expus `0.0.0.0:9000`) | `didi-prod` (single bucket) | `didi-prod` / `627074a6...` |
| Fallback HA (cluster — inactiv) | `<minio-host>:9000` (VIP `10.11.10.128`) | `didi-prod` | didi-prod / `.cluster-credentials.env` |
Switch local/cluster: `agent-v3/scripts/minio-switch.sh local|cluster` (modifica `.env` + reseteaza containerele). Detalii migrare: `agent-v3/MIGRATION_MINIO.md`.
Restrictia cheie mostenita din arhitectura: credentialele `didi-prod` au `s3:*` **doar pe bucket-ul propriu** — nu se creeaza bucket-uri noi. De aici single-bucket architecture (vezi sectiunea urmatoare), pastrata si local.
## Container local (activ)
**Imagine**: minio/minio:RELEASE.2024-08-29T01-40-52Z
**Container**: staging-dataLayer-minio
**Port API**: 9000 (Docker network + expus pe host `0.0.0.0:9000`)
**Port Console**: 9001 (expus pe host `0.0.0.0:9001`)
**Bucket DIDI**: `didi-prod` (singurul bucket)
**Credentiale DIDI**: `didi-prod` / `627074a6...` (din `.env`)
**Volume**: didi-staging-minio-data:/data
---
## Ce stocheaza
1. **Fisiere uploadate de utilizatori** -- imagini, audio, video, documente
2. **Fisiere procesate de agent-v3** -- video downloadat, cadre extrase, transcrieri
3. **Artefacte pipeline** -- rezultate analiza (cu versionare)
4. **Bucket-uri per utilizator** -- fisiere organizate pe foldere tipizate
---
## Single-bucket architecture (refactor 2026-04-25)
Productia foloseste un singur bucket `didi-prod`, iar separarea logica se face prin **prefix-uri**, nu bucket-uri distincte. Numele de prefix-uri sistem sunt identice cu numele bucket-urilor vechi pentru ca URL-urile vechi sa ramana interpretabile.
```
didi-prod/
uploads/ -- upload-uri generale / fallback (legacy "uploads")
image-files/ -- imagini (legacy bucket "image-files")
audio-files/ -- audio (legacy bucket "audio-files")
video-files/ -- video (legacy bucket "video-files")
text-files/ -- text (legacy bucket "text-files")
document-files/ -- PDF, Office (legacy bucket "document-files")
pipeline-artifacts/ -- rezultate analiza (legacy bucket "pipeline-artifacts")
users/{userId}/ -- namespace per utilizator (inlocuieste bucket-urile "user-{id}")
images/
videos/
videos/frames/ -- cadre extrase din video (scrise de media-preprocess worker, citite de techniques + ai-tampered)
audio-files/
text-files/
```
### De ce single-bucket
- Arhitectura a fost proiectata pentru credentiale cu `s3:*` limitat la un singur bucket pre-creat (`didi-prod`), fara `s3:CreateBucket` — pastrata identic si pe MinIO local pentru portabilitate cluster.
- Quota tracking simplificat: nu mai depindem de tag-uri pe bucket; storage-ul utilizatorilor este urmarit in PG (`bos_sysadmin.internet_user.storage_used_bytes` + `storage_limit_bytes`, vezi migration `010_add_user_storage_quota.sql` din didiFramework).
- Separare logica prin prefix-uri, nu prin bucket-uri distincte — acelasi layout functioneaza local si pe cluster fara modificari de cod.
### Backward compat
Caller-ii care paseaza bucket-uri vechi (`user-3`, `image-files`) sunt rezolvati automat la canonic `didi-prod/<full-key>`:
| URL primit | Bucket rezolvat | Key rezolvat |
|---|---|---|
| `user-3/images/abc.jpg` | `didi-prod` | `users/3/images/abc.jpg` |
| `image-files/foo.jpg` | `didi-prod` | `image-files/foo.jpg` |
| `didi-prod/users/3/images/abc.jpg` | `didi-prod` | `users/3/images/abc.jpg` (passthrough) |
Implementat in:
- `didiFramework/src/config/minio.ts` -- `resolveBucketRequest(bucket, key)` + constanta `BUCKET = process.env.MINIO_BUCKET || 'didi-prod'`.
- `agent-v3/src/shared/media/media-service.ts` -- `proxyFile()` (ownership check accepta atat `user-{N}` cat si prefix `users/{N}/`) + `uploadFile()` foloseste prefix `users/{userId}/{folder}/`.
### Lifecycle si versionare
- Politicile de lifecycle (90 zile pentru transient, retentie permanenta pentru `pipeline-artifacts/`, `backups/`) se aplica pe bucket-ul `didi-prod` prin prefix; pe MinIO local pot fi setate cu `mc ilm` (optional).
- Versionarea pentru `pipeline-artifacts/` si `backups/` este pastrata la nivel de bucket.
- Daca se comuta pe cluster (`minio-switch.sh cluster`), lifecycle-ul devine responsabilitatea operatorilor cluster-ului (nu detinem bucket-ul acolo).
### Mod legacy (multi-bucket)
Pentru referinta — modul vechi avea 8 bucket-uri sistem (`uploads`, `text-files`, `image-files`, `audio-files`, `video-files`, `document-files`, `pipeline-artifacts`, `backups`) plus bucket-uri dinamice `user-{id}` create la primul login. Acest layout a fost inlocuit de single-bucket `didi-prod` cu prefix-uri.
---
## Limite dimensiune fisiere
| Tip | Limita | MIME types |
|-----|--------|------------|
| Imagini | 20 MB | image/jpeg, image/png, image/gif, image/webp, image/bmp, image/svg+xml |
| Audio | 100 MB | audio/mpeg, audio/wav, audio/ogg, audio/webm, audio/flac, audio/mp4, audio/x-m4a |
| Video | 500 MB | video/mp4, video/webm, video/quicktime, video/x-msvideo, video/x-matroska |
| Text | 10 MB | text/plain, text/html, text/markdown, text/csv |
| Documente | 50 MB | application/pdf, application/msword, application/vnd.openxmlformats-* |
Rutarea automata: fisierul e pus in bucket-ul corespunzator MIME type-ului.
---
## Cine scrie in MinIO
| Serviciu | Ce scrie | Locatie (bucket `didi-prod` local) | Logica in fisier |
|----------|----------|--------|------------------|
| didiFramework (uploads) | Fisiere uploadate via API | `didi-prod/users/{id}/{mimeFolder}/...` (fallback `didi-prod/{mimeBucket}/`) | didiFramework/src/routes/uploads.ts |
| didiFramework (auth) | (Nu mai creeaza bucket) Logging registration; quota in PG | -- | didiFramework/src/routes/auth.ts + migration 010_add_user_storage_quota.sql |
| agent-v3 (media upload) | Fisiere uploadate direct sau via multer | `didi-prod/users/{userId}/...` (fallback `didi-prod/uploads/{userId}/`) | agent-v3/src/api/routes.ts -> media-service.ts |
| agent-v3 (media-preprocess worker) | Video downloadat, cadre extrase ffmpeg, audio extras pentru transcript | `didi-prod/users/{userId}/videos/frames/...` (cand frame-urile sunt persistate); altfel /tmp efemer | agent-v3/src/queue/workers/media-preprocess-worker.ts + shared/media/video-processor.ts |
| agent-v3 (pipeline) | Imagini downloadate din URL-uri | `didi-prod/users/{userId}/images/...` (fallback `didi-prod/uploads/{userId}/`) | agent-v3/src/api/pipeline-routes.ts |
Nota media-preprocess: workerul ruleaza inaintea componentelor de analiza (techniques, ai-tampered, claims) si centralizeaza descarcarea + ffmpeg + transcript + 2× vision. Frame-urile extrase sunt apoi consumate de workerii de techniques / ai-tampered fara duplicare. Cand persistarea frame-urilor in MinIO este activa, prefixul folosit este `users/{userId}/videos/frames/` (cf. `USER_BUCKET_FOLDERS.FRAMES`).
## Cine citeste din MinIO
| Serviciu | Ce citeste | Cum |
|----------|-----------|-----|
| agent-v3 (media proxy) | Servire fisiere catre client | GET /api/v3/media/file/:bucket/:objectKey (proxy cu range support) |
| agent-v3 (vision) | Imagini pentru modele LLM locale | URL intern direct catre MinIO local (host-ul de deployment :9000/bucket/key) |
| agent-v3 (transcription) | Audio/video pentru transcriere | URL presemnat sau intern |
| didiFramework (uploads) | Info fisier + URL presemnat | GET /api/uploads/:fileId |
| Clienti externi | Download fisiere | URL presemnat (1 ora) sau proxy agent-v3 |
---
## URL-uri si acces
### URL public (prin proxy agent-v3)
```
https://didi365.eu/api/v3/media/file/{bucket}/{objectKey}
Exemplu (canonic): https://didi365.eu/api/v3/media/file/didi-prod/users/3/audio-files/1771883851173-audio.mp3
Exemplu (legacy): https://didi365.eu/api/v3/media/file/user-3/audio-files/1771883851173-audio.mp3 (rezolvat la didi-prod)
```
Suporta HTTP Range requests (streaming audio/video). Proxy-ul rezolva atat URL-uri legacy (`user-{id}/...`, `image-files/...`) cat si forme canonice (`didi-prod/users/{id}/...`).
### URL presemnat (direct MinIO local)
```
http://<host-deployment>:9000/didi-prod/{objectKey}?X-Amz-Algorithm=...&X-Amz-Signature=...
```
Valabilitate: 1 ora (GET), 1 ora (PUT upload). Path-style obligatoriu (`forcePathStyle=true`).
### URL intern (pentru modele LLM locale)
```
http://<host-deployment>:9000/didi-prod/{objectKey}
```
Modelele locale (Qwen Vision, pe masinile GPU) nu pot accesa `didi365.eu`, asa ca URL-urile publice sunt convertite la URL-uri MinIO interne catre containerul local `staging-dataLayer-minio` (expus pe host-ul de deployment `:9000`). Logica: `agent-v3/src/shared/media/vision.ts` (`INTERNAL_MEDIA_BASE`).
---
## Integrare cu serviciile
### didiFramework -- configurare MinIO principala
Fisier: `didiFramework/src/config/minio.ts` (refactor 2026-04-25 pentru single-bucket)
Constante:
- `BUCKET` -- bucket fix din `MINIO_BUCKET` env (default `didi-prod`).
- `BUCKETS` -- prefix-uri sistem (`uploads`, `image-files`, `audio-files`, `video-files`, `text-files`, `document-files`, `pipeline-artifacts`).
- `USER_BUCKET_FOLDERS` -- foldere per utilizator (`images`, `videos`, `audio-files`, `text-files`, `videos/frames`).
- `MIME_TO_BUCKET` -- routing MIME -> prefix sistem.
Exporta:
- `getMinioClient()` -- client singleton.
- `checkMinioHealth()` -- health check via `listBuckets()`.
- `resolveBucketRequest(bucket, key)` -- traduce input legacy (`user-3`, `image-files`) la `(BUCKET, fullKey)` canonic.
- `userObjectKey(userId, folder, filename)` -- construieste `users/{userId}/{folder}/{filename}`.
- `ensureBucket(name)` -- **no-op in single-bucket mode** (logging only). Pentru bucket-uri sistem legacy / `user-{N}` returneaza fara eroare.
- `uploadBuffer(bucket, name, buffer, mimeType, metadata)` -- upload (rezolva bucket-ul intern).
- `deleteObject(bucket, name)` / `getObjectInfo(bucket, name)` / `listObjects(bucket, prefix, maxKeys)`.
- `getPresignedUrl(bucket, name, expiry)` -- URL download (default 1 ora).
- `getPresignedPutUrl(bucket, name, expiry)` -- URL upload (default 1 ora).
- `getDirectUrl(bucket, name)` -- URL direct fara semnatura.
- `createUserBucket(userId, email, planId, planName, storageLimitGb)` -- **lazy in single-bucket mode**: namespace-ul `users/{id}/` "exista" doar cand are obiecte; functia logheaza si scrie quota in PG.
- `getUserBucketUsage(userId)` -- listObjects pe `users/{id}/`, returneaza bytes + count.
- `getUserBucketMetadata(userId)` -- thin shim (in single-bucket mode metadata e in PG, nu in tag-uri).
- `updateUserBucketMetadata(userId, planId, planName, storageLimitGb)` -- no-op pentru bucket tags; caller-ul scrie in PG.
Quota tracking: migrarea `sql/migrations/010_add_user_storage_quota.sql` adauga coloanele `storage_used_bytes` si `storage_limit_bytes` la `bos_sysadmin.internet_user`. Tag-urile vechi (`storage-limit-gb` etc.) nu mai sunt folosite.
### agent-v3 -- MediaService
Fisier: `agent-v3/src/shared/media/media-service.ts`
Exporta:
- uploadFile(userId, buffer, filename, contentType) -- upload cu rutare automata bucket
- getPresignedUploadUrl(userId, filename, contentType) -- URL presemnat PUT (1 ora)
- getPresignedDownloadUrl(objectKey, bucket) -- URL presemnat GET (1 ora)
- proxyFile(bucket, objectKey, ownerUserId, rangeHeader) -- proxy cu verificare proprietar + range support
- ensureBucket(name) -- creeaza daca nu exista
Flow upload in agent-v3:
1. Rezolva bucket-ul utilizatorului din didiFramework (keycloak_id -> bucket + folder)
2. Fallback la uploads/{userId} daca framework indisponibil
3. Returneaza: download_url, public_url, object_key, bucket, filename, size
### Python (shared layer)
Fisier: `shared/minio_presigner.py`
- convert_media_url_for_llm(url) -- converteste URL-uri interne MinIO in URL-uri presemnate pentru LLM-uri externe
- parse_minio_url(url) -- parseaza formate: minio://bucket/path, /bucket/path, http://minio:9000/bucket/path
Fisier: `shared/url_config.py`
- convert_minio_to_public_url(url) -- converteste URL-uri interne in URL-uri publice HTTP
---
## Fluxul de upload (utilizator)
```
Utilizator uploadeaza fisier
|
v
POST /api/v3/media/upload (agent-v3, multer, max 50MB)
|
v
MediaService.uploadFile()
|-- Cere didiFramework /internal/get-bucket-info -> { bucketName: 'didi-prod', folder: 'users/{id}/{mimeFolder}' }
|-- Fallback: bucket = MINIO_BUCKET (didi-prod), prefix = uploads/{userId}/
|
v
MinIO local: putObject('didi-prod', 'users/{id}/{mimeFolder}/{filename}', buffer)
|
v
Genereaza URL public: https://didi365.eu/api/v3/media/file/didi-prod/users/{id}/{mimeFolder}/{filename}
|
v
Returneaza: { download_url, public_url, object_key, bucket, size, content_type }
```
## Fluxul de inregistrare utilizator (single-bucket)
```
Utilizator face login prima data
|
v
GET /api/auth/me (didiFramework)
|
v
Utilizator nu exista in PG -> auto-inregistrare
|
v
createUserBucket(internetUserId, email, planId='1', planName='Free', storageLimitGb=1)
|-- (single-bucket mode) -- nu apeleaza MinIO makeBucket
|-- Logheaza initializarea
|-- Quota persistata in PG: bos_sysadmin.internet_user.storage_limit_bytes
|
v
Namespace logic users/{id}/ exista de cum primul fisier e uploadat.
```
## Fluxul media-preprocess (async, video/audio/imagine)
```
Job analiza pe URL/upload media
|
v
Dispatcher RabbitMQ -> media-preprocess queue (un singur worker per sesiune)
|
v
MediaPreprocessWorker:
|-- yt-dlp / fetch URL -> /tmp/video_{sessionId}_{ts}/source.mp4
|-- ffmpeg extrage frame-uri uniform (max 10) -> /tmp/.../frame_%03d.jpg
|-- ffmpeg extrage audio -> /tmp/.../audio.mp3
|-- transcript via Whisper (M17 -> Groq -> OpenAI)
|-- 2× vision call pe ACELEASI frame-uri (misinformation + ai_detection)
|-- (optional) upload frame-uri persistente -> didi-prod/users/{id}/videos/frames/
|
v
Cache rezultatele in Redis (TTL 1h):
agent:media:{sessionId}:transcript
agent:media:{sessionId}:vision:misinformation
agent:media:{sessionId}:vision:ai_detection
agent:media:{sessionId}:merged_text
agent:media:{sessionId}:ready = "1"
|
v
Dispatch task-uri pentru techniques + ai-tampered + claims (citesc din Redis, nu reproceseaza media)
```
Beneficiu: 1 download + 1 ffmpeg + 1 transcript + 2 vision in loc de 3× pe fiecare component.
---
## Fisiere in directorul didiStorage
```
init-buckets.sh -- Script initializare: creeaza 8 bucket-uri + lifecycle + versionare (136 linii)
.env.example -- Template variabile de mediu
README.md -- Documentatie (253 linii)
.gitignore -- Exclude .env, data/, config/
```
Zero cod custom. Bucket-urile si politicile sunt create de init-buckets.sh la prima pornire.
---
## Configurare Docker
```yaml
# din data-layer/docker-compose.yml
staging-dataLayer-minio:
image: minio/minio:RELEASE.2024-08-29T01-40-52Z
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minio123
MINIO_REGION_NAME: us-east-1
MINIO_BROWSER: "on"
ports:
- "9000:9000" # API (expus pe host 0.0.0.0:9000)
- "9001:9001" # Console (expus pe host 0.0.0.0:9001)
volumes:
- didi-staging-minio-data:/data
```
Nota: Nu exista container `minio-init` in docker-compose.yml. Scriptul `init-buckets.sh` trebuie rulat manual dupa prima pornire a MinIO.
---
## Variabile de mediu (conectare din alte servicii)
Setarile actuale (productie LOCALA, valori confirmate din containerul `didi-agent-v3`):
| Variabila | Valoare productie | Note |
|-----------|-------------------|------|
| MINIO_ENDPOINT | `staging-dataLayer-minio` | container local pe `didi-network`, path-style obligatoriu |
| MINIO_PORT | `9000` | expus si pe host (`0.0.0.0:9000`) |
| MINIO_USE_SSL | `false` | HTTP intern |
| MINIO_BUCKET | `didi-prod` | bucket fix, single-bucket arch (singurul bucket din instanta) |
| MINIO_ACCESS_KEY | `didi-prod` | full s3:* pe `didi-prod` |
| MINIO_SECRET_KEY | (in `.env`) | `627074a6...` |
Switch rapid local <-> cluster: `backend/services/orchestration-layer/agent-v3/scripts/minio-switch.sh local|cluster` (citeste credentiale din `.cluster-credentials.env`, modifica `.env`-urile pentru agent-v3 + didiFramework, restart containere). Status curent: `local`.
Fallback HA (cluster extern — inactiv, doar dupa `minio-switch.sh cluster`):
| Serviciu | MINIO_ENDPOINT | MINIO_PORT | Credentiale |
|----------|---------------|------------|-------------|
| didiFramework | <minio-host> (VIP 10.11.10.128) | 9000 | didi-prod / `.cluster-credentials.env` |
| agent-v3 | <minio-host>:9000 | (inclus in endpoint) | didi-prod / `.cluster-credentials.env` |
| Python shared | <minio-host>:9000 | (inclus in endpoint) | didi-prod / `.cluster-credentials.env` |
---
## Ce NU face
- Containerul local nu are cod custom (doar MinIO standard + script init); productia DIDI ruleaza pe acest container local, iar cluster-ul CAI managed ramane fallback HA inactiv.
- Local este instanta singulara (fara replicare). Fallback-ul cluster are 4 noduri + erasure coding EC:2 (toleranta la 2 noduri pierdute), disponibil doar dupa `minio-switch.sh cluster`.
- Nu are encriptie at-rest dedicata pe DIDI.
- TLS intern: HTTP (fara TLS pe MinIO local).
- Nu enforce-uieste quota la nivel MinIO; quota utilizator (`storage_used_bytes` / `storage_limit_bytes`) este urmarita in PG (`bos_sysadmin.internet_user`) si verificata de didiFramework la upload.
- Nu mai face create-bucket per utilizator (single-bucket: namespace logic prin prefix).