esqueleto/examples/Blog.hs

222 lines
7.3 KiB
Haskell

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Main
( main
) where
-------------------------------------------------------------------------------
import Control.Monad (void)
import Control.Monad.Base (MonadBase (..))
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Logger (MonadLogger, NoLoggingT (..))
import Control.Monad.Reader
import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..),
MonadTransControl (..),
defaultLiftBaseWith,
defaultRestoreM)
import Data.Text (Text)
import Database.Esqueleto
import Database.Persist.Postgresql (ConnectionString,
withPostgresqlConn)
import Database.Persist.TH (mkDeleteCascade, mkMigrate,
mkPersist, persistLowerCase,
share, sqlSettings)
import Lens.Micro ((&), (.~))
-------------------------------------------------------------------------------
share [mkPersist sqlSettings, mkDeleteCascade sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int Maybe
deriving Eq Show
BlogPost
title String
authorId PersonId
deriving Eq Show
Follow
follower PersonId
followed PersonId
deriving Eq Show
|]
-------------------------------------------------------------------------------
putPersons :: (MonadIO m, MonadLogger m)
=> SqlPersistT m ()
putPersons = do
people <- select $
from $ \person -> do
return person
liftIO $ mapM_ (putStrLn . ("Name: " ++) . personName . entityVal) people
-------------------------------------------------------------------------------
getJohns :: (MonadIO m, MonadLogger m)
=> SqlReadT m [Entity Person]
getJohns =
select $
from $ \p -> do
where_ (p ^. PersonName ==. val "John")
return p
-------------------------------------------------------------------------------
getJaoas :: (MonadIO m, MonadLogger m)
=> SqlReadT m [Entity Person]
getJaoas =
select $
from $ \p -> do
where_ (p ^. PersonName ==. val "João" ||. p ^. PersonName ==. val "Joao")
return p
-------------------------------------------------------------------------------
getAdults :: (MonadIO m, MonadLogger m)
=> SqlReadT m [Entity Person]
getAdults =
select $
from $ \p -> do
where_ (p ^. PersonAge >=. just (val 18))
return p
-------------------------------------------------------------------------------
getBlogPostsByAuthors :: (MonadIO m, MonadLogger m)
=> SqlReadT m [(Entity BlogPost, Entity Person)]
getBlogPostsByAuthors =
select $
from $ \(b, p) -> do
where_ (b ^. BlogPostAuthorId ==. p ^. PersonId)
orderBy [asc (b ^. BlogPostTitle)]
return (b, p)
-------------------------------------------------------------------------------
getAuthorMaybePosts :: (MonadIO m, MonadLogger m)
=> SqlReadT m [(Entity Person, Maybe (Entity BlogPost))]
getAuthorMaybePosts =
select $
from $ \(p `LeftOuterJoin` mb) -> do
on (just (p ^. PersonId) ==. mb ?. BlogPostAuthorId)
orderBy [asc (p ^. PersonName), asc (mb ?. BlogPostTitle)]
return (p, mb)
-------------------------------------------------------------------------------
followers :: (MonadIO m, MonadLogger m)
=> SqlReadT m [(Entity Person, Entity Follow, Entity Person)]
followers =
select $
from $ \(p1 `InnerJoin` f `InnerJoin` p2) -> do
on (p2 ^. PersonId ==. f ^. FollowFollowed)
on (p1 ^. PersonId ==. f ^. FollowFollower)
return (p1, f, p2)
-------------------------------------------------------------------------------
updateJoao :: (MonadIO m, MonadLogger m)
=> SqlWriteT m ()
updateJoao =
update $ \p -> do
set p [ PersonName =. val "João" ]
where_ (p ^. PersonName ==. val "Joao")
-------------------------------------------------------------------------------
deleteYoungsters :: (MonadIO m, MonadLogger m)
=> SqlWriteT m ()
deleteYoungsters = do
delete $
from $ \p -> do
where_ (p ^. PersonAge <. just (val 14))
-------------------------------------------------------------------------------
insertBlogPosts :: (MonadIO m, MonadLogger m)
=> SqlWriteT m ()
insertBlogPosts =
insertSelect $ from $ \p ->
return $ BlogPost <# (val "Group Blog Post") <&> (p ^. PersonId)
-------------------------------------------------------------------------------
cleanDB :: (MonadIO m, MonadLogger m)
=> SqlPersistT m ()
cleanDB = do
rawExecute "DROP TABLE follow" []
rawExecute "DROP TABLE blog_post" []
rawExecute "DROP TABLE person" []
testDb :: (MonadIO m, MonadLogger m)
=> SqlPersistT m ()
testDb = do
john <- insert $ Person "John" (Just 24)
sean <- insert $ Person "Seán" (Just 70)
joao <- insert $ Person "Joao" (Just 13)
void $ insertMany [ BlogPost "How to play a bodhrán" sean
, BlogPost "Haskell for the working class hero" john
]
void $ insert $ Follow john sean
void $ insert $ Follow sean john
void $ insert $ Follow joao sean
-------------------------------------------------------------------------------
newtype BlogT m a = BlogT { unBlogT :: NoLoggingT (ReaderT ConnectionString m) a }
deriving (Functor, Applicative, Monad, MonadLogger, MonadReader ConnectionString, MonadIO)
instance MonadTrans BlogT where
lift = BlogT . lift . lift
deriving instance (MonadBase b m) => MonadBase b (BlogT m)
instance MonadBaseControl b m => MonadBaseControl b (BlogT m) where
type StM (BlogT m) a = ComposeSt BlogT m a
liftBaseWith = defaultLiftBaseWith
restoreM = defaultRestoreM
instance MonadTransControl BlogT where
type StT BlogT a = StT NoLoggingT (StT (ReaderT ConnectionString) a)
liftWith f = BlogT $ liftWith $ \run ->
liftWith $ \run' ->
f (run' . run . unBlogT)
restoreT = BlogT . restoreT . restoreT
runBlogT :: ConnectionString -> BlogT m a -> m a
runBlogT backend (BlogT m) =
runReaderT (runNoLoggingT m) backend
type Blog a = BlogT IO a
runDB :: (MonadReader ConnectionString m, MonadIO m, MonadBaseControl IO m, Monad m, MonadLogger m)
=> SqlPersistT m a -> m a
runDB query = do
conn <- ask
withPostgresqlConn conn $ \backend -> runReaderT query backend
setupBlog :: (MonadIO m, MonadLogger m)
=> SqlPersistT m ()
setupBlog = do
runMigration migrateAll
testDb
main :: IO ()
main =
let connection = "host=localhost port=5433 user=postgres dbname=esqueleto_blog_example password=***"
in runBlogT connection . runDB $ do
setupBlog
putPersons
cleanDB