package service import ( "context" "git.sundynix.cn/Blizzard/Veyra/internal/apperror" "git.sundynix.cn/Blizzard/Veyra/internal/model" "github.com/google/uuid" "regexp" "strconv" ) type TradePlanRepository interface { Preview(context.Context, string, string, string, int64, int64, int64) (model.PlanPreview, error) Confirm(context.Context, string, string, string) (model.TradePlan, error) List(context.Context, string) ([]model.TradePlan, error) Cancel(context.Context, string, string, int64, string) (model.TradePlan, error) Budget(context.Context, string, string) (model.PlanBudget, error) } type TradePlanService struct { repo TradePlanRepository development bool } func NewTradePlanService(repo TradePlanRepository, development bool) *TradePlanService { return &TradePlanService{repo, development} } func (s *TradePlanService) authorize(actor model.User) error { if actor.Role != "USER" || actor.Status != "ACTIVE" { return apperror.ErrForbidden } if s == nil || s.repo == nil || !s.development { return apperror.ErrUnavailable } return nil } var quantityPattern = regexp.MustCompile(`^[1-9][0-9]{0,6}$`) func ParseQuantity(value string) (int64, error) { if !quantityPattern.MatchString(value) { return 0, apperror.ErrInvalidInput } qty, err := strconv.ParseInt(value, 10, 64) if err != nil || qty > 1000000 { return 0, apperror.ErrInvalidInput } return qty, nil } type PlanPreviewInput struct { AccountID string `json:"account_id"` CandidateRevisionID string `json:"candidate_revision_id"` ExpectedCandidateVersion int64 `json:"expected_candidate_version"` Quantity string `json:"quantity"` FeeBudget string `json:"fee_budget"` } func (s *TradePlanService) Preview(ctx context.Context, actor model.User, input PlanPreviewInput) (model.PlanPreview, error) { if err := s.authorize(actor); err != nil { return model.PlanPreview{}, err } if _, err := uuid.Parse(input.AccountID); err != nil { return model.PlanPreview{}, apperror.ErrInvalidInput } if _, err := uuid.Parse(input.CandidateRevisionID); err != nil { return model.PlanPreview{}, apperror.ErrInvalidInput } qty, err := ParseQuantity(input.Quantity) if err != nil || qty%100 != 0 || input.ExpectedCandidateVersion < 1 { return model.PlanPreview{}, apperror.ErrInvalidInput } fee, err := model.ParseMoney(input.FeeBudget) if err != nil { return model.PlanPreview{}, err } return s.repo.Preview(ctx, actor.ID, input.AccountID, input.CandidateRevisionID, input.ExpectedCandidateVersion, qty, fee) } func (s *TradePlanService) Confirm(ctx context.Context, actor model.User, id, requestID string) (model.TradePlan, error) { if err := s.authorize(actor); err != nil { return model.TradePlan{}, err } if _, err := uuid.Parse(id); err != nil { return model.TradePlan{}, apperror.ErrInvalidInput } return s.repo.Confirm(ctx, actor.ID, id, requestID) } func (s *TradePlanService) List(ctx context.Context, actor model.User) ([]model.TradePlan, error) { if err := s.authorize(actor); err != nil { return nil, err } return s.repo.List(ctx, actor.ID) } func (s *TradePlanService) Cancel(ctx context.Context, actor model.User, id string, expected int64, requestID string) (model.TradePlan, error) { if err := s.authorize(actor); err != nil { return model.TradePlan{}, err } if _, err := uuid.Parse(id); err != nil || expected < 1 { return model.TradePlan{}, apperror.ErrInvalidInput } return s.repo.Cancel(ctx, actor.ID, id, expected, requestID) } func (s *TradePlanService) Budget(ctx context.Context, actor model.User, id string) (model.PlanBudget, error) { if err := s.authorize(actor); err != nil { return model.PlanBudget{}, err } if _, err := uuid.Parse(id); err != nil { return model.PlanBudget{}, apperror.ErrInvalidInput } return s.repo.Budget(ctx, actor.ID, id) }