Merge branch 'master' of gitlab.cip.ifi.lmu.de:jost/UniWorX

This commit is contained in:
Gregor Kleen 2018-10-24 18:01:23 +02:00
commit c24370b7cc
18 changed files with 197 additions and 31 deletions

6
.vscode/tasks.json vendored
View File

@ -6,7 +6,11 @@
{ {
"label": "echo", "label": "echo",
"type": "shell", "type": "shell",
"command": "echo Hello" "command": "echo Hello",
"group": {
"kind": "build",
"isDefault": true
}
} }
] ]
} }

View File

@ -83,6 +83,10 @@ The following Description applies to Ubuntu or similar.
ldap: https://wiki.ubuntuusers.de/OpenLDAP_ab_Precise/ ldap: https://wiki.ubuntuusers.de/OpenLDAP_ab_Precise/
Instead of run.sh, use:
stack build --flag uniworx:dev --flag uniworx:library-only
*** ***
# PostgreSQL # PostgreSQL

View File

@ -24,6 +24,8 @@ job-flush-interval: "_env:JOB_FLUSH:30"
job-cron-interval: "_env:CRON_INTERVAL:60" job-cron-interval: "_env:CRON_INTERVAL:60"
job-stale-threshold: 300 job-stale-threshold: 300
notification-rate-limit: 3600 notification-rate-limit: 3600
notification-collate-delay: 300
notification-expiration: 259201
log-settings: log-settings:
log-detailed: "_env:DETAILED_LOGGING:false" log-detailed: "_env:DETAILED_LOGGING:false"

View File

@ -16,7 +16,7 @@ WinterTerm year@Integer: Wintersemester #{display year}/#{display $ succ year}
SummerTermShort year@Integer: SoSe #{display year} SummerTermShort year@Integer: SoSe #{display year}
WinterTermShort year@Integer: WiSe #{display year}/#{display $ mod (succ year) 100} WinterTermShort year@Integer: WiSe #{display year}/#{display $ mod (succ year) 100}
PSLimitNonPositive: “pagesize” muss größer als null sein PSLimitNonPositive: “pagesize” muss größer als null sein
Page n@Int64: #{display n} Page num@Int64: #{display num}
TermsHeading: Semesterübersicht TermsHeading: Semesterübersicht
TermCurrent: Aktuelles Semester TermCurrent: Aktuelles Semester
@ -131,7 +131,7 @@ SubmissionWrongSheet: Abgabenummer gehört nicht zum angegebenen Übungsblatt.
SubmissionAlreadyExists: Sie haben bereits eine Abgabe zu diesem Übungsblatt. SubmissionAlreadyExists: Sie haben bereits eine Abgabe zu diesem Übungsblatt.
SubmissionEditHead tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: #{display tid}-#{display ssh}-#{csh} #{sheetName}: Abgabe editieren/anlegen SubmissionEditHead tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: #{display tid}-#{display ssh}-#{csh} #{sheetName}: Abgabe editieren/anlegen
CorrectionHead tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName cid@CryptoFileNameSubmission: #{display tid}-#{display ssh}-#{csh} #{sheetName}: Korrektur CorrectionHead tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName cid@CryptoFileNameSubmission: #{display tid}-#{display ssh}-#{csh} #{sheetName}: Korrektur
SubmissionMember g@Int: Mitabgebende(r) ##{display g} SubmissionMember n@Int: Mitabgebende(r) ##{display n}
SubmissionArchive: Zip-Archiv der Abgabedatei(en) SubmissionArchive: Zip-Archiv der Abgabedatei(en)
SubmissionFile: Datei zur Abgabe SubmissionFile: Datei zur Abgabe
SubmissionFiles: Abgegebene Dateien SubmissionFiles: Abgegebene Dateien
@ -203,7 +203,7 @@ ImpressumHeading: Impressum
SystemMessageHeading: Uni2Work Statusmeldung SystemMessageHeading: Uni2Work Statusmeldung
SystemMessageListHeading: Uni2Work Statusmeldungen SystemMessageListHeading: Uni2Work Statusmeldungen
NumCourses n@Int64: #{display n} Kurse NumCourses num@Int64: #{display num} Kurse
CloseAlert: Schliessen CloseAlert: Schliessen
Name: Name Name: Name
@ -343,8 +343,11 @@ MailSubmissionRatedIntro courseName@Text termDesc@Text: Ihre Abgabe im Kurs #{co
MailSubjectSheetActive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} wurde herausgegeben MailSubjectSheetActive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} wurde herausgegeben
MailSheetActiveIntro courseName@Text termDesc@Text sheetName@SheetName: Sie können nun #{sheetName} im Kurs #{courseName} (#{termDesc}) herunterladen. MailSheetActiveIntro courseName@Text termDesc@Text sheetName@SheetName: Sie können nun #{sheetName} im Kurs #{courseName} (#{termDesc}) herunterladen.
MailSubjectSheetInactive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} kann nur noch kurze Zeit abgegeben werden MailSubjectSheetSoonInactive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} kann nur noch kurze Zeit abgegeben werden
MailSheetInactiveIntro courseName@Text termDesc@Text sheetName@SheetName: Dia Abgabefirst für #{sheetName} im Kurs #{courseName} (#{termDesc}) endet in Kürze. MailSheetSoonInactiveIntro courseName@Text termDesc@Text sheetName@SheetName: Abgabefirst für #{sheetName} im Kurs #{courseName} (#{termDesc}) endet in Kürze.
MailSubjectSheetInactive csh@CourseShorthand sheetName@SheetName: Abgabfristt für #{sheetName} in #{csh} abgelaufen
MailSheetInactiveIntro courseName@Text termDesc@Text sheetName@SheetName: Die Abgabefirst für #{sheetName} im Kurs #{courseName} (#{termDesc}) beendet.
MailCorrectionsAssignedIntro courseName@Text termDesc@Text sheetName@SheetName n@Int: #{display n} Abgaben wurden Ihnen zur Korrektur für #{sheetName} im Kurs #{courseName} (#{termDesc}) zugeteilt.
MailSubjectSupport: Supportanfrage MailSubjectSupport: Supportanfrage
@ -371,7 +374,9 @@ SheetFiles: Übungsblatt-Dateien
NotificationTriggerSubmissionRatedGraded: Meine Abgabe in einem gewerteten Übungsblatt wurde korrigiert NotificationTriggerSubmissionRatedGraded: Meine Abgabe in einem gewerteten Übungsblatt wurde korrigiert
NotificationTriggerSubmissionRated: Meine Abgabe wurde korrigiert NotificationTriggerSubmissionRated: Meine Abgabe wurde korrigiert
NotificationTriggerSheetActive: Ich kann ein neues Übungsblatt herunterladen NotificationTriggerSheetActive: Ich kann ein neues Übungsblatt herunterladen
NotificationTriggerSheetInactive: Ich kann ein Übungsblatt bald nicht mehr abgeben NotificationTriggerSheetSoonInactive: Ich kann ein Übungsblatt bald nicht mehr abgeben
NotificationTriggerSheetInactive: Abgabefrist eines meiner Übungsblätter ist abgelaufen
NotificationCorrectionsAssigned: Mir wurden Abgaben zur Korrektur zugeteilt
CorrCreate: Abgaben erstellen CorrCreate: Abgaben erstellen
UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}" UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}"
@ -399,6 +404,7 @@ HelpEMail: E-Mail
HelpRequest: Supportanfrage / Verbesserungsvorschlag HelpRequest: Supportanfrage / Verbesserungsvorschlag
HelpProblemPage: Problematische Seite HelpProblemPage: Problematische Seite
HelpIntroduction: Wenn Ihnen die Benutzung dieser Webseite Schwierigkeiten bereitet oder Sie einen verbesserbaren Umstand entdecken bitten wir Sie uns das zu melden, auch wenn Sie Ihr Problem bereits selbst lösen konnten. Wir passen die Seite ständig an und versuchen sie auch für zukünftige Benutzer so einsichtig wie möglich zu halten. HelpIntroduction: Wenn Ihnen die Benutzung dieser Webseite Schwierigkeiten bereitet oder Sie einen verbesserbaren Umstand entdecken bitten wir Sie uns das zu melden, auch wenn Sie Ihr Problem bereits selbst lösen konnten. Wir passen die Seite ständig an und versuchen sie auch für zukünftige Benutzer so einsichtig wie möglich zu halten.
HelpSent: Ihre Supportanfrage wurde weitergeleitet.
SystemMessageFrom: Sichtbar ab SystemMessageFrom: Sichtbar ab
SystemMessageTo: Sichtbar bis SystemMessageTo: Sichtbar bis

View File

@ -255,8 +255,9 @@ instance RenderMessage UniWorX NotificationTrigger where
NTSubmissionRatedGraded -> MsgNotificationTriggerSubmissionRatedGraded NTSubmissionRatedGraded -> MsgNotificationTriggerSubmissionRatedGraded
NTSubmissionRated -> MsgNotificationTriggerSubmissionRated NTSubmissionRated -> MsgNotificationTriggerSubmissionRated
NTSheetActive -> MsgNotificationTriggerSheetActive NTSheetActive -> MsgNotificationTriggerSheetActive
NTSheetSoonInactive -> MsgNotificationTriggerSheetSoonInactive
NTSheetInactive -> MsgNotificationTriggerSheetInactive NTSheetInactive -> MsgNotificationTriggerSheetInactive
NTCorrectionsAssigned -> MsgNotificationCorrectionsAssigned
instance RenderMessage UniWorX (UnsupportedAuthPredicate (Route UniWorX)) where instance RenderMessage UniWorX (UnsupportedAuthPredicate (Route UniWorX)) where
renderMessage f ls (UnsupportedAuthPredicate tag route) = renderMessage f ls $ MsgUnsupportedAuthPredicate tag (show route) renderMessage f ls (UnsupportedAuthPredicate tag route) = renderMessage f ls $ MsgUnsupportedAuthPredicate tag (show route)

View File

@ -297,7 +297,9 @@ postHelpR = do
, jHelpRequest = hfRequest , jHelpRequest = hfRequest
, jRequestTime = now , jRequestTime = now
, jReferer = hfReferer } , jReferer = hfReferer }
redirect $ HelpR -- redirect $ HelpR
addMessageI Success MsgHelpSent
return ()
{-selectRep $ do {-selectRep $ do
provideJson () provideJson ()
provideRep (redirect $ HelpR :: Handler Html) -} provideRep (redirect $ HelpR :: Handler Html) -}

View File

@ -86,7 +86,6 @@ data SheetForm = SheetForm
, sfDescription :: Maybe Html , sfDescription :: Maybe Html
, sfType :: SheetType , sfType :: SheetType
, sfGrouping :: SheetGroup , sfGrouping :: SheetGroup
, sfMarkingText :: Maybe Html
, sfVisibleFrom :: Maybe UTCTime , sfVisibleFrom :: Maybe UTCTime
, sfActiveFrom :: UTCTime , sfActiveFrom :: UTCTime
, sfActiveTo :: UTCTime , sfActiveTo :: UTCTime
@ -98,6 +97,7 @@ data SheetForm = SheetForm
, sfSolutionFrom :: Maybe UTCTime , sfSolutionFrom :: Maybe UTCTime
, sfSolutionF :: Maybe (Source Handler (Either FileId File)) , sfSolutionF :: Maybe (Source Handler (Either FileId File))
, sfMarkingF :: Maybe (Source Handler (Either FileId File)) , sfMarkingF :: Maybe (Source Handler (Either FileId File))
, sfMarkingText :: Maybe Html
-- Keine SheetId im Formular! -- Keine SheetId im Formular!
} }
@ -121,7 +121,6 @@ makeSheetForm msId template = identForm FIDsheet $ \html -> do
<*> aopt htmlField (fslI MsgSheetDescription) (sfDescription <$> template) <*> aopt htmlField (fslI MsgSheetDescription) (sfDescription <$> template)
<*> sheetTypeAFormReq (fslI MsgSheetType) (sfType <$> template) <*> sheetTypeAFormReq (fslI MsgSheetType) (sfType <$> template)
<*> sheetGroupAFormReq (fslI MsgSheetGroup) (sfGrouping <$> template) <*> sheetGroupAFormReq (fslI MsgSheetGroup) (sfGrouping <$> template)
<*> aopt htmlField (fslI MsgSheetMarking) (sfMarkingText <$> template)
<*> aopt utcTimeField (fslI MsgSheetVisibleFrom <*> aopt utcTimeField (fslI MsgSheetVisibleFrom
& setTooltip MsgSheetVisibleFromTip) & setTooltip MsgSheetVisibleFromTip)
((sfVisibleFrom <$> template) <|> pure (Just ctime)) ((sfVisibleFrom <$> template) <|> pure (Just ctime))
@ -141,6 +140,7 @@ makeSheetForm msId template = identForm FIDsheet $ \html -> do
<*> aopt (multiFileField $ oldFileIds SheetSolution) (fslI MsgSheetSolution) (sfSolutionF <$> template) <*> aopt (multiFileField $ oldFileIds SheetSolution) (fslI MsgSheetSolution) (sfSolutionF <$> template)
<*> aopt (multiFileField $ oldFileIds SheetMarking) (fslI MsgSheetMarking <*> aopt (multiFileField $ oldFileIds SheetMarking) (fslI MsgSheetMarking
& setTooltip MsgSheetMarkingTip) (sfMarkingF <$> template) & setTooltip MsgSheetMarkingTip) (sfMarkingF <$> template)
<*> aopt htmlField (fslI MsgSheetMarking) (sfMarkingText <$> template)
<* submitButton <* submitButton
return $ case result of return $ case result of
FormSuccess sheetResult FormSuccess sheetResult
@ -420,7 +420,6 @@ getSheetNewR tid ssh csh = do
, sfDescription = sheetDescription , sfDescription = sheetDescription
, sfType = sheetType , sfType = sheetType
, sfGrouping = sheetGrouping , sfGrouping = sheetGrouping
, sfMarkingText = sheetMarkingText
, sfVisibleFrom = addOneWeek <$> sheetVisibleFrom , sfVisibleFrom = addOneWeek <$> sheetVisibleFrom
, sfActiveFrom = addOneWeek sheetActiveFrom , sfActiveFrom = addOneWeek sheetActiveFrom
, sfActiveTo = addOneWeek sheetActiveTo , sfActiveTo = addOneWeek sheetActiveTo
@ -432,6 +431,7 @@ getSheetNewR tid ssh csh = do
, sfSolutionFrom = addOneWeek <$> sheetSolutionFrom , sfSolutionFrom = addOneWeek <$> sheetSolutionFrom
, sfSolutionF = Nothing , sfSolutionF = Nothing
, sfMarkingF = Nothing , sfMarkingF = Nothing
, sfMarkingText = sheetMarkingText
} }
_other -> Nothing _other -> Nothing
let action newSheet = -- More specific error message for new sheet could go here, if insertUnique returns Nothing let action newSheet = -- More specific error message for new sheet could go here, if insertUnique returns Nothing
@ -455,7 +455,6 @@ getSEditR tid ssh csh shn = do
, sfDescription = sheetDescription , sfDescription = sheetDescription
, sfType = sheetType , sfType = sheetType
, sfGrouping = sheetGrouping , sfGrouping = sheetGrouping
, sfMarkingText = sheetMarkingText
, sfVisibleFrom = sheetVisibleFrom , sfVisibleFrom = sheetVisibleFrom
, sfActiveFrom = sheetActiveFrom , sfActiveFrom = sheetActiveFrom
, sfActiveTo = sheetActiveTo , sfActiveTo = sheetActiveTo
@ -467,6 +466,7 @@ getSEditR tid ssh csh shn = do
, sfSolutionFrom = sheetSolutionFrom , sfSolutionFrom = sheetSolutionFrom
, sfSolutionF = Just . yieldMany . map Left . Set.elems $ sheetFileIds SheetSolution , sfSolutionF = Just . yieldMany . map Left . Set.elems $ sheetFileIds SheetSolution
, sfMarkingF = Just . yieldMany . map Left . Set.elems $ sheetFileIds SheetMarking , sfMarkingF = Just . yieldMany . map Left . Set.elems $ sheetFileIds SheetMarking
, sfMarkingText = sheetMarkingText
} }
let action newSheet = do let action newSheet = do
replaceRes <- myReplaceUnique sid $ newSheet replaceRes <- myReplaceUnique sid $ newSheet

View File

@ -1,6 +1,9 @@
{-# LANGUAGE NoImplicitPrelude {-# LANGUAGE NoImplicitPrelude
, RecordWildCards , RecordWildCards
, FlexibleContexts , FlexibleContexts
, MultiWayIf
, NamedFieldPuns
, TypeFamilies
#-} #-}
module Jobs.Crontab module Jobs.Crontab
@ -12,6 +15,10 @@ import Import
import qualified Data.HashMap.Strict as HashMap import qualified Data.HashMap.Strict as HashMap
import Jobs.Types import Jobs.Types
import Data.Maybe (fromJust)
import qualified Data.Map as Map
import Data.Semigroup (Max(..))
import Data.Time import Data.Time
import Data.Time.Zones import Data.Time.Zones
@ -57,11 +64,43 @@ determineCrontab = execWriterT $ do
, cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ sheetActiveTo , cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ sheetActiveTo
} }
tell $ HashMap.singleton tell $ HashMap.singleton
(JobCtlQueue $ JobQueueNotification NotificationSheetInactive{..}) (JobCtlQueue $ JobQueueNotification NotificationSheetSoonInactive{..})
Cron Cron
{ cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . max sheetActiveFrom $ addUTCTime (-nominalDay) sheetActiveTo { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . max sheetActiveFrom $ addUTCTime (-nominalDay) sheetActiveTo
, cronRepeat = CronRepeatOnChange -- Allow repetition of the notification (if something changes), but wait at least an hour , cronRepeat = CronRepeatOnChange -- Allow repetition of the notification (if something changes), but wait at least an hour
, cronRateLimit = appNotificationRateLimit , cronRateLimit = appNotificationRateLimit
, cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ sheetActiveTo , cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ sheetActiveTo
} }
tell $ HashMap.singleton
(JobCtlQueue $ JobQueueNotification NotificationSheetInactive{..})
Cron
{ cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ sheetActiveTo
, cronRepeat = CronRepeatOnChange
, cronRateLimit = appNotificationRateLimit
, cronNotAfter = Left appNotificationExpiration
}
sheetSubmissions <- lift $ collateSubmissions <$>
selectList [SubmissionRatingBy !=. Nothing, SubmissionSheet ==. nSheet] []
tell $ flip Map.foldMapWithKey sheetSubmissions $
\nUser (Max mbTime) -> if
| Just time <- mbTime -> HashMap.singleton
(JobCtlQueue $ JobQueueNotification NotificationCorrectionsAssigned { nUser, nSheet } )
Cron
{ cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ $ addUTCTime appNotificationCollateDelay time
, cronRepeat = CronRepeatNever
, cronRateLimit = appNotificationRateLimit
, cronNotAfter = Left appNotificationExpiration
}
| otherwise -> mempty
runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ sheetJobs runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ sheetJobs
-- | Partial function: Submission must not have Nothing at ratingBy
collateSubmissions :: [Entity Submission] -> Map UserId (Max (Maybe UTCTime))
collateSubmissions = Map.fromListWith (<>) . fmap procCorrector
where
procCorrector :: Entity Submission -> (UserId , (Max (Maybe UTCTime)))
procCorrector = (,) <$> fromJust . submissionRatingBy . entityVal
<*> Max . submissionRatingAssigned . entityVal

View File

@ -36,11 +36,17 @@ determineNotificationCandidates NotificationSheetActive{..} = E.select . E.from
E.on $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUser E.on $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUser
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
return user return user
determineNotificationCandidates NotificationSheetInactive{..} = E.select . E.from $ \(user `E.InnerJoin` courseParticipant `E.InnerJoin` sheet) -> do determineNotificationCandidates NotificationSheetSoonInactive{..} = E.select . E.from $ \(user `E.InnerJoin` courseParticipant `E.InnerJoin` sheet) -> do
E.on $ sheet E.^. SheetCourse E.==. courseParticipant E.^. CourseParticipantCourse E.on $ sheet E.^. SheetCourse E.==. courseParticipant E.^. CourseParticipantCourse
E.on $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUser E.on $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUser
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
return user return user
determineNotificationCandidates NotificationSheetInactive{..} = E.select . E.from $ \(user `E.InnerJoin` lecturer `E.InnerJoin` sheet) -> do
E.on $ lecturer E.^. LecturerCourse E.==. sheet E.^. SheetCourse
E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId
E.where_ $ sheet E.^. SheetId E.==. E.val nSheet
return user
determineNotificationCandidates NotificationCorrectionsAssigned{..} = selectList [UserId ==. nUser] []
classifyNotification :: Notification -> DB NotificationTrigger classifyNotification :: Notification -> DB NotificationTrigger
classifyNotification NotificationSubmissionRated{..} = do classifyNotification NotificationSubmissionRated{..} = do
@ -49,6 +55,8 @@ classifyNotification NotificationSubmissionRated{..} = do
NotGraded -> NTSubmissionRated NotGraded -> NTSubmissionRated
_other -> NTSubmissionRatedGraded _other -> NTSubmissionRatedGraded
classifyNotification NotificationSheetActive{} = return NTSheetActive classifyNotification NotificationSheetActive{} = return NTSheetActive
classifyNotification NotificationSheetSoonInactive{} = return NTSheetSoonInactive
classifyNotification NotificationSheetInactive{} = return NTSheetInactive classifyNotification NotificationSheetInactive{} = return NTSheetInactive
classifyNotification NotificationCorrectionsAssigned{} = return NTCorrectionsAssigned

View File

@ -15,6 +15,7 @@ import Jobs.Types
import Jobs.Handler.SendNotification.SubmissionRated import Jobs.Handler.SendNotification.SubmissionRated
import Jobs.Handler.SendNotification.SheetActive import Jobs.Handler.SendNotification.SheetActive
import Jobs.Handler.SendNotification.SheetInactive import Jobs.Handler.SendNotification.SheetInactive
import Jobs.Handler.SendNotification.CorrectionsAssigned
dispatchJobSendNotification :: UserId -> Notification -> Handler () dispatchJobSendNotification :: UserId -> Notification -> Handler ()

View File

@ -0,0 +1,41 @@
{-# LANGUAGE NoImplicitPrelude
, RecordWildCards
, NamedFieldPuns
, TemplateHaskell
, OverloadedStrings
#-}
module Jobs.Handler.SendNotification.CorrectionsAssigned
( dispatchNotificationCorrectionsAssigned
) where
import Import
import Utils.Lens
import Handler.Utils.Mail
import Text.Hamlet
import qualified Data.CaseInsensitive as CI
dispatchNotificationCorrectionsAssigned :: UserId -> SheetId -> UserId -> Handler ()
dispatchNotificationCorrectionsAssigned nUser nSheet jRecipient = do
(Course{..}, Sheet{..}, nbrSubs) <- liftHandlerT . runDB $ do
sheet <- getJust nSheet
course <- belongsToJust sheetCourse sheet
nbrSubs <- count [ SubmissionSheet ==. nSheet
, SubmissionRatingBy ==. Just nUser
, SubmissionRatingTime ==. Nothing
]
return (course, sheet, nbrSubs)
when (nbrSubs > 0) . userMailT jRecipient $ do
setSubjectI $ MsgMailSubjectSheetActive courseShorthand sheetName
MsgRenderer mr <- getMailMsgRenderer
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
tid = courseTerm
ssh = courseSchool
csh = courseShorthand
shn = sheetName
addAlternatives $ do
providePreferredAlternative ($(ihamletFile "templates/mail/correctionsAssigned.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))

View File

@ -6,7 +6,8 @@
#-} #-}
module Jobs.Handler.SendNotification.SheetInactive module Jobs.Handler.SendNotification.SheetInactive
( dispatchNotificationSheetInactive ( dispatchNotificationSheetSoonInactive
, dispatchNotificationSheetInactive
) where ) where
import Import import Import
@ -17,6 +18,24 @@ import Handler.Utils.Mail
import Text.Hamlet import Text.Hamlet
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
dispatchNotificationSheetSoonInactive :: SheetId -> UserId -> Handler ()
dispatchNotificationSheetSoonInactive nSheet jRecipient = userMailT jRecipient $ do
(Course{..}, Sheet{..}) <- liftHandlerT . runDB $ do
sheet <- getJust nSheet
course <- belongsToJust sheetCourse sheet
return (course, sheet)
setSubjectI $ MsgMailSubjectSheetSoonInactive courseShorthand sheetName
MsgRenderer mr <- getMailMsgRenderer
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
tid = courseTerm
ssh = courseSchool
csh = courseShorthand
shn = sheetName
addAlternatives $ do
providePreferredAlternative ($(ihamletFile "templates/mail/sheetSoonInactive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))
dispatchNotificationSheetInactive :: SheetId -> UserId -> Handler () dispatchNotificationSheetInactive :: SheetId -> UserId -> Handler ()
dispatchNotificationSheetInactive nSheet jRecipient = userMailT jRecipient $ do dispatchNotificationSheetInactive nSheet jRecipient = userMailT jRecipient $ do
(Course{..}, Sheet{..}) <- liftHandlerT . runDB $ do (Course{..}, Sheet{..}) <- liftHandlerT . runDB $ do
@ -34,3 +53,4 @@ dispatchNotificationSheetInactive nSheet jRecipient = userMailT jRecipient $ do
addAlternatives $ do addAlternatives $ do
providePreferredAlternative ($(ihamletFile "templates/mail/sheetInactive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX)) providePreferredAlternative ($(ihamletFile "templates/mail/sheetInactive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))

View File

@ -28,7 +28,9 @@ data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notifica
deriving (Eq, Ord, Show, Read, Generic, Typeable) deriving (Eq, Ord, Show, Read, Generic, Typeable)
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId } data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
| NotificationSheetActive { nSheet :: SheetId } | NotificationSheetActive { nSheet :: SheetId }
| NotificationSheetSoonInactive { nSheet :: SheetId }
| NotificationSheetInactive { nSheet :: SheetId } | NotificationSheetInactive { nSheet :: SheetId }
| NotificationCorrectionsAssigned { nUser :: UserId, nSheet :: SheetId }
deriving (Eq, Ord, Show, Read, Generic, Typeable) deriving (Eq, Ord, Show, Read, Generic, Typeable)
instance Hashable Job instance Hashable Job

View File

@ -487,7 +487,9 @@ derivePersistFieldJSON ''Value
data NotificationTrigger = NTSubmissionRatedGraded data NotificationTrigger = NTSubmissionRatedGraded
| NTSubmissionRated | NTSubmissionRated
| NTSheetActive | NTSheetActive
| NTSheetSoonInactive
| NTSheetInactive | NTSheetInactive
| NTCorrectionsAssigned
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable) deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
instance Universe NotificationTrigger instance Universe NotificationTrigger
@ -515,7 +517,9 @@ instance Default NotificationSettings where
NTSubmissionRatedGraded -> True NTSubmissionRatedGraded -> True
NTSubmissionRated -> False NTSubmissionRated -> False
NTSheetActive -> True NTSheetActive -> True
NTSheetInactive -> False NTSheetSoonInactive -> False
NTSheetInactive -> True
NTCorrectionsAssigned -> True
instance ToJSON NotificationSettings where instance ToJSON NotificationSettings where
toJSON v = toJSON . HashMap.fromList $ map (id &&& notificationAllowed v) universeF toJSON v = toJSON . HashMap.fromList $ map (id &&& notificationAllowed v) universeF

View File

@ -89,6 +89,8 @@ data AppSettings = AppSettings
, appJobCronInterval :: NominalDiffTime , appJobCronInterval :: NominalDiffTime
, appJobStaleThreshold :: NominalDiffTime , appJobStaleThreshold :: NominalDiffTime
, appNotificationRateLimit :: NominalDiffTime , appNotificationRateLimit :: NominalDiffTime
, appNotificationCollateDelay :: NominalDiffTime
, appNotificationExpiration :: NominalDiffTime
, appInitialLogSettings :: LogSettings , appInitialLogSettings :: LogSettings
@ -293,6 +295,8 @@ instance FromJSON AppSettings where
appJobCronInterval <- o .: "job-cron-interval" appJobCronInterval <- o .: "job-cron-interval"
appJobStaleThreshold <- o .: "job-stale-threshold" appJobStaleThreshold <- o .: "job-stale-threshold"
appNotificationRateLimit <- o .: "notification-rate-limit" appNotificationRateLimit <- o .: "notification-rate-limit"
appNotificationCollateDelay <- o .: "notification-collate-delay"
appNotificationExpiration <- o .: "notification-expiration"
appReloadTemplates <- o .:? "reload-templates" .!= defaultDev appReloadTemplates <- o .:? "reload-templates" .!= defaultDev
appMutableStatic <- o .:? "mutable-static" .!= defaultDev appMutableStatic <- o .:? "mutable-static" .!= defaultDev

View File

@ -0,0 +1,17 @@
$newline never
\<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<style>
h1 {
font-size: 1.25em;
font-variant: small-caps;
font-weight: normal;
}
<body>
<h1>
_{MsgMailCorrectionsAssignedIntro (CI.original courseName) termDesc sheetName nbrSubs}
<p>
<a href=@{CorrectionsR}>
_{MsgCorrectionsTitle}

View File

@ -0,0 +1,17 @@
$newline never
\<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<style>
h1 {
font-size: 1.25em;
font-variant: small-caps;
font-weight: normal;
}
<body>
<h1>
_{MsgMailSheetSoonInactiveIntro (CI.original courseName) termDesc sheetName}
<p>
<a href=@{CSheetR tid ssh csh shn SShowR}>
#{sheetName}

View File

@ -5,12 +5,6 @@ $maybe descr <- sheetDescription sheet
<p> <p>
#{descr} #{descr}
$maybe marking <- sheetMarkingText sheet
<section>
<h2>_{MsgSheetMarking}
<p>
#{marking}
<section> <section>
<dl .deflist> <dl .deflist>
<dt .deflist__dt>_{MsgSheetActiveFrom} <dt .deflist__dt>_{MsgSheetActiveFrom}