packages feed

espial-0.0.41: src/Handler/Common.hs

-- | Common handler functions.
module Handler.Common where

import Data.Aeson qualified as A
import Data.FileEmbed (embedFile)
import Data.Text qualified as T
import Import
import Text.Read
import Util (format8601z, parseTimeText)

-- These handlers embed files in the executable at compile time to avoid a
-- runtime dependency, and for efficiency.

getFaviconR :: Handler TypedContent
getFaviconR = do
  cacheSeconds $ 60 * 5
  -- cacheSeconds $ 60 * 60 * 24 * 30 -- cache for a month
  pure
    $ TypedContent "image/x-icon"
    $ toContent $(embedFile "config/favicon.ico")

getRobotsR :: Handler TypedContent
getRobotsR =
  pure
    $ TypedContent typePlain
    $ toContent $(embedFile "config/robots.txt")

lookupPagingParams :: Maybe Text -> Handler (Maybe Int64, Maybe Int64)
lookupPagingParams prefix =
  (,)
    <$> getsetUrlSessionParam "count" (fromMaybe "" prefix)
    <*> getUrlParam (pagingPageParam prefix)

pagingPageParam :: Maybe Text -> Text
pagingPageParam prefix = fromMaybe "" prefix <> "page"

pagingCursorBeforeParam :: Text
pagingCursorBeforeParam = "before"

pagingCursorAfterParam :: Text
pagingCursorAfterParam = "after"

sortParam :: Text
sortParam = "sort"

orderParam :: Text
orderParam = "order"

-- | "~" is unreserved in URL queries and cannot occur in the ISO8601 time
pagingCursorSeparator :: Text
pagingCursorSeparator = "~"

formatEntityPagingCursorBm :: Entity Bookmark -> Text
formatEntityPagingCursorBm (Entity bid bm) =
  format8601z (bookmarkTime bm) <> pagingCursorSeparator <> tshow (fromSqlKey bid)

parsePagingCursorBm :: Text -> Maybe BookmarkCursor
parsePagingCursorBm t = case T.splitOn pagingCursorSeparator t of
  [timeText] -> flip BookmarkCursor Nothing <$> parseTimeText timeText
  [timeText, idText] ->
    BookmarkCursor
      <$> parseTimeText timeText
      <*> (Just <$> parsePagingCursorKey idText)
  _ -> Nothing

formatEntityPagingCursorNt :: Entity Note -> Text
formatEntityPagingCursorNt (Entity nid nt) =
  format8601z (noteCreated nt) <> pagingCursorSeparator <> tshow (fromSqlKey nid)

parsePagingCursorNt :: Text -> Maybe NoteCursor
parsePagingCursorNt t = case T.splitOn pagingCursorSeparator t of
  [timeText] -> flip NoteCursor Nothing <$> parseTimeText timeText
  [timeText, idText] ->
    NoteCursor
      <$> parseTimeText timeText
      <*> (Just <$> parsePagingCursorKey idText)
  _ -> Nothing

formatEntityPagingCursorKey :: (ToBackendKey SqlBackend record) => Entity record -> Text
formatEntityPagingCursorKey = tshow . fromSqlKey . entityKey

parsePagingCursorKey :: (ToBackendKey SqlBackend record) => Text -> Maybe (Key record)
parsePagingCursorKey t =
  toSqlKey <$> (readMaybe (unpack t) :: Maybe Int64)

parsePagingCursorParams ::
  (Text -> Maybe cursor) ->
  (Text -> Maybe cursor) ->
  Maybe Text ->
  Maybe Text ->
  Maybe cursor
parsePagingCursorParams mkBefore mkAfter mbefore mafter =
  (mkBefore =<< mbefore) <|> (mkAfter =<< mafter)

espialUserAgent :: UserAgent
espialUserAgent = UserAgent "espial"

browserUserAgent :: UserAgent
browserUserAgent =
  UserAgent
    $ pack
    $ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
    <> "AppleWebKit/537.36 (KHTML, like Gecko) "
    <> "Chrome/136.0.0.0 Safari/537.36"

getUrlParam :: (Read a) => Text -> Handler (Maybe a)
getUrlParam name = do
  fmap parseMaybe (lookupGetParam name)
  where
    parseMaybe x = readMaybe . unpack =<< x

getsetUrlSessionParam ::
  forall a.
  (Show a, Read a) =>
  Text ->
  Text ->
  Handler (Maybe a)
getsetUrlSessionParam name sessionPrefix = do
  p <- fmap parseMaybe (lookupGetParam name)
  s <- fmap parseMaybe (lookupSession (sessionPrefix <> name))
  for_ p (setSession (sessionPrefix <> name) . (pack . show))
  pure (p <|> s)
  where
    parseMaybe :: Maybe Text -> Maybe a
    parseMaybe x = readMaybe . unpack =<< x

lookupTagCloudMode :: (MonadHandler m) => m (Maybe TagCloudMode)
lookupTagCloudMode = do
  (A.decode . fromStrict =<<) <$> lookupSessionBS "tagCloudMode"

setTagCloudMode :: (MonadHandler m) => TagCloudMode -> m ()
setTagCloudMode = setSessionBS "tagCloudMode" . toStrict . A.encode

getTagCloudMode :: (MonadHandler m) => Bool -> Bool -> [Tag] -> m TagCloudMode
getTagCloudMode isowner publicTagCloud tags = do
  ms <- lookupTagCloudMode
  let expanded = maybe False isExpanded ms
  if not isowner && not publicTagCloud
    then pure TagCloudModeNone
    else
      if not (null tags)
        then pure $ TagCloudModeRelated False tags
        else pure $ case ms of
          Nothing -> TagCloudModeTop expanded
          Just (TagCloudModeRelated e _) -> TagCloudModeTop e
          Just (TagCloudModeRelatedLowerBound e _ _) -> TagCloudModeTop e
          Just m -> m