package model import ( "encoding/base64" "encoding/json" "fmt" "reflect" "time" "github.com/pkg/errors" basemodel "go.signoz.io/signoz/pkg/query-service/model" ) type License struct { Key string `json:"key" db:"key"` ActivationId string `json:"activationId" db:"activationId"` CreatedAt time.Time `db:"created_at"` // PlanDetails contains the encrypted plan info PlanDetails string `json:"planDetails" db:"planDetails"` // stores parsed license details LicensePlan FeatureSet basemodel.FeatureSet // populated in case license has any errors ValidationMessage string `db:"validationMessage"` // used only for sending details to front-end IsCurrent bool `json:"isCurrent"` } func (l *License) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { Key string `json:"key" db:"key"` ActivationId string `json:"activationId" db:"activationId"` ValidationMessage string `db:"validationMessage"` IsCurrent bool `json:"isCurrent"` PlanKey string `json:"planKey"` ValidFrom time.Time `json:"ValidFrom"` ValidUntil time.Time `json:"ValidUntil"` Status string `json:"status"` }{ Key: l.Key, ActivationId: l.ActivationId, IsCurrent: l.IsCurrent, PlanKey: l.PlanKey, ValidFrom: time.Unix(l.ValidFrom, 0), ValidUntil: time.Unix(l.ValidUntil, 0), Status: l.Status, ValidationMessage: l.ValidationMessage, }) } type LicensePlan struct { PlanKey string `json:"planKey"` ValidFrom int64 `json:"validFrom"` ValidUntil int64 `json:"validUntil"` Status string `json:"status"` } func (l *License) ParsePlan() error { l.LicensePlan = LicensePlan{} planData, err := base64.StdEncoding.DecodeString(l.PlanDetails) if err != nil { return err } plan := LicensePlan{} err = json.Unmarshal([]byte(planData), &plan) if err != nil { l.ValidationMessage = "failed to parse plan from license" return errors.Wrap(err, "failed to parse plan from license") } l.LicensePlan = plan l.ParseFeatures() return nil } func (l *License) ParseFeatures() { switch l.PlanKey { case Pro: l.FeatureSet = ProPlan case Enterprise: l.FeatureSet = EnterprisePlan default: l.FeatureSet = BasicPlan } } type Licenses struct { TrialStart int64 `json:"trialStart"` TrialEnd int64 `json:"trialEnd"` OnTrial bool `json:"onTrial"` WorkSpaceBlock bool `json:"workSpaceBlock"` TrialConvertedToSubscription bool `json:"trialConvertedToSubscription"` GracePeriodEnd int64 `json:"gracePeriodEnd"` Licenses []License `json:"licenses"` } type SubscriptionServerResp struct { Status string `json:"status"` Data Licenses `json:"data"` } type Plan struct { Name string `json:"name"` } type LicenseDB struct { ID string `json:"id"` Key string `json:"key"` Data string `json:"data"` } type LicenseV3 struct { ID string Key string Data map[string]interface{} PlanName string Features basemodel.FeatureSet Status string IsCurrent bool ValidFrom int64 ValidUntil int64 } func extractKeyFromMapStringInterface[T any](data map[string]interface{}, key string) (T, error) { var zeroValue T if val, ok := data[key]; ok { if value, ok := val.(T); ok { return value, nil } return zeroValue, fmt.Errorf("%s key is not a valid %s", key, reflect.TypeOf(zeroValue)) } return zeroValue, fmt.Errorf("%s key is missing", key) } func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) { var features basemodel.FeatureSet // extract id from data licenseID, err := extractKeyFromMapStringInterface[string](data, "id") if err != nil { return nil, err } delete(data, "id") // extract key from data licenseKey, err := extractKeyFromMapStringInterface[string](data, "key") if err != nil { return nil, err } delete(data, "key") // extract status from data status, err := extractKeyFromMapStringInterface[string](data, "status") if err != nil { return nil, err } planMap, err := extractKeyFromMapStringInterface[map[string]any](data, "plan") if err != nil { return nil, err } planName, err := extractKeyFromMapStringInterface[string](planMap, "name") if err != nil { return nil, err } // if license status is inactive then default it to basic if status == LicenseStatusInactive { planName = PlanNameBasic } featuresFromZeus := basemodel.FeatureSet{} if _features, ok := data["features"]; ok { featuresData, err := json.Marshal(_features) if err != nil { return nil, errors.Wrap(err, "failed to marshal features data") } if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil { return nil, errors.Wrap(err, "failed to unmarshal features data") } } switch planName { case PlanNameTeams: features = append(features, ProPlan...) case PlanNameEnterprise: features = append(features, EnterprisePlan...) case PlanNameBasic: features = append(features, BasicPlan...) default: features = append(features, BasicPlan...) } if len(featuresFromZeus) > 0 { for _, feature := range featuresFromZeus { exists := false for i, existingFeature := range features { if existingFeature.Name == feature.Name { features[i] = feature // Replace existing feature exists = true break } } if !exists { features = append(features, feature) // Append if it doesn't exist } } } data["features"] = features _validFrom, err := extractKeyFromMapStringInterface[float64](data, "valid_from") if err != nil { _validFrom = 0 } validFrom := int64(_validFrom) _validUntil, err := extractKeyFromMapStringInterface[float64](data, "valid_until") if err != nil { _validUntil = 0 } validUntil := int64(_validUntil) return &LicenseV3{ ID: licenseID, Key: licenseKey, Data: data, PlanName: planName, Features: features, ValidFrom: validFrom, ValidUntil: validUntil, Status: status, }, nil } func NewLicenseV3WithIDAndKey(id string, key string, data map[string]interface{}) (*LicenseV3, error) { licenseDataWithIdAndKey := data licenseDataWithIdAndKey["id"] = id licenseDataWithIdAndKey["key"] = key return NewLicenseV3(licenseDataWithIdAndKey) } func ConvertLicenseV3ToLicenseV2(l *LicenseV3) *License { planKeyFromPlanName, ok := MapOldPlanKeyToNewPlanName[l.PlanName] if !ok { planKeyFromPlanName = Basic } return &License{ Key: l.Key, ActivationId: "", PlanDetails: "", FeatureSet: l.Features, ValidationMessage: "", IsCurrent: l.IsCurrent, LicensePlan: LicensePlan{ PlanKey: planKeyFromPlanName, ValidFrom: l.ValidFrom, ValidUntil: l.ValidUntil, Status: l.Status}, } }