Namespace UsluzionicaServer.Infrastructure
Classes
- BoostExpiryService
Background servis koji svakih sat vremena pronalazi i deaktivira istekle boostove.
Logika pri isticanju boosta: listing.BoostScore -= boost.TokensSpent / boost.DurationDays boost.IsActive = false
Ako listing više nema ni jednog aktivnog boosta: listing.IsBoosted = false listing.BoostScore = 0 (cleanup floating point ostataka) listing.BoostExpiresAt = null
Koristi IServiceScopeFactory jer je BackgroundService singleton a AppDbContext je scoped — ne smeju se mešati lifetime-ovi.
- MessageCleanupService
Background servis koji svake noći u ponoć briše poruke starije od N dana. N se konfiguriše u appsettings.json kao "MessageRetentionDays" (default: 14).
Briše se samo sadržaj poruka (Message entiteti) — Conversation zapisi ostaju zauvek, tako da korisnici mogu videti sa kim su pričali.
Koristi IServiceScopeFactory jer je BackgroundService singleton, a AppDbContext je scoped — ne smeju se mešati životni vekovi.
- MessageEncryption
Simetrična AES-256-CBC enkripcija teksta poruka.
Svaka poruka se enkriptuje sa random IV (Initialization Vector) koji se dodaje ispred ciphertexta i sve zajedno se čuva kao Base64 string. Format u bazi: Base64( [16 bajta IV] + [N bajta ciphertext] )
Ovo je "encryption at rest" — štiti sadržaj poruka ako neko dobije direktan pristup SQL bazi. Server i dalje može da čita poruke (nije E2E).
- OnlineTracker
Praćenje online korisnika i njihovih SignalR konekcija.
ZAŠTO OVO MORA U REDIS: SignalR konekcija je vezana za JEDAN proces. Sa dve instance API-ja iza load balancera, Ana je konektovana na instancu A, Marko na instancu B. Kad Marko otvori listu razgovora, njegov zahtev obrađuje instanca B — koja u svojoj memoriji nema pojma o Aninoj konekciji i prikaže je kao offline. Uvek.
Redis drži to stanje van procesa, pa obe instance vide isto.
STRUKTURA: Redis SET po korisniku. usluzionica:online:{userId} → { connectionId1, connectionId2, ... } Skup jer isti korisnik može biti prijavljen sa telefona i sa laptopa istovremeno — offline je tek kad NIJEDNA konekcija ne ostane.
TTL na ključu je zaštita od procesa koji padne:
OnDisconnectedAsyncse tada nikad ne izvrši i connectionId bi zauvek ostao u skupu, pa bi korisnik večno bio „online". TTL se obnavlja pri svakoj registraciji.FALLBACK: bez Redis-a radi tačno kao pre — ConcurrentDictionary u memoriji. To je ispravno za jednu instancu i drži testove zelenim.
- PolicyVersion
Verzija politike privatnosti koja je trenutno na snazi.
ZAŠTO KONSTANTA A NE DATUM IZMENE FAJLA: politika živi na sajtu (usluzionica.rs/policy), van ovog repoa. Server ne može da zna kad se promenila — mora mu se reći.
Oblik je „GGGG-MM". Dovoljno grubo da se ne menja zbog ispravke tipfelera, dovoljno precizno da se zna o kojoj je verziji reč.
KAD SE POLITIKA SUŠTINSKI IZMENI:
- promeni ovu konstantu
- korisnici sa starijom verzijom se prepoznaju upitom: WHERE PolicyVersionAccepted IS NULL OR PolicyVersionAccepted <> '2026-09'
- njima se pri sledećoj prijavi traži nova saglasnost
Korak 3 još nije implementiran — dolazi kad politika prvi put bude menjana. Do tada se vrednost samo beleži, što je dovoljno da se saglasnost dokaže.
- SecretsGuard
Proverava na startu da su sve obavezne tajne popunjene i da nisu ostale na placeholder vrednostima. Pada odmah, sa jasnom porukom šta nedostaje — bolje nego da server krene pa tek na prvom loginu pukne, ili (gore) da radi sa slabim/podrazumevanim ključem u produkciji.
- SerbianMunicipalities
Statička lista svih opština u Srbiji. Korisnik bira lokaciju iz ove liste — garantuje konzistentnost podataka u bazi.