wikimusic-ssr-1.0.1.6: src/WikiMusic/SSR/Servant/Utilities.hs
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}
module WikiMusic.SSR.Servant.Utilities where
import Data.ByteString.Base16.Lazy qualified as B16
import Data.Map qualified as Map
import Data.Text qualified as T
import NeatInterpolation
import Principium
import Servant
import Servant.Multipart
import Text.Blaze.Html.Renderer.Utf8
import WikiMusic.SSR.Backend.Rest ()
import WikiMusic.SSR.Free.View
import WikiMusic.SSR.View.Html ()
fromForm :: MultipartData tag -> Text -> Text -> Text
fromForm multipart fallback name =
maybe fallback head
. nonEmpty
. map iValue
. filter (\i -> iName i == name)
$ inputs multipart
maybeFromForm :: MultipartData tag -> Text -> Maybe Text
maybeFromForm multipart name = case rawVal of
(Just "") -> Nothing
(Just x) -> Just x
Nothing -> Nothing
where
rawVal =
fmap head
. nonEmpty
. map iValue
. filter (\i -> iName i == name)
$ inputs multipart
setCookieRoute :: (MonadError ServerError m) => CookieConfig -> Text -> Map Text Text -> m a
setCookieRoute cookieConfig newLocation cookieMap =
throwError
$ ServerError
{ errHTTPCode = 302,
errReasonPhrase = "Found",
errBody = "",
errHeaders =
("Location", encodeUtf8 newLocation) : cookieHeaders
}
where
mkCookieHeaders (cookieName, cookieValue) =
( "Set-Cookie",
fromString
. T.unpack
. mkCookieData cookieConfig
$ [trimming|$cookieName=$cookieValue|]
)
cookieHeaders = map mkCookieHeaders (Map.assocs cookieMap)
mkCookieData :: CookieConfig -> Text -> Text
mkCookieData cookieConfig dyn =
[trimming|
$dyn; HttpOnly; $sameSite; Domain=$domain; Path=/; Max-Age=$maxAge $secureSuffix
|]
where
maxAge = show $ cookieConfig ^. #maxAge
domain = cookieConfig ^. #domain
sameSite = cookieConfig ^. #sameSite
secureSuffix = if cookieConfig ^. #secure then "; Secure" else ""
mkCookieMap :: Maybe Text -> Map Text Text
mkCookieMap cookie = do
let diffCookies = maybe [] (T.splitOn "; ") cookie
cookieParser [a, b] = Just (a, b)
cookieParser _ = Nothing
cookieMap = Map.fromList $ mapMaybe (cookieParser . T.splitOn "=") diffCookies
cookieMap
decodeToken :: Text -> Text
decodeToken = decodeUtf8 . B16.decodeLenient . encodeUtf8
encodeToken :: Text -> Text
encodeToken = decodeUtf8 . B16.encode . encodeUtf8
vvFromCookies :: Maybe Text -> ViewVars
vvFromCookies cookie = ViewVars {..}
where
cookieMap = mkCookieMap cookie
language = Language {value = fromMaybe "en" (cookieMap !? localeCookieName)}
uiMode = UiMode {value = fromMaybe "light" (cookieMap !? uiModeCookieName)}
authToken = AuthToken {value = decodeToken $ fromMaybe "" (cookieMap !? authCookieName)}
songAsciiSize = SongAsciiSize {value = fromMaybe "sm" (cookieMap !? songAsciiSizeCookieName)}
artistSorting =
SortOrder
{ value = fromMaybe "created-at-desc" (cookieMap !? artistSortingCookieName)
}
songSorting =
SortOrder
{ value = fromMaybe "created-at-desc" (cookieMap !? songSortingCookieName)
}
genreSorting =
SortOrder
{ value = fromMaybe "created-at-desc" (cookieMap !? genreSortingCookieName)
}
palette = Palette {value = fromMaybe "purple" (cookieMap !? paletteCookieName)}
errorRoute :: (MonadIO m, MonadError ServerError m) => Env -> Maybe Text -> Maybe Text -> Maybe Int -> Maybe Text -> m Html
errorRoute env cookie _ maybeCode maybeMessage = do
h <- liftIO $ exec @View (errorPage env vv maybeCode maybeMessage)
throwError
$ ServerError
{ errHTTPCode = fromMaybe 500 maybeCode,
errReasonPhrase = "Error!",
errBody = renderHtml h,
errHeaders = []
}
where
vv = vvFromCookies cookie
data ServerResponse = ServerResponse
{ code :: Int,
cause :: Maybe Text,
body :: Maybe Text,
headers :: [(Text, Text)]
}
deriving (Eq, Show)
makeFieldLabelsNoPrefix ''ServerResponse
serverResponse :: ServerResponse
serverResponse =
ServerResponse
{ code = 200,
cause = Just "OK",
body = Nothing,
headers = []
}
withLocation :: (IsString a, IsString b) => Text -> (a, b)
withLocation location = ("Location", fromString . T.unpack $ location)
respondWithHttp :: (MonadIO m, MonadError ServerError m) => ServerResponse -> m a
respondWithHttp sr =
throwError
$ ServerError
{ errHTTPCode = sr ^. #code,
errReasonPhrase = T.unpack $ fromMaybe "" $ sr ^. #cause,
errBody = encodeUtf8 $ fromMaybe "" $ sr ^. #body,
errHeaders =
map
( bimap
(fromString . unpackText)
(fromString . unpackText)
)
(sr ^. #headers)
}
httpFound :: ServerResponse
httpFound = serverResponse {code = 302}
callErrorPage :: (MonadIO m, MonadError ServerError m) => Text -> m a
callErrorPage e = respondWithHttp httpFound {cause = Just "Error occured!", headers = [withLocation newLocation]}
where
newLocation =
"/error?code="
<> ( T.pack
. show
$ 500
)
<> "&message="
<> ( decodeUtf8
. B16.encode
. fromString
. T.unpack
$ e
)
respondWithViewOrErr :: (MonadIO m, MonadError ServerError m) => Either Text t -> (t -> IO a) -> m a
respondWithViewOrErr x eff = case x of
Left e -> callErrorPage e
Right r -> liftIO $ eff r
respondWithViewOrErr' :: (MonadIO m, MonadError ServerError m) => Either Text (Maybe t) -> (t -> IO a) -> m a
respondWithViewOrErr' x eff = case x of
Left e -> callErrorPage e
Right r -> case r of
Nothing -> callErrorPage "Error!"
Just item -> liftIO $ eff item