package ticket_service import ( "context" "crypto/rand" "crypto/sha256" "encoding/base64" "errors" "fmt" "net/url" "strings" "evening_detective_server/internal/repos/tickets_repo" ) const maxTicketsPerRequest = 100 var ( ErrInvalidTicketCount = errors.New("количество билетов должно быть от 1 до 100") ErrInvalidBaseURL = errors.New("необходимо настроить APP_BASE_URL — публичный адрес приложения") ) type Ticket struct { ID int URL string } type Service struct { repo *tickets_repo.Repo appBaseURL string } func New(repo *tickets_repo.Repo, appBaseURL string) *Service { return &Service{repo: repo, appBaseURL: appBaseURL} } func (s *Service) Issue(ctx context.Context, gameID, count int) ([]*Ticket, error) { if count < 1 || count > maxTicketsPerRequest { return nil, ErrInvalidTicketCount } base, err := parseBaseURL(s.appBaseURL) if err != nil { return nil, err } hashes := make([][]byte, count) tokens := make([]string, count) for i := range count { token, err := newToken() if err != nil { return nil, err } hash := sha256.Sum256([]byte(token)) tokens[i] = token hashes[i] = hash[:] } ids, err := s.repo.CreateBatch(ctx, gameID, hashes) if err != nil { return nil, err } tickets := make([]*Ticket, count) for i, id := range ids { link := *base link.Path = strings.TrimRight(base.Path, "/") + "/tickets/" + tokens[i] link.RawPath = "" tickets[i] = &Ticket{ID: id, URL: link.String()} } return tickets, nil } func newToken() (string, error) { data := make([]byte, 32) if _, err := rand.Read(data); err != nil { return "", fmt.Errorf("generate ticket token: %w", err) } return base64.RawURLEncoding.EncodeToString(data), nil } func parseBaseURL(value string) (*url.URL, error) { base, err := url.Parse(strings.TrimSpace(value)) if err != nil || base.Host == "" || (base.Scheme != "http" && base.Scheme != "https") || base.User != nil || base.RawQuery != "" || base.Fragment != "" { return nil, ErrInvalidBaseURL } return base, nil }