Ispravke: ugnježdena forma, greška čitanja fajla, mrtvi flag, refaktor struct
- podesavanja_opste.html: forma za uklanjanje loga premešena van forme za otpremanje — ugnježdene forme su nevažeći HTML - ProfilOtpremiAvatar: greška iz fajl.Read(buf) se sada proverava (dozvoljava io.EOF, odbacuje pravi problem čitanja) - TopbarLogoTekst uklonjen: iz modela, oba handlera i struct-a u podesavanja.go (podešavanje nije korišćeno ni u jednom šablonu) - korisnici.go: dodeliOpcijeKorisnika prima korisnikOpcije struct umesto dugačke liste parametara; skeniraiKorisnika i Lista ažurirani
This commit is contained in:
@@ -17,43 +17,48 @@ type sqliteKorisniciRepo struct {
|
||||
kljuc []byte
|
||||
}
|
||||
|
||||
// dodeliOpcijeKorisnika popunjava bool i opciona polja korisnika iz skeniranih
|
||||
// NULL vrednosti — deljeno između skeniraiKorisnika (jedan red) i Lista (više redova)
|
||||
func dodeliOpcijeKorisnika(k *model.Korisnik, aktivan, koristiLokalnuTemu int,
|
||||
lokalnaTema, lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur,
|
||||
lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity, avatarPutanja sql.NullString,
|
||||
datumKreiranja time.Time) {
|
||||
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
|
||||
k.AvatarPutanja = avatarPutanja.String
|
||||
// korisnikOpcije drži NULL vrednosti skeniranih opcionalnih kolona korisnika;
|
||||
// dodavanje novog polja zahteva izmenu samo ovog struct-a i relevantnih Scan poziva.
|
||||
type korisnikOpcije struct {
|
||||
aktivan int
|
||||
koristiLokalnuTemu int
|
||||
datumKreiranja time.Time
|
||||
lokalnaTema sql.NullString
|
||||
lokalnaPozadina sql.NullString
|
||||
lokalnaPozadinaOpacity sql.NullString
|
||||
lokalnaPozadinaBlur sql.NullString
|
||||
lokalnaPozadinaBlurPozadine sql.NullString
|
||||
lokalnaPozadinaGlassOpacity sql.NullString
|
||||
avatarPutanja sql.NullString
|
||||
}
|
||||
|
||||
// dodeliOpcijeKorisnika prenosi vrednosti iz korisnikOpcije na model.Korisnik
|
||||
func dodeliOpcijeKorisnika(k *model.Korisnik, o korisnikOpcije) {
|
||||
k.Aktivan = o.aktivan == 1
|
||||
k.KoristiLokalnuTemu = o.koristiLokalnuTemu == 1
|
||||
k.DatumKreiranja = o.datumKreiranja
|
||||
k.LokalnaTema = o.lokalnaTema.String
|
||||
k.LokalnaPozadina = o.lokalnaPozadina.String
|
||||
k.LokalnaPozadinaOpacity = o.lokalnaPozadinaOpacity.String
|
||||
k.LokalnaPozadinaBlur = o.lokalnaPozadinaBlur.String
|
||||
k.LokalnaPozadinaBlurPozadine = o.lokalnaPozadinaBlurPozadine.String
|
||||
k.LokalnaPozadinaGlassOpacity = o.lokalnaPozadinaGlassOpacity.String
|
||||
k.AvatarPutanja = o.avatarPutanja.String
|
||||
}
|
||||
|
||||
// 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 avatarPutanja sql.NullString
|
||||
var datumKreiranja time.Time
|
||||
var o korisnikOpcije
|
||||
if err := row.Scan(
|
||||
&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna,
|
||||
&lokalnaTema, &koristiLokalnuTemu, &datumKreiranja,
|
||||
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur,
|
||||
&lokalnaPozadinaBlurPozadine, &lokalnaPozadinaGlassOpacity, &avatarPutanja,
|
||||
&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &o.aktivan, &k.TotpTajna,
|
||||
&o.lokalnaTema, &o.koristiLokalnuTemu, &o.datumKreiranja,
|
||||
&o.lokalnaPozadina, &o.lokalnaPozadinaOpacity, &o.lokalnaPozadinaBlur,
|
||||
&o.lokalnaPozadinaBlurPozadine, &o.lokalnaPozadinaGlassOpacity, &o.avatarPutanja,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dodeliOpcijeKorisnika(k, aktivan, koristiLokalnuTemu, lokalnaTema,
|
||||
lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur,
|
||||
lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity, avatarPutanja, datumKreiranja)
|
||||
dodeliOpcijeKorisnika(k, o)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
@@ -134,20 +139,16 @@ func (r *sqliteKorisniciRepo) Lista(ctx context.Context) ([]model.Korisnik, erro
|
||||
var lista []model.Korisnik
|
||||
for rows.Next() {
|
||||
var k model.Korisnik
|
||||
var aktivan, koristiLokalnuTemu int
|
||||
var lokalnaTema sql.NullString
|
||||
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
|
||||
var avatarPutanja sql.NullString
|
||||
var datumKreiranja time.Time
|
||||
if err := rows.Scan(&k.ID, &k.KorisnickoIme, &k.Uloga, &aktivan, &k.TotpTajna,
|
||||
&lokalnaTema, &koristiLokalnuTemu, &datumKreiranja,
|
||||
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine,
|
||||
&lokalnaPozadinaGlassOpacity, &avatarPutanja); err != nil {
|
||||
var o korisnikOpcije
|
||||
if err := rows.Scan(
|
||||
&k.ID, &k.KorisnickoIme, &k.Uloga, &o.aktivan, &k.TotpTajna,
|
||||
&o.lokalnaTema, &o.koristiLokalnuTemu, &o.datumKreiranja,
|
||||
&o.lokalnaPozadina, &o.lokalnaPozadinaOpacity, &o.lokalnaPozadinaBlur,
|
||||
&o.lokalnaPozadinaBlurPozadine, &o.lokalnaPozadinaGlassOpacity, &o.avatarPutanja,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("ntech: korisnici.Lista: %w", err)
|
||||
}
|
||||
dodeliOpcijeKorisnika(&k, aktivan, koristiLokalnuTemu, lokalnaTema,
|
||||
lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur,
|
||||
lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity, avatarPutanja, datumKreiranja)
|
||||
dodeliOpcijeKorisnika(&k, o)
|
||||
r.desifrujTotpTajnu(&k)
|
||||
lista = append(lista, k)
|
||||
}
|
||||
|
||||
@@ -166,7 +166,6 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
TopbarLogoSlika: podesavanja["topbar_logo_slika"] == "1",
|
||||
TopbarLogoTekst: podesavanja["topbar_logo_tekst"] == "1",
|
||||
Korisnik: "Admin",
|
||||
}
|
||||
var korisnik *model.Korisnik
|
||||
|
||||
@@ -30,7 +30,6 @@ type PodaciPodesavanja struct {
|
||||
PIB string
|
||||
LogoPutanja string
|
||||
TopbarLogoSlika bool
|
||||
TopbarLogoTekst bool
|
||||
// profil firme — pravni/poreski status (Faza 0); određuje koji se zakonski moduli pale
|
||||
FirmaPravniOblik string
|
||||
FirmaPdvObveznik string
|
||||
@@ -84,7 +83,6 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) {
|
||||
PIB: podesavanja["pib"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
TopbarLogoSlika: podesavanja["topbar_logo_slika"] == "1",
|
||||
TopbarLogoTekst: podesavanja["topbar_logo_tekst"] == "1",
|
||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
||||
BackupVracen: r.URL.Query().Get("sacuvano") == "vraceno",
|
||||
Verzija: h.Verzija,
|
||||
@@ -240,10 +238,6 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
||||
if r.FormValue("topbar_logo_slika") == "1" {
|
||||
topbarLogoSlika = "1"
|
||||
}
|
||||
topbarLogoTekst := "0"
|
||||
if r.FormValue("topbar_logo_tekst") == "1" {
|
||||
topbarLogoTekst = "1"
|
||||
}
|
||||
|
||||
polja := map[string]string{
|
||||
"naziv_firme": r.FormValue("naziv_firme"),
|
||||
@@ -252,7 +246,6 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
||||
"telefon": r.FormValue("telefon"),
|
||||
"pib": r.FormValue("pib"),
|
||||
"topbar_logo_slika": topbarLogoSlika,
|
||||
"topbar_logo_tekst": topbarLogoTekst,
|
||||
// profil firme (Faza 0) — radio dugmad uvek šalju vrednost, pa se uredno čuvaju
|
||||
"firma_pravni_oblik": r.FormValue("firma_pravni_oblik"),
|
||||
"firma_pdv_obveznik": r.FormValue("firma_pdv_obveznik"),
|
||||
@@ -654,7 +647,6 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
|
||||
PIB: podesavanja["pib"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
TopbarLogoSlika: podesavanja["topbar_logo_slika"] == "1",
|
||||
TopbarLogoTekst: podesavanja["topbar_logo_tekst"] == "1",
|
||||
FirmaPravniOblik: vrednostIliDefault(podesavanja, "firma_pravni_oblik", "pausalac"),
|
||||
FirmaPdvObveznik: vrednostIliDefault(podesavanja, "firma_pdv_obveznik", "ne"),
|
||||
FirmaFiskalizacija: vrednostIliDefault(podesavanja, "firma_fiskalizacija", "ne"),
|
||||
|
||||
@@ -328,7 +328,12 @@ func (h *Handler) ProfilOtpremiAvatar(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
buf := make([]byte, 512)
|
||||
n, _ := fajl.Read(buf)
|
||||
n, err := fajl.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čitanju fajla.")
|
||||
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
stvarniMime := http.DetectContentType(buf[:n])
|
||||
if !strings.HasPrefix(stvarniMime, ocekivaniMime) {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Sadržaj fajla ne odgovara odabranoj ekstenziji.")
|
||||
|
||||
@@ -38,7 +38,6 @@ type PodaciStranice struct {
|
||||
Podnazlov string
|
||||
LogoPutanja string // putanja do slike loga firme
|
||||
TopbarLogoSlika bool // prikaži logo sliku u topbaru
|
||||
TopbarLogoTekst bool // prikaži naziv firme u topbaru
|
||||
AvatarPutanja string // putanja do lične avatar slike korisnika
|
||||
Korisnik string
|
||||
KorisnikIme string // korisničko ime prijavljenog korisnika
|
||||
|
||||
@@ -9,27 +9,27 @@
|
||||
<div class="poruka-uspeh">Podešavanja su uspešno sačuvana.</div>
|
||||
{{end}}
|
||||
|
||||
<!-- upload loga — posebna forma jer je multipart, mora biti van glavne forme -->
|
||||
<form method="POST" action="/podesavanja/logo" enctype="multipart/form-data">
|
||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||
<div class="kartica animiraj" style="margin-bottom:16px;">
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
||||
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Logo firme</span>
|
||||
</div>
|
||||
{{if .LogoPutanja}}
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
|
||||
<img src="{{.LogoPutanja}}" alt="Trenutni logo"
|
||||
style="max-height:60px;max-width:200px;object-fit:contain;border:0.5px solid var(--ivica);border-radius:8px;padding:6px;background:var(--kartica);flex-shrink:0;">
|
||||
<form method="POST" action="/podesavanja/logo/ukloni" style="margin:0;">
|
||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||
<button type="submit"
|
||||
style="padding:6px 14px;background:none;border:0.5px solid #dc2626;color:#dc2626;border-radius:8px;font-size:13px;cursor:pointer;white-space:nowrap;">
|
||||
Ukloni sliku
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
<!-- logo kartica: dve forme su sestre unutar div.kartica, nisu ugnježdene -->
|
||||
<div class="kartica animiraj" style="margin-bottom:16px;">
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
||||
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Logo firme</span>
|
||||
</div>
|
||||
{{if .LogoPutanja}}
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
|
||||
<img src="{{.LogoPutanja}}" alt="Trenutni logo"
|
||||
style="max-height:60px;max-width:200px;object-fit:contain;border:0.5px solid var(--ivica);border-radius:8px;padding:6px;background:var(--kartica);flex-shrink:0;">
|
||||
<form method="POST" action="/podesavanja/logo/ukloni" style="margin:0;">
|
||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||
<button type="submit"
|
||||
style="padding:6px 14px;background:none;border:0.5px solid #dc2626;color:#dc2626;border-radius:8px;font-size:13px;cursor:pointer;white-space:nowrap;">
|
||||
Ukloni sliku
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
<form method="POST" action="/podesavanja/logo" enctype="multipart/form-data">
|
||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
|
||||
<input type="file" id="logo-file" name="logo" accept=".png,.jpg,.jpeg,.svg"
|
||||
style="display:none;"
|
||||
@@ -46,8 +46,8 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="pomocni-tekst" style="font-size:12px;margin-top:6px;">PNG, JPG ili SVG — maksimum 2 MB</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- firma: naziv, podnazlov, adresa, telefon, PIB, logo zona -->
|
||||
<form method="POST" action="/podesavanja/sacuvaj">
|
||||
|
||||
Reference in New Issue
Block a user