packages feed

typed-admin-0.1.0.0: src/TypedAdmin.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE ScopedTypeVariables #-}

module TypedAdmin
  ( module TypedAdmin.Class
  , module TypedAdmin.Instance
  , module TypedAdmin.Router
  , module TypedAdmin.Extra
  , module TypedAdmin
  ) where

import           Control.Monad.State.Class
import           Control.Monad.State.Strict
import qualified Data.ByteString.Lazy.UTF8  as LBS
import qualified Data.ByteString.UTF8       as BS
import           Data.Maybe
import           Data.Proxy
import           Data.Text                  (unpack)
import           Data.Yaml                  as Y
import           Lucid
import           Network.HTTP.Types
import           Network.Wai
import           Network.Wai.Parse
import           TypedAdmin.Class
import           TypedAdmin.Extra
import           TypedAdmin.Instance
import           TypedAdmin.Router
import           TypedAdmin.Util

type Layout m =  HtmlT m () -> HtmlT m ()

runHandler' ctx a = evalStateT (runHandler a) ctx

admin :: forall m a.
  MonadState Context m => [Route m]
  -> (forall x. m x -> Handler x)
  -> Maybe (Layout m)
  -> Maybe Dic
  -> Middleware
admin rt nt layout dic = \app -> \req res -> do
  let ctx = Context dic []
  case (requestMethod req, pathInfo req) of
    (method, ps) -> do
      let
        f "GET" (ListR path p1 p2) =
          case fromPath path (Prelude.reverse ps) of
            Just () ->
              Just $ handleListConsole nt req res (path, ()) layout ctx p1 p2
            _ -> Nothing
        f "GET" (DetailR path p1) = do
          case fromPath path (Prelude.reverse ps) of
            Just x ->
              Just $ handleDetailConsole nt req res p1 ctx x
            _ -> Nothing
        f "GET" (CreateR path p1 p2) = do
          case fromPath (StaticP "_create" path) (Prelude.reverse ps) of
            Just () ->
              Just $ handleCreateConsole nt req res (path, ()) layout ctx p1 p2
            _ -> Nothing
        f "POST" (CreateR path p1 p2) = do
          case fromPath (path) (Prelude.reverse ps) of
            Just () ->
              Just $ handleCreate nt req res p1 p2 ctx
            _ -> Nothing
        f "GET" (EditR path p1 p2) = do
          case fromPath (StaticP "_edit" path) (reverse ps) of
            Just x ->
              Just $ handleEditConsole nt req res (path, x) layout p1 p2 ctx x
            _ -> Nothing
        f "POST" (EditR path p1 p2) = do
          handleEdit nt req res p1 p2 ctx <$> fromPath path (reverse ps)
        f "POST" (DeleteR path p1) = do
          case fromPath (StaticP "_delete" path) (reverse ps) of
            Just x ->
              Just $ handleDelete nt req res p1 ctx x
            _ -> Nothing
        f _ _ = Nothing
      case firstJust (f method) rt of
        Just h  -> h
        Nothing -> app req res

-- newtype Handler a = Handler { runHandler :: IO a }

contentType = ("Content-Type", "Content-type: text/html; charset=UTF-8")

-- todo:: receive func like (m - IO)

handleListConsole :: forall proxy1 proxy2 p1 p2 z m a b.
  (ListConsole m p1 p2, PathParam a b, MonadState Context m)
  => (forall x . m x -> Handler x)
  -> Request
  -> (Response -> IO z)
  -> (a, b)
  -> Maybe (Layout m)
  -> Context
  -> proxy1 p1
  -> proxy2 p2
  -> IO z
handleListConsole nt req res (path, param) layout ctx _ _ = do
  let query = queryString req
  let page = fromMaybe 0 $ lookupMaybe "page" query
  let
    f :: m LBS.ByteString
    f = do
      p <- r2m <$> fromForm query
      beers <- (list :: (Maybe p2) -> Page -> m ([p1])) p page
      mtotal <- total p (Proxy :: Proxy p1)
      let body = renderListHtml beers p (path, param, query) (page, mtotal)
      renderBST $ (fromMaybe defaultLayout layout) body
  body <- runHandler' ctx $ nt f
  res $
    responseLBS status200 [contentType] body

r2m (Right x) = Just x
r2m _         = Nothing

handleDetailConsole :: forall proxy1 p1 b m.
  (DetailConsole m p1,  MonadState Context m)
  => (forall x . m x -> Handler x)
  -> Request
  -> (Response -> IO b)
  -> proxy1 p1
  -> Context
  -> Ident p1
  -> IO b
handleDetailConsole nt req res _ ctx rid = do
  mbody <- runHandler' ctx $ nt $ do
    mr <- detail rid :: m (Maybe p1)
    case mr of
      Just r -> do
        Just <$> renderBST (renderDetailHtml r)
      Nothing -> pure Nothing
  case mbody of
     Just x ->
       res $ responseLBS status200 [("Content-Type", "text/html")] x
     Nothing ->
       res404 res

handleCreateConsole :: forall proxy1 proxy2 a b z m c d.
  (CreateConsole m a b, PathParam c d)
  => (forall x. m x -> Handler x)
  -> Request
  -> (Response -> IO z)
  -> (c, d)
  -> Maybe (Layout m)
  -> Context
  -> proxy1 a
  -> proxy2 b
  -> IO z
handleCreateConsole nt req res path layout ctx _ _ = do
  let query = queryString req
  body <- runHandler' ctx $ nt $ do
    mr <- detailForCreate query (Proxy :: Proxy b) :: m (Maybe a)
    case mr of
      Just r ->
        Just <$> (renderBST $ (fromMaybe defaultLayout layout) (toCreateForm r (Proxy :: Proxy b) path))
      Nothing ->
        pure Nothing
  case body of
    Just x ->
      res $ responseLBS status200 [contentType] x
    Nothing -> res404 res

handleCreate :: forall proxy1 proxy2 a b z m. (CreateConsole m a b)
  => (forall x. m x -> Handler x)
  -> Request
  -> (Response -> IO z)
  -> proxy1 a
  -> proxy2 b
  -> Context
  -> IO z
handleCreate nt req res _ _ ctx = do
  (ps, _) <- parseRequestBody lbsBackEnd req
  mx <- runHandler' ctx $ nt $ fromForm (mapSnd Just <$> ps)
  case mx of
    Right x -> do
      path <- runHandler' ctx $ nt $ do
        create (Proxy :: Proxy a) (x :: b)
        createdRedirectPath (Proxy :: Proxy a) (x :: b)
      res $ responseLBS status302 [contentType, ("Location", BS.fromString $ unpack path)] "not found"
    Left x -> res400 res (LBS.fromString x)

handleEditConsole :: forall proxy1 proxy2 a b c d z m.
  (EditConsole m a b, PathParam c d, MonadState Context m)
  => (forall x. m x -> Handler x)
  -> Request
  -> (Response -> IO z)
  -> (c, d)
  -> Maybe (Layout m)
  -> proxy1 a
  -> proxy2 b
  -> Context
  -> EditIdent a b
  -> IO z
handleEditConsole nt req res path layout _ _ ctx rid = do
  body <- runHandler' ctx $ nt $ do
    mr <- detailForEdit (Proxy :: Proxy b) rid :: m (Maybe a)
    case mr of
      Just r ->
        renderBST $ (fromMaybe defaultLayout layout) (renderEditHtml r (Proxy :: Proxy b) rid path)
  res $
    responseLBS status200 [contentType] body

handleEdit :: forall proxy1 proxy2 a b z m. (EditConsole m a b)
  => (forall x. m x -> Handler x)
  -> Request
  -> (Response -> IO z)
  -> proxy1 a
  -> proxy2 b
  -> Context
  -> EditIdent a b
  -> IO z
handleEdit nt req res _ _ ctx rid = do
  (ps, _) <- parseRequestBody lbsBackEnd req
  mx <- runHandler' ctx $ nt $ fromForm (mapSnd Just <$> ps)
  case mx of
    Right x -> do
      editP <- runHandler' ctx $ nt $ do
        edit (Proxy :: Proxy a) rid (x :: b)
        editedRedirectPath (Proxy :: Proxy a) (Proxy :: Proxy b) rid
      res $ responseLBS status302 [contentType, ("Location", BS.fromString $ unpack editP)] "not found"
    Left x -> res400 res (LBS.fromString x)

handleDelete :: forall proxy1 a z m. (DeleteConsole m a)
  => (forall x. m x -> Handler x)
  -> Request
  -> (Response -> IO z)
  -> proxy1 a
  -> Context
  -> DeleteIdent a
  -> IO z
handleDelete nt req res _ ctx rid = do
  (ps, _) <- parseRequestBody lbsBackEnd req
  mx <- runHandler' ctx $ nt $ fromForm (mapSnd Just <$> ps)
  case mx of
    Right x -> do
      editP <- runHandler' ctx $ nt $ do
        delete rid (x :: a)
        deletedRedirectPath rid x
      res $ responseLBS status302 [contentType, ("Location", BS.fromString $ unpack editP)] "not found"
    Left x -> res400 res (LBS.fromString x)

res400 res str = res $ responseLBS status400 [("Content-Type", "text/plain")] str
res404 res = res $ responseLBS status404 [("Content-Type", "text/plain")] "not found"

defaultLayout :: Monad m => HtmlT m a -> HtmlT m a
defaultLayout x = do
  doctypehtml_ $ do
    head_ [] $ return ()
    body_ [] x

loadDictionary :: FilePath -> IO (Either ParseException Dic)
loadDictionary path = do
  decodeFileEither path