packages feed

cqrs-example-0.9.0: src/CQRSExample/Routing.hs

module CQRSExample.Routing
       ( toServerEvent
       , routes
       ) where

import           Blaze.ByteString.Builder.Char8 (fromLazyText)
import           Control.Monad (forever, void)
import           Control.Monad.IO.Class (liftIO)
import           Control.Monad.Trans.Class (lift)
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.Conduit (yield)
import           Data.CQRS.Command (GUID, EventStoreBackend, Repository, runUnitOfWorkT)
import           Data.CQRS.GUID (hexDecode)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TLB
import           Network.Wai.EventSource (ServerEvent(..), sourceToSource)
import           Network.Wai.Middleware.Static (staticPolicy, addBase)
import           Web.Scotty (ScottyM, get, source, header, post, json, middleware, param, redirect, Parsable(..))

import qualified CQRSExample.Command as C
import           CQRSExample.Events
import           CQRSExample.Instances ()
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 = [fromLazyText $ TLB.toLazyText $ AE.fromValue $ toJSON j]

-- Need parsing for GUIDs
instance Parsable GUID where
    parseParam text =
      case hexDecode $ TE.encodeUtf8 $ TL.toStrict text of
        Just g -> Right g
        Nothing -> Left "Could not parse GUID"

-- Example application
routes :: (EventStoreBackend b) => TVar QueryState -> Repository Event b -> 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
    header "Content-Type" "text/event-stream"
    source $ sourceToSource $ do
      chan <- lift $ lift $ atomically $ dupTChan serverEvents
      forever $ do
        e <- lift $ lift $ atomically $ readTChan chan
        yield e

  where
    run unitOfWork = do
        void $ liftIO $ runUnitOfWorkT repository $ unitOfWork
        json Null