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.ObjRef("account_ref", 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.ObjRef("account_ref", 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.ObjRef("account_ref", 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.ObjRef("account_ref", 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.ObjRef("account_ref", 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.ObjRef("account_ref", accountRef)) return &ledgerv1.UnblockAccountResponse{Account: toProtoAccount(account)}, nil } }