Merge branch 'master' of gitlab.cip.ifi.lmu.de:jost/UniWorX
This commit is contained in:
commit
7ea7b6f0fc
3
.gitignore
vendored
3
.gitignore
vendored
@ -30,4 +30,5 @@ src/Handler/Course.SnapCustom.hs
|
|||||||
/instance
|
/instance
|
||||||
.stack-work-*
|
.stack-work-*
|
||||||
.directory
|
.directory
|
||||||
tags
|
tags
|
||||||
|
test.log
|
||||||
@ -29,9 +29,11 @@ notification-expiration: 259201
|
|||||||
session-timeout: 7200
|
session-timeout: 7200
|
||||||
|
|
||||||
log-settings:
|
log-settings:
|
||||||
log-detailed: "_env:DETAILED_LOGGING:false"
|
detailed: "_env:DETAILED_LOGGING:false"
|
||||||
log-all: "_env:LOG_ALL:false"
|
all: "_env:LOG_ALL:false"
|
||||||
log-minimum-level: "_env:LOGLEVEL:warn"
|
minimum-level: "_env:LOGLEVEL:warn"
|
||||||
|
destination: "_env:LOGDEST:stderr"
|
||||||
|
|
||||||
|
|
||||||
# Debugging
|
# Debugging
|
||||||
auth-dummy-login: "_env:DUMMY_LOGIN:false"
|
auth-dummy-login: "_env:DUMMY_LOGIN:false"
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
database:
|
database:
|
||||||
# NOTE: By design, this setting prevents the PGDATABASE environment variable
|
database: "_env:PGDATABASE_TEST:uniworx_test"
|
||||||
# from affecting test runs, so that we don't accidentally affect the
|
|
||||||
# production database during testing. If you're not concerned about that and
|
log-settings:
|
||||||
# would like to have environment variable overrides, you could instead use
|
detailed: true
|
||||||
# something like:
|
all: true
|
||||||
#
|
minimum-level: "debug"
|
||||||
# database: "_env:PGDATABASE:uniworx_test"
|
destination: "test.log"
|
||||||
database: uniworx_test
|
|
||||||
|
|
||||||
auth-dummy-login: true
|
auth-dummy-login: true
|
||||||
|
|||||||
@ -30,8 +30,9 @@ import Network.Wai.Middleware.RequestLogger (Destination (Logger),
|
|||||||
IPAddrSource (..),
|
IPAddrSource (..),
|
||||||
OutputFormat (..), destination,
|
OutputFormat (..), destination,
|
||||||
mkRequestLogger, outputFormat)
|
mkRequestLogger, outputFormat)
|
||||||
import System.Log.FastLogger (defaultBufSize, newStderrLoggerSet,
|
import System.Log.FastLogger ( defaultBufSize, newStderrLoggerSet, newStdoutLoggerSet, newFileLoggerSet
|
||||||
toLogStr)
|
, toLogStr, rmLoggerSet
|
||||||
|
)
|
||||||
|
|
||||||
import qualified Data.Map.Strict as Map
|
import qualified Data.Map.Strict as Map
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ import qualified Yesod.Core.Types as Yesod (Logger(..))
|
|||||||
|
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
|
|
||||||
import Control.Lens ((&))
|
import Control.Lens
|
||||||
|
|
||||||
import Data.Proxy
|
import Data.Proxy
|
||||||
|
|
||||||
@ -100,10 +101,30 @@ makeFoundation appSettings@AppSettings{..} = do
|
|||||||
-- Some basic initializations: HTTP connection manager, logger, and static
|
-- Some basic initializations: HTTP connection manager, logger, and static
|
||||||
-- subsite.
|
-- subsite.
|
||||||
appHttpManager <- newManager
|
appHttpManager <- newManager
|
||||||
appLogger <- liftIO $ do
|
appLogSettings <- liftIO $ newTVarIO appInitialLogSettings
|
||||||
tgetter <- newTimeCache "%Y-%m-%d %T %z"
|
|
||||||
loggerSet <- newStderrLoggerSet defaultBufSize
|
let
|
||||||
return $ Yesod.Logger loggerSet tgetter
|
mkLogger LogSettings{..} = do
|
||||||
|
tgetter <- newTimeCache "%Y-%m-%d %T %z"
|
||||||
|
loggerSet <- case logDestination of
|
||||||
|
LogDestStderr -> newStderrLoggerSet defaultBufSize
|
||||||
|
LogDestStdout -> newStdoutLoggerSet defaultBufSize
|
||||||
|
LogDestFile{..} -> newFileLoggerSet defaultBufSize logDestFile
|
||||||
|
return $ Yesod.Logger loggerSet tgetter
|
||||||
|
mkLogger' = liftIO $ do
|
||||||
|
initialSettings <- readTVarIO appLogSettings
|
||||||
|
tVar <- newTVarIO =<< mkLogger initialSettings
|
||||||
|
let updateLogger prevSettings = do
|
||||||
|
newSettings <- atomically $ do
|
||||||
|
newSettings <- readTVar appLogSettings
|
||||||
|
guard $ newSettings /= prevSettings
|
||||||
|
return newSettings
|
||||||
|
oldLogger <- atomically . swapTVar tVar =<< mkLogger newSettings
|
||||||
|
rmLoggerSet $ loggerSet oldLogger
|
||||||
|
updateLogger newSettings
|
||||||
|
(tVar, ) <$> fork (updateLogger initialSettings)
|
||||||
|
appLogger <- over _2 fst <$> allocate mkLogger' (\(tVar, tId) -> killThread tId >> (readTVarIO tVar >>= rmLoggerSet . loggerSet))
|
||||||
|
|
||||||
appStatic <- liftIO $ bool static staticDevel appMutableStatic appStaticDir
|
appStatic <- liftIO $ bool static staticDevel appMutableStatic appStaticDir
|
||||||
|
|
||||||
appInstanceID <- liftIO $ maybe UUID.nextRandom (either readInstanceIDFile return) appInitialInstanceID
|
appInstanceID <- liftIO $ maybe UUID.nextRandom (either readInstanceIDFile return) appInitialInstanceID
|
||||||
@ -111,8 +132,6 @@ makeFoundation appSettings@AppSettings{..} = do
|
|||||||
appJobCtl <- liftIO $ newTVarIO Map.empty
|
appJobCtl <- liftIO $ newTVarIO Map.empty
|
||||||
appCronThread <- liftIO newEmptyTMVarIO
|
appCronThread <- liftIO newEmptyTMVarIO
|
||||||
|
|
||||||
appLogSettings <- liftIO $ newTVarIO appInitialLogSettings
|
|
||||||
|
|
||||||
-- We need a log function to create a connection pool. We need a connection
|
-- We need a log function to create a connection pool. We need a connection
|
||||||
-- pool to create our foundation. And we need our foundation to get a
|
-- pool to create our foundation. And we need our foundation to get a
|
||||||
-- logging function. To get out of this loop, we initially create a
|
-- logging function. To get out of this loop, we initially create a
|
||||||
@ -128,7 +147,9 @@ makeFoundation appSettings@AppSettings{..} = do
|
|||||||
(error "cryptoIDKey forced in tempFoundation")
|
(error "cryptoIDKey forced in tempFoundation")
|
||||||
(error "sessionKey forced in tempFoundation")
|
(error "sessionKey forced in tempFoundation")
|
||||||
(error "errorMsgKey forced in tempFoundation")
|
(error "errorMsgKey forced in tempFoundation")
|
||||||
logFunc = messageLoggerSource tempFoundation appLogger
|
logFunc loc src lvl str = do
|
||||||
|
f <- messageLoggerSource tempFoundation <$> readTVarIO (snd appLogger)
|
||||||
|
f loc src lvl str
|
||||||
|
|
||||||
flip runLoggingT logFunc $ do
|
flip runLoggingT logFunc $ do
|
||||||
$logDebugS "InstanceID" $ UUID.toText appInstanceID
|
$logDebugS "InstanceID" $ UUID.toText appInstanceID
|
||||||
@ -228,12 +249,13 @@ makeLogWare app = do
|
|||||||
|
|
||||||
let
|
let
|
||||||
mkLogWare ls@LogSettings{..} = do
|
mkLogWare ls@LogSettings{..} = do
|
||||||
|
logger <- readTVarIO . snd $ appLogger app
|
||||||
logWare <- mkRequestLogger def
|
logWare <- mkRequestLogger def
|
||||||
{ outputFormat = bool
|
{ outputFormat = bool
|
||||||
(Apache . bool FromSocket FromHeader . appIpFromHeader $ appSettings app)
|
(Apache . bool FromSocket FromHeader . appIpFromHeader $ appSettings app)
|
||||||
(Detailed True)
|
(Detailed True)
|
||||||
logDetailed
|
logDetailed
|
||||||
, destination = Logger . loggerSet $ appLogger app
|
, destination = Logger $ loggerSet logger
|
||||||
}
|
}
|
||||||
atomically . modifyTVar' logWareMap $ HashMap.insert ls logWare
|
atomically . modifyTVar' logWareMap $ HashMap.insert ls logWare
|
||||||
return logWare
|
return logWare
|
||||||
@ -255,9 +277,11 @@ warpSettings foundation = defaultSettings
|
|||||||
& setPort (appPort $ appSettings foundation)
|
& setPort (appPort $ appSettings foundation)
|
||||||
& setHost (appHost $ appSettings foundation)
|
& setHost (appHost $ appSettings foundation)
|
||||||
& setOnException (\_req e ->
|
& setOnException (\_req e ->
|
||||||
when (defaultShouldDisplayException e) $ messageLoggerSource
|
when (defaultShouldDisplayException e) $ do
|
||||||
|
logger <- readTVarIO . snd $ appLogger foundation
|
||||||
|
messageLoggerSource
|
||||||
foundation
|
foundation
|
||||||
(appLogger foundation)
|
logger
|
||||||
$(qLocation >>= liftLoc)
|
$(qLocation >>= liftLoc)
|
||||||
"yesod"
|
"yesod"
|
||||||
LevelError
|
LevelError
|
||||||
@ -322,7 +346,9 @@ getApplicationRepl = do
|
|||||||
return (getPort wsettings, foundation, app1)
|
return (getPort wsettings, foundation, app1)
|
||||||
|
|
||||||
shutdownApp :: MonadIO m => UniWorX -> m ()
|
shutdownApp :: MonadIO m => UniWorX -> m ()
|
||||||
shutdownApp = stopJobCtl
|
shutdownApp app = do
|
||||||
|
stopJobCtl app
|
||||||
|
release . fst $ appLogger app
|
||||||
|
|
||||||
|
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|||||||
10
src/Cron.hs
10
src/Cron.hs
@ -150,10 +150,11 @@ genMatch p m st (CronMatchUnion aGen bGen) = merge (genMatch p m st aGen) (genMa
|
|||||||
|
|
||||||
nextCronMatch :: TZ -- ^ Timezone of the `Cron`-Entry
|
nextCronMatch :: TZ -- ^ Timezone of the `Cron`-Entry
|
||||||
-> Maybe UTCTime -- ^ Time of last execution of the job
|
-> Maybe UTCTime -- ^ Time of last execution of the job
|
||||||
|
-> NominalDiffTime -- ^ Scheduling precision
|
||||||
-> UTCTime -- ^ Current time, used only for `CronCalendar`
|
-> UTCTime -- ^ Current time, used only for `CronCalendar`
|
||||||
-> Cron
|
-> Cron
|
||||||
-> CronNextMatch UTCTime
|
-> CronNextMatch UTCTime
|
||||||
nextCronMatch tz mPrev now c@Cron{..} = case notAfter of
|
nextCronMatch tz mPrev prec now c@Cron{..} = case notAfter of
|
||||||
MatchAsap -> MatchNone
|
MatchAsap -> MatchNone
|
||||||
MatchAt ts
|
MatchAt ts
|
||||||
| MatchAt ts' <- nextMatch
|
| MatchAt ts' <- nextMatch
|
||||||
@ -183,7 +184,7 @@ nextCronMatch tz mPrev now c@Cron{..} = case notAfter of
|
|||||||
Just prevT
|
Just prevT
|
||||||
-> case cronRepeat of
|
-> case cronRepeat of
|
||||||
CronRepeatOnChange
|
CronRepeatOnChange
|
||||||
| not $ matchesCron tz Nothing prevT c
|
| not $ matchesCron tz Nothing prec prevT c
|
||||||
-> let
|
-> let
|
||||||
cutoffTime = addUTCTime cronRateLimit prevT
|
cutoffTime = addUTCTime cronRateLimit prevT
|
||||||
in case execRef now False cronInitial of
|
in case execRef now False cronInitial of
|
||||||
@ -240,13 +241,14 @@ nextCronMatch tz mPrev now c@Cron{..} = case notAfter of
|
|||||||
|
|
||||||
matchesCron :: TZ -- ^ Timezone of the `Cron`-Entry
|
matchesCron :: TZ -- ^ Timezone of the `Cron`-Entry
|
||||||
-> Maybe UTCTime -- ^ Previous execution of the job
|
-> Maybe UTCTime -- ^ Previous execution of the job
|
||||||
|
-> NominalDiffTime -- ^ Scheduling precision
|
||||||
-> UTCTime -- ^ "Current" time
|
-> UTCTime -- ^ "Current" time
|
||||||
-> Cron
|
-> Cron
|
||||||
-> Bool
|
-> Bool
|
||||||
-- ^ @matchesCron tz prev prec now c@ determines whether the given `Cron`
|
-- ^ @matchesCron tz prev prec now c@ determines whether the given `Cron`
|
||||||
-- specification @c@ should match @now@, under the assumption that the next
|
-- specification @c@ should match @now@, under the assumption that the next
|
||||||
-- check will occur no earlier than @now + prec@.
|
-- check will occur no earlier than @now + prec@.
|
||||||
matchesCron tz mPrev now cron = case nextCronMatch tz mPrev now cron of
|
matchesCron tz mPrev prec now cron = case nextCronMatch tz mPrev prec now cron of
|
||||||
MatchAsap -> True
|
MatchAsap -> True
|
||||||
MatchNone -> False
|
MatchNone -> False
|
||||||
MatchAt ts -> ts <= now
|
MatchAt ts -> ts <= addUTCTime prec now
|
||||||
|
|||||||
@ -104,7 +104,7 @@ data UniWorX = UniWorX
|
|||||||
, appConnPool :: ConnectionPool -- ^ Database connection pool.
|
, appConnPool :: ConnectionPool -- ^ Database connection pool.
|
||||||
, appSmtpPool :: Maybe SMTPPool
|
, appSmtpPool :: Maybe SMTPPool
|
||||||
, appHttpManager :: Manager
|
, appHttpManager :: Manager
|
||||||
, appLogger :: Logger
|
, appLogger :: (ReleaseKey, TVar Logger)
|
||||||
, appLogSettings :: TVar LogSettings
|
, appLogSettings :: TVar LogSettings
|
||||||
, appCryptoIDKey :: CryptoIDKey
|
, appCryptoIDKey :: CryptoIDKey
|
||||||
, appInstanceID :: InstanceId
|
, appInstanceID :: InstanceId
|
||||||
@ -678,10 +678,10 @@ instance Yesod UniWorX where
|
|||||||
encrypted plaintextJson plaintext = do
|
encrypted plaintextJson plaintext = do
|
||||||
canDecrypt <- (== Authorized) <$> evalAccess AdminErrMsgR True
|
canDecrypt <- (== Authorized) <$> evalAccess AdminErrMsgR True
|
||||||
shouldEncrypt <- getsYesod $ appEncryptErrors . appSettings
|
shouldEncrypt <- getsYesod $ appEncryptErrors . appSettings
|
||||||
errKey <- getsYesod appErrorMsgKey
|
|
||||||
if
|
if
|
||||||
| shouldEncrypt
|
| shouldEncrypt
|
||||||
, not canDecrypt -> do
|
, not canDecrypt -> do
|
||||||
|
errKey <- getsYesod appErrorMsgKey
|
||||||
nonce <- liftIO SecretBox.newNonce
|
nonce <- liftIO SecretBox.newNonce
|
||||||
let ciphertext = SecretBox.secretbox errKey nonce . Lazy.ByteString.toStrict $ encode plaintextJson
|
let ciphertext = SecretBox.secretbox errKey nonce . Lazy.ByteString.toStrict $ encode plaintextJson
|
||||||
encoded = decodeUtf8 . Base64.encode $ Saltine.encode nonce <> ciphertext
|
encoded = decodeUtf8 . Base64.encode $ Saltine.encode nonce <> ciphertext
|
||||||
@ -757,7 +757,7 @@ instance Yesod UniWorX where
|
|||||||
LogSettings{..} <- readTVarIO $ appLogSettings app
|
LogSettings{..} <- readTVarIO $ appLogSettings app
|
||||||
return $ logAll || level >= logMinimumLevel
|
return $ logAll || level >= logMinimumLevel
|
||||||
|
|
||||||
makeLogger = return . appLogger
|
makeLogger = readTVarIO . snd . appLogger
|
||||||
|
|
||||||
|
|
||||||
siteLayout :: Maybe Html -- ^ Optionally override `pageHeading`
|
siteLayout :: Maybe Html -- ^ Optionally override `pageHeading`
|
||||||
@ -1694,7 +1694,9 @@ instance HasHttpManager UniWorX where
|
|||||||
getHttpManager = appHttpManager
|
getHttpManager = appHttpManager
|
||||||
|
|
||||||
unsafeHandler :: UniWorX -> Handler a -> IO a
|
unsafeHandler :: UniWorX -> Handler a -> IO a
|
||||||
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
|
unsafeHandler f h = do
|
||||||
|
logger <- makeLogger f
|
||||||
|
Unsafe.fakeHandlerGetLogger (const logger) f h
|
||||||
|
|
||||||
|
|
||||||
instance YesodMail UniWorX where
|
instance YesodMail UniWorX where
|
||||||
|
|||||||
@ -207,7 +207,7 @@ execCrontab = evalStateT go HashMap.empty
|
|||||||
| otherwise
|
| otherwise
|
||||||
= Just (jobCtl, t)
|
= Just (jobCtl, t)
|
||||||
where
|
where
|
||||||
t = nextCronMatch appTZ (getMax <$> HashMap.lookup jobCtl lastTimes) now cron
|
t = nextCronMatch appTZ (getMax <$> HashMap.lookup jobCtl lastTimes) acc now cron
|
||||||
|
|
||||||
waitUntil :: (Eq a, MonadResourceBase m, MonadLogger m) => TMVar a -> a -> UTCTime -> m Bool
|
waitUntil :: (Eq a, MonadResourceBase m, MonadLogger m) => TMVar a -> a -> UTCTime -> m Bool
|
||||||
waitUntil crontabTV crontab nextTime = runResourceT $ do
|
waitUntil crontabTV crontab nextTime = runResourceT $ do
|
||||||
|
|||||||
@ -50,9 +50,9 @@ share [mkPersist sqlSettings, mkMigrate "migrateDBVersioning"]
|
|||||||
deriving Show Eq Ord
|
deriving Show Eq Ord
|
||||||
|]
|
|]
|
||||||
|
|
||||||
migrateAll :: MonadIO m => ReaderT SqlBackend m ()
|
migrateAll :: (MonadLogger m, MonadBaseControl IO m, MonadIO m) => ReaderT SqlBackend m ()
|
||||||
migrateAll = do
|
migrateAll = do
|
||||||
runMigration $ do
|
mapM_ ($logInfoS "Migration") <=< runMigrationSilent $ do
|
||||||
-- Manual migrations to go to InitialVersion below:
|
-- Manual migrations to go to InitialVersion below:
|
||||||
migrateEnableExtension "citext"
|
migrateEnableExtension "citext"
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ migrateAll = do
|
|||||||
-- Map.foldlWithKey traverses migrations in ascending order of AppliedMigrationKey
|
-- Map.foldlWithKey traverses migrations in ascending order of AppliedMigrationKey
|
||||||
Map.foldlWithKey doCustomMigration (return ()) missingMigrations
|
Map.foldlWithKey doCustomMigration (return ()) missingMigrations
|
||||||
|
|
||||||
runMigration migrateAll'
|
mapM_ ($logInfoS "Migration") =<< runMigrationSilent migrateAll'
|
||||||
|
|
||||||
{-
|
{-
|
||||||
Confusion about quotes, from the PostgreSQL Manual:
|
Confusion about quotes, from the PostgreSQL Manual:
|
||||||
|
|||||||
@ -114,11 +114,16 @@ data AppSettings = AppSettings
|
|||||||
data LogSettings = LogSettings
|
data LogSettings = LogSettings
|
||||||
{ logAll, logDetailed :: Bool
|
{ logAll, logDetailed :: Bool
|
||||||
, logMinimumLevel :: LogLevel
|
, logMinimumLevel :: LogLevel
|
||||||
|
, logDestination :: LogDestination
|
||||||
} deriving (Show, Read, Generic, Eq, Ord)
|
} deriving (Show, Read, Generic, Eq, Ord)
|
||||||
|
|
||||||
|
data LogDestination = LogDestStderr | LogDestStdout | LogDestFile { logDestFile :: !FilePath }
|
||||||
|
deriving (Show, Read, Generic, Eq, Ord)
|
||||||
|
|
||||||
deriving instance Generic LogLevel
|
deriving instance Generic LogLevel
|
||||||
instance Hashable LogLevel
|
instance Hashable LogLevel
|
||||||
instance Hashable LogSettings
|
instance Hashable LogSettings
|
||||||
|
instance Hashable LogDestination
|
||||||
|
|
||||||
data UserDefaultConf = UserDefaultConf
|
data UserDefaultConf = UserDefaultConf
|
||||||
{ userDefaultTheme :: Theme
|
{ userDefaultTheme :: Theme
|
||||||
@ -178,12 +183,19 @@ data SmtpAuthConf = SmtpAuthConf
|
|||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ fieldLabelModifier = intercalate "-" . map toLower . splitCamel
|
{ constructorTagModifier = camelToPathPiece' 2
|
||||||
|
, fieldLabelModifier = camelToPathPiece' 2
|
||||||
|
, sumEncoding = UntaggedValue
|
||||||
|
, unwrapUnaryRecords = True
|
||||||
|
} ''LogDestination
|
||||||
|
|
||||||
|
deriveJSON defaultOptions
|
||||||
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
} ''LogSettings
|
} ''LogSettings
|
||||||
|
|
||||||
deriveFromJSON defaultOptions ''Ldap.Scope
|
deriveFromJSON defaultOptions ''Ldap.Scope
|
||||||
deriveFromJSON defaultOptions
|
deriveFromJSON defaultOptions
|
||||||
{ fieldLabelModifier = intercalate "-" . map toLower . drop 2 . splitCamel
|
{ fieldLabelModifier = camelToPathPiece' 2
|
||||||
} ''UserDefaultConf
|
} ''UserDefaultConf
|
||||||
|
|
||||||
instance FromJSON LdapConf where
|
instance FromJSON LdapConf where
|
||||||
|
|||||||
@ -5,8 +5,9 @@
|
|||||||
--color-success: #23d160;
|
--color-success: #23d160;
|
||||||
--color-info: #c4c4c4;
|
--color-info: #c4c4c4;
|
||||||
--color-lightblack: #1A2A36;
|
--color-lightblack: #1A2A36;
|
||||||
--color-lightwhite: #FCFFFA;
|
--color-lightwhite: #fcfffa;
|
||||||
--color-grey: #B1B5C0;
|
--color-grey: #B1B5C0;
|
||||||
|
--color-grey-light: #f4f5f6;
|
||||||
--color-font: #34303a;
|
--color-font: #34303a;
|
||||||
--color-fontsec: #5b5861;
|
--color-fontsec: #5b5861;
|
||||||
|
|
||||||
@ -515,7 +516,7 @@ section {
|
|||||||
padding: 0 0 12px;
|
padding: 0 0 12px;
|
||||||
margin: 0 0 12px;
|
margin: 0 0 12px;
|
||||||
border-bottom: 1px solid #d3d3d3;
|
border-bottom: 1px solid #d3d3d3;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section:last-of-type {
|
section:last-of-type {
|
||||||
|
|||||||
@ -1,17 +1,16 @@
|
|||||||
$newline never
|
$newline never
|
||||||
$if hasPageActions
|
$if hasPageActions
|
||||||
<div .page-nav-prime>
|
<div .page-nav-prime>
|
||||||
<ul .pagenav__list>
|
$forall (MenuItem{menuItemLabel, menuItemType, menuItemModal}, menuIdent, route) <- menuTypes
|
||||||
$forall (MenuItem{menuItemLabel, menuItemType, menuItemModal}, menuIdent, route) <- menuTypes
|
$case menuItemType
|
||||||
$case menuItemType
|
$of PageActionPrime
|
||||||
$of PageActionPrime
|
<div .pagenav__list-item>
|
||||||
<li .pagenav__list-item>
|
$if menuItemModal
|
||||||
$if menuItemModal
|
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic>
|
||||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic>
|
<a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
||||||
<a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
$of PageActionSecondary
|
||||||
$of PageActionSecondary
|
<div .pagenav__list-item>
|
||||||
<li .pagenav__list-item>
|
$if menuItemModal
|
||||||
$if menuItemModal
|
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic>
|
||||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic>
|
<a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
||||||
<a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
$of _
|
||||||
$of _
|
|
||||||
|
|||||||
@ -1,19 +1,10 @@
|
|||||||
.page-nav-prime {
|
.page-nav-prime {
|
||||||
margin: 4px 0 13px;
|
margin: 10px 0 20px;
|
||||||
border-left: 2px solid #c3c3c3;
|
background-color: var(--color-grey-light);
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagenav__list {
|
|
||||||
display: block;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagenav__list-item {
|
.pagenav__list-item {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
padding: 15px;
|
||||||
&:not(:last-child) {
|
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1);
|
||||||
margin-right: 7px;
|
|
||||||
padding-right: 7px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ sampleCron :: Natural -> Cron -> [UTCTime]
|
|||||||
sampleCron n = go n baseTime Nothing
|
sampleCron n = go n baseTime Nothing
|
||||||
where
|
where
|
||||||
go 0 _ _ _ = []
|
go 0 _ _ _ = []
|
||||||
go (pred -> n') t mPrev cron = case nextCronMatch utcTZ mPrev t cron of
|
go (pred -> n') t mPrev cron = case nextCronMatch utcTZ mPrev 0 t cron of
|
||||||
MatchAsap -> t : go n' t (Just t) cron
|
MatchAsap -> t : go n' t (Just t) cron
|
||||||
MatchAt t' -> t' : go n' t' (Just t') cron
|
MatchAt t' -> t' : go n' t' (Just t') cron
|
||||||
MatchNone -> []
|
MatchNone -> []
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import Data.Pool (destroyAllResources)
|
|||||||
|
|
||||||
import Database.Persist.Postgresql
|
import Database.Persist.Postgresql
|
||||||
import Control.Monad.Logger
|
import Control.Monad.Logger
|
||||||
|
import Control.Monad.Trans.Resource
|
||||||
|
|
||||||
import System.Console.GetOpt
|
import System.Console.GetOpt
|
||||||
import System.Exit (exitWith, ExitCode(..))
|
import System.Exit (exitWith, ExitCode(..))
|
||||||
@ -50,6 +51,7 @@ main = do
|
|||||||
DBTruncate -> db $ do
|
DBTruncate -> db $ do
|
||||||
foundation <- getYesod
|
foundation <- getYesod
|
||||||
stopJobCtl foundation
|
stopJobCtl foundation
|
||||||
|
release . fst $ appLogger foundation
|
||||||
liftIO . destroyAllResources $ appConnPool foundation
|
liftIO . destroyAllResources $ appConnPool foundation
|
||||||
truncateDb
|
truncateDb
|
||||||
DBMigrate -> db $ return ()
|
DBMigrate -> db $ return ()
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import TestImport
|
|||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
import Yesod.Core.Handler (toTextUrl)
|
import Yesod.Core.Handler (toTextUrl)
|
||||||
import Yesod.Core.Unsafe (fakeHandlerGetLogger)
|
|
||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = withApp $ do
|
spec = withApp $ do
|
||||||
@ -15,8 +14,7 @@ spec = withApp $ do
|
|||||||
it "asserts no access to my-account for anonymous users" $ do
|
it "asserts no access to my-account for anonymous users" $ do
|
||||||
get ProfileR
|
get ProfileR
|
||||||
|
|
||||||
app <- getTestYesod
|
loginText <- runHandler . toTextUrl $ AuthR LoginR
|
||||||
loginText <- fakeHandlerGetLogger appLogger app (toTextUrl $ AuthR LoginR)
|
|
||||||
|
|
||||||
assertHeader "Location" $ encodeUtf8 loginText
|
assertHeader "Location" $ encodeUtf8 loginText
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,8 @@ runDBWithApp app query = liftIO $ runSqlPersistMPool query (appConnPool app)
|
|||||||
runHandler :: Handler a -> YesodExample UniWorX a
|
runHandler :: Handler a -> YesodExample UniWorX a
|
||||||
runHandler handler = do
|
runHandler handler = do
|
||||||
app <- getTestYesod
|
app <- getTestYesod
|
||||||
fakeHandlerGetLogger appLogger app handler
|
logger <- liftIO . readTVarIO . snd $ appLogger app
|
||||||
|
fakeHandlerGetLogger (const logger) app handler
|
||||||
|
|
||||||
|
|
||||||
withApp :: YSpec UniWorX -> Spec
|
withApp :: YSpec UniWorX -> Spec
|
||||||
|
|||||||
Reference in New Issue
Block a user