package auth import ( "context" "errors" "github.com/google/uuid" "github.com/jackc/pgx/v5" ) var ( ErrNotCompanyOwner = errors.New("company owner required") ErrCannotRemoveOwner = errors.New("transfer ownership before removing the company owner") ErrTransferSelf = errors.New("user is already the company owner") ErrOwnerRequired = errors.New("new owner must be an active company member") ) // CompanyOwnerID returns the billing representative for the company, if set. func (s *Service) CompanyOwnerID(ctx context.Context, companyID uuid.UUID) (uuid.UUID, bool, error) { var owner *uuid.UUID err := s.Pool.QueryRow(ctx, `SELECT owner_user_id FROM companies WHERE id = $1`, companyID).Scan(&owner) if errors.Is(err, pgx.ErrNoRows) { return uuid.Nil, false, ErrCompanyNotFound } if err != nil { return uuid.Nil, false, err } if owner == nil || *owner == uuid.Nil { return uuid.Nil, false, nil } return *owner, true, nil } // IsCompanyOwner reports whether userID is the company's owner_user_id. func (s *Service) IsCompanyOwner(ctx context.Context, companyID, userID uuid.UUID) (bool, error) { ownerID, ok, err := s.CompanyOwnerID(ctx, companyID) if err != nil { return false, err } return ok && ownerID == userID, nil } // TransferOwnership sets a new company owner. The target must be an active member. // The new owner is promoted to membership admin so they retain team powers. func (s *Service) TransferOwnership(ctx context.Context, companyID, newOwnerID uuid.UUID) error { tx, err := s.Pool.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) var current *uuid.UUID err = tx.QueryRow(ctx, `SELECT owner_user_id FROM companies WHERE id = $1 FOR UPDATE`, companyID).Scan(¤t) if errors.Is(err, pgx.ErrNoRows) { return ErrCompanyNotFound } if err != nil { return err } if current != nil && *current == newOwnerID { return ErrTransferSelf } var status string err = tx.QueryRow(ctx, ` SELECT status FROM memberships WHERE company_id = $1 AND user_id = $2`, companyID, newOwnerID).Scan(&status) if errors.Is(err, pgx.ErrNoRows) { return ErrOwnerRequired } if err != nil { return err } if status != "active" { return ErrOwnerRequired } _, err = tx.Exec(ctx, ` UPDATE memberships SET role = 'admin', status = 'active', updated_at = now() WHERE company_id = $1 AND user_id = $2`, companyID, newOwnerID) if err != nil { return err } tag, err := tx.Exec(ctx, ` UPDATE companies SET owner_user_id = $2, updated_at = now() WHERE id = $1`, companyID, newOwnerID) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrCompanyNotFound } return tx.Commit(ctx) }