package models import ( "log" "github.com/google/uuid" "github.com/lib/pq" "gorm.io/gorm" "rocketgit.ru/rsmon/worker/app/models/concerns" ) var canonicalPlanCodes = []string{"free", "solo", "team", "enterprise"} // CanonicalPlanCodes returns the supported public catalog codes. func CanonicalPlanCodes() []string { return append([]string(nil), canonicalPlanCodes...) } // Plan is a versioned-by-code catalog entry. A zero cap means unlimited. type Plan struct { concerns.Model Code string `gorm:"size:32;not null" json:"code"` NameRU string `gorm:"size:64;not null" json:"name_ru"` NameEN string `gorm:"size:64;not null" json:"name_en"` PriceMonthlyMinor int64 `gorm:"not null;default:0" json:"price_monthly_minor"` PriceAnnualMinor int64 `gorm:"not null;default:0" json:"price_annual_minor"` Currency string `gorm:"size:3;not null;default:'RUB'" json:"currency"` MonitorCap int64 `gorm:"not null;default:0" json:"monitor_cap"` IntervalMinSeconds int `gorm:"not null;default:30" json:"interval_min_seconds"` StatusPagesCap int64 `gorm:"not null;default:0" json:"status_pages_cap"` MaintenanceCap int64 `gorm:"not null;default:0" json:"maintenance_cap"` LoginSeatsIncluded int64 `gorm:"not null;default:0" json:"login_seats_included"` NotifySeatsIncluded int64 `gorm:"not null;default:0" json:"notify_seats_included"` UnlimitedSeats bool `gorm:"not null;default:false" json:"unlimited_seats"` Integrations pq.StringArray `gorm:"type:text[];not null;default:'{}'" json:"integrations"` CheckKinds pq.StringArray `gorm:"type:text[];not null;default:'{}'" json:"check_kinds"` DataRetentionMonths int `gorm:"not null;default:3" json:"data_retention_months"` DistributedWorkers bool `gorm:"not null;default:false" json:"distributed_workers"` Confirmations bool `gorm:"not null;default:false" json:"confirmations"` AllowHardAlerts bool `gorm:"not null;default:false" json:"allow_hard_alerts"` ConfirmTimeoutSec int `gorm:"not null;default:90" json:"confirm_timeout_sec"` HealthWindowSec int `gorm:"not null;default:300" json:"health_window_sec"` HealthRateThreshold float64 `gorm:"not null;default:0.5" json:"health_rate_threshold"` HealthMinAttempts int `gorm:"not null;default:10" json:"health_min_attempts"` SOC2 bool `gorm:"not null;default:false" json:"soc2"` GDPRDPA bool `gorm:"not null;default:false" json:"gdpr_dpa"` IsDefault bool `gorm:"not null;default:false" json:"is_default"` Archived bool `gorm:"not null;default:false" json:"archived"` // Deprecated source-compatibility fields. The legacy plans table is retained // as plans_legacy; these values are never written to the canonical catalog. Default bool `gorm:"-" json:"Default,omitempty"` Name string `gorm:"-" json:"Name,omitempty"` HTTPMonitors *int64 `gorm:"-" json:"HTTPMonitors,omitempty"` DNSMonitors *int64 `gorm:"-" json:"DNSMonitors,omitempty"` WHOISMonitors *int64 `gorm:"-" json:"WHOISMonitors,omitempty"` TotalMonitors *int64 `gorm:"-" json:"TotalMonitors,omitempty"` Price int `gorm:"-" json:"Price,omitempty"` TrialPeriod int `gorm:"-" json:"TrialPeriod,omitempty"` concerns.Timestamped Audited } func (p *Plan) BeforeCreate(_ *gorm.DB) error { if p.Code == "" { p.Code = "legacy-" + uuid.NewString()[:24] } if p.NameRU == "" { p.NameRU = p.Name } if p.NameEN == "" { p.NameEN = p.NameRU } if p.Currency == "" { p.Currency = "RUB" } if p.IntervalMinSeconds == 0 { p.IntervalMinSeconds = 30 } return nil } func (p *Plan) AfterFind(_ *gorm.DB) error { p.Name = p.NameRU p.Price = int(p.PriceMonthlyMinor / 100) p.Default = p.IsDefault p.TotalMonitors = &p.MonitorCap return nil } // DefaultPlan returns the canonical free plan. func DefaultPlan() Plan { pl := Plan{} if err := DB().Where("code = ? AND archived = FALSE", "free").First(&pl).Error; err != nil { log.Println("unable to find default plan") panic(err) } return pl } // AllowsDistributed keeps legacy callers working while using the canonical // entitlement flag for catalog plans. func (p *Plan) AllowsDistributed() bool { return p != nil && (p.DistributedWorkers || p.Price > 0) }