Files
GoNtech/internal/db/sqlite/migracije.go
T
Dasko 49296e4315 Bezbednosne i integritetne popravke iz pregleda koda
- foreign_keys pragma po konekciji (DSN _pragma) — ranije aktivna na
  samo delu konekcija iz pula, rizik siročadi u bazi pri brisanju
- busy_timeout + _txlock=immediate za konkurentan pristup SQLite bazi
- ispravljen DnevniPrometMaloprodaje (DATE() -> substr) — dnevni pazar
  za KIR/KPO je bio uvek nula
- rate-limit (isti IP-bruteforce brojač) dodat na TOTP/2FA verifikaciju
- ispravljen nil-pointer u DodajDeloNalogu + uklonjeni debug logovi
- izvuciIP prihvata X-Real-IP/X-Forwarded-For samo sa poverljive
  (loopback/privatne) adrese — sprečava zaobilaženje bruteforce brave
- ispravljen naziv dozvole podesavanja.izmena -> podesavanja.izmeni
- brisanje artikala/kategorija prebačeno sa GET na POST (CSRF zaštita)
2026-07-03 23:00:44 +02:00

107 lines
3.0 KiB
Go

package sqlite
import (
"database/sql"
"fmt"
"io/fs"
"log/slog"
"path"
"sort"
"strings"
_ "modernc.org/sqlite"
)
// OtvoriDB otvara konekciju ka SQLite bazi i primenjuje performance PRAGMA podešavanja.
//
// Pragme se prosleđuju kroz DSN (_pragma=...) jer database/sql drži pul konekcija —
// PRAGMA postavljena preko db.Exec važi samo za konekciju koja je taj Exec uslužila,
// dok DSN _pragma parametri primenjuje driver na SVAKU novu konekciju pri otvaranju.
func OtvoriDB(putanja string) (*sql.DB, error) {
dsn := "file:" + putanja +
"?_pragma=journal_mode(WAL)" +
"&_pragma=synchronous(NORMAL)" +
"&_pragma=cache_size(10000)" +
"&_pragma=foreign_keys(1)" +
"&_pragma=busy_timeout(5000)" +
"&_txlock=immediate"
db, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, fmt.Errorf("ntech: OtvoriDB: %w", err)
}
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("ntech: OtvoriDB: %w", err)
}
return db, nil
}
// PokreniMigracije izvršava sve SQL fajlove iz fs.FS koji još nisu izvršeni
func PokreniMigracije(db *sql.DB, fsys fs.FS) error {
// kreiramo tabelu za praćenje migracija ako ne postoji
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS migracije (
naziv TEXT PRIMARY KEY,
datum_izvrsavanja DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)
`)
if err != nil {
return fmt.Errorf("ntech: PokreniMigracije: kreiranje tabele: %w", err)
}
// čitamo sve .sql fajlove iz foldera
unosi, err := fs.ReadDir(fsys, "migrations")
if err != nil {
return fmt.Errorf("ntech: PokreniMigracije: čitanje foldera: %w", err)
}
// sortiramo po imenu da bi se izvršavali po redu (001, 002, ...)
sort.Slice(unosi, func(i, j int) bool {
return unosi[i].Name() < unosi[j].Name()
})
for _, unos := range unosi {
if path.Ext(unos.Name()) != ".sql" {
continue
}
naziv := unos.Name()
// proveravamo da li je migracija već izvršena
var broj int
err := db.QueryRow("SELECT COUNT(*) FROM migracije WHERE naziv = ?", naziv).Scan(&broj)
if err != nil {
return fmt.Errorf("ntech: PokreniMigracije: provera %s: %w", naziv, err)
}
if broj > 0 {
continue
}
// čitamo sadržaj SQL fajla
putanja := "migrations/" + naziv
sadrzaj, err := fs.ReadFile(fsys, putanja)
if err != nil {
return fmt.Errorf("ntech: PokreniMigracije: čitanje %s: %w", naziv, err)
}
// izvršavamo SQL
if _, err := db.Exec(string(sadrzaj)); err != nil {
// "duplicate column name" znači da kolona već postoji — željeno stanje je ispunjeno,
// pa nastavljamo i beležimo migraciju kao izvršenu
if strings.Contains(err.Error(), "duplicate column name") {
slog.Warn("migracija: kolona već postoji, preskačemo", "migracija", naziv)
} else {
return fmt.Errorf("ntech: PokreniMigracije: izvršavanje %s: %w", naziv, err)
}
}
// upisujemo u tabelu migracija da je izvršena
if _, err := db.Exec("INSERT INTO migracije (naziv) VALUES (?)", naziv); err != nil {
return fmt.Errorf("ntech: PokreniMigracije: upis %s: %w", naziv, err)
}
}
return nil
}