Merge remote-tracking branch 'origin/master' into 126-ubungsbetrieb-statistik-seiten-pro-kurs
This commit is contained in:
commit
59714bd3c7
@ -72,6 +72,8 @@ CourseSecretTip: Anmeldung zum Kurs erfordert Eingabe des Passworts, sofern gese
|
|||||||
CourseRegisterFromTip: Ohne Datum ist keine Anmeldung möglich
|
CourseRegisterFromTip: Ohne Datum ist keine Anmeldung möglich
|
||||||
CourseRegisterToTip: Anmeldung darf auch ohne Begrenzung möglich sein
|
CourseRegisterToTip: Anmeldung darf auch ohne Begrenzung möglich sein
|
||||||
CourseDeregisterUntilTip: Abmeldung darf auch ohne Begrenzung möglich sein
|
CourseDeregisterUntilTip: Abmeldung darf auch ohne Begrenzung möglich sein
|
||||||
|
CourseFilterSearch: Volltext-Suche
|
||||||
|
CourseFilterRegistered: Registriert
|
||||||
|
|
||||||
NoSuchTerm tid@TermId: Semester #{display tid} gibt es nicht.
|
NoSuchTerm tid@TermId: Semester #{display tid} gibt es nicht.
|
||||||
NoSuchSchool ssh@SchoolId: Institut #{display ssh} gibt es nicht.
|
NoSuchSchool ssh@SchoolId: Institut #{display ssh} gibt es nicht.
|
||||||
@ -171,7 +173,7 @@ MaterialFree: Kursmaterialien ohne Anmeldung zugänglich
|
|||||||
UnauthorizedWrite: Sie haben hierfür keine Schreibberechtigung
|
UnauthorizedWrite: Sie haben hierfür keine Schreibberechtigung
|
||||||
UnauthorizedSystemMessageTime: Diese Systemnachricht ist noch nicht oder nicht mehr einsehbar.
|
UnauthorizedSystemMessageTime: Diese Systemnachricht ist noch nicht oder nicht mehr einsehbar.
|
||||||
UnauthorizedSystemMessageAuth: Diese Systemnachricht ist nur für angemeldete Benutzer einsehbar.
|
UnauthorizedSystemMessageAuth: Diese Systemnachricht ist nur für angemeldete Benutzer einsehbar.
|
||||||
UnsupportedAuthPredicate tag@String shownRoute@String: "#{tag}" wurde auf eine Route angewandt, die dies nicht unterstützt: #{shownRoute}
|
UnsupportedAuthPredicate authTagT@Text shownRoute@String: "#{authTagT}" wurde auf eine Route angewandt, die dies nicht unterstützt: #{shownRoute}
|
||||||
UnauthorizedDisabledTag authTag@AuthTag: Authorisierungsprädikat "#{toPathPiece authTag}" ist für Ihre Sitzung nicht aktiv
|
UnauthorizedDisabledTag authTag@AuthTag: Authorisierungsprädikat "#{toPathPiece authTag}" ist für Ihre Sitzung nicht aktiv
|
||||||
UnknownAuthPredicate tag@String: Authorisierungsprädikat "#{tag}" ist dem System nicht bekannt
|
UnknownAuthPredicate tag@String: Authorisierungsprädikat "#{tag}" ist dem System nicht bekannt
|
||||||
|
|
||||||
@ -233,6 +235,7 @@ CorrUpload: Korrekturen hochladen
|
|||||||
CorrSetCorrector: Korrektor zuweisen
|
CorrSetCorrector: Korrektor zuweisen
|
||||||
CorrAutoSetCorrector: Korrekturen verteilen
|
CorrAutoSetCorrector: Korrekturen verteilen
|
||||||
NatField xyz@Text: #{xyz} muss eine natürliche Zahl sein!
|
NatField xyz@Text: #{xyz} muss eine natürliche Zahl sein!
|
||||||
|
JSONFieldDecodeFailure aesonFailure@String: Konnte JSON nicht parsen: #{aesonFailure}
|
||||||
|
|
||||||
SubmissionsAlreadyAssigned num@Int64: #{display num} Abgaben waren bereits einem Korrektor zugeteilt und wurden nicht verändert:
|
SubmissionsAlreadyAssigned num@Int64: #{display num} Abgaben waren bereits einem Korrektor zugeteilt und wurden nicht verändert:
|
||||||
SubmissionsAssignUnauthorized num@Int64: #{display num} Abgaben können momentan nicht einem Korrektor zugeteilt werden (z.B. weil die Abgabe noch offen ist):
|
SubmissionsAssignUnauthorized num@Int64: #{display num} Abgaben können momentan nicht einem Korrektor zugeteilt werden (z.B. weil die Abgabe noch offen ist):
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
module Data.CaseInsensitive.Instances
|
module Data.CaseInsensitive.Instances
|
||||||
@ -20,6 +21,8 @@ import Language.Haskell.TH.Syntax (Lift(..))
|
|||||||
|
|
||||||
import Data.Aeson (ToJSON(..), FromJSON(..), ToJSONKey(..), FromJSONKey(..), ToJSONKeyFunction(..))
|
import Data.Aeson (ToJSON(..), FromJSON(..), ToJSONKey(..), FromJSONKey(..), ToJSONKeyFunction(..))
|
||||||
|
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
|
||||||
instance PersistField (CI Text) where
|
instance PersistField (CI Text) where
|
||||||
toPersistValue ciText = PersistDbSpecific . Text.encodeUtf8 $ CI.original ciText
|
toPersistValue ciText = PersistDbSpecific . Text.encodeUtf8 $ CI.original ciText
|
||||||
@ -37,6 +40,8 @@ instance PersistFieldSql (CI Text) where
|
|||||||
instance PersistFieldSql (CI String) where
|
instance PersistFieldSql (CI String) where
|
||||||
sqlType _ = SqlOther "citext"
|
sqlType _ = SqlOther "citext"
|
||||||
|
|
||||||
|
instance (E.SqlString a, PersistField (CI a)) => E.SqlString (CI a)
|
||||||
|
|
||||||
instance ToJSON a => ToJSON (CI a) where
|
instance ToJSON a => ToJSON (CI a) where
|
||||||
toJSON = toJSON . CI.original
|
toJSON = toJSON . CI.original
|
||||||
|
|
||||||
|
|||||||
19
src/Data/Monoid/Instances.hs
Normal file
19
src/Data/Monoid/Instances.hs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
module Data.Monoid.Instances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Data.Monoid
|
||||||
|
|
||||||
|
type instance Element (Dual a) = a
|
||||||
|
instance MonoPointed (Dual a)
|
||||||
|
type instance Element (Sum a) = a
|
||||||
|
instance MonoPointed (Sum a)
|
||||||
|
type instance Element (Product a) = a
|
||||||
|
instance MonoPointed (Product a)
|
||||||
|
type instance Element (First a) = a
|
||||||
|
instance MonoPointed (First a)
|
||||||
|
type instance Element (Last a) = a
|
||||||
|
instance MonoPointed (Last a)
|
||||||
@ -454,7 +454,7 @@ tagAccessPredicate AuthTime = APDB $ \route _ -> case route of
|
|||||||
&& NTop systemMessageTo >= cTime
|
&& NTop systemMessageTo >= cTime
|
||||||
return Authorized
|
return Authorized
|
||||||
|
|
||||||
r -> $unsupportedAuthPredicate "time" r
|
r -> $unsupportedAuthPredicate AuthTime r
|
||||||
tagAccessPredicate AuthRegistered = APDB $ \route _ -> case route of
|
tagAccessPredicate AuthRegistered = APDB $ \route _ -> case route of
|
||||||
CourseR tid ssh csh _ -> exceptT return return $ do
|
CourseR tid ssh csh _ -> exceptT return return $ do
|
||||||
authId <- maybeExceptT AuthenticationRequired $ lift maybeAuthId
|
authId <- maybeExceptT AuthenticationRequired $ lift maybeAuthId
|
||||||
@ -467,14 +467,14 @@ tagAccessPredicate AuthRegistered = APDB $ \route _ -> case route of
|
|||||||
return (E.countRows :: E.SqlExpr (E.Value Int64))
|
return (E.countRows :: E.SqlExpr (E.Value Int64))
|
||||||
guardMExceptT (c > 0) (unauthorizedI MsgUnauthorizedParticipant)
|
guardMExceptT (c > 0) (unauthorizedI MsgUnauthorizedParticipant)
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate "registered" r
|
r -> $unsupportedAuthPredicate AuthRegistered r
|
||||||
tagAccessPredicate AuthCapacity = APDB $ \route _ -> case route of
|
tagAccessPredicate AuthCapacity = APDB $ \route _ -> case route of
|
||||||
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNoCapacity) $ do
|
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNoCapacity) $ do
|
||||||
Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
||||||
registered <- lift $ fromIntegral <$> count [ CourseParticipantCourse ==. cid ]
|
registered <- lift $ fromIntegral <$> count [ CourseParticipantCourse ==. cid ]
|
||||||
guard $ NTop courseCapacity > NTop (Just registered)
|
guard $ NTop courseCapacity > NTop (Just registered)
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate "capacity" r
|
r -> $unsupportedAuthPredicate AuthCapacity r
|
||||||
tagAccessPredicate AuthEmpty = APDB $ \route _ -> case route of
|
tagAccessPredicate AuthEmpty = APDB $ \route _ -> case route of
|
||||||
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNotEmpty) $ do
|
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNotEmpty) $ do
|
||||||
-- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
-- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
||||||
@ -482,41 +482,41 @@ tagAccessPredicate AuthEmpty = APDB $ \route _ -> case route of
|
|||||||
registered <- lift $ fromIntegral <$> count [ CourseParticipantCourse ==. cid ]
|
registered <- lift $ fromIntegral <$> count [ CourseParticipantCourse ==. cid ]
|
||||||
guard $ registered <= 0
|
guard $ registered <= 0
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate "empty" r
|
r -> $unsupportedAuthPredicate AuthEmpty r
|
||||||
tagAccessPredicate AuthMaterials = APDB $ \route _ -> case route of
|
tagAccessPredicate AuthMaterials = APDB $ \route _ -> case route of
|
||||||
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnfreeMaterials) $ do
|
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnfreeMaterials) $ do
|
||||||
Entity _ Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
Entity _ Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
||||||
guard courseMaterialFree
|
guard courseMaterialFree
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate "materials" r
|
r -> $unsupportedAuthPredicate AuthMaterials r
|
||||||
tagAccessPredicate AuthOwner = APDB $ \route _ -> case route of
|
tagAccessPredicate AuthOwner = APDB $ \route _ -> case route of
|
||||||
CSubmissionR _ _ _ _ cID _ -> exceptT return return $ do
|
CSubmissionR _ _ _ _ cID _ -> exceptT return return $ do
|
||||||
sid <- catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedSubmissionOwner) (const True :: CryptoIDError -> Bool) $ decrypt cID
|
sid <- catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedSubmissionOwner) (const True :: CryptoIDError -> Bool) $ decrypt cID
|
||||||
authId <- maybeExceptT AuthenticationRequired $ lift maybeAuthId
|
authId <- maybeExceptT AuthenticationRequired $ lift maybeAuthId
|
||||||
void . maybeMExceptT (unauthorizedI MsgUnauthorizedSubmissionOwner) . getBy $ UniqueSubmissionUser authId sid
|
void . maybeMExceptT (unauthorizedI MsgUnauthorizedSubmissionOwner) . getBy $ UniqueSubmissionUser authId sid
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate "owner" r
|
r -> $unsupportedAuthPredicate AuthOwner r
|
||||||
tagAccessPredicate AuthRated = APDB $ \route _ -> case route of
|
tagAccessPredicate AuthRated = APDB $ \route _ -> case route of
|
||||||
CSubmissionR _ _ _ _ cID _ -> maybeT (unauthorizedI MsgUnauthorizedSubmissionRated) $ do
|
CSubmissionR _ _ _ _ cID _ -> maybeT (unauthorizedI MsgUnauthorizedSubmissionRated) $ do
|
||||||
sid <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
|
sid <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
|
||||||
sub <- MaybeT $ get sid
|
sub <- MaybeT $ get sid
|
||||||
guard $ submissionRatingDone sub
|
guard $ submissionRatingDone sub
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate "rated" r
|
r -> $unsupportedAuthPredicate AuthRated r
|
||||||
tagAccessPredicate AuthUserSubmissions = APDB $ \route _ -> case route of
|
tagAccessPredicate AuthUserSubmissions = APDB $ \route _ -> case route of
|
||||||
CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedUserSubmission) $ do
|
CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedUserSubmission) $ do
|
||||||
Entity cid _ <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
Entity cid _ <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
||||||
Entity _ Sheet{sheetSubmissionMode} <- MaybeT . getBy $ CourseSheet cid shn
|
Entity _ Sheet{sheetSubmissionMode} <- MaybeT . getBy $ CourseSheet cid shn
|
||||||
guard $ sheetSubmissionMode == UserSubmissions
|
guard $ sheetSubmissionMode == UserSubmissions
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate "user-submissions" r
|
r -> $unsupportedAuthPredicate AuthUserSubmissions r
|
||||||
tagAccessPredicate AuthCorrectorSubmissions = APDB $ \route _ -> case route of
|
tagAccessPredicate AuthCorrectorSubmissions = APDB $ \route _ -> case route of
|
||||||
CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedCorrectorSubmission) $ do
|
CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedCorrectorSubmission) $ do
|
||||||
Entity cid _ <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
Entity cid _ <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
|
||||||
Entity _ Sheet{sheetSubmissionMode} <- MaybeT . getBy $ CourseSheet cid shn
|
Entity _ Sheet{sheetSubmissionMode} <- MaybeT . getBy $ CourseSheet cid shn
|
||||||
guard $ sheetSubmissionMode == CorrectorSubmissions
|
guard $ sheetSubmissionMode == CorrectorSubmissions
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate "corrector-submissions" r
|
r -> $unsupportedAuthPredicate AuthCorrectorSubmissions r
|
||||||
tagAccessPredicate AuthAuthentication = APDB $ \route _ -> case route of
|
tagAccessPredicate AuthAuthentication = APDB $ \route _ -> case route of
|
||||||
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageAuth) $ do
|
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageAuth) $ do
|
||||||
smId <- decrypt cID
|
smId <- decrypt cID
|
||||||
@ -524,7 +524,7 @@ tagAccessPredicate AuthAuthentication = APDB $ \route _ -> case route of
|
|||||||
isAuthenticated <- isJust <$> liftHandlerT maybeAuthId
|
isAuthenticated <- isJust <$> liftHandlerT maybeAuthId
|
||||||
guard $ not systemMessageAuthenticatedOnly || isAuthenticated
|
guard $ not systemMessageAuthenticatedOnly || isAuthenticated
|
||||||
return Authorized
|
return Authorized
|
||||||
r -> $unsupportedAuthPredicate "authentication" r
|
r -> $unsupportedAuthPredicate AuthAuthentication r
|
||||||
tagAccessPredicate AuthRead = APHandler . const $ bool (return Authorized) (unauthorizedI MsgUnauthorizedWrite)
|
tagAccessPredicate AuthRead = APHandler . const $ bool (return Authorized) (unauthorizedI MsgUnauthorizedWrite)
|
||||||
tagAccessPredicate AuthWrite = APHandler . const $ bool (unauthorizedI MsgUnauthorized) (return Authorized)
|
tagAccessPredicate AuthWrite = APHandler . const $ bool (unauthorizedI MsgUnauthorized) (return Authorized)
|
||||||
|
|
||||||
@ -721,11 +721,12 @@ instance Yesod UniWorX where
|
|||||||
UniWorX{appWidgetMemcached, appSettings} <- getYesod
|
UniWorX{appWidgetMemcached, appSettings} <- getYesod
|
||||||
for ((,) <$> appWidgetMemcached <*> appWidgetMemcachedConf appSettings) $ \(mConn, WidgetMemcachedConf{ widgetMemcachedConnectInfo = _, .. }) -> do
|
for ((,) <$> appWidgetMemcached <*> appWidgetMemcachedConf appSettings) $ \(mConn, WidgetMemcachedConf{ widgetMemcachedConnectInfo = _, .. }) -> do
|
||||||
let expiry = (maybe 0 ceiling widgetMemcachedExpiry)
|
let expiry = (maybe 0 ceiling widgetMemcachedExpiry)
|
||||||
touch = liftIO $ Memcached.touch expiry fileName mConn
|
touch = liftIO $ Memcached.touch expiry (encodeUtf8 $ pack fileName) mConn
|
||||||
add = liftIO $ Memcached.add zeroBits expiry fileName content mConn
|
add = liftIO $ Memcached.add zeroBits expiry (encodeUtf8 $ pack fileName) content mConn
|
||||||
|
absoluteLink = unpack widgetMemcachedBaseUrl </> fileName
|
||||||
C.catchIf Memcached.isKeyNotFound touch $ \_ ->
|
C.catchIf Memcached.isKeyNotFound touch $ \_ ->
|
||||||
C.handleIf Memcached.isKeyExists (\_ -> return ()) add
|
C.handleIf Memcached.isKeyExists (\_ -> return ()) add
|
||||||
return . Left $ widgetMemcachedBaseUrl <> "/" <> decodeUtf8 fileName
|
return . Left $ pack absoluteLink
|
||||||
where
|
where
|
||||||
-- Generate a unique filename based on the content itself, this is used
|
-- Generate a unique filename based on the content itself, this is used
|
||||||
-- for deduplication so a collision resistant hash function is required
|
-- for deduplication so a collision resistant hash function is required
|
||||||
@ -734,7 +735,9 @@ instance Yesod UniWorX where
|
|||||||
--
|
--
|
||||||
-- Length of hash is 144 bits instead of MD5's 128, so as to avoid
|
-- Length of hash is 144 bits instead of MD5's 128, so as to avoid
|
||||||
-- padding after base64-conversion
|
-- padding after base64-conversion
|
||||||
fileName = (<> ("." <> encodeUtf8 ext))
|
fileName = (<.> unpack ext)
|
||||||
|
. unpack
|
||||||
|
. decodeUtf8
|
||||||
. Base64.encode
|
. Base64.encode
|
||||||
. (convert :: Digest (SHAKE256 144) -> ByteString)
|
. (convert :: Digest (SHAKE256 144) -> ByteString)
|
||||||
. runIdentity
|
. runIdentity
|
||||||
|
|||||||
@ -14,12 +14,13 @@ import Utils.Lens
|
|||||||
|
|
||||||
import Data.Set (Set)
|
import Data.Set (Set)
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
import Data.Map (Map)
|
import Data.Map (Map, (!))
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
|
|
||||||
import Data.Semigroup (Sum(..))
|
import Data.Semigroup (Sum(..))
|
||||||
|
import Data.Monoid (All(..))
|
||||||
|
|
||||||
-- import Data.Time
|
-- import Data.Time
|
||||||
-- import qualified Data.Text as T
|
-- import qualified Data.Text as T
|
||||||
@ -46,7 +47,8 @@ import Database.Persist.Sql (updateWhereCount)
|
|||||||
|
|
||||||
import Data.List (genericLength)
|
import Data.List (genericLength)
|
||||||
|
|
||||||
import Control.Monad.Trans.Writer (WriterT(..), runWriter)
|
import Control.Monad.Trans.Writer (WriterT(..), runWriter, execWriterT)
|
||||||
|
import Control.Monad.Trans.Reader (mapReaderT)
|
||||||
|
|
||||||
import Control.Monad.Trans.RWS (RWST)
|
import Control.Monad.Trans.RWS (RWST)
|
||||||
|
|
||||||
@ -275,6 +277,7 @@ makeCorrectionsTable whereClause dbtColonnade psValidator dbtProj' = do
|
|||||||
E.||. (if Nothing `Set.member` emails then E.isNothing (corrector E.?. UserEmail) else E.val False)
|
E.||. (if Nothing `Set.member` emails then E.isNothing (corrector E.?. UserEmail) else E.val False)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
, dbtFilterUI = mempty
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
, dbtIdent = "corrections" :: Text
|
, dbtIdent = "corrections" :: Text
|
||||||
}
|
}
|
||||||
@ -668,81 +671,96 @@ postCorrectionsCreateR = do
|
|||||||
FormMissing -> return ()
|
FormMissing -> return ()
|
||||||
FormFailure errs -> forM_ errs $ addMessage Error . toHtml
|
FormFailure errs -> forM_ errs $ addMessage Error . toHtml
|
||||||
FormSuccess (sid, (pss, invalids)) -> do
|
FormSuccess (sid, (pss, invalids)) -> do
|
||||||
forM_ (Map.toList invalids) $ \((oPseudonyms, iPseudonym), alts) -> $(addMessageFile Warning "templates/messages/ignoredInvalidPseudonym.hamlet")
|
allDone <- fmap getAll . execWriterT $ do
|
||||||
|
forM_ (Map.toList invalids) $ \((oPseudonyms, iPseudonym), alts) -> $(addMessageFile Error "templates/messages/ignoredInvalidPseudonym.hamlet")
|
||||||
|
tell . All $ null invalids
|
||||||
|
|
||||||
runDB $ do
|
WriterT . runDB . mapReaderT runWriterT $ do
|
||||||
Sheet{..} <- get404 sid
|
Sheet{..} <- get404 sid
|
||||||
(sps, unknown) <- fmap partitionEithers' . forM pss . mapM $ \p -> maybe (Left p) Right <$> getBy (UniqueSheetPseudonym sid p)
|
(sps, unknown) <- fmap partitionEithers' . forM pss . mapM $ \p -> maybe (Left p) Right <$> getBy (UniqueSheetPseudonym sid p)
|
||||||
forM_ unknown $ addMessageI Error . MsgUnknownPseudonym . review _PseudonymText
|
forM_ unknown $ addMessageI Error . MsgUnknownPseudonym . review _PseudonymText
|
||||||
now <- liftIO getCurrentTime
|
tell . All $ null unknown
|
||||||
let
|
now <- liftIO getCurrentTime
|
||||||
sps' :: [[SheetPseudonym]]
|
let
|
||||||
duplicate :: Set Pseudonym
|
sps' :: [[SheetPseudonym]]
|
||||||
( sps'
|
duplicate :: Set Pseudonym
|
||||||
, Map.keysSet . Map.filter (\(getSum -> n) -> n > 1) -> duplicate
|
( sps'
|
||||||
) = flip runState Map.empty . forM sps . flip (foldrM :: (Entity SheetPseudonym -> [SheetPseudonym] -> State (Map Pseudonym (Sum Integer)) [SheetPseudonym]) -> [SheetPseudonym] -> [Entity SheetPseudonym] -> State (Map Pseudonym (Sum Integer)) [SheetPseudonym]) [] $ \(Entity _ p@SheetPseudonym{sheetPseudonymPseudonym}) ps -> do
|
, Map.keysSet . Map.filter (\(getSum -> n) -> n > 1) -> duplicate
|
||||||
known <- State.gets $ Map.member sheetPseudonymPseudonym
|
) = flip runState Map.empty . forM sps . flip (foldrM :: (Entity SheetPseudonym -> [SheetPseudonym] -> State (Map Pseudonym (Sum Integer)) [SheetPseudonym]) -> [SheetPseudonym] -> [Entity SheetPseudonym] -> State (Map Pseudonym (Sum Integer)) [SheetPseudonym]) [] $ \(Entity _ p@SheetPseudonym{sheetPseudonymPseudonym}) ps -> do
|
||||||
State.modify $ Map.insertWith (<>) sheetPseudonymPseudonym (Sum 1)
|
known <- State.gets $ Map.member sheetPseudonymPseudonym
|
||||||
return $ bool (p :) id known ps
|
State.modify $ Map.insertWith (<>) sheetPseudonymPseudonym (Sum 1)
|
||||||
submissionPrototype = Submission
|
return $ bool (p :) id known ps
|
||||||
{ submissionSheet = sid
|
submissionPrototype = Submission
|
||||||
, submissionRatingPoints = Nothing
|
{ submissionSheet = sid
|
||||||
, submissionRatingComment = Nothing
|
, submissionRatingPoints = Nothing
|
||||||
, submissionRatingBy = Just uid
|
, submissionRatingComment = Nothing
|
||||||
, submissionRatingAssigned = Just now
|
, submissionRatingBy = Just uid
|
||||||
, submissionRatingTime = Nothing
|
, submissionRatingAssigned = Just now
|
||||||
}
|
, submissionRatingTime = Nothing
|
||||||
unless (null duplicate)
|
|
||||||
$(addMessageFile Warning "templates/messages/submissionCreateDuplicates.hamlet")
|
|
||||||
existingSubUsers <- E.select . E.from $ \(submissionUser `E.InnerJoin` submission) -> do
|
|
||||||
E.on $ submission E.^. SubmissionId E.==. submissionUser E.^. SubmissionUserSubmission
|
|
||||||
E.where_ $ submissionUser E.^. SubmissionUserUser `E.in_` E.valList (sheetPseudonymUser <$> concat sps')
|
|
||||||
E.&&. submission E.^. SubmissionSheet E.==. E.val sid
|
|
||||||
return submissionUser
|
|
||||||
unless (null existingSubUsers) $ do
|
|
||||||
(Map.toList -> subs) <- foldrM (\(Entity _ SubmissionUser{..}) mp -> Map.insertWith (<>) <$> (encrypt submissionUserSubmission :: DB CryptoFileNameSubmission) <*> pure (Set.fromList . map sheetPseudonymPseudonym . filter (\SheetPseudonym{..} -> sheetPseudonymUser == submissionUserUser) $ concat sps') <*> pure mp) Map.empty existingSubUsers
|
|
||||||
$(addMessageFile Warning "templates/messages/submissionCreateExisting.hamlet")
|
|
||||||
let sps'' = filter (not . null) $ filter (\spGroup -> not . flip any spGroup $ \SheetPseudonym{sheetPseudonymUser} -> sheetPseudonymUser `elem` map (submissionUserUser . entityVal) existingSubUsers) sps'
|
|
||||||
forM_ sps'' $ \spGroup
|
|
||||||
-> let
|
|
||||||
sheetGroupDesc = Text.intercalate ", " $ map (review _PseudonymText . sheetPseudonymPseudonym) spGroup
|
|
||||||
in case sheetGrouping of
|
|
||||||
Arbitrary maxSize -> do
|
|
||||||
subId <- insert submissionPrototype
|
|
||||||
void . insert $ SubmissionEdit uid now subId
|
|
||||||
insertMany_ . flip map spGroup $ \SheetPseudonym{sheetPseudonymUser} -> SubmissionUser
|
|
||||||
{ submissionUserUser = sheetPseudonymUser
|
|
||||||
, submissionUserSubmission = subId
|
|
||||||
}
|
}
|
||||||
when (genericLength spGroup > maxSize) $
|
unless (null duplicate)
|
||||||
addMessageI Warning $ MsgSheetGroupTooLarge sheetGroupDesc
|
$(addMessageFile Warning "templates/messages/submissionCreateDuplicates.hamlet")
|
||||||
RegisteredGroups -> do
|
existingSubUsers <- E.select . E.from $ \(submissionUser `E.InnerJoin` submission) -> do
|
||||||
groups <- E.select . E.from $ \submissionGroup -> do
|
E.on $ submission E.^. SubmissionId E.==. submissionUser E.^. SubmissionUserSubmission
|
||||||
E.where_ . E.exists . E.from $ \submissionGroupUser ->
|
E.where_ $ submissionUser E.^. SubmissionUserUser `E.in_` E.valList (sheetPseudonymUser <$> concat sps')
|
||||||
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser `E.in_` E.valList (map sheetPseudonymUser spGroup)
|
E.&&. submission E.^. SubmissionSheet E.==. E.val sid
|
||||||
return $ submissionGroup E.^. SubmissionGroupId
|
return submissionUser
|
||||||
if
|
unless (null existingSubUsers) . mapReaderT lift $ do
|
||||||
| length (groups :: [E.Value SubmissionGroupId]) < 2
|
(Map.toList -> subs) <- foldrM (\(Entity _ SubmissionUser{..}) mp -> Map.insertWith (<>) <$> (encrypt submissionUserSubmission :: DB CryptoFileNameSubmission) <*> pure (Set.fromList . map sheetPseudonymPseudonym . filter (\SheetPseudonym{..} -> sheetPseudonymUser == submissionUserUser) $ concat sps') <*> pure mp) Map.empty existingSubUsers
|
||||||
-> do
|
$(addMessageFile Warning "templates/messages/submissionCreateExisting.hamlet")
|
||||||
subId <- insert submissionPrototype
|
let sps'' = filter (not . null) $ filter (\spGroup -> not . flip any spGroup $ \SheetPseudonym{sheetPseudonymUser} -> sheetPseudonymUser `elem` map (submissionUserUser . entityVal) existingSubUsers) sps'
|
||||||
void . insert $ SubmissionEdit uid now subId
|
forM_ sps'' $ \spGroup
|
||||||
insertMany_ . flip map spGroup $ \SheetPseudonym{sheetPseudonymUser} -> SubmissionUser
|
-> let
|
||||||
{ submissionUserUser = sheetPseudonymUser
|
sheetGroupDesc = Text.intercalate ", " $ map (review _PseudonymText . sheetPseudonymPseudonym) spGroup
|
||||||
, submissionUserSubmission = subId
|
in case sheetGrouping of
|
||||||
}
|
Arbitrary maxSize -> do
|
||||||
when (null groups) $
|
subId <- insert submissionPrototype
|
||||||
addMessageI Warning $ MsgSheetNoRegisteredGroup sheetGroupDesc
|
void . insert $ SubmissionEdit uid now subId
|
||||||
| otherwise -> addMessageI Error $ MsgSheetAmbiguousRegisteredGroup sheetGroupDesc
|
insertMany_ . flip map spGroup $ \SheetPseudonym{sheetPseudonymUser} -> SubmissionUser
|
||||||
NoGroups -> do
|
{ submissionUserUser = sheetPseudonymUser
|
||||||
subId <- insert submissionPrototype
|
, submissionUserSubmission = subId
|
||||||
void . insert $ SubmissionEdit uid now subId
|
}
|
||||||
insertMany_ . flip map spGroup $ \SheetPseudonym{sheetPseudonymUser} -> SubmissionUser
|
when (genericLength spGroup > maxSize) $
|
||||||
{ submissionUserUser = sheetPseudonymUser
|
addMessageI Warning $ MsgSheetGroupTooLarge sheetGroupDesc
|
||||||
, submissionUserSubmission = subId
|
RegisteredGroups -> do
|
||||||
}
|
let spGroup' = Map.fromList $ map (sheetPseudonymUser &&& id) spGroup
|
||||||
when (length spGroup > 1) $
|
groups <- E.select . E.from $ \submissionGroup -> do
|
||||||
addMessageI Warning $ MsgSheetNoGroupSubmission sheetGroupDesc
|
E.where_ . E.exists . E.from $ \submissionGroupUser ->
|
||||||
redirect CorrectionsGradeR
|
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser `E.in_` E.valList (map sheetPseudonymUser spGroup)
|
||||||
|
return $ submissionGroup E.^. SubmissionGroupId
|
||||||
|
groupUsers <- fmap (Set.fromList . map E.unValue) . E.select . E.from $ \submissionGroupUser -> do
|
||||||
|
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup `E.in_` E.valList (map E.unValue groups)
|
||||||
|
return $ submissionGroupUser E.^. SubmissionGroupUserUser
|
||||||
|
if
|
||||||
|
| [_] <- groups
|
||||||
|
, Map.keysSet spGroup' `Set.isSubsetOf` groupUsers
|
||||||
|
-> do
|
||||||
|
subId <- insert submissionPrototype
|
||||||
|
void . insert $ SubmissionEdit uid now subId
|
||||||
|
insertMany_ . flip map (Set.toList groupUsers) $ \sheetUser -> SubmissionUser
|
||||||
|
{ submissionUserUser = sheetUser
|
||||||
|
, submissionUserSubmission = subId
|
||||||
|
}
|
||||||
|
when (null groups) $
|
||||||
|
addMessageI Warning $ MsgSheetNoRegisteredGroup sheetGroupDesc
|
||||||
|
| length groups < 2
|
||||||
|
-> do
|
||||||
|
forM_ (Set.toList (Map.keysSet spGroup' `Set.difference` groupUsers)) $ \((spGroup' !) -> SheetPseudonym{sheetPseudonymPseudonym}) -> do
|
||||||
|
addMessageI Error $ MsgSheetNoRegisteredGroup (review _PseudonymText sheetPseudonymPseudonym)
|
||||||
|
tell $ All False
|
||||||
|
| otherwise ->
|
||||||
|
addMessageI Error $ MsgSheetAmbiguousRegisteredGroup sheetGroupDesc
|
||||||
|
NoGroups -> do
|
||||||
|
subId <- insert submissionPrototype
|
||||||
|
void . insert $ SubmissionEdit uid now subId
|
||||||
|
insertMany_ . flip map spGroup $ \SheetPseudonym{sheetPseudonymUser} -> SubmissionUser
|
||||||
|
{ submissionUserUser = sheetPseudonymUser
|
||||||
|
, submissionUserSubmission = subId
|
||||||
|
}
|
||||||
|
when (length spGroup > 1) $
|
||||||
|
addMessageI Warning $ MsgSheetNoGroupSubmission sheetGroupDesc
|
||||||
|
when allDone $
|
||||||
|
redirect CorrectionsGradeR
|
||||||
|
|
||||||
|
|
||||||
defaultLayout $
|
defaultLayout $
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import qualified Data.Text as T
|
|||||||
import Data.Function ((&))
|
import Data.Function ((&))
|
||||||
-- import Yesod.Form.Bootstrap3
|
-- import Yesod.Form.Bootstrap3
|
||||||
|
|
||||||
|
import Data.Monoid (Last(..))
|
||||||
|
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
@ -161,13 +163,27 @@ makeCourseTable whereClause colChoices psValidator = do
|
|||||||
| Set.null criterias -> E.val True :: E.SqlExpr (E.Value Bool)
|
| Set.null criterias -> E.val True :: E.SqlExpr (E.Value Bool)
|
||||||
| otherwise -> school E.^. SchoolShorthand `E.in_` E.valList (Set.toList criterias)
|
| otherwise -> school E.^. SchoolShorthand `E.in_` E.valList (Set.toList criterias)
|
||||||
)
|
)
|
||||||
|
, ( "registered", FilterColumn $ \tExpr criterion -> case getLast (criterion :: Last Bool) of
|
||||||
|
Nothing -> E.val True :: E.SqlExpr (E.Value Bool)
|
||||||
|
Just needle -> course2Registered muid tExpr E.==. E.val needle
|
||||||
|
)
|
||||||
|
, ( "search", FilterColumn $ \(course `E.InnerJoin` _school :: CourseTableExpr) criterion -> case getLast (criterion :: Last Text) of
|
||||||
|
Nothing -> E.val True :: E.SqlExpr (E.Value Bool)
|
||||||
|
Just needle -> (E.castString (course E.^. CourseName) `E.ilike` (E.%) E.++. E.val needle E.++. (E.%))
|
||||||
|
E.||. (E.castString (course E.^. CourseShorthand) `E.ilike` (E.%) E.++. E.val needle E.++. (E.%))
|
||||||
|
E.||. (E.castString (course E.^. CourseDescription) `E.ilike` (E.%) E.++. E.val needle E.++. (E.%))
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
, dbtFilterUI = mconcat
|
||||||
|
[ Map.singleton "search" . maybeToList <$> aopt textField (fslI MsgCourseFilterSearch) Nothing
|
||||||
|
, Map.singleton "registered" . fmap toPathPiece . maybeToList <$> aopt boolField (fslI MsgCourseFilterRegistered) Nothing
|
||||||
|
]
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
, dbtIdent = "courses" :: Text
|
, dbtIdent = "courses" :: Text
|
||||||
}
|
}
|
||||||
|
|
||||||
getCourseListR :: Handler Html
|
getCourseListR :: Handler Html
|
||||||
getCourseListR = do -- TODO: Suchfunktion für Kurse und Kürzel!!!
|
getCourseListR = do
|
||||||
muid <- maybeAuthId
|
muid <- maybeAuthId
|
||||||
let colonnade = widgetColonnade $ mconcat
|
let colonnade = widgetColonnade $ mconcat
|
||||||
[ colCourseDescr
|
[ colCourseDescr
|
||||||
@ -182,7 +198,6 @@ getCourseListR = do -- TODO: Suchfunktion für Kurse und Kürzel!!!
|
|||||||
coursesTable <- runDB $ makeCourseTable whereClause colonnade validator
|
coursesTable <- runDB $ makeCourseTable whereClause colonnade validator
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitleI MsgCourseListTitle
|
setTitleI MsgCourseListTitle
|
||||||
[whamlet|TODO: Such-/Filterfunktion hier einbauen|] -- TODO
|
|
||||||
$(widgetFile "courses")
|
$(widgetFile "courses")
|
||||||
|
|
||||||
getTermCurrentR :: Handler Html
|
getTermCurrentR :: Handler Html
|
||||||
|
|||||||
@ -97,6 +97,7 @@ homeAnonymous = do
|
|||||||
| otherwise -> course E.^. CourseTerm `E.in_` E.valList (Set.toList tids)
|
| otherwise -> course E.^. CourseTerm `E.in_` E.valList (Set.toList tids)
|
||||||
)
|
)
|
||||||
] -}
|
] -}
|
||||||
|
, dbtFilterUI = mempty
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
, dbtIdent = "upcomingdeadlines" :: Text
|
, dbtIdent = "upcomingdeadlines" :: Text
|
||||||
}
|
}
|
||||||
@ -198,6 +199,7 @@ homeUser uid = do
|
|||||||
| otherwise -> course E.^. CourseTerm `E.in_` E.valList (Set.toList tids)
|
| otherwise -> course E.^. CourseTerm `E.in_` E.valList (Set.toList tids)
|
||||||
)
|
)
|
||||||
] -}
|
] -}
|
||||||
|
, dbtFilterUI = mempty
|
||||||
, dbtStyle = def { dbsEmptyStyle = DBESNoHeading, dbsEmptyMessage = MsgNoUpcomingSheetDeadlines }
|
, dbtStyle = def { dbsEmptyStyle = DBESNoHeading, dbsEmptyMessage = MsgNoUpcomingSheetDeadlines }
|
||||||
, dbtIdent = "upcomingdeadlines" :: Text
|
, dbtIdent = "upcomingdeadlines" :: Text
|
||||||
}
|
}
|
||||||
@ -283,8 +285,12 @@ getAuthPredsR, postAuthPredsR :: Handler Html
|
|||||||
getAuthPredsR = postAuthPredsR
|
getAuthPredsR = postAuthPredsR
|
||||||
postAuthPredsR = do
|
postAuthPredsR = do
|
||||||
(AuthTagActive authTagCurrentActive) <- fromMaybe def <$> lookupSessionJson SessionActiveAuthTags
|
(AuthTagActive authTagCurrentActive) <- fromMaybe def <$> lookupSessionJson SessionActiveAuthTags
|
||||||
|
|
||||||
let taForm authTag = fromMaybe False <$> aopt checkBoxField (fslI authTag) (Just . Just $ authTagCurrentActive authTag)
|
let
|
||||||
|
blacklist = Set.fromList [ AuthFree, AuthDevelopment, AuthDeprecated ]
|
||||||
|
taForm authTag
|
||||||
|
| authTag `Set.member` blacklist = aforced checkBoxField (fslI authTag) (authTagIsActive def authTag)
|
||||||
|
| otherwise = fromMaybe False <$> aopt checkBoxField (fslI authTag) (Just . Just $ authTagCurrentActive authTag)
|
||||||
|
|
||||||
((authActiveRes, authActiveWidget), authActiveEnctype) <- runFormPost . renderAForm FormStandard
|
((authActiveRes, authActiveWidget), authActiveEnctype) <- runFormPost . renderAForm FormStandard
|
||||||
$ AuthTagActive
|
$ AuthTagActive
|
||||||
|
|||||||
@ -273,6 +273,7 @@ mkOwnedCoursesTable =
|
|||||||
, ( "term", FilterColumn $ withType $ \(crse `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseTerm )
|
, ( "term", FilterColumn $ withType $ \(crse `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseTerm )
|
||||||
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool )
|
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool )
|
||||||
]
|
]
|
||||||
|
dbtFilterUI = mempty
|
||||||
in \uid -> let dbtSQLQuery = dbtSQLQuery' uid in (_1 %~ getAny) <$> dbTableWidget validator DBTable{..}
|
in \uid -> let dbtSQLQuery = dbtSQLQuery' uid in (_1 %~ getAny) <$> dbTableWidget validator DBTable{..}
|
||||||
|
|
||||||
|
|
||||||
@ -319,6 +320,7 @@ mkEnrolledCoursesTable =
|
|||||||
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool)
|
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool)
|
||||||
-- , ( "time" , FilterColumn $ \(_ `E.InnerJoin` part :: CourseTableData) -> emptyOrIn $ part E.^. CourseParticipantRegistration )
|
-- , ( "time" , FilterColumn $ \(_ `E.InnerJoin` part :: CourseTableData) -> emptyOrIn $ part E.^. CourseParticipantRegistration )
|
||||||
]
|
]
|
||||||
|
, dbtFilterUI = mempty
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,6 +398,7 @@ mkSubmissionTable =
|
|||||||
, ( "term", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseTerm )
|
, ( "term", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseTerm )
|
||||||
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool )
|
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool )
|
||||||
]
|
]
|
||||||
|
dbtFilterUI = mempty
|
||||||
in \uid -> let dbtSQLQuery = dbtSQLQuery' uid
|
in \uid -> let dbtSQLQuery = dbtSQLQuery' uid
|
||||||
dbtSorting = dbtSorting' uid
|
dbtSorting = dbtSorting' uid
|
||||||
in dbTableWidget' validator DBTable{..}
|
in dbTableWidget' validator DBTable{..}
|
||||||
@ -465,6 +468,7 @@ mkSubmissionGroupTable =
|
|||||||
, ( "term", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseTerm )
|
, ( "term", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseTerm )
|
||||||
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool )
|
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool )
|
||||||
]
|
]
|
||||||
|
dbtFilterUI = mempty
|
||||||
in \uid -> let dbtSQLQuery = dbtSQLQuery' uid
|
in \uid -> let dbtSQLQuery = dbtSQLQuery' uid
|
||||||
in dbTableWidget' validator DBTable{..}
|
in dbTableWidget' validator DBTable{..}
|
||||||
|
|
||||||
@ -538,6 +542,7 @@ mkCorrectionsTable =
|
|||||||
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool )
|
, ( "school", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseSchool )
|
||||||
, ( "course", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseShorthand)
|
, ( "course", FilterColumn $ withType $ \(crse `E.InnerJoin` _ `E.InnerJoin` _) -> emptyOrIn $ crse E.^. CourseShorthand)
|
||||||
]
|
]
|
||||||
|
dbtFilterUI = mempty
|
||||||
in \uid -> let dbtSQLQuery = dbtSQLQuery' uid
|
in \uid -> let dbtSQLQuery = dbtSQLQuery' uid
|
||||||
in dbTableWidget' validator DBTable{..}
|
in dbTableWidget' validator DBTable{..}
|
||||||
|
|
||||||
|
|||||||
@ -225,8 +225,8 @@ getSheetListR tid ssh csh = do
|
|||||||
-- (submission E.?. SubmissionRatingPoints) E./. (sheet E.^. SheetType)
|
-- (submission E.?. SubmissionRatingPoints) E./. (sheet E.^. SheetType)
|
||||||
-- )
|
-- )
|
||||||
]
|
]
|
||||||
, dbtFilter = Map.fromList
|
, dbtFilter = mempty
|
||||||
[]
|
, dbtFilterUI = mempty
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
, dbtIdent = "sheets" :: Text
|
, dbtIdent = "sheets" :: Text
|
||||||
}
|
}
|
||||||
@ -294,7 +294,8 @@ getSShowR tid ssh csh shn = do
|
|||||||
, dbtProj = \DBRow{ dbrOutput = dbrOutput@(E.Value fName, _, E.Value fType) }
|
, dbtProj = \DBRow{ dbrOutput = dbrOutput@(E.Value fName, _, E.Value fType) }
|
||||||
-> dbrOutput <$ guardM (lift $ (== Authorized) <$> evalAccessDB (CSheetR tid ssh csh shn $ SFileR fType fName) False)
|
-> dbrOutput <$ guardM (lift $ (== Authorized) <$> evalAccessDB (CSheetR tid ssh csh shn $ SFileR fType fName) False)
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
, dbtFilter = Map.empty
|
, dbtFilter = mempty
|
||||||
|
, dbtFilterUI = mempty
|
||||||
, dbtIdent = "files" :: Text
|
, dbtIdent = "files" :: Text
|
||||||
, dbtSorting = Map.fromList
|
, dbtSorting = Map.fromList
|
||||||
[ ( "type"
|
[ ( "type"
|
||||||
|
|||||||
@ -310,7 +310,8 @@ submissionHelper tid ssh csh shn (SubmissionMode mcid) = do
|
|||||||
, SortColumn $ \((_sf1 `E.InnerJoin` f1) `E.FullOuterJoin` (_sf2 `E.InnerJoin` f2)) -> (E.unsafeSqlFunction "GREATEST" ([f1 E.?. FileModified, f2 E.?. FileModified] :: [E.SqlExpr (E.Value (Maybe UTCTime))]) :: E.SqlExpr (E.Value (Maybe UTCTime)))
|
, SortColumn $ \((_sf1 `E.InnerJoin` f1) `E.FullOuterJoin` (_sf2 `E.InnerJoin` f2)) -> (E.unsafeSqlFunction "GREATEST" ([f1 E.?. FileModified, f2 E.?. FileModified] :: [E.SqlExpr (E.Value (Maybe UTCTime))]) :: E.SqlExpr (E.Value (Maybe UTCTime)))
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
, dbtFilter = Map.empty
|
, dbtFilter = mempty
|
||||||
|
, dbtFilterUI = mempty
|
||||||
}
|
}
|
||||||
mFileTable <- traverse (runDB . dbTableWidget' def) . fmap smid2ArchiveTable $ (,) <$> msmid <*> mcid
|
mFileTable <- traverse (runDB . dbTableWidget' def) . fmap smid2ArchiveTable $ (,) <$> msmid <*> mcid
|
||||||
|
|
||||||
|
|||||||
@ -178,12 +178,9 @@ postMessageListR = do
|
|||||||
{ dbtSQLQuery
|
{ dbtSQLQuery
|
||||||
, dbtColonnade
|
, dbtColonnade
|
||||||
, dbtProj
|
, dbtProj
|
||||||
, dbtSorting = Map.fromList
|
, dbtSorting = mempty -- TODO: from, to, authenticated, severity
|
||||||
[ -- TODO: from, to, authenticated, severity
|
, dbtFilter = mempty
|
||||||
]
|
, dbtFilterUI = mempty
|
||||||
, dbtFilter = Map.fromList
|
|
||||||
[
|
|
||||||
]
|
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
, dbtIdent = "messages" :: Text
|
, dbtIdent = "messages" :: Text
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,6 +126,7 @@ getTermShowR = do
|
|||||||
E.&&. course E.^. CourseShorthand `E.in_` E.valList cshs
|
E.&&. course E.^. CourseShorthand `E.in_` E.valList cshs
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
, dbtFilterUI = mempty
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
, dbtIdent = "terms" :: Text
|
, dbtIdent = "terms" :: Text
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,7 @@ getUsersR = do
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
, dbtFilter = mempty
|
, dbtFilter = mempty
|
||||||
|
, dbtFilterUI = mempty
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
, dbtIdent = "users" :: Text
|
, dbtIdent = "users" :: Text
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,6 +45,9 @@ import Text.Read (readMaybe)
|
|||||||
|
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
|
|
||||||
|
import Data.Aeson (eitherDecodeStrict')
|
||||||
|
import Data.Aeson.Text (encodeToLazyText)
|
||||||
|
|
||||||
----------------------------
|
----------------------------
|
||||||
-- Buttons (new version ) --
|
-- Buttons (new version ) --
|
||||||
----------------------------
|
----------------------------
|
||||||
@ -455,6 +458,27 @@ langField :: Bool -- ^ Only allow values from `appLanguages`
|
|||||||
langField False = checkBool (all ((&&) <$> not . null <*> T.all Char.isAlpha) . T.splitOn "-") MsgInvalidLangFormat $ textField & addDatalist (return $ toList appLanguages)
|
langField False = checkBool (all ((&&) <$> not . null <*> T.all Char.isAlpha) . T.splitOn "-") MsgInvalidLangFormat $ textField & addDatalist (return $ toList appLanguages)
|
||||||
langField True = selectField . optionsPairs . map (MsgLanguage &&& id) $ toList appLanguages
|
langField True = selectField . optionsPairs . map (MsgLanguage &&& id) $ toList appLanguages
|
||||||
|
|
||||||
|
jsonField :: ( ToJSON a, FromJSON a
|
||||||
|
, MonadHandler m
|
||||||
|
, RenderMessage (HandlerSite m) UniWorXMessage
|
||||||
|
, RenderMessage (HandlerSite m) FormMessage
|
||||||
|
)
|
||||||
|
=> Bool {-^ Hidden? -}
|
||||||
|
-> Field m a
|
||||||
|
jsonField hide = Field{..}
|
||||||
|
where
|
||||||
|
inputType :: Text
|
||||||
|
inputType
|
||||||
|
| hide = "hidden"
|
||||||
|
| otherwise = "text"
|
||||||
|
fieldParse [v] [] = return . second Just . first (SomeMessage . MsgJSONFieldDecodeFailure) . eitherDecodeStrict' $ encodeUtf8 v
|
||||||
|
fieldParse [] [] = return $ Right Nothing
|
||||||
|
fieldParse _ _ = return . Left $ SomeMessage MsgValueRequired
|
||||||
|
fieldView theId name attrs val isReq = liftWidgetT [whamlet|
|
||||||
|
<input id=#{theId} name=#{name} *{attrs} type=#{inputType} :isReq:required value=#{either fromStrict encodeToLazyText val}>
|
||||||
|
|]
|
||||||
|
fieldEnctype = UrlEncoded
|
||||||
|
|
||||||
|
|
||||||
funcForm :: forall k v m.
|
funcForm :: forall k v m.
|
||||||
( Finite k, Ord k
|
( Finite k, Ord k
|
||||||
|
|||||||
@ -41,7 +41,7 @@ import qualified Database.Esqueleto as E
|
|||||||
|
|
||||||
import qualified Data.Conduit.List as Conduit
|
import qualified Data.Conduit.List as Conduit
|
||||||
|
|
||||||
import Utils.Lens hiding ((<.>))
|
import Utils.Lens
|
||||||
|
|
||||||
|
|
||||||
instance HasResolution prec => Pretty (Fixed prec) where
|
instance HasResolution prec => Pretty (Fixed prec) where
|
||||||
|
|||||||
@ -5,7 +5,7 @@ module Handler.Utils.SheetType
|
|||||||
|
|
||||||
import Import
|
import Import
|
||||||
import Data.Monoid (Sum(..))
|
import Data.Monoid (Sum(..))
|
||||||
import Utils.Lens hiding ((<.>))
|
import Utils.Lens
|
||||||
|
|
||||||
addBonusToPoints :: SheetTypeSummary -> SheetTypeSummary
|
addBonusToPoints :: SheetTypeSummary -> SheetTypeSummary
|
||||||
addBonusToPoints sts =
|
addBonusToPoints sts =
|
||||||
|
|||||||
@ -25,6 +25,7 @@ module Handler.Utils.Table.Pagination
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Handler.Utils.Table.Pagination.Types
|
import Handler.Utils.Table.Pagination.Types
|
||||||
|
import Handler.Utils.Form
|
||||||
import Utils
|
import Utils
|
||||||
import Utils.Lens.TH
|
import Utils.Lens.TH
|
||||||
|
|
||||||
@ -228,6 +229,11 @@ data DBStyle = DBStyle
|
|||||||
{ dbsEmptyStyle :: DBEmptyStyle
|
{ dbsEmptyStyle :: DBEmptyStyle
|
||||||
, dbsEmptyMessage :: UniWorXMessage
|
, dbsEmptyMessage :: UniWorXMessage
|
||||||
, dbsAttrs :: [(Text, Text)]
|
, dbsAttrs :: [(Text, Text)]
|
||||||
|
, dbsLayoutFilter :: Widget -- ^ Filter UI
|
||||||
|
-> Enctype
|
||||||
|
-> Text -- ^ Filter action (target uri)
|
||||||
|
-> Widget -- ^ Table
|
||||||
|
-> Widget
|
||||||
}
|
}
|
||||||
|
|
||||||
instance Default DBStyle where
|
instance Default DBStyle where
|
||||||
@ -235,8 +241,12 @@ instance Default DBStyle where
|
|||||||
{ dbsEmptyStyle = def
|
{ dbsEmptyStyle = def
|
||||||
, dbsEmptyMessage = MsgNoTableContent
|
, dbsEmptyMessage = MsgNoTableContent
|
||||||
, dbsAttrs = [ ("class", "table table--striped table--hover table--sortable") ]
|
, dbsAttrs = [ ("class", "table table--striped table--hover table--sortable") ]
|
||||||
|
, dbsLayoutFilter = \filterWgdt filterEnctype filterAction scrolltable -> $(widgetFile "table/layout-filter-default")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FilterKey = CI Text
|
||||||
|
type SortingKey = CI Text
|
||||||
|
|
||||||
data DBTable m x = forall a r r' h i t.
|
data DBTable m x = forall a r r' h i t.
|
||||||
( ToSortable h, Functor h
|
( ToSortable h, Functor h
|
||||||
, E.SqlSelect a r
|
, E.SqlSelect a r
|
||||||
@ -246,8 +256,9 @@ data DBTable m x = forall a r r' h i t.
|
|||||||
{ dbtSQLQuery :: t -> E.SqlQuery a
|
{ dbtSQLQuery :: t -> E.SqlQuery a
|
||||||
, dbtProj :: DBRow r -> MaybeT (ReaderT SqlBackend (HandlerT UniWorX IO)) r'
|
, dbtProj :: DBRow r -> MaybeT (ReaderT SqlBackend (HandlerT UniWorX IO)) r'
|
||||||
, dbtColonnade :: Colonnade h r' (DBCell m x)
|
, dbtColonnade :: Colonnade h r' (DBCell m x)
|
||||||
, dbtSorting :: Map (CI Text) (SortColumn t)
|
, dbtSorting :: Map SortingKey (SortColumn t)
|
||||||
, dbtFilter :: Map (CI Text) (FilterColumn t)
|
, dbtFilter :: Map FilterKey (FilterColumn t)
|
||||||
|
, dbtFilterUI :: AForm (ReaderT SqlBackend (HandlerT UniWorX IO)) (Map FilterKey [Text])
|
||||||
, dbtStyle :: DBStyle
|
, dbtStyle :: DBStyle
|
||||||
, dbtIdent :: i
|
, dbtIdent :: i
|
||||||
}
|
}
|
||||||
@ -379,18 +390,31 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
, fieldEnctype = UrlEncoded
|
, fieldEnctype = UrlEncoded
|
||||||
}
|
}
|
||||||
|
|
||||||
piResult <- lift . runInputGetResult $ PaginationInput
|
piPrevious <- fmap (maybe FormMissing FormSuccess) . runMaybeT $ MaybeT . return . decodeStrict' . encodeUtf8 =<< MaybeT (lookupPostParam $ wIdent "pagination")
|
||||||
|
|
||||||
|
piInput <- lift . runInputGetResult $ PaginationInput
|
||||||
<$> iopt (multiSelectField $ return sortingOptions) (wIdent "sorting")
|
<$> iopt (multiSelectField $ return sortingOptions) (wIdent "sorting")
|
||||||
<*> (assertM' (not . Map.null) . Map.mapMaybe (assertM $ not . null) <$> Map.traverseWithKey (\k _ -> iopt multiTextField . wIdent $ CI.foldedCase k) dbtFilter)
|
<*> (assertM' (not . Map.null) . Map.mapMaybe (assertM $ not . null) <$> Map.traverseWithKey (\k _ -> iopt multiTextField . wIdent $ CI.foldedCase k) dbtFilter)
|
||||||
<*> iopt intField (wIdent "pagesize")
|
<*> iopt intField (wIdent "pagesize")
|
||||||
<*> iopt intField (wIdent "page")
|
<*> iopt intField (wIdent "page")
|
||||||
|
|
||||||
piPrevious <- fmap (maybe FormMissing FormSuccess) . runMaybeT $ MaybeT . return . decodeStrict' . encodeUtf8 =<< MaybeT (lookupPostParam $ wIdent "pagination")
|
let filterPi
|
||||||
|
| FormSuccess PaginationInput{..} <- piPrevious <|> piInput
|
||||||
|
= def{ piSorting, piLimit }
|
||||||
|
| otherwise
|
||||||
|
= def
|
||||||
|
|
||||||
|
((filterRes, filterWdgt), filterEnc) <- runFormGet . renderAForm FormDBTableFilter $ (,)
|
||||||
|
<$> areq (jsonField True) "" (Just filterPi)
|
||||||
|
<*> dbtFilterUI
|
||||||
|
|
||||||
|
let
|
||||||
|
piResult = piPrevious <|> (\(prev, fSettings) -> prev & _piFilter .~ Just fSettings) <$> filterRes <|> piInput
|
||||||
|
|
||||||
psShortcircuit <- (== Just dbtIdent') <$> lookupCustomHeader HeaderDBTableShortcircuit
|
psShortcircuit <- (== Just dbtIdent') <$> lookupCustomHeader HeaderDBTableShortcircuit
|
||||||
|
|
||||||
let
|
let
|
||||||
(errs, PaginationSettings{..}) = case piPrevious <|> piResult of
|
(errs, PaginationSettings{..}) = case piResult of
|
||||||
FormSuccess pi
|
FormSuccess pi
|
||||||
| not (piIsUnset pi)
|
| not (piIsUnset pi)
|
||||||
-> runPSValidator dbtable $ Just pi
|
-> runPSValidator dbtable $ Just pi
|
||||||
@ -398,7 +422,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
-> first (map SomeMessage errs' <>) $ runPSValidator dbtable Nothing
|
-> first (map SomeMessage errs' <>) $ runPSValidator dbtable Nothing
|
||||||
_ -> runPSValidator dbtable Nothing
|
_ -> runPSValidator dbtable Nothing
|
||||||
paginationInput
|
paginationInput
|
||||||
| FormSuccess pi <- piPrevious <|> piResult
|
| FormSuccess pi <- piResult
|
||||||
, not $ piIsUnset pi
|
, not $ piIsUnset pi
|
||||||
= pi
|
= pi
|
||||||
| otherwise
|
| otherwise
|
||||||
@ -419,18 +443,23 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
|
|
||||||
rows <- mapMaybeM dbtProj . map (\(dbrIndex, (E.Value dbrCount, dbrOutput)) -> DBRow{..}) $ zip [succ (psPage * psLimit)..] rows'
|
rows <- mapMaybeM dbtProj . map (\(dbrIndex, (E.Value dbrCount, dbrOutput)) -> DBRow{..}) $ zip [succ (psPage * psLimit)..] rows'
|
||||||
|
|
||||||
|
getParams <- liftHandlerT $ queryToQueryText . Wai.queryString . reqWaiRequest <$> getRequest
|
||||||
|
|
||||||
let
|
let
|
||||||
rowCount
|
rowCount
|
||||||
| (E.Value n, _):_ <- rows' = n
|
| (E.Value n, _):_ <- rows' = n
|
||||||
| otherwise = 0
|
| otherwise = 0
|
||||||
|
|
||||||
|
tblLink f = decodeUtf8 . toStrict . Builder.toLazyByteString . renderQueryText True $ f getParams
|
||||||
|
|
||||||
|
filterAction = tblLink
|
||||||
|
$ setParam (wIdent "page") Nothing
|
||||||
|
. Map.foldrWithKey (\k _ f -> setParam (wIdent $ CI.foldedCase k) Nothing . f) id dbtFilter
|
||||||
|
|
||||||
table' :: WriterT x m Widget
|
table' :: WriterT x m Widget
|
||||||
table' = do
|
table' = do
|
||||||
getParams <- liftHandlerT $ queryToQueryText . Wai.queryString . reqWaiRequest <$> getRequest
|
|
||||||
|
|
||||||
let
|
let
|
||||||
tblLink f = decodeUtf8 . Builder.toLazyByteString . renderQueryText True $ f getParams
|
|
||||||
|
|
||||||
genHeaders SortableP{..} = forM (toSortable . oneColonnadeHead <$> getColonnade dbtColonnade) $ \Sortable{..} -> do
|
genHeaders SortableP{..} = forM (toSortable . oneColonnadeHead <$> getColonnade dbtColonnade) $ \Sortable{..} -> do
|
||||||
widget <- sortableContent ^. cellContents
|
widget <- sortableContent ^. cellContents
|
||||||
let
|
let
|
||||||
@ -456,7 +485,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
|
|
||||||
return $(widgetFile "table/layout")
|
return $(widgetFile "table/layout")
|
||||||
|
|
||||||
bool (dbHandler dbtable paginationInput $ \table -> $(widgetFile "table/layout-wrapper")) (sendResponse <=< tblLayout <=< dbWidget dbtable paginationInput) psShortcircuit <=< runDBTable . fmap swap $ runWriterT table'
|
bool (dbHandler dbtable paginationInput $ (\table -> $(widgetFile "table/layout-wrapper")) . dbsLayoutFilter filterWdgt filterEnc filterAction) (sendResponse <=< tblLayout . dbsLayoutFilter filterWdgt filterEnc filterAction <=< dbWidget dbtable paginationInput) psShortcircuit <=< runDBTable . fmap swap $ runWriterT table'
|
||||||
where
|
where
|
||||||
tblLayout :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => Widget -> m' Html
|
tblLayout :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => Widget -> m' Html
|
||||||
tblLayout tbl' = do
|
tblLayout tbl' = do
|
||||||
@ -464,7 +493,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
withUrlRenderer $(hamletFile "templates/table/layout-standalone.hamlet")
|
withUrlRenderer $(hamletFile "templates/table/layout-standalone.hamlet")
|
||||||
|
|
||||||
setParam :: Text -> Maybe Text -> QueryText -> QueryText
|
setParam :: Text -> Maybe Text -> QueryText -> QueryText
|
||||||
setParam key v qt = (key, v) : [ i | i@(key', _) <- qt, key' /= key ]
|
setParam key v qt = maybe id (\v' -> (:) (key, Just v')) v [ i | i@(key', _) <- qt, key' /= key ]
|
||||||
|
|
||||||
dbTableWidget :: Monoid x => PSValidator (HandlerT UniWorX IO) x -> DBTable (HandlerT UniWorX IO) x
|
dbTableWidget :: Monoid x => PSValidator (HandlerT UniWorX IO) x -> DBTable (HandlerT UniWorX IO) x
|
||||||
-> DB (DBResult (HandlerT UniWorX IO) x)
|
-> DB (DBResult (HandlerT UniWorX IO) x)
|
||||||
|
|||||||
@ -41,6 +41,7 @@ import Data.Hashable as Import
|
|||||||
import Data.List.NonEmpty as Import (NonEmpty(..))
|
import Data.List.NonEmpty as Import (NonEmpty(..))
|
||||||
import Data.Text.Encoding.Error as Import(UnicodeException(..))
|
import Data.Text.Encoding.Error as Import(UnicodeException(..))
|
||||||
import Data.Semigroup as Import (Semigroup)
|
import Data.Semigroup as Import (Semigroup)
|
||||||
|
import Data.Monoid.Instances as Import ()
|
||||||
|
|
||||||
import Control.Monad.Morph as Import (MFunctor(..))
|
import Control.Monad.Morph as Import (MFunctor(..))
|
||||||
|
|
||||||
|
|||||||
14
src/Jobs.hs
14
src/Jobs.hs
@ -134,6 +134,7 @@ execCrontab = evalStateT go HashMap.empty
|
|||||||
runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ merge
|
runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ merge
|
||||||
|
|
||||||
refT <- liftIO getCurrentTime
|
refT <- liftIO getCurrentTime
|
||||||
|
settings <- getsYesod appSettings
|
||||||
currentState <- mapStateT (mapReaderT $ liftIO . atomically) $ do
|
currentState <- mapStateT (mapReaderT $ liftIO . atomically) $ do
|
||||||
crontab' <- liftBase . tryReadTMVar =<< asks jobCrontab
|
crontab' <- liftBase . tryReadTMVar =<< asks jobCrontab
|
||||||
case crontab' of
|
case crontab' of
|
||||||
@ -141,7 +142,7 @@ execCrontab = evalStateT go HashMap.empty
|
|||||||
Just crontab -> Just <$> do
|
Just crontab -> Just <$> do
|
||||||
State.modify . HashMap.filterWithKey $ \k _ -> HashMap.member k crontab
|
State.modify . HashMap.filterWithKey $ \k _ -> HashMap.member k crontab
|
||||||
prevExec <- State.get
|
prevExec <- State.get
|
||||||
case earliestJob prevExec crontab refT of
|
case earliestJob settings prevExec crontab refT of
|
||||||
Nothing -> liftBase retry
|
Nothing -> liftBase retry
|
||||||
Just (_, MatchNone) -> liftBase retry
|
Just (_, MatchNone) -> liftBase retry
|
||||||
Just x -> return (crontab, x)
|
Just x -> return (crontab, x)
|
||||||
@ -189,6 +190,11 @@ execCrontab = evalStateT go HashMap.empty
|
|||||||
acc :: NominalDiffTime
|
acc :: NominalDiffTime
|
||||||
acc = 1e-3
|
acc = 1e-3
|
||||||
|
|
||||||
|
debouncingAcc :: AppSettings -> JobCtl -> NominalDiffTime
|
||||||
|
debouncingAcc AppSettings{appNotificationRateLimit} = \case
|
||||||
|
JobCtlQueue (JobQueueNotification _) -> appNotificationRateLimit
|
||||||
|
_ -> acc
|
||||||
|
|
||||||
applyJitter :: (MonadHandler m, HandlerSite m ~ UniWorX, Hashable seed) => seed -> UTCTime -> m UTCTime
|
applyJitter :: (MonadHandler m, HandlerSite m ~ UniWorX, Hashable seed) => seed -> UTCTime -> m UTCTime
|
||||||
applyJitter seed t = do
|
applyJitter seed t = do
|
||||||
appInstance <- getsYesod appInstanceID
|
appInstance <- getsYesod appInstanceID
|
||||||
@ -197,8 +203,8 @@ execCrontab = evalStateT go HashMap.empty
|
|||||||
diff = evalRand ( (* acc) . fromInteger <$> getRandomR (- halfRange, halfRange)) $ mkStdGen (hash appInstance `hashWithSalt` seed)
|
diff = evalRand ( (* acc) . fromInteger <$> getRandomR (- halfRange, halfRange)) $ mkStdGen (hash appInstance `hashWithSalt` seed)
|
||||||
return $ addUTCTime diff t
|
return $ addUTCTime diff t
|
||||||
|
|
||||||
earliestJob :: HashMap JobCtl (Max UTCTime) -> Crontab JobCtl -> UTCTime -> Maybe (JobCtl, CronNextMatch UTCTime)
|
earliestJob :: AppSettings -> HashMap JobCtl (Max UTCTime) -> Crontab JobCtl -> UTCTime -> Maybe (JobCtl, CronNextMatch UTCTime)
|
||||||
earliestJob lastTimes crontab now = foldr go' Nothing $ HashMap.toList crontab
|
earliestJob settings lastTimes crontab now = foldr go' Nothing $ HashMap.toList crontab
|
||||||
where
|
where
|
||||||
go' (jobCtl, cron) mbPrev
|
go' (jobCtl, cron) mbPrev
|
||||||
| Just (_, t') <- mbPrev
|
| Just (_, t') <- mbPrev
|
||||||
@ -207,7 +213,7 @@ execCrontab = evalStateT go HashMap.empty
|
|||||||
| otherwise
|
| otherwise
|
||||||
= Just (jobCtl, t)
|
= Just (jobCtl, t)
|
||||||
where
|
where
|
||||||
t = nextCronMatch appTZ (getMax <$> HashMap.lookup jobCtl lastTimes) acc now cron
|
t = nextCronMatch appTZ (getMax <$> HashMap.lookup jobCtl lastTimes) (debouncingAcc settings jobCtl) 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
|
||||||
|
|||||||
19
src/Utils.hs
19
src/Utils.hs
@ -63,6 +63,9 @@ import qualified Crypto.Saltine.Core.SecretBox as SecretBox
|
|||||||
import qualified Crypto.Saltine.Class as Saltine
|
import qualified Crypto.Saltine.Class as Saltine
|
||||||
import qualified Crypto.Data.PKCS7 as PKCS7
|
import qualified Crypto.Data.PKCS7 as PKCS7
|
||||||
|
|
||||||
|
import Data.Fixed (Centi)
|
||||||
|
import Data.Ratio ((%))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-----------
|
-----------
|
||||||
@ -87,7 +90,7 @@ guardAuthResult AuthenticationRequired = notAuthenticated
|
|||||||
guardAuthResult (Unauthorized t) = permissionDenied t
|
guardAuthResult (Unauthorized t) = permissionDenied t
|
||||||
guardAuthResult Authorized = return ()
|
guardAuthResult Authorized = return ()
|
||||||
|
|
||||||
data UnsupportedAuthPredicate route = UnsupportedAuthPredicate String route
|
data UnsupportedAuthPredicate route = UnsupportedAuthPredicate Text route
|
||||||
deriving (Eq, Ord, Typeable, Show)
|
deriving (Eq, Ord, Typeable, Show)
|
||||||
instance (Show route, Typeable route) => Exception (UnsupportedAuthPredicate route)
|
instance (Show route, Typeable route) => Exception (UnsupportedAuthPredicate route)
|
||||||
|
|
||||||
@ -95,8 +98,8 @@ unsupportedAuthPredicate :: ExpQ
|
|||||||
unsupportedAuthPredicate = do
|
unsupportedAuthPredicate = do
|
||||||
logFunc <- logErrorS
|
logFunc <- logErrorS
|
||||||
[e| \tag route -> do
|
[e| \tag route -> do
|
||||||
$(return logFunc) "AccessControl" [st|"!#{tag}" used on route that doesn't support it: #{tshow route}|]
|
$(return logFunc) "AccessControl" [st|"!#{toPathPiece tag}" used on route that doesn't support it: #{tshow route}|]
|
||||||
unauthorizedI (UnsupportedAuthPredicate tag route)
|
unauthorizedI (UnsupportedAuthPredicate (toPathPiece tag) route)
|
||||||
|]
|
|]
|
||||||
|
|
||||||
|
|
||||||
@ -204,17 +207,15 @@ instance {-# OVERLAPPABLE #-} Show a => DisplayAble a where -- The easy way out
|
|||||||
display = pack . show
|
display = pack . show
|
||||||
-}
|
-}
|
||||||
|
|
||||||
textPercent :: Double -> Text -- slow, maybe use Data.Double.Conversion.Text.toFixed instead?
|
textPercent :: Real a => a -> Text -- slow, maybe use Data.Double.Conversion.Text.toFixed instead?
|
||||||
textPercent x = lz <> pack (show rx) <> "%"
|
textPercent x = lz <> pack (show rx) <> "%"
|
||||||
where
|
where
|
||||||
round' :: Double -> Int -- avoids annoying warning
|
rx :: Centi
|
||||||
round' = round
|
rx = realToFrac (x * 100)
|
||||||
rx :: Double
|
|
||||||
rx = fromIntegral (round' $ 1000.0*x) / 10.0
|
|
||||||
lz = if rx < 10.0 then "0" else ""
|
lz = if rx < 10.0 then "0" else ""
|
||||||
|
|
||||||
textPercentInt :: Integral a => a -> a -> Text -- slow, maybe use Data.Double.Conversion.Text.toFixed instead?
|
textPercentInt :: Integral a => a -> a -> Text -- slow, maybe use Data.Double.Conversion.Text.toFixed instead?
|
||||||
textPercentInt part whole = textPercent $ (fromIntegral part) / (fromIntegral whole)
|
textPercentInt part whole = textPercent $ fromIntegral part % fromIntegral whole
|
||||||
|
|
||||||
stepTextCounterCI :: CI Text -> CI Text -- find and increment rightmost-number, preserving leading zeroes
|
stepTextCounterCI :: CI Text -> CI Text -- find and increment rightmost-number, preserving leading zeroes
|
||||||
stepTextCounterCI = CI.map stepTextCounter
|
stepTextCounterCI = CI.map stepTextCounter
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import Utils.Message
|
|||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
-- | Use this type to pass information to the form template
|
-- | Use this type to pass information to the form template
|
||||||
data FormLayout = FormStandard
|
data FormLayout = FormStandard | FormDBTableFilter
|
||||||
|
|
||||||
renderAForm :: Monad m => FormLayout -> FormRender m a
|
renderAForm :: Monad m => FormLayout -> FormRender m a
|
||||||
renderAForm formLayout aform fragment = do
|
renderAForm formLayout aform fragment = do
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
module Utils.Lens ( module Utils.Lens ) where
|
module Utils.Lens ( module Utils.Lens ) where
|
||||||
|
|
||||||
import Import.NoFoundation
|
import Import.NoFoundation
|
||||||
import Control.Lens as Utils.Lens
|
import Control.Lens as Utils.Lens hiding ((<.>))
|
||||||
import Control.Lens.Extras as Utils.Lens (is)
|
import Control.Lens.Extras as Utils.Lens (is)
|
||||||
import Utils.Lens.TH as Utils.Lens (makeLenses_)
|
import Utils.Lens.TH as Utils.Lens (makeLenses_)
|
||||||
|
|
||||||
|
|||||||
@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
var scriptClone = document.createElement('script');
|
var scriptClone = document.createElement('script');
|
||||||
if (scriptTag.text)
|
if (scriptTag.text)
|
||||||
scriptClone.text = striptTag.text;
|
scriptClone.text = scriptTag.text;
|
||||||
if (scriptTag.hasAttributes()) {
|
if (scriptTag.hasAttributes()) {
|
||||||
var attrs = scriptTag.attributes;
|
var attrs = scriptTag.attributes;
|
||||||
for (var i = attrs.length - 1; i >= 0; i--) {
|
for (var i = attrs.length - 1; i >= 0; i--) {
|
||||||
|
|||||||
6
templates/table/layout-filter-default.hamlet
Normal file
6
templates/table/layout-filter-default.hamlet
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
$newline never
|
||||||
|
<section>
|
||||||
|
<form method=GET action=#{filterAction} enctype=#{filterEnctype}>
|
||||||
|
^{filterWgdt}
|
||||||
|
<section>
|
||||||
|
^{scrolltable}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
$newline never
|
$newline never
|
||||||
#{fragment}
|
#{fragment}
|
||||||
$case formLayout
|
$case formLayout
|
||||||
$of FormStandard
|
$of _
|
||||||
$forall view <- views
|
$forall view <- views
|
||||||
$# TODO: add class 'form-group--submit' if this is the submit-button view
|
$# TODO: add class 'form-group--submit' if this is the submit-button view
|
||||||
<div .form-group :fvRequired view:.form-group--required :not $ fvRequired view:.form-group--optional :isJust $ fvErrors view:.form-group--has-error>
|
<div .form-group :fvRequired view:.form-group--required :not $ fvRequired view:.form-group--optional :isJust $ fvErrors view:.form-group--has-error>
|
||||||
|
|||||||
@ -24,6 +24,9 @@ import qualified Data.ByteString as BS
|
|||||||
|
|
||||||
import Data.Time
|
import Data.Time
|
||||||
|
|
||||||
|
import Utils.Lens (review)
|
||||||
|
import Control.Monad.Random.Class (MonadRandom(..))
|
||||||
|
|
||||||
|
|
||||||
data DBAction = DBClear
|
data DBAction = DBClear
|
||||||
| DBTruncate
|
| DBTruncate
|
||||||
@ -151,7 +154,7 @@ fillDb = do
|
|||||||
, userMailLanguages = MailLanguages ["de"]
|
, userMailLanguages = MailLanguages ["de"]
|
||||||
, userNotificationSettings = def
|
, userNotificationSettings = def
|
||||||
}
|
}
|
||||||
void . insert $ User
|
tinaTester <- insert $ User
|
||||||
{ userIdent = "tester@campus.lmu.de"
|
{ userIdent = "tester@campus.lmu.de"
|
||||||
, userAuthentication = AuthLDAP
|
, userAuthentication = AuthLDAP
|
||||||
, userMatrikelnummer = Just "999"
|
, userMatrikelnummer = Just "999"
|
||||||
@ -312,6 +315,7 @@ fillDb = do
|
|||||||
insert_ $ CourseEdit jost now pmo
|
insert_ $ CourseEdit jost now pmo
|
||||||
void . insert $ DegreeCourse pmo sdBsc sdInf
|
void . insert $ DegreeCourse pmo sdBsc sdInf
|
||||||
void . insert $ Lecturer jost pmo
|
void . insert $ Lecturer jost pmo
|
||||||
|
void . insertMany $ map (\u -> CourseParticipant pmo u now) [fhamann, maxMuster, tinaTester]
|
||||||
sh1 <- insert Sheet
|
sh1 <- insert Sheet
|
||||||
{ sheetCourse = pmo
|
{ sheetCourse = pmo
|
||||||
, sheetName = "Blatt 1"
|
, sheetName = "Blatt 1"
|
||||||
@ -328,6 +332,10 @@ fillDb = do
|
|||||||
, sheetSolutionFrom = Nothing
|
, sheetSolutionFrom = Nothing
|
||||||
}
|
}
|
||||||
void . insert $ SheetEdit jost now sh1
|
void . insert $ SheetEdit jost now sh1
|
||||||
|
forM_ [fhamann, maxMuster, tinaTester] $ \u -> do
|
||||||
|
p <- liftIO getRandom
|
||||||
|
$logDebug (review _PseudonymText p)
|
||||||
|
void . insert $ SheetPseudonym sh1 p u
|
||||||
void . insert $ SheetCorrector jost sh1 (Load (Just True) 0) CorrectorNormal
|
void . insert $ SheetCorrector jost sh1 (Load (Just True) 0) CorrectorNormal
|
||||||
void . insert $ SheetCorrector gkleen sh1 (Load (Just True) 1) CorrectorNormal
|
void . insert $ SheetCorrector gkleen sh1 (Load (Just True) 1) CorrectorNormal
|
||||||
h102 <- insertFile "H10-2.hs"
|
h102 <- insertFile "H10-2.hs"
|
||||||
|
|||||||
Reference in New Issue
Block a user