Sari la conținut

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).

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.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):

codpoate_scrienivel_maxim
user_readfalsePERSONAL
user_writetruePERSONAL
admintruePERSONAL
admin_securizattrueSECRET

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
);
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)
);
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_id referă template(id); declararea template apare după document în JDL, dar Liquibase creează tabelele + FK în ordine corectă. (În DDL manual de mai sus, creați template înainte de FK-ul din document sau 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)
);
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.

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”.

-- căutare/listare documente
CREATE 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);
-- audit
CREATE 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();

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 TIMESTAMP

7. 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:

EntitateCâmpConținut
Documentetichetelistă de keywords ["...","..."]
Documentmetadatemetadate libere per document
Templatecontinutcorpul șablonului (dacă nu e în object-store)
ConversierezultatMetanr. pagini, scor OCR, avertismente
EvenimentDocumentpayloadsnapshot-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_indexat rămâne TEXT (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_securizat deschide un L3 doar cu authority Keycloak ROLE_SECRET ȘI un grant explicit pe acel document (partajare_document.beneficiar_operator_id). Nimic „blanket”; fiecare acces L3 e jurnalizat. → coloane beneficiar_operator_id / solicitant_operator_id adăugate (§2.7-2.8).
  • /api/v1 (mașină-mașină) — token de serviciu Keycloak (OAuth2 client-credentials, realm gdocs, scope-uri mapate pe niveluri de acces). NU mTLS.
  • JSONB — AMÂNAT: @Lob text pentru demo; jsonb = hardening (§7).
  • Etichete/keywords — jsonb array pe document (fațete în Elasticsearch); FĂRĂ tabel eticheta M2M.

Decizii închise (2026-07-16, runda 2) — detalii în platform_design.md §11:

  • Unicitate (tip, identificator) pe client — 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 din rol.nivel_maxim=SECRET (authority-mapper Spring); rol ră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 și eveniment_document se păstrează permanent (niciodată hard-delete — FK required + append-only).

Toate deciziile sunt închise — schema e gata pentru jhipster jdl.