diff --git a/bin/evening_detective_server b/bin/evening_detective_server index 0b17b05..6e1f936 100755 Binary files a/bin/evening_detective_server and b/bin/evening_detective_server differ diff --git a/internal/app/server.go b/internal/app/server.go index 8203b2c..1ec7558 100644 --- a/internal/app/server.go +++ b/internal/app/server.go @@ -274,7 +274,9 @@ func screenState(session *screen_sessions_repo.ScreenSession) *proto.ScreenState Id: fmt.Sprint(session.ID), Status: "disconnected", SoundEnabled: session.Sound, - Mode: proto.ScreenMode_SCREEN_MODE_UNSPECIFIED, + Mode: proto.ScreenMode(session.Mode), + StateVersion: session.Version, + PositionMs: session.Position, } if time.Since(session.LastSeen) <= 5*time.Second { state.Status = "connected" @@ -282,17 +284,46 @@ func screenState(session *screen_sessions_repo.ScreenSession) *proto.ScreenState if session.GameID != nil { gameID := int32(*session.GameID) state.GameId = &gameID - state.Mode = proto.ScreenMode_SCREEN_MODE_COVER + if state.Mode == proto.ScreenMode_SCREEN_MODE_UNSPECIFIED { + state.Mode = proto.ScreenMode_SCREEN_MODE_COVER + } } return state } -func (s *server) SetScreenMode(context.Context, *proto.SetScreenModeReq) (*proto.SetScreenModeRsp, error) { - panic("SetScreenMode is not implemented") +func (s *server) SetScreenMode(ctx context.Context, req *proto.SetScreenModeReq) (*proto.SetScreenModeRsp, error) { + if !canManageScreens(ctx) { + return nil, status.Error(codes.PermissionDenied, "permission denied") + } + if req == nil || req.GameId <= 0 || req.Mode == proto.ScreenMode_SCREEN_MODE_UNSPECIFIED { + return nil, status.Error(codes.InvalidArgument, "некорректный режим экрана") + } + if _, err := s.gameService.GetGame(ctx, int(req.GameId)); err != nil { + return nil, status.Error(codes.NotFound, "игра не найдена") + } + if err := s.screenRepo.SetMode(ctx, int(req.GameId), int32(req.Mode)); err != nil { + return nil, status.Error(codes.Internal, "не удалось изменить режим экранов") + } + return &proto.SetScreenModeRsp{}, nil } -func (s *server) SendScreenCommand(context.Context, *proto.SendScreenCommandReq) (*proto.SendScreenCommandRsp, error) { - panic("SendScreenCommand is not implemented") +func (s *server) SendScreenCommand(ctx context.Context, req *proto.SendScreenCommandReq) (*proto.SendScreenCommandRsp, error) { + if !canManageScreens(ctx) { + return nil, status.Error(codes.PermissionDenied, "permission denied") + } + if req == nil || req.GameId <= 0 || req.Command == proto.ScreenCommand_SCREEN_COMMAND_UNSPECIFIED { + return nil, status.Error(codes.InvalidArgument, "некорректная команда экрана") + } + if _, err := s.gameService.GetGame(ctx, int(req.GameId)); err != nil { + return nil, status.Error(codes.NotFound, "игра не найдена") + } + if req.PositionMs < 0 { + return nil, status.Error(codes.InvalidArgument, "некорректная позиция") + } + if err := s.screenRepo.SetPosition(ctx, int(req.GameId), req.PositionMs); err != nil { + return nil, status.Error(codes.Internal, "не удалось сохранить позицию экранов") + } + return &proto.SendScreenCommandRsp{}, nil } func (s *server) Ping(_ context.Context, _ *proto.PingReq) (*proto.PingRsp, error) { diff --git a/internal/repos/screen_sessions_repo/repo.go b/internal/repos/screen_sessions_repo/repo.go index 5b4eed9..a7b7efd 100644 --- a/internal/repos/screen_sessions_repo/repo.go +++ b/internal/repos/screen_sessions_repo/repo.go @@ -16,6 +16,9 @@ type ScreenSession struct { GameID *int Sound bool LastSeen time.Time + Mode int32 + Version int64 + Position int64 } type ScreenSessionsRepo struct { @@ -38,10 +41,10 @@ func (r *ScreenSessionsRepo) Create(ctx context.Context, tokenHash string) (int, func (r *ScreenSessionsRepo) Get(ctx context.Context, id int, tokenHash string) (*ScreenSession, error) { session := &ScreenSession{ID: id} err := r.pool.QueryRow(ctx, ` - SELECT game_id, sound_enabled, last_seen_at + SELECT game_id, sound_enabled, last_seen_at, mode, state_version, position_ms FROM screen_sessions WHERE id = $1 AND access_token_hash = $2`, id, tokenHash). - Scan(&session.GameID, &session.Sound, &session.LastSeen) + Scan(&session.GameID, &session.Sound, &session.LastSeen, &session.Mode, &session.Version, &session.Position) if errors.Is(err, pgx.ErrNoRows) { return nil, ErrScreenNotFound } @@ -58,7 +61,7 @@ func (r *ScreenSessionsRepo) Get(ctx context.Context, id int, tokenHash string) func (r *ScreenSessionsRepo) ListByGame(ctx context.Context, gameID int) ([]*ScreenSession, error) { rows, err := r.pool.Query(ctx, ` - SELECT id, game_id, sound_enabled, last_seen_at + SELECT id, game_id, sound_enabled, last_seen_at, mode, state_version, position_ms FROM screen_sessions WHERE game_id = $1 ORDER BY id`, gameID) @@ -70,7 +73,7 @@ func (r *ScreenSessionsRepo) ListByGame(ctx context.Context, gameID int) ([]*Scr var sessions []*ScreenSession for rows.Next() { session := &ScreenSession{} - if err := rows.Scan(&session.ID, &session.GameID, &session.Sound, &session.LastSeen); err != nil { + if err := rows.Scan(&session.ID, &session.GameID, &session.Sound, &session.LastSeen, &session.Mode, &session.Version, &session.Position); err != nil { return nil, err } sessions = append(sessions, session) @@ -78,6 +81,16 @@ func (r *ScreenSessionsRepo) ListByGame(ctx context.Context, gameID int) ([]*Scr return sessions, rows.Err() } +func (r *ScreenSessionsRepo) SetMode(ctx context.Context, gameID int, mode int32) error { + _, err := r.pool.Exec(ctx, `UPDATE screen_sessions SET mode = $2, state_version = state_version + 1, updated_at = NOW() WHERE game_id = $1`, gameID, mode) + return err +} + +func (r *ScreenSessionsRepo) SetPosition(ctx context.Context, gameID int, position int64) error { + _, err := r.pool.Exec(ctx, `UPDATE screen_sessions SET position_ms = $2, state_version = state_version + 1, updated_at = NOW() WHERE game_id = $1`, gameID, position) + return err +} + func (r *ScreenSessionsRepo) Bind(ctx context.Context, id, gameID int, tokenHash string) error { result, err := r.pool.Exec(ctx, ` UPDATE screen_sessions diff --git a/migrations/20260910150000_add_screen_playback_state.sql b/migrations/20260910150000_add_screen_playback_state.sql new file mode 100644 index 0000000..eee6888 --- /dev/null +++ b/migrations/20260910150000_add_screen_playback_state.sql @@ -0,0 +1,11 @@ +-- +goose Up +ALTER TABLE screen_sessions + ADD COLUMN IF NOT EXISTS mode INTEGER NOT NULL DEFAULT 1, + ADD COLUMN IF NOT EXISTS state_version BIGINT NOT NULL DEFAULT 1, + ADD COLUMN IF NOT EXISTS position_ms BIGINT NOT NULL DEFAULT 0; + +-- +goose Down +ALTER TABLE screen_sessions + DROP COLUMN IF EXISTS position_ms, + DROP COLUMN IF EXISTS state_version, + DROP COLUMN IF EXISTS mode;