96 lines
2.6 KiB
Go
96 lines
2.6 KiB
Go
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)
|
|
}
|