141 lines
4.8 KiB
Go
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
|
|
}
|
|
}
|