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:
- ClamAV skenira za malware
- Konverzija u PDF (ako je DOC/DOCX/PPT/etc.)
- Ekstrakcija markdown-a za AI
- Gatekeeper provera: da li je akademski sadržaj?
- Ako prođe: puna AI klasifikacija
- 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:
- PDF redakcija (30% preview za neregistrovane)
- PDF kompresija
- Generisanje slika stranica
- PDF → HTML preview
- Thumbnail generisanje
- AutoRAG markdown za pretragu
- Deduplication provera (MinHash/LSH)
- Finalna AI kapija
Faza 5: Final Gate Decision
AI donosi finalnu odluku:
GO→ objaviNO_GO+ low risk → drži za admin pregledNO_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:
- Generiši MinHash fingerprint dokumenta
- LSH (Locality Sensitive Hashing) za O(1) candidate retrieval
- Exact match (SHA-256) → automatski reject
- High similarity (≥0.98) → automatski reject
- 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-adocument-approved-published— Kada je objavljeno (PRO)document-approved-scheduled— Kada je odobreno ali zakazano (non-PRO)document-published— Kada zakazani post ide livedocument-rejected— Kada ne prođe review
Za admine:
admin-upload— Novi dokument uploadovanadmin-published— Dokument objavljenadmin-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 |
| direct link | zakazani datum | |
| Profile visibility | odmah u "Objave" | "Zakazano" badge |
Non-PRO delay ima dva razloga:
- Incentive za PRO (odmah objavljeno)
- 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 minstuck_warning(>1h): 1 satdaily_anomaly(>5% stuck): 24 satar2_missing: 30 minpipeline_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.