happs-tutorial-0.4.1: src/ControllerPostActions.hs
module ControllerPostActions where
import Control.Monad
import Control.Monad.Trans
import HAppS.Server
import StateStuff
import ViewStuff
import Misc
import ControllerMisc
import ControllerGetActions
data UserAuthInfo = UserAuthInfo String String
instance FromData UserAuthInfo where
fromData = liftM2 UserAuthInfo (look "username")
(look "password" `mplus` return "nopassword")
loginPage rglobs =
withData $ \(UserAuthInfo user pass) ->
[ ServerPartT $ \rq -> do
mbUser <- authUser user pass
case mbUser of
Nothing -> ( return . tutlayoutU rglobs [("errormsg","login error: invalid username or password")] ) "home"
Just u -> startsess rglobs u
]
data ChangeUserInfo = ChangeUserInfo String String String
instance FromData ChangeUserInfo where
fromData = liftM3 ChangeUserInfo ( (look "oldpass") `mplus` (return "no old password") )
(look "password" `mplus` return "no password")
(look "password2" `mplus` return "no password2")
changePasswordSP rglobs = withData $ \(ChangeUserInfo oldpass newpass1 newpass2) -> [ ServerPartT $ \rq -> do
if newpass1 == newpass2
then do mbL <- liftIO $ getmbLoggedInUser rq
maybe
(errW "Not logged in" rq)
(\u -> do mbU <- query (GetUser u)
case mbU of
Nothing -> errW ("bad username: " ++ u) rq
Just user -> do update $ ChangePassword user oldpass newpass1
return $ tutlayoutU rglobs [] "accountsettings-changed" )
mbL
else errW "new passwords did not match" rq
]
where errW msg rq = ( return . tutlayoutU rglobs [("errormsgAccountSettings", msg)] ) "changepassword"
instance FromData ConsultantProfile where
fromData = liftM3 ConsultantProfile ( (look "contact") `mplus` (return "") )
(look "consultantblurb")
--very, VERY hackish way of reading a checkbox
( return . not . (=="noval")
=<< look "listasconsultant" `mplus` return "noval" )
-- should reuse auth code from change password
processformEditConsultantProfile rglobs =
withData $ \fd@(ConsultantProfile pContact pBlurb listAsC) -> [ ServerPartT $ \rq -> do
case (mbUser rglobs) of
Nothing -> errW "Not logged in" rq
Just olduser@(User uname p cp js) -> do
let newuser = User uname p (ConsultantProfile pContact pBlurb listAsC) js
updateUserSp rglobs newuser viewEditConsultantProfile rq
]
where errW msg rq = ( return . tutlayoutU rglobs [("errormsg", msg)] ) "errortemplate"
data EditJob = EditJob {oldjobname::String,jobtitle::String,jobbudget::String,jobdescription::String}
-- Recommendation: ALWAYS give a default value via mplus, otherwise debugging can be hell.
instance FromData EditJob where
fromData = liftM4 EditJob (look "oldjobname" `mplus` return "bad old job name")
(look "jobtitle" `mplus` return "bad job title")
(look "jobbudget" `mplus` return "bad budget")
(look "jobdescription" `mplus` return "bad job description")
processformEditJob :: RenderGlobals -> ServerPartT IO Response
processformEditJob rglobs@(RenderGlobals ts mbU) =
withData $ \(EditJob ojn jn jbud jblu) -> [ ServerPartT $ \rq -> do
case mbU of
Nothing -> errW "Not logged in" rq
Just olduser@(User uname p cp js) -> do
let -- update the jobs list with the new info
-- it feels kludgy to use map, because it would seem that you could have jobs with duplicate names
-- probably the jobs collection could be a set, maybe fix later. it's just a demo.
newjob = Job jn jbud jblu
newjobs = map f js
where f j | jobname j == ojn = newjob
| otherwise = j
newuser = User uname p cp newjobs
updateUserSp rglobs newuser (viewEditJob uname jn) rq
]
where errW msg rq = ( return . tutlayoutU rglobs [("errormsg", msg)] ) "errortemplate"
instance FromData Job where
fromData = liftM3 Job (look "jobtitle" `mplus` return "bad job title")
(look "jobbudget" `mplus` return "bad budget")
(look "jobdescription" `mplus` return "bad job description")
processformNewJob rglobs@(RenderGlobals ts mbU) =
withData $ \newjob@(Job n budget blurb) -> [ ServerPartT $ \rq -> do
case mbU of
Nothing -> errW "Not logged in" rq
Just olduser@(User uname p cp js) -> do
let newuser = User uname p cp (newjob:js)
update (UpdateUser olduser newuser)
let newrglobs = RenderGlobals ts (Just $ User uname p cp (newjob:js) )
unServerPartT ( pageMyJobPosts newrglobs ) rq
]
where errW msg rq = ( return . tutlayoutU rglobs [("errormsg", msg)] ) "errortemplate"
data NewUserInfo = NewUserInfo String String String
instance FromData NewUserInfo where
fromData = liftM3 NewUserInfo (look "username")
(look "password" `mplus` return "nopassword")
(look "password2" `mplus` return "nopassword2")
-- newUserPage :: RenderGlobals -> ServerPartT IO Response
newUserPage rglobs =
withData $ \(NewUserInfo user pass1 pass2) ->
[ ServerPartT $ \rq ->
if pass1 == pass2
then do exists <- query $ IsUser user
if exists
then errW "User already exists" rq
else do
update $ ( AddUser user pass1 )
mbU <- query (GetUser user)
case mbU of
Nothing -> errW "newUserPage, update failed" rq
Just u -> startsess rglobs u
else errW "passwords did not match" rq
]
where errW msg rq = ( return . tutlayoutU rglobs [("errormsgRegister", msg)] ) "register"
-- check if a username and password is valid. If it is, return the user as successful monadic value
-- otherwise fail monadically
authUser :: Monad m => String -> String -> WebT IO (m User)
authUser name pass = do
mbUser <- query (GetUser name)
case mbUser of
Nothing -> return . fail $ "login failed"
(Just u) -> do
p <- return . password $ u
if p == (scramblepass pass)
then return . return $ u
else return . fail $ "login failed"
-- to do: make it so keeps your current page if you login/logout
-- probably modify RenderGlobals to keep track of that bit of state
startsess :: RenderGlobals -> User -> WebT IO Response
startsess (RenderGlobals ts _) user = do
key <- update $ NewSession (SessionData $ username user)
addCookie (3600) (mkCookie "sid" (show key))
let newRGlobs = RenderGlobals ts (Just user)
(return . tutlayoutU newRGlobs [] ) "home"