diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15ff06f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +build/ +.env +*.db +*.db-shm +*.db-wal +*.sqlite +*.sqlite3 +.stack-root +.pi/ \ No newline at end of file diff --git a/app/Main.hs b/app/Main.hs new file mode 100644 index 0000000..898b49f --- /dev/null +++ b/app/Main.hs @@ -0,0 +1,72 @@ +{-# LANGUAGE OverloadedStrings #-} + +{- | Entry point for the Roux recipe server. +Starts a Warp HTTP server and serves recipe content. +-} +module Main (main) where + +import Data.String (fromString) +import qualified Network.Wai.Handler.Warp as Warp +import qualified Options.Applicative as Opts + +import qualified Roux + +-- --------------------------------------------------------------------------- +-- CLI options +-- --------------------------------------------------------------------------- + +data Options = Options + { optHost :: String + , optPort :: Int + , optRecipeDir :: FilePath + } + +optionsParser :: Opts.Parser Options +optionsParser = + Options + <$> Opts.strOption + ( Opts.long "host" + <> Opts.metavar "HOST" + <> Opts.help "Listen host" + <> Opts.value "0.0.0.0" + <> Opts.showDefault + ) + <*> Opts.option + Opts.auto + ( Opts.long "port" + <> Opts.short 'p' + <> Opts.metavar "PORT" + <> Opts.help "Listen port" + <> Opts.value 8080 + <> Opts.showDefault + ) + <*> Opts.strOption + ( Opts.long "recipe-dir" + <> Opts.short 'r' + <> Opts.metavar "DIR" + <> Opts.help "Path to directory containing Cooklang recipe files" + <> Opts.value "recipes" + <> Opts.showDefault + ) + +main :: IO () +main = do + opts <- + Opts.execParser $ + Opts.info + (optionsParser Opts.<**> Opts.helper) + ( Opts.fullDesc + <> Opts.progDesc "Roux recipe management server" + <> Opts.header "roux-server — personal/family recipe manager" + ) + let settings = + Warp.setPort (optPort opts) $ + Warp.setHost + (fromString (optHost opts)) + Warp.defaultSettings + + putStrLn $ "[roux] scanning recipes from " <> optRecipeDir opts + putStrLn $ "[roux] listening on " <> optHost opts <> ":" <> show (optPort opts) + + waiApp <- Roux.app (optRecipeDir opts) + Warp.runSettings settings waiApp diff --git a/package.yaml b/package.yaml new file mode 100644 index 0000000..6254488 --- /dev/null +++ b/package.yaml @@ -0,0 +1,63 @@ +name: roux-server +version: 0.1.0 +synopsis: Personal/family recipe management site +description: A low-friction recipe manager using Cooklang files, + with cook history tracking and search. +author: James Brechtel +maintainer: james@flipstone.com +copyright: 2026 James Brechtel +license: BSD-3-Clause + +default-extensions: + - OverloadedStrings + +ghc-options: + - -Wall + - -Werror + - -Wcompat + - -Widentities + - -Wincomplete-record-updates + - -Wincomplete-uni-patterns + - -Wmissing-export-lists + - -Wmissing-home-modules + - -Wpartial-fields + - -Wredundant-constraints + +dependencies: + - base >= 4.7 && < 5 + - blaze-html + - blaze-markup + - bytestring + - directory + - http-types + - optparse-applicative + - parsec + - text + - time + - wai + - warp + +library: + source-dirs: src + +executables: + roux-server: + main: Main.hs + source-dirs: app + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - optparse-applicative + - roux-server + - unix + +tests: + roux-server-test: + main: Spec.hs + source-dirs: test + dependencies: + - roux-server + - tasty + - tasty-hunit diff --git a/roux-server.cabal b/roux-server.cabal new file mode 100644 index 0000000..82517ef --- /dev/null +++ b/roux-server.cabal @@ -0,0 +1,101 @@ +cabal-version: 2.2 + +-- This file has been generated from package.yaml by hpack version 0.38.1. +-- +-- see: https://github.com/sol/hpack + +name: roux-server +version: 0.1.0 +synopsis: Personal/family recipe management site +description: A low-friction recipe manager using Cooklang files, with cook history tracking and search. +author: James Brechtel +maintainer: james@flipstone.com +copyright: 2026 James Brechtel +license: BSD-3-Clause +build-type: Simple + +library + exposed-modules: + Roux + Roux.Types + other-modules: + Paths_roux_server + autogen-modules: + Paths_roux_server + hs-source-dirs: + src + default-extensions: + OverloadedStrings + ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + build-depends: + base >=4.7 && <5 + , blaze-html + , blaze-markup + , bytestring + , directory + , http-types + , optparse-applicative + , parsec + , text + , time + , wai + , warp + default-language: Haskell2010 + +executable roux-server + main-is: Main.hs + other-modules: + Paths_roux_server + autogen-modules: + Paths_roux_server + hs-source-dirs: + app + default-extensions: + OverloadedStrings + ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N + build-depends: + base >=4.7 && <5 + , blaze-html + , blaze-markup + , bytestring + , directory + , http-types + , optparse-applicative + , parsec + , roux-server + , text + , time + , unix + , wai + , warp + default-language: Haskell2010 + +test-suite roux-server-test + type: exitcode-stdio-1.0 + main-is: Spec.hs + other-modules: + Paths_roux_server + autogen-modules: + Paths_roux_server + hs-source-dirs: + test + default-extensions: + OverloadedStrings + ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + build-depends: + base >=4.7 && <5 + , blaze-html + , blaze-markup + , bytestring + , directory + , http-types + , optparse-applicative + , parsec + , roux-server + , tasty + , tasty-hunit + , text + , time + , wai + , warp + default-language: Haskell2010 diff --git a/src/Roux.hs b/src/Roux.hs new file mode 100644 index 0000000..cf1c37c --- /dev/null +++ b/src/Roux.hs @@ -0,0 +1,21 @@ +{- | Top-level re-exports and application setup for Roux. +-} +module Roux + ( app + , module X + ) where + +import qualified Network.HTTP.Types as HTTP +import qualified Network.Wai as Wai + +import Roux.Types as X + +-- | Build the WAI 'Application' given a path to the recipe directory. +app :: FilePath -> IO Wai.Application +app _recipeDir = + pure $ \_request respond -> + respond $ + Wai.responseLBS + HTTP.status200 + [("Content-Type", "text/plain")] + "Hello from Roux!" diff --git a/src/Roux/Types.hs b/src/Roux/Types.hs new file mode 100644 index 0000000..f696219 --- /dev/null +++ b/src/Roux/Types.hs @@ -0,0 +1,9 @@ +{- | Core types for Roux. +-} +module Roux.Types + ( -- * Types + RecipeDir (..) + ) where + +-- | Path to the directory containing Cooklang recipe files. +newtype RecipeDir = RecipeDir FilePath diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..007bafe --- /dev/null +++ b/stack.yaml @@ -0,0 +1,4 @@ +resolver: lts-24.38 + +packages: + - . diff --git a/stack.yaml.lock b/stack.yaml.lock new file mode 100644 index 0000000..d025909 --- /dev/null +++ b/stack.yaml.lock @@ -0,0 +1,12 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/topics/lock_files + +packages: [] +snapshots: +- completed: + sha256: abc790b571e0c70e929db74b329e3c18d7e76a6e173e8bdf94f1ba20770d4c24 + size: 728990 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/38.yaml + original: lts-24.38 diff --git a/test/Spec.hs b/test/Spec.hs new file mode 100644 index 0000000..b58388d --- /dev/null +++ b/test/Spec.hs @@ -0,0 +1,10 @@ +module Main (main) where + +import Test.Tasty (defaultMain, testGroup) + +main :: IO () +main = + defaultMain $ + testGroup + "roux-server" + [ ]