dal-license-server/internal/model/license.go
djuka dc0114e4b7 Inicijalni commit: kompletna implementacija + dokumentacija + testovi
- Kompletna Go implementacija licencnog servera (19 Go fajlova)
- Klijentski API: activate, deactivate, validate
- Admin API: CRUD licence, stats, audit log
- Admin dashboard: htmx + Go templates
- RSA-2048 potpisivanje licencnih podataka
- Rate limiting i API key autentifikacija
- MySQL migracije i seed podaci (ESIR, ARV, LIGHT_TICKET)
- Unit testovi: keygen, crypto, model, middleware (24 testa)
- Dokumentacija: SPEC.md, ARCHITECTURE.md, SETUP.md, API.md, TESTING.md, README.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 07:42:25 +00:00

130 lines
3.2 KiB
Go

package model
import (
"database/sql"
"encoding/json"
"time"
)
type Product struct {
ID int64 `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
KeyPrefix string `json:"key_prefix"`
DefaultLimits json.RawMessage `json:"default_limits"`
AvailableFeatures json.RawMessage `json:"available_features"`
Active bool `json:"active"`
CreatedAt time.Time `json:"created_at"`
}
type License struct {
ID int64 `json:"id"`
ProductID int64 `json:"product_id"`
LicenseKey string `json:"license_key"`
LicenseType string `json:"license_type"`
CustomerName string `json:"customer_name"`
CustomerPIB string `json:"customer_pib"`
CustomerEmail string `json:"customer_email"`
Limits json.RawMessage `json:"limits"`
Features json.RawMessage `json:"features"`
IssuedAt time.Time `json:"issued_at"`
ExpiresAt sql.NullTime `json:"expires_at"`
GraceDays int `json:"grace_days"`
Active bool `json:"active"`
Revoked bool `json:"revoked"`
RevokedAt sql.NullTime `json:"revoked_at"`
RevokedReason string `json:"revoked_reason"`
Notes string `json:"notes"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Joined fields
ProductCode string `json:"product_code,omitempty"`
ProductName string `json:"product_name,omitempty"`
ProductPrefix string `json:"-"`
}
func (l *License) IsExpired() bool {
if !l.ExpiresAt.Valid {
return false // PERPETUAL
}
return time.Now().After(l.ExpiresAt.Time)
}
func (l *License) IsInGrace() bool {
if !l.ExpiresAt.Valid {
return false
}
if !l.IsExpired() {
return false
}
graceEnd := l.ExpiresAt.Time.AddDate(0, 0, l.GraceDays)
return time.Now().Before(graceEnd)
}
func (l *License) IsGraceExpired() bool {
if !l.ExpiresAt.Valid {
return false
}
graceEnd := l.ExpiresAt.Time.AddDate(0, 0, l.GraceDays)
return time.Now().After(graceEnd)
}
func (l *License) MaskedKey() string {
if len(l.LicenseKey) < 10 {
return l.LicenseKey
}
return l.LicenseKey[:len(l.ProductPrefix)+4] + "-****-****-" + l.LicenseKey[len(l.LicenseKey)-4:]
}
func (l *License) StatusText() string {
if l.Revoked {
return "Opozvana"
}
if !l.Active {
return "Neaktivna"
}
if l.IsGraceExpired() {
return "Istekla (grace)"
}
if l.IsInGrace() {
return "Grace period"
}
if l.IsExpired() {
return "Istekla"
}
if l.LicenseType == "TRIAL" {
return "Trial"
}
return "Aktivna"
}
func (l *License) StatusClass() string {
switch l.StatusText() {
case "Aktivna":
return "status-active"
case "Trial":
return "status-trial"
case "Grace period":
return "status-grace"
case "Istekla", "Istekla (grace)":
return "status-expired"
case "Opozvana":
return "status-revoked"
default:
return ""
}
}
func (l *License) ExpiresAtFormatted() string {
if !l.ExpiresAt.Valid {
return "Neograniceno"
}
return l.ExpiresAt.Time.Format("02.01.2006")
}
type LicenseWithActivation struct {
License
ActiveActivations int `json:"active_activations"`
}