This commit is contained in:
2026-10-03 02:38:17 +07:00
parent 1375ec9218
commit 9ab0a76e98
10 changed files with 473 additions and 1437 deletions
+56 -130
View File
@@ -47,7 +47,7 @@ service EveningDetectiveServer {
security : {};
tags : "Подтверждение участия";
summary : "Получить состояние участия";
description: "Возвращает команду, игру и текущее решение по персональному токену из письма. Авторизация JWT не требуется. Чтение не меняет статус участия. Токен передаётся в теле запроса. Недействительная ссылка и ошибки обработки возвращаются в поле error при HTTP 200.";
description: "Возвращает команду, игру и текущее решение по персональному токену из письма. Авторизация JWT не требуется. Чтение не меняет статус участия. Токен передаётся в теле запроса. Недействительная ссылка и ошибки обработки возвращаются стандартным gRPC status error.";
};
}
rpc AnswerParticipation(AnswerParticipationReq) returns (ParticipationRsp) {
@@ -56,7 +56,7 @@ service EveningDetectiveServer {
security : {};
tags : "Подтверждение участия";
summary : "Подтвердить или отменить участие";
description: "Принимает окончательный ответ капитана: confirmed или cancelled. Авторизация JWT не требуется; доступ определяется персональным токеном. Первый ответ сохраняется, повтор того же ответа успешен, противоположный ответ отклоняется. Новый ответ принимается только до планового начала игры и пока игра имеет статус draft. Ошибки ссылки, значения answer, закрытого приёма или изменения решения возвращаются в поле error при HTTP 200; при уже принятом решении возвращается его текущее состояние.";
description: "Принимает окончательный ответ капитана: confirmed или cancelled. Авторизация JWT не требуется; доступ определяется персональным токеном. Первый ответ сохраняется, повтор того же ответа успешен, противоположный ответ отклоняется. Новый ответ принимается только до планового начала игры и пока игра имеет статус draft. Ошибки ссылки, значения answer, закрытого приёма или изменения решения возвращаются стандартным gRPC status error; при уже принятом решении возвращается его текущее состояние.";
};
}
rpc Ping(PingReq) returns (PingRsp) {
@@ -1003,7 +1003,6 @@ message SignupReq {
}
message SignupRsp {
string error = 1;
}
message RefreshPasswordReq {
@@ -1011,7 +1010,6 @@ message RefreshPasswordReq {
}
message RefreshPasswordRsp {
string error = 1;
}
message LoginReq {
@@ -1020,9 +1018,8 @@ message LoginReq {
}
message LoginRsp {
string error = 1;
string accessToken = 2;
string refreshToken = 3;
string accessToken = 1;
string refreshToken = 2;
}
message RefreshReq {
@@ -1030,9 +1027,8 @@ message RefreshReq {
}
message RefreshRsp {
string error = 1;
string accessToken = 2;
string refreshToken = 3;
string accessToken = 1;
string refreshToken = 2;
}
// Пароль подтверждает, что удаляет аккаунт его владелец, а не обладатель
@@ -1042,7 +1038,6 @@ message RefreshRsp {
message DeleteAccountReq {}
message DeleteAccountRsp {
string error = 1;
}
// Документы раздаются по постоянным URL, на которые ссылается сервис при
@@ -1050,29 +1045,25 @@ message DeleteAccountRsp {
message GetTermsReq {}
message GetTermsRsp {
string error = 1;
string text = 2;
string text = 1;
}
message GetPrivacyReq {}
message GetPrivacyRsp {
string error = 1;
string text = 2;
string text = 1;
}
message GetConsentReq {}
message GetConsentRsp {
string error = 1;
string text = 2;
string text = 1;
}
message GetUsersReq {}
message GetUsersRsp {
string error = 1;
repeated User users = 2;
repeated User users = 1;
}
message User {
@@ -1089,15 +1080,13 @@ message GetUserByIDReq {
}
message GetUserByIDRsp {
string error = 1;
User user = 2;
User user = 1;
}
message GetMeReq {}
message GetMeRsp {
string error = 1;
User user = 2;
User user = 1;
}
message ChangeUserPasswordReq {
@@ -1113,7 +1102,6 @@ message AddUserRoleReq {
}
message AddUserRoleRsp {
string error = 1;
}
message DeleteUserRoleReq {
@@ -1122,14 +1110,12 @@ message DeleteUserRoleReq {
}
message DeleteUserRoleRsp {
string error = 1;
}
message GetPermissionsReq {}
message GetPermissionsRsp {
string error = 1;
repeated string permissions = 2;
repeated string permissions = 1;
}
message UploadFileReq {
@@ -1138,11 +1124,10 @@ message UploadFileReq {
}
message UploadFileRsp {
string error = 1;
string filename = 2;
string filename = 1;
// Тип загруженного файла: image | pdf | audio | video (определяется сервером по
// содержимому). Удобен редактору для предзаполнения file_type улики.
string file_type = 3 [json_name = "file_type"];
string file_type = 2 [json_name = "file_type"];
}
message DownloadFileReq {
@@ -1154,22 +1139,19 @@ message AddScenarioReq {
}
message AddScenarioRsp {
string error = 1;
int32 ID = 2;
int32 ID = 1;
}
message GetMyScenariosReq {}
message GetMyScenariosRsp {
string error = 1;
repeated Scenario scenarios = 2;
repeated Scenario scenarios = 1;
}
message GetScenariosCatalogReq {}
message GetScenariosCatalogRsp {
string error = 1;
repeated Scenario scenarios = 2;
repeated Scenario scenarios = 1;
}
message GetScenarioReq {
@@ -1177,8 +1159,7 @@ message GetScenarioReq {
}
message GetScenarioRsp {
string error = 1;
Scenario scenario = 2;
Scenario scenario = 1;
}
message Scenario {
@@ -1289,7 +1270,6 @@ message UpdateScenarioReq {
}
message UpdateScenarioRsp {
string error = 1;
}
message PublicScenarioReq {
@@ -1297,7 +1277,6 @@ message PublicScenarioReq {
}
message PublicScenarioRsp {
string error = 1;
}
message DraftScenarioReq {
@@ -1305,7 +1284,6 @@ message DraftScenarioReq {
}
message DraftScenarioRsp {
string error = 1;
}
message DeleteScenarioReq {
@@ -1313,7 +1291,6 @@ message DeleteScenarioReq {
}
message DeleteScenarioRsp {
string error = 1;
}
message AddScenarioPlaceReq {
@@ -1322,7 +1299,6 @@ message AddScenarioPlaceReq {
}
message AddScenarioPlaceRsp {
string error = 1;
}
message UpdateScenarioPlaceReq {
@@ -1332,7 +1308,6 @@ message UpdateScenarioPlaceReq {
}
message UpdateScenarioPlaceRsp {
string error = 1;
}
message DeleteScenarioPlaceReq {
@@ -1341,7 +1316,6 @@ message DeleteScenarioPlaceReq {
}
message DeleteScenarioPlaceRsp {
string error = 1;
}
message UpdateScenarioConclusionReq {
@@ -1350,7 +1324,6 @@ message UpdateScenarioConclusionReq {
}
message UpdateScenarioConclusionRsp {
string error = 1;
}
message UpdateScenarioIntroReq {
@@ -1359,7 +1332,6 @@ message UpdateScenarioIntroReq {
}
message UpdateScenarioIntroRsp {
string error = 1;
}
message AddScenarioQuestionReq {
@@ -1368,7 +1340,6 @@ message AddScenarioQuestionReq {
}
message AddScenarioQuestionRsp {
string error = 1;
}
message UpdateScenarioQuestionReq {
@@ -1378,7 +1349,6 @@ message UpdateScenarioQuestionReq {
}
message UpdateScenarioQuestionRsp {
string error = 1;
}
message DeleteScenarioQuestionReq {
@@ -1387,7 +1357,6 @@ message DeleteScenarioQuestionReq {
}
message DeleteScenarioQuestionRsp {
string error = 1;
}
message DownloadScenarioArchiveReq {
@@ -1395,8 +1364,7 @@ message DownloadScenarioArchiveReq {
}
message UploadScenarioArchiveRsp {
string error = 1;
int32 ID = 2;
int32 ID = 1;
}
message AddGameReq {
@@ -1409,15 +1377,13 @@ message AddGameReq {
}
message AddGameRsp {
string error = 1;
int32 ID = 2;
int32 ID = 1;
}
message GetGamesReq {}
message GetGamesRsp {
string error = 1;
repeated Game games = 2;
repeated Game games = 1;
}
message Game {
@@ -1493,9 +1459,8 @@ message ScreenState {
message CreateScreenSessionReq {}
message CreateScreenSessionRsp {
string error = 1;
string screenID = 2;
string access_token = 3;
string screenID = 1;
string access_token = 2;
}
message GetScreenStateReq {
@@ -1504,8 +1469,7 @@ message GetScreenStateReq {
}
message GetScreenStateRsp {
string error = 1;
ScreenState state = 2;
ScreenState state = 1;
}
message GetGameScreensReq {
@@ -1513,8 +1477,7 @@ message GetGameScreensReq {
}
message GetGameScreensRsp {
string error = 1;
repeated ScreenState screens = 2;
repeated ScreenState screens = 1;
}
message BindScreenReq {
@@ -1524,8 +1487,7 @@ message BindScreenReq {
}
message BindScreenRsp {
string error = 1;
ScreenState state = 2;
ScreenState state = 1;
}
message UnbindScreenReq {
@@ -1533,7 +1495,6 @@ message UnbindScreenReq {
}
message UnbindScreenRsp {
string error = 1;
}
message SetScreenSoundReq {
@@ -1542,7 +1503,6 @@ message SetScreenSoundReq {
}
message SetScreenSoundRsp {
string error = 1;
}
message MapItem {
@@ -1561,8 +1521,7 @@ message GetGameMapReq {
}
message GetGameMapRsp {
string error = 1;
repeated MapItem items = 2;
repeated MapItem items = 1;
}
message UpdateGameMapItemReq {
@@ -1574,7 +1533,7 @@ message UpdateGameMapItemReq {
bool hidden = 6;
}
message UpdateGameMapItemRsp { string error = 1; }
message UpdateGameMapItemRsp {}
message SetScreenModeReq {
int32 gameID = 1;
@@ -1582,7 +1541,6 @@ message SetScreenModeReq {
}
message SetScreenModeRsp {
string error = 1;
}
message SetScreenTimerReq {
@@ -1592,7 +1550,6 @@ message SetScreenTimerReq {
}
message SetScreenTimerRsp {
string error = 1;
}
message SendScreenCommandReq {
@@ -1603,7 +1560,6 @@ message SendScreenCommandReq {
}
message SendScreenCommandRsp {
string error = 1;
}
message GetScreenResultsReq {
@@ -1612,9 +1568,8 @@ message GetScreenResultsReq {
}
message GetScreenResultsRsp {
string error = 1;
repeated ResultsQuestion questions = 2;
repeated TeamResult teams = 3;
repeated ResultsQuestion questions = 1;
repeated TeamResult teams = 2;
}
message GetParticipationReq {
@@ -1630,21 +1585,19 @@ message AnswerParticipationReq {
}
message ParticipationRsp {
// Пустая строка при успехе; иначе сообщение об ошибке для пользователя.
string error = 1;
// Название команды. Данные доступны только по действительному токену.
string team_name = 2;
string team_name = 1;
// Название игры.
string game_name = 3;
string game_name = 2;
// Плановое начало игры, UTC (RFC 3339 в JSON).
google.protobuf.Timestamp start_at = 4;
google.protobuf.Timestamp start_at = 3;
// Заявленное число участников команды.
int32 participants_count = 5;
int32 participants_count = 4;
// pending — ожидает ответа, confirmed — подтверждено, cancelled — отменено.
string status = 6;
string status = 5;
// Новые ответы закрыты: наступило плановое начало или игра вышла из draft.
// Изменение уже принятого решения запрещено независимо от этого поля.
bool closed = 7;
bool closed = 6;
}
message Team {
@@ -1671,8 +1624,7 @@ message GetGameReq {
}
message GetGameRsp {
string error = 1;
Game game = 2;
Game game = 1;
}
message GetGameAnswersReq {
@@ -1680,9 +1632,8 @@ message GetGameAnswersReq {
}
message GetGameAnswersRsp {
string error = 1;
repeated Question questions = 2;
repeated TeamAnswers teams = 3;
repeated Question questions = 1;
repeated TeamAnswers teams = 2;
}
message GetGameResultsReq {
@@ -1711,9 +1662,8 @@ message TeamResult {
}
message GetGameResultsRsp {
string error = 1;
repeated ResultsQuestion questions = 2;
repeated TeamResult teams = 3;
repeated ResultsQuestion questions = 1;
repeated TeamResult teams = 2;
}
message GameReview {
@@ -1737,8 +1687,7 @@ message CreateGameReviewReq {
}
message CreateGameReviewRsp {
string error = 1;
GameReview review = 2;
GameReview review = 1;
}
message GetGameReviewsReq {
@@ -1752,10 +1701,9 @@ message GetScenarioReviewsReq {
message GetReviewsForModerationReq {}
message GetGameReviewsRsp {
string error = 1;
double average_rating = 2;
int32 reviews_count = 3;
repeated GameReview reviews = 4;
double average_rating = 1;
int32 reviews_count = 2;
repeated GameReview reviews = 3;
}
message ModerateGameReviewReq {
@@ -1764,8 +1712,7 @@ message ModerateGameReviewReq {
}
message ModerateGameReviewRsp {
string error = 1;
GameReview review = 2;
GameReview review = 1;
}
message UpdateGameReq {
@@ -1779,7 +1726,6 @@ message UpdateGameReq {
}
message UpdateGameRsp {
string error = 1;
}
message StartGameReq {
@@ -1787,7 +1733,6 @@ message StartGameReq {
}
message StartGameRsp {
string error = 1;
}
message PauseGameReq {
@@ -1795,7 +1740,6 @@ message PauseGameReq {
}
message PauseGameRsp {
string error = 1;
}
message PlayGameReq {
@@ -1803,7 +1747,6 @@ message PlayGameReq {
}
message PlayGameRsp {
string error = 1;
}
message FinishGameReq {
@@ -1811,7 +1754,6 @@ message FinishGameReq {
}
message FinishGameRsp {
string error = 1;
}
message ResetGameReq {
@@ -1819,7 +1761,6 @@ message ResetGameReq {
}
message ResetGameRsp {
string error = 1;
}
message DeleteGameReq {
@@ -1827,7 +1768,6 @@ message DeleteGameReq {
}
message DeleteGameRsp {
string error = 1;
}
message AddTeamReq {
@@ -1837,9 +1777,8 @@ message AddTeamReq {
}
message AddTeamRsp {
string error = 1;
int32 ID = 2;
string password = 3;
int32 ID = 1;
string password = 2;
}
message UpdateTeamReq {
@@ -1848,7 +1787,6 @@ message UpdateTeamReq {
}
message UpdateTeamRsp {
string error = 1;
}
message DeleteTeamReq {
@@ -1856,7 +1794,6 @@ message DeleteTeamReq {
}
message DeleteTeamRsp {
string error = 1;
}
message GiveTeamApplicationsReq {
@@ -1865,7 +1802,6 @@ message GiveTeamApplicationsReq {
}
message GiveTeamApplicationsRsp {
string error = 1;
}
message GetTeamStoryReq {
@@ -1874,10 +1810,9 @@ message GetTeamStoryReq {
}
message GetTeamStoryRsp {
string error = 1;
Story story = 2;
Game game = 3;
repeated TeamAction actions = 4;
Story story = 1;
Game game = 2;
repeated TeamAction actions = 3;
}
message TeamAction {
@@ -1892,7 +1827,6 @@ message AddTeamActionReq {
}
message AddTeamActionRsp {
string error = 1;
}
message DeleteLastTeamActionReq {
@@ -1900,7 +1834,6 @@ message DeleteLastTeamActionReq {
}
message DeleteLastTeamActionRsp {
string error = 1;
}
message GetTeamAnswersReq {
@@ -1909,8 +1842,7 @@ message GetTeamAnswersReq {
}
message GetTeamAnswersRsp {
string error = 1;
repeated Answer answers = 2;
repeated Answer answers = 1;
}
message SubmitTeamAnswersReq {
@@ -1920,7 +1852,6 @@ message SubmitTeamAnswersReq {
}
message SubmitTeamAnswersRsp {
string error = 1;
}
message SetTeamAnswerScoreReq {
@@ -1931,7 +1862,6 @@ message SetTeamAnswerScoreReq {
}
message SetTeamAnswerScoreRsp {
string error = 1;
}
message UpdateScenarioBackgroundReq {
@@ -1940,7 +1870,6 @@ message UpdateScenarioBackgroundReq {
}
message UpdateScenarioBackgroundRsp {
string error = 1;
}
message IssueGameTicketsReq {
@@ -1949,8 +1878,7 @@ message IssueGameTicketsReq {
}
message IssueGameTicketsRsp {
string error = 1;
repeated Ticket tickets = 2;
repeated Ticket tickets = 1;
}
message Ticket {
@@ -1963,8 +1891,7 @@ message GetTicketReq {
}
message GetTicketRsp {
string error = 1;
TicketPage ticket = 2;
TicketPage ticket = 1;
}
message AcceptTicketReq {
@@ -1972,8 +1899,7 @@ message AcceptTicketReq {
}
message AcceptTicketRsp {
string error = 1;
TicketPage ticket = 2;
TicketPage ticket = 1;
}
message TicketPage {
+40 -339
View File
@@ -1235,7 +1235,7 @@
"/api/participation/answer": {
"post": {
"summary": "Подтвердить или отменить участие",
"description": "Принимает окончательный ответ капитана: confirmed или cancelled. Авторизация JWT не требуется; доступ определяется персональным токеном. Первый ответ сохраняется, повтор того же ответа успешен, противоположный ответ отклоняется. Новый ответ принимается только до планового начала игры и пока игра имеет статус draft. Ошибки ссылки, значения answer, закрытого приёма или изменения решения возвращаются в поле error при HTTP 200; при уже принятом решении возвращается его текущее состояние.",
"description": "Принимает окончательный ответ капитана: confirmed или cancelled. Авторизация JWT не требуется; доступ определяется персональным токеном. Первый ответ сохраняется, повтор того же ответа успешен, противоположный ответ отклоняется. Новый ответ принимается только до планового начала игры и пока игра имеет статус draft. Ошибки ссылки, значения answer, закрытого приёма или изменения решения возвращаются стандартным gRPC status error; при уже принятом решении возвращается его текущее состояние.",
"operationId": "EveningDetectiveServer_AnswerParticipation",
"responses": {
"200": {
@@ -1270,7 +1270,7 @@
"/api/participation/get": {
"post": {
"summary": "Получить состояние участия",
"description": "Возвращает команду, игру и текущее решение по персональному токену из письма. Авторизация JWT не требуется. Чтение не меняет статус участия. Токен передаётся в теле запроса. Недействительная ссылка и ошибки обработки возвращаются в поле error при HTTP 200.",
"description": "Возвращает команду, игру и текущее решение по персональному токену из письма. Авторизация JWT не требуется. Чтение не меняет статус участия. Токен передаётся в теле запроса. Недействительная ссылка и ошибки обработки возвращаются стандартным gRPC status error.",
"operationId": "EveningDetectiveServer_GetParticipation",
"responses": {
"200": {
@@ -3376,9 +3376,6 @@
"evening_detective_serverAcceptTicketRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ticket": {
"$ref": "#/definitions/evening_detective_serverTicketPage"
}
@@ -3412,9 +3409,6 @@
"evening_detective_serverAddGameRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ID": {
"type": "integer",
"format": "int32"
@@ -3422,20 +3416,10 @@
}
},
"evening_detective_serverAddScenarioPlaceRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverAddScenarioQuestionRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverAddScenarioReq": {
"type": "object",
@@ -3448,9 +3432,6 @@
"evening_detective_serverAddScenarioRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ID": {
"type": "integer",
"format": "int32"
@@ -3458,12 +3439,7 @@
}
},
"evening_detective_serverAddTeamActionRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverAddTeamReq": {
"type": "object",
@@ -3483,9 +3459,6 @@
"evening_detective_serverAddTeamRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ID": {
"type": "integer",
"format": "int32"
@@ -3496,12 +3469,7 @@
}
},
"evening_detective_serverAddUserRoleRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverAnswer": {
"type": "object",
@@ -3565,9 +3533,6 @@
"evening_detective_serverBindScreenRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"state": {
"$ref": "#/definitions/evening_detective_serverScreenState"
}
@@ -3590,9 +3555,6 @@
"evening_detective_serverCreateGameReviewRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"review": {
"$ref": "#/definitions/evening_detective_serverGameReview"
}
@@ -3604,9 +3566,6 @@
"evening_detective_serverCreateScreenSessionRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"screenID": {
"type": "string"
},
@@ -3616,68 +3575,28 @@
}
},
"evening_detective_serverDeleteAccountRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverDeleteGameRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverDeleteLastTeamActionRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverDeleteScenarioPlaceRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverDeleteScenarioQuestionRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverDeleteScenarioRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverDeleteTeamRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverDeleteUserRoleRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverDoor": {
"type": "object",
@@ -3698,12 +3617,7 @@
}
},
"evening_detective_serverDraftScenarioRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverEchoRsp": {
"type": "object",
@@ -3714,12 +3628,7 @@
}
},
"evening_detective_serverFinishGameRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverGame": {
"type": "object",
@@ -3807,9 +3716,6 @@
"evening_detective_serverGetConsentRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"text": {
"type": "string"
}
@@ -3818,9 +3724,6 @@
"evening_detective_serverGetGameAnswersRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"questions": {
"type": "array",
"items": {
@@ -3840,9 +3743,6 @@
"evening_detective_serverGetGameMapRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"items": {
"type": "array",
"items": {
@@ -3855,9 +3755,6 @@
"evening_detective_serverGetGameResultsRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"questions": {
"type": "array",
"items": {
@@ -3877,9 +3774,6 @@
"evening_detective_serverGetGameReviewsRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"averageRating": {
"type": "number",
"format": "double"
@@ -3900,9 +3794,6 @@
"evening_detective_serverGetGameRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"game": {
"$ref": "#/definitions/evening_detective_serverGame"
}
@@ -3911,9 +3802,6 @@
"evening_detective_serverGetGameScreensRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"screens": {
"type": "array",
"items": {
@@ -3926,9 +3814,6 @@
"evening_detective_serverGetGamesRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"games": {
"type": "array",
"items": {
@@ -3941,9 +3826,6 @@
"evening_detective_serverGetMeRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"user": {
"$ref": "#/definitions/evening_detective_serverUser"
}
@@ -3952,9 +3834,6 @@
"evening_detective_serverGetMyScenariosRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"scenarios": {
"type": "array",
"items": {
@@ -3976,9 +3855,6 @@
"evening_detective_serverGetPermissionsRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"permissions": {
"type": "array",
"items": {
@@ -3990,9 +3866,6 @@
"evening_detective_serverGetPrivacyRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"text": {
"type": "string"
}
@@ -4001,9 +3874,6 @@
"evening_detective_serverGetScenarioRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"scenario": {
"$ref": "#/definitions/evening_detective_serverScenario"
}
@@ -4012,9 +3882,6 @@
"evening_detective_serverGetScenariosCatalogRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"scenarios": {
"type": "array",
"items": {
@@ -4038,9 +3905,6 @@
"evening_detective_serverGetScreenResultsRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"questions": {
"type": "array",
"items": {
@@ -4071,9 +3935,6 @@
"evening_detective_serverGetScreenStateRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"state": {
"$ref": "#/definitions/evening_detective_serverScreenState"
}
@@ -4082,9 +3943,6 @@
"evening_detective_serverGetTeamAnswersRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"answers": {
"type": "array",
"items": {
@@ -4097,9 +3955,6 @@
"evening_detective_serverGetTeamStoryRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"story": {
"$ref": "#/definitions/evening_detective_serverStory"
},
@@ -4118,9 +3973,6 @@
"evening_detective_serverGetTermsRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"text": {
"type": "string"
}
@@ -4129,9 +3981,6 @@
"evening_detective_serverGetTicketRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ticket": {
"$ref": "#/definitions/evening_detective_serverTicketPage"
}
@@ -4140,9 +3989,6 @@
"evening_detective_serverGetUserByIDRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"user": {
"$ref": "#/definitions/evening_detective_serverUser"
}
@@ -4151,9 +3997,6 @@
"evening_detective_serverGetUsersRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"users": {
"type": "array",
"items": {
@@ -4164,12 +4007,7 @@
}
},
"evening_detective_serverGiveTeamApplicationsRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverIntroduction": {
"type": "object",
@@ -4185,9 +4023,6 @@
"evening_detective_serverIssueGameTicketsRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"tickets": {
"type": "array",
"items": {
@@ -4219,9 +4054,6 @@
"evening_detective_serverLoginRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"accessToken": {
"type": "string"
},
@@ -4265,9 +4097,6 @@
"evening_detective_serverModerateGameReviewRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"review": {
"$ref": "#/definitions/evening_detective_serverGameReview"
}
@@ -4276,10 +4105,6 @@
"evening_detective_serverParticipationRsp": {
"type": "object",
"properties": {
"error": {
"type": "string",
"description": "Пустая строка при успехе; иначе сообщение об ошибке для пользователя."
},
"teamName": {
"type": "string",
"description": "Название команды. Данные доступны только по действительному токену."
@@ -4309,12 +4134,7 @@
}
},
"evening_detective_serverPauseGameRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverPingRsp": {
"type": "object"
@@ -4361,20 +4181,10 @@
}
},
"evening_detective_serverPlayGameRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverPublicScenarioRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverQuestion": {
"type": "object",
@@ -4403,12 +4213,7 @@
}
},
"evening_detective_serverRefreshPasswordRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverRefreshReq": {
"type": "object",
@@ -4421,9 +4226,6 @@
"evening_detective_serverRefreshRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"accessToken": {
"type": "string"
},
@@ -4433,12 +4235,7 @@
}
},
"evening_detective_serverResetGameRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverResultsQuestion": {
"type": "object",
@@ -4611,44 +4408,19 @@
"default": "SCREEN_TIMER_STATE_UNSPECIFIED"
},
"evening_detective_serverSendScreenCommandRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverSetScreenModeRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverSetScreenSoundRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverSetScreenTimerRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverSetTeamAnswerScoreRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverSignupReq": {
"type": "object",
@@ -4668,20 +4440,10 @@
}
},
"evening_detective_serverSignupRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverStartGameRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverStory": {
"type": "object",
@@ -4713,12 +4475,7 @@
}
},
"evening_detective_serverSubmitTeamAnswersRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverTeam": {
"type": "object",
@@ -4875,84 +4632,34 @@
}
},
"evening_detective_serverUnbindScreenRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUpdateGameMapItemRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUpdateGameRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUpdateScenarioBackgroundRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUpdateScenarioConclusionRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUpdateScenarioIntroRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUpdateScenarioPlaceRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUpdateScenarioQuestionRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUpdateScenarioRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUpdateTeamRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
"type": "object"
},
"evening_detective_serverUploadFileReq": {
"type": "object",
@@ -4969,9 +4676,6 @@
"evening_detective_serverUploadFileRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"filename": {
"type": "string"
},
@@ -4984,9 +4688,6 @@
"evening_detective_serverUploadScenarioArchiveRsp": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ID": {
"type": "integer",
"format": "int32"
-3
View File
@@ -131,9 +131,6 @@ func TestUploadFileSuccess(t *testing.T) {
if rsp.FileType != "pdf" {
t.Errorf("FileType = %q, want pdf", rsp.FileType)
}
if rsp.Error != "" {
t.Errorf("Error = %q, want пусто", rsp.Error)
}
}
// mockServerTransportStream — заглушка для grpc.SetHeader в юнит-тестах.
+5 -3
View File
@@ -5,6 +5,8 @@ import (
"evening_detective_server/docs"
proto "evening_detective_server/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// GetTerms возвращает текст Пользовательского соглашения (постоянный URL
@@ -12,7 +14,7 @@ import (
func (s *server) GetTerms(_ context.Context, _ *proto.GetTermsReq) (*proto.GetTermsRsp, error) {
text, err := docs.FS.ReadFile("TERMS.md")
if err != nil {
return &proto.GetTermsRsp{Error: "Не удалось получить документ"}, nil
return nil, status.Error(codes.Internal, "не удалось получить документ")
}
return &proto.GetTermsRsp{Text: string(text)}, nil
}
@@ -22,7 +24,7 @@ func (s *server) GetTerms(_ context.Context, _ *proto.GetTermsReq) (*proto.GetTe
func (s *server) GetPrivacy(_ context.Context, _ *proto.GetPrivacyReq) (*proto.GetPrivacyRsp, error) {
text, err := docs.FS.ReadFile("PRIVACY.md")
if err != nil {
return &proto.GetPrivacyRsp{Error: "Не удалось получить документ"}, nil
return nil, status.Error(codes.Internal, "не удалось получить документ")
}
return &proto.GetPrivacyRsp{Text: string(text)}, nil
}
@@ -32,7 +34,7 @@ func (s *server) GetPrivacy(_ context.Context, _ *proto.GetPrivacyReq) (*proto.G
func (s *server) GetConsent(_ context.Context, _ *proto.GetConsentReq) (*proto.GetConsentRsp, error) {
text, err := docs.FS.ReadFile("CONSENT.md")
if err != nil {
return &proto.GetConsentRsp{Error: "Не удалось получить документ"}, nil
return nil, status.Error(codes.Internal, "не удалось получить документ")
}
return &proto.GetConsentRsp{Text: string(text)}, nil
}
+8 -11
View File
@@ -13,39 +13,39 @@ func TestGetLegalDocs(t *testing.T) {
tests := []struct {
name string
call func() (text, errText string)
call func() string
wantPart string
}{
{
name: "GetTerms отдаёт Пользовательское соглашение",
call: func() (string, string) {
call: func() string {
rsp, err := s.GetTerms(context.Background(), &proto.GetTermsReq{})
if err != nil {
t.Fatalf("GetTerms error: %v", err)
}
return rsp.Text, rsp.Error
return rsp.Text
},
wantPart: "СОГЛАШЕНИЕ",
},
{
name: "GetPrivacy отдаёт Политику конфиденциальности",
call: func() (string, string) {
call: func() string {
rsp, err := s.GetPrivacy(context.Background(), &proto.GetPrivacyReq{})
if err != nil {
t.Fatalf("GetPrivacy error: %v", err)
}
return rsp.Text, rsp.Error
return rsp.Text
},
wantPart: "КОНФИДЕНЦИАЛЬНОСТИ",
},
{
name: "GetConsent отдаёт текст согласия на обработку ПДн",
call: func() (string, string) {
call: func() string {
rsp, err := s.GetConsent(context.Background(), &proto.GetConsentReq{})
if err != nil {
t.Fatalf("GetConsent error: %v", err)
}
return rsp.Text, rsp.Error
return rsp.Text
},
wantPart: "СОГЛАСИЕ",
},
@@ -53,10 +53,7 @@ func TestGetLegalDocs(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
text, errText := tt.call()
if errText != "" {
t.Fatalf("ожидался успех, получена ошибка: %s", errText)
}
text := tt.call()
if !strings.Contains(text, tt.wantPart) {
t.Errorf("текст документа не содержит %q", tt.wantPart)
}
+19 -13
View File
@@ -6,7 +6,9 @@ import (
"evening_detective_server/internal/services/participation_service"
"evening_detective_server/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -23,36 +25,40 @@ func mapParticipationResponse(p *participation_service.Participation) *proto.Par
return result
}
func participationError(err error) string {
if errors.Is(err, participation_service.ErrParticipationLink) || errors.Is(err, participation_service.ErrParticipationFinal) || errors.Is(err, participation_service.ErrParticipationClosed) {
return err.Error()
func participationErrorStatus(err error, internalMessage string) error {
switch {
case errors.Is(err, participation_service.ErrParticipationLink):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, participation_service.ErrInvalidAnswer):
return status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, participation_service.ErrParticipationFinal), errors.Is(err, participation_service.ErrParticipationClosed):
return status.Error(codes.FailedPrecondition, err.Error())
default:
return status.Error(codes.Internal, internalMessage)
}
return "Не удалось обработать ответ. Попробуйте позже."
}
func (s *server) GetParticipation(ctx context.Context, req *proto.GetParticipationReq) (*proto.ParticipationRsp, error) {
_ = grpc.SetHeader(ctx, metadata.Pairs("Cache-Control", "no-store"))
if req == nil {
return &proto.ParticipationRsp{Error: participationError(participation_service.ErrParticipationLink)}, nil
return nil, participationErrorStatus(participation_service.ErrParticipationLink, "не удалось получить данные об участии")
}
p, err := s.participationService.GetParticipation(ctx, req.Token)
result := mapParticipationResponse(p)
if err != nil {
result.Error = participationError(err)
return nil, participationErrorStatus(err, "не удалось получить данные об участии")
}
return result, nil
return mapParticipationResponse(p), nil
}
func (s *server) AnswerParticipation(ctx context.Context, req *proto.AnswerParticipationReq) (*proto.ParticipationRsp, error) {
_ = grpc.SetHeader(ctx, metadata.Pairs("Cache-Control", "no-store"))
if req == nil {
return &proto.ParticipationRsp{Error: participationError(participation_service.ErrParticipationLink)}, nil
return nil, participationErrorStatus(participation_service.ErrParticipationLink, "не удалось сохранить ответ")
}
if req.Answer != "confirmed" && req.Answer != "cancelled" {
return &proto.ParticipationRsp{Error: participation_service.ErrInvalidAnswer.Error()}, nil
return nil, participationErrorStatus(participation_service.ErrInvalidAnswer, "не удалось сохранить ответ")
}
p, err := s.participationService.AnswerParticipation(ctx, req.Token, req.Answer)
result := mapParticipationResponse(p)
if err != nil {
result.Error = participationError(err)
return nil, participationErrorStatus(err, "не удалось сохранить ответ")
}
return result, nil
return mapParticipationResponse(p), nil
}
+13 -8
View File
@@ -6,24 +6,29 @@ import (
"testing"
"evening_detective_server/internal/services/participation_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestParticipationError(t *testing.T) {
func TestParticipationErrorStatus(t *testing.T) {
for _, tc := range []struct {
name string
err error
code codes.Code
want string
}{
{"invalid link", participation_service.ErrParticipationLink, "Ссылка недействительна"},
{"closed", participation_service.ErrParticipationClosed, "Приём ответов закрыт"},
{"final", participation_service.ErrParticipationFinal, "Решение уже принято и не может быть изменено"},
{"internal", errors.New("private database detail"), "Не удалось обработать ответ. Попробуйте позже."},
{"invalid link", participation_service.ErrParticipationLink, codes.NotFound, "Ссылка недействительна"},
{"invalid answer", participation_service.ErrInvalidAnswer, codes.InvalidArgument, "Выберите подтверждение или отмену участия"},
{"closed", participation_service.ErrParticipationClosed, codes.FailedPrecondition, "Приём ответов закрыт"},
{"final", participation_service.ErrParticipationFinal, codes.FailedPrecondition, "Решение уже принято и не может быть изменено"},
{"internal", errors.New("private database detail"), codes.Internal, "participation failed"},
} {
t.Run(tc.name, func(t *testing.T) {
if got := participationError(tc.err); got != tc.want {
t.Fatalf("message=%q, want %q", got, tc.want)
got := participationErrorStatus(tc.err, "participation failed")
if status.Code(got) != tc.code || status.Convert(got).Message() != tc.want {
t.Fatalf("status=%v, want %s %q", got, tc.code, tc.want)
}
if strings.Contains(participationError(tc.err), "private database detail") {
if tc.code == codes.Internal && strings.Contains(status.Convert(got).Message(), "private database detail") {
t.Fatal("private error exposed")
}
})
+69
View File
@@ -0,0 +1,69 @@
package app
import (
"context"
"strings"
"testing"
"evening_detective_server/internal/services/participation_service"
"evening_detective_server/internal/services/ticket_service"
"evening_detective_server/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func assertGRPCStatus(t *testing.T, err error, wantCode codes.Code, wantMessage string) {
t.Helper()
if status.Code(err) != wantCode {
t.Fatalf("status code = %v, want %v", status.Code(err), wantCode)
}
if status.Convert(err).Message() != wantMessage {
t.Fatalf("status message = %q, want %q", status.Convert(err).Message(), wantMessage)
}
}
func TestGetTicketNotFoundUsesGRPCStatus(t *testing.T) {
rsp, err := (&server{}).GetTicket(context.Background(), nil)
if rsp != nil {
t.Fatalf("GetTicket response = %v, want nil", rsp)
}
assertGRPCStatus(t, err, codes.NotFound, ticket_service.ErrTicketNotFound.Error())
}
func TestAcceptTicketNilRequestUsesGRPCStatus(t *testing.T) {
rsp, err := (&server{}).AcceptTicket(ctxWithClaims("organizer"), nil)
if rsp != nil {
t.Fatalf("AcceptTicket response = %v, want nil", rsp)
}
assertGRPCStatus(t, err, codes.NotFound, ticket_service.ErrTicketNotFound.Error())
}
func TestGetParticipationInvalidRequestUsesGRPCStatus(t *testing.T) {
rsp, err := (&server{}).GetParticipation(context.Background(), nil)
if rsp != nil {
t.Fatalf("GetParticipation response = %v, want nil", rsp)
}
assertGRPCStatus(t, err, codes.NotFound, participation_service.ErrParticipationLink.Error())
}
func TestAnswerParticipationInvalidAnswerUsesGRPCStatus(t *testing.T) {
rsp, err := (&server{}).AnswerParticipation(context.Background(), &proto.AnswerParticipationReq{Answer: "unknown"})
if rsp != nil {
t.Fatalf("AnswerParticipation response = %v, want nil", rsp)
}
assertGRPCStatus(t, err, codes.InvalidArgument, participation_service.ErrInvalidAnswer.Error())
}
func TestResponseMessagesDoNotExposePayloadError(t *testing.T) {
for i := 0; i < proto.File_main_proto.Messages().Len(); i++ {
message := proto.File_main_proto.Messages().Get(i)
if !strings.HasSuffix(string(message.Name()), "Rsp") {
continue
}
for j := 0; j < message.Fields().Len(); j++ {
if message.Fields().Get(j).Name() == "error" {
t.Errorf("%s still exposes payload error field", message.FullName())
}
}
}
}
+3 -3
View File
@@ -17,11 +17,11 @@ import (
func (s *server) GetTicket(ctx context.Context, req *proto.GetTicketReq) (*proto.GetTicketRsp, error) {
_ = grpc.SetHeader(ctx, metadata.Pairs("Cache-Control", "no-store"))
if req == nil {
return &proto.GetTicketRsp{Error: ticket_service.ErrTicketNotFound.Error()}, nil
return nil, ticketErrorStatus(ticket_service.ErrTicketNotFound, "не удалось получить билет")
}
page, err := s.ticketService.Get(ctx, req.Token)
if err != nil {
return &proto.GetTicketRsp{Error: ticket_service.ErrTicketNotFound.Error()}, nil
return nil, ticketErrorStatus(err, "не удалось получить билет")
}
result := &proto.TicketPage{
@@ -49,7 +49,7 @@ func (s *server) AcceptTicket(ctx context.Context, req *proto.AcceptTicketReq) (
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
if req == nil {
return &proto.AcceptTicketRsp{Error: ticket_service.ErrTicketNotFound.Error()}, nil
return nil, ticketErrorStatus(ticket_service.ErrTicketNotFound, "не удалось принять билет")
}
page, err := s.ticketService.Accept(ctx, req.Token, claims.UserID)
if err != nil {
+260 -927
View File
File diff suppressed because it is too large Load Diff