Table of Contents

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: OnDisconnectedAsync se 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:

  1. promeni ovu konstantu
  2. korisnici sa starijom verzijom se prepoznaju upitom: WHERE PolicyVersionAccepted IS NULL OR PolicyVersionAccepted <> '2026-09'
  3. 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.