Files

106 lines
3.9 KiB
Go

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
}
if err := model.ValidateScreening(p.Screening); err != nil {
return model.StrategyDetail{}, err
}
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)
}