DB Schema — GDocs (v1)
Acest conținut nu este încă disponibil în limba selectată.
Schema derivată din ../gdocs.jdl pentru verificare manuală
înainte de jhipster jdl ../gdocs.jdl --force.
Tipuri de date: PostgreSQL (profil prod). H2 (dev) folosește mapări
echivalente generate de Liquibase. Toate tabelele moștenesc 4 câmpuri de audit
de la AbstractAuditingEntity (omise din DDL-urile de mai jos pentru
lizibilitate — vezi §6). Numele coloanelor sunt snake_case (convenția
JHipster); enum-urile se stochează ca varchar (numele constantei).
Fișierele binare nu se stochează în DB — storage_uri este cheia obiectului
în object-store (MinIO/S3).
1. ER diagram
Section titled “1. ER diagram”erDiagram CLIENT ||--o{ DOCUMENT : "proprietar" EMITENT ||--o{ DOCUMENT : "emite" APP_USER ||--o{ DOCUMENT : "încarcă" TEMPLATE ||--o{ DOCUMENT : "generat din" EMITENT ||--o{ TEMPLATE : "publică" CLIENT ||--o{ APP_USER : "reprezentat de" ROL ||--o{ APP_USER : "are rol" DOCUMENT ||--o{ PARTAJARE_DOCUMENT : "" CLIENT ||--o{ PARTAJARE_DOCUMENT : "beneficiar" DOCUMENT ||--o{ CERERE_ACCES : "" CLIENT ||--o{ CERERE_ACCES : "solicitant" DOCUMENT ||--o{ CONVERSIE : "" APP_USER ||--o{ CONVERSIE : "inițiază" DOCUMENT ||--o{ SEMNATURA_DOCUMENT : "" DOCUMENT ||--o{ EVENIMENT_DOCUMENT : "audit append-only"2. Tabele de domeniu (DDL)
Section titled “2. Tabele de domeniu (DDL)”2.1 client — subiectul documentului (PF/PJ/ONG/OGOV)
Section titled “2.1 client — subiectul documentului (PF/PJ/ONG/OGOV)”CREATE TABLE client ( id BIGSERIAL PRIMARY KEY, tip VARCHAR(16) NOT NULL, -- enum TipClient identificator VARCHAR(64) NOT NULL, -- IDNP/IDNO (poate conține litere) nume VARCHAR(255) NOT NULL, prenume VARCHAR(255), adresa VARCHAR(512), telefon VARCHAR(32), email VARCHAR(255), CONSTRAINT uq_client_tip_ident UNIQUE (tip, identificator) -- vezi §4);2.2 rol — rol funcțional (administrabil)
Section titled “2.2 rol — rol funcțional (administrabil)”CREATE TABLE rol ( id BIGSERIAL PRIMARY KEY, cod VARCHAR(32) NOT NULL UNIQUE, -- user_read / user_write / admin / admin_securizat denumire VARCHAR(64) NOT NULL, poate_scrie BOOLEAN NOT NULL, nivel_maxim VARCHAR(16) NOT NULL -- enum NivelAcces);Seed inițial (changelog dedicat):
| cod | poate_scrie | nivel_maxim |
|---|---|---|
user_read | false | PERSONAL |
user_write | true | PERSONAL |
admin | true | PERSONAL |
admin_securizat | true | SECRET |
2.3 app_user — operator / utilizator de business (≠ jhi_user)
Section titled “2.3 app_user — operator / utilizator de business (≠ jhi_user)”CREATE TABLE app_user ( id BIGSERIAL PRIMARY KEY, username VARCHAR(64) NOT NULL UNIQUE, email VARCHAR(255), statut VARCHAR(16) NOT NULL, -- enum StatutUser id_sec_local VARCHAR(64), client_id BIGINT REFERENCES client(id), rol_id BIGINT NOT NULL REFERENCES rol(id), emitent_id BIGINT REFERENCES emitent(id) -- apartenență (acces de grup));2.4 emitent — organizație emitentă (acces de grup)
Section titled “2.4 emitent — organizație emitentă (acces de grup)”CREATE TABLE emitent ( id BIGSERIAL PRIMARY KEY, denumire VARCHAR(255) NOT NULL, tip VARCHAR(16) NOT NULL, -- enum TipClient idno VARCHAR(13), acces_grup BOOLEAN);2.5 document — nucleul platformei
Section titled “2.5 document — nucleul platformei”CREATE TABLE document ( id BIGSERIAL PRIMARY KEY, cod VARCHAR(64) NOT NULL UNIQUE, -- server-side: DOC-<an>-<secv. 6 cifre> denumire VARCHAR(255) NOT NULL, descriere TEXT, format VARCHAR(16) NOT NULL, -- enum FormatDocument extensie VARCHAR(16), dimensiune BIGINT, -- bytes storage_uri VARCHAR(512), -- cheie MinIO/S3 (NU binar în DB); -- obligatoriu la creare (app-level), -- golit (NULL) la purjarea binarului hash_sha256 VARCHAR(64), tip_securitate VARCHAR(16) NOT NULL, -- enum TipSecuritate password_hash VARCHAR(255), -- doar când tip_securitate=PAROLA nivel_acces VARCHAR(16) NOT NULL, -- enum NivelAcces (L1/L2/L3) statut VARCHAR(16) NOT NULL, -- enum StatutDocument data_emiterii DATE, data_expirarii DATE, arhivat_la TIMESTAMP, sters_la TIMESTAMP, -- trash bin: purjare la +30 zile text_indexat TEXT, -- text extras (OCR) → Elasticsearch etichete TEXT, -- jsonb după §7 (keywords) metadate TEXT, -- jsonb după §7 proprietar_id BIGINT NOT NULL REFERENCES client(id), emitent_id BIGINT REFERENCES emitent(id), incarcat_de_id BIGINT REFERENCES app_user(id), template_id BIGINT REFERENCES template(id));2.6 template — șablon de document
Section titled “2.6 template — șablon de document”CREATE TABLE template ( id BIGSERIAL PRIMARY KEY, cod VARCHAR(64) NOT NULL UNIQUE, denumire VARCHAR(255) NOT NULL, format VARCHAR(16) NOT NULL, -- enum FormatDocument limba VARCHAR(16), continut TEXT, -- jsonb după §7 (sau storage_uri) storage_uri VARCHAR(512), publicat BOOLEAN NOT NULL, -- NU `public` (cuvânt rezervat SQL) emitent_id BIGINT REFERENCES emitent(id));Notă:
document.template_idreferătemplate(id); declarareatemplateapare dupădocumentîn JDL, dar Liquibase creează tabelele + FK în ordine corectă. (În DDL manual de mai sus, creațitemplateînainte de FK-ul dindocumentsau adăugați FK-ul separat.)
2.7 partajare_document — grant de partajare
Section titled “2.7 partajare_document — grant de partajare”CREATE TABLE partajare_document ( id BIGSERIAL PRIMARY KEY, drept VARCHAR(16) NOT NULL, -- enum DreptAcces acordat_de VARCHAR(64), data_acordare TIMESTAMP NOT NULL, data_expirare TIMESTAMP, document_id BIGINT NOT NULL REFERENCES document(id), beneficiar_id BIGINT REFERENCES client(id), -- partajare normală (L1/L2) beneficiar_operator_id BIGINT REFERENCES app_user(id) -- grant L3 către admin_securizat -- exact UNUL dintre beneficiar_id / beneficiar_operator_id este setat -- (validat la nivel aplicație; grantul L3 către operator cere și ROLE_SECRET));2.8 cerere_acces — cerere de acces la L2/L3
Section titled “2.8 cerere_acces — cerere de acces la L2/L3”CREATE TABLE cerere_acces ( id BIGSERIAL PRIMARY KEY, drept_cerut VARCHAR(16) NOT NULL, -- enum DreptAcces nivel_cerut VARCHAR(16), -- enum NivelAcces motivare TEXT, statut VARCHAR(16) NOT NULL, -- enum StatutCerere data_cerere TIMESTAMP NOT NULL, data_solutionare TIMESTAMP, solutionat_de VARCHAR(64), document_id BIGINT NOT NULL REFERENCES document(id), solicitant_id BIGINT REFERENCES client(id), solicitant_operator_id BIGINT REFERENCES app_user(id) -- exact UNUL dintre solicitant_id / solicitant_operator_id (validat la app));2.9 conversie — transcriere / OCR
Section titled “2.9 conversie — transcriere / OCR”CREATE TABLE conversie ( id BIGSERIAL PRIMARY KEY, tip VARCHAR(24) NOT NULL, -- enum TipConversie sursa_uri VARCHAR(512), rezultat_uri VARCHAR(512), statut VARCHAR(16) NOT NULL, -- enum StatutConversie eroare TEXT, rezultat_meta TEXT, -- jsonb după §7 data_initiere TIMESTAMP NOT NULL, data_finalizare TIMESTAMP, document_id BIGINT REFERENCES document(id), initiat_de_id BIGINT REFERENCES app_user(id));2.10 semnatura_document — semnătură (MSign, demo)
Section titled “2.10 semnatura_document — semnătură (MSign, demo)”CREATE TABLE semnatura_document ( id BIGSERIAL PRIMARY KEY, semnatar VARCHAR(255) NOT NULL, secret VARCHAR(255), valoare VARCHAR(2048), statut VARCHAR(16) NOT NULL, -- enum StatutSemnatura data_semnare TIMESTAMP, document_id BIGINT NOT NULL REFERENCES document(id));2.11 eveniment_document — jurnal de audit append-only
Section titled “2.11 eveniment_document — jurnal de audit append-only”CREATE TABLE eveniment_document ( id BIGSERIAL PRIMARY KEY, tip VARCHAR(24) NOT NULL, -- enum TipEvenimentDoc actor VARCHAR(64) NOT NULL, data TIMESTAMP NOT NULL, payload TEXT, -- jsonb după §7 document_id BIGINT NOT NULL REFERENCES document(id));2.12 versiune_document — istoricul versiunilor de conținut
Section titled “2.12 versiune_document — istoricul versiunilor de conținut”CREATE TABLE versiune_document ( id BIGSERIAL PRIMARY KEY, numar_versiune INTEGER NOT NULL, -- 1, 2, 3, … storage_uri VARCHAR(512) NOT NULL, -- cheie versionată: uploads/<cod>/v<n> hash_sha_256 VARCHAR(64), dimensiune BIGINT, extensie VARCHAR(16), format VARCHAR(255), -- enum FormatDocument content_type VARCHAR(255), tip_securitate VARCHAR(255) NOT NULL, -- oglindește Document.tipSecuritate comentariu TEXT, creat_de VARCHAR(64), creat_la TIMESTAMP NOT NULL, curenta BOOLEAN, document_id BIGINT NOT NULL REFERENCES document(id));CREATE INDEX idx_versiune_document__document_id ON versiune_document (document_id);Document.storageUri indică versiunea curentă (curenta = true). Vechile octeți nu se
suprascriu (cheie per versiune). Restaurarea e forward-only: creează o versiune nouă cu
conținutul celei alese. Changelog Liquibase: 20260929120000_added_entity_VersiuneDocument.xml.
3. Tabele built-in JHipster (auth)
Section titled “3. Tabele built-in JHipster (auth)”Generate automat: jhi_user, jhi_authority, jhi_user_authority,
jhi_persistent_audit_event, jhi_persistent_audit_evt_data. jhi_user este
identitatea de autentificare (corelată cu Keycloak, realm gdocs) — distinctă
de app_user (operatorul de business). Vezi convenția „AppUser ≠ jhi_user”.
4. Indici suplimentari recomandați
Section titled “4. Indici suplimentari recomandați”-- căutare/listare documenteCREATE INDEX ix_document_nivel_statut ON document (nivel_acces, statut);CREATE INDEX ix_document_proprietar ON document (proprietar_id);CREATE INDEX ix_document_emitent ON document (emitent_id);CREATE INDEX ix_document_expirarii ON document (data_expirarii);CREATE INDEX ix_document_sters_la ON document (sters_la); -- job purjare trash
-- partajări / cereri (beneficiarul e Client SAU operator → două indexuri)CREATE INDEX ix_partajare_doc_benef ON partajare_document (document_id, beneficiar_id);CREATE INDEX ix_partajare_doc_oper ON partajare_document (document_id, beneficiar_operator_id);CREATE INDEX ix_cerere_statut ON cerere_acces (statut);
-- auditCREATE INDEX ix_eveniment_document ON eveniment_document (document_id, data);
-- unicitate compusă (JDL nu suportă unique compus → changelog dedicat)ALTER TABLE client ADD CONSTRAINT uq_client_tip_ident UNIQUE (tip, identificator);Pentru căutarea full-text la scară se folosește Elasticsearch (mirror pe
document + template), nu tsvector — dar un index GIN pe text_indexat
poate fi util pentru fallback.
5. Trigger append-only pentru eveniment_document
Section titled “5. Trigger append-only pentru eveniment_document”Pe lângă garda din repository (listener Hibernate AppendOnlyAuditGuard), o
plasă de siguranță la nivel de DB:
CREATE OR REPLACE FUNCTION eveniment_document_append_only()RETURNS TRIGGER AS $$BEGIN RAISE EXCEPTION 'eveniment_document este append-only (% interzis)', TG_OP;END;$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_eveniment_document_no_update BEFORE UPDATE OR DELETE ON eveniment_document FOR EACH ROW EXECUTE FUNCTION eveniment_document_append_only();6. Câmpuri standard de audit
Section titled “6. Câmpuri standard de audit”Toate entitățile moștenesc de la AbstractAuditingEntity:
created_by VARCHAR(50) NOT NULL, created_date TIMESTAMP, last_modified_by VARCHAR(50), last_modified_date TIMESTAMP7. Conversie JSONB (AMÂNATĂ — pas de hardening)
Section titled “7. Conversie JSONB (AMÂNATĂ — pas de hardening)”Decizie: pentru demo, câmpurile JSON rămân @Lob text (TEXT care conține
JSON) — gdocs nu interoghează în interiorul lor la runtime (fațetele de keywords
vin din Elasticsearch, nu din DB). Conversia la jsonb de mai jos este un pas de
hardening documentat, NU se aplică în demo (ca gnotify). Câmpuri vizate:
| Entitate | Câmp | Conținut |
|---|---|---|
Document | etichete | listă de keywords ["...","..."] |
Document | metadate | metadate libere per document |
Template | continut | corpul șablonului (dacă nu e în object-store) |
Conversie | rezultatMeta | nr. pagini, scor OCR, avertismente |
EvenimentDocument | payload | snapshot-ul evenimentului |
Exemplu de changelog + adnotare:
<changeSet id="document-etichete-jsonb" author="gdocs"> <sql>ALTER TABLE document ALTER COLUMN etichete TYPE jsonb USING etichete::jsonb;</sql></changeSet>@JdbcTypeCode(SqlTypes.JSON)@Column(columnDefinition = "jsonb")private String etichete;
text_indexatrămâneTEXT(text brut extras, indexat în ES), NU jsonb.
8. Checklist de revizuit (înainte de generare / hardening)
Section titled “8. Checklist de revizuit (înainte de generare / hardening)”Decizii închise (2026-07-16):
- Nivel 3 acordat —
admin_securizatdeschide un L3 doar cu authority KeycloakROLE_SECRETȘI un grant explicit pe acel document (partajare_document.beneficiar_operator_id). Nimic „blanket”; fiecare acces L3 e jurnalizat. → coloanebeneficiar_operator_id/solicitant_operator_idadăugate (§2.7-2.8). -
/api/v1(mașină-mașină) — token de serviciu Keycloak (OAuth2 client-credentials, realmgdocs, scope-uri mapate pe niveluri de acces). NU mTLS. - JSONB — AMÂNAT:
@Lobtext pentru demo;jsonb= hardening (§7). - Etichete/keywords —
jsonbarray pedocument(fațete în Elasticsearch); FĂRĂ tabeletichetaM2M.
Decizii închise (2026-07-16, runda 2) — detalii în platform_design.md §11:
- Unicitate
(tip, identificator)peclient— changelog dedicat (§4). - Generarea
cod— server-side:DOC-<an>-<secvență 6 cifre>(document),TPL-<an>-<secvență>(template); secvență DB per an. -
ROLE_SECRET— derivată la login dinrol.nivel_maxim=SECRET(authority-mapper Spring);rolrămâne unica sursă de adevăr, fără mapare separată în Keycloak. - Purjare trash + comprimare arhivă — hibrid:
@Scheduled(detecție zilnică) enqueue-ază → workeri RabbitMQ (comprimare/purjare). - Object-store — MinIO (dev) → S3-compatibil (prod); descărcare prin backend pentru L2/L3 (ACL), URL pre-semnate doar pentru L1.
- Ștergere/retenție — soft-delete
statut=STERS; la +30 zile se șterge binarul +storage_uri=NULL, dar rândul rămâne tombstone șieveniment_documentse păstrează permanent (niciodată hard-delete — FK required + append-only).
Toate deciziile sunt închise — schema e gata pentru jhipster jdl.