Monday, February 5, 2018

A URL for a Registration (4)

So far, there is a web server (servant) that can send arbitrary HTML from Haskell (generated with blaze-html); and there is a database of registrations, also accessible from Haskell.

It's time to tie them together.

In this post, I'll make read-only registrations available through a web interface. (but with not much protection, so don't put your real details in the database along with a column for the name of your first pet.)

I'm going to assume that there are already some records magically in the database - for example, by raw INSERT statements from a previous post.

The URL space for the servant webserver has two URL paths: /ping and /htmlcode. To those, I'm going to add new URLs that look like registration/1, registration/2, and so on.

Each URL will return a view of a registration in the database - that number on the end, /1, /2, ... will identify a record in the SQL database using the id column.

First modify the API description to add in this new piece of URL space:

type RegistrationAPI = "registration" :> S.Capture "id" Integer :> S.Get '[SB.HTML] B.Html

type API = PingAPI
      :<|> HtmlPingAPI
      :<|> RegistrationAPI

RegistrationAPI then means something like: match a path segment called registration, then capture whatever the next path segment is, and this will return some HTML generated by blaze-html.

Try compiling this and you'll get a lovely Haskell style type error, because this API doesn't match up with the implementation, server.

Here's a simple handleRegistration that will return only the captured registration identifier, so you can see how the value flows from the URL to Haskell code:

handleRegistration :: Integer -> S.Handler B.Html
handleRegistration identifier = return $ B.docTypeHtml $ do
  B.body $ B.p $ do "Registration ID is "
                    B.toHtml (show identifier)

You can figure out how to splice that onto the end of server yourself, and then try loading a URL such as http://localhost:8080/registration/1 - in fact, you should be able to use any Integer instead of that 1.

Next, I want that handler to read in a Registration from the database, and use that to generate the response HTML.

There are a few imports needed at the top of app/Main.hs to bring various needed pieces into scope. Also add postgresql-simple to the dependencies for main executable - previously it was only needed on the library component, because it was only used there.

import Registration
import Control.Exception (bracket)
import Control.Monad.IO.Class (liftIO)
import qualified Database.PostgreSQL.Simple as PG

Then handleRegistration can look like:

handleRegistration :: Integer -> S.Handler B.Html
handleRegistration identifier = do
  [registration] <- liftIO $ bracket
    (PG.connectPostgreSQL "user='postgres'")
    PG.close
    $ \conn -> do
      PG.query
        conn
        "SELECT firstname, lastname, dob FROM registration WHERE id = ?"
        [identifier] :: IO [Registration]

  return $ B.docTypeHtml $ do
    B.body $ do
      B.h1 $ do "Registration "
                B.toHtml (show identifier)
      B.p $ do  "First name: "
                B.toHtml (firstname registration)
      B.p $ do  "Last name: "
                B.toHtml (lastname registration)
      B.p $ do  "Date of Birth: "
                B.toHtml (dob registration)

Now, you can view registrations in a browser, using URLs like localhost:8080/registration/1 ... where /1 is the ID value for a registration from the database registration table. (Use SELECT in psql to see the available values for your particular database: they'll usually start at 1 and go upwards, but if you've deleted records then you might see other values.

If you use an invalid registration identifier (perhaps because it isn't a Haskell Integer or because no such registration exists, you should get an HTTP 500 server error response.

Next, I'll talk about making these fields editable.

commit b5d29063 gives the changes for this post.

No comments:

Post a Comment