From 1cfb44b9a42677b7365fa3b6b72edc3135d5f3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Fri, 19 Jun 2026 01:11:40 +0200 Subject: [PATCH] Demo mod, favicon ispravka i putanja ntech.env uz bazu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NTECH_ENV=demo aktivira demo mod: korisnik Demo/Demo1234 (admin) se kreira ili resetuje pri svakom pokretanju - Login ekran u demo modu prikazuje pre-popunjena polja i "DEMO verzija" - ntech.env se čuva u istom direktorijumu kao SQLite baza (umesto uvek u radnom direktorijumu) — rešava Docker volume problem - favicon.svg: uklonjen width="100%" koji je sprečavao prikaz ikone u brauzeru --- cmd/ntech/main.go | 63 ++++++++++++++++++++++++----- internal/config/firstrun.go | 17 ++++++-- internal/config/setup.go | 4 +- internal/handler/handler.go | 1 + internal/handler/prijava.go | 1 + web/static/favicon.svg | 2 +- web/templates/stranice/prijava.html | 4 +- 7 files changed, 72 insertions(+), 20 deletions(-) diff --git a/cmd/ntech/main.go b/cmd/ntech/main.go index 00c9eef..70dd93b 100644 --- a/cmd/ntech/main.go +++ b/cmd/ntech/main.go @@ -19,6 +19,7 @@ import ( "ntech" "ntech/internal/auth" "ntech/internal/config" + "ntech/internal/db" "ntech/internal/db/sqlite" "ntech/internal/handler" ntechmw "ntech/internal/middleware" @@ -47,7 +48,12 @@ func podesiLog() { func main() { mime.AddExtensionType(".js", "text/javascript") mime.AddExtensionType(".css", "text/css") - godotenv.Load("ntech.env") + putanjaBaze := os.Getenv("NTECH_SQLITE") + if putanjaBaze == "" { + putanjaBaze = "ntech.db" + } + envFajl := config.PutanjaNtechEnv(putanjaBaze) + godotenv.Load(envFajl) podesiLog() auth.InitAuthLog() @@ -70,8 +76,8 @@ func main() { staticFS = os.DirFS("web/static") } - if config.JelPrvoPokretanje() { - config.PokreniSetup(templFS) + if config.JelPrvoPokretanje(envFajl) { + config.PokreniSetup(templFS, envFajl) return } @@ -80,11 +86,6 @@ func main() { port = "8080" } - putanjaBaze := os.Getenv("NTECH_SQLITE") - if putanjaBaze == "" { - putanjaBaze = "ntech.db" - } - db, err := sqlite.OtvoriDB(putanjaBaze) if err != nil { slog.Error("Greška pri otvaranju baze", "error", err) @@ -111,7 +112,7 @@ func main() { } // ključ za šifrovanje TOTP tajni u mirovanju (AES-256-GCM) - totpKljuc, err := ucitajTotpKljuc() + totpKljuc, err := ucitajTotpKljuc(envFajl) if err != nil { slog.Error("Greška pri učitavanju ključa za TOTP", "error", err) os.Exit(1) @@ -130,6 +131,13 @@ func main() { h := handler.Novi(db, totpKljuc) h.Verzija = Verzija + h.JelDemo = os.Getenv("NTECH_ENV") == "demo" + if h.JelDemo { + h.Verzija = "DEMO verzija" + if err := postaviDemoKorisnika(context.Background(), h.KorisniciRepo); err != nil { + slog.Warn("demo: greška pri postavljanju demo korisnika", "error", err) + } + } // verzija statičkih fajlova za cache-busting — menja se pri svakom pokretanju, // pa novi build/restart natera brauzer da povuče sveži CSS/JS (umesto starog iz keša) h.AssetV = strconv.FormatInt(time.Now().Unix(), 36) @@ -382,7 +390,7 @@ func main() { // // VAŽNO: ako se ovaj ključ izgubi ili promeni, postojeće šifrovane TOTP tajne se // više ne mogu dešifrovati (korisnici moraju ponovo aktivirati 2FA). -func ucitajTotpKljuc() ([]byte, error) { +func ucitajTotpKljuc(envFajl string) ([]byte, error) { if v := os.Getenv("NTECH_TOTP_KEY"); v != "" { kljuc, err := base64.StdEncoding.DecodeString(v) if err != nil { @@ -400,7 +408,7 @@ func ucitajTotpKljuc() ([]byte, error) { return nil, fmt.Errorf("generisanje ključa: %w", err) } enkodiran := base64.StdEncoding.EncodeToString(kljuc) - f, err := os.OpenFile("ntech.env", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + f, err := os.OpenFile(envFajl, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return nil, fmt.Errorf("otvaranje ntech.env: %w", err) } @@ -413,6 +421,39 @@ func ucitajTotpKljuc() ([]byte, error) { return kljuc, nil } +// postaviDemoKorisnika osigurava da pri pokretanju demo instance postoji korisnik "Demo" +// sa ulogom "admin" i resetovanom lozinkom. Poziva se samo kada je NTECH_ENV=demo. +func postaviDemoKorisnika(ctx context.Context, repo db.KorisniciRepository) error { + const ( + demoIme = "Demo" + demoLozinka = "Demo1234" + ) + hash, err := auth.HashujLozinku(demoLozinka) + if err != nil { + return fmt.Errorf("ntech: postaviDemoKorisnika: %w", err) + } + korisnik, err := repo.DohvatiPoImenu(ctx, demoIme) + if err != nil { + // korisnik ne postoji — kreiraj ga + if _, err := repo.Kreiraj(ctx, demoIme, hash, "admin"); err != nil { + return fmt.Errorf("ntech: postaviDemoKorisnika: kreiranje: %w", err) + } + slog.Info("demo korisnik kreiran", "korisnik", demoIme) + return nil + } + // korisnik postoji — resetuj lozinku i osiguraj da je aktivan + if err := repo.PromeniLozinku(ctx, korisnik.ID, hash); err != nil { + return fmt.Errorf("ntech: postaviDemoKorisnika: lozinka: %w", err) + } + if !korisnik.Aktivan { + if err := repo.AzurirajAktivan(ctx, korisnik.ID, true); err != nil { + return fmt.Errorf("ntech: postaviDemoKorisnika: aktivan: %w", err) + } + } + slog.Info("demo korisnik resetovan", "korisnik", demoIme) + return nil +} + // napraviBackup kreira konzistentnu kopiju baze i briše najstarije preko zadatog broja kopija. // Koristi već otvorenu vezu ka bazi (VACUUM INTO je bezbedan na pooled konekciji). func napraviBackup(db *sql.DB, putanjaBaze string) { diff --git a/internal/config/firstrun.go b/internal/config/firstrun.go index 3fc40c9..52758a3 100644 --- a/internal/config/firstrun.go +++ b/internal/config/firstrun.go @@ -4,8 +4,17 @@ import ( "fmt" "net" "os" + "path/filepath" ) +// PutanjaNtechEnv vraća putanju do ntech.env fajla na osnovu putanje SQLite baze. +// Fajl se čuva u istom direktorijumu kao i baza, tako da volume mount direktorijuma +// pokriva i bazu i konfiguraciju. +func PutanjaNtechEnv(sqlitePutanja string) string { + dir := filepath.Dir(sqlitePutanja) + return filepath.Join(dir, "ntech.env") +} + // lista portova koje proveravamo pri prvom pokretanju var kandidatPortovi = []int{8080, 3000, 8000, 9090} @@ -32,8 +41,8 @@ func NadjiSlobodanPort() int { } // proverava da li je ovo prvo pokretanje programa -func JelPrvoPokretanje() bool { - _, err := os.Stat("ntech.env") +func JelPrvoPokretanje(envFajl string) bool { + _, err := os.Stat(envFajl) return os.IsNotExist(err) } @@ -56,7 +65,7 @@ func StatusPortova() []PortStatus { } // SacuvajEnv upisuje izabrani port u ntech.env fajl -func SacuvajEnv(port int) error { +func SacuvajEnv(port int, envFajl string) error { sadrzaj := fmt.Sprintf("NTECH_PORT=%d\n", port) - return os.WriteFile("ntech.env", []byte(sadrzaj), 0600) + return os.WriteFile(envFajl, []byte(sadrzaj), 0600) } diff --git a/internal/config/setup.go b/internal/config/setup.go index 45ddfcd..8a997c6 100644 --- a/internal/config/setup.go +++ b/internal/config/setup.go @@ -46,7 +46,7 @@ func nadjiLokalneAdrese() []string { } // PokreniSetup pokreće HTTP server za prvo podešavanje i čeka da korisnik završi -func PokreniSetup(fsys fs.FS) { +func PokreniSetup(fsys fs.FS, envFajl string) { port := NadjiSlobodanPort() if port == 0 { slog.Error("setup: nije pronađen nijedan slobodan port"); os.Exit(1) @@ -90,7 +90,7 @@ func PokreniSetup(fsys fs.FS) { Port int `json:"port"` } json.NewDecoder(req.Body).Decode(&telo) - if err := SacuvajEnv(telo.Port); err != nil { + if err := SacuvajEnv(telo.Port, envFajl); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 8b5a35a..bf06032 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -41,6 +41,7 @@ type Handler struct { PdvKprRepo db.PdvKprRepository NivelacijaRepo db.NivelacijaRepository Verzija string + JelDemo bool AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju) Templates map[string]*template.Template TemplatesFS fs.FS diff --git a/internal/handler/prijava.go b/internal/handler/prijava.go index 5f1ef4a..2c6964e 100644 --- a/internal/handler/prijava.go +++ b/internal/handler/prijava.go @@ -64,6 +64,7 @@ func (h *Handler) PrikazPrijave(w http.ResponseWriter, r *http.Request) { "LoginPozadinaBlurKartice": loginBlurKartice, "LoginPozadinaZatamnjenjeKartice": loginZatamnjenjeKartice, "Verzija": h.Verzija, + "JelDemo": h.JelDemo, }) } diff --git a/web/static/favicon.svg b/web/static/favicon.svg index 1bc68f7..13aaea8 100644 --- a/web/static/favicon.svg +++ b/web/static/favicon.svg @@ -1,4 +1,4 @@ - + NTech favicon v4 Moderna ikonica sa NT slovima i tehnološkim akcentom diff --git a/web/templates/stranice/prijava.html b/web/templates/stranice/prijava.html index cefeaf4..be8cfdf 100644 --- a/web/templates/stranice/prijava.html +++ b/web/templates/stranice/prijava.html @@ -57,11 +57,11 @@
- +
- +