FeedMonad/FeedMonad/src/FeedMonad.hs

83 lines
2.7 KiB
Haskell

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
module FeedMonad where
import Control.Exception (bracket)
import Control.Monad.App (App, runApp)
import Control.Monad.HTTP (execute, fetch)
import Control.Monad.Reader (asks)
import Control.Monad.Trans (liftIO)
import Data.Acid (AcidState(closeAcidState), openLocalState, query, update)
import Data.Category (Category)
import Data.Environment
import Data.Foldable (for_, toList)
import Data.Text (Text)
import Database
( CountEntries(CountEntries)
, FeedId(FeedId)
, SaveEntries(SaveEntries)
, emptyFeedMonadState, UnseenEntries (UnseenEntries), EntryId (EntryId)
)
import Feed.Parser (parseEntries)
import Middleware (Middleware)
import Network.HTTP.Client.TLS (newTlsManager)
import Numeric.Natural (Natural)
import Text.Feed.Import (parseFeedSource)
import Data.Entry (Entry(entryURL))
import qualified Data.Map.Strict as M
newtype Minutes = Minutes Natural
data FeedMonad = FeedMonad
{ feeds :: [Category FeedId]
-- ^ The forest of urls for the feeds. It's a forest because of the categories
, filters :: Middleware
-- ^ The middleware. Modifies the scoring, tags and content
, refreshTime :: Minutes
-- ^ How often to refresh the feeds
, secretToken :: Text
-- ^ Used for authenticating the UI. This is a single user app
-- served over http, so we can get around with hardcoded
-- authentication token
}
defaultConfig :: FeedMonad
defaultConfig = FeedMonad
{ feeds = []
, filters = id
, refreshTime = Minutes 30
, secretToken = "i am a secret"
}
updateFeeds :: FeedMonad -> App ()
updateFeeds f = do
mgr <- asks environmentManager
st <- asks environmentAcidState
for_ (feeds f) $ \c -> for_ c $ \fid -> liftIO $ do
let FeedId u = fid
entries <- maybe [] parseEntries . parseFeedSource <$> liftIO (execute mgr (fetch u))
finalEntries <- foldMap (\e -> M.singleton (EntryId $ entryURL e) e) <$> traverse (execute mgr . filters f pure) entries
newEntries <- query st (UnseenEntries fid (M.keysSet finalEntries))
update st (SaveEntries fid (foldMap (\eid -> toList $ M.lookup eid finalEntries) newEntries))
queryCategory :: FeedMonad -> App [Category (FeedId, Int)]
queryCategory = traverse (traverse q) . feeds
where
q :: FeedId -> App (FeedId, Int)
q fid = do
st <- asks environmentAcidState
(fid, ) <$> liftIO (query st (CountEntries fid))
defaultMain :: FeedMonad -> IO ()
defaultMain f =
bracket (openLocalState emptyFeedMonadState) closeAcidState $ \st -> do
mgr <- newTlsManager
runApp (Environment mgr st) $ do
updateFeeds f
cat <- queryCategory f
liftIO $ print cat