Using Yesod.Auth.Hardcoded SiteAdmin in a hamlet template - haskell

Problem description
I've been unable to get a compiling example of using Yesod.Auth.Hardcoded. My problem is in trying to interrogate the user in a hamlet template. My Foundation.hs is set up as per the documentation in the link for hardcoded. My handler looks like this:
getHomeR :: Handler Html
getHomeR = do
uid <- maybeAuthId
(widget, enctype) <- generateFormPost . renderBootstrap3 BootstrapBasicForm $ blurbForm Nothing
currentPost <- runDB $ selectFirst [] [Desc BlogId]
currentBlurb <- runDB $ selectFirst [] [Desc BlurbId]
defaultLayout $ do
setTitle "My site"
addScriptRemote "https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"
addScriptRemote "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/js/collapse.js"
$(widgetFile "homepage")
My site compiles and renders happily until I try to do anything useful with the uid being assigned in the do block above.
What I've tried
I've tried both the $maybe and $case constructs in the hamlet documentation. $maybe looked like this:
$maybe user <- uid
<p>There's a name
$nothing
<p>No name
This succeeded regardless of whether I logged in as the hardcoded user.
The $case version looked like this:
$case uid
$of Left _
<p>There's no name
$of Right username
<p>It worked
and failed with:
Exception when trying to run compile-time code:
Inside a $case there may only be $of. Use '$of _' for a wildcard.
Code: widgetFile "homepage"
In the splice: $(widgetFile "homepage")
Question(s)
Am I setting the uid correctly in my handler code and, if so, how should I access the hardcoded SiteManager in my templates?

As often posting the question made me think of an answer, though I'd still be grateful for any better ones. Using a combination of $maybe and $case like so:
$maybe user <- uid
$case user
$of Left _
<p>There's no name
$of Right username
<p>There might be a name of #{username}
$nothing
<p>No name
got me the correct username. Please post another answer if there is a better way.

The AuthId type here is Either UserId String.
A Right value represents a "hardcoded" user which does not appear in the User table. A Left value represents a User row in the Users table.
If you want to show a name for an AuthId, you can call getAuthEntity which returns an Either User SiteManager and then process it like this:
getUserName :: Either User SiteManager -> String
getUserName (Left user) = ...get the name field from user...
getUserName (Right sitemgr) = manUserName sitemgr

Related

Is there a way to use defaultLayout and a source (conduit) in Yesod

I'm trying to optimise an Yesod application so that it runs in constant space.
For example, I'm reading a DB table and display it as an HTML table. I should be able to start sending the first row before having finished processing the full table. I understand I can do that using selectSource(to read the DB) and responseSource or responseSourceDB but I how can wrap it in the default layout using defaultLayout ?
At the moment I have to consume the full source to generate a list and the Html in one go. For example let say I have (might not compiles)
data User = {name :: Text, email :: Text } deriving ...
userSource = selectSource [] [Asc UserName]
userToTR user = [whamlet|
<tr>
<td>#{user name}
<td>#{user email}
getUsers :: Handler Html
getUsers = do
rows <- runConduit $ userSource =$= mapC userToTR =$= sinkList
table = [whamlet|
<table>
<tr>
<th>Name
<th>Email
^{mconcat rows}
defaultLayout table
How I can transform this to stream the rows nicely ?
(This is only a made up example to explain the problem, the real problem is much more complicated).

How make Yesod.Auth.Email display an error when an email is already registered?

I found out when I try to register an email on RegisterR which already exists, instead of displaying an error message, that the user already exists, it sends an an email confirmation request to the given email address, just like if I asked for a password reset.
After going through the sources, I found out that registerHelper checks if the email exists, and if it does, it sends a verify email even if it's already verified.
-- [...]
mecreds <- lift $ getEmailCreds identifier
registerCreds <-
case mecreds of
Just (EmailCreds lid _ _ (Just key) email) -> return $ Just (lid, key, email)
Just (EmailCreds lid _ _ Nothing email) -> do
key <- liftIO $ randomKey y
lift $ setVerifyKey lid key
return $ Just (lid, key, email)
Nothing
| allowUsername -> return Nothing
| otherwise -> do
key <- liftIO $ randomKey y
lid <- lift $ addUnverified identifier key
return $ Just (lid, key, identifier)
case registerCreds of
Nothing -> loginErrorMessageI dest (Msg.IdentifierNotFound identifier)
Just (lid, verKey, email) -> do
render <- getUrlRender
let verUrl = render $ verify (toPathPiece lid) verKey
lift $ sendVerifyEmail email verKey verUrl
lift $ confirmationEmailSentResponse identifier
-- [...]
Source: https://hackage.haskell.org/package/yesod-auth-1.4.3.1/docs/src/Yesod-Auth-Email.html#registerHelper
Since registerCreds is a Just when the email exists and it has a key, it sends the verify email again to the user, whiche he/she can use as a password reset link.
My questions are:
Why does it work this way? This basically makes registering an already existing email address equal requesting password reset. I'm sure it has a good reason, and it makes me wonder if I'm using Auth.Email correctly.
Yesod.Auth.Email.postRegisterR gets called immediately when the request arrives - where do I have a chance to check that the email address already exists, and inform the user?
EDIT: It turned out I was looking at the source of a much older version: I updated the question. In yesod-auth-1.4.3, this code is in registerHelper, not in postRegisterR.
After discussing it on #yesod, this is probably a bug, so I filed an issue on it:
https://github.com/yesodweb/yesod/issues/948

How to combine "details" from a separate query in a list in Hamlet?

I'd like to display a list of items in a webpage, along with associated details from a separate table (with a many-to-one relationship). How do I do this in Yesod? I am using the default scaffolding. The upshot is that runDB cannot be nested in a WidgetT context — or so I think.
To make this more concrete, how do I define the function featuresAssociatedWith to use in the following hamlet code:
<h2> Cars
$forall Entity carId car <- carList
<div class="car-item">
<h3> #{carYear car} #{carMake car} #{carModel car}
<ul>
$forall feature <- featuresAssociatedWith carId
<li> #{feature}
Given the following models:
Car
make Text
model Text
year Int
CarFeature
car CarId
text Text
UniqueCF car text
Here is the current handler function
getCarListR :: Handler Html
getCarListR = do
carList <- runDB $ selectList [] [Asc CarOrder]
liftIO $ print $ length carList
defaultLayout $ do
setTitle "Cars"
$(widgetFile "carList")
It seems most natural to embed a runDB query in the Widget this way, but again, this isn't possible:
featuresAssocWith :: CarId -> [Entity CarFeature]
featuresAssocWith carID = selectList [CarFeatureCar ==. carID] []
Hamlet is designed to not allow you to perform actions like database queries inside of it. Instead, you'll need to perform the query outside of hamlet, and then pass in a list of tuples of car info together with the data from the associated table.

Getting FieldView From a Monadic Form or Separating View-related code from the Form

I was following along the Monadic Form example from the Yesod Web Framework Book (http://www.yesodweb.com/book-1.2/forms Monadic Forms section). The example shows how to construct a widget and return it from the form. However, I would like to avoid styling and view related code in my Handler / Form and I thought I can just get all the FieldView but I cannot figure out how to do this because runFormPost returns an xml (guessing that's the Widget).
Modified code from them book
personForm :: Html -> MForm Handler (FormResult Person, FieldView, FieldView)
personForm extra = do
(nameRes, nameView) <- mreq textField "this is not used" Nothing
(ageRes, ageView) <- mreq intField "neither is this" Nothing
let personRes = Person <$> nameRes <*> ageRes
return (personRes, nameView, ageView) -- my change and removed all the View related code
In my Handler I call it as:
((res, v1, v2), enctype) <- runFormPost personForm
There are a couple of issues here. (1) I get an error message that FieldView takes another parameter and unsure what it is I put () that seems to satisfy the compiler but I have no idea why that makes any sense. (2) Then I get the error message: Couldn't match expected type (FormResult a0, xml0)' with actual type(FormResult Person, FieldView (), FieldView ())'
The second one I take it to be something related to what runFormPost returns.
Could someone please help me with the best way to get the FieldViews directly so I can place them appropriately in my Hamlet file? Or if there is a better way to separate view related items from the Handler / Form, that will be fine as well.
I did not want to use Input Forms because if there is an invalid input, there seems to be no way to trap it and handle the error in the code -- it just directs to an error page.
Thanks!

Form validation based on values in database

I have a form where a user can update its username. A username should be unique. I thought of using the standard validation as mentioned in the Yesod book: Forms, but I don't get my head around it... Relevant database definition:
Profile
username Text
user UserId
UniqueProfile user
UniqueUsername username
Without validation, the user will receive an error-page (because of the username-uniqueness constraint on db-level). I want it to be more friendly.
How can I solve this? I was thinking that the validation should count the rows with the filled in username and should differ from the logged in UserId (a user can update other items too and keep its username). But how do I get that result to be used in the Left part of the validation?
Solution:
profileForm :: Maybe ProfileForm -> Form ProfileForm
profileForm mpf = renderBootstrap $ ProfileForm
<$> areq usernameField (FieldSettings {fsLabel = "Username", fsTooltip = Nothing, fsId = Nothing, fsName = Nothing, fsAttrs = [("autofocus","autofocus")]}) (pfUsername <$> mpf)
where
unav x = do
(Entity uid _) <- requireAuth
usernamecount <- runDB $ count [ ProfileUsername ==. x
, ProfileUser !=. uid ]
return $ if usernamecount > 0
then Left ("Username already taken" :: Text)
else Right x
usernameField = checkM unav textField
I think you're looking for the checkM function, which will allow you to perform arbitrary actions during the validation of a field.

Resources