packages feed

ihp-ide-1.4.0: IHP/IDE/ToolServer.hs

module IHP.IDE.ToolServer (runToolServer) where

import IHP.Prelude
import qualified Network.Wai as Wai
import qualified Network.Wai.Handler.Warp as Warp
import IHP.IDE.Types
import IHP.IDE.PortConfig
import qualified IHP.ControllerSupport as ControllerSupport
import IHP.ApplicationContext
import IHP.ModelSupport
import IHP.RouterSupport hiding (get)
import Network.Wai.Session.ClientSession (clientsessionStore)
import qualified Web.ClientSession as ClientSession
import Network.Wai.Middleware.MethodOverridePost (methodOverridePost)
import Network.Wai.Session (withSession)
import qualified Network.WebSockets as Websocket
import qualified Network.Wai.Handler.WebSockets as Websocket

import qualified IHP.FrameworkConfig as Config
import IHP.IDE.SchemaDesigner.Controller.EnumValues ()
import IHP.IDE.SchemaDesigner.Controller.Enums ()
import IHP.IDE.SchemaDesigner.Controller.Columns ()
import IHP.IDE.SchemaDesigner.Controller.Policies ()
import IHP.IDE.SchemaDesigner.Controller.Schema ()
import IHP.IDE.SchemaDesigner.Controller.Tables ()
import IHP.IDE.SchemaDesigner.Controller.Migrations ()
import IHP.IDE.SchemaDesigner.Controller.Indexes ()
import IHP.IDE.Data.Controller ()
import IHP.IDE.Logs.Controller ()
import IHP.IDE.CodeGen.Controller ()
import IHP.IDE.ToolServer.Types
import IHP.IDE.ToolServer.Helper.Controller as Helper
import IHP.IDE.ToolServer.Routes ()
import qualified System.Process as Process
import System.Info
import qualified IHP.EnvVar as EnvVar
import qualified IHP.AutoRefresh.Types as AutoRefresh
import qualified IHP.AutoRefresh as AutoRefresh
import IHP.Controller.Context
import qualified IHP.IDE.ToolServer.Layout as Layout
import IHP.Controller.Layout
import qualified IHP.IDE.LiveReloadNotificationServer as LiveReloadNotificationServer
import qualified IHP.Version as Version
import qualified IHP.PGListener as PGListener

import qualified Network.Wai.Application.Static as Static
import qualified Network.Wai.Middleware.Approot as Approot
import qualified WaiAppStatic.Types as Static
import IHP.Controller.NotFound (handleNotFound)
import IHP.Controller.Session (sessionVaultKey)
import Paths_ihp_ide (getDataFileName)

runToolServer :: (?context :: Context) => ToolServerApplication -> _ -> IO ()
runToolServer toolServerApplication liveReloadClients = do
    let port = ?context.portConfig.toolServerPort |> fromIntegral
    let isDebugMode = ?context.isDebugMode

    startToolServer' toolServerApplication port isDebugMode liveReloadClients

startToolServer' :: (?context :: Context) => ToolServerApplication -> Int -> Bool -> _ -> IO ()
startToolServer' toolServerApplication port isDebugMode liveReloadClients = do

    frameworkConfig <- Config.buildFrameworkConfig do
        Config.option $ Config.AppHostname "localhost"
        Config.option $ Config.AppPort port
        Config.option $ Config.AssetVersion Version.ihpVersion

        ihpIdeBaseUrlEnvVar <- EnvVar.envOrNothing "IHP_IDE_BASEURL"
        case ihpIdeBaseUrlEnvVar of
            Just baseUrl -> Config.option $ Config.BaseUrl baseUrl
            Nothing -> pure ()

    store <- fmap clientsessionStore (ClientSession.getKey "Config/client_session_key.aes")
    let sessionMiddleware :: Wai.Middleware = withSession store "SESSION" (frameworkConfig.sessionCookie) sessionVaultKey
    let modelContext = notConnectedModelContext undefined

    approotMiddleware <- Approot.envFallback
    
    PGListener.withPGListener modelContext \pgListener -> do
        autoRefreshServer <- newIORef (AutoRefresh.newAutoRefreshServer pgListener)
        staticApp <- initStaticApp

        let applicationContext = ApplicationContext { modelContext, autoRefreshServer, frameworkConfig, pgListener }
        let application :: Wai.Application = \request respond -> do
                let ?applicationContext = applicationContext
                frontControllerToWAIApp @ToolServerApplication @AutoRefresh.AutoRefreshWSApp (\app -> app) toolServerApplication staticApp request respond

        let openAppUrl = openUrl ("http://localhost:" <> tshow port <> "/")
        let warpSettings = Warp.defaultSettings
                |> Warp.setPort port
                |> Warp.setBeforeMainLoop openAppUrl

        let logMiddleware = if isDebugMode then frameworkConfig.requestLoggerMiddleware else IHP.Prelude.id

        Warp.runSettings warpSettings $
                logMiddleware $ methodOverridePost $ sessionMiddleware $ approotMiddleware
                    $ Websocket.websocketsOr
                        Websocket.defaultConnectionOptions
                        (LiveReloadNotificationServer.app liveReloadClients)
                        application

initStaticApp :: IO Wai.Application
initStaticApp = do
    toolServerStatic <- getDataFileName "static"
    ihpStatic <- EnvVar.env "IHP_STATIC"

    let ssMaxAge = Static.MaxAgeSeconds (60 * 60 * 24 * 30) -- 30 days
    let ihpStaticSettings = (Static.defaultWebAppSettings ihpStatic)
            { Static.ss404Handler = Just handleNotFound
            , Static.ssMaxAge = ssMaxAge
            }
    let toolServerStaticSettings = (Static.defaultWebAppSettings toolServerStatic)
            { Static.ss404Handler = Just (Static.staticApp ihpStaticSettings)
            , Static.ssMaxAge = ssMaxAge
            }
    pure (Static.staticApp toolServerStaticSettings)

openUrl :: Text -> IO ()
openUrl url = do
    selectedBrowser <- EnvVar.envOrNothing "IHP_BROWSER"
    let defaultOSBrowser = case os of
            "linux" -> "xdg-open"
            "darwin" -> "open"
    let browser = selectedBrowser |> fromMaybe defaultOSBrowser
    async $ Process.callCommand (browser <> " " <> cs url)
    pure ()

instance FrontController ToolServerApplication where
    controllers =
        [ parseRoute @SchemaController
        , parseRoute @TablesController
        , parseRoute @ColumnsController
        , parseRoute @PoliciesController
        , parseRoute @EnumsController
        , parseRoute @EnumValuesController
        , parseRoute @LogsController
        , parseRoute @DataController
        , parseRoute @CodeGenController
        , parseRoute @MigrationsController
        , parseRoute @IndexesController
        , startPage TablesAction
        ]

instance ControllerSupport.InitControllerContext ToolServerApplication where
    initContext = do
        availableApps <- AvailableApps <$> findApplications
        webControllers <- WebControllers <$> findWebControllers

        appPort <- Helper.theAppPort
        let defaultAppUrl = "http://localhost:" <> tshow appPort
        appUrl :: Text <- EnvVar.envOrDefault "IHP_BASEURL" defaultAppUrl

        putContext availableApps
        putContext webControllers
        putContext (AppUrl appUrl)
        setLayout Layout.toolServerLayout

        databaseNeedsMigration <- readDatabaseNeedsMigration
        putContext (DatabaseNeedsMigration databaseNeedsMigration)


readDatabaseNeedsMigration :: (?context :: ControllerContext) => IO Bool
readDatabaseNeedsMigration = do
    context <- fromContext @ToolServerApplication
    readIORef context.databaseNeedsMigration