{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE LambdaCase #-} -- | spawns a few processes that serve the que.run website -- -- : exe que-website -- -- : dep async -- : dep config-ini -- : dep process -- : dep protolude -- : dep req module Run.Que.Website ( main ) where import qualified Control.Concurrent.Async as Async import Com.Simatime.Alpha import qualified Data.ByteString.Char8 as BS import qualified Data.Ini.Config as Config import qualified Data.Text as Text import Data.Text.Encoding ( encodeUtf8 ) import qualified Data.Text.IO as Text import Network.HTTP.Req import Prelude ( error ) import qualified System.Directory as Directory import System.Environment as Environment import qualified System.Exit as Exit import System.FilePath ( () ) import qualified System.Process as Process main :: IO () main = do (src, ns) <- Environment.getArgs >>= \case [src] -> return (src, "_") -- default to _ ns which is special [src, ns] -> return (src, ns) _ -> Exit.die "usage: que-website [namespace]" home <- Directory.getHomeDirectory conf <- Text.readFile <| home ".config" "que.conf" let (Auth _ key) = either needConf identity <| Config.parseIniFile conf <| auth <| Text.pack ns putStrLn $ "serving " ++ src ++ " at " ++ ns run key (Text.pack ns) $ Sources { index = src "index.md" , client = src "client.py" , quescripts = src "quescripts.md" , style = src "style.css" , apidocs = src "apidocs.md" , tutorial = src "tutorial.md" } needConf :: error needConf = error "you need a ~/.config/que.conf" data Sources = Sources { index :: FilePath , quescripts :: FilePath , client :: FilePath , style :: FilePath , tutorial :: FilePath , apidocs :: FilePath } type Namespace = Text type Key = Text data Auth = Auth Namespace Key auth :: Text -> Config.IniParser Auth auth ns = Config.section ns $ do key <- Config.field "key" return <| Auth ns key run :: Key -> Text -> Sources -> IO () run key ns Sources {..} = Async.runConcurrently actions >> return () where actions = traverse Async.Concurrently [ forever <| toHtml index >>= serve key ns "index" , forever <| toHtml quescripts >>= serve key ns "quescripts" , forever <| BS.readFile client >>= serve key ns "client" , forever <| toHtml tutorial >>= serve key ns "tutorial" , forever <| toHtml apidocs >>= serve key ns "apidocs" ] toHtml :: FilePath -> IO ByteString toHtml md = BS.pack <$> Process.readProcess "pandoc" [ "--self-contained" , "--css" , style , "-i" , md , "--from" , "markdown" , "--to" , "html" ] [] -- TODO: recover from 502 errors serve :: Key -> Namespace -> Text -> ByteString -> IO () serve key ns path content = runReq defaultHttpConfig $ do let options = header "Authorization" (encodeUtf8 key) <> responseTimeout maxBound _ <- req POST (http "que.run" /: ns /: path) (ReqBodyBs content) ignoreResponse options liftIO $ return ()