Files
sendico/api/ledger/internal/service/ledger/account_status.go
2026-02-10 01:55:33 +01:00

141 lines
4.8 KiB
Go

package ledger
import (
"context"
"strings"
"github.com/tech/sendico/ledger/storage"
"github.com/tech/sendico/pkg/api/routers/gsresponse"
"github.com/tech/sendico/pkg/merrors"
pmodel "github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mutil/mzap"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
"go.uber.org/zap"
)
// blockAccountResponder freezes a ledger account, optionally asserting its role first.
func (s *Service) blockAccountResponder(_ context.Context, req *ledgerv1.BlockAccountRequest) gsresponse.Responder[ledgerv1.BlockAccountResponse] {
return func(ctx context.Context) (*ledgerv1.BlockAccountResponse, error) {
if s.storage == nil {
return nil, errStorageNotInitialized
}
if req == nil {
return nil, merrors.InvalidArgument("request is required")
}
if strings.TrimSpace(req.LedgerAccountRef) == "" {
return nil, merrors.InvalidArgument("ledger_account_ref is required")
}
accountRef, err := parseObjectID(req.LedgerAccountRef)
if err != nil {
return nil, err
}
logger := s.logger.With(mzap.AccRef(accountRef))
account, err := s.storage.Accounts().Get(ctx, accountRef)
if err != nil {
if err == storage.ErrAccountNotFound {
return nil, merrors.NoData("account not found")
}
logger.Warn("failed to get account for block", zap.Error(err))
return nil, merrors.Internal("failed to get account")
}
// If organization_ref is provided, validate ownership
if strings.TrimSpace(req.OrganizationRef) != "" {
orgRef, err := parseObjectID(req.OrganizationRef)
if err != nil {
return nil, err
}
if *account.OrganizationRef != orgRef {
return nil, merrors.InvalidArgument("account does not belong to organization")
}
}
// Optional role assertion
if roleModel, err := protoAccountRoleToModel(req.Role); err == nil && roleModel != "" {
if err := validateAccountRole(account, roleModel, "account"); err != nil {
return nil, err
}
}
if account.Status == pmodel.LedgerAccountStatusFrozen {
logger.Debug("account already frozen", mzap.AccRef(accountRef))
return &ledgerv1.BlockAccountResponse{Account: toProtoAccount(account)}, nil
}
if err := s.storage.Accounts().UpdateStatus(ctx, accountRef, pmodel.LedgerAccountStatusFrozen); err != nil {
logger.Warn("failed to freeze account", zap.Error(err))
return nil, merrors.Internal("failed to block account")
}
account.Status = pmodel.LedgerAccountStatusFrozen
logger.Info("account blocked (frozen)", mzap.AccRef(accountRef))
return &ledgerv1.BlockAccountResponse{Account: toProtoAccount(account)}, nil
}
}
// unblockAccountResponder unfreezes a ledger account, optionally asserting its role first.
func (s *Service) unblockAccountResponder(_ context.Context, req *ledgerv1.UnblockAccountRequest) gsresponse.Responder[ledgerv1.UnblockAccountResponse] {
return func(ctx context.Context) (*ledgerv1.UnblockAccountResponse, error) {
if s.storage == nil {
return nil, errStorageNotInitialized
}
if req == nil {
return nil, merrors.InvalidArgument("request is required")
}
if strings.TrimSpace(req.LedgerAccountRef) == "" {
return nil, merrors.InvalidArgument("ledger_account_ref is required")
}
accountRef, err := parseObjectID(req.LedgerAccountRef)
if err != nil {
return nil, err
}
logger := s.logger.With(mzap.AccRef(accountRef))
account, err := s.storage.Accounts().Get(ctx, accountRef)
if err != nil {
if err == storage.ErrAccountNotFound {
return nil, merrors.NoData("account not found")
}
logger.Warn("failed to get account for unblock", zap.Error(err))
return nil, merrors.Internal("failed to get account")
}
// If organization_ref is provided, validate ownership
if strings.TrimSpace(req.OrganizationRef) != "" {
orgRef, err := parseObjectID(req.OrganizationRef)
if err != nil {
return nil, err
}
if *account.OrganizationRef != orgRef {
return nil, merrors.InvalidArgument("account does not belong to organization")
}
}
// Optional role assertion
if roleModel, err := protoAccountRoleToModel(req.Role); err == nil && roleModel != "" {
if err := validateAccountRole(account, roleModel, "account"); err != nil {
return nil, err
}
}
if account.Status == pmodel.LedgerAccountStatusActive {
logger.Debug("account already active", mzap.AccRef(accountRef))
return &ledgerv1.UnblockAccountResponse{Account: toProtoAccount(account)}, nil
}
if err := s.storage.Accounts().UpdateStatus(ctx, accountRef, pmodel.LedgerAccountStatusActive); err != nil {
logger.Warn("failed to activate account", zap.Error(err))
return nil, merrors.Internal("failed to unblock account")
}
account.Status = pmodel.LedgerAccountStatusActive
logger.Info("account unblocked (active)", mzap.AccRef(accountRef))
return &ledgerv1.UnblockAccountResponse{Account: toProtoAccount(account)}, nil
}
}