Web server with recipe index page
- Add Roux.Server: WAI application using wai+warp directly - Scans .cook files from recipe directory on startup - GET / → HTML index page listing all recipes - Other routes → 404 - Add Roux.RecipeIndex: directory scanning and recipe parsing - scanRecipes: lists .cook files, parses each, extracts titles - Falls back to filename when metadata title is absent - Add Roux.Html: blaze-html rendering with Pico CSS - indexPage: renders recipe list with links - urlEncode: percent-encodes filenames for URLs - Update Roux.hs: re-exports Server.app and Types - Add filepath to dependencies
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
{- | WAI application for the Roux recipe server.
|
||||
|
||||
Handles HTTP requests directly via wai — no framework. Currently serves
|
||||
a single index page listing all discovered recipes.
|
||||
-}
|
||||
module Roux.Server (
|
||||
app,
|
||||
) where
|
||||
|
||||
import Control.Monad (when)
|
||||
import Data.ByteString.Lazy (ByteString)
|
||||
import qualified Network.HTTP.Types as HTTP
|
||||
import qualified Network.Wai as Wai
|
||||
|
||||
import qualified Roux.Html as Html
|
||||
import qualified Roux.RecipeIndex as Idx
|
||||
|
||||
{- | Build the WAI 'Application' given a path to the recipe directory.
|
||||
|
||||
Recipes are scanned once at startup. A future improvement could watch
|
||||
the directory for changes and rebuild the index.
|
||||
-}
|
||||
app :: FilePath -> IO Wai.Application
|
||||
app recipeDir = do
|
||||
putStrLn $ "[roux] scanning recipes from " <> recipeDir
|
||||
recipes <- Idx.scanRecipes recipeDir
|
||||
let count = length recipes
|
||||
ok = length [() | r <- recipes, isRight (Idx.riRecipe r)]
|
||||
errs = count - ok
|
||||
putStrLn $ "[roux] found " <> show count <> " recipe(s)"
|
||||
when (errs > 0) $
|
||||
putStrLn $
|
||||
"[roux] " <> show errs <> " had parse errors"
|
||||
-- Return the application; recipes are captured in the closure.
|
||||
pure (handleRequest recipes)
|
||||
|
||||
-- | Route an incoming request to the appropriate handler.
|
||||
handleRequest :: [Idx.RecipeInfo] -> Wai.Application
|
||||
handleRequest recipes request respond =
|
||||
case Wai.pathInfo request of
|
||||
[] -> respond (htmlResponse (Html.indexPage recipes))
|
||||
_ -> respond notFound
|
||||
|
||||
-- | Build a 200 HTML response.
|
||||
htmlResponse :: ByteString -> Wai.Response
|
||||
htmlResponse body =
|
||||
Wai.responseLBS
|
||||
HTTP.status200
|
||||
[("Content-Type", "text/html; charset=utf-8")]
|
||||
body
|
||||
|
||||
-- | 404 response.
|
||||
notFound :: Wai.Response
|
||||
notFound =
|
||||
Wai.responseLBS
|
||||
HTTP.status404
|
||||
[("Content-Type", "text/plain")]
|
||||
"Not found"
|
||||
|
||||
-- | Check if an 'Either' is 'Right'.
|
||||
isRight :: Either a b -> Bool
|
||||
isRight (Right _) = True
|
||||
isRight (Left _) = False
|
||||
Reference in New Issue
Block a user