diff --git a/.hlint.yaml b/.hlint.yaml new file mode 100644 index 000000000..b9203d95b --- /dev/null +++ b/.hlint.yaml @@ -0,0 +1,14 @@ +# HLint configuration file +# https://github.com/ndmitchell/hlint +########################## + + - ignore: { name: "Parse error" } + - ignore: { name: "Reduce duplication" } + - ignore: { name: "Use ||" } + - ignore: { name: "Use &&" } + - ignore: { name: "Use ++" } + + - arguments: + - -XQuasiQuotes + - -XTemplateHaskell + - -j diff --git a/hlint/Hlint.hs b/hlint/Hlint.hs new file mode 100644 index 000000000..857467823 --- /dev/null +++ b/hlint/Hlint.hs @@ -0,0 +1,4 @@ +{-# OPTIONS_GHC + -F -pgmF hlint-test + -optF src + #-} diff --git a/models b/models index 88e88243f..32dba863f 100644 --- a/models +++ b/models @@ -11,7 +11,7 @@ User json dateFormat DateTimeFormat "default='%d.%m.%Y'" timeFormat DateTimeFormat "default='%R'" downloadFiles Bool default=false - mailLanguages MailLanguages "default='[]'" + mailLanguages MailLanguages default='[]' notificationSettings NotificationSettings UniqueAuthentication ident UniqueEmail email @@ -139,7 +139,7 @@ File title FilePath content ByteString Maybe -- Nothing iff this is a directory modified UTCTime - deriving Show Eq + deriving Show Eq Generic Submission sheet SheetId ratingPoints Points Maybe -- "Just" does not mean done diff --git a/package.yaml b/package.yaml index 246f6bcf3..10ef926b4 100644 --- a/package.yaml +++ b/package.yaml @@ -2,114 +2,111 @@ name: uniworx version: "0.0.0" dependencies: - -# Due to a bug in GHC 8.0.1, we block its usage -# See: https://ghc.haskell.org/trac/ghc/ticket/12130 -- base >=4.8.2.0 && <4.9 || >=4.9.1.0 && <5 - -# version 1.0 had a bug in reexporting Handler, causing trouble -- classy-prelude-yesod >=0.10.2 && <1.0 || >=1.1 - -- foreign-store -- yesod >=1.4.3 && <1.5 -- yesod-core >=1.4.30 && <1.5 -- yesod-auth >=1.4.0 && <1.5 -- yesod-static >=1.4.0.3 && <1.6 -- yesod-form >=1.4.0 && <1.5 -- classy-prelude >=0.10.2 -- classy-prelude-conduit >=0.10.2 -- bytestring >=0.9 && <0.11 -- text >=0.11 && <2.0 -- persistent >=2.7.2 && <2.8 -- persistent-postgresql >=2.1.1 && <2.8 -- persistent-template >=2.0 && <2.8 -- template-haskell -- shakespeare >=2.0 && <2.1 -- hjsmin >=0.1 && <0.3 -- monad-control >=0.3 && <1.1 -- wai-extra >=3.0 && <3.1 -- yaml >=0.8 && <0.9 -- http-conduit >=2.1 && <2.3 -- directory >=1.1 && <1.4 -- warp >=3.0 && <3.3 -- data-default -- aeson >=0.6 && <1.3 -- conduit >=1.0 && <2.0 -- monad-logger >=0.3 && <0.4 -- fast-logger >=2.2 && <2.5 -- wai-logger >=2.2 && <2.4 -- file-embed -- safe -- unordered-containers -- containers -- vector -- time -- case-insensitive -- wai -- cryptonite -- cryptonite-conduit -- saltine -- base64-bytestring -- memory -- http-api-data -- profunctors -- colonnade >=1.1.1 -- yesod-colonnade >=1.1.0 -- blaze-markup -- zip-stream -- filepath -- transformers -- wl-pprint-text -- uuid-types -- path-pieces -- uuid-crypto -- filepath-crypto -- cryptoids-types -- cryptoids -- cryptoids-class -- binary -- cereal -- mtl -- sandi -- esqueleto -- mime-types -- generic-deriving -- blaze-html -- conduit-resumablesink >=0.2 -- parsec -- uuid -- exceptions -- stm -- stm-chans -- stm-conduit -- lens -- MonadRandom -- email-validate -- scientific -- tz -- system-locale -- th-lift-instances -- gitrev -- Glob -- ldap-client -- connection -- universe -- universe-base -- random -- random-shuffle -- th-abstraction -- HaskellNet -- HaskellNet-SSL -- network -- resource-pool -- mime-mail -- hashable -- aeson-pretty -- resourcet -- postgresql-simple -- word24 -- mmorph -- clientsession + # Due to a bug in GHC 8.0.1, we block its usage + # See: https://ghc.haskell.org/trac/ghc/ticket/12130 + - base >=4.8.2.0 && <4.9 || >=4.9.1.0 && <5 + # version 1.0 had a bug in reexporting Handler, causing trouble + - classy-prelude-yesod >=0.10.2 && <1.0 || >=1.1 + - foreign-store + - yesod >=1.4.3 && <1.5 + - yesod-core >=1.4.30 && <1.5 + - yesod-auth >=1.4.0 && <1.5 + - yesod-static >=1.4.0.3 && <1.6 + - yesod-form >=1.4.0 && <1.5 + - classy-prelude >=0.10.2 + - classy-prelude-conduit >=0.10.2 + - bytestring >=0.9 && <0.11 + - text >=0.11 && <2.0 + - persistent >=2.7.2 && <2.8 + - persistent-postgresql >=2.1.1 && <2.8 + - persistent-template >=2.0 && <2.8 + - template-haskell + - shakespeare >=2.0 && <2.1 + - hjsmin >=0.1 && <0.3 + - monad-control >=0.3 && <1.1 + - wai-extra >=3.0 && <3.1 + - yaml >=0.8 && <0.9 + - http-conduit >=2.1 && <2.3 + - directory >=1.1 && <1.4 + - warp >=3.0 && <3.3 + - data-default + - aeson >=0.6 && <1.3 + - conduit >=1.0 && <2.0 + - monad-logger >=0.3 && <0.4 + - fast-logger >=2.2 && <2.5 + - wai-logger >=2.2 && <2.4 + - file-embed + - safe + - unordered-containers + - containers + - vector + - time + - case-insensitive + - wai + - cryptonite + - cryptonite-conduit + - saltine + - base64-bytestring + - memory + - http-api-data + - profunctors + - colonnade >=1.1.1 + - yesod-colonnade >=1.1.0 + - blaze-markup + - zip-stream + - filepath + - transformers + - wl-pprint-text + - uuid-types + - path-pieces + - uuid-crypto + - filepath-crypto + - cryptoids-types + - cryptoids + - cryptoids-class + - binary + - cereal + - mtl + - sandi + - esqueleto + - mime-types + - generic-deriving + - blaze-html + - conduit-resumablesink >=0.2 + - parsec + - uuid + - exceptions + - stm + - stm-chans + - stm-conduit + - lens + - MonadRandom + - email-validate + - scientific + - tz + - system-locale + - th-lift-instances + - gitrev + - Glob + - ldap-client + - connection + - universe + - universe-base + - random + - random-shuffle + - th-abstraction + - HaskellNet + - HaskellNet-SSL + - network + - resource-pool + - mime-mail + - hashable + - aeson-pretty + - resourcet + - postgresql-simple + - word24 + - mmorph + - clientsession other-extensions: - GeneralizedNewtypeDeriving @@ -159,24 +156,31 @@ default-extensions: - BinaryLiterals - PolyKinds +ghc-options: + - -Wall + - -fno-warn-type-defaults + - -fno-warn-partial-type-signatures + +when: + - condition: flag(pedantic) + ghc-options: + - -Werror + - -fwarn-tabs + # The library contains all of our application code. The executable # defined below is just a thin wrapper. library: source-dirs: src when: - - condition: (flag(dev)) || (flag(library-only)) - then: - ghc-options: - - -Wall - - -fwarn-tabs - - -O0 - - -ddump-splices - cpp-options: -DDEVELOPMENT - else: - ghc-options: - - -Wall - - -fwarn-tabs - - -O2 + - condition: (flag(dev)) || (flag(library-only)) + then: + ghc-options: + - -O0 + - -ddump-splices + cpp-options: -DDEVELOPMENT + else: + ghc-options: + - -O2 # Runnable executable for our application executables: @@ -184,28 +188,36 @@ executables: main: main.hs source-dirs: app ghc-options: - - -threaded - - -rtsopts - - -with-rtsopts=-N + - -threaded + - -rtsopts + - -with-rtsopts=-N dependencies: - - uniworx + - uniworx when: - - condition: flag(library-only) - buildable: false + - condition: flag(library-only) + buildable: false # Test suite tests: - test: + yesod: main: Spec.hs source-dirs: test - ghc-options: -Wall dependencies: - - uniworx - - hspec >=2.0.0 - - QuickCheck - - yesod-test - - conduit-extra - - quickcheck-instances + - uniworx + - hspec >=2.0.0 + - QuickCheck + - yesod-test + - conduit-extra + - quickcheck-instances + hlint: + main: Hlint.hs + other-modules: [] + source-dirs: hlint + dependencies: + - hlint-test + when: + - condition: "!flag(pedantic)" + buildable: false # Define flags used by "yesod devel" to make compilation faster flags: @@ -217,3 +229,7 @@ flags: description: Turn on development settings, like auto-reload templates. manual: false default: false + pedantic: + description: Be very pedantic about warnings and errors + manual: true + default: true diff --git a/routes b/routes index 17a653125..5808a7347 100644 --- a/routes +++ b/routes @@ -34,7 +34,7 @@ / HomeR GET !free /users UsersR GET -- no tags, i.e. admins only /admin/test AdminTestR GET POST -/admin/user/#CryptoUUIDUser AdminUserR GET +/admin/user/#CryptoUUIDUser AdminUserR GET !development /admin/user/#CryptoUUIDUser/hijack AdminHijackUserR POST /admin/errMsg AdminErrMsgR GET POST /info VersionR GET !free @@ -50,8 +50,8 @@ !/term/#TermId TermCourseListR GET !free !/term/#TermId/#SchoolId TermSchoolCourseListR GET !free -/school SchoolListR GET -/school/#SchoolId SchoolShowR GET +/school SchoolListR GET !development +/school/#SchoolId SchoolShowR GET !development -- For Pattern Synonyms see Foundation @@ -64,7 +64,7 @@ /edit CEditR GET POST /delete CDeleteR GET POST !lecturerANDempty /users CUsersR GET - /user/#CryptoUUIDUser CUserR GET + /user/#CryptoUUIDUser CUserR GET !development /correctors CHiWisR GET /subs CCorrectionsR GET POST /ex SheetListR GET !registered !materials diff --git a/shell.nix b/shell.nix index d305354a1..931e7ade0 100644 --- a/shell.nix +++ b/shell.nix @@ -22,7 +22,7 @@ let ''; override = oldAttrs: { - nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ postgresql cabal-install ]) ++ (with haskellPackages; [ stack yesod-bin ]); + nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ postgresql ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install ]); shellHook = '' export PROMPT_INFO="${oldAttrs.name}" diff --git a/src/Application.hs b/src/Application.hs index 3757d98f7..e1fbfa575 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -30,9 +30,11 @@ import Network.Wai.Middleware.RequestLogger (Destination (Logger), IPAddrSource (..), OutputFormat (..), destination, mkRequestLogger, outputFormat) -import System.Log.FastLogger (defaultBufSize, newStdoutLoggerSet, +import System.Log.FastLogger (defaultBufSize, newStderrLoggerSet, toLogStr) +import qualified Data.Map.Strict as Map + import Foreign.Store import qualified Data.UUID as UUID @@ -94,22 +96,20 @@ mkYesodDispatch "UniWorX" resourcesUniWorX -- the place to put your migrate statements to have automatic database -- migrations handled by Yesod. makeFoundation :: (MonadResource m, MonadBaseControl IO m) => AppSettings -> m UniWorX -makeFoundation appSettings@(AppSettings{..}) = do +makeFoundation appSettings@AppSettings{..} = do -- Some basic initializations: HTTP connection manager, logger, and static -- subsite. appHttpManager <- newManager appLogger <- liftIO $ do tgetter <- newTimeCache "%Y-%m-%d %T %z" - loggerSet <- newStdoutLoggerSet defaultBufSize + loggerSet <- newStderrLoggerSet defaultBufSize return $ Yesod.Logger loggerSet tgetter appStatic <- liftIO $ bool static staticDevel appMutableStatic appStaticDir appInstanceID <- liftIO $ maybe UUID.nextRandom (either readInstanceIDFile return) appInitialInstanceID - (appJobCtl, recvChans) <- fmap unzip . atomically . replicateM appJobWorkers $ do - chan <- newBroadcastTMChan - recvChan <- dupTMChan chan - return (chan, recvChan) + appJobCtl <- liftIO $ newTVarIO Map.empty + appCronThread <- liftIO newEmptyTMVarIO appLogSettings <- liftIO $ newTVarIO appInitialLogSettings @@ -149,7 +149,7 @@ makeFoundation appSettings@(AppSettings{..}) = do let foundation = mkFoundation sqlPool smtpPool appCryptoIDKey appSessionKey appErrorMsgKey - handleJobs recvChans foundation + handleJobs foundation -- Return the foundation return foundation @@ -208,7 +208,7 @@ createSmtpPool SmtpConf{ smtpPool = ResourcePoolConf{..}, .. } = do applyAuth SmtpAuthConf{..} conn = withLogging $ do $logDebugS "SMTP" "Doing authentication" authSuccess <- liftIO $ SMTP.authenticate smtpAuthType smtpAuthUsername smtpAuthPassword conn - when (not authSuccess) $ do + unless authSuccess $ fail "SMTP authentication failed" return conn liftIO $ createPool (mkConnection >>= maybe return applyAuth smtpAuth) reapConnection poolStripes poolTimeout poolLimit @@ -322,8 +322,7 @@ getApplicationRepl = do return (getPort wsettings, foundation, app1) shutdownApp :: MonadIO m => UniWorX -> m () -shutdownApp UniWorX{..} = do - liftIO . atomically $ mapM_ closeTMChan appJobCtl +shutdownApp = stopJobCtl --------------------------------------------- diff --git a/src/Cron.hs b/src/Cron.hs index cb2d9a338..600eb873c 100644 --- a/src/Cron.hs +++ b/src/Cron.hs @@ -1,5 +1,6 @@ module Cron - ( CronNextMatch(..) + ( evalCronMatch + , CronNextMatch(..) , nextCronMatch , module Cron.Types ) where @@ -18,11 +19,7 @@ import Data.Ratio ((%)) import qualified Data.Set as Set -import Data.List.NonEmpty (NonEmpty(..)) -import qualified Data.List.NonEmpty as NonEmpty - -import Utils.Lens.TH -import Control.Lens +import Utils.Lens hiding (from, to) data CronDate = CronDate @@ -38,7 +35,7 @@ makeLenses_ ''CronDate evalCronMatch :: CronMatch -> Natural -> Bool evalCronMatch CronMatchAny _ = True evalCronMatch CronMatchNone _ = False -evalCronMatch (CronMatchSome set) x = Set.member x $ toNullable set +evalCronMatch (CronMatchSome xs) x = Set.member x $ toNullable xs evalCronMatch (CronMatchStep step) x = (x `mod` step) == 0 evalCronMatch (CronMatchContiguous from to) x = from <= x && x <= to evalCronMatch (CronMatchIntersect a b) x = evalCronMatch a x && evalCronMatch b x @@ -115,7 +112,7 @@ genMatch :: Int -- ^ Period -> [Natural] genMatch p m st CronMatchAny = take p $ map (bool id (succ . (`mod` fromIntegral p)) m) [st..] genMatch _ _ _ CronMatchNone = [] -genMatch p m _ (CronMatchSome set) = take p . map (bool id (succ . (`mod` fromIntegral p)) m) . Set.toAscList $ toNullable set +genMatch p m _ (CronMatchSome xs) = take p . map (bool id (succ . (`mod` fromIntegral p)) m) . Set.toAscList $ toNullable xs genMatch p m st (CronMatchStep step) = do start <- [st..st + step] guard $ (start `mod` step) == 0 @@ -135,9 +132,9 @@ genMatch p m st (CronMatchIntersect aGen bGen) mergeAnd [] _ = [] mergeAnd _ [] = [] mergeAnd (a:as) (b:bs) - | a < b = mergeAnd as (b:bs) - | a == b = a : mergeAnd as bs - | a > b = mergeAnd (a:as) bs + | a < b = mergeAnd as (b:bs) + | a == b = a : mergeAnd as bs + | otherwise = mergeAnd (a:as) bs genMatch p m st (CronMatchUnion CronMatchNone other) = genMatch p m st other genMatch p m st (CronMatchUnion other CronMatchNone) = genMatch p m st other genMatch p m st (CronMatchUnion CronMatchAny _) = genMatch p m st CronMatchAny @@ -147,9 +144,9 @@ genMatch p m st (CronMatchUnion aGen bGen) = merge (genMatch p m st aGen) (genMa merge [] bs = bs merge as [] = as merge (a:as) (b:bs) - | a < b = a : merge as (b:bs) - | a == b = a : merge as bs - | a > b = b : merge (a:as) bs + | a < b = a : merge as (b:bs) + | a == b = a : merge as bs + | otherwise = b : merge (a:as) bs nextCronMatch :: TZ -- ^ Timezone of the `Cron`-Entry -> Maybe UTCTime -- ^ Time of last execution of the job @@ -166,7 +163,6 @@ nextCronMatch tz mPrev now c@Cron{..} = case notAfter of | otherwise -> MatchNone MatchNone -> nextMatch where - nextMatch = nextCronMatch' tz mPrev now c notAfter | Right c' <- cronNotAfter , Just ref <- notAfterRef @@ -178,34 +174,34 @@ nextCronMatch tz mPrev now c@Cron{..} = case notAfter of notAfterRef | Just prevT <- mPrev = Just prevT | otherwise = case execRef' now False cronInitial of + MatchAsap -> error "execRef' should not return MatchAsap" MatchAt t -> Just t MatchNone -> Nothing - - nextCronMatch' tz mPrev now c@Cron{..} - | isNothing mPrev - = execRef now False cronInitial - | Just prevT <- mPrev - = case cronRepeat of - CronRepeatOnChange - | not $ matchesCron tz Nothing prevT c - -> let - cutoffTime = addUTCTime cronRateLimit prevT - in case execRef now False cronInitial of - MatchAsap - | now < cutoffTime -> MatchAt cutoffTime - MatchAt ts - | ts < cutoffTime -> MatchAt cutoffTime - other -> other - CronRepeatScheduled cronNext - -> case cronNext of - CronAsap - | addUTCTime cronRateLimit prevT <= now - -> MatchAsap - | otherwise - -> MatchAt $ addUTCTime cronRateLimit prevT - cronNext - -> execRef (addUTCTime cronRateLimit prevT) True cronNext - _other -> MatchNone + nextMatch = case mPrev of + Nothing + -> execRef now False cronInitial + Just prevT + -> case cronRepeat of + CronRepeatOnChange + | not $ matchesCron tz Nothing prevT c + -> let + cutoffTime = addUTCTime cronRateLimit prevT + in case execRef now False cronInitial of + MatchAsap + | now < cutoffTime -> MatchAt cutoffTime + MatchAt ts + | ts < cutoffTime -> MatchAt cutoffTime + other -> other + CronRepeatScheduled cronNext + -> case cronNext of + CronAsap + | addUTCTime cronRateLimit prevT <= now + -> MatchAsap + | otherwise + -> MatchAt $ addUTCTime cronRateLimit prevT + _other + -> execRef (addUTCTime cronRateLimit prevT) True cronNext + _other -> MatchNone execRef ref wasExecd cronAbsolute = case execRef' ref wasExecd cronAbsolute of MatchAt t @@ -219,19 +215,26 @@ nextCronMatch tz mPrev now c@Cron{..} = case notAfter of | otherwise -> MatchNone CronCalendar{..} -> listToMatch $ do let CronDate{..} = toCronDate $ utcToLocalTimeTZ tz ref - cronYear <- genMatch 400 False cdYear cronYear - cronWeekOfYear <- genMatch 53 True cdWeekOfYear cronWeekOfYear - cronDayOfYear <- genMatch 366 True cdDayOfYear cronDayOfYear - cronMonth <- genMatch 12 True cdMonth cronMonth - cronWeekOfMonth <- genMatch 5 True cdWeekOfMonth cronWeekOfMonth - cronDayOfMonth <- genMatch 31 True cdDayOfMonth cronDayOfMonth - cronDayOfWeek <- genMatch 7 True cdDayOfWeek cronDayOfWeek - cronHour <- genMatch 24 True cdHour cronHour - cronMinute <- genMatch 60 True cdMinute cronMinute - cronSecond <- genMatch 60 True cdSecond cronSecond - guard $ consistentCronDate CronDate{..} - localDay <- maybeToList $ fromGregorianValid (fromIntegral cronYear) (fromIntegral cronMonth) (fromIntegral cronDayOfMonth) - let localTimeOfDay = TimeOfDay (fromIntegral cronHour) (fromIntegral cronMinute) (fromIntegral cronSecond) + + mCronYear <- genMatch 400 False cdYear cronYear + mCronWeekOfYear <- genMatch 53 True cdWeekOfYear cronWeekOfYear + mCronDayOfYear <- genMatch 366 True cdDayOfYear cronDayOfYear + mCronMonth <- genMatch 12 True cdMonth cronMonth + mCronWeekOfMonth <- genMatch 5 True cdWeekOfMonth cronWeekOfMonth + mCronDayOfMonth <- genMatch 31 True cdDayOfMonth cronDayOfMonth + mCronDayOfWeek <- genMatch 7 True cdDayOfWeek cronDayOfWeek + mCronHour <- genMatch 24 True cdHour cronHour + mCronMinute <- genMatch 60 True cdMinute cronMinute + mCronSecond <- genMatch 60 True cdSecond cronSecond + guard $ consistentCronDate CronDate + { cdYear = mCronYear, cdMonth = mCronMonth, cdDayOfMonth = mCronDayOfMonth + , cdHour = mCronHour, cdMinute = mCronMinute, cdSecond = mCronSecond + , cdWeekOfYear = mCronWeekOfYear, cdWeekOfMonth = mCronWeekOfMonth + , cdDayOfYear = mCronDayOfYear, cdDayOfWeek = mCronDayOfWeek + } + + localDay <- maybeToList $ fromGregorianValid (fromIntegral mCronYear) (fromIntegral mCronMonth) (fromIntegral mCronDayOfMonth) + let localTimeOfDay = TimeOfDay (fromIntegral mCronHour) (fromIntegral mCronMinute) (fromIntegral mCronSecond) return $ localTimeToUTCTZ tz LocalTime{..} CronNotScheduled -> MatchNone diff --git a/src/CryptoID.hs b/src/CryptoID.hs index 6d4163982..2331dbfc3 100644 --- a/src/CryptoID.hs +++ b/src/CryptoID.hs @@ -9,7 +9,7 @@ module CryptoID import CryptoID.TH -import ClassyPrelude hiding (fromString) +import ClassyPrelude import Model import qualified Data.CryptoID as E diff --git a/src/Data/Universe/Instances/Reverse/JSON.hs b/src/Data/Universe/Instances/Reverse/JSON.hs index 14c7d04fa..7c8dbb3ed 100644 --- a/src/Data/Universe/Instances/Reverse/JSON.hs +++ b/src/Data/Universe/Instances/Reverse/JSON.hs @@ -24,4 +24,4 @@ instance (Eq a, Hashable a, Finite a, FromJSON b, FromJSONKey a) => FromJSON (a vMap <- parseJSON val :: Parser (HashMap a b) unless (HashSet.fromMap (HashMap.map (const ()) vMap) == HashSet.fromList universeF) $ fail "Not all required keys found" - return $ (vMap !) + return (vMap !) diff --git a/src/Foundation.hs b/src/Foundation.hs index dc350557d..435c8a838 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1,4 +1,5 @@ {-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} -- MonadCrypto module Foundation where @@ -10,20 +11,18 @@ import Text.Jasmine (minifym) import qualified Web.ClientSession as ClientSession import Yesod.Auth.Message -import Yesod.Auth.Dummy import Auth.LDAP import Auth.PWHash import Auth.Dummy import Jobs.Types -import qualified Network.Wai as W (requestMethod, pathInfo) +import qualified Network.Wai as W (pathInfo) import Yesod.Default.Util (addStaticContentExternal) import Yesod.Core.Types (Logger) import qualified Yesod.Core.Unsafe as Unsafe import Data.CaseInsensitive (CI) import qualified Data.CaseInsensitive as CI -import qualified Data.Text.Encoding as TE import qualified Data.CryptoID as E @@ -40,12 +39,10 @@ import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Data.List (foldr1) -import qualified Data.List as List import Data.Set (Set) import qualified Data.Set as Set import Data.Map (Map, (!?)) import qualified Data.Map as Map -import Data.List (findIndex) import Data.Monoid (Any(..)) @@ -61,22 +58,14 @@ import Control.Monad.Trans.Maybe (MaybeT(..)) import Control.Monad.Trans.Reader (runReader, mapReaderT) import Control.Monad.Trans.Writer (WriterT(..)) import Control.Monad.Writer.Class (MonadWriter(..)) -import Control.Monad.Catch (handleAll) import qualified Control.Monad.Catch as C -import System.FilePath - -import Handler.Utils.Templates import Handler.Utils.StudyFeatures import Control.Lens -import Utils import Utils.Form -import Utils.Lens import Utils.SystemMessage import Data.Aeson hiding (Error, Success) -import Data.Aeson.TH -import qualified Data.Yaml as Yaml import Text.Shakespeare.Text (st) @@ -118,7 +107,8 @@ data UniWorX = UniWorX , appLogSettings :: TVar LogSettings , appCryptoIDKey :: CryptoIDKey , appInstanceID :: InstanceId - , appJobCtl :: [TMChan JobCtl] + , appJobCtl :: TVar (Map ThreadId (TMChan JobCtl)) + , appCronThread :: TMVar (ReleaseKey, ThreadId) , appErrorMsgKey :: SecretBox.Key , appSessionKey :: ClientSession.Key } @@ -146,9 +136,11 @@ type MsgRenderer = MsgRendererS UniWorX -- see Utils type MailM a = MailT (HandlerT UniWorX IO) a -- Pattern Synonyms for convenience +pattern CSheetR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> SheetR -> Route UniWorX pattern CSheetR tid ssh csh shn ptn = CourseR tid ssh csh (SheetR shn ptn) +pattern CSubmissionR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> CryptoFileNameSubmission -> SubmissionR -> Route UniWorX pattern CSubmissionR tid ssh csh shn cid ptn = CSheetR tid ssh csh shn (SubmissionR cid ptn) @@ -211,9 +203,10 @@ instance RenderMessage UniWorX Load where newtype MsgLanguage = MsgLanguage Lang deriving (Eq, Ord, Show, Read) instance RenderMessage UniWorX MsgLanguage where - renderMessage foundation ls (MsgLanguage lang) - | lang == "de-DE" = mr MsgGermanGermany - | "de" `isPrefixOf` lang = mr MsgGerman + renderMessage foundation ls (MsgLanguage lang@(Text.splitOn "-" -> lang')) + | ["de", "DE"] <- lang' = mr MsgGermanGermany + | ("de" : _) <- lang' = mr MsgGerman + | otherwise = lang where mr = renderMessage foundation ls @@ -230,7 +223,7 @@ embedRenderMessage ''UniWorX ''SheetType ("SheetType" <>) newtype SheetTypeComplete = SheetTypeComplete SheetType instance RenderMessage UniWorX (SheetTypeComplete) where - renderMessage foundation ls (SheetTypeComplete st) = case st of + renderMessage foundation ls (SheetTypeComplete sheetType) = case sheetType of NotGraded -> mr NotGraded other -> mr (grading other) <> ", " <> mr other where @@ -288,8 +281,8 @@ orAR _ _ AuthenticationRequired = AuthenticationRequired orAR mr (Unauthorized x) (Unauthorized y) = Unauthorized . render mr $ MsgUnauthorizedOr x y -- and andAR mr (Unauthorized x) (Unauthorized y) = Unauthorized . render mr $ MsgUnauthorizedAnd x y -andAR _ reason@(Unauthorized x) _ = reason -andAR _ _ reason@(Unauthorized x) = reason +andAR _ reason@(Unauthorized _) _ = reason +andAR _ _ reason@(Unauthorized _) = reason andAR _ Authorized other = other andAR _ AuthenticationRequired _ = AuthenticationRequired @@ -346,6 +339,14 @@ knownTags = Map.fromList -- should not throw exceptions, i.e. no getBy404 or req allow <- appAllowDeprecated . appSettings <$> getYesod return $ bool (Unauthorized "Deprecated Route") Authorized allow ) + ,("development", APHandler $ \r _ -> do + $logWarnS "AccessControl" ("route in development: " <> tshow r) +#ifdef DEVELOPMENT + return Authorized +#else + return $ Unauthorized "Route under development" +#endif + ) ,("lecturer", APDB $ \route _ -> case route of CourseR tid ssh csh _ -> exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ lift maybeAuthId @@ -414,7 +415,7 @@ knownTags = Map.fromList -- should not throw exceptions, i.e. no getBy404 or req return Authorized CourseR tid ssh csh CRegisterR -> maybeT (unauthorizedI MsgUnauthorizedCourseTime) $ do - Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh + Entity _ Course{courseRegisterFrom, courseRegisterTo} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh cTime <- (NTop . Just) <$> liftIO getCurrentTime guard $ NTop courseRegisterFrom <= cTime && NTop courseRegisterTo >= cTime @@ -422,7 +423,7 @@ knownTags = Map.fromList -- should not throw exceptions, i.e. no getBy404 or req MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do smId <- decrypt cID - SystemMessage{..} <- MaybeT $ get smId + SystemMessage{systemMessageFrom, systemMessageTo} <- MaybeT $ get smId cTime <- (NTop . Just) <$> liftIO getCurrentTime guard $ NTop systemMessageFrom <= cTime && NTop systemMessageTo >= cTime @@ -625,14 +626,14 @@ instance Yesod UniWorX where errPage = case err of NotFound -> [whamlet|

_{MsgErrorResponseNotFound}|] - InternalError err -> encrypted err [whamlet|

#{err}|] + InternalError err' -> encrypted err' [whamlet|

#{err'}|] InvalidArgs errs -> [whamlet|