@@ -11,6 +11,7 @@ module Obelisk.Backend
1111 ( Backend (..)
1212 -- * Re-exports
1313 , Default (def)
14+ , ComponentConfigs(..)
1415 , getPageName
1516 , getRouteWith
1617 , runSnapWithCommandLineArgs
@@ -19,6 +20,7 @@ module Obelisk.Backend
1920 , runBackend
2021 , staticRenderContentType
2122 , mkRouteToUrl
23+ , getComponentConfigs
2224 ) where
2325
2426import Prelude hiding (id, (.))
@@ -32,11 +34,15 @@ import Data.Default (Default (..))
3234import Data.Dependent.Sum
3335import Data.Functor.Sum
3436import Data.Functor.Identity
37+import Data.Map (Map)
38+import qualified Data.Map as Map
3539import Data.Monoid ((<>))
3640import Data.Text (Text)
3741import qualified Data.Text as T
3842import Data.Text.Encoding
3943import Obelisk.Asset.Serve.Snap (serveAsset)
44+import Obelisk.ExecutableConfig.Backend
45+import Obelisk.ExecutableConfig.Lookup
4046import Obelisk.Frontend
4147import Obelisk.Route
4248import Obelisk.Snap.Extras (doNotCache, serveFileIfExistsAs)
@@ -49,7 +55,14 @@ import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
4955
5056data Backend backendRoute frontendRoute = Backend
5157 { _backend_routeEncoder :: Encoder (Either Text) Identity (R (Sum backendRoute (ObeliskRoute frontendRoute))) PageName
52− , _backend_run :: ((R backendRoute -> Snap ()) -> IO ()) -> IO ()
58+ , _backend_run :: ((R backendRoute -> BackendConfigsT Snap ()) -> BackendConfigsT IO ()) -> BackendConfigsT IO ()
59+ }
60+
61+-- | All the configs separated by which context they are accessible in. (Common
62+-- configs are present in both.)
63+data ComponentConfigs = ComponentConfigs
64+ { _componentConfigs_backend :: Map Text Text
65+ , _componentConfigs_frontend :: Map Text Text
5366 }
5467
5568-- | The static assets provided must contain a compiled GHCJS app that corresponds exactly to the Frontend provided
@@ -67,9 +80,10 @@ serveDefaultObeliskApp
6780 -> ([Text]
6881 -> m ())
6982 -> Frontend (R appRoute)
83+ -> Map Text Text
7084 -> R (ObeliskRoute appRoute)
7185 -> m ()
72−serveDefaultObeliskApp urlEnc serveStaticAsset frontend = serveObeliskApp urlEnc serveStaticAsset frontendApp
86+serveDefaultObeliskApp urlEnc serveStaticAsset frontend configs = serveObeliskApp urlEnc serveStaticAsset frontendApp configs
7387 where frontendApp = GhcjsApp
7488 { _ghcjsApp_compiled = defaultFrontendGhcjsAssets
7589 , _ghcjsApp_value = frontend
@@ -94,16 +108,16 @@ defaultFrontendGhcjsAssets = StaticAssets
94108 , _staticAssets_unprocessed = "frontend.jsexe"
95109 }
96110
97−runSnapWithCommandLineArgs :: Snap () -> IO ()
111+runSnapWithCommandLineArgs :: MonadIO m => Snap () -> m ()
98112runSnapWithCommandLineArgs a = do
99113 -- Get the web server configuration from the command line
100− cmdLineConf <- commandLineConfig defaultConfig
114+ cmdLineConf <- liftIO $ commandLineConfig defaultConfig
101115 let httpConf = cmdLineConf
102116 { accessLog = Just $ ConfigIoLog BSC8.putStrLn
103117 , errorLog = Just $ ConfigIoLog BSC8.putStrLn
104118 }
105119 -- Start the web server
106− httpServe httpConf a
120+ liftIO $ httpServe httpConf a
107121
108122getPageName :: (MonadSnap m) => m PageName
109123getPageName = do
@@ -125,13 +139,14 @@ serveObeliskApp
125139 => (R appRoute -> Text)
126140 -> ([Text] -> m ())
127141 -> GhcjsApp (R appRoute)
142+ -> Map Text Text
128143 -> R (ObeliskRoute appRoute)
129144 -> m ()
130−serveObeliskApp urlEnc serveStaticAsset frontendApp = \case
131− ObeliskRoute_App appRouteComponent :=> Identity appRouteRest -> serveGhcjsApp urlEnc frontendApp $ GhcjsAppRoute_App appRouteComponent :/ appRouteRest
145+serveObeliskApp urlEnc serveStaticAsset frontendApp config = \case
146+ ObeliskRoute_App appRouteComponent :=> Identity appRouteRest -> serveGhcjsApp urlEnc frontendApp config $ GhcjsAppRoute_App appRouteComponent :/ appRouteRest
132147 ObeliskRoute_Resource resComponent :=> Identity resRest -> case resComponent :=> Identity resRest of
133148 ResourceRoute_Static :=> Identity pathSegments -> serveStaticAsset pathSegments
134− ResourceRoute_Ghcjs :=> Identity pathSegments -> serveGhcjsApp urlEnc frontendApp $ GhcjsAppRoute_Resource :/ pathSegments
149+ ResourceRoute_Ghcjs :=> Identity pathSegments -> serveGhcjsApp urlEnc frontendApp config $ GhcjsAppRoute_Resource :/ pathSegments
135150 ResourceRoute_JSaddleWarp :=> Identity _ -> do
136151 let msg = "Error: Obelisk.Backend received jsaddle request"
137152 liftIO $ putStrLn $ T.unpack msg
@@ -159,24 +174,30 @@ serveGhcjsApp
159174 :: (MonadSnap m, HasCookies m)
160175 => (R appRouteComponent -> Text)
161176 -> GhcjsApp (R appRouteComponent)
177+ -> Map Text Text
162178 -> R (GhcjsAppRoute appRouteComponent)
163179 -> m ()
164−serveGhcjsApp urlEnc app = \case
180+serveGhcjsApp urlEnc app config = \case
165181 GhcjsAppRoute_App appRouteComponent :=> Identity appRouteRest -> do
166182 modifyResponse $ setContentType staticRenderContentType
167− writeBS <=< renderGhcjsFrontend urlEnc (appRouteComponent :/ appRouteRest) $ _ghcjsApp_value app
183+ writeBS <=< renderGhcjsFrontend urlEnc (appRouteComponent :/ appRouteRest) config $ _ghcjsApp_value app
168184 GhcjsAppRoute_Resource :=> Identity pathSegments -> serveStaticAssets (_ghcjsApp_compiled app) pathSegments
169185
170186runBackend :: Backend fullRoute frontendRoute -> Frontend (R frontendRoute) -> IO ()
171187runBackend backend frontend = case checkEncoder $ _backend_routeEncoder backend of
172188 Left e -> fail $ "backend error:\n" <> T.unpack e
173− Right validFullEncoder -> _backend_run backend $ \serveRoute -> do
174− runSnapWithCommandLineArgs $ do
175− getRouteWith validFullEncoder >>= \case
176− Identity r -> case r of
177− InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
178− InR obeliskRoute :=> Identity a ->
179− serveDefaultObeliskApp (mkRouteToUrl validFullEncoder) (serveStaticAssets defaultStaticAssets) frontend $ obeliskRoute :/ a
189+ Right validFullEncoder -> do
190+ configs <- getComponentConfigs
191+ runBackendConfigsT (_componentConfigs_backend configs) $
192+ _backend_run backend $ \serveRoute -> do
193+ runSnapWithCommandLineArgs $ do
194+ getRouteWith validFullEncoder >>= \case
195+ Identity r -> case r of
196+ InL backendRoute :=> Identity a ->
197+ runBackendConfigsT (_componentConfigs_backend configs) $
198+ serveRoute $ backendRoute :/ a
199+ InR obeliskRoute :=> Identity a ->
200+ serveDefaultObeliskApp (mkRouteToUrl validFullEncoder) (serveStaticAssets defaultStaticAssets) frontend (_componentConfigs_frontend configs) $ obeliskRoute :/ a
180201
181202mkRouteToUrl :: Encoder Identity parse (R (Sum f (ObeliskRoute r))) PageName -> R r -> Text
182203mkRouteToUrl validFullEncoder =
@@ -187,15 +208,25 @@ renderGhcjsFrontend
187208 :: (MonadSnap m, HasCookies m)
188209 => (route -> Text)
189210 -> route
211+ -> Map Text Text
190212 -> Frontend route
191213 -> m ByteString
192−renderGhcjsFrontend urlEnc route f = do
214+renderGhcjsFrontend urlEnc route configs f = do
193215 let ghcjsPreload = elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: "ghcjs/all.js") blank
194216 ghcjsScript = elAttr "script" ("language" =: "javascript" <> "src" =: "ghcjs/all.js" <> "defer" =: "defer") blank
195217 cookies <- askCookies
196− renderFrontendHtml cookies urlEnc route
197− (_frontend_head f >> ghcjsPreload)
198− (_frontend_body f >> ghcjsScript)
218+ renderFrontendHtml configs cookies urlEnc route f ghcjsPreload ghcjsScript
199219
200220instance HasCookies Snap where
201221 askCookies = map (\c -> (cookieName c, cookieValue c)) <$> getsRequest rqCookies
222+
223+getComponentConfigs :: IO ComponentConfigs
224+getComponentConfigs = do
225+ allConfigs <- getConfigs
226+ return $ ComponentConfigs
227+ { _componentConfigs_backend =
228+ Map.filterWithKey (\k _ -> isMemberOf k ["common", "backend"]) allConfigs
229+ , _componentConfigs_frontend =
230+ Map.filterWithKey (\k _ -> isMemberOf k ["common", "frontend"]) allConfigs
231+ }
232+ where isMemberOf k dirs = or $ map (\dir -> ("config/" <> dir) `T.isPrefixOf` k) dirs