package service import ( "dal-license-server/internal/model" "dal-license-server/internal/repository" "database/sql" "encoding/json" "fmt" "time" ) type ActivationService struct { activations *repository.ActivationRepo licenses *repository.LicenseRepo audit *repository.AuditRepo crypto *CryptoService licSvc *LicenseService } func NewActivationService(activations *repository.ActivationRepo, licenses *repository.LicenseRepo, audit *repository.AuditRepo, crypto *CryptoService, licSvc *LicenseService) *ActivationService { return &ActivationService{activations: activations, licenses: licenses, audit: audit, crypto: crypto, licSvc: licSvc} } func (s *ActivationService) Activate(req *model.ActivateRequest, ip string) (*model.ActivateResponse, error) { license, err := s.licenses.GetByKey(req.LicenseKey) if err != nil { return nil, &LicenseError{Code: "INVALID_KEY", Message: "Licencni kljuc nije pronadjen"} } if license.Revoked { return nil, &LicenseError{Code: "KEY_REVOKED", Message: "Licenca je opozvana"} } if !license.Active { return nil, &LicenseError{Code: "KEY_REVOKED", Message: "Licenca nije aktivna"} } if license.IsExpired() && !license.IsInGrace() { return nil, &LicenseError{Code: "KEY_EXPIRED", Message: "Licenca je istekla"} } // Check existing activation existing, err := s.activations.GetActiveByLicense(license.ID) if err == nil && existing != nil { if existing.MachineFingerprint == req.MachineFingerprint { // Same machine — refresh s.activations.UpdateLastSeen(existing.ID) } else { return nil, &LicenseError{ Code: "ALREADY_ACTIVATED", Message: "Licenca je vec aktivirana na drugom racunaru", Details: map[string]interface{}{ "activated_on": existing.Hostname, "activated_at": existing.ActivatedAt.Format(time.RFC3339), }, } } } else { // New activation _, err = s.activations.Create(&model.Activation{ LicenseID: license.ID, MachineFingerprint: req.MachineFingerprint, Hostname: req.Hostname, OSInfo: req.OS, AppVersion: req.AppVersion, IPAddress: ip, }) if err != nil { return nil, fmt.Errorf("activate: %w", err) } } s.audit.Log(&license.ID, "ACTIVATE", ip, map[string]interface{}{ "fingerprint": req.MachineFingerprint, "hostname": req.Hostname, "os": req.OS, "app_version": req.AppVersion, }) // Build signed license data licenseJSON, err := s.licSvc.BuildLicenseData(license, req.MachineFingerprint) if err != nil { return nil, fmt.Errorf("build license data: %w", err) } signature, err := s.crypto.Sign(licenseJSON) if err != nil { return nil, fmt.Errorf("sign license: %w", err) } var ld model.LicenseData json.Unmarshal(licenseJSON, &ld) return &model.ActivateResponse{ License: ld, Signature: signature, }, nil } func (s *ActivationService) Deactivate(req *model.DeactivateRequest, ip string) (*model.DeactivateResponse, error) { license, err := s.licenses.GetByKey(req.LicenseKey) if err != nil { return nil, &LicenseError{Code: "INVALID_KEY", Message: "Licencni kljuc nije pronadjen"} } act, err := s.activations.GetByLicenseAndFingerprint(license.ID, req.MachineFingerprint) if err != nil || act == nil { return nil, &LicenseError{Code: "NOT_ACTIVATED", Message: "Licenca nije aktivirana na ovom racunaru"} } s.activations.Deactivate(license.ID, req.MachineFingerprint) s.audit.Log(&license.ID, "DEACTIVATE", ip, map[string]interface{}{ "fingerprint": req.MachineFingerprint, }) return &model.DeactivateResponse{ Message: "Licenca uspesno deaktivirana", CanReactivate: true, }, nil } func (s *ActivationService) Validate(req *model.ValidateRequest, ip string) (*model.ValidateResponse, error) { license, err := s.licenses.GetByKey(req.LicenseKey) if err != nil { return &model.ValidateResponse{Valid: false}, nil } // Update last_seen act, err := s.activations.GetByLicenseAndFingerprint(license.ID, req.MachineFingerprint) if err == nil && act != nil { s.activations.UpdateLastSeen(act.ID) } s.audit.Log(&license.ID, "VALIDATE", ip, map[string]interface{}{ "fingerprint": req.MachineFingerprint, }) expiresAt := "" if license.ExpiresAt.Valid { expiresAt = license.ExpiresAt.Time.Format(time.RFC3339) } valid := license.Active && !license.Revoked if license.IsExpired() && !license.IsInGrace() { valid = false } return &model.ValidateResponse{ Valid: valid, ExpiresAt: expiresAt, Revoked: license.Revoked, }, nil } func (s *ActivationService) ForceRelease(licenseID int64, ip string) error { s.activations.ForceRelease(licenseID) s.audit.Log(&licenseID, "FORCE_RELEASE", ip, nil) return nil } func (s *ActivationService) ListByLicense(licenseID int64) ([]model.Activation, error) { return s.activations.ListByLicense(licenseID) } // LicenseError is a typed error for client API type LicenseError struct { Code string Message string Details interface{} } func (e *LicenseError) Error() string { return e.Message } // Needed to satisfy import var _ = sql.NullTime{}