Code-review popravke: RequireDozvola middleware, validacija backupa, dedup partiala
Bezbednost / dozvole:
- Nov RequireDozvola(proveri, akcija) middleware (po uzoru na RequireAdmin):
na odbijanje redirekt na /dashboard sa flash porukom umesto golog 403
- 10 "pregled" ruta (prodaja, servis, klijenti, dobavljači, nabavke) prešlo
na deklarativnu proveru na nivou rute
- Uklonjene inline pregled-provere iz handlera — provera je sad na jednom
mestu po ruti, vidljiva u ruteru
Backup podešavanja:
- Neispravan unos (van opsega ili ne-broj) više se ne preskače tiho;
prikazuje se jasna greška i ne prikazuje se lažno "sačuvano"
- Hvata se greška pri čuvanju u bazu (uklonjen progutani _ =)
Šabloni:
- "Premesti" dropdown izvučen u jedan {{define "premestiMeni"}} partial
(više nije dupliran u tabeli i mobilnoj kartici)
- Dodat dict helper u FuncMap saSidebar šablona radi prosleđivanja više
vrednosti partialu; standalone šabloni namerno ostaju bez izmene
This commit is contained in:
+10
-10
@@ -190,40 +190,40 @@ func main() {
|
|||||||
r.Get("/magacin/kategorije", h.Kategorije)
|
r.Get("/magacin/kategorije", h.Kategorije)
|
||||||
r.Post("/magacin/kategorije/dodaj", h.DodajKategoriju)
|
r.Post("/magacin/kategorije/dodaj", h.DodajKategoriju)
|
||||||
r.Get("/magacin/kategorije/obrisi/{id}", h.ObrisiKategoriju)
|
r.Get("/magacin/kategorije/obrisi/{id}", h.ObrisiKategoriju)
|
||||||
r.Get("/nabavke", h.Nabavke)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke", h.Nabavke)
|
||||||
r.Get("/nabavke/nova", h.NovaNabavka)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke/nova", h.NovaNabavka)
|
||||||
r.Post("/nabavke/nova", h.SacuvajNabavku)
|
r.Post("/nabavke/nova", h.SacuvajNabavku)
|
||||||
r.Get("/nabavke/{id}", h.DetaljiNabavke)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke/{id}", h.DetaljiNabavke)
|
||||||
r.Post("/nabavke/obrisi/{id}", h.ObrisiNabavku)
|
r.Post("/nabavke/obrisi/{id}", h.ObrisiNabavku)
|
||||||
r.Get("/dobavljaci", h.Dobavljaci)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "dobavljac.pregled")).Get("/dobavljaci", h.Dobavljaci)
|
||||||
r.Get("/dobavljaci/novi", h.NoviDobavljac)
|
r.Get("/dobavljaci/novi", h.NoviDobavljac)
|
||||||
r.Post("/dobavljaci/novi", h.SacuvajDobavljaca)
|
r.Post("/dobavljaci/novi", h.SacuvajDobavljaca)
|
||||||
r.Get("/dobavljaci/izmeni/{id}", h.IzmeniDobavljaca)
|
r.Get("/dobavljaci/izmeni/{id}", h.IzmeniDobavljaca)
|
||||||
r.Post("/dobavljaci/izmeni/{id}", h.SacuvajIzmeneDobavljaca)
|
r.Post("/dobavljaci/izmeni/{id}", h.SacuvajIzmeneDobavljaca)
|
||||||
r.Post("/dobavljaci/obrisi/{id}", h.ObrisiDobavljaca)
|
r.Post("/dobavljaci/obrisi/{id}", h.ObrisiDobavljaca)
|
||||||
r.Get("/klijenti", h.Klijenti)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "klijent.pregled")).Get("/klijenti", h.Klijenti)
|
||||||
r.Get("/klijenti/novi", h.NoviKlijent)
|
r.Get("/klijenti/novi", h.NoviKlijent)
|
||||||
r.Post("/klijenti/novi", h.SacuvajKlijenta)
|
r.Post("/klijenti/novi", h.SacuvajKlijenta)
|
||||||
r.Get("/klijenti/izmeni/{id}", h.IzmeniKlijenta)
|
r.Get("/klijenti/izmeni/{id}", h.IzmeniKlijenta)
|
||||||
r.Post("/klijenti/izmeni/{id}", h.SacuvajIzmenuKlijenta)
|
r.Post("/klijenti/izmeni/{id}", h.SacuvajIzmenuKlijenta)
|
||||||
r.Post("/klijenti/obrisi/{id}", h.ObrisiKlijenta)
|
r.Post("/klijenti/obrisi/{id}", h.ObrisiKlijenta)
|
||||||
r.Get("/servis", h.Servis)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis", h.Servis)
|
||||||
r.Get("/servis/novi", h.NoviNalog)
|
r.Get("/servis/novi", h.NoviNalog)
|
||||||
r.Post("/servis/novi", h.SacuvajNalog)
|
r.Post("/servis/novi", h.SacuvajNalog)
|
||||||
r.Get("/servis/izmeni/{id}", h.IzmeniNalog)
|
r.Get("/servis/izmeni/{id}", h.IzmeniNalog)
|
||||||
r.Post("/servis/izmeni/{id}", h.SacuvajIzmenaNaloga)
|
r.Post("/servis/izmeni/{id}", h.SacuvajIzmenaNaloga)
|
||||||
r.Post("/servis/obrisi/{id}", h.ObrisiNalog)
|
r.Post("/servis/obrisi/{id}", h.ObrisiNalog)
|
||||||
r.Get("/servis/{id}", h.DetaljiNaloga)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}", h.DetaljiNaloga)
|
||||||
r.Post("/servis/{id}/delovi", h.DodajDeloNalogu)
|
r.Post("/servis/{id}/delovi", h.DodajDeloNalogu)
|
||||||
r.Post("/servis/{id}/delovi/{deo_id}/obrisi", h.ObrisiDeloNaloga)
|
r.Post("/servis/{id}/delovi/{deo_id}/obrisi", h.ObrisiDeloNaloga)
|
||||||
r.Get("/izvestaji", h.Izvestaji)
|
r.Get("/izvestaji", h.Izvestaji)
|
||||||
r.Get("/prodaja", h.Prodaja)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja", h.Prodaja)
|
||||||
r.Get("/prodaja/nova", h.NovaProdaja)
|
r.Get("/prodaja/nova", h.NovaProdaja)
|
||||||
r.Post("/prodaja/nova", h.SacuvajProdaju)
|
r.Post("/prodaja/nova", h.SacuvajProdaju)
|
||||||
r.Post("/prodaja/obrisi/{id}", h.ObrisiProdaju)
|
r.Post("/prodaja/obrisi/{id}", h.ObrisiProdaju)
|
||||||
r.Post("/prodaja/storno/{id}", h.StornoProdaje)
|
r.Post("/prodaja/storno/{id}", h.StornoProdaje)
|
||||||
r.Get("/prodaja/{id}/stampa", h.StampaProdaje)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja/{id}/stampa", h.StampaProdaje)
|
||||||
r.Get("/prodaja/{id}", h.DetaljiProdaje)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja/{id}", h.DetaljiProdaje)
|
||||||
|
|
||||||
// podsetnici
|
// podsetnici
|
||||||
r.Get("/podsetnici", h.Podsetnici)
|
r.Get("/podsetnici", h.Podsetnici)
|
||||||
|
|||||||
@@ -30,11 +30,6 @@ type PodaciFormeDobavljaca struct {
|
|||||||
|
|
||||||
// Dobavljaci renderuje listu svih dobavljača
|
// Dobavljaci renderuje listu svih dobavljača
|
||||||
func (h *Handler) Dobavljaci(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Dobavljaci(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "dobavljac.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za pregled dobavljača.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
|
|||||||
+22
-2
@@ -36,6 +36,26 @@ var standaloneIme = []string{
|
|||||||
"prijava", "setup", "totp_provera", "prodaja_stampa",
|
"prijava", "setup", "totp_provera", "prodaja_stampa",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sablonskeFunkcije su pomoćne funkcije dostupne u svim šablonima.
|
||||||
|
// dict gradi mapu iz parova ključ/vrednost — koristi se da se jednom partialu
|
||||||
|
// prosledi više vrednosti (npr. {{template "x" (dict "ID" .ID "Lista" $.Lista)}}).
|
||||||
|
var sablonskeFunkcije = template.FuncMap{
|
||||||
|
"dict": func(parovi ...any) (map[string]any, error) {
|
||||||
|
if len(parovi)%2 != 0 {
|
||||||
|
return nil, fmt.Errorf("dict: neparan broj argumenata")
|
||||||
|
}
|
||||||
|
m := make(map[string]any, len(parovi)/2)
|
||||||
|
for i := 0; i < len(parovi); i += 2 {
|
||||||
|
kljuc, ok := parovi[i].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("dict: ključ mora biti string")
|
||||||
|
}
|
||||||
|
m[kljuc] = parovi[i+1]
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// KreirajKes parsuje sve šablone iz fsys i vraća ih keširane u mapi
|
// KreirajKes parsuje sve šablone iz fsys i vraća ih keširane u mapi
|
||||||
func KreirajKes(fsys fs.FS) (map[string]*template.Template, error) {
|
func KreirajKes(fsys fs.FS) (map[string]*template.Template, error) {
|
||||||
kes := make(map[string]*template.Template)
|
kes := make(map[string]*template.Template)
|
||||||
@@ -44,7 +64,7 @@ func KreirajKes(fsys fs.FS) (map[string]*template.Template, error) {
|
|||||||
fajlovi := make([]string, len(bazniSabloni), len(bazniSabloni)+1)
|
fajlovi := make([]string, len(bazniSabloni), len(bazniSabloni)+1)
|
||||||
copy(fajlovi, bazniSabloni)
|
copy(fajlovi, bazniSabloni)
|
||||||
fajlovi = append(fajlovi, "web/templates/stranice/"+ime+".html")
|
fajlovi = append(fajlovi, "web/templates/stranice/"+ime+".html")
|
||||||
t, err := template.ParseFS(fsys, fajlovi...)
|
t, err := template.New(ime).Funcs(sablonskeFunkcije).ParseFS(fsys, fajlovi...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("kes: %s: %w", ime, err)
|
return nil, fmt.Errorf("kes: %s: %w", ime, err)
|
||||||
}
|
}
|
||||||
@@ -80,7 +100,7 @@ func (h *Handler) renderujTemplate(w http.ResponseWriter, ime string, podaci any
|
|||||||
copy(fajlovi, bazniSabloni)
|
copy(fajlovi, bazniSabloni)
|
||||||
fajlovi = append(fajlovi, "web/templates/stranice/"+ime+".html")
|
fajlovi = append(fajlovi, "web/templates/stranice/"+ime+".html")
|
||||||
var err error
|
var err error
|
||||||
if tmpl, err = template.ParseFS(h.TemplatesFS, fajlovi...); err != nil {
|
if tmpl, err = template.New(ime).Funcs(sablonskeFunkcije).ParseFS(h.TemplatesFS, fajlovi...); err != nil {
|
||||||
log.Printf("greška pri parsiranju šablona %s: %v", ime, err)
|
log.Printf("greška pri parsiranju šablona %s: %v", ime, err)
|
||||||
http.Error(w, "Greška pri učitavanju stranice", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju stranice", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -31,11 +31,6 @@ type PodaciFormeKlijenta struct {
|
|||||||
|
|
||||||
// Klijenti renderuje listu svih klijenata sa opcionom pretragom
|
// Klijenti renderuje listu svih klijenata sa opcionom pretragom
|
||||||
func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "klijent.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za pregled klijenata.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -57,12 +57,6 @@ func artikalUJSON(artikli []model.ArtikalSaKategorijom) template.JS {
|
|||||||
|
|
||||||
// Nabavke renderuje listu svih nabavki
|
// Nabavke renderuje listu svih nabavki
|
||||||
func (h *Handler) Nabavke(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Nabavke(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "nabavka.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za pregled nabavki.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
@@ -90,12 +84,6 @@ func (h *Handler) Nabavke(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// NovaNabavka prikazuje formu za unos nove nabavke
|
// NovaNabavka prikazuje formu za unos nove nabavke
|
||||||
func (h *Handler) NovaNabavka(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) NovaNabavka(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "nabavka.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za ovu akciju.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
@@ -175,12 +163,6 @@ func (h *Handler) SacuvajNabavku(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// DetaljiNabavke prikazuje pregled jedne nabavke sa svim stavkama
|
// DetaljiNabavke prikazuje pregled jedne nabavke sa svim stavkama
|
||||||
func (h *Handler) DetaljiNabavke(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) DetaljiNabavke(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "nabavka.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za ovu akciju.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Neispravan ID nabavke", http.StatusBadRequest)
|
http.Error(w, "Neispravan ID nabavke", http.StatusBadRequest)
|
||||||
|
|||||||
@@ -248,22 +248,38 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// backup podešavanja — čuvamo samo ako su validni pozitivni brojevi u razumnom opsegu
|
|
||||||
if v := r.FormValue("backup_interval_sati"); v != "" {
|
|
||||||
if n, err := strconv.Atoi(v); err == nil && n >= 1 && n <= 720 {
|
|
||||||
_ = ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_interval_sati", strconv.Itoa(n))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v := r.FormValue("backup_broj_kopija"); v != "" {
|
|
||||||
if n, err := strconv.Atoi(v); err == nil && n >= 1 && n <= 100 {
|
|
||||||
_ = ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_broj_kopija", strconv.Itoa(n))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sledeci := r.FormValue("_next")
|
sledeci := r.FormValue("_next")
|
||||||
if sledeci == "" || !strings.HasPrefix(sledeci, "/") {
|
if sledeci == "" || !strings.HasPrefix(sledeci, "/") {
|
||||||
sledeci = "/podesavanja"
|
sledeci = "/podesavanja"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// backup podešavanja — pri neispravnom unosu javljamo jasnu grešku
|
||||||
|
// umesto da ga tiho preskočimo a korisniku prikažemo "sačuvano"
|
||||||
|
if v := r.FormValue("backup_interval_sati"); v != "" {
|
||||||
|
n, err := strconv.Atoi(v)
|
||||||
|
if err != nil || n < 1 || n > 720 {
|
||||||
|
middleware.SetFlash(w, r, h.DB, "greska", "Razmak između backupa mora biti broj između 1 i 720 sati.")
|
||||||
|
http.Redirect(w, r, sledeci, http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_interval_sati", strconv.Itoa(n)); err != nil {
|
||||||
|
http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := r.FormValue("backup_broj_kopija"); v != "" {
|
||||||
|
n, err := strconv.Atoi(v)
|
||||||
|
if err != nil || n < 1 || n > 100 {
|
||||||
|
middleware.SetFlash(w, r, h.DB, "greska", "Broj kopija mora biti broj između 1 i 100.")
|
||||||
|
http.Redirect(w, r, sledeci, http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_broj_kopija", strconv.Itoa(n)); err != nil {
|
||||||
|
http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, sledeci+"?sacuvano=1", http.StatusSeeOther)
|
http.Redirect(w, r, sledeci+"?sacuvano=1", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,11 +76,6 @@ func artikalUJSONSaCenom(artikli []model.ArtikalSaKategorijom) template.JS {
|
|||||||
|
|
||||||
// Prodaja renderuje listu svih prodajnih naloga
|
// Prodaja renderuje listu svih prodajnih naloga
|
||||||
func (h *Handler) Prodaja(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Prodaja(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "prodaja.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za pregled prodaje.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
@@ -208,11 +203,6 @@ func (h *Handler) SacuvajProdaju(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// DetaljiProdaje prikazuje pregled jednog prodajnog naloga sa svim stavkama
|
// DetaljiProdaje prikazuje pregled jednog prodajnog naloga sa svim stavkama
|
||||||
func (h *Handler) DetaljiProdaje(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) DetaljiProdaje(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "prodaja.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za pregled prodaje.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
||||||
@@ -265,11 +255,6 @@ func (h *Handler) DetaljiProdaje(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// StampaProdaje renderuje print-friendly stranicu za dati prodajni nalog
|
// StampaProdaje renderuje print-friendly stranicu za dati prodajni nalog
|
||||||
func (h *Handler) StampaProdaje(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) StampaProdaje(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "prodaja.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za pregled prodaje.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
||||||
|
|||||||
@@ -50,11 +50,6 @@ type PodaciDetaljiNaloga struct {
|
|||||||
|
|
||||||
// Servis renderuje listu servisnih naloga sa opcionom pretragom i filterom statusa
|
// Servis renderuje listu servisnih naloga sa opcionom pretragom i filterom statusa
|
||||||
func (h *Handler) Servis(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Servis(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "servis.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za pregled servisnih naloga.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
@@ -312,11 +307,6 @@ func (h *Handler) ObrisiNalog(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// DetaljiNaloga prikazuje sve podatke jednog servisnog naloga sa ugrađenim delovima
|
// DetaljiNaloga prikazuje sve podatke jednog servisnog naloga sa ugrađenim delovima
|
||||||
func (h *Handler) DetaljiNaloga(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) DetaljiNaloga(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
|
||||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "servis.pregled") {
|
|
||||||
http.Error(w, "Nemate dozvolu za pregled servisnih naloga.", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
||||||
|
|||||||
@@ -109,6 +109,24 @@ func RequireAdmin(next http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequireDozvola je middleware koji propušta korisnika samo ako njegova uloga
|
||||||
|
// ima traženu akciju. Provera je DB-backed (prosleđuje se DozvoleRepo.ImaDozvolu),
|
||||||
|
// tako da poštuje izmene matrice dozvola. Koristi se na nivou rute za "pregled"
|
||||||
|
// stranica, umesto ponavljanja iste provere u svakom handleru.
|
||||||
|
func RequireDozvola(proveri func(ctx context.Context, uloga, akcija string) bool, akcija string) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
k := KorisnikIzKonteksta(r.Context())
|
||||||
|
if k == nil || !proveri(r.Context(), k.Uloga, akcija) {
|
||||||
|
postaviFlashGresku(w, "Nemate dozvolu za ovu stranicu.")
|
||||||
|
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// postaviFlashGresku upisuje jednokratnu poruku o grešci u kolačić
|
// postaviFlashGresku upisuje jednokratnu poruku o grešci u kolačić
|
||||||
func postaviFlashGresku(w http.ResponseWriter, poruka string) {
|
func postaviFlashGresku(w http.ResponseWriter, poruka string) {
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
|||||||
@@ -133,15 +133,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if index $.Dozvole "artikal.premesti"}}{{if $.Kategorije}}
|
{{if index $.Dozvole "artikal.premesti"}}{{if $.Kategorije}}
|
||||||
<details class="premesti-meni" style="align-self:center;">
|
{{template "premestiMeni" (dict "ID" .ID "Kategorije" $.Kategorije)}}
|
||||||
<summary class="btn-primarno-malo" style="cursor:pointer;">Premesti</summary>
|
|
||||||
<form method="POST" action="/magacin/premesti/{{.ID}}" class="premesti-panel">
|
|
||||||
{{range $.Kategorije}}
|
|
||||||
<button type="submit" name="kategorija_id" value="{{.ID}}" class="premesti-opcija">{{.Naziv}}</button>
|
|
||||||
{{end}}
|
|
||||||
<button type="submit" name="kategorija_id" value="" class="premesti-opcija premesti-opcija-prazno">Bez kategorije</button>
|
|
||||||
</form>
|
|
||||||
</details>
|
|
||||||
{{end}}{{end}}
|
{{end}}{{end}}
|
||||||
{{if index $.Dozvole "artikal.obrisi"}}
|
{{if index $.Dozvole "artikal.obrisi"}}
|
||||||
<a href="/magacin/obrisi/{{.ID}}" class="btn-obrisi-malo"
|
<a href="/magacin/obrisi/{{.ID}}" class="btn-obrisi-malo"
|
||||||
@@ -180,15 +172,7 @@
|
|||||||
<a href="/magacin/izmeni/{{.ID}}" class="btn-primarno-malo">Izmeni</a>
|
<a href="/magacin/izmeni/{{.ID}}" class="btn-primarno-malo">Izmeni</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if index $.Dozvole "artikal.premesti"}}{{if $.Kategorije}}
|
{{if index $.Dozvole "artikal.premesti"}}{{if $.Kategorije}}
|
||||||
<details class="premesti-meni">
|
{{template "premestiMeni" (dict "ID" .ID "Kategorije" $.Kategorije)}}
|
||||||
<summary class="btn-primarno-malo" style="cursor:pointer;">Premesti</summary>
|
|
||||||
<form method="POST" action="/magacin/premesti/{{.ID}}" class="premesti-panel">
|
|
||||||
{{range $.Kategorije}}
|
|
||||||
<button type="submit" name="kategorija_id" value="{{.ID}}" class="premesti-opcija">{{.Naziv}}</button>
|
|
||||||
{{end}}
|
|
||||||
<button type="submit" name="kategorija_id" value="" class="premesti-opcija premesti-opcija-prazno">Bez kategorije</button>
|
|
||||||
</form>
|
|
||||||
</details>
|
|
||||||
{{end}}{{end}}
|
{{end}}{{end}}
|
||||||
{{if index $.Dozvole "artikal.obrisi"}}
|
{{if index $.Dozvole "artikal.obrisi"}}
|
||||||
<a href="/magacin/obrisi/{{.ID}}" class="btn-obrisi-malo"
|
<a href="/magacin/obrisi/{{.ID}}" class="btn-obrisi-malo"
|
||||||
@@ -222,3 +206,16 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{/* padajući meni za premeštanje artikla — prima dict {ID, Kategorije}; koristi se i u tabeli i u mobilnoj kartici */}}
|
||||||
|
{{define "premestiMeni"}}
|
||||||
|
<details class="premesti-meni" style="align-self:center;">
|
||||||
|
<summary class="btn-primarno-malo" style="cursor:pointer;">Premesti</summary>
|
||||||
|
<form method="POST" action="/magacin/premesti/{{.ID}}" class="premesti-panel">
|
||||||
|
{{range .Kategorije}}
|
||||||
|
<button type="submit" name="kategorija_id" value="{{.ID}}" class="premesti-opcija">{{.Naziv}}</button>
|
||||||
|
{{end}}
|
||||||
|
<button type="submit" name="kategorija_id" value="" class="premesti-opcija premesti-opcija-prazno">Bez kategorije</button>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
{{end}}
|
||||||
|
|||||||
Reference in New Issue
Block a user