{-| Module : RatingChgkInfo.Types Description : Типы для библиотеки работы с API сайта рейтинга Copyright : (c) Mansur Ziiatdinov, 2018-2019 License : BSD-3 Maintainer : chgk@pm.me Stability : experimental Portability : POSIX Типы в этом модуле практически совпадают с теми, которые возвращаются сайтом рейтинга. Поэтому и проблемы у них (такие, как использование строк вместо целых и т.п.) общие. Часть этих проблем задокументирована при помощи пометок __API NOTE__. Возможно, в следующих версиях библиотеки будут какие-то способы обезопасить себя от ошибок, либо (надеюсь) в результате развития API сайта рейтинга, либо без этого. -} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE StrictData #-} {-# LANGUAGE TypeOperators #-} module RatingChgkInfo.Types ( -- * Работа с API -- -- | В этом разделе описаны типы, используемые при запросах к -- предоставляемому сайтом рейтинга REST API -- RatingClient -- ** Общие типы , Items(..) , SeasonMap(..) , RatingApi -- ** Игрок , Player(..) , PlayerTeam(..) , PlayerSeason(..) , PlayerTournament(..) , PlayerRating(..) -- ** Команда , Team(..) , TeamBaseRecap(..) , TeamTournament(..) , TeamRating(..) -- ** Турнир , TournamentShort(..) , Tournament(..) , tournamentToShort , TournamentResult(..) , RecapPlayer(..) , TourResult(..) , Controversial (..) , Appeal (..) -- ** Типы-перечисления , RatingFormula(..) , TournamentType(..) , ClaimStatus (..) , AppealType (..) -- ** Типы для идентификаторов -- -- | Экспортируются без функций, позволяющих вытащить данные из типа, -- поскольку предполагается, что идентификаторы получаются только из -- запросов к серверу. Это должно помочь избежать ошибок, когда -- идентификатор одного типа (например, id игрока) ошибочно передаётся туда, -- где ожидается идентификатор другого типа (например, id турнира). Если вам -- совершенно точно без этого не обойтись, используйте модуль -- "RatingChgkInfo.Types.Unsafe". , PlayerId , TeamId , TournamentId -- -- * Работа без API -- -- | В этом разделе - типы, которые используются при запросах к CSV-таблицам -- на сайте рейтинга для функциональности, которая (надеюсь, пока) __не__ -- предоставляется через REST API -- -- Функции для работы с этими типами находятся в модуле "RatingChgkInfo.NoApi" -- , Request(..) , TeamName(..) ) where import RatingChgkInfo.Types.Unsafe import Control.Lens hiding (Wrapped, Unwrapped) import Data.Aeson import Data.List (lookup) import Data.Map (Map) import qualified Data.Map as M import Data.Swagger (SchemaOptions, declareNamedSchema, genericDeclareNamedSchema, schema, title, description, ToSchema) import qualified Data.Swagger as Swagger import qualified Data.Text as T import Data.Time import Servant.API import Servant.Client (ClientM) import Text.Read (read) -------------------------------------------------------------------------------- -- API Types -- | Синоним типа для реэкспорта. Монада, в которой возможно выполнять запросы к REST API сайта рейтинга type RatingClient = ClientM -- | Список элементов с общим количеством для разбиения на страницы data Items a = Items { total :: Int -- ^ Общее количество , items :: [a] -- ^ Сами элементы } deriving (Eq,Show,Read,Generic) instance FromJSON a => FromJSON (Items a) where parseJSON = withObject "Items list" $ \v -> Items <$> (read <$> v .: "total_items") <*> v .: "items" -- | Отображение сезонов на элементы -- -- __API NOTE__: пустое отображение должно обозначаться @{}@ вместо @[]@ newtype SeasonMap a = SeasonMap { unSeasonMap :: Map Int a } deriving (Eq,Show,Read,Generic) instance FromJSON a => FromJSON (SeasonMap a) where parseJSON (Array []) = pure $ SeasonMap M.empty parseJSON v = SeasonMap <$> parseJSON v -- | Игрок data Player = Player { idplayer :: PlayerId -- ^ Идентификатор игрока. __API NOTE__: должен быть @Int@ , surname :: Text -- ^ Фамилия игрока , name :: Text -- ^ Имя игрока (пустое, если нет имени) , patronymic :: Text -- ^ Отчество игрока (пустое, если его нет) , db_chgk_info_tag :: Maybe Text -- ^ Логин в Базе вопросов. Не возвращается в общем списке игроков, только при запросе отдельного игрока } deriving (Eq,Show,Read,Generic) instance FromJSON Player instance ToJSON Player -- | Игрок в базовом составе команды data PlayerTeam = PlayerTeam { pt_idplayer :: PlayerId -- ^ Идентификатор игрока. __API NOTE__: должен быть @Int@ , pt_idteam :: TeamId -- ^ Идентификатор команды. __API NOTE__: должен быть @Int@ , pt_idseason :: Text -- ^ Идентификатор сезона. __API NOTE__: должен быть @Int@ , pt_is_captain :: Text -- ^ Является ли игрок капитаном (0/1). __API NOTE__: должен быть @Bool@ , pt_added_since :: Day -- ^ С какого момента игрок в базовом составе } deriving (Eq,Show,Read,Generic) instance FromJSON PlayerTeam where parseJSON = genericParseJSON $ jsonOpts '_' 3 instance ToJSON PlayerTeam where toJSON = genericToJSON $ jsonOpts '_' 3 toEncoding = genericToEncoding $ jsonOpts '_' 3 -- | Турниры, сыгранные игроком в сезоне data PlayerSeason = PlayerSeason { ps_idplayer :: PlayerId -- ^ Идентификатор игрока. __API NOTE__: должен быть @Int@ , ps_idseason :: Text -- ^ Идентификатор сезона. __API NOTE__: должен быть @Int@ , ps_tournaments :: [PlayerTournament] -- ^ Список турниров, сыгранных игроком в этом сезоне } deriving (Eq,Show,Read,Generic) instance FromJSON PlayerSeason where parseJSON = genericParseJSON $ jsonOpts '_' 3 instance ToJSON PlayerSeason where toJSON = genericToJSON $ jsonOpts '_' 3 toEncoding = genericToEncoding $ jsonOpts '_' 3 -- | Турнир, сыгранный игроком data PlayerTournament = PlayerTournament { ptr_idtournament :: TournamentId -- ^ Идентификатор турнира. __API NOTE__: должен быть @Int@ , ptr_idteam :: TeamId -- ^ Идентификатор команды. __API NOTE__: должен быть @Int@ , ptr_in_base_team :: Text -- ^ Игра за базовую команды (0/1). __API NOTE__: должен быть @Bool@ } deriving (Eq,Show,Read,Generic) instance FromJSON PlayerTournament where parseJSON = genericParseJSON $ jsonOpts '_' 4 instance ToJSON PlayerTournament where toJSON = genericToJSON $ jsonOpts '_' 4 toEncoding = genericToEncoding $ jsonOpts '_' 4 -- | Рейтинг игрока data PlayerRating = PlayerRating { prat_idplayer :: PlayerId -- ^ Идентификатор игрока. __API NOTE__: должен быть Int , prat_idrelease :: Text -- ^ Идентификатор релиза. __API NOTE__: должен быть Int , prat_rating :: Text -- ^ Рейтинг. __API NOTE__: должен быть Int , prat_rating_position :: Text -- ^ Позиция в рейтинге. __API NOTE__: должен быть Int или Rational , prat_date :: Day -- ^ Дата, когда был рассчитан рейтинг , prat_tournaments_in_year :: Text -- ^ Количество сыгранных турниров за год. __API NOTE__: должен быть Int , prat_tournament_count_total :: Text -- ^ Количество сыгранных турниров всего. __API NOTE__: должен быть Int } deriving (Eq,Show,Read,Generic) instance FromJSON PlayerRating where parseJSON = genericParseJSON $ jsonOpts '_' 5 instance ToJSON PlayerRating where toJSON = genericToJSON $ jsonOpts '_' 5 toEncoding = genericToEncoding $ jsonOpts '_' 5 -- | Команда data Team = Team { tm_idteam :: TeamId -- ^ Идентификатор команды. __API NOTE__: должен быть Int , tm_name :: Text -- ^ Название команды , tm_town :: Text -- ^ Город приписки команды , tm_region_name :: Text -- ^ Регион приписки , tm_country_name :: Text -- ^ Страна , tm_tournaments_this_season :: Text -- ^ Количество турниров, сыгранных в последнем сезоне. __API NOTE__: должен быть Int , tm_tournaments_total :: Text -- ^ Количество турниров всего. __API NOTE__: должен быть Int , tm_comment :: Maybe Text -- ^ Вероятно, комментарий } deriving (Eq,Show,Read,Generic) instance FromJSON Team where parseJSON = genericParseJSON $ jsonOpts '_' 3 instance ToJSON Team where toJSON = genericToJSON $ jsonOpts '_' 3 toEncoding = genericToEncoding $ jsonOpts '_' 3 -- | Базовый состав команды data TeamBaseRecap = TeamBaseRecap { tbr_idteam :: TeamId -- ^ Идентификатор команды. __API NOTE__: должен быть Int , tbr_idseason :: Text -- ^ Идентификатор сезона. __API NOTE__: должен быть Int , tbr_players :: [Text] -- ^ Список игроков (вместе с капитаном). TODO: должен быть Set Int , tbr_captain :: Text -- ^ Капитан команды. __API NOTE__: должен быть Maybe Int } deriving (Eq,Show,Read,Generic) instance FromJSON TeamBaseRecap where parseJSON = genericParseJSON $ jsonOpts '_' 4 instance ToJSON TeamBaseRecap where toJSON = genericToJSON $ jsonOpts '_' 4 toEncoding = genericToEncoding $ jsonOpts '_' 4 -- | Турниры, сыгранные командой в сезоне data TeamTournament = TeamTournament { tt_idteam :: TeamId -- ^ Идентификатор команды. __API NOTE__: должен быть Int , tt_idseason :: Text -- ^ Идентификатор сезона. __API NOTE__: должен быть Int , tt_tournaments :: [Text] -- ^ Список идентификаторов турниров. TODO: должен быть Set Int } deriving (Eq,Show,Read,Generic) instance FromJSON TeamTournament where parseJSON = genericParseJSON $ jsonOpts '_' 3 instance ToJSON TeamTournament where toJSON = genericToJSON $ jsonOpts '_' 3 toEncoding = genericToEncoding $ jsonOpts '_' 3 -- | Формула рейтинга data RatingFormula = FormulaA -- ^ Рейтинг А (аддитивный) | FormulaB -- ^ Рейтинг Б (балансный) deriving (Eq,Show,Read,Generic) instance FromJSON RatingFormula where parseJSON = withText "Formula should be String" $ \case "a" -> pure FormulaA "b" -> pure FormulaB _ -> fail "Only two formula: a & b" instance ToJSON RatingFormula where toJSON FormulaA = toJSON ("a" :: Text) toJSON FormulaB = toJSON ("b" :: Text) toEncoding FormulaA = toEncoding ("a" :: Text) toEncoding FormulaB = toEncoding ("b" :: Text) -- | Рейтинг команды data TeamRating = TeamRating { rat_idteam :: TeamId -- ^ Идентификатор команды. __API NOTE__: должен быть Int , rat_idrelease :: Text -- ^ Идентификатор релиза. __API NOTE__: должен быть Int , rat_rating :: Text -- ^ Рейтинг команды. __API NOTE__: должен быть Int , rat_rating_position :: Text -- ^ Позиция в рейтинге. __API NOTE__: должен быть Int или Rational , rat_date :: Text -- ^ Дата, на которую рассчитан рейтинг. __API NOTE__: должен быть Day (например, @/teams/1/rating@ возвращает пустую строку для релиза 26; причём @/teams/1/rating/26@ возвращает пустой ответ) , rat_formula :: RatingFormula -- ^ Формула подсчёта рейтинга } deriving (Eq,Show,Read,Generic) instance FromJSON TeamRating where parseJSON = genericParseJSON $ jsonOpts '_' 4 instance ToJSON TeamRating where toJSON = genericToJSON $ jsonOpts '_' 4 toEncoding = genericToEncoding $ jsonOpts '_' 4 -- | Тип турнира -- -- __API NOTE__: типа @""@ (пустая строка) быть не должно. На данный момент (2019-01-13) таких турниров три: 2864, 2937, 2995. Типа @Неизвестный@ тоже быть не должно. Такой один: 2186 data TournamentType = Synchronous -- ^ Синхрон | StrictlySynchronous -- ^ Строго синхронный | Asynchronous -- ^ Асинхрон | Casual -- ^ Обычный | Regional -- ^ Региональный | Marathon -- ^ Марафон | TotalScore -- ^ Общий зачёт | TypeUnknown -- ^ Неизвестный | TypeEmpty -- ^ (пустая строка) deriving (Eq,Show,Read,Generic) tournamentTypes :: [(Text, TournamentType)] tournamentTypes = [ ("Синхрон" , Synchronous) , ("Строго синхронный", StrictlySynchronous) , ("Асинхрон" , Asynchronous) , ("Обычный" , Casual) , ("Региональный" , Regional) , ("Марафон" , Marathon) , ("Общий зачёт" , TotalScore) , ("Неизвестный" , TypeUnknown) , ("" , TypeEmpty) ] tournamentTypenames :: [(TournamentType, Text)] tournamentTypenames = map swap tournamentTypes instance FromJSON TournamentType where parseJSON = withText "TournamentType should be String" $ \t -> case lookup t tournamentTypes of Nothing -> fail $ "Wrong TournamentType: " ++ T.unpack t Just tt -> pure tt instance ToJSON TournamentType where toJSON tt = toJSON $ fromMaybe (error "Not all tournamentTypes have names") $ lookup tt tournamentTypenames toEncoding tt = toEncoding $ fromMaybe (error "Not all tournamentTypes have names") $ lookup tt tournamentTypenames -- | Короткая информация о турнире (в списке турниров) data TournamentShort = TournamentShort { trs_idtournament :: TournamentId -- ^ Идентификатор турнира. __API NOTE__: должен быть Int , trs_name :: Text -- ^ Название турнира , trs_dateStart :: LocalTime -- ^ Дата начала турнира (в часовом поясе МСК) , trs_dateEnd :: LocalTime -- ^ Дата окончания турнира (в часовом поясе МСК) , trs_typeName :: TournamentType -- ^ Тип турнира } deriving (Eq,Show,Read,Generic) instance FromJSON TournamentShort where parseJSON = genericParseJSON $ jsonOpts '_' 4 instance ToJSON TournamentShort where toJSON = genericToJSON $ jsonOpts '_' 4 toEncoding = genericToEncoding $ jsonOpts '_' 4 -- | Полная информация о турнире (по отдельному запросу) -- -- Тип 'Tournament' можно было бы объединить с 'TournamentShort', однако, в этом -- случае бóльшая часть полей имела бы тип 'Maybe' x, что подразумевало бы -- другой смысл, с менее строгой проверкой типов (некоторые поля могут быть -- установлены, а некоторые нет). Поэтому было решено разделить эти два типа. -- -- Сконвертировать 'Tournament' в 'TournamentShort' можно при помощи функции -- 'tournamentToShort'. -- -- В отличие от 'Tournament' в типах 'Player' и 'Team' есть единственное поле, -- которое устанавливается в запросе более полной информации, поэтому эти типы -- не разделены на два. data Tournament = Tournament { trn_idtournament :: TournamentId -- ^ Идентификатор турнира. __API NOTE__: должен быть Int , trn_name :: Text -- ^ Название турнира , trn_town :: Text -- ^ Город проведения , trn_longName :: Text -- ^ Длинное название турнира , trn_dateStart :: LocalTime -- ^ Дата начала турнира (в часовом поясе МСК) , trn_dateEnd :: LocalTime -- ^ Дата окончания турнира (в часовом поясе МСК) , trn_tournamentInRating :: Text -- ^ Учитывается ли турнир в рейтинге. __API NOTE__: должен быть Bool , trn_tourCount :: Text -- ^ Количество туров. __API NOTE__: должен быть Int , trn_tourQuestions :: Text -- ^ Количество вопросов в туре. __API NOTE__: должен быть Int , trn_tourQuestPerTour :: Maybe Text -- ^ Количество вопросов по турам, разделённое запятой. __API NOTE__: должен быть [Int] , trn_questionsTotal :: Text -- ^ Количество вопросов общее. __API NOTE__: должен быть Int , trn_typeName :: TournamentType -- ^ Тип турнира , trn_mainPaymentValue :: Text -- ^ Размер обычного взноса , trn_mainPaymentCurrency :: Text -- ^ Валюта обычного взноса , trn_discountedPaymentValue :: Text -- ^ Размер льготного взноса , trn_discountedPaymentCurrency :: Text -- ^ Валюта льготного взноса , trn_discountedPaymentReason :: Text -- ^ Кому доступен льготный взнос , trn_dateRequestsAllowedTo :: Text -- ^ Дата, до которой разрешена подача заявок. __API NOTE__: должен быть Day , trn_comment :: Text -- ^ Комментарий (это __не__ текст внизу на странице турнира; например, в турнире 5003 комментарий пуст, хотя текст внизу гласит «Сроки турнира привязаны к Новому году и финалу года телеЧГК») , trn_siteUrl :: Text -- ^ Адрес официального сайта } deriving (Eq,Show,Read,Generic) instance FromJSON Tournament where parseJSON = genericParseJSON $ jsonOpts '_' 4 instance ToJSON Tournament where toJSON = genericToJSON $ jsonOpts '_' 4 toEncoding = genericToEncoding $ jsonOpts '_' 4 -- | Преобразует 'Tournament' в 'TournamentShort', убирая лишние поля tournamentToShort :: Tournament -> TournamentShort tournamentToShort Tournament{ trn_idtournament = idtournament , trn_name = name , trn_dateStart = dateStart , trn_dateEnd = dateEnd , trn_typeName = typeName } = TournamentShort { trs_idtournament = idtournament , trs_name = name , trs_dateStart = dateStart , trs_dateEnd = dateEnd , trs_typeName = typeName } -- | Результаты турнира для команды data TournamentResult = TournamentResult { tr_idteam :: TeamId -- ^ Идентификатор команды. __API NOTE__: должен быть Int , tr_current_name :: Text -- ^ Название команды на турнире. Может совпадать с основным названием. Если название разовое, отличается от основного названия , tr_base_name :: Text -- ^ Основное название команды. , tr_position :: Text -- ^ Положение в турнирной таблице. __API NOTE__: должен быть Int or Rational , tr_questions_total :: Text -- ^ Общее количество взятых вопросов. __API NOTE__: должен быть Int , tr_mask :: Text -- ^ Расплюсовка команды (строка, где на каждый вопрос турнира указано: @0@ - не взят; @1@ - взят; @X@ - вопрос снят). __API NOTE__: должен быть [Bool] or [Answer] , tr_tech_rating :: Text -- ^ Технический рейтинг команды. __API NOTE__: должен быть Int , tr_predicted_position :: Text -- ^ Предсказанное положение в турнирной таблице. __API NOTE__: должен быть Int or Rational , tr_bonus_a :: Text -- ^ Бонус по формуле А (аддитивной). __API NOTE__: должен быть Int , tr_bonus_b :: Text -- ^ Бонус по формуле Б (балансной). __API NOTE__: должен быть Int , tr_diff_bonus :: Text -- ^ Разностный балл D. __API NOTE__: должен быть Int , tr_included_in_rating :: Text -- ^ Результаты команды будут учтены в релизе. __API NOTE__: должен быть Bool } deriving (Eq,Show,Read,Generic) instance FromJSON TournamentResult where parseJSON = genericParseJSON $ jsonOpts '_' 3 instance ToJSON TournamentResult where toJSON = genericToJSON $ jsonOpts '_' 3 toEncoding = genericToEncoding $ jsonOpts '_' 3 -- | Информация об игроке в составе команды на турнире -- -- __API NOTE__. Так как игрок не может быть одновременно в базовом составе и легионером, нужно заменить эти два поля одним. data RecapPlayer = RecapPlayer { rp_idplayer :: PlayerId -- ^ Идентификатор игрока. __API NOTE__: должен быть Int , rp_is_captain :: Text -- ^ Является ли игрок капитаном (К). __API NOTE__: должен быть Bool , rp_is_base :: Text -- ^ Находится ли игрок в базовом составе (Б). __API NOTE__: должен быть Bool , rp_is_foreign :: Text -- ^ Является ли игрок легионером (Л). __API NOTE__: должен быть Bool } deriving (Eq,Show,Read,Generic) instance FromJSON RecapPlayer where parseJSON = genericParseJSON $ jsonOpts '_' 3 instance ToJSON RecapPlayer where toJSON = genericToJSON $ jsonOpts '_' 3 toEncoding = genericToEncoding $ jsonOpts '_' 3 -- | Результаты команды по турам data TourResult = TourResult { tor_tour :: Text -- ^ Номер тура. __API NOTE__: должен быть Int , tor_mask :: [Text] -- ^ Расплюсовка команды (количество элементов списка совпадает с количеством вопросов в туре; каждый элемент списка либо @0@ - не взят, либо @1@ - взят, либо @X@ - вопрос снят). __API NOTE__: должен быть [Int] or [Answer] } deriving (Eq,Show,Read,Generic) instance FromJSON TourResult where parseJSON = genericParseJSON $ jsonOpts '_' 4 instance ToJSON TourResult where toJSON = genericToJSON $ jsonOpts '_' 4 toEncoding = genericToEncoding $ jsonOpts '_' 4 -- | Статус спорного или апелляции data ClaimStatus = ClaimNew -- ^ Новый (N) | ClaimAccepted -- ^ Принят (A) | ClaimRejected -- ^ Отклонён (D) deriving (Eq,Show,Read,Generic) claimTypeText :: [(ClaimStatus, Text)] claimTypeText = [ (ClaimNew, "N") , (ClaimAccepted, "A") , (ClaimRejected, "D") ] claimTextType :: [(Text, ClaimStatus)] claimTextType = map swap claimTypeText instance FromJSON ClaimStatus where parseJSON = withText "ClaimStatus should be String" $ \t -> case lookup t claimTextType of Nothing -> fail $ "Wrong ClaimStatus " ++ T.unpack t Just tt -> pure tt instance ToJSON ClaimStatus where toJSON tt = toJSON $ fromMaybe (error "Not all ClaimStatus have names") $ lookup tt claimTypeText toEncoding tt = toEncoding $ fromMaybe (error "Not all ClaimStatus have names") $ lookup tt claimTypeText -- | Спорный data Controversial = Controversial { conQuestionNumber :: Text -- ^ Номер вопроса. __API NOTE__: должен быть @Int@ , conAnswer :: Text -- ^ Ответ команды , conIssuedAt :: LocalTime -- ^ Время подачи. __API NOTE__: должен быть @UTCTime@ , conStatus :: ClaimStatus -- ^ Статус спорного , conComment :: Text -- ^ Вердикт ИЖ , conResolvedAt :: Text -- ^ Время вынесения решения. __API NOTE__: должен быть @UTCTime@ , conAppealJuryComment :: Text -- ^ Вердикт АЖ } deriving (Eq,Show,Read,Generic) instance FromJSON Controversial where parseJSON = genericParseJSON $ jsonOpts '_' 3 instance ToJSON Controversial where toJSON = genericToJSON $ jsonOpts '_' 3 toEncoding = genericToEncoding $ jsonOpts '_' 3 -- | Вид апелляции data AppealType = AppealApprove -- ^ Апелляция на зачёт ответа (A) | AppealRemove -- ^ Апелляция на снятие вопроса (R) | AppealNarrator -- ^ Апелляция на снятие из-за ошибки ведущего (N) deriving (Eq,Show,Read,Generic) appealTypeText :: [(AppealType, Text)] appealTypeText = [ (AppealApprove, "A") , (AppealRemove, "R") , (AppealNarrator, "N") ] appealTextType :: [(Text, AppealType)] appealTextType = map swap appealTypeText instance FromJSON AppealType where parseJSON = withText "AppealType should be String" $ \t -> case lookup t appealTextType of Nothing -> fail $ "Wrong AppealType " ++ T.unpack t Just tt -> pure tt instance ToJSON AppealType where toJSON tt = toJSON $ fromMaybe (error "Not all AppealType have names") $ lookup tt appealTypeText toEncoding tt = toEncoding $ fromMaybe (error "Not all AppealType have names") $ lookup tt appealTypeText -- | Апелляция data Appeal = Appeal { appType :: AppealType -- ^ Тип апелляции , appQuestionNumber :: Text -- ^ Номер вопроса , appIssuedAt :: LocalTime -- ^ Время создания. __API NOTE__: должен быть @UTCTime@ , appStatus :: ClaimStatus -- ^ Статус апелляции. Статус может быть "новая" и в том случае, если апелляция не рассмотрена, так как была зачтена другая апелляция , appAppeal :: Text -- ^ Текст апелляции , appComment :: Text -- ^ Вердикт АЖ , appResolvedAt :: Text -- ^ Время публикации вердикта. __API NOTE__: должен быть @UTCTime@ , appAnswer :: Text -- ^ Ответ команды } deriving (Eq,Show,Read,Generic) instance FromJSON Appeal where parseJSON = genericParseJSON $ jsonOpts '_' 3 instance ToJSON Appeal where toJSON = genericToJSON $ jsonOpts '_' 3 toEncoding = genericToEncoding $ jsonOpts '_' 3 -- | Тип, описывающий API сайта рейтинга. Функции, которые позволяют делать запросы к API, находятся в модуле "RatingChgkInfo.Api" -- -- Некоторые замечания по общему дизайну API: -- -- * __API NOTE__: в запросах @\/players\/:id@, @\/tournaments\/:id@ и некоторых других должен возвращаться единственный результат вместо списка из одного результата -- -- * __API NOTE__: в запросе @\/players\/:id\/teams@ и других запросах, возвращающие элементы по сезонам, следует возвращать список вместо отображения номера сезона на элемент (идентификатор сезона дублируется в самом элементе) -- -- * __API NOTE__: запросы, возвращающие элементы по сезонам, и запрос @\/tournaments\/:tourn\/results\/:team@ устроены по-разному -- -- * __API NOTE__: запрос @\/teams\/:id\/rating\/:formula@, по-видимому, несколько сломан: для команды 1 он возвращает пустую строку (по состоянию на 2019-01-11) -- -- * __API NOTE__: запрос @\/players\/:id\/rating\/last@, по-видимому, нсколько сломан: для игрока 54345 он возвращает пустую строку (по состоянию на 2019-01-11) -- -- * __API NOTE__: запросы @\/tournament\/:id\/town\/:town@ должны использовать QueryParam вместо параметров путей type RatingApi = "players" :> QueryParam "page" Int :> Get '[JSON] (Items Player) :<|> "players" :> Capture "idplayer" PlayerId :> Get '[JSON] [Player] :<|> "players" :> Capture "idplayer" PlayerId :> "teams" :> Get '[JSON] [PlayerTeam] -- TODO: Set? :<|> "players" :> Capture "idplayer" PlayerId :> "teams" :> "last" :> Get '[JSON] [PlayerTeam] -- единственный элемент! :<|> "players" :> Capture "idplayer" PlayerId :> "teams" :> Capture "idseason" Int :> Get '[JSON] [PlayerTeam] -- единственный элемент! :<|> "players" :> Capture "idplayer" PlayerId :> "tournaments" :> Get '[JSON] (SeasonMap PlayerSeason) :<|> "players" :> Capture "idplayer" PlayerId :> "tournaments" :> "last" :> Get '[JSON] PlayerSeason :<|> "players" :> Capture "idplayer" PlayerId :> "tournaments" :> Capture "idseason" Int :> Get '[JSON] PlayerSeason :<|> "players" :> Capture "idplayer" PlayerId :> "rating" :> Get '[JSON] [PlayerRating] -- TODO: Set? :<|> "players" :> Capture "idplayer" PlayerId :> "rating" :> "last" :> Get '[JSON] PlayerRating :<|> "players" :> Capture "idplayer" PlayerId :> "rating" :> Capture "idrelease" Int :> Get '[JSON] PlayerRating :<|> "teams" :> QueryParam "page" Int :> Get '[JSON] (Items Team) :<|> "teams" :> Capture "idteam" TeamId :> Get '[JSON] [Team] :<|> "teams" :> Capture "idteam" TeamId :> "recaps" :> Get '[JSON] (SeasonMap TeamBaseRecap) :<|> "teams" :> Capture "idteam" TeamId :> "recaps" :> "last" :> Get '[JSON] TeamBaseRecap :<|> "teams" :> Capture "idteam" TeamId :> "recaps" :> Capture "idseason" Int :> Get '[JSON] TeamBaseRecap :<|> "teams" :> Capture "idteam" TeamId :> "tournaments" :> Get '[JSON] (SeasonMap TeamTournament) :<|> "teams" :> Capture "idteam" TeamId :> "tournaments" :> "last" :> Get '[JSON] TeamTournament :<|> "teams" :> Capture "idteam" TeamId :> "tournaments" :> Capture "idseason" Int :> Get '[JSON] TeamTournament :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> Get '[JSON] [TeamRating] -- TODO: Set? :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> "a" :> Get '[JSON] TeamRating :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> "b" :> Get '[JSON] TeamRating :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> Capture "idrelease" Int :> Get '[JSON] TeamRating :<|> "tournaments" :> QueryParam "page" Int :> Get '[JSON] (Items TournamentShort) :<|> "tournaments" :> Capture "idtournament" TournamentId :> Get '[JSON] [Tournament] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "town" :> Capture "idtown" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "region" :> Capture "idregion" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "country" :> Capture "idcountry" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "recaps" :> Capture "idteam" TeamId :> Get '[JSON] [RecapPlayer] -- TODO: Set? :<|> "tournaments" :> Capture "idtournament" TournamentId :> "results" :> Capture "idteam" TeamId :> Get '[JSON] [TourResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "controversials" :> Get '[JSON] [Controversial] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "appeals" :> Get '[JSON] [Appeal] :<|> "teams" :> "search" :> QueryParam "name" Text :> QueryParam "town" Text :> QueryParam "region_name" Text :> QueryParam "country_name" Text :> QueryFlag "active_this_season" :> QueryParam "page" Int :> Get '[JSON] (Items Team) :<|> "players" :> "search" :> QueryParam "surname" Text :> QueryParam "name" Text :> QueryParam "patronymic" Text :> QueryParam "page" Int :> Get '[JSON] (Items Player) -------------------------------------------------------------------------------- -- Non-API Types -- | Название команды на турнире data TeamName = TeamName { tnTeamId :: Int -- ^ Идентификатор команды , tnCurrentName :: Text -- ^ Название на турнире (может совпадать с основным) , tnCurrentTown :: Text -- ^ Город приписки на турнире (может совпадать с основным) , tnBaseName :: Text -- ^ Основное название , tnBaseTown :: Text -- ^ Основной город прописки } deriving (Eq,Show,Read,Generic) instance ToJSON TeamName where toJSON = genericToJSON $ jsonOpts '-' 2 toEncoding = genericToEncoding $ jsonOpts '-' 2 instance ToSchema TeamName where declareNamedSchema p = genericDeclareNamedSchema (schemaOpts 2) p & mapped.schema.title ?~ "TeamName" & mapped.schema.description ?~ "Описание команды. В объекте содержатся поля: team-id - идентификатор команды; current-name - (разовое) название команды; current-town - (разовый) город приписки; base-name - название команды на сайте рейтинга; base-town - город приписки на сайте рейтинга" -- | Заявка на проведение data Request = Request { reqAccepted :: Maybe Bool -- ^ Заявка принята или отклонена , reqTown :: Text -- ^ Город проведения , reqRepresentativeId :: Int -- ^ Идентификатор представителя , reqRepresentativeFullname :: Text -- ^ ФИО представителя , reqNarratorId :: Int -- ^ Идентификатор ведущего (сайт рейтинга не экспортирует его в CSV, поэтому всегда установлен в 0) , reqNarratorFullname :: Text -- ^ ФИО ведущего , reqTeamsCount :: Int -- ^ Количество команд, которое было заявлено представителем , reqTeams :: [TeamName] -- ^ Команды, введённые представителем } deriving (Eq,Show,Read,Generic) instance ToJSON Request where toJSON = genericToJSON $ jsonOpts '-' 3 toEncoding = genericToEncoding $ jsonOpts '-' 3 instance ToSchema Request where declareNamedSchema p = genericDeclareNamedSchema (schemaOpts 3) p & mapped.schema.title ?~ "Request" & mapped.schema.description ?~ "Заявка. В объекте содержатся поля accepted - статус заявки (null - не рассмотрена, false/true - отклонена или принята); town - город; representative-id - id представителя; representative-fullname - ФИО представителя, narrator-id - id ведущего (сейчас установлена в 0, сайт рейтинга не экспортирует id); narrator-fullname - ФИО ведущего; teams-count - примерное количество команд (заявлено); teams - список введённых команд" jsonOpts :: Char -> Int -> Options jsonOpts c k = defaultOptions { fieldLabelModifier = camelTo2 c . drop k } schemaOpts :: Int -> SchemaOptions schemaOpts k = Swagger.defaultSchemaOptions { Swagger.fieldLabelModifier = camelTo2 '-' . drop k , Swagger.constructorTagModifier = camelTo2 '-' , Swagger.unwrapUnaryRecords = True }