Prepare for some persistence

This commit is contained in:
Mats Rauhala 2021-11-11 22:08:46 +02:00
parent b73c4cfd1b
commit a788a188fe
9 changed files with 132 additions and 15 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
dist-newstyle dist-newstyle
.envrc .envrc
/FeedMonad/state/

View File

@ -3,7 +3,14 @@ module Main where
import FeedMonad import FeedMonad
import Data.Category import Data.Category
import Data.Entry (URL(URL))
myFeeds :: [ Category URL ]
myFeeds =
[ Category "News"
[ Leaf (URL "https://github.com/feediron/feediron-recipes/commits/master.atom")]
]
main :: IO () main :: IO ()
main = do main = do
defaultMain defaultConfig{feeds = [ Category "News" [] ]} defaultMain defaultConfig{feeds = myFeeds }

View File

@ -23,12 +23,15 @@ extra-source-files: CHANGELOG.md
library library
exposed-modules: MyLib exposed-modules: MyLib
Trace Data.Category
FeedMonad
Data.Entry Data.Entry
Database
FeedMonad
Middleware Middleware
Paths_FeedMonad Paths_FeedMonad
Data.Category Trace
Data.Environment
Control.Monad.App
-- Modules included in this library but not exported. -- Modules included in this library but not exported.
-- other-modules: -- other-modules:

View File

@ -1,4 +1,4 @@
{ mkDerivation, acid-state, base, bytestring, containers, dyre { mkDerivation, acid-state, base, bytestring, containers
, http-client, http-client-tls, lens, lib, mtl, safecopy, servant , http-client, http-client-tls, lens, lib, mtl, safecopy, servant
, servant-server, text, xdg-basedir , servant-server, text, xdg-basedir
}: }:
@ -6,14 +6,10 @@ mkDerivation {
pname = "FeedMonad"; pname = "FeedMonad";
version = "0.1.0.0"; version = "0.1.0.0";
src = ./.; src = ./.;
isLibrary = true;
isExecutable = true;
libraryHaskellDepends = [ libraryHaskellDepends = [
acid-state base bytestring containers dyre http-client acid-state base bytestring containers http-client http-client-tls
http-client-tls lens mtl safecopy servant servant-server text lens mtl safecopy servant servant-server text xdg-basedir
xdg-basedir
]; ];
executableHaskellDepends = [ base ];
license = "unknown"; license = "unknown";
hydraPlatforms = lib.platforms.none; hydraPlatforms = lib.platforms.none;
} }

View File

@ -0,0 +1,17 @@
{-# LANGUAGE DerivingVia #-}
module Control.Monad.App where
import Data.Environment (Environment)
import Control.Monad.Reader ( MonadIO, MonadReader, ReaderT(..) )
newtype App a = App ( ReaderT Environment IO a )
deriving
( Functor
, Applicative
, Monad
, MonadReader Environment
, MonadIO
) via ReaderT Environment IO
runApp :: Environment -> App a -> IO a
runApp env (App f) = runReaderT f env

View File

@ -1,13 +1,22 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TemplateHaskell #-}
module Data.Entry where module Data.Entry where
import Data.ByteString.Lazy (ByteString) import Data.ByteString.Lazy (ByteString)
import Data.Set (Set) import Data.Set (Set)
import Data.Text (Text) import Data.Text (Text)
import Control.Lens import Control.Lens
import Data.SafeCopy
import GHC.Generics (Generic)
newtype URL = URL String newtype URL = URL String
deriving (Show) deriving (Show, Eq, Ord, Generic)
instance SafeCopy URL
newtype Tag = Tag Text newtype Tag = Tag Text
deriving (Show, Eq, Ord) deriving (Show, Eq, Ord, Generic)
instance SafeCopy Tag
data Entry = Entry data Entry = Entry
{ entryURL :: URL { entryURL :: URL
@ -16,7 +25,7 @@ data Entry = Entry
, entryScore :: Int , entryScore :: Int
, entryTags :: Set Tag , entryTags :: Set Tag
} }
deriving Show deriving (Eq, Ord, Show)
-- * Lenses for accessing relevant parts of entry -- * Lenses for accessing relevant parts of entry
@ -38,3 +47,7 @@ score = lens entryScore (\ en n -> en{entryScore=n})
tags :: Lens' Entry (Set Tag) tags :: Lens' Entry (Set Tag)
tags = lens entryTags (\ en txts -> en{entryTags=txts}) tags = lens entryTags (\ en txts -> en{entryTags=txts})
-- * TH generated instances
deriveSafeCopy 0 'base ''Entry

View File

@ -0,0 +1,9 @@
module Data.Environment where
import Network.HTTP.Client (Manager)
import Database (FeedMonadState)
import Data.Acid (AcidState)
data Environment = Environment
{ environmentManager :: Manager
, environmentAcidState :: AcidState FeedMonadState
}

61
FeedMonad/src/Database.hs Normal file
View File

@ -0,0 +1,61 @@
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeFamilies #-}
module Database
(
-- * Internal State
FeedMonadState
, emptyFeedMonadState
-- * Update functions
, SaveEntry(..)
-- * Query functions
, GetEntries(..)
, GetEntry(..)
)
where
import Data.Entry
import Data.Map.Strict (Map)
import Data.SafeCopy
import GHC.Generics (Generic)
import Data.Acid
import Control.Lens
import Control.Applicative ((<|>))
import Control.Monad.Reader (asks)
newtype FeedId = FeedId URL
deriving (Show, Eq, Ord, Generic)
instance SafeCopy FeedId
newtype EntryId = EntryId URL
deriving (Show, Eq, Ord, Generic)
instance SafeCopy EntryId
newtype FeedMonadState = FeedMonadState
{ _feeds :: Map FeedId (Map EntryId Entry) }
deriving Show
emptyFeedMonadState :: FeedMonadState
emptyFeedMonadState = FeedMonadState mempty
feeds :: Lens' FeedMonadState (Map FeedId (Map EntryId Entry))
feeds = lens _feeds (\ fms f -> fms{_feeds=f})
-- | Save the entry to the database
saveEntry :: FeedId -> EntryId -> Entry -> Update FeedMonadState ()
saveEntry fid eid en = modifying (feeds . at fid . non mempty . at eid) (<|> Just en)
getEntries :: FeedId -> Query FeedMonadState [Entry]
getEntries fid = asks (toListOf (feeds . ix fid . traversed))
getEntry :: FeedId -> EntryId -> Query FeedMonadState (Maybe Entry)
getEntry fid eid = asks (preview (feeds . ix fid . ix eid))
deriveSafeCopy 0 'base ''FeedMonadState
makeAcidic ''FeedMonadState ['saveEntry, 'getEntries, 'getEntry]

View File

@ -7,6 +7,13 @@ import Data.Entry (URL)
import Middleware (Middleware) import Middleware (Middleware)
import Numeric.Natural (Natural) import Numeric.Natural (Natural)
import Data.Category (Category) import Data.Category (Category)
import Data.Acid (openLocalState, AcidState (closeAcidState))
import Control.Exception (bracket)
import Database (emptyFeedMonadState)
import Network.HTTP.Client.TLS (newTlsManager)
import Control.Monad.App (runApp)
import Data.Environment
import Control.Monad.Trans (liftIO)
newtype Minutes = Minutes Natural newtype Minutes = Minutes Natural
@ -34,4 +41,7 @@ defaultConfig = FeedMonad
defaultMain :: FeedMonad -> IO () defaultMain :: FeedMonad -> IO ()
defaultMain f = defaultMain f =
print $ feeds f bracket (openLocalState emptyFeedMonadState) closeAcidState $ \st -> do
mgr <- newTlsManager
runApp (Environment mgr st) $
liftIO $ print $ feeds f