Add debug route and refactor some code
This commit is contained in:
parent
3ca153ba87
commit
48dab03326
@ -27,6 +27,7 @@ library
|
|||||||
exposed-modules:
|
exposed-modules:
|
||||||
Lisa
|
Lisa
|
||||||
Lisa.Squeak
|
Lisa.Squeak
|
||||||
|
Lisa.Types
|
||||||
Lisa.Views
|
Lisa.Views
|
||||||
other-modules:
|
other-modules:
|
||||||
Paths_lisa
|
Paths_lisa
|
||||||
@ -38,9 +39,11 @@ library
|
|||||||
, aeson >=2.0
|
, aeson >=2.0
|
||||||
, base >=4.7 && <5
|
, base >=4.7 && <5
|
||||||
, blaze-html >=0.9
|
, blaze-html >=0.9
|
||||||
|
, pretty-simple >=4.1
|
||||||
, req >=3.10
|
, req >=3.10
|
||||||
, text >=1.0
|
, text >=1.0
|
||||||
, time >=1.11
|
, time >=1.11
|
||||||
|
, wai >=3.2
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
executable lisa-exe
|
executable lisa-exe
|
||||||
@ -56,9 +59,11 @@ executable lisa-exe
|
|||||||
, base >=4.7 && <5
|
, base >=4.7 && <5
|
||||||
, blaze-html >=0.9
|
, blaze-html >=0.9
|
||||||
, lisa
|
, lisa
|
||||||
|
, pretty-simple >=4.1
|
||||||
, req >=3.10
|
, req >=3.10
|
||||||
, text >=1.0
|
, text >=1.0
|
||||||
, time >=1.11
|
, time >=1.11
|
||||||
|
, wai >=3.2
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
test-suite lisa-test
|
test-suite lisa-test
|
||||||
@ -75,7 +80,9 @@ test-suite lisa-test
|
|||||||
, base >=4.7 && <5
|
, base >=4.7 && <5
|
||||||
, blaze-html >=0.9
|
, blaze-html >=0.9
|
||||||
, lisa
|
, lisa
|
||||||
|
, pretty-simple >=4.1
|
||||||
, req >=3.10
|
, req >=3.10
|
||||||
, text >=1.0
|
, text >=1.0
|
||||||
, time >=1.11
|
, time >=1.11
|
||||||
|
, wai >=3.2
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
@ -23,10 +23,12 @@ dependencies:
|
|||||||
- base >= 4.7 && < 5
|
- base >= 4.7 && < 5
|
||||||
- aeson >= 2.0
|
- aeson >= 2.0
|
||||||
- blaze-html >= 0.9
|
- blaze-html >= 0.9
|
||||||
|
- pretty-simple >= 4.1
|
||||||
- req >= 3.10
|
- req >= 3.10
|
||||||
- Spock >= 0.14
|
- Spock >= 0.14
|
||||||
- text >= 1.0
|
- text >= 1.0
|
||||||
- time >= 1.11
|
- time >= 1.11
|
||||||
|
- wai >= 3.2
|
||||||
|
|
||||||
library:
|
library:
|
||||||
source-dirs: src
|
source-dirs: src
|
||||||
|
66
src/Lisa.hs
66
src/Lisa.hs
@ -1,5 +1,3 @@
|
|||||||
{-# LANGUAGE FlexibleInstances #-}
|
|
||||||
{-# LANGUAGE LambdaCase #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
module Lisa
|
module Lisa
|
||||||
@ -7,18 +5,14 @@ module Lisa
|
|||||||
, mkConfig
|
, mkConfig
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Concurrent (MVar, newMVar, putMVar, takeMVar)
|
import Control.Monad.IO.Class (MonadIO)
|
||||||
import Control.Monad.IO.Class (MonadIO, liftIO)
|
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
import Data.Time.Clock (UTCTime, getCurrentTime, addUTCTime)
|
|
||||||
import Network.HTTP.Req (https)
|
|
||||||
import Text.Blaze.Html (Html)
|
import Text.Blaze.Html (Html)
|
||||||
import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
|
import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
|
||||||
import Text.Printf (printf)
|
import Web.Spock (SpockM, ActionCtxT, get, lazyBytes, param, readSession, request, root, setHeader, var, (<//>))
|
||||||
import Web.Spock (SpockAction, SpockM, ActionCtxT, get, getState, lazyBytes, param, root, setHeader, var, (<//>))
|
|
||||||
import Web.Spock.Config (PoolOrConn(PCNoDatabase), SpockCfg, defaultSpockCfg)
|
import Web.Spock.Config (PoolOrConn(PCNoDatabase), SpockCfg, defaultSpockCfg)
|
||||||
|
|
||||||
import Lisa.Squeak (SqueakCtx, SqueakError, Lecture)
|
import Lisa.Types (Session, State, emtpySession, mkState, getAllLecturesCached)
|
||||||
|
|
||||||
import qualified Lisa.Squeak as Squeak
|
import qualified Lisa.Squeak as Squeak
|
||||||
import qualified Lisa.Views as Views
|
import qualified Lisa.Views as Views
|
||||||
@ -31,58 +25,28 @@ blaze html = do
|
|||||||
setHeader "Content-Type" "text/html; charset=utf-8"
|
setHeader "Content-Type" "text/html; charset=utf-8"
|
||||||
lazyBytes $ renderHtml html
|
lazyBytes $ renderHtml html
|
||||||
|
|
||||||
instance SqueakCtx (SpockAction () () State) where
|
|
||||||
getLocationInfo = pure $ Right (https "api.squeak-test.fsmi.uni-karlsruhe.de", mempty)
|
|
||||||
getAuthToken = pure Nothing
|
|
||||||
|
|
||||||
-- State
|
|
||||||
|
|
||||||
data State = State
|
|
||||||
{ stateLectures :: MVar (UTCTime, [Lecture]) -- ^ Cached lectures with cache expiration time.
|
|
||||||
}
|
|
||||||
|
|
||||||
-- | Get lectures from cache if they've been retrieved before or from Squeak.
|
|
||||||
-- Uses 'MVar's for synchronisation: As long as each thread 'takeMVar's before it 'putMVar's, we're in the clear.
|
|
||||||
-- TODO: Evaluate lectures to NF before 'putMVar'ing them.
|
|
||||||
getAllLecturesCached :: SpockAction () () State (Either SqueakError [Lecture])
|
|
||||||
getAllLecturesCached = do
|
|
||||||
lecturesMVar <- stateLectures <$> getState
|
|
||||||
(expirationTime, lectures) <- liftIO $ takeMVar lecturesMVar
|
|
||||||
now <- liftIO getCurrentTime
|
|
||||||
(newContents, result) <- if now >= expirationTime
|
|
||||||
then -- Cache expired => fetch lectures again.
|
|
||||||
Squeak.getAllLectures >>= \case
|
|
||||||
-- On error: Clear cache.
|
|
||||||
Left err ->
|
|
||||||
pure ((now, []), Left err)
|
|
||||||
-- On success: Cache fetched lectures for 60 seconds.
|
|
||||||
Right lectures' -> do
|
|
||||||
let expirationTime' = addUTCTime 60 now
|
|
||||||
liftIO $ printf "Fetched %d lectures, valid until %s\n" (length lectures') (show expirationTime')
|
|
||||||
pure ((expirationTime', lectures'), Right lectures')
|
|
||||||
else -- Cache still valid => return it.
|
|
||||||
pure ((expirationTime, lectures), Right lectures)
|
|
||||||
liftIO $ putMVar lecturesMVar newContents
|
|
||||||
pure result
|
|
||||||
|
|
||||||
-- Exports
|
-- Exports
|
||||||
|
|
||||||
mkConfig :: IO (SpockCfg () () State)
|
mkConfig :: IO (SpockCfg () Session State)
|
||||||
mkConfig = do
|
mkConfig = do
|
||||||
now <- getCurrentTime
|
state <- mkState
|
||||||
lecturesMVar <- newMVar (now, [])
|
defaultSpockCfg emtpySession PCNoDatabase state
|
||||||
defaultSpockCfg () PCNoDatabase (State lecturesMVar)
|
|
||||||
|
|
||||||
app :: SpockM () () State ()
|
app :: SpockM () Session State ()
|
||||||
app = do
|
app = do
|
||||||
get root $ do
|
get root $ do
|
||||||
blaze Views.viewIndex
|
blaze Views.viewIndex
|
||||||
|
|
||||||
get ("lectures" <//> var) $ \id -> do
|
get ("lectures" <//> var) $ \lid -> do
|
||||||
documents <- Squeak.getDocumentsByLectureId id
|
documents <- Squeak.getDocumentsByLectureId lid
|
||||||
blaze $ either Views.viewSqueakError (Views.viewLecture id) documents
|
blaze $ either Views.viewSqueakError (Views.viewLecture lid) documents
|
||||||
|
|
||||||
get "lectures" $ do
|
get "lectures" $ do
|
||||||
query <- fromMaybe "" <$> param "query"
|
query <- fromMaybe "" <$> param "query"
|
||||||
lectures <- getAllLecturesCached
|
lectures <- getAllLecturesCached
|
||||||
blaze $ either Views.viewSqueakError (Views.viewLectures query) lectures
|
blaze $ either Views.viewSqueakError (Views.viewLectures query) lectures
|
||||||
|
|
||||||
|
get "debug" $ do
|
||||||
|
req <- request
|
||||||
|
sess <- readSession
|
||||||
|
blaze $ Views.viewDebug req sess
|
||||||
|
@ -120,6 +120,7 @@ removePrefixModifier prefix = lowercaseFirst . stripPrefix prefix
|
|||||||
lowercaseFirst (c:cs) = Char.toLower c : cs
|
lowercaseFirst (c:cs) = Char.toLower c : cs
|
||||||
lowercaseFirst [] = error $ "Prefix " ++ prefix ++ " ate all my input"
|
lowercaseFirst [] = error $ "Prefix " ++ prefix ++ " ate all my input"
|
||||||
|
|
||||||
|
removePrefixOpts :: String -> Options
|
||||||
removePrefixOpts prefix = defaultOptions { fieldLabelModifier = removePrefixModifier prefix }
|
removePrefixOpts prefix = defaultOptions { fieldLabelModifier = removePrefixModifier prefix }
|
||||||
|
|
||||||
data Document = Document
|
data Document = Document
|
||||||
|
63
src/Lisa/Types.hs
Normal file
63
src/Lisa/Types.hs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Lisa.Types where
|
||||||
|
|
||||||
|
import Control.Concurrent (MVar, newMVar, putMVar, takeMVar)
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Time.Clock (UTCTime, addUTCTime, getCurrentTime)
|
||||||
|
import Network.HTTP.Req (https)
|
||||||
|
import Text.Printf (printf)
|
||||||
|
import Web.Spock (SpockAction, getState)
|
||||||
|
|
||||||
|
import Lisa.Squeak (Lecture, SqueakCtx, SqueakError)
|
||||||
|
|
||||||
|
import qualified Lisa.Squeak as Squeak
|
||||||
|
|
||||||
|
-- | Session data per-client.
|
||||||
|
data Session = Session
|
||||||
|
{ sessionAuthToken :: Maybe Text
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
emtpySession :: Session
|
||||||
|
emtpySession = Session Nothing
|
||||||
|
|
||||||
|
-- | Global application state.
|
||||||
|
data State = State
|
||||||
|
{ stateLectures :: MVar (UTCTime, [Lecture]) -- ^ Cached lectures with cache expiration time.
|
||||||
|
}
|
||||||
|
|
||||||
|
mkState :: IO State
|
||||||
|
mkState = do
|
||||||
|
now <- getCurrentTime
|
||||||
|
State <$> newMVar (now, [])
|
||||||
|
|
||||||
|
-- | Get lectures from cache if they've been retrieved before or from Squeak.
|
||||||
|
-- Uses 'MVar's for synchronisation: As long as each thread 'takeMVar's before it 'putMVar's, we're in the clear.
|
||||||
|
-- TODO: Evaluate lectures to NF before 'putMVar'ing them.
|
||||||
|
getAllLecturesCached :: SpockAction conn sess State (Either SqueakError [Lecture])
|
||||||
|
getAllLecturesCached = do
|
||||||
|
lecturesMVar <- stateLectures <$> getState
|
||||||
|
(expirationTime, lectures) <- liftIO $ takeMVar lecturesMVar
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
(newContents, result) <- if now >= expirationTime
|
||||||
|
then -- Cache expired => fetch lectures again.
|
||||||
|
Squeak.getAllLectures >>= \case
|
||||||
|
-- On error: Clear cache.
|
||||||
|
Left err ->
|
||||||
|
pure ((now, []), Left err)
|
||||||
|
-- On success: Cache fetched lectures for 60 seconds.
|
||||||
|
Right lectures' -> do
|
||||||
|
let expirationTime' = addUTCTime 60 now
|
||||||
|
liftIO $ printf "Fetched %d lectures, valid until %s\n" (length lectures') (show expirationTime')
|
||||||
|
pure ((expirationTime', lectures'), Right lectures')
|
||||||
|
else -- Cache still valid => return it.
|
||||||
|
pure ((expirationTime, lectures), Right lectures)
|
||||||
|
liftIO $ putMVar lecturesMVar newContents
|
||||||
|
pure result
|
||||||
|
|
||||||
|
instance SqueakCtx (SpockAction conn sess State) where
|
||||||
|
getLocationInfo = pure $ Right (https "api.squeak-test.fsmi.uni-karlsruhe.de", mempty)
|
||||||
|
getAuthToken = pure Nothing
|
@ -1,20 +1,17 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
module Lisa.Views
|
module Lisa.Views where
|
||||||
( viewIndex
|
|
||||||
, viewLecture
|
|
||||||
, viewLectures
|
|
||||||
, viewSqueakError
|
|
||||||
) where
|
|
||||||
|
|
||||||
import Prelude hiding (div)
|
import Prelude hiding (div)
|
||||||
|
|
||||||
import Control.Monad (forM_)
|
import Control.Monad (forM_)
|
||||||
import Data.List (intersperse)
|
import Data.List (intersperse)
|
||||||
import Data.Text (Text, isInfixOf)
|
import Data.Text (Text, isInfixOf)
|
||||||
|
import Network.Wai (Request)
|
||||||
import Text.Blaze.Html5 hiding (map)
|
import Text.Blaze.Html5 hiding (map)
|
||||||
import Text.Blaze.Html5.Attributes hiding (form)
|
import Text.Blaze.Html5.Attributes hiding (form)
|
||||||
|
|
||||||
|
import Lisa.Types (Session)
|
||||||
import Lisa.Squeak (Document(..), Lecture(..), SqueakError(..))
|
import Lisa.Squeak (Document(..), Lecture(..), SqueakError(..))
|
||||||
|
|
||||||
viewIndex :: Html
|
viewIndex :: Html
|
||||||
@ -22,6 +19,7 @@ viewIndex = do
|
|||||||
h1 "Lisa"
|
h1 "Lisa"
|
||||||
ul $ do
|
ul $ do
|
||||||
li $ a "Vorlesungen" ! href "/lectures"
|
li $ a "Vorlesungen" ! href "/lectures"
|
||||||
|
li $ a "Debuginformation" ! href "/debug"
|
||||||
|
|
||||||
viewLectures :: Text -> [Lecture] -> Html
|
viewLectures :: Text -> [Lecture] -> Html
|
||||||
viewLectures query lectures = do
|
viewLectures query lectures = do
|
||||||
@ -29,17 +27,17 @@ viewLectures query lectures = do
|
|||||||
form ! action "/lectures" ! method "GET" $ do
|
form ! action "/lectures" ! method "GET" $ do
|
||||||
input ! name "query" ! value (textValue query)
|
input ! name "query" ! value (textValue query)
|
||||||
button ! type_ "submit" $ text "Filtern"
|
button ! type_ "submit" $ text "Filtern"
|
||||||
ul $ forM_ (filter matchQuery lectures) $ \(Lecture id displayName aliases) -> do
|
ul $ forM_ (filter matchQuery lectures) $ \(Lecture lid displayName aliases) -> do
|
||||||
li $ do
|
li $ do
|
||||||
div $ a ! href (textValue $ "/lectures/" <> id) $ text displayName
|
div $ a ! href (textValue $ "/lectures/" <> lid) $ text displayName
|
||||||
div $ text $ mconcat $ intersperse ", " aliases
|
div $ text $ mconcat $ intersperse ", " aliases
|
||||||
where
|
where
|
||||||
matchQuery (Lecture _id displayName aliases) =
|
matchQuery (Lecture _id displayName aliases) =
|
||||||
any (query `isInfixOf`) (displayName : aliases)
|
any (query `isInfixOf`) (displayName : aliases)
|
||||||
|
|
||||||
viewLecture :: Text -> [Document] -> Html
|
viewLecture :: Text -> [Document] -> Html
|
||||||
viewLecture id documents = do
|
viewLecture lecture documents = do
|
||||||
h1 $ text ("Vorlesung " <> id)
|
h1 $ text ("Vorlesung " <> lecture)
|
||||||
table $ do
|
table $ do
|
||||||
thead $ tr $ mapM_ th ["Art", "Vorlesungen", "Prüfer", "Datum", "Semester", "Seiten"]
|
thead $ tr $ mapM_ th ["Art", "Vorlesungen", "Prüfer", "Datum", "Semester", "Seiten"]
|
||||||
tbody $ forM_ documents $ \document -> tr $ do
|
tbody $ forM_ documents $ \document -> tr $ do
|
||||||
@ -50,5 +48,15 @@ viewLecture id documents = do
|
|||||||
td $ text $ documentSemester document
|
td $ text $ documentSemester document
|
||||||
td $ string $ show $ documentNumPages document
|
td $ string $ show $ documentNumPages document
|
||||||
|
|
||||||
|
viewDebug :: Request -> Session -> Html
|
||||||
|
viewDebug request session = do
|
||||||
|
h1 $ text "Debuginformation"
|
||||||
|
fieldset $ do
|
||||||
|
legend "Request"
|
||||||
|
pre $ string $ show request
|
||||||
|
fieldset $ do
|
||||||
|
legend "Session"
|
||||||
|
pre $ string $ show session
|
||||||
|
|
||||||
viewSqueakError :: SqueakError -> Html
|
viewSqueakError :: SqueakError -> Html
|
||||||
viewSqueakError error = string $ show error
|
viewSqueakError err = string $ show err
|
||||||
|
Loading…
x
Reference in New Issue
Block a user