14-fazni document pipeline: virus skeniranje, AI klasifikacija i automatsko objavljivanje

· 7 min
Sadržaj

Svaki dokument koji prolazi kroz studenti.rs platform mora proći 14 koraka pre nego što korisnik vidi "Objavljeno."

Virus skeniranje. AI klasifikacija. PDF redakcija. HTML preview generisanje. Deduplication provera. Finalna AI kapija. R2 upload. WordPress callback.

Svaki korak može da failuje. Sistem mora da zna šta da radi kada se to desi.

Šest faza, jedan cilj

Faza 1: Upload & Init (3-7 sekundi)

Korisnik attachuje dokument. Frontend šalje na /post/init.

Šta se dešava:

  1. ClamAV skenira za malware
  2. Konverzija u PDF (ako je DOC/DOCX/PPT/etc.)
  3. Ekstrakcija markdown-a za AI
  4. Gatekeeper provera: da li je akademski sadržaj?
  5. Ako prođe: puna AI klasifikacija
  6. Vraća jobId + klasifikaciju frontend-u

Korisnik vidi prepopulisan form za 3-7 sekundi.

Faza 2: Form Review

Korisnik pregleda AI sugestije. Može da edituje naslov, kategoriju, ključne reči. Može da promeni instituciju.

Ovo je human-in-the-loop trenutak. AI predlaže, čovek odlučuje.

Faza 3: Draft Creation

Form submit → WordPress kreira draft post sa ACF poljima. save_post hook triggeruje /post/review (async, fire-and-forget).

Faza 4: Full Processing

Node API obrađuje dokument u pozadini:

  1. PDF redakcija (30% preview za neregistrovane)
  2. PDF kompresija
  3. Generisanje slika stranica
  4. PDF → HTML preview
  5. Thumbnail generisanje
  6. AutoRAG markdown za pretragu
  7. Deduplication provera (MinHash/LSH)
  8. Finalna AI kapija

Faza 5: Final Gate Decision

AI donosi finalnu odluku:

  • GO → objavi
  • NO_GO + low risk → drži za admin pregled
  • NO_GO + high risk → automatski trash

Faza 6: Publish/Schedule

  • PRO korisnici: odmah objavljeno
  • Non-PRO: zakazano 3-7 dana unapred (random, 10-min cache granice)

Arhitektura: Šest servisa

Browser → Caddy (:443) → PHP-FPM (wordpress:9000) → MariaDB
                      → Node API (node-api:3005) → Gemini / R2
                                                 → ClamAV (:3310)

Caddy: Reverse proxy, HTTPS terminacija, statički fajlovi
PHP-FPM 8.3: WordPress (tema + plugin)
MariaDB 11: Database sa 50GB external volume
Node API: Hono + TypeScript, document processing
ClamAV: Malware skeniranje
Redis 7: Cache, 50-70% manje upita

Tri bucket-a na R2

Bucket Sadržaj Pristup
studenti-private Original PDF, redacted PDF, metadata.json Signed URL (60s TTL)
studenti-public HTML preview, thumbnails, page images Public CDN
studenti-rag RAG-optimized markdown Internal only

Konvencija putanja: {postId}/original.pdf, {postId}/redacted.pdf, {postId}/index.html

ClamAV: Prva linija odbrane

Pre nego što AI dodirne dokument, ClamAV skenira.

// clamav-scan.ts
const scanResult = await clamavClient.scanBuffer(fileBuffer);
if (scanResult.isInfected) {
  throw new Error(`Malware detected: ${scanResult.viruses.join(', ')}`);
}

ClamAV trči kao Docker sidecar na portu 3310. Timeout: 60 sekundi.

Ako je ClamAV nedostupan: upload prolazi sa warning logom (fail-open za availability). Kompromis: bolje dozvoliti upload nego blokirati ceo sistem.

Ali AI je fail-closed. Ako Gemini ne radi, ništa ne prolazi.

Dozvoljeni formati dokumenata

Samo sigurni document formati:

Ekstenzija MIME Type
.pdf application/pdf
.doc application/msword
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
.ppt application/vnd.ms-powerpoint
.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
.odt application/vnd.oasis.opendocument.text
.odp application/vnd.oasis.opendocument.presentation
.rtf application/rtf, text/rtf

Isključeni rizični formati: SVG, HTML, XML, slike, spreadsheets.

Prepoznavanje duplikata: MinHash/LSH

Pre finalne kapije, provera za duplikate.

Algoritam:

  1. Generiši MinHash fingerprint dokumenta
  2. LSH (Locality Sensitive Hashing) za O(1) candidate retrieval
  3. Exact match (SHA-256) → automatski reject
  4. High similarity (≥0.98) → automatski reject
  5. Near-duplicate (0.85-0.98) → Gemini verifikacija

Per-user violation tracking: repeat offenders dobijaju stricter thresholds.

Ako je duplikat: short-circuit pipeline → TRASH callback sa reason 'duplicate'.

Email notifikacije: 12 šablona

Za korisnike:

  • upload-received — Odmah posle upload-a
  • document-approved-published — Kada je objavljeno (PRO)
  • document-approved-scheduled — Kada je odobreno ali zakazano (non-PRO)
  • document-published — Kada zakazani post ide live
  • document-rejected — Kada ne prođe review

Za admine:

  • admin-upload — Novi dokument uploadovan
  • admin-published — Dokument objavljen
  • admin-review — Potreban manualni pregled

Svi šabloni su React Email komponente. Preview: pnpm --filter studenti-emails dev.

PRO vs Non-PRO: Različiti flow-ovi

Aspekt PRO Non-PRO
Post status publish (odmah) future (zakazano)
Schedule 3-7 dana random
Email direct link zakazani datum
Profile visibility odmah u "Objave" "Zakazano" badge

Non-PRO delay ima dva razloga:

  1. Incentive za PRO (odmah objavljeno)
  2. Batch processing za admin review

Fail-closed dizajn

Svaki kritičan korak ima fail-safe:

Korak Ako failuje
ClamAV Warning log, nastavi (availability > security)
Gemini gatekeeper Reject sa retry message
Gemini classification Reject sa retry message
R2 upload Retry 3x, onda fail
WordPress callback Retry queue, alert admin
Dedup check Skip, nastavi (new docs still processed)

Filozofija: bolje odbiti legitiman dokument nego objaviti problematičan.

Monitoring: 4 signala

Signal Šta prati
Stuck Jobs Drafts stuck >60min bez R2 upload-a
Daily Diff Pipeline input vs output states
R2 Verify HTTP HEAD ka CDN-u za objavljene postove
Failed Callbacks Node API callback failures

Alert tipovi sa rate limiting-om:

  • stuck_critical (>4h): 30 min
  • stuck_warning (>1h): 1 sat
  • daily_anomaly (>5% stuck): 24 sata
  • r2_missing: 30 min
  • pipeline_down (no uploads 24h): 1 sat

Pipeline za dokumente nije samo "upload fajl, sačuvaj u bazu."

To je 14 koraka gde svaki može da failuje. ClamAV, Gemini, R2, WordPress — sve mora da radi. A kada ne radi, sistem mora da zna šta da radi.

Fail-closed dizajn znači da greške nikada tiho ne prolaze. Korisnik zna da nešto nije u redu. Admin zna gde da traži.

Pipeline postoji zato što Stripe treba da vidi moderaciju. Svaka faza je signal poverenja.