generated from VLADIMIR/template
up
This commit is contained in:
+40
-7
@@ -921,6 +921,10 @@ service EveningDetectiveServer {
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetScreenTeams(GetScreenTeamsReq) returns (GetScreenTeamsRsp) {
|
||||
option (google.api.http) = { post: "/api/screens/teams" body: "*" };
|
||||
}
|
||||
|
||||
rpc GetScreenResults(GetScreenResultsReq) returns (GetScreenResultsRsp) {
|
||||
option (google.api.http) = { post: "/api/screens/results" body: "*" };
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
@@ -1783,6 +1787,14 @@ enum ScreenMode {
|
||||
SCREEN_MODE_RESULTS = 6;
|
||||
SCREEN_MODE_TIMER = 7;
|
||||
SCREEN_MODE_TEXT = 8;
|
||||
SCREEN_MODE_QR = 9;
|
||||
SCREEN_MODE_TEAMS = 10;
|
||||
}
|
||||
|
||||
enum ScreenTimerType {
|
||||
SCREEN_TIMER_TYPE_BREAK = 0;
|
||||
SCREEN_TIMER_TYPE_BEFORE_GAME = 1;
|
||||
SCREEN_TIMER_TYPE_UNTIL_END = 2;
|
||||
}
|
||||
|
||||
enum ScreenTimerState {
|
||||
@@ -1830,6 +1842,8 @@ message ScreenState {
|
||||
ScreenTimerState timer_state = 19;
|
||||
string text = 20;
|
||||
MusicState music = 21;
|
||||
string qr_url = 22;
|
||||
ScreenTimerType timer_type = 23;
|
||||
}
|
||||
|
||||
message CreateScreenSessionReq {}
|
||||
@@ -1966,15 +1980,18 @@ message UpdateGameMapItemRsp {}
|
||||
message SetScreenModeReq {
|
||||
int32 gameID = 1;
|
||||
ScreenMode mode = 2;
|
||||
string qr_url = 3;
|
||||
string text = 4;
|
||||
}
|
||||
|
||||
message SetScreenModeRsp {
|
||||
}
|
||||
|
||||
message SetScreenTimerReq {
|
||||
int32 gameID = 1;
|
||||
int64 duration_seconds = 2;
|
||||
string label = 3;
|
||||
int32 gameID = 1;
|
||||
int64 duration_seconds = 2;
|
||||
string label = 3;
|
||||
ScreenTimerType timer_type = 4;
|
||||
}
|
||||
|
||||
message SetScreenTimerRsp {
|
||||
@@ -1991,10 +2008,12 @@ message SendScreenCommandRsp {
|
||||
}
|
||||
|
||||
message ScreenPlanStep {
|
||||
ScreenMode mode = 1;
|
||||
string text = 2;
|
||||
string timer_label = 3;
|
||||
int64 timer_duration_seconds = 4;
|
||||
ScreenMode mode = 1;
|
||||
string text = 2;
|
||||
string timer_label = 3;
|
||||
int64 timer_duration_seconds = 4;
|
||||
string qr_url = 5;
|
||||
ScreenTimerType timer_type = 6;
|
||||
}
|
||||
|
||||
message ScreenPlan {
|
||||
@@ -2029,6 +2048,20 @@ message SetScreenPlanStepRsp {
|
||||
ScreenPlan plan = 1;
|
||||
}
|
||||
|
||||
message ScreenTeam {
|
||||
string ID = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message GetScreenTeamsReq {
|
||||
string screenID = 1;
|
||||
string access_token = 2;
|
||||
}
|
||||
|
||||
message GetScreenTeamsRsp {
|
||||
repeated ScreenTeam teams = 1;
|
||||
}
|
||||
|
||||
message GetScreenResultsReq {
|
||||
string screenID = 1;
|
||||
string accessToken = 2;
|
||||
|
||||
@@ -276,6 +276,7 @@ func main() {
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetScreenState": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/AdvanceScreenMusic": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetScreenResults": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetScreenTeams": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/CreateGameReview": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetGameReviews": true,
|
||||
},
|
||||
|
||||
@@ -3055,6 +3055,38 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/screens/teams": {
|
||||
"post": {
|
||||
"operationId": "EveningDetectiveServer_GetScreenTeams",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverGetScreenTeamsRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detective_serverGetScreenTeamsReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"EveningDetectiveServer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/screens/{screenID}/bind": {
|
||||
"post": {
|
||||
"summary": "Привязать экран к игре",
|
||||
@@ -4387,6 +4419,12 @@
|
||||
"properties": {
|
||||
"mode": {
|
||||
"$ref": "#/definitions/evening_detective_serverScreenMode"
|
||||
},
|
||||
"qrUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4416,6 +4454,9 @@
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"timerType": {
|
||||
"$ref": "#/definitions/evening_detective_serverScreenTimerType"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5551,6 +5592,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGetScreenTeamsReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"screenID": {
|
||||
"type": "string"
|
||||
},
|
||||
"accessToken": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGetScreenTeamsRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"teams": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/evening_detective_serverScreenTeam"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverGetTaskAssigneesRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6064,7 +6128,9 @@
|
||||
"SCREEN_MODE_CONCLUSION",
|
||||
"SCREEN_MODE_RESULTS",
|
||||
"SCREEN_MODE_TIMER",
|
||||
"SCREEN_MODE_TEXT"
|
||||
"SCREEN_MODE_TEXT",
|
||||
"SCREEN_MODE_QR",
|
||||
"SCREEN_MODE_TEAMS"
|
||||
],
|
||||
"default": "SCREEN_MODE_UNSPECIFIED"
|
||||
},
|
||||
@@ -6099,6 +6165,12 @@
|
||||
"timerDurationSeconds": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"qrUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"timerType": {
|
||||
"$ref": "#/definitions/evening_detective_serverScreenTimerType"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6173,6 +6245,23 @@
|
||||
},
|
||||
"music": {
|
||||
"$ref": "#/definitions/evening_detective_serverMusicState"
|
||||
},
|
||||
"qrUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"timerType": {
|
||||
"$ref": "#/definitions/evening_detective_serverScreenTimerType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detective_serverScreenTeam": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ID": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6187,6 +6276,15 @@
|
||||
],
|
||||
"default": "SCREEN_TIMER_STATE_UNSPECIFIED"
|
||||
},
|
||||
"evening_detective_serverScreenTimerType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SCREEN_TIMER_TYPE_BREAK",
|
||||
"SCREEN_TIMER_TYPE_BEFORE_GAME",
|
||||
"SCREEN_TIMER_TYPE_UNTIL_END"
|
||||
],
|
||||
"default": "SCREEN_TIMER_TYPE_BREAK"
|
||||
},
|
||||
"evening_detective_serverSendScreenCommandRsp": {
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,8 @@ func mapScreenPlanSteps(steps []*proto.ScreenPlanStep) []screen_plan_service.Ste
|
||||
}
|
||||
mapped = append(mapped, screen_plan_service.Step{
|
||||
Mode: int32(step.Mode),
|
||||
QRURL: step.QrUrl,
|
||||
TimerType: int32(step.TimerType),
|
||||
Text: step.Text,
|
||||
TimerLabel: step.TimerLabel,
|
||||
TimerDurationSeconds: step.TimerDurationSeconds,
|
||||
@@ -30,6 +32,8 @@ func mapScreenPlan(plan *screen_plan_service.Plan) *proto.ScreenPlan {
|
||||
for _, step := range plan.Steps {
|
||||
steps = append(steps, &proto.ScreenPlanStep{
|
||||
Mode: proto.ScreenMode(step.Mode),
|
||||
QrUrl: step.QRURL,
|
||||
TimerType: proto.ScreenTimerType(step.TimerType),
|
||||
Text: step.Text,
|
||||
TimerLabel: step.TimerLabel,
|
||||
TimerDurationSeconds: step.TimerDurationSeconds,
|
||||
|
||||
+40
-3
@@ -222,6 +222,8 @@ func screenState(session *screen_service.ScreenSession) *proto.ScreenState {
|
||||
TimerRemainingSeconds: timerRemaining,
|
||||
TimerState: timerState,
|
||||
Text: session.Text,
|
||||
QrUrl: session.QRURL,
|
||||
TimerType: proto.ScreenTimerType(session.TimerType),
|
||||
}
|
||||
if time.Since(session.LastSeen) <= 5*time.Second {
|
||||
state.Status = "connected"
|
||||
@@ -277,6 +279,15 @@ func (s *server) SetScreenMode(ctx context.Context, req *proto.SetScreenModeReq)
|
||||
if _, err := s.gameService.GetGame(ctx, int(req.GameID)); err != nil {
|
||||
return nil, status.Error(codes.NotFound, "игра не найдена")
|
||||
}
|
||||
if req.Mode == proto.ScreenMode_SCREEN_MODE_QR {
|
||||
if !screen_service.ValidQR(req.QrUrl, req.Text) {
|
||||
return nil, status.Error(codes.InvalidArgument, "Укажите ссылку HTTP/HTTPS до 2048 байт и текст до 10000 байт")
|
||||
}
|
||||
if err := s.screenService.SetQR(ctx, int(req.GameID), req.QrUrl, req.Text); err != nil {
|
||||
return nil, status.Error(codes.Internal, "не удалось изменить QR-код")
|
||||
}
|
||||
return &proto.SetScreenModeRsp{}, nil
|
||||
}
|
||||
if err := s.screenService.SetMode(ctx, int(req.GameID), int32(req.Mode)); err != nil {
|
||||
return nil, status.Error(codes.Internal, "не удалось изменить режим экранов")
|
||||
}
|
||||
@@ -287,12 +298,12 @@ func (s *server) SetScreenTimer(ctx context.Context, req *proto.SetScreenTimerRe
|
||||
if !canManageScreens(ctx) {
|
||||
return nil, status.Error(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
if req == nil || req.GameID <= 0 || req.DurationSeconds <= 0 || req.DurationSeconds > 24*60*60 {
|
||||
if req == nil || req.GameID <= 0 || req.DurationSeconds <= 0 || req.DurationSeconds > 24*60*60 || !screen_service.ValidTimerType(int32(req.TimerType)) {
|
||||
return nil, status.Error(codes.InvalidArgument, "некорректные параметры таймера")
|
||||
}
|
||||
label := strings.TrimSpace(req.Label)
|
||||
if label == "" {
|
||||
label = "Перерыв"
|
||||
label = screen_service.TimerLabel(int32(req.TimerType))
|
||||
}
|
||||
if len(label) > 200 {
|
||||
return nil, status.Error(codes.InvalidArgument, "слишком длинная надпись таймера")
|
||||
@@ -300,7 +311,7 @@ func (s *server) SetScreenTimer(ctx context.Context, req *proto.SetScreenTimerRe
|
||||
if _, err := s.gameService.GetGame(ctx, int(req.GameID)); err != nil {
|
||||
return nil, status.Error(codes.NotFound, "игра не найдена")
|
||||
}
|
||||
if err := s.screenService.SetTimer(ctx, int(req.GameID), req.DurationSeconds, label); err != nil {
|
||||
if err := s.screenService.SetTimer(ctx, int(req.GameID), req.DurationSeconds, label, int32(req.TimerType)); err != nil {
|
||||
return nil, status.Error(codes.Internal, "не удалось настроить таймер экранов")
|
||||
}
|
||||
return &proto.SetScreenTimerRsp{}, nil
|
||||
@@ -366,3 +377,29 @@ func (s *server) GetScreenResults(ctx context.Context, req *proto.GetScreenResul
|
||||
}
|
||||
return &proto.GetScreenResultsRsp{Questions: mapResultsQuestions(results.Questions), Teams: mapTeamResults(results.Teams)}, nil
|
||||
}
|
||||
|
||||
func (s *server) GetScreenTeams(ctx context.Context, req *proto.GetScreenTeamsReq) (*proto.GetScreenTeamsRsp, error) {
|
||||
if req == nil || req.ScreenID == "" || req.AccessToken == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "некорректные данные экрана")
|
||||
}
|
||||
id, err := strconv.Atoi(req.ScreenID)
|
||||
if err != nil || id <= 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "некорректный идентификатор экрана")
|
||||
}
|
||||
session, err := s.screenService.Get(ctx, id, hashScreenToken(req.AccessToken))
|
||||
if errors.Is(err, screen_service.ErrScreenNotFound) {
|
||||
return nil, status.Error(codes.Unauthenticated, "недействительная сессия экрана")
|
||||
}
|
||||
if err != nil || session.GameID == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "экран не привязан к игре")
|
||||
}
|
||||
teams, err := s.teamService.GetScreenTeams(ctx, *session.GameID)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "не удалось получить команды")
|
||||
}
|
||||
result := make([]*proto.ScreenTeam, 0, len(teams))
|
||||
for _, team := range teams {
|
||||
result = append(result, &proto.ScreenTeam{ID: team.ID, Name: team.Name})
|
||||
}
|
||||
return &proto.GetScreenTeamsRsp{Teams: result}, nil
|
||||
}
|
||||
|
||||
@@ -26,3 +26,22 @@ func TestSetScreenModeRejectsRemovedModes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScreenTeamsRejectsInvalidAccess(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
req *proto.GetScreenTeamsReq
|
||||
}{
|
||||
{"missing", nil},
|
||||
{"missing token", &proto.GetScreenTeamsReq{ScreenID: "1"}},
|
||||
{"bad ID", &proto.GetScreenTeamsReq{ScreenID: "bad", AccessToken: "token"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := (&server{}).GetScreenTeams(context.Background(), tt.req)
|
||||
if status.Code(err) != codes.InvalidArgument {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ var ErrPlanNotFound = errors.New("план экранов не найден")
|
||||
|
||||
// Step is a stored screen plan step.
|
||||
type Step struct {
|
||||
QRURL string `json:"qrUrl,omitempty"`
|
||||
TimerType int32 `json:"timerType,omitempty"`
|
||||
Mode int32 `json:"mode"`
|
||||
Text string `json:"text,omitempty"`
|
||||
TimerLabel string `json:"timerLabel,omitempty"`
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"evening_detective_server/internal/services/screen_service"
|
||||
)
|
||||
|
||||
func TestReorderPreservesPlaybackAndRejectsChangedMembership(t *testing.T) {
|
||||
@@ -135,7 +137,7 @@ func TestBindInitializesAudioWithoutChangingTransport(t *testing.T) {
|
||||
if isNewBinding {
|
||||
t.Fatal("repeat binding would reset transport")
|
||||
}
|
||||
requireNoError(t, f.screens.InitializeScreen(ctx, second, f.gameID, 1, "", "Pause", 600))
|
||||
requireNoError(t, f.screens.InitializeScreen(ctx, second, f.gameID, 1, "", "Pause", 600, "", 0))
|
||||
original, err := f.screens.Get(ctx, first, "first")
|
||||
requireNoError(t, err)
|
||||
joined, err := f.screens.Get(ctx, second, "second")
|
||||
@@ -245,3 +247,62 @@ func TestConcurrentBindingsAssignSoundOnce(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewScreenStatePersistsAndCopiesToJoiningScreen(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mode int32
|
||||
link, label string
|
||||
timerType int32
|
||||
}{
|
||||
{name: "QR", mode: 9, link: "https://example.org/join"},
|
||||
{name: "timer", mode: 7, label: "Обсуждение", timerType: 2},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := newPlaylistFixture(t)
|
||||
ctx := context.Background()
|
||||
first, err := f.screens.Create(ctx, "first-hash")
|
||||
requireNoError(t, err)
|
||||
requireNoError(t, f.screens.Bind(ctx, first, f.gameID, "first-hash"))
|
||||
if tt.mode == 9 {
|
||||
service := screen_service.NewScreenService(f.screens)
|
||||
requireNoError(t, service.InitializeScreen(ctx, first, f.gameID, 9, "Присоединяйтесь", "", 0, tt.link, 0))
|
||||
initialized, err := f.screens.Get(ctx, first, "first-hash")
|
||||
requireNoError(t, err)
|
||||
if initialized.TimerDurationSeconds != 600 {
|
||||
t.Fatal("non-timer plan initialized an invalid timer")
|
||||
}
|
||||
requireNoError(t, f.screens.SetQR(ctx, f.gameID, tt.link, "Присоединяйтесь"))
|
||||
} else {
|
||||
requireNoError(t, f.screens.SetTimer(ctx, f.gameID, 60, tt.label, tt.timerType))
|
||||
}
|
||||
second, err := f.screens.Create(ctx, "second-hash")
|
||||
requireNoError(t, err)
|
||||
requireNoError(t, f.screens.Bind(ctx, second, f.gameID, "second-hash"))
|
||||
requireNoError(t, f.screens.InitializeScreen(ctx, second, f.gameID, 1, "", "Перерыв", 600, "", 0))
|
||||
copied, err := f.screens.Get(ctx, second, "second-hash")
|
||||
requireNoError(t, err)
|
||||
if copied.Mode != tt.mode || copied.QRURL != tt.link || copied.TimerType != tt.timerType {
|
||||
t.Fatalf("joining screen lost state: %+v", copied)
|
||||
}
|
||||
if tt.mode == 9 && copied.Text != "Присоединяйтесь" {
|
||||
t.Fatal("lost QR caption")
|
||||
}
|
||||
if tt.mode == 7 && (copied.TimerLabel != tt.label || copied.TimerDurationSeconds != 60) {
|
||||
t.Fatal("lost timer settings")
|
||||
}
|
||||
all, err := f.screens.ListByGame(ctx, f.gameID)
|
||||
requireNoError(t, err)
|
||||
if len(all) != 2 || all[1].QRURL != copied.QRURL || all[1].TimerType != copied.TimerType {
|
||||
t.Fatal("state list lost new fields")
|
||||
}
|
||||
requireNoError(t, f.screens.Unbind(ctx, second))
|
||||
detached, err := f.screens.Get(ctx, second, "second-hash")
|
||||
requireNoError(t, err)
|
||||
if detached.QRURL != "" || detached.TimerType != 0 {
|
||||
t.Fatal("unbind retained another game's settings")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ type ScreenSession struct {
|
||||
TimerRemainingSeconds int64
|
||||
TimerState int32
|
||||
TimerEndsAt *time.Time
|
||||
QRURL string
|
||||
TimerType int32
|
||||
Text string
|
||||
}
|
||||
|
||||
@@ -55,20 +57,41 @@ func (r *ScreenSessionsRepo) Create(ctx context.Context, tokenHash string) (int,
|
||||
// Get returns a screen session by ID and token hash.
|
||||
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, mode, state_version, position_ms, command, volume,
|
||||
timer_label, timer_duration_seconds, timer_remaining_seconds, timer_state, timer_ends_at, text
|
||||
err := r.pool.QueryRow(
|
||||
ctx,
|
||||
`SELECT
|
||||
game_id,
|
||||
sound_enabled,
|
||||
last_seen_at,
|
||||
mode,
|
||||
state_version,
|
||||
position_ms,
|
||||
command,
|
||||
volume,
|
||||
timer_label,
|
||||
timer_duration_seconds,
|
||||
timer_remaining_seconds,
|
||||
timer_state,
|
||||
timer_ends_at,
|
||||
text,
|
||||
qr_url,
|
||||
timer_type
|
||||
FROM screen_sessions
|
||||
WHERE id = $1 AND access_token_hash = $2`, id, tokenHash).
|
||||
WHERE id = $1 AND access_token_hash = $2`,
|
||||
id,
|
||||
tokenHash,
|
||||
).
|
||||
Scan(&session.GameID, &session.Sound, &session.LastSeen, &session.Mode, &session.Version, &session.Position, &session.Command, &session.Volume,
|
||||
&session.TimerLabel, &session.TimerDurationSeconds, &session.TimerRemainingSeconds, &session.TimerState, &session.TimerEndsAt, &session.Text)
|
||||
&session.TimerLabel, &session.TimerDurationSeconds, &session.TimerRemainingSeconds, &session.TimerState, &session.TimerEndsAt, &session.Text, &session.QRURL, &session.TimerType)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrScreenNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = r.pool.Exec(ctx, `UPDATE screen_sessions SET last_seen_at = NOW() WHERE id = $1`, id)
|
||||
_, err = r.pool.Exec(ctx, `UPDATE screen_sessions SET last_seen_at = NOW() WHERE id = $1`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -78,12 +101,31 @@ func (r *ScreenSessionsRepo) Get(ctx context.Context, id int, tokenHash string)
|
||||
|
||||
// ListByGame returns screen sessions for a game.
|
||||
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, mode, state_version, position_ms, command, volume,
|
||||
timer_label, timer_duration_seconds, timer_remaining_seconds, timer_state, timer_ends_at, text
|
||||
rows, err := r.pool.Query(
|
||||
ctx,
|
||||
`SELECT
|
||||
id,
|
||||
game_id,
|
||||
sound_enabled,
|
||||
last_seen_at,
|
||||
mode,
|
||||
state_version,
|
||||
position_ms,
|
||||
command,
|
||||
volume,
|
||||
timer_label,
|
||||
timer_duration_seconds,
|
||||
timer_remaining_seconds,
|
||||
timer_state,
|
||||
timer_ends_at,
|
||||
text,
|
||||
qr_url,
|
||||
timer_type
|
||||
FROM screen_sessions
|
||||
WHERE game_id = $1
|
||||
ORDER BY id`, gameID)
|
||||
ORDER BY id`,
|
||||
gameID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,7 +135,7 @@ func (r *ScreenSessionsRepo) ListByGame(ctx context.Context, gameID int) ([]*Scr
|
||||
for rows.Next() {
|
||||
session := &ScreenSession{}
|
||||
if err := rows.Scan(&session.ID, &session.GameID, &session.Sound, &session.LastSeen, &session.Mode, &session.Version, &session.Position, &session.Command, &session.Volume,
|
||||
&session.TimerLabel, &session.TimerDurationSeconds, &session.TimerRemainingSeconds, &session.TimerState, &session.TimerEndsAt, &session.Text); err != nil {
|
||||
&session.TimerLabel, &session.TimerDurationSeconds, &session.TimerRemainingSeconds, &session.TimerState, &session.TimerEndsAt, &session.Text, &session.QRURL, &session.TimerType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessions = append(sessions, session)
|
||||
@@ -148,10 +190,12 @@ func (r *ScreenSessionsRepo) SetCommand(ctx context.Context, gameID int, command
|
||||
}
|
||||
|
||||
// SetTimer updates the display timer for a game.
|
||||
func (r *ScreenSessionsRepo) SetTimer(ctx context.Context, gameID int, duration int64, label string) error {
|
||||
_, err := r.pool.Exec(ctx, `
|
||||
UPDATE screen_sessions
|
||||
func (r *ScreenSessionsRepo) SetTimer(ctx context.Context, gameID int, duration int64, label string, timerType int32) error {
|
||||
_, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE screen_sessions
|
||||
SET mode = 7,
|
||||
timer_type = $4,
|
||||
timer_label = $2,
|
||||
timer_duration_seconds = $3,
|
||||
timer_remaining_seconds = $3,
|
||||
@@ -161,7 +205,12 @@ func (r *ScreenSessionsRepo) SetTimer(ctx context.Context, gameID int, duration
|
||||
state_version = state_version + 1,
|
||||
text = '',
|
||||
updated_at = NOW()
|
||||
WHERE game_id = $1`, gameID, label, duration)
|
||||
WHERE game_id = $1`,
|
||||
gameID,
|
||||
label,
|
||||
duration,
|
||||
timerType,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -280,12 +329,15 @@ func (r *ScreenSessionsRepo) BindWithAudio(ctx context.Context, id, gameID int,
|
||||
|
||||
// Unbind detaches a screen session from its game.
|
||||
func (r *ScreenSessionsRepo) Unbind(ctx context.Context, id int) error {
|
||||
result, err := r.pool.Exec(ctx, `
|
||||
UPDATE screen_sessions
|
||||
result, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE screen_sessions
|
||||
SET game_id = NULL,
|
||||
mode = 1,
|
||||
command = 0,
|
||||
position_ms = 0,
|
||||
qr_url = '',
|
||||
timer_type = 0,
|
||||
timer_label = 'Перерыв',
|
||||
timer_duration_seconds = 600,
|
||||
timer_remaining_seconds = 600,
|
||||
@@ -294,7 +346,9 @@ func (r *ScreenSessionsRepo) Unbind(ctx context.Context, id int) error {
|
||||
text = '',
|
||||
state_version = state_version + 1,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1`, id)
|
||||
WHERE id = $1`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -339,12 +393,14 @@ func (r *ScreenSessionsRepo) SetSound(ctx context.Context, id int, enabled bool)
|
||||
}
|
||||
|
||||
// InitializeScreen copies a live display or uses the plan for the first screen.
|
||||
func (r *ScreenSessionsRepo) InitializeScreen(ctx context.Context, id, gameID int, mode int32, text, label string, duration int64) error {
|
||||
func (r *ScreenSessionsRepo) InitializeScreen(ctx context.Context, id, gameID int, mode int32, text, label string, duration int64, qrURL string, timerType int32) error {
|
||||
_, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE screen_sessions target
|
||||
SET mode = COALESCE(source.mode, $3),
|
||||
text = COALESCE(source.text, $4),
|
||||
qr_url = COALESCE(source.qr_url, $7),
|
||||
timer_type = COALESCE(source.timer_type, $8),
|
||||
command = COALESCE(source.command, 0),
|
||||
position_ms = COALESCE(source.position_ms, 0),
|
||||
timer_label = COALESCE(source.timer_label, $5),
|
||||
@@ -365,7 +421,9 @@ func (r *ScreenSessionsRepo) InitializeScreen(ctx context.Context, id, gameID in
|
||||
timer_duration_seconds,
|
||||
timer_remaining_seconds,
|
||||
timer_state,
|
||||
timer_ends_at
|
||||
timer_ends_at,
|
||||
qr_url,
|
||||
timer_type
|
||||
FROM screen_sessions
|
||||
WHERE game_id = $2 AND id <> $1
|
||||
ORDER BY updated_at DESC, id DESC
|
||||
@@ -378,6 +436,27 @@ func (r *ScreenSessionsRepo) InitializeScreen(ctx context.Context, id, gameID in
|
||||
text,
|
||||
label,
|
||||
duration,
|
||||
qrURL,
|
||||
timerType,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetQR updates QR content and mode together.
|
||||
func (r *ScreenSessionsRepo) SetQR(ctx context.Context, gameID int, link, text string) error {
|
||||
_, err := r.pool.Exec(
|
||||
ctx,
|
||||
`UPDATE screen_sessions
|
||||
SET mode = 9,
|
||||
qr_url = $2,
|
||||
text = $3,
|
||||
command = 0,
|
||||
state_version = state_version + 1,
|
||||
updated_at = NOW()
|
||||
WHERE game_id = $1`,
|
||||
gameID,
|
||||
link,
|
||||
text,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ func mapPlan(plan *game_screen_plans_repo.Plan) *Plan {
|
||||
for _, step := range plan.Steps {
|
||||
steps = append(steps, Step{
|
||||
Mode: step.Mode,
|
||||
QRURL: step.QRURL,
|
||||
TimerType: step.TimerType,
|
||||
Text: step.Text,
|
||||
TimerLabel: step.TimerLabel,
|
||||
TimerDurationSeconds: step.TimerDurationSeconds,
|
||||
@@ -20,6 +22,8 @@ func mapSteps(steps []Step) []game_screen_plans_repo.Step {
|
||||
for _, step := range steps {
|
||||
mapped = append(mapped, game_screen_plans_repo.Step{
|
||||
Mode: step.Mode,
|
||||
QRURL: step.QRURL,
|
||||
TimerType: step.TimerType,
|
||||
Text: step.Text,
|
||||
TimerLabel: step.TimerLabel,
|
||||
TimerDurationSeconds: step.TimerDurationSeconds,
|
||||
|
||||
@@ -21,6 +21,8 @@ var (
|
||||
|
||||
// Step is a screen plan step used by the application layer.
|
||||
type Step struct {
|
||||
QRURL string
|
||||
TimerType int32
|
||||
Mode int32
|
||||
Text string
|
||||
TimerLabel string
|
||||
@@ -103,13 +105,13 @@ func (s *ScreenPlanService) SetStep(ctx context.Context, gameID, currentStep int
|
||||
func (s *ScreenPlanService) ApplyCurrent(ctx context.Context, gameID, screenID int) error {
|
||||
plan, err := s.Get(ctx, gameID)
|
||||
if errors.Is(err, ErrPlanNotFound) {
|
||||
return s.screenService.InitializeScreen(ctx, screenID, gameID, 1, "", "Перерыв", 600)
|
||||
return s.screenService.InitializeScreen(ctx, screenID, gameID, 1, "", "Перерыв", 600, "", 0)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
step := plan.Steps[plan.CurrentStep]
|
||||
return s.screenService.InitializeScreen(ctx, screenID, gameID, step.Mode, step.Text, step.TimerLabel, step.TimerDurationSeconds)
|
||||
return s.screenService.InitializeScreen(ctx, screenID, gameID, step.Mode, step.Text, step.TimerLabel, step.TimerDurationSeconds, step.QRURL, step.TimerType)
|
||||
}
|
||||
|
||||
func normalizePlan(steps []Step, currentStep int) ([]Step, error) {
|
||||
@@ -126,12 +128,18 @@ func normalizePlan(steps []Step, currentStep int) ([]Step, error) {
|
||||
if step.Mode == 8 && (step.Text == "" || len(step.Text) > 10000) {
|
||||
return nil, ErrInvalidPlan
|
||||
}
|
||||
if step.Mode == 9 && !screen_service.ValidQR(step.QRURL, step.Text) {
|
||||
return nil, ErrInvalidPlan
|
||||
}
|
||||
if !screen_service.ValidTimerType(step.TimerType) {
|
||||
return nil, ErrInvalidPlan
|
||||
}
|
||||
if step.Mode == 7 {
|
||||
if step.TimerDurationSeconds <= 0 || step.TimerDurationSeconds > 24*60*60 {
|
||||
return nil, ErrInvalidPlan
|
||||
}
|
||||
if step.TimerLabel == "" {
|
||||
step.TimerLabel = "Перерыв"
|
||||
step.TimerLabel = screen_service.TimerLabel(step.TimerType)
|
||||
}
|
||||
if len(step.TimerLabel) > 200 {
|
||||
return nil, ErrInvalidPlan
|
||||
@@ -144,11 +152,14 @@ func normalizePlan(steps []Step, currentStep int) ([]Step, error) {
|
||||
|
||||
func (s *ScreenPlanService) applyStep(ctx context.Context, gameID int, plan *Plan) error {
|
||||
step := plan.Steps[plan.CurrentStep]
|
||||
if step.Mode == 9 {
|
||||
return s.screenService.SetQR(ctx, gameID, step.QRURL, step.Text)
|
||||
}
|
||||
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.SetTimer(ctx, gameID, step.TimerDurationSeconds, step.TimerLabel, step.TimerType)
|
||||
}
|
||||
return s.screenService.SetMode(ctx, gameID, step.Mode)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package screen_plan_service
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"evening_detective_server/internal/repos/game_screen_plans_repo"
|
||||
)
|
||||
|
||||
func TestNormalizePlan(t *testing.T) {
|
||||
@@ -52,3 +54,38 @@ func TestNormalizePlan(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeNewScreens(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
step Step
|
||||
wantLabel string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "QR", step: Step{Mode: 9, QRURL: "https://example.org", Text: ""}},
|
||||
{name: "teams", step: Step{Mode: 10}},
|
||||
{name: "invalid QR", step: Step{Mode: 9, QRURL: "javascript:alert(1)"}, wantErr: true},
|
||||
{name: "before game", step: Step{Mode: 7, TimerType: 1, TimerDurationSeconds: 60}, wantLabel: "До начала игры"},
|
||||
{name: "until end", step: Step{Mode: 7, TimerType: 2, TimerDurationSeconds: 60}, wantLabel: "До конца игры"},
|
||||
{name: "custom legacy", step: Step{Mode: 7, TimerLabel: "Обсуждение", TimerDurationSeconds: 60}, wantLabel: "Обсуждение"},
|
||||
{name: "invalid type", step: Step{Mode: 7, TimerType: 3, TimerDurationSeconds: 60}, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := normalizePlan([]Step{tt.step}, 0)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Fatalf("normalizePlan() error: %v", err)
|
||||
}
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
if result[0].TimerLabel != tt.wantLabel {
|
||||
t.Fatalf("label = %q", result[0].TimerLabel)
|
||||
}
|
||||
roundTrip := mapPlan(&game_screen_plans_repo.Plan{Steps: mapSteps(result)})
|
||||
if roundTrip.Steps[0] != result[0] {
|
||||
t.Fatal("lost step fields")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ type ScreenSession struct {
|
||||
TimerRemainingSeconds int64
|
||||
TimerState int32
|
||||
TimerEndsAt *time.Time
|
||||
QRURL string
|
||||
TimerType int32
|
||||
Text string
|
||||
}
|
||||
|
||||
@@ -43,5 +45,7 @@ func mapSession(session *screen_sessions_repo.ScreenSession) *ScreenSession {
|
||||
TimerState: session.TimerState,
|
||||
TimerEndsAt: session.TimerEndsAt,
|
||||
Text: session.Text,
|
||||
QRURL: session.QRURL,
|
||||
TimerType: session.TimerType,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package screen_service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"evening_detective_server/internal/repos/screen_sessions_repo"
|
||||
)
|
||||
@@ -53,7 +55,7 @@ func (s *ScreenService) ListByGame(ctx context.Context, gameID int) ([]*ScreenSe
|
||||
// ValidMode reports whether mode is a supported display mode.
|
||||
func ValidMode(mode int32) bool {
|
||||
switch mode {
|
||||
case 1, 2, 4, 6, 7, 8:
|
||||
case 1, 2, 4, 6, 7, 8, 9, 10:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -69,8 +71,11 @@ func (s *ScreenService) SetMode(ctx context.Context, gameID int, mode int32) err
|
||||
}
|
||||
|
||||
// SetTimer updates the display timer for a game.
|
||||
func (s *ScreenService) SetTimer(ctx context.Context, gameID int, duration int64, label string) error {
|
||||
return s.repo.SetTimer(ctx, gameID, duration, label)
|
||||
func (s *ScreenService) SetTimer(ctx context.Context, gameID int, duration int64, label string, timerType int32) error {
|
||||
if !ValidTimerType(timerType) {
|
||||
return ErrInvalidMode
|
||||
}
|
||||
return s.repo.SetTimer(ctx, gameID, duration, label, timerType)
|
||||
}
|
||||
|
||||
// SetText updates the text screen for a game.
|
||||
@@ -112,6 +117,42 @@ func mapError(err error) error {
|
||||
}
|
||||
|
||||
// InitializeScreen joins the current display without resetting other screens.
|
||||
func (s *ScreenService) InitializeScreen(ctx context.Context, id, gameID int, mode int32, text, label string, duration int64) error {
|
||||
return s.repo.InitializeScreen(ctx, id, gameID, mode, text, label, duration)
|
||||
func (s *ScreenService) InitializeScreen(ctx context.Context, id, gameID int, mode int32, text, label string, duration int64, qrURL string, timerType int32) error {
|
||||
// Non-timer steps have no duration, but every state includes a valid timer.
|
||||
if mode != 7 && duration == 0 {
|
||||
duration = 600
|
||||
}
|
||||
if label == "" {
|
||||
label = TimerLabel(timerType)
|
||||
}
|
||||
return s.repo.InitializeScreen(ctx, id, gameID, mode, text, label, duration, qrURL, timerType)
|
||||
}
|
||||
|
||||
// ValidQR validates a URL and caption without fetching the URL.
|
||||
func ValidQR(link, text string) bool {
|
||||
u, err := url.Parse(link)
|
||||
return err == nil && (u.Scheme == "https" || u.Scheme == "http") && u.Hostname() != "" && u.User == nil && len(link) <= 2048 && len(text) <= 10000 && strings.TrimSpace(link) == link
|
||||
}
|
||||
|
||||
// ValidTimerType reports whether the timer category is supported.
|
||||
func ValidTimerType(kind int32) bool { return kind >= 0 && kind <= 2 }
|
||||
|
||||
// TimerLabel returns the initial caption for a timer category.
|
||||
func TimerLabel(kind int32) string {
|
||||
switch kind {
|
||||
case 1:
|
||||
return "До начала игры"
|
||||
case 2:
|
||||
return "До конца игры"
|
||||
default:
|
||||
return "Перерыв"
|
||||
}
|
||||
}
|
||||
|
||||
// SetQR updates the QR screen for a game.
|
||||
func (s *ScreenService) SetQR(ctx context.Context, gameID int, link, text string) error {
|
||||
if !ValidQR(link, text) {
|
||||
return ErrInvalidMode
|
||||
}
|
||||
return s.repo.SetQR(ctx, gameID, link, text)
|
||||
}
|
||||
|
||||
@@ -3,14 +3,38 @@ package screen_service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetModeRejectsUnsupportedModes(t *testing.T) {
|
||||
s := NewScreenService(nil)
|
||||
for _, mode := range []int32{-1, 0, 3, 5, 9} {
|
||||
for _, mode := range []int32{-1, 0, 3, 5, 11} {
|
||||
if err := s.SetMode(context.Background(), 1, mode); !errors.Is(err, ErrInvalidMode) {
|
||||
t.Errorf("SetMode(%d) = %v, want ErrInvalidMode", mode, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidQR(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, link, caption string
|
||||
valid bool
|
||||
}{
|
||||
{"link without caption", "https://example.org/join?q=1#join", "", true},
|
||||
{"caption", "http://localhost:8080", "Команды", true},
|
||||
{"missing link", "", "Текст", false},
|
||||
{"script", "javascript:alert(1)", "", false},
|
||||
{"missing host", "https:///path", "", false},
|
||||
{"credentials", "https://user:secret@example.org", "", false},
|
||||
{"long link", "https://example.org/" + strings.Repeat("x", 2048), "", false},
|
||||
{"long caption", "https://example.org", strings.Repeat("я", 5001), false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ValidQR(tt.link, tt.caption); got != tt.valid {
|
||||
t.Fatalf("ValidQR() = %v", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,3 +210,8 @@ func (s *TeamService) CheckPresence(ctx context.Context, teamID string, password
|
||||
}
|
||||
return team.GameID, nil
|
||||
}
|
||||
|
||||
// GetScreenTeams returns active registrations for the public screen.
|
||||
func (s *TeamService) GetScreenTeams(ctx context.Context, gameID int) ([]*repos.Team, error) {
|
||||
return s.teamsRepo.GetActiveTeamsByGameID(ctx, gameID)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE screen_sessions
|
||||
ADD COLUMN qr_url TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN timer_type INTEGER NOT NULL DEFAULT 0 CHECK (timer_type BETWEEN 0 AND 2);
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE screen_sessions DROP COLUMN qr_url, DROP COLUMN timer_type;
|
||||
+1214
-918
File diff suppressed because it is too large
Load Diff
@@ -3248,6 +3248,33 @@ func local_request_EveningDetectiveServer_SetScreenPlanStep_0(ctx context.Contex
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_GetScreenTeams_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetScreenTeamsReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.GetScreenTeams(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func local_request_EveningDetectiveServer_GetScreenTeams_0(ctx context.Context, marshaler runtime.Marshaler, server EveningDetectiveServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetScreenTeamsReq
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
msg, err := server.GetScreenTeams(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
}
|
||||
|
||||
func request_EveningDetectiveServer_GetScreenResults_0(ctx context.Context, marshaler runtime.Marshaler, client EveningDetectiveServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var (
|
||||
protoReq GetScreenResultsReq
|
||||
@@ -6262,6 +6289,26 @@ func RegisterEveningDetectiveServerHandlerServer(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_SetScreenPlanStep_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPost, pattern_EveningDetectiveServer_GetScreenTeams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetScreenTeams", runtime.WithHTTPPathPattern("/api/screens/teams"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_EveningDetectiveServer_GetScreenTeams_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetScreenTeams_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPost, pattern_EveningDetectiveServer_GetScreenResults_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@@ -8364,6 +8411,23 @@ func RegisterEveningDetectiveServerHandlerClient(ctx context.Context, mux *runti
|
||||
}
|
||||
forward_EveningDetectiveServer_SetScreenPlanStep_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPost, pattern_EveningDetectiveServer_GetScreenTeams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.evening_detective_server.EveningDetectiveServer/GetScreenTeams", runtime.WithHTTPPathPattern("/api/screens/teams"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_EveningDetectiveServer_GetScreenTeams_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
forward_EveningDetectiveServer_GetScreenTeams_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
})
|
||||
mux.Handle(http.MethodPost, pattern_EveningDetectiveServer_GetScreenResults_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@@ -8964,6 +9028,7 @@ var (
|
||||
pattern_EveningDetectiveServer_GetScreenPlan_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "games", "gameID", "screen-plan"}, ""))
|
||||
pattern_EveningDetectiveServer_SaveScreenPlan_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "games", "gameID", "screen-plan"}, ""))
|
||||
pattern_EveningDetectiveServer_SetScreenPlanStep_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 2, 4}, []string{"api", "games", "gameID", "screen-plan", "step"}, ""))
|
||||
pattern_EveningDetectiveServer_GetScreenTeams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "screens", "teams"}, ""))
|
||||
pattern_EveningDetectiveServer_GetScreenResults_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "screens", "results"}, ""))
|
||||
pattern_EveningDetectiveServer_DownloadGamePrintMaterials_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"api", "games", "ID", "print-materials"}, ""))
|
||||
pattern_EveningDetectiveServer_PreviewTeamReceipt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"api", "games", "gameID", "teams", "teamID", "receipt.png"}, ""))
|
||||
@@ -9083,6 +9148,7 @@ var (
|
||||
forward_EveningDetectiveServer_GetScreenPlan_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_SaveScreenPlan_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_SetScreenPlanStep_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetScreenTeams_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_GetScreenResults_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_DownloadGamePrintMaterials_0 = runtime.ForwardResponseMessage
|
||||
forward_EveningDetectiveServer_PreviewTeamReceipt_0 = runtime.ForwardResponseMessage
|
||||
|
||||
@@ -112,6 +112,7 @@ const (
|
||||
EveningDetectiveServer_GetScreenPlan_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetScreenPlan"
|
||||
EveningDetectiveServer_SaveScreenPlan_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/SaveScreenPlan"
|
||||
EveningDetectiveServer_SetScreenPlanStep_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/SetScreenPlanStep"
|
||||
EveningDetectiveServer_GetScreenTeams_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetScreenTeams"
|
||||
EveningDetectiveServer_GetScreenResults_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/GetScreenResults"
|
||||
EveningDetectiveServer_DownloadGamePrintMaterials_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/DownloadGamePrintMaterials"
|
||||
EveningDetectiveServer_PreviewTeamReceipt_FullMethodName = "/crabs.evening_detective_server.EveningDetectiveServer/PreviewTeamReceipt"
|
||||
@@ -248,6 +249,7 @@ type EveningDetectiveServerClient interface {
|
||||
GetScreenPlan(ctx context.Context, in *GetScreenPlanReq, opts ...grpc.CallOption) (*GetScreenPlanRsp, error)
|
||||
SaveScreenPlan(ctx context.Context, in *SaveScreenPlanReq, opts ...grpc.CallOption) (*SaveScreenPlanRsp, error)
|
||||
SetScreenPlanStep(ctx context.Context, in *SetScreenPlanStepReq, opts ...grpc.CallOption) (*SetScreenPlanStepRsp, error)
|
||||
GetScreenTeams(ctx context.Context, in *GetScreenTeamsReq, opts ...grpc.CallOption) (*GetScreenTeamsRsp, error)
|
||||
GetScreenResults(ctx context.Context, in *GetScreenResultsReq, opts ...grpc.CallOption) (*GetScreenResultsRsp, error)
|
||||
DownloadGamePrintMaterials(ctx context.Context, in *GetGameReq, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
|
||||
PreviewTeamReceipt(ctx context.Context, in *TeamReceiptReq, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
|
||||
@@ -1148,6 +1150,16 @@ func (c *eveningDetectiveServerClient) SetScreenPlanStep(ctx context.Context, in
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) GetScreenTeams(ctx context.Context, in *GetScreenTeamsReq, opts ...grpc.CallOption) (*GetScreenTeamsRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetScreenTeamsRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetectiveServer_GetScreenTeams_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveServerClient) GetScreenResults(ctx context.Context, in *GetScreenResultsReq, opts ...grpc.CallOption) (*GetScreenResultsRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetScreenResultsRsp)
|
||||
@@ -1552,6 +1564,7 @@ type EveningDetectiveServerServer interface {
|
||||
GetScreenPlan(context.Context, *GetScreenPlanReq) (*GetScreenPlanRsp, error)
|
||||
SaveScreenPlan(context.Context, *SaveScreenPlanReq) (*SaveScreenPlanRsp, error)
|
||||
SetScreenPlanStep(context.Context, *SetScreenPlanStepReq) (*SetScreenPlanStepRsp, error)
|
||||
GetScreenTeams(context.Context, *GetScreenTeamsReq) (*GetScreenTeamsRsp, error)
|
||||
GetScreenResults(context.Context, *GetScreenResultsReq) (*GetScreenResultsRsp, error)
|
||||
DownloadGamePrintMaterials(context.Context, *GetGameReq) (*httpbody.HttpBody, error)
|
||||
PreviewTeamReceipt(context.Context, *TeamReceiptReq) (*httpbody.HttpBody, error)
|
||||
@@ -1850,6 +1863,9 @@ func (UnimplementedEveningDetectiveServerServer) SaveScreenPlan(context.Context,
|
||||
func (UnimplementedEveningDetectiveServerServer) SetScreenPlanStep(context.Context, *SetScreenPlanStepReq) (*SetScreenPlanStepRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method SetScreenPlanStep not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) GetScreenTeams(context.Context, *GetScreenTeamsReq) (*GetScreenTeamsRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetScreenTeams not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServerServer) GetScreenResults(context.Context, *GetScreenResultsReq) (*GetScreenResultsRsp, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetScreenResults not implemented")
|
||||
}
|
||||
@@ -3510,6 +3526,24 @@ func _EveningDetectiveServer_SetScreenPlanStep_Handler(srv interface{}, ctx cont
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_GetScreenTeams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetScreenTeamsReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServerServer).GetScreenTeams(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetectiveServer_GetScreenTeams_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServerServer).GetScreenTeams(ctx, req.(*GetScreenTeamsReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetectiveServer_GetScreenResults_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetScreenResultsReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -4401,6 +4435,10 @@ var EveningDetectiveServer_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "SetScreenPlanStep",
|
||||
Handler: _EveningDetectiveServer_SetScreenPlanStep_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetScreenTeams",
|
||||
Handler: _EveningDetectiveServer_GetScreenTeams_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetScreenResults",
|
||||
Handler: _EveningDetectiveServer_GetScreenResults_Handler,
|
||||
|
||||
Reference in New Issue
Block a user