package model import ( "time" "git.sendico.io/tech/sxchnge/bff/pkg/mservice" "go.mongodb.org/mongo-driver/v2/bson" ) type OrganizationSignupState string const ( OrganizationSignupStatePending OrganizationSignupState = "pending_signup_provisioning" OrganizationSignupStateBlocked OrganizationSignupState = "blocked_signup_provisioning" OrganizationSignupStateActive OrganizationSignupState = "active" ) type OrganizationBase struct { PermissionBound `bson:",inline" json:",inline"` Describable `bson:",inline" json:",inline"` TenantRef bson.ObjectID `bson:"tenantRef" json:"tenantRef"` TimeZone string `bson:"timeZone" json:"timeZone"` LogoURL *string `bson:"logoUrl,omitempty" json:"logoUrl,omitempty"` SignupState OrganizationSignupState `bson:"signupState,omitempty" json:"signupState,omitempty"` } func (*OrganizationBase) Collection() string { return mservice.Organizations } type Organization struct { OrganizationBase `bson:",inline" json:",inline"` Members []bson.ObjectID `bson:"members" json:"members"` Provisioning *OrganizationProvisioning `bson:"provisioning,omitempty" json:"-"` } // OrganizationProvisioning tracks an organization that is being provisioned and // has no members or roles yet. It is the only link between such an organization // and its future owner, and it also guards concurrent provisioning attempts. // Activation clears only the lease fields: the provenance stays so a retried // create can recognize the organization it already activated. // // It lives on Organization rather than OrganizationBase on purpose: update // paths replace OrganizationBase wholesale from client payloads, which would // otherwise wipe it. It is never exposed over the API. type OrganizationProvisioning struct { OwnerRef bson.ObjectID `bson:"ownerRef" json:"-"` // Name snapshots the organization name of the first attempt. It is the // immutable half of the dedup key: renaming the organization must not // detach it from retries of the create that produced it. Name string `bson:"name,omitempty" json:"-"` Origin string `bson:"origin" json:"-"` ActorRef string `bson:"actorRef,omitempty" json:"-"` StartedAt time.Time `bson:"startedAt" json:"-"` LeaseOwner string `bson:"leaseOwner,omitempty" json:"-"` LeaseExpiresAt time.Time `bson:"leaseExpiresAt,omitempty" json:"-"` } const ( OrganizationSignupStateField = "signupState" OrganizationProvisioningField = "provisioning" OrganizationProvisioningOwnerRefField = "provisioning.ownerRef" OrganizationProvisioningNameField = "provisioning.name" OrganizationProvisioningLeaseOwnerField = "provisioning.leaseOwner" OrganizationProvisioningLeaseField = "provisioning.leaseExpiresAt" ) // LeaseHeld reports whether another attempt still owns the provisioning lease. func (p *OrganizationProvisioning) LeaseHeld(now time.Time) bool { return p != nil && p.LeaseExpiresAt.After(now) } // CanFinishProvisioning reports whether an operator can drive this organization // to active through the backoffice create operation: only pending organizations // stamped by operator provisioning are resumable. Signup-era organizations // carry no provisioning stamp. func (o *Organization) CanFinishProvisioning() bool { return o != nil && o.Provisioning != nil && o.SignupState == OrganizationSignupStatePending } func (o *Organization) NormalizeSignupState() { if o == nil { return } switch o.SignupState { case OrganizationSignupStatePending, OrganizationSignupStateBlocked, OrganizationSignupStateActive: default: o.SignupState = OrganizationSignupStateActive } } type OrganizationBound interface { GetOrganizationRef() bson.ObjectID SetOrganizationRef(organizationRef bson.ObjectID) } const ( OrganizationRefField = "organizationRef" ) type OrganizationBoundBase struct { OrganizationRef bson.ObjectID `bson:"organizationRef" json:"organizationRef"` } func (a *OrganizationBoundBase) GetOrganizationRef() bson.ObjectID { return a.OrganizationRef } func (a *OrganizationBoundBase) SetOrganizationRef(organizationRef bson.ObjectID) { a.OrganizationRef = organizationRef }