module Main where import Control.Logger.Simple qualified as Log import DB import Data.Aeson qualified as A import Data.Functor ((<&>)) import Data.SecureMem (secureMemFromByteString) import Data.Text qualified as T import Data.Text.Encoding qualified as T import Data.Text.IO qualified as T import Database.Sqlite.Easy qualified as Sqlite import Network.Wai.Handler.Warp (Port, run) import Network.Wai.Middleware.HttpAuth (basicAuth) import Network.Wai.Middleware.RequestLogger qualified as Logger import Network.Wai.Middleware.RequestSizeLimit qualified as Limit import Network.Wai.Middleware.Routed qualified as Wai import Routes import Fedi qualified as Fedi import System.Environment (getArgs, lookupEnv) import Web.Twain qualified as Twain data Command = Serve | Insert FilePath main :: IO () main = do command <- getArgs >>= \case ["insert", file] -> pure (Insert file) ["serve"] -> pure Serve _ -> usageError case command of Insert file -> do insertNoteFromFile file Serve -> serve insertNoteFromFile :: FilePath -> IO () insertNoteFromFile file = do connStr <- maybe "/tmp/fediserve_sqlite_db.sqlite" fromString <$> lookupEnv "FEDI_CONN_STRING" content <- T.readFile file detailsFile <- lookupEnv "FEDI_DETAILS" <&> maybe (error "missing FEDI_DETAILS") id details <- A.eitherDecodeFileStrict detailsFile <&> either (\err -> error $ "could not read file '" <> detailsFile <> "'.\n" <> err) id db <- mkDB connStr details note <- db.insertNote NoteEntry { content = content , inReplyTo = Nothing , name = Nothing , url = Nothing } putStrLn "Inserted." print note serve :: IO () serve = Log.withGlobalLogging (Log.LogConfig (Just "fedi.log") True) do logLevel <- maybe Log.LogDebug read <$> lookupEnv "FEDI_LOG_LEVEL" Log.setLogLevel logLevel auth <- fmap (T.splitOn "," . T.pack) <$> lookupEnv "FEDI_AUTH" authMiddleware <- case auth of Nothing -> do Log.logInfo "Starting server with authentication disabled." pure id Just [user, pass] -> do Log.logInfo "Starting server with authentication enabled," let username = secureMemFromByteString $ T.encodeUtf8 user password = secureMemFromByteString $ T.encodeUtf8 pass pure $ basicAuth ( \u p -> pure $ secureMemFromByteString u == username && secureMemFromByteString p == password ) "My Fediserve" Just {} -> usageError fediPort <- maybe 3001 read <$> lookupEnv "FEDI_PORT" conn <- maybe "/tmp/fediserve_sqlite_db.sqlite" fromString <$> lookupEnv "FEDI_CONN_STRING" Log.logInfo $ "and with connection string " <> Sqlite.unConnectionString conn <> "." runServer fediPort authMiddleware =<< mkFediApp conn usageError :: err usageError = errorWithoutStackTrace $ unlines [ "Usage: fedi [ insert | serve ]" , "Env vars:" , " - FEDI_PORT=" , " - FEDI_DETAILS=" , " - FEDI_CONN_STRING=" , " - FEDI_AUTH=," , " - FEDI_LOG=[ LogTrace | LogDebug | LogInfo | LogNote | LogWarn | LogError ]" ] -- | Run server at at specific port. runServer :: Port -> Twain.Middleware -> Twain.Application -> IO () runServer port authMiddleware app = do Log.logInfo $ T.unwords [ "Running fedi at" , "http://localhost:" <> T.pack (show port) , "(ctrl-c to quit)" ] auth <- Wai.routedMiddleware matchAdmin <$> pure authMiddleware run port $ ( Logger.logStdoutDev . Limit.requestSizeLimitMiddleware Limit.defaultRequestSizeLimitSettings . auth ) app matchAdmin :: [T.Text] -> Bool matchAdmin = any (== "admin") -- | Application description. mkFediApp :: Sqlite.ConnectionString -> IO Twain.Application mkFediApp connStr = do detailsFile <- lookupEnv "FEDI_DETAILS" <&> maybe (error "missing FEDI_DETAILS") id details <- Fedi.readUserDetailsFile detailsFile db <- mkDB connStr details pure $ foldr ($) (Twain.notFound $ Twain.send $ Twain.text "Error: not found.") (routes db detailsFile)