feat(moduli): mehanizam uključenih modula — Moduli mapa i RequireModul (Faza 0)
config.SviModuli + PodaciStranice.Moduli (uslovni meni, analogno Dozvole) i middleware.RequireModul (zaštita ruta, analogno RequireDozvola). Sloj iznad RBAC-a: zahtev mora proći i „modul uključen" i „korisnik sme". Dopunjen test (TestSviModuli). Time je Faza 0 kompletna.
This commit is contained in:
@@ -54,3 +54,14 @@ func ModulUkljucen(podesavanja map[string]string, modul string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SviModuli vraća mapu svih poznatih modula → da li su uključeni za dati profil firme.
|
||||||
|
// Koristi se da šabloni uslovno prikazuju stavke menija (analogno mapi Dozvole).
|
||||||
|
func SviModuli(podesavanja map[string]string) map[string]bool {
|
||||||
|
moduli := []string{ModulPdv, ModulFiskalizacija, ModulKpo, ModulDvojno}
|
||||||
|
m := make(map[string]bool, len(moduli))
|
||||||
|
for _, modul := range moduli {
|
||||||
|
m[modul] = ModulUkljucen(podesavanja, modul)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,3 +66,31 @@ func TestModulUkljucen(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSviModuli(t *testing.T) {
|
||||||
|
// pun režim, doo, PDV obveznik, fiskalizacija → pdv, fiskalizacija, dvojno uključeni; kpo ne
|
||||||
|
pod := map[string]string{
|
||||||
|
KljucRezim: "pun",
|
||||||
|
KljucPdvObveznik: "da",
|
||||||
|
KljucFiskalizacija: "da",
|
||||||
|
KljucPravniOblik: "doo",
|
||||||
|
}
|
||||||
|
m := SviModuli(pod)
|
||||||
|
if len(m) != 4 {
|
||||||
|
t.Fatalf("SviModuli vraća %d modula, očekivano 4", len(m))
|
||||||
|
}
|
||||||
|
ocek := map[string]bool{ModulPdv: true, ModulFiskalizacija: true, ModulKpo: false, ModulDvojno: true}
|
||||||
|
for modul, want := range ocek {
|
||||||
|
if m[modul] != want {
|
||||||
|
t.Errorf("SviModuli[%q] = %v, očekivano %v", modul, m[modul], want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// samo evidencija → svi ugašeni
|
||||||
|
prazna := SviModuli(map[string]string{KljucRezim: "samo_evidencija"})
|
||||||
|
for modul, ukljucen := range prazna {
|
||||||
|
if ukljucen {
|
||||||
|
t.Errorf("u režimu samo_evidencija modul %q ne sme biti uključen", modul)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"ntech/internal/config"
|
||||||
"ntech/internal/db"
|
"ntech/internal/db"
|
||||||
"ntech/internal/db/sqlite"
|
"ntech/internal/db/sqlite"
|
||||||
"ntech/internal/middleware"
|
"ntech/internal/middleware"
|
||||||
@@ -159,6 +160,8 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s
|
|||||||
ps.CsrfToken = middleware.CsrfToken(r.Context())
|
ps.CsrfToken = middleware.CsrfToken(r.Context())
|
||||||
ps.AssetV = h.AssetV
|
ps.AssetV = h.AssetV
|
||||||
ps.Flash = middleware.GetFlash(r, h.DB)
|
ps.Flash = middleware.GetFlash(r, h.DB)
|
||||||
|
// uključeni zakonski moduli prema profilu firme — šabloni ih koriste za uslovni meni
|
||||||
|
ps.Moduli = config.SviModuli(podesavanja)
|
||||||
|
|
||||||
// logika pozadine:
|
// logika pozadine:
|
||||||
// - lična pozadina → uvek se prikazuje i forsira tamnu temu, bez obzira na KoristiLokalnuTemu
|
// - lična pozadina → uvek se prikazuje i forsira tamnu temu, bez obzira na KoristiLokalnuTemu
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequireModul je chi middleware koji propušta zahtev samo ako je traženi zakonski
|
||||||
|
// modul uključen za firmu (prema profilu firme iz podešavanja). Ovo je sloj IZNAD
|
||||||
|
// RBAC-a: „da li firma uopšte koristi modul", nezavisno od „da li korisnik sme"
|
||||||
|
// (RequireDozvola). Zahtev mora proći oba sloja.
|
||||||
|
//
|
||||||
|
// Provera se prosleđuje kao funkcija (proveri) da paket middleware ne zavisi od
|
||||||
|
// config/sqlite — isti obrazac kao RequireDozvola. U praksi je to closure koja
|
||||||
|
// učita podešavanja i pozove config.ModulUkljucen.
|
||||||
|
func RequireModul(proveri func(ctx context.Context, modul string) bool, modul string) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !proveri(r.Context(), modul) {
|
||||||
|
postaviFlashGresku(w, "Ovaj modul nije uključen za vašu firmu.")
|
||||||
|
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ type PodaciStranice struct {
|
|||||||
CsrfToken string // CSRF zaštitni token za forme
|
CsrfToken string // CSRF zaštitni token za forme
|
||||||
AssetV string // verzija statičkih fajlova (cache-busting za CSS/JS)
|
AssetV string // verzija statičkih fajlova (cache-busting za CSS/JS)
|
||||||
Dozvole map[string]bool // mapa akcija → dozvoljeno/nije
|
Dozvole map[string]bool // mapa akcija → dozvoljeno/nije
|
||||||
|
Moduli map[string]bool // mapa zakonskih modula → uključen za firmu (profil firme)
|
||||||
Flash *FlashPoruka // jednokratna poruka nakon redirecta
|
Flash *FlashPoruka // jednokratna poruka nakon redirecta
|
||||||
// app pozadina — popunjava se iz podešavanja za sve stranice
|
// app pozadina — popunjava se iz podešavanja za sve stranice
|
||||||
AppPozadina string
|
AppPozadina string
|
||||||
|
|||||||
Reference in New Issue
Block a user