diff --git a/cmd/ntech/main.go b/cmd/ntech/main.go index 602c1a6..66a9a5e 100644 --- a/cmd/ntech/main.go +++ b/cmd/ntech/main.go @@ -234,7 +234,7 @@ func main() { r.Get("/admin/profil/totp/pokreni", h.AdminTotpPokreni) r.Post("/admin/profil/totp/aktiviraj", h.AdminTotpAktivacija) r.Post("/admin/profil/totp/deaktiviraj", h.AdminTotpDeaktivacija) - r.Post("/profil/tema", h.AdminSacuvajLokalnuTemu) + r.Post("/profil/tema", h.SacuvajLokalnuTemu) r.Get("/profil/tema", h.ProfilTema) r.Post("/profil/pozadina", h.ProfilOtpremiPozadinu) r.Post("/profil/pozadina/ukloni", h.ProfilUkloniPozadinu) diff --git a/internal/db/sqlite/korisnici.go b/internal/db/sqlite/korisnici.go index 9ccead6..fe6746d 100644 --- a/internal/db/sqlite/korisnici.go +++ b/internal/db/sqlite/korisnici.go @@ -11,6 +11,33 @@ import ( type sqliteKorisniciRepo struct{ db *sql.DB } +// skeniraiKorisnika čita jedan red iz baze i popunjava model.Korisnik +func skeniraiKorisnika(row interface{ Scan(...any) error }) (*model.Korisnik, error) { + k := &model.Korisnik{} + var aktivan, koristiLokalnuTemu int + var lokalnaTema sql.NullString + var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString + var datumKreiranja time.Time + if err := row.Scan( + &k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna, + &lokalnaTema, &koristiLokalnuTemu, &datumKreiranja, + &lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, + &lokalnaPozadinaBlurPozadine, &lokalnaPozadinaGlassOpacity, + ); err != nil { + return nil, err + } + k.Aktivan = aktivan == 1 + k.LokalnaTema = lokalnaTema.String + k.KoristiLokalnuTemu = koristiLokalnuTemu == 1 + k.DatumKreiranja = datumKreiranja + k.LokalnaPozadina = lokalnaPozadina.String + k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String + k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String + k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String + k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String + return k, nil +} + // NoviKorisniciRepo kreira SQLite implementaciju KorisniciRepository func NoviKorisniciRepo(db *sql.DB) *sqliteKorisniciRepo { return &sqliteKorisniciRepo{db: db} @@ -28,67 +55,32 @@ func (r *sqliteKorisniciRepo) Kreiraj(ctx context.Context, korisnickoIme, lozink } func (r *sqliteKorisniciRepo) DohvatiPoImenu(ctx context.Context, korisnickoIme string) (*model.Korisnik, error) { - k := &model.Korisnik{} - var aktivan, koristiLokalnuTemu int - var totpTajna, lokalnaTema sql.NullString - var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString - var datumKreiranja time.Time - err := r.db.QueryRowContext(ctx, + row := r.db.QueryRowContext(ctx, `SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''), COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja, COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'), COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'), COALESCE(lokalna_pozadina_glass_opacity, '10') - FROM korisnici WHERE korisnicko_ime = ?`, korisnickoIme). - Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &totpTajna, - &lokalnaTema, &koristiLokalnuTemu, &datumKreiranja, - &lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine, - &lokalnaPozadinaGlassOpacity) + FROM korisnici WHERE korisnicko_ime = ?`, korisnickoIme) + k, err := skeniraiKorisnika(row) if err != nil { return nil, fmt.Errorf("ntech: korisnici.DohvatiPoImenu: %w", err) } - k.Aktivan = aktivan == 1 - k.TotpTajna = totpTajna.String - k.LokalnaTema = lokalnaTema.String - k.KoristiLokalnuTemu = koristiLokalnuTemu == 1 - k.DatumKreiranja = datumKreiranja - k.LokalnaPozadina = lokalnaPozadina.String - k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String - k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String - k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String - k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String return k, nil } func (r *sqliteKorisniciRepo) DohvatiPoID(ctx context.Context, id int64) (*model.Korisnik, error) { - k := &model.Korisnik{} - var aktivan, koristiLokalnuTemu int - var lokalnaTema sql.NullString - var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString - var datumKreiranja time.Time - err := r.db.QueryRowContext(ctx, + row := r.db.QueryRowContext(ctx, `SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''), COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja, COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'), COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'), COALESCE(lokalna_pozadina_glass_opacity, '10') - FROM korisnici WHERE id = ?`, id). - Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna, - &lokalnaTema, &koristiLokalnuTemu, &datumKreiranja, - &lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine, - &lokalnaPozadinaGlassOpacity) + FROM korisnici WHERE id = ?`, id) + k, err := skeniraiKorisnika(row) if err != nil { return nil, fmt.Errorf("ntech: korisnici.DohvatiPoID: %w", err) } - k.Aktivan = aktivan == 1 - k.LokalnaTema = lokalnaTema.String - k.KoristiLokalnuTemu = koristiLokalnuTemu == 1 - k.DatumKreiranja = datumKreiranja - k.LokalnaPozadina = lokalnaPozadina.String - k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String - k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String - k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String - k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String return k, nil } diff --git a/internal/handler/admin.go b/internal/handler/admin.go index 5efb5f8..61e4ad2 100644 --- a/internal/handler/admin.go +++ b/internal/handler/admin.go @@ -305,44 +305,6 @@ func (h *Handler) AdminProfil(w http.ResponseWriter, r *http.Request) { }) } -// AdminSacuvajLokalnuTemu čuva korisnikovu lokalnu temu -func (h *Handler) AdminSacuvajLokalnuTemu(w http.ResponseWriter, r *http.Request) { - k := middleware.KorisnikIzKonteksta(r.Context()) - if k == nil { - http.Redirect(w, r, "/prijava", http.StatusSeeOther) - return - } - if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "tema.lokalno") { - http.Error(w, "Pristup odbijen", http.StatusForbidden) - return - } - - if err := r.ParseForm(); err != nil { - middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.") - http.Redirect(w, r, "/admin/profil", http.StatusSeeOther) - return - } - - koristi := r.FormValue("koristi_lokalnu_temu") == "1" - lokalnaTema := r.FormValue("lokalna_tema") - if lokalnaTema != "tamna" && lokalnaTema != "svetla" { - lokalnaTema = "tamna" - } - - if err := h.KorisniciRepo.SacuvajLokalnuTemu(r.Context(), k.ID, lokalnaTema, koristi); err != nil { - middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.") - http.Redirect(w, r, "/admin/profil", http.StatusSeeOther) - return - } - - middleware.SetFlash(w, r, h.DB, "uspeh", "Tema je sačuvana.") - // vrati korisnika na stranicu odakle je došao (Referer), ili na profil kao fallback - if ref := r.Referer(); ref != "" { - http.Redirect(w, r, ref, http.StatusSeeOther) - return - } - http.Redirect(w, r, "/admin/profil", http.StatusSeeOther) -} // AdminPromeniLozinku menja lozinku prijavljenog korisnika func (h *Handler) AdminPromeniLozinku(w http.ResponseWriter, r *http.Request) { diff --git a/internal/handler/dashboard.go b/internal/handler/dashboard.go index 9fec234..ba9f61c 100644 --- a/internal/handler/dashboard.go +++ b/internal/handler/dashboard.go @@ -70,7 +70,7 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) { if korisnikDash.Uloga == "radnik" { korisnikFilter.KorisnikID = &korisnikDash.ID } - if n, err := h.PodsetniciFRepo.BrojAktivnih(ctx, korisnikFilter); err != nil { + if n, err := h.PodsetnikRepo.BrojAktivnih(ctx, korisnikFilter); err != nil { log.Printf("dashboard: aktivni podsetnici: %v", err) } else { aktivniPodsetnici = n diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 83906d0..5d9dac1 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -25,7 +25,7 @@ type Handler struct { ProdajaRepo db.ProdajaRepository KorisniciRepo db.KorisniciRepository SesijeRepo db.SesijeRepository - PodsetniciFRepo db.PodsetnikRepository + PodsetnikRepo db.PodsetnikRepository PokusajiRepo db.PokusajiPrijaveRepository LoginIstorijsaRepo db.LoginIstorijsaRepository DozvoleRepo db.DozvoleRepository @@ -47,15 +47,15 @@ func Novi(baza *sql.DB) *Handler { ProdajaRepo: sqlite.NoviProdajaRepo(baza), KorisniciRepo: sqlite.NoviKorisniciRepo(baza), SesijeRepo: sqlite.NoviSesijeRepo(baza), - PodsetniciFRepo: sqlite.NoviPodsetnikRepo(baza), + PodsetnikRepo: sqlite.NoviPodsetnikRepo(baza), PokusajiRepo: sqlite.NoviPokusajiPrijaveRepo(baza), LoginIstorijsaRepo: sqlite.NoviLoginIstorijsaRepo(baza), DozvoleRepo: sqlite.NoviDozvoleRepo(baza, middleware.ImaDozvolu, middleware.SveAkcije()), } } -// reinicijalzijRepozitorijume zamenjuje sve repozitorijume posle obnove baze -func (h *Handler) reinicijalzijRepozitorijume(novaDB *sql.DB) { +// reinicijalizujRepozitorijume zamenjuje sve repozitorijume posle obnove baze +func (h *Handler) reinicijalizujRepozitorijume(novaDB *sql.DB) { h.DB = novaDB h.Artikli = sqlite.NoviArtikalRepo(novaDB) h.KategorijeRepo = sqlite.NovaKategorijaRepo(novaDB) @@ -66,7 +66,7 @@ func (h *Handler) reinicijalzijRepozitorijume(novaDB *sql.DB) { h.ProdajaRepo = sqlite.NoviProdajaRepo(novaDB) h.KorisniciRepo = sqlite.NoviKorisniciRepo(novaDB) h.SesijeRepo = sqlite.NoviSesijeRepo(novaDB) - h.PodsetniciFRepo = sqlite.NoviPodsetnikRepo(novaDB) + h.PodsetnikRepo = sqlite.NoviPodsetnikRepo(novaDB) h.PokusajiRepo = sqlite.NoviPokusajiPrijaveRepo(novaDB) h.LoginIstorijsaRepo = sqlite.NoviLoginIstorijsaRepo(novaDB) h.DozvoleRepo = sqlite.NoviDozvoleRepo(novaDB, middleware.ImaDozvolu, middleware.SveAkcije()) diff --git a/internal/handler/podesavanja.go b/internal/handler/podesavanja.go index 30ee89e..1c3c585 100644 --- a/internal/handler/podesavanja.go +++ b/internal/handler/podesavanja.go @@ -82,27 +82,11 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) { Verzija: h.Verzija, LogoGreska: r.URL.Query().Get("logo_greska"), Backupi: ucitajListuBackupa(), - LoginPozadina: podesavanja["login_pozadina"], - LoginPozadinaOpacity: func() string { - v := podesavanja["login_pozadina_opacity"] - if v == "" { return "50" } - return v - }(), - LoginPozadinaBlurPozadine: func() string { - v := podesavanja["login_pozadina_blur_pozadine"] - if v == "" { return "0" } - return v - }(), - LoginPozadinaBlurKartice: func() string { - v := podesavanja["login_pozadina_blur_kartice"] - if v == "" { return "12" } - return v - }(), - LoginPozadinaZatamnjenjeKartice: func() string { - v := podesavanja["login_pozadina_zatamnjenje_kartice"] - if v == "" { return "0" } - return v - }(), + LoginPozadina: podesavanja["login_pozadina"], + LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"), + LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"), + LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"), + LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"), } h.renderujTemplate(w, "podesavanja", podaci) @@ -136,6 +120,14 @@ func ucitajListuBackupa() []BackupInfo { return lista } +// vrednostIliDefault vraća vrednost iz mape ako postoji i nije prazan string, inače vraća podrazumevanu vrednost +func vrednostIliDefault(m map[string]string, kljuc, podrazumevano string) string { + if v := m[kljuc]; v != "" { + return v + } + return podrazumevano +} + // VratiBackup zamenjuje trenutnu bazu sa izabranim backup fajlom func (h *Handler) VratiBackup(w http.ResponseWriter, r *http.Request) { k := middleware.KorisnikIzKonteksta(r.Context()) @@ -197,7 +189,7 @@ func (h *Handler) VratiBackup(w http.ResponseWriter, r *http.Request) { return } - h.reinicijalzijRepozitorijume(novaDB) + h.reinicijalizujRepozitorijume(novaDB) log.Printf("Baza uspešno obnovljena iz: %s", ime) http.Redirect(w, r, "/podesavanja?sacuvano=vraceno", http.StatusSeeOther) @@ -594,31 +586,11 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac Verzija: h.Verzija, LogoGreska: r.URL.Query().Get("logo_greska"), Backupi: ucitajListuBackupa(), - LoginPozadina: podesavanja["login_pozadina"], - LoginPozadinaOpacity: func() string { - if v := podesavanja["login_pozadina_opacity"]; v != "" { - return v - } - return "50" - }(), - LoginPozadinaBlurPozadine: func() string { - if v := podesavanja["login_pozadina_blur_pozadine"]; v != "" { - return v - } - return "0" - }(), - LoginPozadinaBlurKartice: func() string { - if v := podesavanja["login_pozadina_blur_kartice"]; v != "" { - return v - } - return "12" - }(), - LoginPozadinaZatamnjenjeKartice: func() string { - if v := podesavanja["login_pozadina_zatamnjenje_kartice"]; v != "" { - return v - } - return "0" - }(), + LoginPozadina: podesavanja["login_pozadina"], + LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"), + LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"), + LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"), + LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"), }, nil } diff --git a/internal/handler/podsetnici.go b/internal/handler/podsetnici.go index defdb41..ea18812 100644 --- a/internal/handler/podsetnici.go +++ b/internal/handler/podsetnici.go @@ -52,7 +52,7 @@ func (h *Handler) Podsetnici(w http.ResponseWriter, r *http.Request) { filter.KorisnikID = &k.ID } - lista, err := h.PodsetniciFRepo.Lista(r.Context(), filter) + lista, err := h.PodsetnikRepo.Lista(r.Context(), filter) if err != nil { http.Error(w, "Greška pri učitavanju podsetnika", http.StatusInternalServerError) return @@ -88,7 +88,7 @@ func (h *Handler) NoviPodsetnik(w http.ResponseWriter, r *http.Request) { ps.NaslovStranice = "Novi podsetnik" var korisnici []model.Korisnik - if k.Uloga == "admin" || k.Uloga == "superadmin" { + if middleware.JeAdmin(k) { korisnici, _ = h.KorisniciRepo.Lista(r.Context()) } @@ -110,32 +110,14 @@ func (h *Handler) SacuvajPodsetnik(w http.ResponseWriter, r *http.Request) { podsetnik, greska := parseFormuPodsetnika(r, k) - prikaziGresku := func(poruka string) { - podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) - ps := h.popuniPodaciStranice(r, podesavanja) - ps.Stranica = "podsetnici" - ps.NaslovStranice = "Novi podsetnik" - var korisnici []model.Korisnik - if k.Uloga == "admin" || k.Uloga == "superadmin" { - korisnici, _ = h.KorisniciRepo.Lista(r.Context()) - } - h.renderujFormuPodsetnika(w, podaciPodsetnikForma{ - PodaciStranice: ps, - Podsetnik: podsetnik, - Greska: poruka, - Izmena: false, - Korisnici: korisnici, - }) - } - if greska != "" { - prikaziGresku(greska) + h.prikaziGreskuPodsetnika(w, r, k, podsetnik, greska, false) return } - if _, err := h.PodsetniciFRepo.Kreiraj(r.Context(), &podsetnik); err != nil { + if _, err := h.PodsetnikRepo.Kreiraj(r.Context(), &podsetnik); err != nil { log.Printf("greška pri čuvanju podsetnika: %v", err) - prikaziGresku("Došlo je do greške pri čuvanju. Pokušajte ponovo.") + h.prikaziGreskuPodsetnika(w, r, k, podsetnik, "Došlo je do greške pri čuvanju. Pokušajte ponovo.", false) return } @@ -152,7 +134,7 @@ func (h *Handler) IzmeniPodsetnik(w http.ResponseWriter, r *http.Request) { return } - podsetnik, err := h.PodsetniciFRepo.DohvatiID(r.Context(), id) + podsetnik, err := h.PodsetnikRepo.DohvatiID(r.Context(), id) if err != nil { http.Error(w, "Podsetnik nije pronađen", http.StatusNotFound) return @@ -169,7 +151,7 @@ func (h *Handler) IzmeniPodsetnik(w http.ResponseWriter, r *http.Request) { ps.NaslovStranice = "Izmeni podsetnik" var korisnici []model.Korisnik - if k.Uloga == "admin" || k.Uloga == "superadmin" { + if middleware.JeAdmin(k) { korisnici, _ = h.KorisniciRepo.Lista(r.Context()) } @@ -199,32 +181,14 @@ func (h *Handler) SacuvajIzmenePodsetnika(w http.ResponseWriter, r *http.Request podsetnik, greska := parseFormuPodsetnika(r, k) podsetnik.ID = id - prikaziGresku := func(poruka string) { - podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) - ps := h.popuniPodaciStranice(r, podesavanja) - ps.Stranica = "podsetnici" - ps.NaslovStranice = "Izmeni podsetnik" - var korisnici []model.Korisnik - if k.Uloga == "admin" || k.Uloga == "superadmin" { - korisnici, _ = h.KorisniciRepo.Lista(r.Context()) - } - h.renderujFormuPodsetnika(w, podaciPodsetnikForma{ - PodaciStranice: ps, - Podsetnik: podsetnik, - Greska: poruka, - Izmena: true, - Korisnici: korisnici, - }) - } - if greska != "" { - prikaziGresku(greska) + h.prikaziGreskuPodsetnika(w, r, k, podsetnik, greska, true) return } - if err := h.PodsetniciFRepo.Izmeni(r.Context(), &podsetnik); err != nil { + if err := h.PodsetnikRepo.Izmeni(r.Context(), &podsetnik); err != nil { log.Printf("greška pri čuvanju izmene podsetnika: %v", err) - prikaziGresku("Došlo je do greške pri čuvanju. Pokušajte ponovo.") + h.prikaziGreskuPodsetnika(w, r, k, podsetnik, "Došlo je do greške pri čuvanju. Pokušajte ponovo.", true) return } @@ -240,13 +204,13 @@ func (h *Handler) OznaciPodsetnik(w http.ResponseWriter, r *http.Request) { } // učitamo trenutni status pa ga preokrenemo - podsetnik, err := h.PodsetniciFRepo.DohvatiID(r.Context(), id) + podsetnik, err := h.PodsetnikRepo.DohvatiID(r.Context(), id) if err != nil { http.Error(w, "Podsetnik nije pronađen", http.StatusNotFound) return } - if err := h.PodsetniciFRepo.OznaciZavrsenim(r.Context(), id, !podsetnik.Zavrseno); err != nil { + if err := h.PodsetnikRepo.OznaciZavrsenim(r.Context(), id, !podsetnik.Zavrseno); err != nil { http.Error(w, "Greška pri ažuriranju statusa", http.StatusInternalServerError) return } @@ -262,7 +226,7 @@ func (h *Handler) ObrisiPodsetnik(w http.ResponseWriter, r *http.Request) { return } - if err := h.PodsetniciFRepo.Obrisi(r.Context(), id); err != nil { + if err := h.PodsetnikRepo.Obrisi(r.Context(), id); err != nil { http.Error(w, "Greška pri brisanju podsetnika", http.StatusInternalServerError) return } @@ -295,7 +259,7 @@ func parseFormuPodsetnika(r *http.Request, k *model.Korisnik) (model.Podsetnik, } // admin/superadmin mogu dodeliti podsetnik drugom korisniku - if k.Uloga == "admin" || k.Uloga == "superadmin" { + if middleware.JeAdmin(k) { if kidStr := strings.TrimSpace(r.FormValue("korisnik_id")); kidStr != "" { if kid, err := strconv.ParseInt(kidStr, 10, 64); err == nil && kid > 0 { p.KorisnikID = &kid @@ -309,6 +273,29 @@ func parseFormuPodsetnika(r *http.Request, k *model.Korisnik) (model.Podsetnik, return p, "" } +// prikaziGreskuPodsetnika prikazuje formu podsetnika sa porukom o grešci +func (h *Handler) prikaziGreskuPodsetnika(w http.ResponseWriter, r *http.Request, k *model.Korisnik, podsetnik model.Podsetnik, poruka string, izmena bool) { + podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "podsetnici" + if izmena { + ps.NaslovStranice = "Izmeni podsetnik" + } else { + ps.NaslovStranice = "Novi podsetnik" + } + var korisnici []model.Korisnik + if middleware.JeAdmin(k) { + korisnici, _ = h.KorisniciRepo.Lista(r.Context()) + } + h.renderujFormuPodsetnika(w, podaciPodsetnikForma{ + PodaciStranice: ps, + Podsetnik: podsetnik, + Greska: poruka, + Izmena: izmena, + Korisnici: korisnici, + }) +} + // renderujFormuPodsetnika renderuje HTML šablon forme za unos ili izmenu podsetnika func (h *Handler) renderujFormuPodsetnika(w http.ResponseWriter, podaci podaciPodsetnikForma) { h.renderujTemplate(w, "podsetnik_forma", podaci) diff --git a/internal/handler/profil.go b/internal/handler/profil.go index 5b61d72..8e21138 100644 --- a/internal/handler/profil.go +++ b/internal/handler/profil.go @@ -250,3 +250,42 @@ func (h *Handler) ProfilSacuvajPozadinuStilove(w http.ResponseWriter, r *http.Re middleware.SetFlash(w, r, h.DB, "uspeh", "Podešavanja su sačuvana.") http.Redirect(w, r, "/profil/tema", http.StatusSeeOther) } + +// SacuvajLokalnuTemu čuva korisnikovu lokalnu temu +func (h *Handler) SacuvajLokalnuTemu(w http.ResponseWriter, r *http.Request) { + k := middleware.KorisnikIzKonteksta(r.Context()) + if k == nil { + http.Redirect(w, r, "/prijava", http.StatusSeeOther) + return + } + if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "tema.lokalno") { + http.Error(w, "Pristup odbijen", http.StatusForbidden) + return + } + + if err := r.ParseForm(); err != nil { + middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.") + http.Redirect(w, r, "/admin/profil", http.StatusSeeOther) + return + } + + koristi := r.FormValue("koristi_lokalnu_temu") == "1" + lokalnaTema := r.FormValue("lokalna_tema") + if lokalnaTema != "tamna" && lokalnaTema != "svetla" { + lokalnaTema = "tamna" + } + + if err := h.KorisniciRepo.SacuvajLokalnuTemu(r.Context(), k.ID, lokalnaTema, koristi); err != nil { + middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.") + http.Redirect(w, r, "/admin/profil", http.StatusSeeOther) + return + } + + middleware.SetFlash(w, r, h.DB, "uspeh", "Tema je sačuvana.") + // vrati korisnika na stranicu odakle je došao (Referer), ili na profil kao fallback + if ref := r.Referer(); ref != "" { + http.Redirect(w, r, ref, http.StatusSeeOther) + return + } + http.Redirect(w, r, "/admin/profil", http.StatusSeeOther) +} diff --git a/internal/middleware/bezbednost.go b/internal/middleware/bezbednost.go index d27b275..96254c6 100644 --- a/internal/middleware/bezbednost.go +++ b/internal/middleware/bezbednost.go @@ -14,7 +14,8 @@ func BezbednostHeaders() func(http.Handler) http.Handler { h.Set("Content-Security-Policy", "default-src 'self'; "+ "style-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com; "+ - "script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://cdn.jsdelivr.net; "+ + // Alpine.js v3 koristi new Function() interno — unsafe-eval je neophodan; CSP build zahteva značajan refaktoring. + "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.tailwindcss.com https://cdn.jsdelivr.net; "+ "img-src 'self' data: blob:; "+ "font-src 'self'; "+ "connect-src 'self'") diff --git a/web/templates/setup/index.html b/web/templates/setup/index.html index 8fdbbfe..5c8ea83 100644 --- a/web/templates/setup/index.html +++ b/web/templates/setup/index.html @@ -4,142 +4,26 @@
Skenirajte QR kod u aplikaciji (Google Authenticator, Authy...), pa unesite generisani kod da potvrdite podešavanje.
- {{if .TotpURI}} - - {{if eq .Greska "totp"}} -- Skenirajte QR kod u aplikaciji (Google Authenticator, Authy...), pa unesite generisani kod da potvrdite podešavanje. -
+{{ .TotpTajna }}
+ {{.TotpTajna}}
-