Files

96 lines
2.6 KiB
Go
Raw Permalink Normal View History

2026-08-17 00:39:25 +02:00
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(&current)
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)
}