Silly sampu

Originally posted: 2024-03-13

A limitation

You add, edit, or remove posts by creating new Markdown files in the data/posts directory. Only unhidden (no leading .) files with the file suffix/extension of .md are included as posts. If you create or remove a post entirely, the application must be restarted to pick up these changes. A new post won't have a route, be displayed in the "posts index", or be included the Atom feed. A deleted post will cause a 500 ISE when visited in the event that you don't restart sampu.

I'm undecided whether I will have the routes built on every request but with caching or if I will simply add better error handling to the above scenario. I do know one of the two will happen before a 1.0 release.

... Why though?

I got so caught up with how cool I thought it was to construct my routes as a standard Haskell list that I architected the app like a dummy. This was an obvious place to reach for the param function in Twain's "ResponderM" monad:


-- Core routes expressed as a list of WAI Middlewares.
routes :: [Middleware]
routes =
  [ get "/"            Handle.index 
  , get "/style.css"   Handle.theme
  , get "/posts"       Handle.postsIndex
  , get "/posts/:name" Handle.posts
  , get "/contact"
  , get "/atom.xml"    Handle.feed
  , get "/feed"        Handle.feed


-- Responds with processed Commonmark -> HTML for posts 
posts :: ResponderM a
posts = do
  postName  <- param "name"
  postValid <- liftIO $ postExists postName
  case postValid of
    False  -> missing
    True   -> do
      title    <- liftIO Conf.title
      footerMd <- liftIO $ mdFileToLucid "./data/posts/"
      postMd   <- liftIO $ mdFileToLucid $ postPath postName
      sendLucidFragment  $ basePage title (basePost postMd) footerMd
      postExists :: T.Text -> IO Bool
      postExists postName = doesFileExist $ postPath postName

      postPath :: T.Text -> FilePath
      postPath postName = "./data/posts/" ++ T.unpack postName ++ ".md"

This is nowhere near as novel or clever as the original solution but it simply works better. It avoids the caveat of having to restart the application to pick up changes. I was trying to stick to the best practice of not having IO infect random functions, but these handlers already use IO. Overall, this was an important lesson for me to learn about not getting too caught up in novelty.