summaryrefslogtreecommitdiff
path: root/Com/MusicMeetsComics/Server/Config.hs
blob: 2bbfabc72ed9e6c3baf9bfa146fdf6bfcc6fd8e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Com.MusicMeetsComics.Server.Config where

import           Com.MusicMeetsComics.Server.Logger
import           Control.Concurrent (ThreadId)
import           Control.Monad.Except (ExceptT, MonadError)
import           Control.Monad.IO.Class
import           Control.Monad.Logger (MonadLogger(..))
import           Control.Monad.Metrics (Metrics, MonadMetrics, getMetrics)
import qualified Control.Monad.Metrics as M
import           Control.Monad.Reader (MonadIO, MonadReader, ReaderT, asks)
import           Data.Text (pack)
import           GHC.Base (String)
import           Network.Wai (Middleware)
import           Network.Wai.Handler.Warp (Port)
import           Network.Wai.Middleware.RequestLogger (logStdout, logStdoutDev)
import           Protolude
import           Safe (readMay)
import           Servant (ServantErr)
import           System.Environment (lookupEnv)
import           System.Remote.Monitoring (forkServer, serverMetricStore, serverThreadId)

-- | This type represents the effects we want to have for our application.
-- We wrap the standard Servant monad with 'ReaderT Config', which gives us
-- access to the application configuration using the 'MonadReader'
-- interface's 'ask' function.
--
-- By encapsulating the effects in our newtype, we can add layers to the
-- monad stack without having to modify code that uses the current layout.
newtype AppT m a = AppT
  { runApp :: ReaderT Config (ExceptT ServantErr m) a
  } deriving ( Functor
             , Applicative
             , Monad
             , MonadReader Config
             , MonadError ServantErr
             , MonadIO
             )

type App = AppT IO

-- | The Config for our application is (for now) the 'Environment' we're
-- running in and a Persistent 'ConnectionPool'.
data Config = Config
  { configEnv :: Environment
  , configMetrics :: Metrics
  , configEkgServer :: ThreadId
  , configLogEnv :: LogEnv
  , configPort :: Port
  }

instance Monad m => MonadMetrics (AppT m) where
  getMetrics = asks configMetrics

-- | Katip instance for @AppT m@
instance MonadIO m => Katip (AppT m) where
  getLogEnv = asks configLogEnv
  localLogEnv = panic "not implemented"

-- | MonadLogger instance to use within @AppT m@
instance MonadIO m => MonadLogger (AppT m) where
  monadLoggerLog = adapt logMsg

-- | MonadLogger instance to use in @makePool@
instance MonadIO m => MonadLogger (KatipT m) where
  monadLoggerLog = adapt logMsg

-- | Right now, we're distinguishing between three environments. We could
-- also add a @Staging@ environment if we needed to.
data Environment
  = Development
  | Test
  | Production
  deriving (Eq, Show, Read)

-- | This returns a 'Middleware' based on the environment that we're in.
setLogger :: Environment -> Middleware
setLogger Test = identity
setLogger Development = logStdoutDev
setLogger Production = logStdout

-- | Web request logger (currently unimplemented and unused). For inspiration
-- see ApacheLogger from wai-logger package.
katipLogger :: LogEnv -> Middleware
katipLogger env app req respond =
  runKatipT env $ do
    logMsg "web" InfoS "todo: received some request"
    -- todo: log proper request data
    liftIO $ app req respond

-- | The number of pools to use for a given environment.
envPool :: Environment -> Int
envPool Test = 1
envPool Development = 1
envPool Production = 8

-- | Allocates resources for 'Config'
acquire :: IO Config
acquire = do
  port <- lookupSetting "PORT" 3001
  env <- lookupSetting "ENV" Development
  logEnv <- defaultLogEnv
  ekgServer <- forkServer "localhost" 8000
  let store = serverMetricStore ekgServer
  metr <- M.initializeWith store
  pure
    Config
      { configEnv = env
      , configMetrics = metr
      , configLogEnv = logEnv
      , configPort = port
      , configEkgServer = serverThreadId ekgServer
      }

-- | Looks up a setting in the environment, with a provided default, and
-- 'read's that information into the inferred type.
lookupSetting :: Read a => String -> a -> IO a
lookupSetting env def_ = do
  maybeValue <- lookupEnv env
  case maybeValue of
    Nothing  -> return def_
    Just str -> maybe (handleFailedRead str) return (readMay str)
 where
  handleFailedRead str = panic
    $ mconcat ["Failed to read [[", pack str, "]] for environment variable ", pack env]