licence-server/cmd/server/main.go
Nenad Djukic 56f2dfe17f Z1: proizvod ASP-Terminia (migracija + registracija u startup listi)
Migracija po sablonu 002_seed_products.sql (INSERT IGNORE, idempotentna).

limits/features su PLACEHOLDER - vlasnik jos nije odlucio pakete
(basic/pro). Bezbedno jer:
- server cuva oba polja kao json.RawMessage, cist passthrough, ne tumaci ih
- firmware (ASP-TERMINIA repo, licenca.cpp) JOS NE CITA features/limits -
  parsira samo edition/customer/expires_at/grace_days/machine_fingerprint;
  enforcement dolazi tek u zadatku Z4
- svaka izdata licenca nosi SVOJE limits/features (ne nasledjuje default
  posle izdavanja), pa promena default-a kasnije ne trazi novu migraciju

cmd/server/main.go: registrovan migrations/004_... u runMigrations() listi
(migracije se pokrecu automatski pri svakom startu servisa, ali SAMO iz te
liste - nov fajl sam po sebi ne bi se pokrenuo bez ove linije).

Resava P3 iz docs/asp-terminia/02-api-ugovor-firmware.md (format
limits/features za ASP proizvod) - opisano u poruci commit-a kao odgovor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 08:40:43 +02:00

129 lines
3.1 KiB
Go

package main
import (
"licence-server/internal/config"
"licence-server/internal/handler"
"licence-server/internal/model"
"licence-server/internal/repository"
"licence-server/internal/router"
"licence-server/internal/service"
"database/sql"
"fmt"
"log"
"net/http"
"os"
"strconv"
_ "github.com/go-sql-driver/mysql"
"golang.org/x/crypto/bcrypt"
)
func main() {
cfg := config.Load()
// Connect to MySQL
db, err := sql.Open("mysql", cfg.DSN())
if err != nil {
log.Fatal("DB open: ", err)
}
defer db.Close()
if err := db.Ping(); err != nil {
log.Fatal("DB ping: ", err)
}
// Run migrations
runMigrations(db)
// Crypto service
cryptoSvc, err := service.NewCryptoService(cfg.RSAPrivateKey)
if err != nil {
log.Fatal("Crypto: ", err)
}
// Repositories
licenseRepo := repository.NewLicenseRepo(db)
activationRepo := repository.NewActivationRepo(db)
auditRepo := repository.NewAuditRepo(db)
userRepo := repository.NewUserRepo(db)
// Seed default admin user
seedDefaultAdmin(userRepo, cfg.AdminPassword)
// Services
licenseSvc := service.NewLicenseService(licenseRepo, auditRepo)
activationSvc := service.NewActivationService(activationRepo, licenseRepo, auditRepo, cryptoSvc, licenseSvc)
// Handlers
clientHandler := handler.NewClientHandler(activationSvc)
adminHandler := handler.NewAdminHandler(licenseSvc, activationSvc, auditRepo)
dashboardHandler := handler.NewDashboardHandler(licenseSvc, activationSvc, auditRepo, userRepo, "templates")
// Rate limits
rlActivate, _ := strconv.Atoi(cfg.RateLimitActivate)
if rlActivate == 0 {
rlActivate = 10
}
rlValidate, _ := strconv.Atoi(cfg.RateLimitValidate)
if rlValidate == 0 {
rlValidate = 60
}
// Router
mux := router.Setup(clientHandler, adminHandler, dashboardHandler, cfg.AdminAPIKey, rlActivate, rlValidate)
addr := ":" + cfg.Port
fmt.Printf("Licence Server pokrenut na http://localhost%s\n", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
log.Fatal(err)
}
}
func runMigrations(db *sql.DB) {
files := []string{
"migrations/001_create_tables.sql",
"migrations/002_seed_products.sql",
"migrations/003_create_admin_users.sql",
"migrations/004_seed_asp_terminia.sql",
}
for _, f := range files {
data, err := os.ReadFile(f)
if err != nil {
log.Printf("Migration %s: %v", f, err)
continue
}
_, err = db.Exec(string(data))
if err != nil {
log.Printf("Migration %s: %v (may already exist)", f, err)
}
}
}
func seedDefaultAdmin(userRepo *repository.UserRepo, adminPassword string) {
count, err := userRepo.Count()
if err != nil {
log.Printf("Seed admin: count error: %v", err)
return
}
if count > 0 {
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
if err != nil {
log.Fatal("Seed admin: bcrypt error: ", err)
}
user := &model.AdminUser{
Username: "admin",
PasswordHash: string(hash),
FullName: "Administrator",
Active: true,
}
if err := userRepo.Create(user); err != nil {
log.Printf("Seed admin: %v (may already exist)", err)
} else {
log.Println("Default admin korisnik kreiran (username: admin)")
}
}