Problem to show value out of Handler monad in Haskell and Yesod - haskell

I am a beginner in Haskell and I am still studying and I stopped at the problem that has to do with monads.
The problem is that I want to show the comment writer in the template.
I can't do that because when I add a comment from the database it is always in the Handler monad.
Maybe my whole idea is wrong I don't know.
This is the comment entity:
ManComment
text Text sqltype=varchar(500)
created UTCTime sqltype=DateTime
writer UserId Maybe sqltype=varchar(255) default=NULL -- comment writer
manId ManifestationId sqltype=varchar(255) default=NULL
deriving Show Typeable
This is the handler function:
getManDetailsR :: ManifestationId -> Handler Html
getManDetailsR mid = do
(ui, user) <- requireAuthPair
comments <- runDB $ getComFromMan mid
defaultLayout $ do
setTitle "Manifestation details"
$(widgetFile "man-details")
Part of hamlet file (in last line trying to show writer):
$if null comments
<h4>There is not comments!
$else
$forall Entity cid com <- comments
<form method=post action=#{DeleteManCommentR mid cid}>
<li .list-group-item>
<div class="row">
<div class="col-xs-10 col-md-11">
<div>
<div .mic-info> By: <a href=#{ProfileR}>#{getCommentWriter $ com}</a>
I'm trying to get a comment writer here:
getCommentWriter :: ManComment -> Handler Text
getCommentWriter c = do
user <- runDB $ get404 $ fromJust $ manCommentWriter c --get writer from database
let username = userIdent user
return username -- but return puts in Handler monad
Eventually an error occurred:
No instance for (blaze-markup-0.8.2.7:Text.Blaze.ToMarkup
(Handler Text))
arising from a use of ‘toHtml’

you should be able to use it in the same way you use comments there: bind it to a local value and access this in your hamlet-file/code.
Of course you want to pair this up with the comment itself so I'd propose something like this:
getCommentAndWriter :: ManComment -> Handler (Entity ManComment, Text)
getCommentAndWriter c = do
user <- runDB $ get404 $ fromJust $ manCommentWriter c --get writer from database
let username = userIdent user
return (c, username)
getManDetailsR :: ManifestationId -> Handler Html
getManDetailsR mid = do
(ui, user) <- requireAuthPair
cs <- runDB $ getComFromMan mid
comments <- forM cs getCommentAndWriter
defaultLayout $ do
setTitle "Manifestation details"
$(widgetFile "man-details")
$if null comments
<h4>There is not comments!
$else
$forall (Entity cid com, writer) <- comments
<form method=post action=#{DeleteManCommentR mid cid}>
<li .list-group-item>
<div class="row">
<div class="col-xs-10 col-md-11">
<div>
<div .mic-info> By: <a href=#{ProfileR}>#{writer}</a>
Of course you should look into querying both of this together if you can but basically this should work.
PS: if you want to use more fields you probably would want to use a record - here I choose to tuple it up for simplicity.

Related

Haskell Yesod: Sending a Post Request On Image Click

I am writing a simple website in Haskell with Yesod. I have the following hamlet
<ul id="gallery">
$forall file <- listOfFiles
<form action=#{CharacterUpdateR} method="POST">
<input type="hidden" name="Name" value=#{characterName character} />
<input type="hidden" name="Portrait" value=#{file} />
<input type="hidden" name="Description" value=#{fromMaybe "" (characterDescription character)} />
<input type="image" src=#{imageFilePath $ (++) (unpack $ characterName character) ((++) "\\" file)} name="select" alt="select" />
The following Haskell code is supposed to handle the request:
postCharacterUpdateR :: Handler Html
postCharacterUpdateR = do
((result, widget), enctype) <- runFormPost updateCharacterForm
case result of
FormSuccess (name, file, desc) -> do
_ <- runDB $ updateWhere [CharacterName ==. name] [CharacterPortrait =. file]
_ <- runDB $ updateWhere [CharacterName ==. name] [CharacterDescription =. desc]
setMessage "Character Updated"
redirect CharactersR
_ -> do
liftIO $ putStrLn $ show result
setMessage $ "Something went wrong: "
redirect CharactersR
The page renders fine and when the images in the gallery are clicked, a POST request is sent and received by the handler, but it always fails, with the following error message: FormFailure ["Value is required","Value is required"]
I have tried to send the post request via a Julius widget instead with ajax, but reached a similar problem. In the developer console, it seems like the payload for the sent POST request is fine, but nonetheless the handler never receives the POST parameters.
I fixed this issue by, rather than using Hamlet itself to create the form submission, using runFormPost to get a widget to send the request.
updateCharacterForm :: Text -> String -> Maybe Textarea -> Html -> MForm Handler (FormResult (Text, Text, Maybe Textarea), Widget)
updateCharacterForm name portrait desc = renderBootstrap $ (,,)
<$> areq hiddenField "Name" (Just name)
<*> areq hiddenField "Portrait" (Just $ pack portrait)
<*> aopt textareaField "Description" (Just desc)
In conjunction with
characterGalleryEntry :: Character -> String -> Widget
characterGalleryEntry character file = do
((_, widget), enctype) <- liftHandler $ runFormPost (updateCharacterForm (characterName character) file (characterDescription character))
[whamlet|$newline never
<form action=#{CharacterUpdateR} method=post enctype=#{enctype}>
^{widget}
<image src=#{imageFilePath $ (++) (unpack $ characterName character) ((++) "\\" file)}>
<input .btn type=submit value="Set Profile Picture and Update">
|]```
For the post itself

Couldn't match expected type 'SomeEntity’ with actual type ‘Key SomeEntity’

Hi I am a beginner in Haskell and Yesod and I need help with this problem.
These are my entities:
Location
name Text sqltype=varchar(255)
address AddressId Maybe sqltype=varchar(255)
UniqueLocationName name
deriving Show Typeable
Manifestation
name Text sqltype=varchar(255)
description Text Maybe sqltype=varchar(255)
category Category Maybe
startDateTime UTCTime sqltype=DateTime
location LocationId Maybe sqltype=varchar(255)
UniqueManName name
deriving Show Typeable
This is my handler that calls the man-details template:
getManDetailsR :: ManifestationId -> Handler Html
getManDetailsR mid = do
(_, user) <- requireAuthPair
md <- runDB $ get404 mid -- type is Manifestation
defaultLayout $ do
setTitle "Manifestation details"
$(widgetFile "man-details")
And part of the man-details hamlet file where I want to display information about the event:
<div class="row">
<div class="col-md-5">
<div class="project-info-box mt-0">
<h2>#{manifestationName md}
<p class="mb-0">#{fromJust(manifestationDescription md)}
<div class="project-info-box">
<p><b>Start :</b>{show $ manifestationStartDateTime md}
<p><b>Location :</b>#{locationName (manifestationLocation md)}
In this case error is :
Couldn't match expected type ‘Location’
with actual type ‘Maybe (Key Location)’
Then I try like this :
$maybe l <- manifestationLocation md
<p><b>Location :</b>{locationName l}
And error is :
Couldn't match expected type ‘Location’
with actual type ‘Key Location’
I apologize for the huge question, but I don't know how to get out of this, ie how to get only the value out of this pair (Key Location)?
Any advice and help is welcome,
Thanks.
I'll post my solution, it's a beginner's problem, but it might be useful to someone.
As #Willem Van Onsem wrote in the comment, I again made a query to the database
now for a location.
Now handler looks like this, with the fromJust function because of the Maybe wrapper:
getManDetailsR :: ManifestationId -> Handler Html
getManDetailsR mid = do
(_, user) <- requireAuthPair
md <- runDB $ get404 mid
loc <- runDB $ get404 $ fromJust(manifestationLocation md) -- and now use loc in template
liftIO $ print (loc)
defaultLayout $ do
setTitle "Manifestation details"
$(widgetFile "man-details")

Having trouble compiling yesod route after introducing a parameter

I am running a modified example of
http://www.yesodweb.com/book/blog-example-advanced
that previously did not use a parameter id of type DBEntitySchemaId.
Since introducing it, I am getting
Handler/Entity.hs:146:34:
Couldn't match type ‘DBEntitySchemaId -> Route App’
with ‘Route (HandlerSite (WidgetT App IO))’
Expected type: WidgetT
App IO ((DBEntitySchemaId -> Route App) -> [(Text, Text)] -> Text)
Actual type: WidgetT
App
IO
(Route (HandlerSite (WidgetT App IO)) -> [(Text, Text)] -> Text)
In the first argument of ‘(>>=)’, namely ‘getUrlRenderParams’
In a stmt of a 'do' block:
(getUrlRenderParams
>>=
(\ urender_akdM
-> (asWidgetT GHC.Base.. toWidget)
(toHtml (\ u_akdN -> urender_akdM u_akdN [] EntityR))))
which refers to the line using the defaultLayout.
The relevant code is:
getEntityR :: DBEntitySchemaId -> Handler Html
getEntityR id = do
oldEntities <- runDB $ selectList [DBEntityDbentitySchemaId ==. id] []
fields <- runDB $ selectList [DBFieldDbentitySchemaId ==. id, DBFieldFieldTypeId ==. (toSqlKey 1)] []
mauthId <- maybeAuthId
((res, widget), enctype) <- runFormPost (form fields)
case (mauthId,res) of
(Just userId, FormSuccess (attributeValues)) -> do
setMessageI $ MsgSubmittedKarma
let entity = DBEntity "Test" id
entId <- runDB $ insert entity
let fieldsNVals = zip fields attributeValues
let einfuegen field attributeValue = case (field, attributeValue) of
((Entity fldId _), (FormSuccess val)) -> do
a <- runDB $ insert (Attribute {attributeEntityId = entId, attributeDbfieldId = fldId, attributeStringValue = val})
return (Just a)
_ -> return Nothing
_ <- forM fieldsNVals (\(field, attributeValue) -> einfuegen field attributeValue)
redirect $ (EntityR id)
_ -> do
--render <- getUrlRenderParams
--return $ renderHtml $ [hamlet|
defaultLayout [whamlet|
$if null oldEntities
<p>There are no entities
$else
<ul>
$forall Entity entityId entity <- oldEntities
<li>
<p>#{dBEntityName entity}
$if mauthId == Nothing
<form method=post action=#{EntityR} enctype=#{enctype}>
<input type=submit value=_{MsgPleaseLogin}>
$else
<form method=post action=#{EntityR} enctype=#{enctype}>
^{widget}
<input type=submit>
|]
What I already tried was changing whamlet to hamlet and
adding some lines of code from here (commented out)
Trying to send an email in yesod using hamlet
and
changing
[whamlet|
to
[whamlet|#{getEntityR}/#{id}|
and
[whamlet||#?{urlParams id}|
and some variations of this inspired by
Render url with query parameters
https://groups.google.com/forum/#!topic/yesodweb/q1gtkOgM32I
I assume there is only a small modification to make. However my understanding of whamlet/hamlet is not too great. So I am posting this in case it is a trivial problem. I fully understand that I need to put in more work, I am just having trouble googling my problem. So please bear with me.
After modifying your route you forgot to pass newly added parameter into #{EntityR} interpolation, something like this: #{EntityR param}.

Persisting an uploaded file in the database (Couldn't match type ‘ConduitM () ByteString (ResourceT IO) ()’ with ‘HandlerT App IO ByteString’)

Using yesod I am trying to persist an uploaded file in the database.
To do this, I want to extract the bytestring and persist it, but I still need the code to typecheck.
The actual problem is
Couldn't match type ‘ConduitM () ByteString (ResourceT IO) ()’
with ‘HandlerT App IO ByteString’
Expected type: HandlerT App IO ByteString
Actual type: Source (ResourceT IO) ByteString
In a stmt of a 'do' block: file <- (fileSourceRaw fileinfo)
In the expression:
do { setMessageI $ MsgUploadedImg;
uuidWrapped <- liftIO (U4.nextRandom);
let uuid = fromString $ U.toString $ uuidWrapped;
transactionId <- runDB $ insert $ Transaction userId;
.... }
The relevant part is this function (see: file <- (fileSourceRaw fileinfo)).
getImgR :: Handler Html
getImgR = do
oldImages <- runDB $ selectList [] []
mauthId <- maybeAuthId
((res, widget), enctype) <- runFormPost $ form Nothing Nothing ""
case (mauthId,res) of
(Just userId, FormSuccess (title,fileinfo)) -> do
transactionId <- runDB $ insert $ Transaction userId
file <- (fileSourceRaw fileinfo)
let newImg = Img {imgFile = Just file, imgTitle = title , imgDesc = Nothing, imgFiletype = Nothing, imgTransactionId = Just transactionId, imgUserId = Just userId}
_ <- runDB $ insert newImg
redirect $ ImgR
_ -> defaultLayout
[whamlet|
$if null oldImages
<p>There are no images
$else
<ul>
$forall Entity imgId img <- oldImages
<li>
<p>#{imgTitle img}
$if mauthId == Nothing
<form method=post action=#{ImgR} enctype=#{enctype}>
<input type=submit value=_{MsgPleaseLogin}>
$else
<form method=post action=#{ImgR} enctype=#{enctype}>
^{widget}
<input type=submit>
|]
Helper code:
type MyForm = Html -> MForm Handler (FormResult (Text, FileInfo), Widget)
form :: Maybe UserId -> Maybe TransactionId -> Text -> MyForm
form userId transactionId uuid = renderBootstrap $ (,)
<$> areq textField "Title" Nothing
<*> fileAFormReq "Image file"
Img
title Text
filetype Text Maybe
desc Textarea Maybe
file ByteString Maybe
transactionId TransactionId Maybe
userId UserId Maybe
deriving Show
Still going over the documentation, but I thought the use case was common enough to ask a question. The datatype of FileInfo is:
data FileInfo = FileInfo
{ fileName :: !Text
, fileContentType :: !Text
, fileSourceRaw :: !(Source (ResourceT IO) ByteString)
, fileMove :: !(FilePath -> IO ())
}
Thank you for your attention.
Edit: I assume the solution is contained in the documentation here
http://www.yesodweb.com/blog/2013/03/simpler-streaming-responses?
Update:
It looks like one of those links
How can I post FileInfo to a web service using Yesod and Http-Conduit?
https://www.schoolofhaskell.com/school/to-infinity-and-beyond/competition-winners/part-5
Yesod handlers, content of POSTed files
contain the solution.
Update2:
Using (Data.Conduit.Binary is DCB)
file <- runResourceT $ fileSource fileinfo $$ DCB.sinkLbs
leaves me with
Handler/Img.hs:62:42:
Couldn't match expected type ‘ByteString’
with actual type ‘Data.ByteString.Lazy.Internal.ByteString’
NB: ‘ByteString’ is defined in ‘Data.ByteString.Internal’
‘Data.ByteString.Lazy.Internal.ByteString’
is defined in ‘Data.ByteString.Lazy.Internal’
In the first argument of ‘Just’, namely ‘file’
In the ‘imgFile’ field of a record
Looks like the missing function is here:
Many types of String (ByteString)
The final code is
module Handler.Img where
import Import
import LambdaCms.Core -- for UserId
import Database.Persist.Sql (toSqlKey)
-- for uuids
import qualified Data.UUID as U
import qualified Data.UUID.V4 as U4
import Yesod.Core.Types
import qualified Data.Conduit.List as CL
import qualified Data.Conduit.Binary as DCB
import Data.ByteString as BS
import Data.ByteString.Lazy as LBS
type MyForm = Html -> MForm Handler (FormResult (Text, FileInfo), Widget)
form :: Maybe UserId -> Maybe TransactionId -> Text -> MyForm
form userId transactionId uuid = renderBootstrap $ (,)
<$> areq textField "Title" Nothing
<*> fileAFormReq "Image file"
lazyToStrictBS :: LBS.ByteString -> BS.ByteString
lazyToStrictBS x = BS.concat $ LBS.toChunks x
getImgR :: Handler Html
getImgR = do
oldImages <- runDB $ selectList [] []
mauthId <- maybeAuthId
((res, widget), enctype) <- runFormPost $ form Nothing Nothing ""
case (mauthId,res) of
(Just userId, FormSuccess (title,fileinfo)) -> do
setMessageI $ MsgUploadedImg
transactionId <- runDB $ insert $ Transaction userId
file <- runResourceT $ fileSource fileinfo $$ DCB.sinkLbs
let newImg = Img {imgFile = Just (lazyToStrictBS file), imgTitle = title , imgDesc = Nothing, imgFiletype = Nothing, imgTransactionId = Just transactionId, imgUserId = Just userId}
_ <- runDB $ insert newImg
redirect $ ImgR
_ -> defaultLayout
[whamlet|
$if Import.null oldImages
<p>There are no images
$else
<ul>
$forall Entity imgId img <- oldImages
<li>
<p>#{imgTitle img}
$if mauthId == Nothing
<form method=post action=#{ImgR} enctype=#{enctype}>
<input type=submit value=_{MsgPleaseLogin}>
$else
<form method=post action=#{ImgR} enctype=#{enctype}>
^{widget}
<input type=submit>
|]
postImgR :: Handler Html
postImgR = getImgR

Why does yesod require authorisation here?

I have a problem with yesod and authorization.
I get to the login page when trying to view blog posts when not logged in.
That is not what I want.
I want to be able to view blog posts even when not logged in.
I have tried to fix it but nothing has worked.
Here is the relevant sections of the code:
mkMessage "Blog" "messages" "en"
mkYesod "Blog" [parseRoutes|
/ RootR GET
/blog BlogR GET POST
/blog/#EntryId EntryR GET POST
/auth AuthR Auth getAuth
|]
instance Yesod Blog where
approot = ApprootStatic "http://localhost:3000"
defaultLayout = defLayout
authRoute _ = Just $ AuthR LoginR
isAuthorized BlogR True = do
mauth <- maybeAuth
case mauth of
Nothing -> return AuthenticationRequired
Just (Entity _ user)
| isAdmin user -> return Authorized
| otherwise -> unauthorizedI MsgNotAnAdmin
isAuthorized (EntryR _) True = do
mauth <- maybeAuth
case mauth of
Nothing -> return AuthenticationRequired
Just _ -> return Authorized
isAuthorized _ _ = return Authorized
isAdmin :: User -> Bool
isAdmin user = userEmail user == "email#something.com"
instance YesodPersist Blog where
type YesodPersistBackend Blog = SqlPersist
runDB f = do
master <- getYesod
let pool = connPool master
runSqlPool f pool
type Form x = Html -> MForm Blog Blog (FormResult x, Widget)
instance RenderMessage Blog FormMessage where
renderMessage _ _ = defaultFormMessage
instance YesodNic Blog
instance YesodAuth Blog where
type AuthId Blog = UserId
loginDest _ = RootR
logoutDest _ = RootR
authHttpManager = httpManager
authPlugins _ = [authBrowserId]
getAuthId creds = do
let email = credsIdent creds
user = User email
res <- runDB $ insertBy user
return $ Just $ either entityKey id res
getRootR :: Handler RepHtml
getRootR = defaultLayout $ do
setTitleI MsgHomepageTitle
[whamlet|
<p>_{MsgWelcomeHomepage}
<p>
<a href=#{BlogR}>_{MsgSeeArchive}
|]
entryForm :: Form Entry
entryForm = renderDivs $ Entry
<$> areq textField (fieldSettingsLabel MsgNewEntryTitle) Nothing
<*> aformM (liftIO getCurrentTime)
<*> areq nicHtmlField (fieldSettingsLabel MsgNewEntryContent)
Nothing
getBlogR :: Handler RepHtml
getBlogR = do
muser <- maybeAuth
entries <- runDB $ selectList [] [Desc EntryPosted]
(entryWidget, enctype) <- generateFormPost entryForm
defaultLayout $ do
setTitleI MsgBlogArchiveTitle
[whamlet|
$if null entries
<p>_{MsgNoEntries}
$else
<ul>
$forall Entity entryId entry <- entries
<li>
<a href=#{EntryR entryId}>#{entryTitle entry}
$maybe Entity _ user <- muser
$if isAdmin user
<form method=post enctype=#{enctype}>
^{entryWidget}
<div>
<input type=submit value=_{MsgNewEntry}>
$nothing
<p>
<a href=#{AuthR LoginR}>_{MsgLoginToPost}
|]
postBlogR :: Handler RepHtml
postBlogR = do
((res, entryWidget), enctype) <- runFormPost entryForm
case res of
FormSuccess entry -> do
entryId <- runDB $ insert entry
setMessageI $ MsgEntryCreated $ entryTitle entry
redirect $ EntryR entryId
_ -> defaultLayout $ do
setTitleI MsgPleaseCorrectEntry
[whamlet|
<form method=post enctype=#{enctype}>
^{entryWidget}
<div>
<input type=submit value=_{MsgNewEntry}>
|]
-- comment form
commentForm :: EntryId -> Form Comment
commentForm entryId = renderDivs $ Comment
<$> pure entryId
<*> aformM (liftIO getCurrentTime)
<*> aformM requireAuthId
<*> areq textField (fieldSettingsLabel MsgCommentName) Nothing
<*> areq textareaField (fieldSettingsLabel MsgCommentText) Nothing
getEntryR :: EntryId -> Handler RepHtml
getEntryR entryId = do
(entry, comments) <- runDB $ do
entry <- get404 entryId
comments <- selectList [] [Asc CommentPosted]
return (entry, map entityVal comments)
muser <- maybeAuth
(commentWidget, enctype) <- generateFormPost (commentForm entryId)
defaultLayout $ do
setTitleI $ MsgEntryTitle $ entryTitle entry
[whamlet|
<h1>#{entryTitle entry}
<article>#{entryContent entry}
<section .comments>
<h1>_{MsgCommentsHeading}
$if null comments
<p>_{MsgNoComments}
$else
$forall Comment _entry posted _user name text <- comments
<div .comment>
<span .by>#{name}
<span .at>#{show posted}
<div .content>#{text}
<section>
<h1>_{MsgAddCommentHeading}
$maybe Entity _ user <- muser
<form method=post enctype=#{enctype}>
^{commentWidget}
<div>
<input type=submit value=_{MsgAddCommentButton}>
$nothing
<p>
<a href=#{AuthR LoginR}>_{MsgLoginToComment}
|]
How do i fix it?
Ok, I found the problem myself.
This line gave me problems:
<*> aformM requireAuthId
I also had to remove references to the user id field where it was being used in the application.
I don't know why this problem existed in the first place because the widget should only be shown when a user is logged in.
I would however like to have the user id of the user who posted the comment is there another way to do it without reintroducing the problem I had?
Or do you think this is an error within yesod?
(This would be a comment if I was allowed to)
I'm currently learning Yesod myself so this may not be the best way to do it, but you can avoid requireAuthId in the form and still record the user id in a persist field under the comment Entity if you make your form of a different type. Instead of
commentForm :: EntryId -> Form Comment
Which is a shorthand for
commentForm :: EntryId -> Html -> MForm Blog Blog (FormResult Comment, Widget)
You could rearrange the fields and have
commentForm :: EntryId -> Html -> MForm Blog Blog (FormResult (UserId -> Comment), Widget)
and supply the user id in the POST handler.
You could even strip down the form to just
commentForm :: Html -> MForm Blog Blog (FormResult (Text, Textarea), Widget)
commentForm = renderDivs $ (,)
<$> areq textField (fieldSettingsLabel MsgCommentName) Nothing
<*> areq textareaField (fieldSettingsLabel MsgCommentText) Nothing
and supply everything else in the POST handler.
Alternatively you could probably put generateFormPost under a case branching so the form isn't generated when you aren't logged in, instead of apparently merely not being shown.

Resources