The file structure now looks like this:
- abc.cabal- log/- access.log- error.log- snaplets/- heist/- templates/- _login.tpl- _new_user.tpl- base.tpl- index.tpl- login.tpl- new_user.tpl- userform.tpl- src/- Application.hs- Main.hs- Site.hs- static/- screen.css
abc.cabal
includes our dependencies and build information.
This file is read when we run cabal install
log
includes two files that log out what browsers access
the site and what error
s occur.
snaplets
is where our snaplets store their files. In this
case we only have heist
there, which contains a
templates
folder that includes the templates we use to
render the site.
static
includes a simple stylesheet that is served when we
visit the site.
src
is the folder we’re currently concerned with. It
includes three files:
This file contains some boilerplate for dynamic recompilation of a snap site. We’ll be leaving this file alone.
Here we define our App datatype with the snaplets we will be using.
data App = App{ _heist :: Snaplet (Heist App), _sess :: Snaplet SessionManager, _auth :: Snaplet (AuthManager App)}
In this case we are using heist
, sessions
and
authentication
.
Then we make a call to makeLenses
makeLenses ''App
makeLenses
does some things under the hood like creating
getters/setters and changing the names for the snaplets in
our app to remove the underscore. This means that _auth
will be referred to as auth
in the rest of our app,
including in src/Site.hs
.
We then define an instance for the Heist Snaplet so we don’t
have to use with heist
every time we want to render a
template (More about this later).
instance HasHeist App whereheistLens = subSnaplet heist
and finally we declare a type alias so that we can use
AppHandler
instead of the longer Handler App App
in the
type signatures for our routes.
type AppHandler = Handler App App
This is where the meat of our site lives. The routing code, handlers and initialization for the entire app.
The first route is "/login"
which uses
handleLoginSubmit
.
("/login", with auth handleLoginSubmit)
with auth
lets us work in the auth Snaplet for
handleLoginSubmit
.
handleLoginSubmit :: Handler App (AuthManager App) ()handleLoginSubmit =loginUser "login" "password" Nothing(_ -> handleLogin err) (redirect "/")whereerr = Just "Unknown user or password"
Since we’re working in the auth Snaplet our type signature
for the handler has the type
Handler App (AuthManager App) ()
, which is slightly
different from the type we aliased in Application.hs
.
loginUser
is a function from the auth snaplet. It takes
the username
field and the password
field from a form
submission, a “remember” field, a failure function and a
success function, in that order. The type signature looks
like this:
loginUser:: ByteString -- name of username field-> ByteString -- name of password field-> Maybe ByteString -- name of remember field (`Nothing` means there isn't one)-> (AuthFailure -> Handler b (AuthManager b) ()) -- failure function-> Handler b (AuthManager b) () -- success function-> Handler b (AuthManager b) () -- return value is a handler
So going back to handleLoginSubmit
we use: "login"
as
the username field "password"
as the password field
Nothing
as the remember field _ -> handleLogin err
as
our error function and redirect "/"
as our success
function.
err
is defined as Just "Unknown user or password"
which
matches up with the type from handleLogin
.
The handleLogin
code will be covered in a Heist tutorial
at another time, but suffice it to say that handleLogin
is
rendering the login form with the error that we’ve supplied
it (which is “Unknown user or password”).
Our "/logout"
route is pretty simple. Just call the
snaplet-auth supplied function logout
and redirect to
"/"
"/new_user"
does a similar thing, except it displays the
empty form on GET
request and handles a form submit on
POST
.
Next we’ll replace the backend, currently a json file, with postgres.