tenancy

package
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Index

Constants

View Source
const (
	OrgRoleOwner  = "owner"
	OrgRoleAdmin  = "admin"
	OrgRoleMember = "member"

	TeamRoleOwner  = "owner"
	TeamRoleAdmin  = "admin"
	TeamRoleMember = "member"
)
View Source
const (
	InviteStatusPending  = "pending"
	InviteStatusAccepted = "accepted"
	InviteStatusRevoked  = "revoked"
	InviteStatusExpired  = "expired"
)

Variables

This section is empty.

Functions

func AssertActorCanChangeRoles

func AssertActorCanChangeRoles(actorRole string) error

AssertActorCanChangeRoles requires the actor to be the org owner.

func AssertSoleOwnerSafe

func AssertSoleOwnerSafe(actorUserID, targetUserID, targetRole, newRole string, ownerCount int) error

AssertSoleOwnerSafe blocks demoting the last owner.

func AssertSoleTeamOwnerSafe

func AssertSoleTeamOwnerSafe(targetRole, newRole string, ownerCount int) error

AssertSoleTeamOwnerSafe blocks demoting the last team owner.

func CanAccessTeam

func CanAccessTeam(ctx context.Context, teams TeamRepository, orgs OrganizationRepository, teamID, userID string) (bool, error)

CanAccessTeam reports whether the user may view the team (org admin or team member).

func CanChangeTeamRoles

func CanChangeTeamRoles(ctx context.Context, teams TeamRepository, orgs OrganizationRepository, teamID, userID string) (bool, error)

CanChangeTeamRoles reports whether the user may change team member roles (org admin or team owner — not team admin).

func CanManageTeam

func CanManageTeam(ctx context.Context, teams TeamRepository, orgs OrganizationRepository, teamID, userID string) (bool, error)

CanManageTeam reports whether the user may add/remove team members (org admin, or team owner/admin).

func HashInviteToken

func HashInviteToken(raw string) string

HashInviteToken returns the hex-encoded SHA-256 of the raw token.

func IsOrgAdmin

func IsOrgAdmin(ctx context.Context, repo OrganizationRepository, userID, orgID string) (bool, error)

IsOrgAdmin reports whether the user has admin privileges in the org.

func IsOrgAdminRole

func IsOrgAdminRole(role string) bool

IsOrgAdminRole reports whether the role grants admin privileges.

func IsOrgOwner

func IsOrgOwner(ctx context.Context, repo OrganizationRepository, userID, orgID string) (bool, error)

IsOrgOwner reports whether the user is the org owner.

func IsTeamManagerRole

func IsTeamManagerRole(role string) bool

IsTeamManagerRole reports whether the team role may manage team members.

func NewInviteToken

func NewInviteToken() (raw, hash string, err error)

NewInviteToken generates an opaque invite token and its hash.

func NormalizeEmail

func NormalizeEmail(email string) string

NormalizeEmail trims and lowercases an email address.

func NormalizeEnvironmentSlug

func NormalizeEnvironmentSlug(slug string) string

NormalizeEnvironmentSlug lowercases and trims a slug.

func RemoveTeamMember

func RemoveTeamMember(ctx context.Context, teams TeamRepository, teamID, targetUserID string) error

RemoveTeamMember removes a user from the team. Cannot remove the sole team owner.

func RevokeInvite

func RevokeInvite(ctx context.Context, invites InviteRepository, orgID, inviteID string) error

RevokeInvite marks a pending invite revoked.

func ShouldDemoteActorOnOwnerTransfer

func ShouldDemoteActorOnOwnerTransfer(actorUserID, targetUserID, newRole string) bool

ShouldDemoteActorOnOwnerTransfer is true when promoting someone else to owner.

func ValidateEnvironmentSlug

func ValidateEnvironmentSlug(slug string) error

ValidateEnvironmentSlug ensures slug is lowercase alnum with -/_ and length ≤ 64.

func ValidateOrgRole

func ValidateOrgRole(role string) error

ValidateOrgRole checks role is owner/admin/member.

func ValidateTeamRole

func ValidateTeamRole(role string) error

ValidateTeamRole checks role is owner/admin/member.

Types

type AcceptInviteInput

type AcceptInviteInput struct {
	Name         string
	PasswordHash string // bcrypt hash for new users; unused when user exists
}

AcceptInviteInput is the accept payload for new users.

type Environment

type Environment struct {
	ID             string    `json:"id"`
	OrganizationID string    `json:"organization_id"`
	ProjectID      string    `json:"project_id"`
	Name           string    `json:"name"`
	Slug           string    `json:"slug"`
	CreatedAt      time.Time `json:"created_at"`
}

Environment is a named deployment stage under a project (dev/stage/prod/…).

func CreateEnvironment

func CreateEnvironment(ctx context.Context, repo EnvironmentRepository, projects ProjectRepository, id, orgID, projectID, name, slug string) (*Environment, error)

CreateEnvironment validates and persists an environment under a project.

func NewEnvironment

func NewEnvironment(id, orgID, projectID, name, slug string, now time.Time) (*Environment, error)

NewEnvironment builds a validated environment entity.

type EnvironmentRepository

type EnvironmentRepository interface {
	ListByProject(ctx context.Context, projectID string) ([]Environment, error)
	Get(ctx context.Context, environmentID string) (*Environment, error)
	Insert(ctx context.Context, e Environment) error
	Delete(ctx context.Context, environmentID string) error
	OrgID(ctx context.Context, environmentID string) (string, error)
}

EnvironmentRepository persists project environments.

type InviteOutcome

type InviteOutcome struct {
	Status string     `json:"status"` // "added" | "invited"
	Member *OrgMember `json:"member,omitempty"`
	Invite *OrgInvite `json:"invite,omitempty"`
}

InviteOutcome is returned by InviteOrgMember.

func InviteOrgMember

func InviteOrgMember(
	ctx context.Context,
	orgs OrganizationRepository,
	invites InviteRepository,
	users UserLookup,
	inviteID, orgID, email, invitedByUserID string,
) (outcome *InviteOutcome, rawToken string, err error)

InviteOrgMember adds an existing user or creates/resends a pending invite. When Status is "invited", rawToken is the plaintext accept token (shown once).

type InvitePreview

type InvitePreview struct {
	Email            string    `json:"email"`
	OrganizationID   string    `json:"organization_id"`
	OrganizationName string    `json:"organization_name"`
	ExpiresAt        time.Time `json:"expires_at"`
	UserExists       bool      `json:"user_exists"`
}

InvitePreview is the public view of an invite token.

func PreviewInvite

func PreviewInvite(ctx context.Context, invites InviteRepository, users UserLookup, rawToken string) (*InvitePreview, error)

PreviewInvite resolves a raw token for the accept UI.

type InviteRepository

type InviteRepository interface {
	Get(ctx context.Context, inviteID string) (*OrgInvite, error)
	GetPendingByOrgEmail(ctx context.Context, orgID, email string) (*OrgInvite, error)
	GetByTokenHash(ctx context.Context, tokenHash string) (*OrgInvite, string, error) // invite, raw org name
	ListByOrg(ctx context.Context, orgID string) ([]OrgInvite, error)
	Insert(ctx context.Context, inv OrgInvite, tokenHash string) error
	UpdateToken(ctx context.Context, inviteID, tokenHash string, expiresAt time.Time) error
	MarkAccepted(ctx context.Context, inviteID string, at time.Time) error
	MarkRevoked(ctx context.Context, inviteID string) error
}

InviteRepository persists organization invites.

type OrgInvite

type OrgInvite struct {
	ID              string     `json:"id"`
	OrganizationID  string     `json:"organization_id"`
	Email           string     `json:"email"`
	Role            string     `json:"role"`
	InvitedByUserID string     `json:"invited_by_user_id"`
	Status          string     `json:"status"`
	ExpiresAt       time.Time  `json:"expires_at"`
	CreatedAt       time.Time  `json:"created_at"`
	AcceptedAt      *time.Time `json:"accepted_at,omitempty"`
}

OrgInvite is a pending (or historical) organization membership invite.

func ResendInvite

func ResendInvite(ctx context.Context, invites InviteRepository, orgID, inviteID string) (inv *OrgInvite, rawToken string, err error)

ResendInvite rotates the token for a pending invite.

type OrgMember

type OrgMember struct {
	UserID string `json:"user_id"`
	Email  string `json:"email"`
	Name   string `json:"name"`
	Role   string `json:"role"`
}

OrgMember is a user membership within an organization.

func AcceptInvite

func AcceptInvite(
	ctx context.Context,
	orgs OrganizationRepository,
	invites InviteRepository,
	users UserLookup,
	creator UserCreator,
	rawToken string,
	in AcceptInviteInput,
	newUserID string,
) (*OrgMember, *identity.User, error)

AcceptInvite consumes a pending invite and ensures org membership.

func AddOrgMemberByEmail

func AddOrgMemberByEmail(ctx context.Context, orgs OrganizationRepository, users UserLookup, orgID, email string) (*OrgMember, error)

AddOrgMemberByEmail invites an existing user as a member.

func UpdateOrgMemberRole

func UpdateOrgMemberRole(ctx context.Context, repo OrganizationRepository, orgID, actorUserID, targetUserID, role string) (*OrgMember, error)

UpdateOrgMemberRole applies ownership/admin/member transitions with sole-owner guards.

type OrgRole

type OrgRole string

OrgRole is a typed organization membership role.

func ParseOrgRole

func ParseOrgRole(role string) (OrgRole, error)

ParseOrgRole validates an organization role string.

func (OrgRole) CanChangeRoles

func (r OrgRole) CanChangeRoles() bool

CanChangeRoles reports whether the role may change other members' roles.

func (OrgRole) IsAdmin

func (r OrgRole) IsAdmin() bool

IsAdmin reports whether the role grants admin privileges.

func (OrgRole) String

func (r OrgRole) String() string

type Organization

type Organization struct {
	ID                 string    `json:"id"`
	Name               string    `json:"name"`
	MailProvider       string    `json:"mail_provider,omitempty"`
	BYOKSelectorHeader string    `json:"byok_selector_header,omitempty"`
	CreatedAt          time.Time `json:"created_at"`
}

Organization is a tenant root.

func CreateOrganization

func CreateOrganization(ctx context.Context, repo OrganizationRepository, id, name, creatorUserID string) (*Organization, error)

CreateOrganization creates an org and assigns the creator as owner.

func NewOrganization

func NewOrganization(id, name string, now time.Time) (*Organization, error)

NewOrganization builds a validated org entity.

type OrganizationRepository

type OrganizationRepository interface {
	Count(ctx context.Context) (int64, error)
	Get(ctx context.Context, orgID string) (*Organization, error)
	ListForUser(ctx context.Context, userID string) ([]Organization, error)
	CreateWithOwner(ctx context.Context, org Organization, ownerUserID string) error
	ListMembers(ctx context.Context, orgID string) ([]OrgMember, error)
	AddMember(ctx context.Context, orgID, userID, role string) error
	GetMemberRole(ctx context.Context, userID, orgID string) (string, error)
	CountOwners(ctx context.Context, orgID string) (int, error)
	ApplyRoleChange(ctx context.Context, orgID, actorUserID, targetUserID, newRole string, demoteActor bool) error
	GetMember(ctx context.Context, orgID, userID string) (*OrgMember, error)
	SetMailProvider(ctx context.Context, orgID, provider string) error
}

OrganizationRepository persists organizations and membership.

type Project

type Project struct {
	ID             string    `json:"id"`
	OrganizationID string    `json:"organization_id"`
	TeamID         string    `json:"team_id,omitempty"`
	Name           string    `json:"name"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
}

Project belongs to an organization (optionally a team).

func CreateProject

func CreateProject(ctx context.Context, repo ProjectRepository, id, orgID, teamID, name string) (*Project, error)

CreateProject validates and persists a project.

func ListVisibleProjects

func ListVisibleProjects(ctx context.Context, projects ProjectRepository, orgs OrganizationRepository, orgID, userID string) ([]Project, error)

ListVisibleProjects returns all org projects for admins, otherwise projects on the user's teams.

func NewProject

func NewProject(id, orgID, teamID, name string, now time.Time) (*Project, error)

NewProject builds a validated project entity.

type ProjectRepository

type ProjectRepository interface {
	ListByOrg(ctx context.Context, orgID string) ([]Project, error)
	ListByOrgForUser(ctx context.Context, orgID, userID string) ([]Project, error)
	Insert(ctx context.Context, p Project) error
	OrgID(ctx context.Context, projectID string) (string, error)
}

ProjectRepository persists projects.

type Team

type Team struct {
	ID             string    `json:"id"`
	TeamID         string    `json:"team_id"`
	OrganizationID string    `json:"organization_id"`
	Name           string    `json:"name"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
}

Team belongs to an organization.

func CreateTeam

func CreateTeam(ctx context.Context, repo TeamRepository, id, orgID, name, creatorUserID string) (*Team, error)

CreateTeam creates a team and assigns the creator as team owner.

func ListVisibleTeams

func ListVisibleTeams(ctx context.Context, teams TeamRepository, orgs OrganizationRepository, orgID, userID string) ([]Team, error)

ListVisibleTeams returns all org teams for admins, otherwise only teams the user belongs to.

func NewTeam

func NewTeam(id, orgID, name string, now time.Time) (*Team, error)

NewTeam builds a validated team entity.

type TeamMember

type TeamMember struct {
	UserID string `json:"user_id"`
	Name   string `json:"name"`
	Email  string `json:"email"`
	Role   string `json:"role"`
}

TeamMember is a user membership within a team.

func AddTeamMember

func AddTeamMember(ctx context.Context, teams TeamRepository, orgs OrganizationRepository, teamID, userID string) (*TeamMember, error)

AddTeamMember adds an existing org member to the team as a member.

func UpdateTeamMemberRole

func UpdateTeamMemberRole(ctx context.Context, teams TeamRepository, orgs OrganizationRepository, teamID, actorUserID, targetUserID, role string) (*TeamMember, error)

UpdateTeamMemberRole sets a team member's role. Only org admins and team owners may change roles. Cannot demote the sole team owner.

type TeamRepository

type TeamRepository interface {
	ListByOrg(ctx context.Context, orgID string) ([]Team, error)
	ListByOrgForUser(ctx context.Context, orgID, userID string) ([]Team, error)
	Get(ctx context.Context, teamID string) (*Team, error)
	OrgID(ctx context.Context, teamID string) (string, error)
	ListMembers(ctx context.Context, teamID string) ([]TeamMember, error)
	GetMember(ctx context.Context, teamID, userID string) (*TeamMember, error)
	GetMemberRole(ctx context.Context, teamID, userID string) (string, error)
	AddMember(ctx context.Context, teamID, userID, role string) error
	RemoveMember(ctx context.Context, teamID, userID string) error
	SetMemberRole(ctx context.Context, teamID, userID, role string) error
	CountOwners(ctx context.Context, teamID string) (int, error)
	CreateWithOwner(ctx context.Context, team Team, ownerUserID string) error
}

TeamRepository persists teams.

type TeamRole

type TeamRole string

TeamRole is a typed team membership role.

func ParseTeamRole

func ParseTeamRole(role string) (TeamRole, error)

ParseTeamRole validates a team role string.

func (TeamRole) CanChangeRoles

func (r TeamRole) CanChangeRoles() bool

CanChangeRoles reports whether the team role may change other members' roles. Team admins manage membership but cannot promote/demote roles.

func (TeamRole) IsManager

func (r TeamRole) IsManager() bool

IsManager reports whether the role may manage team members (add/remove).

func (TeamRole) String

func (r TeamRole) String() string

type UserCreator

type UserCreator interface {
	CreateUser(ctx context.Context, id, email, name, passwordHash string) (*identity.User, error)
}

UserCreator creates platform users during invite accept.

type UserLookup

type UserLookup interface {
	FindByEmail(ctx context.Context, email string) (userID, name, userEmail string, err error)
}

UserLookup resolves users by email for invites.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL