Replace hardcoded values with flags

This commit is contained in:
Saku Laesvuori 2023-09-15 18:05:58 +03:00
parent 3fc144bc78
commit d0d32a04fe
Signed by: slaesvuo
GPG Key ID: 257D284A2A1D3A32
6 changed files with 90 additions and 21 deletions

View File

@ -34,6 +34,7 @@
ghc-mime-mail ghc-mime-mail
ghc-morpheus-graphql ghc-morpheus-graphql
ghc-morpheus-graphql-client ghc-morpheus-graphql-client
ghc-optparse-applicative
ghc-persistent ghc-persistent
ghc-persistent-postgresql ghc-persistent-postgresql
ghc-scotty ghc-scotty

View File

@ -26,6 +26,7 @@ executable datarekisteri
morpheus-graphql-core, morpheus-graphql-core,
morpheus-graphql-server, morpheus-graphql-server,
mtl, mtl,
optparse-applicative,
persistent, persistent,
persistent-postgresql, persistent-postgresql,
process, process,

View File

@ -19,13 +19,31 @@ import Client.Types
import Client.Handlers import Client.Handlers
import Client.Auth () import Client.Auth ()
import Yesod.Static (static, Static) import Yesod.Static (static, Static)
import Server (runMigrations) import Options.Applicative
import Server (Config(..), runMigrations, checkSendmailPath, configOpts)
import System.Directory (createDirectoryIfMissing) import System.Directory (createDirectoryIfMissing)
mkYesodDispatch "DataIdClient" resourcesDataIdClient mkYesodDispatch "DataIdClient" resourcesDataIdClient
main :: IO () main :: IO ()
main = runMigrations >> getStaticDir "/tmp/data-id" >>= warp 3000 . DataIdClient main = do
yesodConfig' <- readConfig
serverConfig' <- checkSendmailPath $ serverConfig yesodConfig'
let config = yesodConfig' {serverConfig = serverConfig'}
runMigrations (configDbUrl . serverConfig $ config)
static <- getStaticDir "/tmp/data-id"
warp (configPort . serverConfig $ config) $ DataIdClient static config
readConfig :: IO YesodConfig
readConfig = execParser $ info (configOpts' <**> helper)
(fullDesc <> progDesc "Serve datarekisteri http client and graphql server"
<> header "Client and server backend servers for datarekisteri")
configOpts' :: Parser YesodConfig
configOpts' = YesodConfig
<$> configOpts
<*> strOption (long "approot" <> short 'r' <> metavar "URL" <> value "http://localhost:3100" <> help "External URL of the server path /")
-- TODO make port depend on the --port flag
getStaticDir :: FilePath -> IO Static getStaticDir :: FilePath -> IO Static
getStaticDir dir = createDirectoryIfMissing True dir >> static dir getStaticDir dir = createDirectoryIfMissing True dir >> static dir

View File

@ -33,4 +33,6 @@ getHomeR :: Handler Html
getHomeR = ifM (isJust <$> maybeAuthId) (redirect OwnProfileR) (redirect $ AuthR LoginR) getHomeR = ifM (isJust <$> maybeAuthId) (redirect OwnProfileR) (redirect $ AuthR LoginR)
postApiR :: Handler () postApiR :: Handler ()
postApiR = sendWaiApplication =<< liftIO serverApp postApiR = do
config <- serverConfig . yesodConfig <$> getYesod
sendWaiApplication =<< liftIO (serverApp config)

View File

@ -29,10 +29,18 @@ import Data.Time.Format.ISO8601 (iso8601Show)
import Client.Auth import Client.Auth
import Client.ApiRequests import Client.ApiRequests
import Data.Morpheus.Client import Data.Morpheus.Client
import Server (readPermission) import Server (Config(..), readPermission)
import Data.Map (findWithDefault) import Data.Map (findWithDefault)
data DataIdClient = DataIdClient { getStatic :: Static } data DataIdClient = DataIdClient
{ getStatic :: Static
, yesodConfig :: YesodConfig
}
data YesodConfig = YesodConfig
{ serverConfig :: Config
, configApproot :: Text
}
instance PathPiece UserID where instance PathPiece UserID where
toPathPiece (UserID id) = show id toPathPiece (UserID id) = show id
@ -124,7 +132,7 @@ authorizedHelper scope False = requirePermission scope ReadOnly
instance Yesod DataIdClient where instance Yesod DataIdClient where
authRoute = const $ Just $ AuthR LoginR authRoute = const $ Just $ AuthR LoginR
approot = ApprootStatic "http://localhost:3000" approot = ApprootMaster $ configApproot . yesodConfig
makeSessionBackend = const $ sslOnlySessions $ laxSameSiteSessions $ makeSessionBackend = const $ sslOnlySessions $ laxSameSiteSessions $
Just <$> defaultClientSessionBackend (60*24*7) "client_session_key.aes" Just <$> defaultClientSessionBackend (60*24*7) "client_session_key.aes"
isAuthorized (AuthR _) _ = pure Authorized isAuthorized (AuthR _) _ = pure Authorized

View File

@ -15,9 +15,9 @@ import Data.Map (findWithDefault)
import Data.Text (toLower, breakOn, stripPrefix) import Data.Text (toLower, breakOn, stripPrefix)
import Database.Persist (entityVal) import Database.Persist (entityVal)
import Database.Persist.Postgresql (withPostgresqlConn, runSqlConn) import Database.Persist.Postgresql (withPostgresqlConn, runSqlConn)
import Network.Mail.Mime (renderSendMail, Address(..)) import Network.Mail.Mime (renderSendMailCustom, Address(..))
import Network.Wai (Application) import Network.Wai (Application)
import Network.Wai.Handler.Warp (run) import Network.Wai.Handler.Warp (Port, run)
import Network.Wai.Middleware.Cors import Network.Wai.Middleware.Cors
import Network.Wai.Middleware.Gzip import Network.Wai.Middleware.Gzip
import Server.API import Server.API
@ -25,24 +25,48 @@ import Server.DB
import Server.DB.Queries (getUserByEmail, getPermissions, getToken) import Server.DB.Queries (getUserByEmail, getPermissions, getToken)
import Server.Types import Server.Types
import Server.Utils (checkPassword) import Server.Utils (checkPassword)
import System.Directory (findExecutable)
import System.Process (callProcess) import System.Process (callProcess)
import Options.Applicative hiding (header)
import qualified Options.Applicative as O
import Web.Scotty.Trans hiding (readEither) import Web.Scotty.Trans hiding (readEither)
import qualified "base64" Data.ByteString.Base64 as B64 (decodeBase64) import qualified "base64" Data.ByteString.Base64 as B64 (decodeBase64)
import Paths_datarekisteri import Paths_datarekisteri
main :: IO () main :: IO ()
main = run 3100 =<< serverApp main = do
config <- readConfig >>= checkSendmailPath
runMigrations (configDbUrl config)
serverApp config >>= run 3100
dbUrl :: IsString a => a readConfig :: IO Config
dbUrl = "postgres:///id.rekisteri" readConfig = do
execParser $ info (configOpts <**> helper)
(fullDesc <> progDesc "Serve a GraphQL API for datarekisteri"
<> O.header "Backend API server for datarekisteri")
runMigrations :: IO () checkSendmailPath :: Config -> IO Config
runMigrations = do checkSendmailPath config = do
sendmailPath <- maybe (error "Sendmail command or file not found!") pure =<<
findExecutable (configSendmail config)
pure $ config {configSendmail = sendmailPath}
configOpts :: Parser Config
configOpts = Config
<$> strOption (long "sendmail" <> short 'm' <> metavar "COMMAND" <> value "sendmail" <> help "Sendmail command")
<*> (Address
<$> optional (strOption (long "sender-name" <> short 'n' <> metavar "NAME" <> help "Display name for email address"))
<*> strOption (long "address" <> short 'a' <> metavar "EMAIL" <> help "Email address to send mail"))
<*> option auto (long "port" <> short 'p' <> metavar "PORT" <> value 3100 <> help "Port to listen on")
<*> strOption (long "db-url" <> short 'u' <> metavar "URL" <> value "postgres:///id.rekisteri" <> help "Postgresql database url")
runMigrations :: Text -> IO ()
runMigrations dbUrl = do
migrationsPath <- getDataFileName "db/migrations" migrationsPath <- getDataFileName "db/migrations"
callProcess "dbmate" ["--url", dbUrl, "--migrations-dir", migrationsPath, "up"] callProcess "dbmate" ["--url", toString dbUrl, "--migrations-dir", migrationsPath, "up"]
serverApp :: IO Application serverApp :: Config -> IO Application
serverApp = scottyAppT runAPIM $ do serverApp config = scottyAppT (runAPIM config) $ do
middleware $ gzip def middleware $ gzip def
middleware $ cors $ const $ Just CorsResourcePolicy middleware $ cors $ const $ Just CorsResourcePolicy
{ corsOrigins = Nothing -- all { corsOrigins = Nothing -- all
@ -122,18 +146,29 @@ newtype APIM a = APIM (ReaderT RequestState IO a)
data RequestState = RequestState data RequestState = RequestState
{ stateCurrentUser :: Maybe UserID { stateCurrentUser :: Maybe UserID
, statePermissions :: Map Scope Permission , statePermissions :: Map Scope Permission
, stateConfig :: Config
} }
data Config = Config
{ configSendmail :: FilePath
, configEmailAddress :: Address
, configPort :: Port
, configDbUrl :: Text
}
instance MonadTime APIM where instance MonadTime APIM where
currentTime = liftIO currentTime currentTime = liftIO currentTime
instance MonadDB APIM where instance MonadDB APIM where
runQuery = liftIO . runStderrLoggingT . withPostgresqlConn dbUrl . runSqlConn runQuery query = do
dbUrl <- asks $ configDbUrl . stateConfig
liftIO $ runStderrLoggingT $ withPostgresqlConn (encodeUtf8 dbUrl) $ runSqlConn query
instance MonadEmail APIM where instance MonadEmail APIM where
sendEmail = liftIO . renderSendMail sendEmail email = do
fromAddress = pure $ Address Nothing "id@datat.fi" sendmailPath <- asks $ configSendmail . stateConfig
liftIO $ renderSendMailCustom sendmailPath ["-t"] email
fromAddress = asks $ configEmailAddress . stateConfig
instance MonadRequest APIM where instance MonadRequest APIM where
currentUser = asks stateCurrentUser currentUser = asks stateCurrentUser
@ -163,5 +198,9 @@ instance MonadPermissions APIM where
readPermission :: Text -> Maybe (Map Scope Permission) readPermission :: Text -> Maybe (Map Scope Permission)
readPermission = rightToMaybe . readEither . toString readPermission = rightToMaybe . readEither . toString
runAPIM :: APIM a -> IO a runAPIM :: Config -> APIM a -> IO a
runAPIM (APIM m) = runReaderT m RequestState { stateCurrentUser = Nothing, statePermissions = fromList [] } runAPIM config (APIM m) = runReaderT m RequestState
{ stateCurrentUser = Nothing
, statePermissions = fromList []
, stateConfig = config
}