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:
/src/Core/HTTP.hs
-- 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" Handle.contact
, get "/atom.xml" Handle.feed
, get "/feed" Handle.feed
]
/src/Core/Handlers.hs
-- 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/footer.md"
postMd <- liftIO $ mdFileToLucid $ postPath postName
sendLucidFragment $ basePage title (basePost postMd) footerMd
where
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.