Added blog sample
This commit is contained in:
parent
f2e71a6c00
commit
65d8e2febd
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
|||||||
client_session_key.aes
|
client_session_key.aes
|
||||||
*.hi
|
*.hi
|
||||||
*.o
|
*.o
|
||||||
|
blog.db3
|
||||||
|
|||||||
@ -29,6 +29,9 @@ module Yesod.Form
|
|||||||
, htmlField
|
, htmlField
|
||||||
, stringInput
|
, stringInput
|
||||||
, fieldsToTable
|
, fieldsToTable
|
||||||
|
, share2
|
||||||
|
, mkIsForm
|
||||||
|
, mapFormXml
|
||||||
{- FIXME
|
{- FIXME
|
||||||
-- * Create your own formlets
|
-- * Create your own formlets
|
||||||
, incr
|
, incr
|
||||||
@ -102,6 +105,11 @@ newtype GForm sub y xml a = GForm
|
|||||||
type Form sub y = GForm sub y (Widget sub y ())
|
type Form sub y = GForm sub y (Widget sub y ())
|
||||||
type FormField sub y = GForm sub y [FieldInfo sub y]
|
type FormField sub y = GForm sub y [FieldInfo sub y]
|
||||||
|
|
||||||
|
mapFormXml :: (xml1 -> xml2) -> GForm s y xml1 a -> GForm s y xml2 a
|
||||||
|
mapFormXml f (GForm g) = GForm $ \e fe -> do
|
||||||
|
(res, xml, enc) <- g e fe
|
||||||
|
return (res, f xml, enc)
|
||||||
|
|
||||||
data FieldInfo sub y = FieldInfo
|
data FieldInfo sub y = FieldInfo
|
||||||
{ fiLabel :: Html ()
|
{ fiLabel :: Html ()
|
||||||
, fiTooltip :: Html ()
|
, fiTooltip :: Html ()
|
||||||
@ -478,6 +486,7 @@ instance Formable Slug where
|
|||||||
| all (\c -> c `elem` "-_" || isAlphaNum c) x' =
|
| all (\c -> c `elem` "-_" || isAlphaNum c) x' =
|
||||||
Right $ Slug x'
|
Right $ Slug x'
|
||||||
| otherwise = Left ["Slug must be alphanumeric, - and _"]
|
| otherwise = Left ["Slug must be alphanumeric, - and _"]
|
||||||
|
-}
|
||||||
|
|
||||||
share2 :: Monad m => (a -> m [b]) -> (a -> m [b]) -> a -> m [b]
|
share2 :: Monad m => (a -> m [b]) -> (a -> m [b]) -> a -> m [b]
|
||||||
share2 f g a = do
|
share2 f g a = do
|
||||||
@ -485,8 +494,8 @@ share2 f g a = do
|
|||||||
g' <- g a
|
g' <- g a
|
||||||
return $ f' ++ g'
|
return $ f' ++ g'
|
||||||
|
|
||||||
deriveFormable :: [EntityDef] -> Q [Dec]
|
mkIsForm :: [EntityDef] -> Q [Dec]
|
||||||
deriveFormable = mapM derive
|
mkIsForm = mapM derive
|
||||||
where
|
where
|
||||||
derive :: EntityDef -> Q Dec
|
derive :: EntityDef -> Q Dec
|
||||||
derive t = do
|
derive t = do
|
||||||
@ -496,24 +505,31 @@ deriveFormable = mapM derive
|
|||||||
just <- [|pure|]
|
just <- [|pure|]
|
||||||
nothing <- [|Nothing|]
|
nothing <- [|Nothing|]
|
||||||
let just' = just `AppE` ConE (mkName $ entityName t)
|
let just' = just `AppE` ConE (mkName $ entityName t)
|
||||||
|
string' <- [|string|]
|
||||||
|
mempty' <- [|mempty|]
|
||||||
|
mfx <- [|mapFormXml|]
|
||||||
|
ftt <- [|fieldsToTable|]
|
||||||
|
let go_ = go ap just' string' mempty' mfx ftt
|
||||||
let c1 = Clause [ ConP (mkName "Nothing") []
|
let c1 = Clause [ ConP (mkName "Nothing") []
|
||||||
]
|
]
|
||||||
(NormalB $ go ap just' $ zip cols $ map (const nothing) cols)
|
(NormalB $ go_ $ zip cols $ map (const nothing) cols)
|
||||||
[]
|
[]
|
||||||
xs <- mapM (const $ newName "x") cols
|
xs <- mapM (const $ newName "x") cols
|
||||||
let xs' = map (AppE just . VarE) xs
|
let xs' = map (AppE just . VarE) xs
|
||||||
let c2 = Clause [ ConP (mkName "Just") [ConP (mkName $ entityName t)
|
let c2 = Clause [ ConP (mkName "Just") [ConP (mkName $ entityName t)
|
||||||
$ map VarP xs]]
|
$ map VarP xs]]
|
||||||
(NormalB $ go ap just' $ zip cols xs')
|
(NormalB $ go_ $ zip cols xs')
|
||||||
[]
|
[]
|
||||||
return $ InstanceD [] (ConT ''Formable
|
return $ InstanceD [] (ConT ''IsForm
|
||||||
`AppT` ConT (mkName $ entityName t))
|
`AppT` ConT (mkName $ entityName t))
|
||||||
[FunD (mkName "formable") [c1, c2]]
|
[FunD (mkName "toForm") [c1, c2]]
|
||||||
go ap just' = foldl (ap' ap) just' . map go'
|
go ap just' string' mem mfx ftt a =
|
||||||
go' (label, ex) =
|
let x = foldl (ap' ap) just' $ map (go' string' mem) a
|
||||||
VarE (mkName "sealForm") `AppE`
|
in mfx `AppE` ftt `AppE` x
|
||||||
(VarE (mkName "wrapperRow") `AppE` LitE (StringL label)) `AppE`
|
go' string' mempty' (label, ex) =
|
||||||
(VarE (mkName "formable") `AppE` ex)
|
let label' = string' `AppE` LitE (StringL label)
|
||||||
|
in VarE (mkName "toFormField") `AppE` label'
|
||||||
|
`AppE` mempty' `AppE` ex
|
||||||
ap' ap x y = InfixE (Just x) ap (Just y)
|
ap' ap x y = InfixE (Just x) ap (Just y)
|
||||||
|
|
||||||
toLabel :: String -> String
|
toLabel :: String -> String
|
||||||
@ -524,4 +540,3 @@ toLabel (x:rest) = toUpper x : go rest
|
|||||||
go (c:cs)
|
go (c:cs)
|
||||||
| isUpper c = ' ' : c : go cs
|
| isUpper c = ' ' : c : go cs
|
||||||
| otherwise = c : go cs
|
| otherwise = c : go cs
|
||||||
-}
|
|
||||||
|
|||||||
@ -103,7 +103,7 @@ class Yesod a where
|
|||||||
-- Return 'Nothing' is the request is authorized, 'Just' a message if
|
-- Return 'Nothing' is the request is authorized, 'Just' a message if
|
||||||
-- unauthorized. If authentication is required, you should use a redirect;
|
-- unauthorized. If authentication is required, you should use a redirect;
|
||||||
-- the Auth helper provides this functionality automatically.
|
-- the Auth helper provides this functionality automatically.
|
||||||
isAuthorized :: Routes a -> GHandler s a (Maybe String)
|
isAuthorized :: Routes a -> GHandler s a (Maybe String) -- FIXME use a data type that specifies whether authentication is required
|
||||||
isAuthorized _ = return Nothing
|
isAuthorized _ = return Nothing
|
||||||
|
|
||||||
-- | A type-safe, concise method of creating breadcrumbs for pages. For each
|
-- | A type-safe, concise method of creating breadcrumbs for pages. For each
|
||||||
|
|||||||
108
blog.hs
Normal file
108
blog.hs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
{-# LANGUAGE TypeFamilies, QuasiQuotes, GeneralizedNewtypeDeriving #-}
|
||||||
|
import Yesod
|
||||||
|
import Yesod.Helpers.Auth
|
||||||
|
import Yesod.Helpers.Crud
|
||||||
|
import Database.Persist.Sqlite
|
||||||
|
import Data.Time (Day)
|
||||||
|
|
||||||
|
type Html' = Html ()
|
||||||
|
share2 mkPersist mkIsForm [$persist|
|
||||||
|
Entry
|
||||||
|
title String
|
||||||
|
posted Day Desc
|
||||||
|
content Html'
|
||||||
|
deriving
|
||||||
|
|]
|
||||||
|
instance Item Entry where
|
||||||
|
itemTitle = entryTitle
|
||||||
|
|
||||||
|
getAuth = const $ Auth
|
||||||
|
{ authIsOpenIdEnabled = False
|
||||||
|
, authRpxnowApiKey = Nothing
|
||||||
|
, authEmailSettings = Nothing
|
||||||
|
-- | client id, secret and requested permissions
|
||||||
|
, authFacebook = Just (clientId, secret, ["email"])
|
||||||
|
}
|
||||||
|
where
|
||||||
|
clientId = "134280699924829"
|
||||||
|
secret = "a7685e10c8977f5435e599aaf1d232eb"
|
||||||
|
|
||||||
|
data Blog = Blog Connection
|
||||||
|
type EntryCrud = Crud Blog Entry
|
||||||
|
mkYesod "Blog" [$parseRoutes|
|
||||||
|
/ RootR GET
|
||||||
|
/entry/#EntryId EntryR GET
|
||||||
|
/admin AdminR EntryCrud defaultCrud
|
||||||
|
/auth AuthR Auth getAuth
|
||||||
|
|]
|
||||||
|
instance Yesod Blog where
|
||||||
|
approot _ = "http://localhost:3000"
|
||||||
|
defaultLayout p = do
|
||||||
|
mcreds <- maybeCreds
|
||||||
|
admin <- maybeAuthorized $ AdminR CrudListR
|
||||||
|
hamletToContent [$hamlet|
|
||||||
|
!!!
|
||||||
|
%html
|
||||||
|
%head
|
||||||
|
%title $pageTitle.p$
|
||||||
|
^pageHead.p^
|
||||||
|
%body
|
||||||
|
%p
|
||||||
|
%a!href=@RootR@ Homepage
|
||||||
|
$maybe admin a
|
||||||
|
\ | $
|
||||||
|
%a!href=@a@ Admin
|
||||||
|
\ | $
|
||||||
|
$maybe mcreds c
|
||||||
|
Welcome $
|
||||||
|
$maybe credsDisplayName.c dn
|
||||||
|
$string.dn$
|
||||||
|
$nothing
|
||||||
|
$string.credsIdent.c$
|
||||||
|
\ $
|
||||||
|
%a!href=@AuthR.Logout@ Logout
|
||||||
|
$nothing
|
||||||
|
%a!href=@AuthR.StartFacebookR@ Facebook Connect
|
||||||
|
^pageBody.p^
|
||||||
|
%p
|
||||||
|
Powered by Yesod Web Framework
|
||||||
|
|]
|
||||||
|
isAuthorized AdminR{} = do
|
||||||
|
mc <- maybeCreds
|
||||||
|
let x = (mc >>= credsEmail) == Just "michael@snoyman.com"
|
||||||
|
return $ if x then Nothing else Just "Permission denied"
|
||||||
|
isAuthorized _ = return Nothing
|
||||||
|
instance YesodAuth Blog where
|
||||||
|
defaultDest _ = RootR
|
||||||
|
defaultLoginRoute _ = RootR
|
||||||
|
instance YesodPersist Blog where
|
||||||
|
type YesodDB Blog = SqliteReader
|
||||||
|
runDB db = do
|
||||||
|
Blog conn <- getYesod
|
||||||
|
runSqlite db conn
|
||||||
|
|
||||||
|
getRootR = do
|
||||||
|
entries <- runDB $ select [] [EntryPostedDesc]
|
||||||
|
applyLayoutW $ do
|
||||||
|
setTitle $ string "Blog tutorial homepage"
|
||||||
|
addBody [$hamlet|
|
||||||
|
%h1 All Entries
|
||||||
|
%ul
|
||||||
|
$forall entries entry
|
||||||
|
%li
|
||||||
|
%a!href=@EntryR.fst.entry@ $string.entryTitle.snd.entry$
|
||||||
|
|]
|
||||||
|
|
||||||
|
getEntryR :: EntryId -> Handler Blog RepHtml
|
||||||
|
getEntryR eid = do
|
||||||
|
entry <- runDB (get eid) >>= maybe notFound return
|
||||||
|
applyLayoutW $ do
|
||||||
|
setTitle $ string $ entryTitle entry
|
||||||
|
addBody [$hamlet|
|
||||||
|
%h1 $string.entryTitle.entry$
|
||||||
|
%h2 $string.show.entryPosted.entry$
|
||||||
|
#content $entryContent.entry$
|
||||||
|
|]
|
||||||
|
main = withSqlite "blog.db3" $ \conn -> do
|
||||||
|
flip runSqlite conn $ initialize (undefined :: Entry)
|
||||||
|
toWaiApp (Blog conn) >>= basicHandler 3000
|
||||||
Loading…
Reference in New Issue
Block a user