This commit is contained in:
2026-09-09 12:30:40 +07:00
parent 94fc85f074
commit 4feece62c8
21 changed files with 854 additions and 85 deletions
@@ -6,6 +6,7 @@ import (
"evening_detective_server/internal/modules/cleaner"
"evening_detective_server/internal/modules/password_generator"
"evening_detective_server/internal/modules/storytelling"
"evening_detective_server/internal/modules/team_qr_pdf"
"evening_detective_server/internal/repos"
"evening_detective_server/internal/repos/actions_repo"
"evening_detective_server/internal/repos/applications_repo"
@@ -29,6 +30,8 @@ type GameService struct {
applicationsRepo *applications_repo.ApplicationsRepo
answersRepo *team_answers_repo.TeamAnswersRepo
cleaner cleaner.ICleaner
teamQRGenerator team_qr_pdf.IGenerator
appBaseURL string
}
func NewGameService(
@@ -43,6 +46,8 @@ func NewGameService(
applicationsRepo *applications_repo.ApplicationsRepo,
answersRepo *team_answers_repo.TeamAnswersRepo,
cleaner cleaner.ICleaner,
teamQRGenerator team_qr_pdf.IGenerator,
appBaseURL string,
) *GameService {
return &GameService{
gamesRepo: gamesRepo,
@@ -56,6 +61,8 @@ func NewGameService(
applicationsRepo: applicationsRepo,
answersRepo: answersRepo,
cleaner: cleaner,
teamQRGenerator: teamQRGenerator,
appBaseURL: appBaseURL,
}
}
+58
View File
@@ -0,0 +1,58 @@
package game_service
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"unicode"
"evening_detective_server/internal/modules/team_qr_pdf"
"evening_detective_server/internal/repos"
)
func (s *GameService) DownloadTeamQR(ctx context.Context, id int) ([]byte, string, error) {
game, err := s.gamesRepo.GetGameByID(ctx, id)
if err != nil {
return nil, "", err
}
teams, err := s.teamsRepo.GetTeamsByGameID(ctx, id)
if err != nil {
return nil, "", err
}
if len(teams) == 0 {
return nil, "", team_qr_pdf.ErrNoTeams
}
cards, filename, err := teamQRData(s.appBaseURL, game, teams)
if err != nil {
return nil, "", err
}
data, err := s.teamQRGenerator.Generate(cards)
return data, filename, err
}
func teamQRData(baseURL string, game *repos.Game, teams []*repos.Team) ([]team_qr_pdf.Card, string, error) {
base, err := url.Parse(strings.TrimSpace(baseURL))
if err != nil || (base.Scheme != "http" && base.Scheme != "https") || base.Host == "" || base.User != nil || base.RawQuery != "" || base.Fragment != "" {
return nil, "", errors.New("для QR-кодов необходимо настроить APP_BASE_URL — публичный адрес приложения")
}
cards := make([]team_qr_pdf.Card, 0, len(teams))
for _, team := range teams {
if team.ID <= 0 || team.Password == "" {
return nil, "", errors.New("у команды отсутствуют данные для входа")
}
link := *base
link.Path = strings.TrimRight(base.Path, "/") + fmt.Sprintf("/team-story/%d", team.ID)
link.RawPath = ""
link.RawQuery = url.Values{"password": {team.Password}}.Encode()
cards = append(cards, team_qr_pdf.Card{Name: team.Name, URL: link.String()})
}
name := strings.Map(func(r rune) rune {
if unicode.IsControl(r) || strings.ContainsRune(`\/:*?"<>|`, r) {
return '_'
}
return r
}, game.Name)
return cards, game.StartAt.Format("2006-01-02") + "_" + strings.TrimSpace(name) + ".pdf", nil
}
@@ -0,0 +1,37 @@
package game_service
import (
"net/url"
"testing"
"time"
"evening_detective_server/internal/repos"
)
func TestTeamQRData(t *testing.T) {
game := &repos.Game{Name: "Дело / №1\r\n", StartAt: time.Date(2026, 9, 8, 14, 0, 0, 0, time.UTC)}
teams := []*repos.Team{{ID: 7, Name: "Знатоки", Password: "a+b&c?"}}
cards, filename, err := teamQRData("https://example.org/app/", game, teams)
if err != nil {
t.Fatal(err)
}
if len(cards) != 1 || cards[0].Name != "Знатоки" {
t.Fatalf("cards: %+v", cards)
}
u, err := url.Parse(cards[0].URL)
if err != nil || u.Path != "/app/team-story/7" || u.Query().Get("password") != teams[0].Password {
t.Fatalf("wrong team link: %s", cards[0].URL)
}
if filename != "2026-09-08_Дело _ №1__.pdf" {
t.Fatalf("filename: %s", filename)
}
for _, base := range []string{"", "ftp://example.org", "https://user:pass@example.org", "https://example.org?q=1", "https://example.org/#hash"} {
if _, _, err := teamQRData(base, game, teams); err == nil {
t.Errorf("accepted invalid base URL %q", base)
}
}
teams[0].Password = ""
if _, _, err := teamQRData("https://example.org", game, teams); err == nil {
t.Fatal("accepted missing password")
}
}