add tickets api

This commit is contained in:
2026-09-18 16:08:35 +07:00
parent 32191529a5
commit 1be16d706e
11 changed files with 778 additions and 185 deletions
@@ -0,0 +1,88 @@
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
}