module Snap.Snaplet.ActionLog.Resource
( actionLogR
, indexH
, showH
, actionLogSplices
, actionLogISplices
) where
import Blaze.ByteString.Builder
import Blaze.ByteString.Builder.Char8
import Control.Error
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Monoid
import qualified Data.Readable as R
import Data.Text (Text)
import qualified Data.Text as T
import Database.Persist
import Heist
import Heist.Compiled
import qualified Heist.Interpreted as I
import Snap
import Snap.Restful
import Snap.Snaplet.ActionLog.API
import Snap.Snaplet.ActionLog.Types
import Snap.Snaplet.Heist.Generic
import Snap.Snaplet.Persistent
import Text.Digestive
import qualified Text.Digestive as Form
import qualified Text.Digestive.Heist as DHI
import Text.Digestive.Heist.Compiled
import Text.Digestive.Snap hiding (method)
import qualified Text.Digestive.Snap as Form
import qualified Text.XmlHtml as X
snapletRender :: HasHeist b => ByteString -> Handler b v ()
snapletRender name = do
root <- getSnapletRootURL
let p = B.intercalate "/" $ filter (not . B.null) [root, name]
gRender p
indexH :: HasHeist b => Handler b v ()
indexH = snapletRender "index"
showH :: HasHeist b => Handler b v ()
showH = snapletRender "show"
actionLogR :: Resource
actionLogR = Resource
{ rName = "actionlog"
, rRoot = ""
, rResourceEndpoints = []
, rItemEndpoints = []
}
data LogFilter = LogFilter
{ filterUser :: Maybe Int
, filterEntity :: Maybe Text
, filterEntityId :: Maybe Int
, filterAction :: Maybe ActionType
} deriving (Show)
instance Monoid LogFilter where
mempty = LogFilter Nothing Nothing Nothing Nothing
mappend (LogFilter u1 e1 i1 a1) (LogFilter u2 e2 i2 a2) =
LogFilter (getFirst $ mappend (First u1) (First u2))
(getFirst $ mappend (First e1) (First e2))
(getFirst $ mappend (First i1) (First i2))
(getFirst $ mappend (First a1) (First a2))
mkFilters :: LogFilter -> [Filter LoggedAction]
mkFilters (LogFilter u e eid a) =
maybe [] (\x -> [LoggedActionUserId ==. x]) u ++
maybe [] (\x -> [LoggedActionEntityName ==. x]) e ++
maybe [] (\x -> [LoggedActionEntityId ==. x]) eid ++
maybe [] (\x -> [LoggedActionAction ==. x]) a
disableOnJust :: (Maybe a -> Form v m b) -> Maybe a -> Form v m b
disableOnJust f Nothing = f Nothing
disableOnJust f def = disable $ f def
logFilterForm :: HasActionLog m
=> Bool
-> Maybe LogFilter -> Form Text m LogFilter
logFilterForm isDisabling d = monadic $ do
entities <- getTenantEntities
let entityPairs = noFilter : map (\x -> (Just x,x)) entities
uids <- getTenantUids
names <- mapM alGetName uids
let userPairs = noFilter : (map firstJust $ zip uids names)
return $ LogFilter
<$> "user" .: choice userPairs ?$ (filterUser <$> d)
<*> "entity" .: choice entityPairs ?$ (filterEntity <$> d)
<*> "entity-id" .: optionalStringRead "id must be an int" ?$
(filterEntityId =<< d)
<*> "action" .: choice actions ?$ (filterAction <$> d)
where
noFilter = (Nothing, "Any")
firstJust (k,u) = (Just k, u)
actions = noFilter : map (\x -> (Just x,T.pack $ show x)) [minBound..maxBound]
infixr 6 ?$
(?$) :: (Maybe a -> Form v m b) -> Maybe a -> Form v m b
(?$) = if isDisabling then disableOnJust else ($)
logFilterFormName :: Text
logFilterFormName = "log-filter-form"
runLogFilterForm :: (HasActionLog m, MonadSnap m)
=> Bool -> Maybe LogFilter -> m (View Text, Maybe LogFilter)
runLogFilterForm isDisabling def =
runFormWith cfg logFilterFormName (logFilterForm isDisabling def)
where
cfg = defaultSnapFormConfig { Form.method = Just Form.Post }
actionLogSplices :: (HasActionLog n, MonadSnap n)
=> Resource -> [(Text, Splice n)]
actionLogSplices r =
[ ("actionDetails", actionViewSplice r)
, ("defaultActions", defaultActionsSplice r)
]
++ applyDeferred (return mempty) (coupledSplices r False)
coupledSplices :: (HasActionLog n, MonadSnap n)
=> Resource -> Bool -> [(Text, Promise LogFilter -> Splice n)]
coupledSplices r b =
[ ("actionlogListing", actionsSplice r (runLogFilterForm b))
, ("actionlogFilterForm", logFilterFormSplice (runLogFilterForm b))
]
getFilterFunc :: Monad n => HeistT n IO (RuntimeSplice n LogFilter)
getFilterFunc = do
n <- getParamNode
attrFunc <- runAttributesRaw $ X.elementAttrs n
return $ do
as <- attrFunc
return $ filterCommon as
filterCommon :: [(Text, Text)] -> LogFilter
filterCommon as =
LogFilter
(R.fromText =<< lookup "uid" as)
(lookup "entity" as)
(R.fromText =<< lookup "entity-id" as)
(R.fromText =<< lookup "action" as)
defaultActionsSplice :: (MonadSnap m, HasActionLog m) => Resource -> Splice m
defaultActionsSplice r = do
filterFunc <- getFilterFunc
withSplices runChildren (coupledSplices r True) filterFunc
actionFromId :: (MonadSnap m, HasPersistPool m)
=> m (Maybe (Entity LoggedAction))
actionFromId = runMaybeT $ do
idBS <- MaybeT $ getParam "id"
_id <- R.fromBS idBS
let key = mkKey _id
action <- MaybeT $ getLoggedAction key
return $ Entity key action
actionViewSplice :: (HasActionLog n, MonadSnap n) => Resource -> Splice n
actionViewSplice r = manyWithSplices runChildren (actionSplices r) $ do
ma <- lift actionFromId
return $ maybe [] (:[]) ma
actionsSplice :: HasActionLog n
=> Resource
-> (Maybe a -> n (t, Maybe LogFilter))
-> Promise LogFilter
-> Splice n
actionsSplice res form prom = manyWithSplices runChildren (actionSplices res) $ do
f <- getPromise prom
(_,r) <- lift $ form Nothing
let filters = case r of
Nothing -> []
Just lf -> mkFilters (f `mappend` lf)
lift $ getTenantActions filters []
applyDeferred :: Monad n
=> RuntimeSplice n a
-> [(Text, Promise a -> Splice n)]
-> [(Text, Splice n)]
applyDeferred m = applySnd m . mapSnd defer
actionSplices :: HasActionLog n
=> Resource
-> [(Text, Promise (Entity LoggedAction) -> Splice n)]
actionSplices r =
("loggedActionUserName", runtimeToPromise getUserName) :
("loggedActionDetails", detailsSplice) :
(pureSplices loggedActionCSplices ++
alCustomCSplices ++
repromise (return . DBId . mkWord64 . entityKey)
(pureSplices $ textSplices $ itemCSplices r)
)
where
getUserName = return . fromText <=< alGetName . loggedActionUserId . entityVal
detailsSplice prom =
manyWithSplices runChildren (pureSplices detailsCSplices)
(lift . getActionDetails . entityKey =<< getPromise prom)
runtimeToPromise :: (Monad n) => (t -> n Builder) -> Promise t -> Splice n
runtimeToPromise f p = return $ yieldRuntime $ do
entity <- getPromise p
lift $ f entity
logFilterFormSplice :: Monad m
=> (Maybe a -> m (View Text, b))
-> Promise a
-> Splice m
logFilterFormSplice form prom =
formSplice [] [] $ do
f <- getPromise prom
lift $ liftM fst $ form (Just f)
logFilterFormISplice :: MonadIO m
=> (Maybe a -> m (View Text, t))
-> a -> HeistT m m Template
logFilterFormISplice form f = do
(v,_) <- lift $ form (Just f)
localHS (DHI.bindDigestiveSplices v) (DHI.dfForm v >>= I.runNodeList)
actionLogISplices :: (HasActionLog n, MonadSnap n)
=> Resource -> [(Text, I.Splice n)]
actionLogISplices r =
[ ("actionDetails", actionDetailsISplice r)
, ("defaultActions", defaultActionsISplice r)
]
++ coupledISplices r False mempty
coupledISplices :: (HasActionLog m, MonadSnap m)
=> Resource -> Bool -> LogFilter -> [(Text, I.Splice m)]
coupledISplices r b f =
[ ("actionlogListing", actionLogListingISplice r (runLogFilterForm b) f)
, ("actionlogFilterForm", logFilterFormISplice (runLogFilterForm b) f)
]
actionDetailsISplice :: (HasActionLog n, MonadSnap n)
=> Resource -> I.Splice n
actionDetailsISplice r = do
ma <- lift $ actionFromId
maybe (return []) (I.runChildrenWith . actionISplices r) ma
actionLogListingISplice :: HasActionLog m
=> Resource
-> (Maybe a -> m (t, Maybe LogFilter))
-> LogFilter
-> I.Splice m
actionLogListingISplice res form f = do
(_,r) <- lift $ form Nothing
let filters = case r of
Nothing -> []
Just lf -> mkFilters (f `mappend` lf)
actions <- lift $ getTenantActions filters []
I.mapSplices (I.runChildrenWith . actionISplices res) actions
actionISplices :: HasActionLog m
=> Resource
-> Entity LoggedAction
-> [(Text, I.Splice m)]
actionISplices r e =
("loggedActionUserName", I.textSplice =<< getUserName) :
("loggedActionDetails", detailsISplice) :
(loggedActionISplices (entityVal e) ++
alCustomISplices e ++
itemSplices r (DBId $ mkWord64 $ entityKey e)
)
where
getUserName = lift $ alGetName $ loggedActionUserId $ entityVal e
detailsISplice = do
ds <- lift $ getActionDetails $ entityKey e
I.mapSplices (I.runChildrenWith . detailsISplices . entityVal) ds
defaultActionsISplice :: (MonadSnap m, HasActionLog m)
=> Resource -> I.Splice m
defaultActionsISplice r = do
n <- getParamNode
let f = filterCommon $ X.elementAttrs n
I.runChildrenWith $ coupledISplices r True f