Spock 0.2.0.0 → 0.3.0.0
raw patch · 5 files changed
+113/−26 lines, 5 files
Files
- Spock.cabal +3/−2
- Web/Spock.hs +27/−21
- Web/Spock/Cookie.hs +3/−0
- Web/Spock/Monad.hs +6/−3
- Web/Spock/Types.hs +74/−0
Spock.cabal view
@@ -1,5 +1,5 @@ name: Spock-version: 0.2.0.0+version: 0.3.0.0 synopsis: Another Haskell web toolkit based on scotty description: This toolbox provides everything you need to get a quick start into web hacking with haskell: sessions, database helper, authentication and the power of scotty Homepage: https://github.com/agrafix/Spock@@ -16,7 +16,8 @@ exposed-modules: Web.Spock other-modules: Web.Spock.SessionManager, Web.Spock.Monad,- Web.Spock.Cookie+ Web.Spock.Cookie,+ Web.Spock.Types build-depends: base >= 4 && < 5, scotty >= 0.5, wai ==1.4.*,
Web/Spock.hs view
@@ -4,12 +4,20 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} module Web.Spock- ( -- * Spock's core functions, types and helpers- spock, authed, runQuery, getState, Http.StdMethod (..), SpockM, SpockAction- , authedUser, unauthCurrent, StorageLayer (..), PoolCfg (..)- , setCookie, getCookie- -- * Reexports from scotty- , middleware, get, post, put, delete, patch, addroute, matchAny, notFound+ ( -- * Spock's core+ spock, SpockM, SpockAction+ -- * Database+ , runQuery, PoolOrConn (..), ConnBuilder (..), PoolCfg (..)+ -- * Routing+ , authed, get, post, put, delete, patch, addroute, Http.StdMethod (..)+ -- * Cookies+ , setCookie, setCookie', getCookie+ -- * Sessions+ , authedUser, unauthCurrent+ -- * State+ , getState+ -- * Other reexports from scotty+ , middleware, matchAny, notFound , request, reqHeader, body, param, params, jsonData, files , status, addHeader, setHeader, redirect , text, html, file, json, source, raw@@ -27,26 +35,24 @@ import Control.Monad.Trans.Reader import Control.Monad.Trans.Resource import Data.Pool-import Data.Time.Clock import Web.Scotty.Trans import qualified Data.Text as T import qualified Network.HTTP.Types as Http -data PoolCfg- = PoolCfg- { pc_stripeCount :: Int- , pc_keepOpenSec :: NominalDiffTime- , pc_resPerStripe :: Int- }- deriving (Show, Eq)---- | Run a spock application using the warp server, a given db storageLayer and an initial state-spock :: Int -> PoolCfg -> StorageLayer conn -> st -> SpockM conn sess st () -> IO ()-spock port pc storageLayer initialState defs =+-- | Run a spock application using the warp server, a given db storageLayer and an initial state.+-- Spock works with database libraries that already implement connection pooling and+-- with those that don't come with it out of the box. For more see the 'PoolOrConn' type.+spock :: Int -> PoolOrConn conn -> st -> SpockM conn sess st () -> IO ()+spock port poolOrConn initialState defs = do sessionMgr <- openSessionManager- connectionPool <- createPool (sl_createConn storageLayer)- (sl_closeConn storageLayer) (pc_stripeCount pc)- (pc_keepOpenSec pc) (pc_resPerStripe pc)+ connectionPool <- case poolOrConn of+ PCPool p ->+ return p+ PCConn cb ->+ let pc = cb_poolConfiguration cb+ in createPool (cb_createConn cb) (cb_destroyConn cb)+ (pc_stripes pc) (pc_keepOpenTime pc)+ (pc_resPerStripe pc) let internalState = WebState { web_dbConn = connectionPool
Web/Spock/Cookie.hs view
@@ -11,6 +11,7 @@ import qualified Data.Text.Lazy as TL import qualified Network.Wai as Wai +-- | Read a cookie previously set in the users browser for your site getCookie :: MonadIO m => T.Text -> ActionT m (Maybe T.Text) getCookie name = do req <- request@@ -21,11 +22,13 @@ parseCookies = map parseCookie . T.splitOn ";" . T.concat . T.words parseCookie = first T.init . T.breakOnEnd "=" +-- | Set a cookie living for a given number of seconds setCookie :: MonadIO m => T.Text -> T.Text -> NominalDiffTime -> ActionT m () setCookie name value validSeconds = do now <- liftIO getCurrentTime setCookie' name value (validSeconds `addUTCTime` now) +-- | Set a cookie living until a specific 'UTCTime' setCookie' :: MonadIO m => T.Text -> T.Text -> UTCTime -> ActionT m () setCookie' name value validUntil = let formattedTime =
Web/Spock/Monad.hs view
@@ -2,12 +2,10 @@ module Web.Spock.Monad where import Web.Spock.Types-import Web.Spock.SessionManager import Control.Applicative import Control.Monad import Control.Monad.Reader-import Control.Monad.Trans.Resource import Data.Pool import Data.Time.Clock ( UTCTime(..) ) import Web.Scotty.Trans@@ -20,12 +18,17 @@ webM :: MonadTrans t => WebStateM conn sess st a -> t (WebStateM conn sess st) a webM = lift -runQuery :: MonadTrans t => (conn -> IO a) -> t (WebStateM conn sess st) a+-- | Give you access to a database connectin from the connection pool. The connection is+-- released back to the pool once the function terminates.+runQuery :: MonadTrans t+ => (conn -> IO a) -> t (WebStateM conn sess st) a runQuery query = webM $ do pool <- asks web_dbConn liftIO $ withResource pool query +-- | Read the application's state. If you wish to have immutable state, you could+-- use a 'TVar' from the STM packge. getState :: MonadTrans t => t (WebStateM conn sess st) st getState = webM $ asks web_state
+ Web/Spock/Types.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+module Web.Spock.Types where++import Web.Scotty.Trans++import Control.Applicative+import Control.Concurrent.STM+import Control.Monad.Reader+import Control.Monad.Trans.Resource+import Data.Pool+import Data.Time.Clock ( UTCTime(..), NominalDiffTime(..) )+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T++-- | Spock is supercharged Scotty, that's why the 'SpockM' is built on the+-- ScottyT monad. Insive the SpockM monad, you may define routes and middleware.+type SpockM conn sess st a = ScottyT (WebStateM conn sess st) a++-- | The SpockAction is the monad of all route-actions. You have access+-- to the database, session and state of your application.+type SpockAction conn sess st a = ActionT (WebStateM conn sess st) a++-- | If Spock should take care of connection pooling, you need to configure+-- it depending on what you need.+data PoolCfg+ = PoolCfg+ { pc_stripes :: Int+ , pc_resPerStripe :: Int+ , pc_keepOpenTime :: NominalDiffTime+ }++-- | The ConnBuilder instructs Spock how to create or close a database connection.+data ConnBuilder a+ = ConnBuilder+ { cb_createConn :: IO a+ , cb_destroyConn :: a -> IO ()+ , cb_poolConfiguration :: PoolCfg+ }++-- | You can feed Spock with either a connection pool, or instructions on how to build+-- a connection pool. See 'ConnBuilder'+data PoolOrConn a+ = PCPool (Pool a)+ | PCConn (ConnBuilder a)++data WebState conn sess st+ = WebState+ { web_dbConn :: Pool conn+ , web_sessionMgr :: SessionManager sess+ , web_state :: st+ }++newtype WebStateM conn sess st a = WebStateM { runWebStateM :: ReaderT (WebState conn sess st) (ResourceT IO) a }+ deriving (Monad, Functor, Applicative, MonadIO, MonadReader (WebState conn sess st))++type SessionId = T.Text+data Session a+ = Session+ { sess_id :: SessionId+ , sess_validUntil :: UTCTime+ , sess_data :: a+ }+type UserSessions a = TVar (HM.HashMap SessionId (Session a))++data SessionManager a+ = SessionManager+ { sm_loadSession :: SessionId -> IO (Maybe (Session a))+ , sm_sessionFromCookie :: MonadIO m => ActionT m (Maybe (Session a))+ , sm_createCookieSession :: MonadIO m => a -> ActionT m ()+ , sm_newSession :: a -> IO (Session a)+ , sm_deleteSession :: SessionId -> IO ()+ }