first commit

This commit is contained in:
Maksim Totmin
2026-10-01 09:50:59 +07:00
commit 4beb4b55d3
29 changed files with 3034 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
package lexicomai
import (
"encoding/json"
"fmt"
"strings"
"time"
)
// License — содержимое license.json продукта. Формат (подтверждён на прод-боксе):
//
// { "service": "cls", "expire": "2045-01-01", "licenses": 0, "key": "50e1...9cf" }
//
// Key — подписанная последовательность (service+expire+licenses+uuid), её
// генерация — на стороне вендора; модуль только валидирует и применяет.
type License struct {
Service string `json:"service"`
Expire string `json:"expire"`
Licenses int `json:"licenses"`
Key string `json:"key"`
}
// ParseLicense разбирает license.json. Отсутствующий файл — доменная ошибка
// (из неё инструмент делает понятный errorResult).
func ParseLicense(data []byte) (*License, error) {
var l License
if err := json.Unmarshal(data, &l); err != nil {
return nil, fmt.Errorf("parse license: %w", err)
}
if strings.TrimSpace(l.Service) == "" {
return nil, fmt.Errorf("license: service is empty")
}
if strings.TrimSpace(l.Expire) == "" {
return nil, fmt.Errorf("license: expire is empty")
}
return &l, nil
}
// VerifyFields проверяет поля лицензии без обращения к сервису: привязана ли
// она к нужному продукту и не истекла ли. Возвращает список замечаний (пустой —
// поля корректны).
func (l *License) VerifyFields(product string) []string {
var notes []string
if strings.TrimSpace(l.Service) != product {
notes = append(notes, fmt.Sprintf("license bound to %q, wanted %q", l.Service, product))
}
if exp, err := time.Parse("2006-01-02", l.Expire); err == nil {
if time.Now().After(exp) {
notes = append(notes, fmt.Sprintf("license expired on %s", l.Expire))
}
} else {
notes = append(notes, fmt.Sprintf("license expire %q is not YYYY-MM-DD", l.Expire))
}
if l.Licenses == 0 {
notes = append(notes, "license has 0 seats")
}
return notes
}
// KeyMasked возвращает ключ лицензии с маской — полный key не выводим
// (может быть чувствительным). Короткие ключи скрываются целиком.
func (l *License) KeyMasked() string {
k := strings.TrimSpace(l.Key)
if k == "" {
return ""
}
if len(k) <= 6 {
return "***"
}
return k[:3] + "…" + k[len(k)-3:]
}