GStorage — Implementation Plan
Realizarea design-ului din
platform_design.md(sursă de adevăr) +../gstorage.jdl(model). Strategie: generează scheletul JHipster microservices, apoi adaugă straturile scrise de mână în clase separat denumite (regenerare-safe, ca gnotify/gdocs). Acest document este planul de build autoritativ; ține-l sincron pe măsură ce fazele aterizează.
0. Locked decisions
Section titled “0. Locked decisions”| # | Decizie | Alegere |
|---|---|---|
| 1 | Poziționare | Strat de infra sub gdocs: object-store generic + catalog open-data + SDK. Fără semnare/OCR. |
| 2 | Fundație | JHipster microservices 8.11 (Spring Boot 3 + Angular 19) |
| 3 | Topologie | gateway (UI :8080) + gstorage (backend :8100) + JHipster Registry/Eureka (:8761); scalare = N replici gstorage |
| 4 | Model de date | Două straturi: fizic (Bucket+ObiectStocat+SesiuneTransfer) + catalog (Organizatie→Dataset→Resursa, Grup) |
| 5 | Object store | MinIO acum, în spatele driverului StorageBackend (S3-swappable); integritate sha-256 + soft-delete + versionare + backup |
| 6 | Auth UI | Dual JWT + Keycloak (realm gstorage), ca glog |
| 7 | Auth mașină | Dual: client-credentials Keycloak (scope storage:* + tenant) SAU cheie API locală (ApiClient) fără Keycloak |
| 8 | Transfer | REST multipart + Range reluabil (download) + upload reluabil (SesiuneTransfer) + server FTP/SFTP real (Apache MINA) |
| 9 | Căutare | Elasticsearch + worker Tika (extragere text) — hand-wired |
| 10 | Integrări | Conectori GLog + GNotify (SDK, client-credentials) live și built-in (Alarma engine + Eveniment audit) |
| 11 | Scalare | RabbitMQ workeri + retry/DLQ; gateway+Eureka LB; 2 replici în demo; restart:always+healthcheck = supervizor |
| 12 | SDK | Java + JS construite/publicate în GitLab registry; PHP proiectat |
| 13 | Secvențiere | Backend corect întâi, UI/portal după |
1. Faza 0 — Scaffold (gstorage.jdl + generare)
Section titled “1. Faza 0 — Scaffold (gstorage.jdl + generare)”gstorage.jdl= sursă de adevăr (deja scris: 2 aplicații, 13 entități, 12 enum-uri).- Generare (Node 20, fără Java local):
Aceasta creează două subproiecte (
Terminal window cd paas_gstoragejhipster jdl gstorage.jdl --force # generează gateway/ + gstorage/ + entitățilegateway/,gstorage/). Alternativ, se pot ține în subfoldere sau se despart în repo-uri; pentru demo rămân înpaas_gstorage/. gitdeja inițializat (remotegit.esempla.systems/govtech/gstack/paas_gstorage, branchmaster).- Adaugă
CLAUDE.md(deja scris) cu convențiile. - Verificare: generarea reușește; ambele apps compilează în container
maven:3.9-eclipse-temurin-21;./npmw run lintverde la gateway.
2. Faza 1 — DB hardening (Liquibase append-only)
Section titled “2. Faza 1 — DB hardening (Liquibase append-only)”- Changelog unicitate compusă
obiect_stocat (bucket_id, cheie, versiune). - Changelog append-only
eveniment(triggerBEFORE UPDATE/DELETE) +AppendOnlyAuditGuard(listener Hibernate). - Changelog CHECK/validare
permisiune(exact un beneficiar + o resursă) — la aplicație; opțional trigger. - Normalizează
api_client.api_key_hash(BCrypt/Argon2) — hash, niciodată clar. - (Hardening amânat) conversie
jsonb+ index GIN pentru câmpurile JSON (vezidb_schema.md §7). - Verificare:
./mvnw -ntp liquibase:updateSQL(în container) generează DDL fără erori.
3. Faza 2 — Object-store + driver StorageBackend
Section titled “3. Faza 2 — Object-store + driver StorageBackend”Clase noi (regenerare-safe): service/storage/.
StorageBackend(interfață, veziplatform_design.md §3) +MinioBackend(client MinIO) + scheletS3Backend(AWS SDK v2).StorageService— orchestrare: laput, calculează sha-256, scrieObiectStocat(metadate), stochează binarul prin backend, publică eveniment de index (RabbitMQ), scrieEvenimentUPLOAD.- Soft-delete (
statut=STERS+sters_la), restore, purge (binar + null pe cheie, păstrează rândul+audit), versiuni. src/main/docker/minio.yml+application.ymlgstorage.storage.*(backend=minio, endpoint, credențiale, bucket implicit).- Verificare: upload → obiect în MinIO + rând
obiect_stocat+ sha-256; soft-delete → tombstone; restore; purge păstrează audit.
4. Faza 3 — Transfer (REST + reluabil + FTP/SFTP)
Section titled “4. Faza 3 — Transfer (REST + reluabil + FTP/SFTP)”Clase noi: web/rest/v1/, service/transfer/, ftp/.
TransferV1Resource—POST /api/v1/transfer(initiate →SesiuneTransfer),PUT /api/v1/transfer/{uploadId}/part/{n},GET /api/v1/transfer/{uploadId}(progres, pentru reluare),POST .../complete(verifică sha-256 →ObiectStocat),DELETE .../abort.ObjectV1Resource—PUT /api/v1/object(upload simplu multipart),GET /api/v1/object/{bucket}/{cheie}cu suport HTTP Range (descărcare reluabilă),DELETE(soft),GET .../versions.ftp/— server Apache MINA (SFTP + FTPS) cuFileSystemViewcare punte-ază spreStorageBackend; autentificare mapată peApiClient/AppUser.- Verificare: upload în părți, întrerupere,
GETprogres, reluare, complete → hash OK; descărcare cuRangereia; conectare SFTP listează/urcă un fișier.
5. Faza 4 — Securitate (dual UI + dual mașină) + ACL
Section titled “5. Faza 4 — Securitate (dual UI + dual mașină) + ACL”Clase noi: security/apikey/, security/oauth2/, security/acl/, security/local/.
- gateway:
SecurityFilterChaindual — Keycloak OIDC (realmgstorage) + JWT local HS512 (JwtIssuerAuthenticationManagerResolver, ca dualauth din glog); pagina de login cu buton Keycloak + formular user/parolă. - gstorage /api/v1:
ApiKeyFilter(antetX-Api-Key→ApiClient.apiKeyHash)- OAuth2 resource-server (token serviciu Keycloak); ambele → authorities
SCOPE_storage:*+tenant.
- OAuth2 resource-server (token serviciu Keycloak); ambele → authorities
AccessControlService— impune nivelurile (PUBLIC/PRIVAT/RESTRICTIONAT) + granturilePermisiune, la fiecare listare/citire/scriere; niciodată doar în UI.- Auto-provisioning: la prima conectare PaaS → creează
Organizatie+Bucket+ApiClient. - Seed
admin/admin(bootstrap) pentru modul standalone. - Verificare: cheie API validă → 200; lipsă/invalidă → 401; scope insuficient → 403; tenant greșit → 403; token Keycloak client-credentials → 200; login UI dual.
6. Faza 5 — Async (RabbitMQ) + căutare (Tika + ES) + integrări
Section titled “6. Faza 5 — Async (RabbitMQ) + căutare (Tika + ES) + integrări”Clase noi: config/RabbitMqConfig, service/search/, service/alarm/,
service/arhiva/, connect/, repository/search/.
- RabbitMQ: exchange + cozi (extract, backup, arhivare, finalizare), retry
[10,30,60,120,300]s+ DLQ (ca gnotify).spring-boot-starter-amqp. service/search/ExtractWorker—@RabbitListener→ Tika extrage text →ObiectStocat.textIndexat+ index ES (repository/search/*cu tipuri ES fully-qualified).SearchService+/api/v1/search+ fațete portal.service/alarm/AlarmEngine— evalueazăAlarma, declanșează pe canal.service/arhiva/ArchiveService— comprimă N obiecte → obiect ZIP (worker).connect/GlogClient(SDK GLog, client-credentialsglog-ingest— ca gnotify→glog)connect/GnotifyClient(SDK GNotify). FiecareEveniment→ și GLog când e configurat; alarmele → GNotify.
- Joburi
@Scheduled: backup, purjare tombstone (30z), expirare sesiuni. - Verificare: upload PDF → text căutabil în ES; căutare „cuvinte din fișier”; alarmă STOCARE_PRAG → GNotify; eveniment vizibil în GLog; arhivă ZIP creată.
7. Faza 6 — UI (gateway, Angular + GDS) + portal public
Section titled “7. Faza 6 — UI (gateway, Angular + GDS) + portal public”- CRUD generat pentru cele 13 entități (board de administrare),
Authority.ADMIN.Evenimentread-only. - Ecrane custom (stil gStack, 5-fișiere per componentă):
app/explorer/— explorator de fișiere (listă bucket/obiecte, upload cu progres reluabil, download cu/fără descărcare, preview).app/portal/— portal public de date deschise: pagini/dataset,/organization,/group(ca dataset.gov.md), căutare + fațete.app/clients/— gestionareApiClient(chei API + scope-uri).app/dashboard/— statistici (spațiu ocupat, obiecte/format, transferuri, top organizații).
- Auth UI: login dual (Keycloak + user/parolă), stil GDS
@gstack/gds-angular. - i18n în ambii arbori (RO nativ + EN):
webapp/i18n/{ro,en}/*.json+resources/i18n/messages_{ro,en}.properties. - Verificare: login dual; CRUD toate entitățile; explorer upload/download; portal public caută + descarcă un dataset public; dashboard actualizat; RO/EN.
8. Faza 7 — SDK (Java + JS reale, PHP proiectat)
Section titled “8. Faza 7 — SDK (Java + JS reale, PHP proiectat)”sdk/java(Maven),sdk/js(npm) — vezisdk.md. Operații: auth (cheie API sau provider client-credentials Keycloak), upload/download (+ reluabil), bucket/obiect ops, publicare dataset/resursă, URL-uri pre-semnate, căutare.- Publicare în GitLab Package Registry (Maven + npm), ca glog.
sdk/php(Composer) — schelet + documentat, nepublicat (ca PHP-ul glog).examples/node-demo— flux end-to-end.- Verificare: SDK Java + JS urcă/descarcă/caută live; publicate în registry.
9. Faza 8 — Dockerize + deploy (mirror gdocs/gnotify)
Section titled “9. Faza 8 — Dockerize + deploy (mirror gdocs/gnotify)”Dockerfile.gateway(jar) +Dockerfile.gstorage(jar) +docker-compose-server.yml(gateway + 2× gstorage + registry + postgres + minio + elasticsearch + rabbitmq) peinternal+ externalgstack-web.- Împinge
registry.esempla.systems/govtech/gstack/paas_gstorage:{gateway,gstorage}-<ver>. - Edge nginx vhost
gstorage.gstack.esempla.systems+ certbot. - Keycloak: realm
gstorage+ cliențigstorage-web(public+PKCE) +gstorage-ingest(confidential, client-credentials) — configurat de utilizator (SSO propriu). - Deploy =
docker save | ssh | docker load→compose up -d(ca aws-server-and-deploy). - Atenție la RAM/disc pe box (ES + MinIO + RabbitMQ + 2 replici — dial la 1 dacă e strâns).
Critical files (noi dacă nu se spune altfel)
Section titled “Critical files (noi dacă nu se spune altfel)”gstorage.jdl(sursă de adevăr) ·docs/{platform_design,db_schema,sdk}.md·CLAUDE.mdgstorage/.../service/storage/{StorageBackend,MinioBackend,S3Backend,StorageService}.javagstorage/.../web/rest/v1/{ObjectV1Resource,TransferV1Resource,SearchV1Resource,TemplateV1Resource}.javagstorage/.../service/transfer/*·gstorage/.../ftp/*(Apache MINA)gstorage/.../security/{apikey,oauth2,acl,local}/*gstorage/.../config/RabbitMqConfig.java·service/{search,alarm,arhiva}/*·repository/search/*gstorage/.../connect/{GlogClient,GnotifyClient}.java- Liquibase: changelog-uri unicitate + append-only + (hardening) jsonb
gateway/.../security/*(dual) · Angularapp/{explorer,portal,clients,dashboard}/sdk/{java,js,php}·examples/node-demoDockerfile.{gateway,gstorage}·docker-compose-server.yml·nginx/
Risks / notes
Section titled “Risks / notes”- Citirea
systemId/cheii din corpul cererii în filtru — pentru upload-uri streamate, autentifică din antet (X-Api-Key/Authorization), nu din body. - RabbitMQ + ES + MinIO + 2 replici — greu pe box-ul cu RAM limitat; dial la 1 replică dacă e nevoie (design-ul suportă N).
- FTP/SFTP (MINA) — suprafață în plus (port, auth bridging); rulează într-un singur worker/instanță ca să nu dublezi sesiunile.
- JDL = sursă de adevăr — toată logica scrisă de mână în clase separat denumite
(
*V1Resource,StorageBackend,*Worker,ftp/*) ca regenerarea să nu o strice. Evenimentappend-only — forensic; conectorul GLog nu înlocuiește jurnalul intern.- Coliziune de nume ES — evitată prin hand-wiring (nu declarăm
searchîn JDL).