package models import ( "time" "github.com/pkg/errors" "gorm.io/gorm" "rocketgit.ru/rsmon/worker/app/models/concerns" ) // Account represents a tenant — an isolated billing/permissions boundary // that groups users, contacts, groups, monitors and notifications. // // Access to an Account is granted via the Access join table (see Access). // `Role` on Account is a gorm:"-":all" virtual column populated by the // controllers for the current session — it is the role the calling user // holds on THIS account, not a property of the account itself. type Account struct { concerns.Model Name string `json:"name"` Accesses []Access `json:"-"` Contacts []Contact `json:"-"` Groups []Group `json:"-"` Notifications []Notification `json:"-"` PlanID *int64 `gorm:"type:bigint REFERENCES plans(id)" json:"-"` Plan *Plan `json:"plan"` // Diagnostic overrides are available only to plans that include confirmations. // Nil keeps the catalog value; bounds are enforced by DiagnosticSettings. ConfirmTimeoutSec *int `json:"confirm_timeout_sec,omitempty"` HealthWindowSec *int `json:"health_window_sec,omitempty"` HealthRateThreshold *float64 `json:"health_rate_threshold,omitempty"` HealthMinAttempts *int `json:"health_min_attempts,omitempty"` Role string `gorm:"-:all" json:"role"` Timezone string `json:"timezone"` Language string `gorm:"default:'ru'" json:"language"` Disabled bool `gorm:"not null;default:false" json:"disabled"` Blocked bool `gorm:"not null;default:false" json:"blocked"` PaidUntil *time.Time `json:"paid_until"` TrialEndsAt *time.Time `json:"trial_ends_at,omitempty"` Deleted bool `gorm:"not null;default:false"` concerns.Timestamped Audited } // Users provides functionality. func (a Account) Users() []User { //nolint:gocritic // hugeParam: accepted for interface compatibility users := make([]User, 0) err := DB().Where("id IN (SELECT user_id FROM accesses WHERE account_id = ?)", a.ID).Find(&users).Error if err != nil { panic(err) } return users } // CreateAccountForUser provides functionality. func CreateAccountForUser(name string, u *User) (*Account, error) { trialPlan := Plan{} if err := DB().Where("code = ? AND archived = FALSE", "team").First(&trialPlan).Error; err != nil { return nil, errors.Wrap(err, "failed to find trial plan") } trialEndsAt := time.Now().UTC().AddDate(0, 0, 14) account := Account{PlanID: &trialPlan.ID, TrialEndsAt: &trialEndsAt} if name != "" { account.Name = name } err := DB().Transaction(func(tx *gorm.DB) error { if err := tx.Create(&account).Error; err != nil { return errors.Wrap(err, "failed to create account") } if err := tx.Create(&Subscription{ AccountID: account.ID, PlanID: trialPlan.ID, Provider: "manual", Status: SubscriptionStatusTrialing, BillingCycle: "monthly", Currency: trialPlan.Currency, AmountMinor: trialPlan.PriceMonthlyMinor, CurrentPeriodEnd: &trialEndsAt, TrialEndsAt: &trialEndsAt, }).Error; err != nil { return errors.Wrap(err, "failed to create subscription") } var subscription Subscription if err := tx.Where("account_id = ?", account.ID).First(&subscription).Error; err != nil { return errors.Wrap(err, "failed to load trial subscription") } if err := tx.Create(&SubscriptionEvent{SubscriptionID: subscription.ID, AccountID: account.ID, Provider: "manual", Kind: "trial_started", ToPlanID: &trialPlan.ID, ActorUserID: &u.ID, CreatedAt: time.Now().UTC()}).Error; err != nil { return errors.Wrap(err, "failed to record trial") } access := Access{AccountID: account.ID, UserID: &u.ID, Role: "owner", SeatType: "admin"} if err := tx.Create(&access).Error; err != nil { return errors.Wrap(err, "failed to create access") } group := Group{AccountID: account.ID, Name: "Основные"} if err := tx.Create(&group).Error; err != nil { return errors.Wrap(err, "failed to create group") } notification := Notification{AccountID: account.ID, Name: "Основные", Enabled: true} if err := tx.Create(¬ification).Error; err != nil { return errors.Wrap(err, "failed to create notification") } if u.Email != nil { contact := Contact{AccountID: &account.ID, UserID: &u.ID, Kind: "email", Value: *u.Email} if err := tx.Create(&contact).Error; err != nil { return errors.Wrap(err, "failed to create contact") } if err := tx.Model(¬ification).Association("Contacts").Append(&contact); err != nil { return errors.Wrap(err, "failed to add contact to notification") } } if err := tx.Model(¬ification).Association("Groups").Append(&group); err != nil { return errors.Wrap(err, "failed to add group to notification") } return nil }) if err != nil { return nil, err } return &account, nil }