package service import ( "context" "git.sundynix.cn/Blizzard/Veyra/internal/apperror" "git.sundynix.cn/Blizzard/Veyra/internal/model" "github.com/google/uuid" "regexp" "strings" "unicode/utf8" ) type StrategyRepository interface { List(context.Context) ([]model.Strategy, error) Detail(context.Context, string) (model.StrategyDetail, error) Save(context.Context, string, StrategyInput, string, string) (model.StrategyDetail, error) Deploy(context.Context, string, string, string, int64, string, string, string) (model.StrategyDetail, error) } type StrategyService struct { repo StrategyRepository development bool } func NewStrategyService(repo StrategyRepository, development bool) *StrategyService { return &StrategyService{repo, development} } func (s *StrategyService) authorize(actor model.User) error { if actor.Role != "ADMIN" || actor.Status != "ACTIVE" { return apperror.ErrForbidden } if s == nil || s.repo == nil || !s.development { return apperror.ErrUnavailable } return nil } type StrategyInput struct { Code string `json:"code"` Name string `json:"name"` Description string `json:"description"` Parameters model.StrategyParameters `json:"parameters"` ExpectedVersion int64 `json:"expected_version"` } var strategyCodePattern = regexp.MustCompile(`^[a-z][a-z0-9_]{2,63}$`) func (s *StrategyService) List(ctx context.Context, actor model.User) ([]model.Strategy, error) { if err := s.authorize(actor); err != nil { return nil, err } return s.repo.List(ctx) } func (s *StrategyService) Detail(ctx context.Context, actor model.User, id string) (model.StrategyDetail, error) { if err := s.authorize(actor); err != nil { return model.StrategyDetail{}, err } if _, err := uuid.Parse(id); err != nil { return model.StrategyDetail{}, apperror.ErrInvalidInput } return s.repo.Detail(ctx, id) } func (s *StrategyService) Save(ctx context.Context, actor model.User, id string, input StrategyInput, requestID string) (model.StrategyDetail, error) { if err := s.authorize(actor); err != nil { return model.StrategyDetail{}, err } if id != "" { if _, err := uuid.Parse(id); err != nil { return model.StrategyDetail{}, apperror.ErrInvalidInput } if input.ExpectedVersion < 1 { return model.StrategyDetail{}, apperror.ErrInvalidInput } } p := input.Parameters input.Code = strings.TrimSpace(input.Code) input.Name = strings.TrimSpace(input.Name) input.Description = strings.TrimSpace(input.Description) if !strategyCodePattern.MatchString(input.Code) || input.Name == "" || utf8.RuneCountInString(input.Name) > 80 || input.Description == "" || utf8.RuneCountInString(input.Description) > 2000 || p.ShortWindow < 2 || p.LongWindow <= p.ShortWindow || p.LongWindow > 200 || p.FeeBPS < 0 || p.FeeBPS > 1000 || p.SlippageBPS < 0 || p.SlippageBPS > 1000 || p.LotSize < 1 || p.LotSize > 10000 { return model.StrategyDetail{}, apperror.ErrInvalidInput } return s.repo.Save(ctx, id, input, actor.ID, requestID) } func (s *StrategyService) Deploy(ctx context.Context, actor model.User, id, versionID, status string, expected int64, reason, requestID string) (model.StrategyDetail, error) { if err := s.authorize(actor); err != nil { return model.StrategyDetail{}, err } if _, err := uuid.Parse(id); err != nil || expected < 1 { return model.StrategyDetail{}, apperror.ErrInvalidInput } if status != "ACTIVE" && status != "PAUSED" { return model.StrategyDetail{}, apperror.ErrInvalidInput } if status == "ACTIVE" { if _, err := uuid.Parse(versionID); err != nil { return model.StrategyDetail{}, apperror.ErrInvalidInput } } if err := model.ValidateReason(reason); err != nil { return model.StrategyDetail{}, err } return s.repo.Deploy(ctx, id, versionID, status, expected, reason, actor.ID, requestID) }