Sunday, February 11, 2018

Suggesting a filename for a downloaded file (9)

Last post, I got the code generating CSVs but (at least in my case) my browser chooses a lame filename: just "csv". I'd much rather it chose something like "registrations.csv". (It decided on "csv" because that was the last component of the URL path.

One way to accomplish this is by adding on a new HTTP header called Content-Disposition.

Here's a change to the API to specify that header:

type CSVAPI = "admin" :> "csv" :> S.Get '[SC.CSV] (S.Headers '[S.Header "Content-Disposition" String] [Registration])

This changes the previous CSVAPI in one place - the type that servant will expect back from the handler code. Why? Because in addition to [Registration] to be serialised, we also need to return a value to go into this content-disposition header. servant is going to check at compile time that we have supplied headers as required by the API.

The updated handleCSV still needs to SELECT the registrations from the database, but needs to wrap those values with a call to addHeader.

handleCSV :: S.Handler (S.Headers '[S.Header "Content-Disposition" String] [Registration])
handleCSV = do
  rs <- liftIO $ bracket
    (PG.connectPostgreSQL "user='postgres'")
    PG.close
    $ \conn -> do
      PG.query
        conn
        "SELECT firstname, lastname, dob FROM registration" ()
  return $ S.addHeader "attachment;filename=\"registrations.csv\"" rs

That was a pretty small change, but now http://localhost:8080/admin/csv should result in a CSV file with a better filename. As the filename is specified as part of the return value of the handler, it should be straightforward to (for example) put a date/time stamp in the filename, or something else like that to disambiguate multiple downloads over time.

No comments:

Post a Comment