cqrs-example-0.10.0: src/CQRSExample/Routing.hs
{-# LANGUAGE OverloadedStrings #-}
module CQRSExample.Routing
( toServerEvent
, routes
) where
import Blaze.ByteString.Builder.ByteString (fromLazyByteString)
import Control.Monad (void)
import Control.Monad.IO.Class (liftIO)
import Control.Concurrent.STM (atomically, TChan)
import Control.Concurrent.STM.TChan (dupTChan, readTChan)
import Control.Concurrent.STM.TVar (TVar)
import Data.Aeson.Types (ToJSON(..), Value(..))
import qualified Data.Aeson.Encode as AE
import Data.CQRS.Command (Repository, runCommandT)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL
import Data.UUID.Types (UUID, fromASCIIBytes)
import Network.Wai.EventSource (ServerEvent(..))
import Network.Wai.EventSource.EventStream (eventToBuilder)
import Network.Wai.Middleware.Static (staticPolicy, addBase)
import Web.Scotty (ScottyM, get, stream, setHeader, post, json, middleware, param, redirect, Parsable(..))
import CQRSExample.Aggregates (Task)
import qualified CQRSExample.Commands as C
import CQRSExample.Events
import CQRSExample.Json
import CQRSExample.Query (QueryState)
-- Convert a JSON value to a server event.
toServerEvent :: ToJSON j => j -> ServerEvent
toServerEvent j = ServerEvent Nothing Nothing builders
where builders = [ fromLazyByteString $ AE.encode j ]
-- Need parsing for UUIDs
instance Parsable UUID where
parseParam text =
case fromASCIIBytes $ TE.encodeUtf8 $ TL.toStrict text of
Just uuid -> Right uuid
Nothing -> Left "Could not parse UUID"
-- Routes for the example application.
routes :: TVar QueryState -> Repository Task Event -> TChan ServerEvent -> ScottyM ()
routes qs repository serverEvents = do
-- Middleware to serve static files
middleware $ staticPolicy (addBase "static")
-- Redirect to index
get "/" $ do
redirect "/index.html"
-- Actions
post "/tasks/archive-completed" $ do
run $ C.archiveCompletedTasks qs
post "/tasks/complete" $ do
tid <- param "id"
run $ C.completeTask tid
post "/tasks/reopen" $ do
tid <- param "id"
run $ C.reopenTask tid
get "/tasks" $ do
tasks <- liftIO $ qTaskListJson qs
json tasks
post "/tasks" $ do
title <- fmap T.pack $ param "title"
run $ C.createTask title
-- Notifications
get "/events" $ do
setHeader "Content-Type" "text/event-stream"
stream $ \sendChunk flush -> do
chan <- atomically $ dupTChan serverEvents
loop sendChunk flush chan
where
loop sendChunk flush chan = do
e <- atomically $ readTChan chan
case eventToBuilder e of
Nothing -> return () -- Done
Just builder -> do
void $ sendChunk builder
void $ flush
loop sendChunk flush chan
run command = do
void $ liftIO $ runCommandT repository $ command
json Null