quotes-api/app/Main.hs

97 lines
3.0 KiB
Haskell
Raw Normal View History

2023-02-03 22:40:28 +00:00
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE DataKinds #-}
2023-02-27 18:16:29 +00:00
{-# LANGUAGE TypeOperators #-}
2023-02-03 13:45:16 +00:00
module Main where
2023-03-05 11:16:56 +00:00
import Database.SQLite.Simple hiding ((:.))
2023-02-03 22:40:28 +00:00
import Database.SQLite.Simple.QQ
import Network.Wai.Handler.Warp
import Data.Proxy
import Servant
import Control.Monad.IO.Class
2023-03-05 09:38:02 +00:00
import Servant.HTML.Blaze
2023-03-05 11:16:56 +00:00
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8)
import Crypto.Argon2
import Data.Text.Short (fromText, ShortText)
import Data.ByteString (ByteString)
2023-02-03 22:40:28 +00:00
2023-02-27 18:16:29 +00:00
import Api.Types
import qualified Parsers.KOReader as KO
2023-03-05 09:38:02 +00:00
import Config
import Options.Applicative
2023-02-03 13:45:16 +00:00
2023-03-05 11:16:56 +00:00
data User = User
{ username :: T.Text
, password :: ByteString
} deriving (Show, Eq)
2023-03-05 09:38:02 +00:00
type API = Get '[HTML] Quote
:<|> "quotes" :> Get '[JSON] [Quote]
2023-02-27 18:16:29 +00:00
:<|> "quote" :> "random" :> Get '[JSON] Quote
2023-03-05 09:38:02 +00:00
:<|> "today" :> Get '[HTML] Quote
2023-03-05 11:16:56 +00:00
:<|> BasicAuth "update data" User :> ("koreader" :> ReqBody '[JSON] KO.KoHighlight :> Post '[JSON] NoContent)
2023-02-03 22:40:28 +00:00
api :: Proxy API
api = Proxy
2023-03-05 11:16:56 +00:00
checkBasicAuth :: T.Text -> ShortText -> BasicAuthCheck User
checkBasicAuth user passhash = BasicAuthCheck $ \authData ->
let username = decodeUtf8 (basicAuthUsername authData)
password = basicAuthPassword authData
in
case user == username of
False -> return NoSuchUser
True -> case verifyEncoded passhash password of
Argon2Ok -> return $ Authorized $ User username password
_ -> return Unauthorized
2023-02-03 22:40:28 +00:00
initDb :: FilePath -> IO ()
initDb dbFile = withConnection dbFile $ \conn ->
execute_ conn
2023-02-27 18:16:29 +00:00
[sql|CREATE TABLE IF NOT EXISTS quotes ( quote text non null
, author text
, title text
, page text
, chapter text
, created_on integer);|]
2023-02-03 22:40:28 +00:00
2023-02-27 18:16:29 +00:00
-- | TODO: readerT
2023-02-03 22:40:28 +00:00
server :: FilePath -> Server API
2023-03-05 11:16:56 +00:00
server dbf = randomQuote dbf :<|> listQuotes dbf :<|> randomQuote dbf :<|> randomQuote dbf :<|> (\_ -> addKoReader dbf)
2023-02-27 18:16:29 +00:00
-- | API begins here
randomQuote :: FilePath -> Handler Quote
randomQuote db = do
2023-03-05 11:16:56 +00:00
qts <- liftIO $ withConnection db $ \c -> query_ c qry
case length qts of
2023-02-27 18:16:29 +00:00
0 -> undefined
_ -> pure (head qts)
where
qry = [sql|SELECT * FROM quotes ORDER BY RANDOM();|]
2023-02-03 22:40:28 +00:00
listQuotes :: FilePath -> Handler [Quote]
listQuotes db = liftIO $ withConnection db $ \conn -> query_ conn [sql|SELECT * FROM quotes;|]
2023-02-27 18:16:29 +00:00
addKoReader :: FilePath -> KO.KoHighlight -> Handler NoContent
addKoReader db hl = do
liftIO $ withConnection db $ \c ->
executeMany c qry qts
pure NoContent
where
qry = [sql|INSERT INTO quotes VALUES (?,?,?,?,?,?);|]
qts = KO.parse hl
2023-03-05 09:38:02 +00:00
runApp :: AppConfig -> IO ()
2023-03-05 11:16:56 +00:00
runApp c = run (appPort c) (serveWithContext api ctx $ server (appDbFile c))
where
ctx = checkBasicAuth (appUser c) (fromText $ appPassHash c):. EmptyContext
2023-03-05 09:38:02 +00:00
main :: IO ()
main = do
conf <- execParser parserOpts
putStrLn $ "running with conf" <> show conf
initDb (appDbFile conf)
runApp conf