packages feed

cqrs-example-0.8.0: src/CQRSExample/Wai.hs

module CQRSExample.Wai
       ( AuthenticatedUser(..)
       , exampleWaiApplication
       ) where

import           Control.Concurrent.Chan (Chan)
import           Control.Monad (void)
import           Control.Monad.Trans.Class (lift)
import           Data.Aeson (ToJSON(..), encode)
import           Data.CQRS
import           Data.CQRS.GUID (hexEncode)
import           Data.Text (Text)
import           Database.HDBC (IConnection)
import           Network.HTTP.Types (status200, status404, status400, status501, StdMethod(..), parseMethod)
import           Network.Wai (Application, Request, Response, pathInfo, responseLBS, requestMethod)
import           Network.Wai.Application.Static (defaultFileServerSettings, staticApp)
import           Network.Wai.EventSource (eventSourceApp, ServerEvent(..))
import           Prelude hiding (mapM)

import           CQRSExample.Aggregates (UserId)
import           CQRSExample.Command
import           CQRSExample.Events
import           CQRSExample.Instances ()
import           CQRSExample.Json
import           CQRSExample.WaiParameters

data AuthenticatedUser = -- TODO: This doesn't really belong here.
  AuthenticatedUser { auUserName :: String
                    , auUserId :: UserId
                    }

exampleWaiApplication :: IConnection c => c -> EventStore Event -> Chan ServerEvent -> AuthenticatedUser -> Application
exampleWaiApplication conn eventStore serverEvents user req =
  case parseMethod (requestMethod req) of
    Right method -> route method
    Left _ -> return $ responseLBS status501 [] ""

  where

    userId = auUserId user

    route method = do
      case (method,pathInfo req) of
        (GET , "events" : _)              -> eventSourceApp serverEvents req
        (_   , "json" : "projects" : _)   -> runParameters (appJsonProjects method) req
        (_   , "json" : "tasks" : _)      -> runParameters (appJsonTasks method) req
        (_   , "json" : "time-sheet" : _) -> runParameters (appJsonTimesheet method) req
        (_   , "json" : "stars" : _)      -> runParameters (appStars method) req
        (GET , _)                         -> staticApp defaultFileServerSettings req
        _                                 -> return notFound

    -- Projects
    appJsonProjects GET =
      lift $ lift $ qProjectListJson conn >>= returnJson
    appJsonProjects POST = do
      pn <- requireText "name"
      pd <- requireText "shortDescription"
      pid <- lift $ lift $ runTransactionT eventStore $ do
        createProject pn pd
      returnJson $ hexEncode pid
    appJsonProjects _ = badRequest

    -- Tasks
    appJsonTasks GET = do
      mProjectId <- lookGUID "projectId"
      lift $ lift $ qTaskListJson userId mProjectId conn >>= returnJson
    appJsonTasks POST = do
      pid <- requireGUID "projectId"
      estimate <- requireDuration "estimate"
      tsd <- requireText "shortDescription"
      tid <- lift $ lift $ runTransactionT eventStore $ do
        createTask pid tsd estimate userId
      returnJson $ hexEncode tid
    appJsonTasks _ = badRequest

    -- Timesheet
    appJsonTimesheet GET = do
      fromDate <- requireDay "fromDate"
      toDate <- requireDay "toDate"
      lift $ lift $ qTimesheetJson fromDate toDate userId conn >>= returnJson
    appJsonTimesheet POST = do
      taskId <- requireGUID "taskId"
      duration <- requireDuration "duration"
      comment <- requireText "comment"
      date <- requireDay "date"
      lift $ lift $ runTransactionT eventStore $ do
        void $ logWork taskId userId date duration comment
      returnJson ("OK" :: Text)
    appJsonTimesheet _ = badRequest

    -- Stars
    appStars POST = do
      taskId <- requireGUID "taskId"
      starred <- requireBool "starred"
      lift $ lift $ runTransactionT eventStore $ void $ do
        case starred of
          True  -> starTask taskId userId
          False -> unstarTask taskId userId
      returnJson ("OK" :: Text)
    appStars _ = badRequest

    badRequest = return $ responseLBS status400 [] ""

returnJson :: Monad m => ToJSON a => a -> m Response
returnJson a = return $ responseLBS status200 [("Content-Type", "text/json")] $ encode a

notFound :: Response
notFound = responseLBS status404 [("Content-Type", "text/plain")] "404 - Not Found"