This commit is contained in:
+12
-6
@@ -36,13 +36,15 @@ data CookEntry = CookEntry
|
|||||||
instance FromRow CookEntry where
|
instance FromRow CookEntry where
|
||||||
fromRow = CookEntry <$> SQL.field <*> SQL.field <*> SQL.field <*> SQL.field <*> (trimQuotes <$> SQL.field)
|
fromRow = CookEntry <$> SQL.field <*> SQL.field <*> SQL.field <*> SQL.field <*> (trimQuotes <$> SQL.field)
|
||||||
|
|
||||||
-- | Strip leading/trailing double-quote characters from a Text value.
|
{- | Strip leading/trailing double-quote characters from a Text value.
|
||||||
-- This handles any accidental quote wrapping from the DB round-trip.
|
This handles any accidental quote wrapping from the DB round-trip.
|
||||||
|
-}
|
||||||
trimQuotes :: Text -> Text
|
trimQuotes :: Text -> Text
|
||||||
trimQuotes = T.dropAround (== '"')
|
trimQuotes = T.dropAround (== '"')
|
||||||
|
|
||||||
instance ToJSON CookEntry where
|
instance ToJSON CookEntry where
|
||||||
toJSON e = A.object
|
toJSON e =
|
||||||
|
A.object
|
||||||
[ "id" A..= A.toJSON (ceId e)
|
[ "id" A..= A.toJSON (ceId e)
|
||||||
, "recipe_filename" A..= A.toJSON (ceRecipeFilename e)
|
, "recipe_filename" A..= A.toJSON (ceRecipeFilename e)
|
||||||
, "cooked_date" A..= A.toJSON (show (ceCookedDate e))
|
, "cooked_date" A..= A.toJSON (show (ceCookedDate e))
|
||||||
@@ -61,7 +63,9 @@ instance FromJSON CookEntry where
|
|||||||
where
|
where
|
||||||
parseDate t = case T.splitOn "-" t of
|
parseDate t = case T.splitOn "-" t of
|
||||||
[y, m, d] -> case fromGregorianValid
|
[y, m, d] -> case fromGregorianValid
|
||||||
(read (T.unpack y)) (read (T.unpack m)) (read (T.unpack d)) of
|
(read (T.unpack y))
|
||||||
|
(read (T.unpack m))
|
||||||
|
(read (T.unpack d)) of
|
||||||
Just day -> pure day
|
Just day -> pure day
|
||||||
Nothing -> fail "invalid date"
|
Nothing -> fail "invalid date"
|
||||||
_ -> fail "expected YYYY-MM-DD"
|
_ -> fail "expected YYYY-MM-DD"
|
||||||
@@ -104,7 +108,8 @@ insertEntry conn filename day mComment = do
|
|||||||
-- | Fetch all entries for a given recipe, newest first.
|
-- | Fetch all entries for a given recipe, newest first.
|
||||||
fetchEntries :: Connection -> Text -> IO [CookEntry]
|
fetchEntries :: Connection -> Text -> IO [CookEntry]
|
||||||
fetchEntries conn filename =
|
fetchEntries conn filename =
|
||||||
SQL.query conn
|
SQL.query
|
||||||
|
conn
|
||||||
"SELECT id, recipe_filename, cooked_date, comment, created_at \
|
"SELECT id, recipe_filename, cooked_date, comment, created_at \
|
||||||
\ FROM cook_log WHERE recipe_filename = ? ORDER BY cooked_date DESC, id DESC"
|
\ FROM cook_log WHERE recipe_filename = ? ORDER BY cooked_date DESC, id DESC"
|
||||||
(Only filename)
|
(Only filename)
|
||||||
@@ -112,6 +117,7 @@ fetchEntries conn filename =
|
|||||||
-- | Fetch all entries across all recipes, newest first.
|
-- | Fetch all entries across all recipes, newest first.
|
||||||
fetchAllEntries :: Connection -> IO [CookEntry]
|
fetchAllEntries :: Connection -> IO [CookEntry]
|
||||||
fetchAllEntries conn =
|
fetchAllEntries conn =
|
||||||
SQL.query_ conn
|
SQL.query_
|
||||||
|
conn
|
||||||
"SELECT id, recipe_filename, cooked_date, comment, created_at \
|
"SELECT id, recipe_filename, cooked_date, comment, created_at \
|
||||||
\ FROM cook_log ORDER BY cooked_date DESC, id DESC"
|
\ FROM cook_log ORDER BY cooked_date DESC, id DESC"
|
||||||
|
|||||||
+8
-3
@@ -853,10 +853,15 @@ renderCookLog filename entries = do
|
|||||||
H.form
|
H.form
|
||||||
! A.class_ "roux-cook-log-form"
|
! A.class_ "roux-cook-log-form"
|
||||||
! H.dataAttribute "filename" (H.toValue (T.pack filename))
|
! H.dataAttribute "filename" (H.toValue (T.pack filename))
|
||||||
! A.style "display: flex; gap: 0.3rem; margin-bottom: 0.5rem;" $ do
|
! A.style "display: flex; gap: 0.3rem; margin-bottom: 0.5rem;"
|
||||||
|
$ do
|
||||||
H.input ! A.type_ "date" ! A.name "cooked-date"
|
H.input ! A.type_ "date" ! A.name "cooked-date"
|
||||||
H.input ! A.type_ "text" ! A.name "comment" ! A.placeholder "Optional note..."
|
H.input
|
||||||
! A.maxlength "500" ! A.style "flex: 1;"
|
! A.type_ "text"
|
||||||
|
! A.name "comment"
|
||||||
|
! A.placeholder "Optional note..."
|
||||||
|
! A.maxlength "500"
|
||||||
|
! A.style "flex: 1;"
|
||||||
H.button ! A.type_ "submit" $ "Log it"
|
H.button ! A.type_ "submit" $ "Log it"
|
||||||
H.div ! A.class_ "roux-cook-entries" $ do
|
H.div ! A.class_ "roux-cook-entries" $ do
|
||||||
mapM_ renderEntry entries
|
mapM_ renderEntry entries
|
||||||
|
|||||||
+16
-7
@@ -47,19 +47,19 @@ import qualified Data.Aeson.KeyMap as KM
|
|||||||
import qualified Data.ByteString as BS
|
import qualified Data.ByteString as BS
|
||||||
import Data.Maybe (fromMaybe, mapMaybe)
|
import Data.Maybe (fromMaybe, mapMaybe)
|
||||||
import qualified Data.Text.Encoding as TE
|
import qualified Data.Text.Encoding as TE
|
||||||
import qualified Database.SQLite.Simple as SQL
|
|
||||||
import Data.Time.Calendar (Day, fromGregorianValid)
|
import Data.Time.Calendar (Day, fromGregorianValid)
|
||||||
import qualified Data.Time.Format as Time
|
import qualified Data.Time.Format as Time
|
||||||
import Text.Read (readMaybe)
|
import qualified Database.SQLite.Simple as SQL
|
||||||
import Roux.Config (AnthropicConfig (..), RouxConfig (..))
|
import Roux.Config (AnthropicConfig (..), RouxConfig (..))
|
||||||
import qualified Roux.CooklangPrint as CooklangPrint
|
|
||||||
import qualified Roux.CookLog as CookLog
|
import qualified Roux.CookLog as CookLog
|
||||||
|
import qualified Roux.CooklangPrint as CooklangPrint
|
||||||
import qualified Roux.Html as Html
|
import qualified Roux.Html as Html
|
||||||
import qualified Roux.RecipeIndex as Idx
|
import qualified Roux.RecipeIndex as Idx
|
||||||
import Roux.SchemaOrg (SchemaOrgRecipe (..), parseSchemaOrgRecipe, schemaOrgToCooklang)
|
import Roux.SchemaOrg (SchemaOrgRecipe (..), parseSchemaOrgRecipe, schemaOrgToCooklang)
|
||||||
import System.Exit (ExitCode (..))
|
import System.Exit (ExitCode (..))
|
||||||
import System.IO (hSetBinaryMode)
|
import System.IO (hSetBinaryMode)
|
||||||
import System.Process (CreateProcess (..), StdStream (CreatePipe), createProcess, proc, waitForProcess)
|
import System.Process (CreateProcess (..), StdStream (CreatePipe), createProcess, proc, waitForProcess)
|
||||||
|
import Text.Read (readMaybe)
|
||||||
|
|
||||||
-- | Read the full request body as a lazy ByteString.
|
-- | Read the full request body as a lazy ByteString.
|
||||||
readRequestBody :: Wai.Request -> IO LB.ByteString
|
readRequestBody :: Wai.Request -> IO LB.ByteString
|
||||||
@@ -645,7 +645,9 @@ handleRequest state db request respond = do
|
|||||||
["cook-log", filename] -> do
|
["cook-log", filename] -> do
|
||||||
entries <- CookLog.fetchEntries db filename
|
entries <- CookLog.fetchEntries db filename
|
||||||
let body = A.encode entries
|
let body = A.encode entries
|
||||||
respond $ Wai.responseLBS HTTP.status200
|
respond $
|
||||||
|
Wai.responseLBS
|
||||||
|
HTTP.status200
|
||||||
[("Content-Type", "application/json")]
|
[("Content-Type", "application/json")]
|
||||||
body
|
body
|
||||||
-- Cook history page (all entries across recipes)
|
-- Cook history page (all entries across recipes)
|
||||||
@@ -656,7 +658,9 @@ handleRequest state db request respond = do
|
|||||||
-- Cook log JSON endpoint for all entries
|
-- Cook log JSON endpoint for all entries
|
||||||
["cook-log"] -> do
|
["cook-log"] -> do
|
||||||
entries <- CookLog.fetchAllEntries db
|
entries <- CookLog.fetchAllEntries db
|
||||||
respond $ Wai.responseLBS HTTP.status200
|
respond $
|
||||||
|
Wai.responseLBS
|
||||||
|
HTTP.status200
|
||||||
[("Content-Type", "application/json")]
|
[("Content-Type", "application/json")]
|
||||||
(A.encode entries)
|
(A.encode entries)
|
||||||
_ -> respond notFound
|
_ -> respond notFound
|
||||||
@@ -671,11 +675,16 @@ handleCookLogPost db filename request respond = do
|
|||||||
case parseDate dateStr of
|
case parseDate dateStr of
|
||||||
Just day -> do
|
Just day -> do
|
||||||
_ <- CookLog.insertEntry db filename day comment
|
_ <- CookLog.insertEntry db filename day comment
|
||||||
let resp = Wai.responseLBS HTTP.status200 [("Content-Type", "application/json")]
|
let resp =
|
||||||
|
Wai.responseLBS
|
||||||
|
HTTP.status200
|
||||||
|
[("Content-Type", "application/json")]
|
||||||
(LB.fromStrict (encodeUtf8 "{\"ok\":true}"))
|
(LB.fromStrict (encodeUtf8 "{\"ok\":true}"))
|
||||||
respond resp
|
respond resp
|
||||||
Nothing ->
|
Nothing ->
|
||||||
let resp = Wai.responseLBS HTTP.status400
|
let resp =
|
||||||
|
Wai.responseLBS
|
||||||
|
HTTP.status400
|
||||||
[("Content-Type", "application/json")]
|
[("Content-Type", "application/json")]
|
||||||
(LB.fromStrict (encodeUtf8 "{\"error\":\"Invalid date format\"}"))
|
(LB.fromStrict (encodeUtf8 "{\"error\":\"Invalid date format\"}"))
|
||||||
in respond resp
|
in respond resp
|
||||||
|
|||||||
Reference in New Issue
Block a user