generated from VLADIMIR/template
up
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
package screen_plan_service
|
||||
|
||||
import "evening_detective_server/internal/repos/game_screen_plans_repo"
|
||||
|
||||
func mapPlan(plan *game_screen_plans_repo.Plan) *Plan {
|
||||
steps := make([]Step, 0, len(plan.Steps))
|
||||
for _, step := range plan.Steps {
|
||||
steps = append(steps, Step{
|
||||
Mode: step.Mode,
|
||||
Text: step.Text,
|
||||
TimerLabel: step.TimerLabel,
|
||||
TimerDurationSeconds: step.TimerDurationSeconds,
|
||||
})
|
||||
}
|
||||
return &Plan{GameID: plan.GameID, Steps: steps, CurrentStep: plan.CurrentStep}
|
||||
}
|
||||
|
||||
func mapSteps(steps []Step) []game_screen_plans_repo.Step {
|
||||
mapped := make([]game_screen_plans_repo.Step, 0, len(steps))
|
||||
for _, step := range steps {
|
||||
mapped = append(mapped, game_screen_plans_repo.Step{
|
||||
Mode: step.Mode,
|
||||
Text: step.Text,
|
||||
TimerLabel: step.TimerLabel,
|
||||
TimerDurationSeconds: step.TimerDurationSeconds,
|
||||
})
|
||||
}
|
||||
return mapped
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Package screen_plan_service handles game screen plans.
|
||||
package screen_plan_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"evening_detective_server/internal/repos/game_screen_plans_repo"
|
||||
"evening_detective_server/internal/services/screen_service"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPlanNotFound indicates that a game has no screen plan.
|
||||
ErrPlanNotFound = errors.New("план экранов не найден")
|
||||
// ErrInvalidPlan indicates invalid screen plan data.
|
||||
ErrInvalidPlan = errors.New("некорректный план экранов")
|
||||
// ErrStepNotFound indicates that a screen plan step does not exist.
|
||||
ErrStepNotFound = errors.New("шаг плана экранов не найден")
|
||||
)
|
||||
|
||||
// Step is a screen plan step used by the application layer.
|
||||
type Step struct {
|
||||
Mode int32
|
||||
Text string
|
||||
TimerLabel string
|
||||
TimerDurationSeconds int64
|
||||
}
|
||||
|
||||
// Plan is a game screen plan used by the application layer.
|
||||
type Plan struct {
|
||||
GameID int
|
||||
Steps []Step
|
||||
CurrentStep int
|
||||
}
|
||||
|
||||
// ScreenPlanService handles game screen plans and applies their current step.
|
||||
type ScreenPlanService struct {
|
||||
repo *game_screen_plans_repo.GameScreenPlansRepo
|
||||
screenService *screen_service.ScreenService
|
||||
}
|
||||
|
||||
// NewScreenPlanService creates a screen plan service.
|
||||
func NewScreenPlanService(
|
||||
repo *game_screen_plans_repo.GameScreenPlansRepo,
|
||||
screenService *screen_service.ScreenService,
|
||||
) *ScreenPlanService {
|
||||
return &ScreenPlanService{repo: repo, screenService: screenService}
|
||||
}
|
||||
|
||||
// Get returns a game's screen plan.
|
||||
func (s *ScreenPlanService) Get(ctx context.Context, gameID int) (*Plan, error) {
|
||||
stored, err := s.repo.Get(ctx, gameID)
|
||||
if errors.Is(err, game_screen_plans_repo.ErrPlanNotFound) {
|
||||
return nil, ErrPlanNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mapPlan(stored), nil
|
||||
}
|
||||
|
||||
// Save validates, stores and applies a game's screen plan.
|
||||
func (s *ScreenPlanService) Save(ctx context.Context, gameID int, steps []Step, currentStep int) (*Plan, error) {
|
||||
steps, err := normalizePlan(steps, currentStep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(steps) == 0 {
|
||||
if err := s.repo.Delete(ctx, gameID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
stored, err := s.repo.Save(ctx, gameID, mapSteps(steps), currentStep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plan := mapPlan(stored)
|
||||
if err := s.applyStep(ctx, gameID, plan); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
// SetStep stores and applies the selected step.
|
||||
func (s *ScreenPlanService) SetStep(ctx context.Context, gameID, currentStep int) (*Plan, error) {
|
||||
plan, err := s.Get(ctx, gameID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if currentStep < 0 || currentStep >= len(plan.Steps) {
|
||||
return nil, ErrStepNotFound
|
||||
}
|
||||
stored, err := s.repo.Save(ctx, gameID, mapSteps(plan.Steps), currentStep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plan = mapPlan(stored)
|
||||
if err := s.applyStep(ctx, gameID, plan); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
// ApplyCurrent applies the current step when a screen joins a game.
|
||||
func (s *ScreenPlanService) ApplyCurrent(ctx context.Context, gameID int) error {
|
||||
plan, err := s.Get(ctx, gameID)
|
||||
if errors.Is(err, ErrPlanNotFound) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.applyStep(ctx, gameID, plan)
|
||||
}
|
||||
|
||||
func normalizePlan(steps []Step, currentStep int) ([]Step, error) {
|
||||
if currentStep < 0 || (len(steps) > 0 && currentStep >= len(steps)) || (len(steps) == 0 && currentStep != 0) {
|
||||
return nil, ErrInvalidPlan
|
||||
}
|
||||
normalized := make([]Step, 0, len(steps))
|
||||
for _, step := range steps {
|
||||
if step.Mode <= 0 || step.Mode > 8 {
|
||||
return nil, ErrInvalidPlan
|
||||
}
|
||||
step.Text = strings.TrimSpace(step.Text)
|
||||
step.TimerLabel = strings.TrimSpace(step.TimerLabel)
|
||||
if step.Mode == 8 && (step.Text == "" || len(step.Text) > 10000) {
|
||||
return nil, ErrInvalidPlan
|
||||
}
|
||||
if step.Mode == 7 {
|
||||
if step.TimerDurationSeconds <= 0 || step.TimerDurationSeconds > 24*60*60 {
|
||||
return nil, ErrInvalidPlan
|
||||
}
|
||||
if step.TimerLabel == "" {
|
||||
step.TimerLabel = "Перерыв"
|
||||
}
|
||||
if len(step.TimerLabel) > 200 {
|
||||
return nil, ErrInvalidPlan
|
||||
}
|
||||
}
|
||||
normalized = append(normalized, step)
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func (s *ScreenPlanService) applyStep(ctx context.Context, gameID int, plan *Plan) error {
|
||||
step := plan.Steps[plan.CurrentStep]
|
||||
if step.Mode == 8 {
|
||||
return s.screenService.SetText(ctx, gameID, step.Text)
|
||||
}
|
||||
if step.Mode == 7 {
|
||||
return s.screenService.SetTimer(ctx, gameID, step.TimerDurationSeconds, step.TimerLabel)
|
||||
}
|
||||
return s.screenService.SetMode(ctx, gameID, step.Mode)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package screen_plan_service
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNormalizePlan(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
steps []Step
|
||||
currentStep int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "normalizes text and timer defaults",
|
||||
steps: []Step{
|
||||
{Mode: 8, Text: " Внимание "},
|
||||
{Mode: 7, TimerDurationSeconds: 60},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rejects empty text",
|
||||
steps: []Step{{Mode: 8}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "rejects missing current step",
|
||||
steps: []Step{{Mode: 1}},
|
||||
currentStep: 1,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
steps, err := normalizePlan(tt.steps, tt.currentStep)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Fatalf("normalizePlan() error = %v, want error: %v", err, tt.wantErr)
|
||||
}
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
if steps[0].Text != "Внимание" || steps[1].TimerLabel != "Перерыв" {
|
||||
t.Fatalf("plan defaults were not normalized: %+v", steps)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ type ScreenSession struct {
|
||||
TimerRemainingSeconds int64
|
||||
TimerState int32
|
||||
TimerEndsAt *time.Time
|
||||
Text string
|
||||
}
|
||||
|
||||
func mapSession(session *screen_sessions_repo.ScreenSession) *ScreenSession {
|
||||
@@ -41,5 +42,6 @@ func mapSession(session *screen_sessions_repo.ScreenSession) *ScreenSession {
|
||||
TimerRemainingSeconds: session.TimerRemainingSeconds,
|
||||
TimerState: session.TimerState,
|
||||
TimerEndsAt: session.TimerEndsAt,
|
||||
Text: session.Text,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ func (s *ScreenService) SetTimer(ctx context.Context, gameID int, duration int64
|
||||
return s.repo.SetTimer(ctx, gameID, duration, label)
|
||||
}
|
||||
|
||||
// SetText updates the text screen for a game.
|
||||
func (s *ScreenService) SetText(ctx context.Context, gameID int, text string) error {
|
||||
return s.repo.SetText(ctx, gameID, text)
|
||||
}
|
||||
|
||||
// SetTimerCommand updates the timer command for a game.
|
||||
func (s *ScreenService) SetTimerCommand(ctx context.Context, gameID int, command int32) error {
|
||||
return s.repo.SetTimerCommand(ctx, gameID, command)
|
||||
|
||||
Reference in New Issue
Block a user