Flash poruke: toast notifikacije umesto ?greska= i ?sacuvano= u URL-u

This commit is contained in:
2026-06-05 23:15:49 +02:00
parent 2b3636528f
commit 9af712edd3
10 changed files with 267 additions and 116 deletions
+114 -83
View File
@@ -15,8 +15,6 @@ import (
type podaciAdminKorisnici struct {
model.PodaciStranice
Korisnici []model.Korisnik
Greska string
Sacuvano bool
}
type podaciLoginIstorija struct {
@@ -27,11 +25,11 @@ type podaciLoginIstorija struct {
type podaciAdminProfil struct {
model.PodaciStranice
Greska string
Sacuvano string
TotpURI string
TotpTajna string
TotpQR template.URL
// Greska se koristi samo za inline prikaz greške pri TOTP aktivaciji (bez redirecta)
Greska string
TotpURI string
TotpTajna string
TotpQR template.URL
TotpAktivan bool
}
@@ -65,14 +63,10 @@ func (h *Handler) AdminKorisnici(w http.ResponseWriter, r *http.Request) {
ps.Stranica = "admin"
ps.NaslovStranice = "Korisnici"
podaci := podaciAdminKorisnici{
h.renderujTemplate(w, "admin_korisnici", podaciAdminKorisnici{
PodaciStranice: ps,
Korisnici: lista,
Greska: r.URL.Query().Get("greska"),
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
}
h.renderujTemplate(w, "admin_korisnici", podaci)
})
}
// AdminSacuvajKorisnika kreira novog korisnika
@@ -84,7 +78,8 @@ func (h *Handler) AdminSacuvajKorisnika(w http.ResponseWriter, r *http.Request)
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
@@ -95,22 +90,26 @@ func (h *Handler) AdminSacuvajKorisnika(w http.ResponseWriter, r *http.Request)
// superadmin uloga se ne može kreirati kroz interfejs — jedini superadmin postoji od setup-a
validneUloge := map[string]bool{"admin": true, "radnik": true}
if len(ime) < 3 || len(lozinka) < 8 || !validneUloge[uloga] {
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
hash, err := auth.HashujLozinku(lozinka)
if err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
if _, err := h.KorisniciRepo.Kreiraj(r.Context(), ime, hash, uloga); err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/admin/korisnici?sacuvano=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Korisnik je uspešno kreiran.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
}
// AdminToggleAktivan menja aktivan status korisnika
@@ -123,34 +122,40 @@ func (h *Handler) AdminToggleAktivan(w http.ResponseWriter, r *http.Request) {
id, err := parseID(chi.URLParam(r, "id"))
if err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
// ne sme deaktivirati sam sebe
if id == k.ID {
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Ne možete promeniti status sopstvenog naloga.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
korisnik, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
if err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
// admin ne sme da menja status drugog admina ni superadmina
if korisnik.Uloga == "superadmin" || (korisnik.Uloga == "admin" && k.Uloga != "superadmin") {
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
if err := h.KorisniciRepo.AzurirajAktivan(r.Context(), id, !korisnik.Aktivan); err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/admin/korisnici?sacuvano=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Promene su uspešno sačuvane.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
}
// AdminPromeniUlogu menja ulogu korisnika
@@ -163,18 +168,21 @@ func (h *Handler) AdminPromeniUlogu(w http.ResponseWriter, r *http.Request) {
id, err := parseID(chi.URLParam(r, "id"))
if err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
// superadmin ne može menjati svoju vlastitu ulogu
if id == k.ID {
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
@@ -182,29 +190,34 @@ func (h *Handler) AdminPromeniUlogu(w http.ResponseWriter, r *http.Request) {
// dozvoljena je samo promena između admin i radnik; superadmin uloga se ne može dodeliti ni ukloniti
validneUloge := map[string]bool{"admin": true, "radnik": true}
if !validneUloge[uloga] {
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
// dohvati korisnika da proverimo njegovu trenutnu ulogu
ciljniKorisnik, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
if err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
// superadmin uloga se ne može menjati
if ciljniKorisnik.Uloga == "superadmin" {
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
if err := h.KorisniciRepo.AzurirajUlogu(r.Context(), id, uloga); err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/admin/korisnici?sacuvano=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Promene su uspešno sačuvane.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
}
// AdminObrisiKorisnika briše korisnika sa ulogom radnik
@@ -217,33 +230,39 @@ func (h *Handler) AdminObrisiKorisnika(w http.ResponseWriter, r *http.Request) {
id, err := parseID(chi.URLParam(r, "id"))
if err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
if id == k.ID {
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
ciljni, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
if err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
// dozvoljeno je brisanje samo radnika
if ciljni.Uloga != "radnik" {
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
if err := h.KorisniciRepo.Obrisi(r.Context(), id); err != nil {
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/admin/korisnici?sacuvano=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Korisnik je obrisan.")
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
}
// AdminProfil prikazuje stranicu profila
@@ -265,14 +284,10 @@ func (h *Handler) AdminProfil(w http.ResponseWriter, r *http.Request) {
ps.Stranica = "profil"
ps.NaslovStranice = "Moj profil"
podaci := podaciAdminProfil{
h.renderujTemplate(w, "admin_profil", podaciAdminProfil{
PodaciStranice: ps,
Greska: r.URL.Query().Get("greska"),
Sacuvano: r.URL.Query().Get("sacuvano"),
TotpAktivan: svezi.TotpTajna != "",
}
h.renderujTemplate(w, "admin_profil", podaci)
})
}
// AdminPromeniLozinku menja lozinku prijavljenog korisnika
@@ -284,7 +299,8 @@ func (h *Handler) AdminPromeniLozinku(w http.ResponseWriter, r *http.Request) {
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/admin/profil?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
return
}
@@ -293,26 +309,31 @@ func (h *Handler) AdminPromeniLozinku(w http.ResponseWriter, r *http.Request) {
potvrda := r.FormValue("nova_lozinka_potvrda")
if !auth.ProveriLozinku(k.LozinkaHash, stara) {
http.Redirect(w, r, "/admin/profil?greska=lozinka", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Stara lozinka nije ispravna.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
return
}
if len(nova) < 8 || nova != potvrda {
http.Redirect(w, r, "/admin/profil?greska=lozinka2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Nova lozinka mora imati najmanje 8 karaktera i lozinke moraju biti iste.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
return
}
hash, err := auth.HashujLozinku(nova)
if err != nil {
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
return
}
if err := h.KorisniciRepo.PromeniLozinku(r.Context(), k.ID, hash); err != nil {
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/admin/profil?sacuvano=lozinka", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Lozinka je uspešno promenjena.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
}
// AdminTotpPokreni generiše TOTP tajnu i prikazuje QR kod
@@ -326,7 +347,8 @@ func (h *Handler) AdminTotpPokreni(w http.ResponseWriter, r *http.Request) {
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
totp, err := auth.GenerisuTotpTajnu(k.KorisnickoIme, podesavanja["naziv_firme"])
if err != nil {
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri generisanju 2FA. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
return
}
@@ -334,14 +356,12 @@ func (h *Handler) AdminTotpPokreni(w http.ResponseWriter, r *http.Request) {
ps.Stranica = "profil"
ps.NaslovStranice = "Podesi 2FA"
podaci := podaciAdminProfil{
h.renderujTemplate(w, "admin_profil", podaciAdminProfil{
PodaciStranice: ps,
TotpURI: totp.URI,
TotpTajna: totp.Tajna,
TotpQR: template.URL("data:image/png;base64," + totp.QRBase64),
}
h.renderujTemplate(w, "admin_profil", podaci)
})
}
// AdminTotpAktivacija verifikuje TOTP kod i čuva tajnu
@@ -353,7 +373,8 @@ func (h *Handler) AdminTotpAktivacija(w http.ResponseWriter, r *http.Request) {
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/admin/profil?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
return
}
@@ -361,36 +382,36 @@ func (h *Handler) AdminTotpAktivacija(w http.ResponseWriter, r *http.Request) {
kod := r.FormValue("kod")
if !auth.VerifikujTotpKod(kod, tajna) {
// ponovni prikaz sa greškom — regenerišemo isti tajnu
// ponovni prikaz sa greškom — regenerišemo isti QR, ne radimo redirect
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "profil"
ps.NaslovStranice = "Podesi 2FA"
// regenerišemo QR za već generisanu tajnu (korisnik je video ovaj QR)
nazivFirme := podesavanja["naziv_firme"]
if nazivFirme == "" {
nazivFirme = "NTech"
}
uri, qr := auth.RegenerisiTotpQR(tajna, k.KorisnickoIme, nazivFirme)
podaci := podaciAdminProfil{
h.renderujTemplate(w, "admin_profil", podaciAdminProfil{
PodaciStranice: ps,
TotpURI: uri,
TotpTajna: tajna,
TotpQR: template.URL("data:image/png;base64," + qr),
Greska: "totp",
}
h.renderujTemplate(w, "admin_profil", podaci)
})
return
}
if err := h.KorisniciRepo.SacuvajTotpTajnu(r.Context(), k.ID, tajna); err != nil {
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/admin/profil?sacuvano=totp", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Dvostepena verifikacija je uspešno uključena.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
}
// AdminTotpDeaktivacija uklanja TOTP tajnu
@@ -402,11 +423,13 @@ func (h *Handler) AdminTotpDeaktivacija(w http.ResponseWriter, r *http.Request)
}
if err := h.KorisniciRepo.SacuvajTotpTajnu(r.Context(), k.ID, ""); err != nil {
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/admin/profil?sacuvano=totp_off", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Dvostepena verifikacija je isključena.")
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
}
// AdminLoginIstorija prikazuje evidenciju prijava za datog korisnika
@@ -460,8 +483,6 @@ type podaciAdminDozvole struct {
DozvoleRadnik map[string]bool
DozvoleAdmin map[string]bool
DozvoleSuperadmin map[string]bool
Greska string
Sacuvano bool
}
// AdminDozvole prikazuje stranicu za upravljanje ulogama i pregled dozvola
@@ -490,8 +511,6 @@ func (h *Handler) AdminDozvole(w http.ResponseWriter, r *http.Request) {
DozvoleRadnik: h.DozvoleRepo.SveDozvole(r.Context(), "radnik"),
DozvoleAdmin: h.DozvoleRepo.SveDozvole(r.Context(), "admin"),
DozvoleSuperadmin: h.DozvoleRepo.SveDozvole(r.Context(), "superadmin"),
Greska: r.URL.Query().Get("greska"),
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
})
}
@@ -505,18 +524,21 @@ func (h *Handler) AdminDozvolePromeniUlogu(w http.ResponseWriter, r *http.Reques
id, err := parseID(chi.URLParam(r, "id"))
if err != nil {
http.Redirect(w, r, "/admin/dozvole?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/admin/dozvole?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
// superadmin ne može menjati svoju vlastitu ulogu
if id == k.ID {
http.Redirect(w, r, "/admin/dozvole?greska=3", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
@@ -524,28 +546,33 @@ func (h *Handler) AdminDozvolePromeniUlogu(w http.ResponseWriter, r *http.Reques
// superadmin uloga se ne može dodeliti kroz interfejs
validneUloge := map[string]bool{"admin": true, "radnik": true}
if !validneUloge[uloga] {
http.Redirect(w, r, "/admin/dozvole?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
ciljni, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
if err != nil {
http.Redirect(w, r, "/admin/dozvole?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
// superadmin uloga se ne može menjati
if ciljni.Uloga == "superadmin" {
http.Redirect(w, r, "/admin/dozvole?greska=3", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
if err := h.KorisniciRepo.AzurirajUlogu(r.Context(), id, uloga); err != nil {
http.Redirect(w, r, "/admin/dozvole?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/admin/dozvole?sacuvano=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Promene su uspešno sačuvane.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
}
// AdminDozvoleSacuvaj prima POST i čuva promene dozvola za radnik i admin uloge
@@ -556,7 +583,8 @@ func (h *Handler) AdminDozvoleSacuvaj(w http.ResponseWriter, r *http.Request) {
return
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/admin/dozvole?greska=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
// čuvamo dozvole samo za radnik i admin — superadmin uvek ima sve
@@ -565,12 +593,14 @@ func (h *Handler) AdminDozvoleSacuvaj(w http.ResponseWriter, r *http.Request) {
kljuc := uloga + "__" + akcija
dozvoljeno := r.FormValue(kljuc) == "on"
if err := h.DozvoleRepo.Sacuvaj(r.Context(), uloga, akcija, dozvoljeno); err != nil {
http.Redirect(w, r, "/admin/dozvole?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
}
}
http.Redirect(w, r, "/admin/dozvole?sacuvano=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Promene su uspešno sačuvane.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
}
// AdminDozvoleReset vraća sve dozvole na podrazumevane vrednosti — samo superadmin
@@ -581,9 +611,10 @@ func (h *Handler) AdminDozvoleReset(w http.ResponseWriter, r *http.Request) {
return
}
if err := h.DozvoleRepo.Reset(r.Context()); err != nil {
http.Redirect(w, r, "/admin/dozvole?greska=2", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/admin/dozvole?sacuvano=1", http.StatusSeeOther)
middleware.SetFlash(w, r, h.DB, "uspeh", "Dozvole su vraćene na podrazumevane vrednosti.")
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
}
+1
View File
@@ -89,5 +89,6 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s
ps.Dozvole = h.DozvoleRepo.SveDozvole(r.Context(), k.Uloga)
}
ps.CsrfToken = middleware.CsrfToken(r.Context())
ps.Flash = middleware.GetFlash(r, h.DB)
return ps
}
+48
View File
@@ -0,0 +1,48 @@
package middleware
import (
"database/sql"
"encoding/json"
"net/http"
"ntech/internal/model"
)
// SetFlash čuva flash poruku u koloni flash aktivne sesije
func SetFlash(w http.ResponseWriter, r *http.Request, db *sql.DB, tip, poruka string) {
kolacic, err := r.Cookie("ntech_sesija")
if err != nil {
return
}
data, err := json.Marshal(model.FlashPoruka{Tip: tip, Poruka: poruka})
if err != nil {
return
}
db.ExecContext(r.Context(),
`UPDATE sesije SET flash = ? WHERE token = ?`,
string(data), kolacic.Value)
}
// GetFlash čita i atomično briše flash poruku iz aktivne sesije
func GetFlash(r *http.Request, db *sql.DB) *model.FlashPoruka {
kolacic, err := r.Cookie("ntech_sesija")
if err != nil {
return nil
}
var flashJSON sql.NullString
if err := db.QueryRowContext(r.Context(),
`SELECT flash FROM sesije WHERE token = ?`, kolacic.Value).Scan(&flashJSON); err != nil {
return nil
}
if !flashJSON.Valid || flashJSON.String == "" {
return nil
}
// briše pre parsiranja — ako parsiranje ne uspe, poruka se svakako ne prikazuje
db.ExecContext(r.Context(),
`UPDATE sesije SET flash = NULL WHERE token = ?`, kolacic.Value)
var f model.FlashPoruka
if err := json.Unmarshal([]byte(flashJSON.String), &f); err != nil {
return nil
}
return &f
}
+7
View File
@@ -23,6 +23,12 @@ type StavkaProdajePregled struct {
Datum string // kratki format, npr. "01.06."
}
// FlashPoruka je jednokratna poruka koja se prikazuje korisniku nakon redirecta
type FlashPoruka struct {
Tip string // "uspeh" ili "greska"
Poruka string
}
// PodaciStranice su zajednički podaci koje svaka stranica prima
type PodaciStranice struct {
Stranica string
@@ -37,6 +43,7 @@ type PodaciStranice struct {
KorisnikUloga string // uloga: "superadmin", "admin", "radnik"
CsrfToken string // CSRF zaštitni token za forme
Dozvole map[string]bool // mapa akcija → dozvoljeno/nije
Flash *FlashPoruka // jednokratna poruka nakon redirecta
}
// PodaciDashboarda su podaci specifični za dashboard stranicu