Files
sendico/api/gateway/tgsettle/internal/service/treasury/module.go
2026-03-05 14:46:26 +01:00

206 lines
5.3 KiB
Go

package treasury
import (
"context"
"strings"
"time"
"github.com/tech/sendico/gateway/tgsettle/internal/service/treasury/bot"
"github.com/tech/sendico/gateway/tgsettle/internal/service/treasury/ledger"
"github.com/tech/sendico/gateway/tgsettle/storage"
storagemodel "github.com/tech/sendico/gateway/tgsettle/storage/model"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type Module struct {
logger mlogger.Logger
service *Service
router *bot.Router
scheduler *Scheduler
ledger ledger.Client
}
func NewModule(
logger mlogger.Logger,
repo storage.TreasuryRequestsStore,
users storage.TreasuryTelegramUsersStore,
ledgerClient ledger.Client,
cfg Config,
send bot.SendTextFunc,
) (*Module, error) {
if logger != nil {
logger = logger.Named("treasury")
}
if users == nil {
return nil, merrors.InvalidArgument("treasury telegram users store is required", "users")
}
service, err := NewService(
logger,
repo,
ledgerClient,
cfg.ExecutionDelay,
cfg.MaxAmountPerOperation,
cfg.MaxDailyAmount,
)
if err != nil {
return nil, err
}
module := &Module{
logger: logger,
service: service,
ledger: ledgerClient,
}
module.scheduler = NewScheduler(logger, service, NotifyFunc(send), cfg.PollInterval)
module.router = bot.NewRouter(logger, &botServiceAdapter{svc: service}, send, module.scheduler, &botUsersAdapter{store: users})
return module, nil
}
func (m *Module) Enabled() bool {
return m != nil && m.router != nil && m.router.Enabled() && m.scheduler != nil
}
func (m *Module) Start() {
if m == nil || m.scheduler == nil {
return
}
m.scheduler.Start()
}
func (m *Module) Shutdown() {
if m == nil {
return
}
if m.scheduler != nil {
m.scheduler.Shutdown()
}
if m.ledger != nil {
_ = m.ledger.Close()
}
}
func (m *Module) HandleUpdate(ctx context.Context, update *model.TelegramWebhookUpdate) bool {
if m == nil || m.router == nil {
return false
}
return m.router.HandleUpdate(ctx, update)
}
type botServiceAdapter struct {
svc *Service
}
type botUsersAdapter struct {
store storage.TreasuryTelegramUsersStore
}
func (a *botUsersAdapter) ResolveUserBinding(ctx context.Context, telegramUserID string) (*bot.UserBinding, error) {
if a == nil || a.store == nil {
return nil, merrors.Internal("treasury users store unavailable")
}
record, err := a.store.FindByTelegramUserID(ctx, telegramUserID)
if err != nil {
return nil, err
}
if record == nil {
return nil, nil
}
return &bot.UserBinding{
TelegramUserID: strings.TrimSpace(record.TelegramUserID),
LedgerAccountID: strings.TrimSpace(record.LedgerAccountID),
AllowedChatIDs: normalizeChatIDs(record.AllowedChatIDs),
}, nil
}
func (a *botServiceAdapter) ExecutionDelay() (delay time.Duration) {
if a == nil || a.svc == nil {
return 0
}
return a.svc.ExecutionDelay()
}
func (a *botServiceAdapter) MaxPerOperationLimit() string {
if a == nil || a.svc == nil {
return ""
}
return a.svc.MaxPerOperationLimit()
}
func (a *botServiceAdapter) GetActiveRequestForAccount(ctx context.Context, ledgerAccountID string) (*storagemodel.TreasuryRequest, error) {
if a == nil || a.svc == nil {
return nil, merrors.Internal("treasury service unavailable")
}
return a.svc.GetActiveRequestForAccount(ctx, ledgerAccountID)
}
func (a *botServiceAdapter) GetAccountProfile(ctx context.Context, ledgerAccountID string) (*bot.AccountProfile, error) {
if a == nil || a.svc == nil {
return nil, merrors.Internal("treasury service unavailable")
}
profile, err := a.svc.GetAccountProfile(ctx, ledgerAccountID)
if err != nil {
return nil, err
}
if profile == nil {
return nil, nil
}
return &bot.AccountProfile{
AccountID: strings.TrimSpace(profile.AccountID),
AccountCode: strings.TrimSpace(profile.AccountCode),
Currency: strings.TrimSpace(profile.Currency),
}, nil
}
func (a *botServiceAdapter) CreateRequest(ctx context.Context, input bot.CreateRequestInput) (*storagemodel.TreasuryRequest, error) {
if a == nil || a.svc == nil {
return nil, merrors.Internal("treasury service unavailable")
}
return a.svc.CreateRequest(ctx, CreateRequestInput{
OperationType: input.OperationType,
TelegramUserID: input.TelegramUserID,
LedgerAccountID: input.LedgerAccountID,
ChatID: input.ChatID,
Amount: input.Amount,
})
}
func (a *botServiceAdapter) ConfirmRequest(ctx context.Context, requestID string, telegramUserID string) (*storagemodel.TreasuryRequest, error) {
if a == nil || a.svc == nil {
return nil, merrors.Internal("treasury service unavailable")
}
return a.svc.ConfirmRequest(ctx, requestID, telegramUserID)
}
func (a *botServiceAdapter) CancelRequest(ctx context.Context, requestID string, telegramUserID string) (*storagemodel.TreasuryRequest, error) {
if a == nil || a.svc == nil {
return nil, merrors.Internal("treasury service unavailable")
}
return a.svc.CancelRequest(ctx, requestID, telegramUserID)
}
func normalizeChatIDs(values []string) []string {
if len(values) == 0 {
return nil
}
out := make([]string, 0, len(values))
seen := map[string]struct{}{}
for _, next := range values {
next = strings.TrimSpace(next)
if next == "" {
continue
}
if _, ok := seen[next]; ok {
continue
}
seen[next] = struct{}{}
out = append(out, next)
}
if len(out) == 0 {
return nil
}
return out
}