<commit_before>{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE OverloadedStrings #-}


module Guide.Api
(
  Site(..),
  runApiServer,
)
where


import Imports

import Data.Aeson
import Servant
import Servant.Generic
import Network.Wai.Handler.Warp (run)
import Data.Acid as Acid
-- putStrLn that works well with concurrency
import Say (say)

import Guide.Types
import Guide.State
import Guide.Utils (Uid)


----------------------------------------------------------------------------
-- Routes
----------------------------------------------------------------------------

-- | The description of the served API.
data Site route = Site
  {
  -- | A list of all categories (the /haskell page). Returns category
  -- titles.
    _getCategories :: route :-
      "categories"              :> Get '[JSON] [Text]

  -- | Details of a single category (and items in it, etc)
  , _getCategory :: route :-
      "category"                :> Capture "id" (Uid Category)
                                :> Get '[JSON] (Either ApiError Category)
  }
  deriving (Generic)

data ApiError = ApiError !Text
  deriving (Generic)

instance FromJSON ApiError
instance ToJSON ApiError

----------------------------------------------------------------------------
-- Boilerplate
----------------------------------------------------------------------------

apiServer :: DB -> Site AsServer
apiServer db = Site {
  _getCategories = getCategories db,
  _getCategory   = getCategory db
  }

type Api = ToServant (Site AsApi)

-- | Serve the API on port 4400.
--
-- You can test this API by doing @withDB mempty runApiServer@.
runApiServer :: AcidState GlobalState -> IO ()
runApiServer db = do
  say "API is running on port 4400"
  run 4400 $ serve (Proxy @Api) (toServant (apiServer db))

----------------------------------------------------------------------------
-- Implementations of methods
----------------------------------------------------------------------------

getCategories :: DB -> Handler [Text]
getCategories db = do
  liftIO (Acid.query db GetCategories) <&> \xs ->
    map (view title) xs

getCategory :: DB -> Uid Category -> Handler (Either ApiError Category)
getCategory db catId =
  liftIO (Acid.query db (GetCategoryMaybe catId)) <&> \case
    Nothing  -> Left (ApiError "category not found")
    Just cat -> Right cat<commit_msg>[api] Provide more information at the /categories endpoint<commit_after>{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE OverloadedStrings #-}


module Guide.Api
(
  Site(..),
  runApiServer,
)
where


import Imports

import Data.Aeson
import Servant
import Servant.Generic
import Network.Wai.Handler.Warp (run)
import Data.Acid as Acid
-- putStrLn that works well with concurrency
import Say (say)

import Guide.Types
import Guide.State
import Guide.Utils (Uid)


----------------------------------------------------------------------------
-- Routes
----------------------------------------------------------------------------

-- | The description of the served API.
data Site route = Site
  {
  -- | A list of all categories (the /haskell page). Returns category
  -- titles.
    _getCategories :: route :-
      "categories"              :> Get '[JSON] [CategoryInfo]

  -- | Details of a single category (and items in it, etc)
  , _getCategory :: route :-
      "category"                :> Capture "id" (Uid Category)
                                :> Get '[JSON] (Either ApiError Category)
  }
  deriving (Generic)

data ApiError = ApiError !Text
  deriving (Generic)

instance FromJSON ApiError
instance ToJSON ApiError

----------------------------------------------------------------------------
-- Boilerplate
----------------------------------------------------------------------------

apiServer :: DB -> Site AsServer
apiServer db = Site {
  _getCategories = getCategories db,
  _getCategory   = getCategory db
  }

type Api = ToServant (Site AsApi)

-- | Serve the API on port 4400.
--
-- You can test this API by doing @withDB mempty runApiServer@.
runApiServer :: AcidState GlobalState -> IO ()
runApiServer db = do
  say "API is running on port 4400"
  run 4400 $ serve (Proxy @Api) (toServant (apiServer db))

----------------------------------------------------------------------------
-- Implementations of methods
----------------------------------------------------------------------------

data CategoryInfo = CategoryInfo {
  _categoryInfoUid     :: Uid Category,
  _categoryInfoTitle   :: Text,
  _categoryInfoCreated :: UTCTime,
  _categoryInfoGroup_  :: Text,
  _categoryInfoStatus  :: CategoryStatus
  }
  deriving (Show, Generic)

instance ToJSON CategoryInfo

getCategories :: DB -> Handler [CategoryInfo]
getCategories db = do
  liftIO (Acid.query db GetCategories) <&> \xs ->
    map categoryToInfo xs
  where
    categoryToInfo Category{..} = CategoryInfo {
      _categoryInfoUid     = _categoryUid,
      _categoryInfoTitle   = _categoryTitle,
      _categoryInfoCreated = _categoryCreated,
      _categoryInfoGroup_  = _categoryGroup_,
      _categoryInfoStatus  = _categoryStatus }

getCategory :: DB -> Uid Category -> Handler (Either ApiError Category)
getCategory db catId =
  liftIO (Acid.query db (GetCategoryMaybe catId)) <&> \case
    Nothing  -> Left (ApiError "category not found")
    Just cat -> Right cat
{'fraction_non_alphanumeric': 0.2520837192072606, 'fraction_numerical': 0.004445267642155954, 'mean_word_length': 1.6892430278884463, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3464899', 'n_tokens_mistral': 1455, 'n_tokens_neox': 1408, 'n_words': 463}