Working CCommR
This commit is contained in:
parent
6f4b09bb0a
commit
8637847fc6
@ -516,6 +516,8 @@ MailEditNotifications: Benachrichtigungen ein-/ausschalten
|
|||||||
MailSubjectSupport: Supportanfrage
|
MailSubjectSupport: Supportanfrage
|
||||||
MailSubjectSupportCustom customSubject@Text: [Support] #{customSubject}
|
MailSubjectSupportCustom customSubject@Text: [Support] #{customSubject}
|
||||||
|
|
||||||
|
CommCourseSubject: Kursmitteilung
|
||||||
|
|
||||||
SheetGrading: Bewertung
|
SheetGrading: Bewertung
|
||||||
SheetGradingPoints maxPoints@Points: #{tshow maxPoints} Punkte
|
SheetGradingPoints maxPoints@Points: #{tshow maxPoints} Punkte
|
||||||
SheetGradingPassPoints maxPoints@Points passingPoints@Points: Bestanden ab #{tshow passingPoints} von #{tshow maxPoints} Punkten
|
SheetGradingPassPoints maxPoints@Points passingPoints@Points: Bestanden ab #{tshow passingPoints} von #{tshow maxPoints} Punkten
|
||||||
@ -670,6 +672,7 @@ MenuLogin: Login
|
|||||||
MenuLogout: Logout
|
MenuLogout: Logout
|
||||||
MenuCourseList: Kurse
|
MenuCourseList: Kurse
|
||||||
MenuCourseMembers: Kursteilnehmer
|
MenuCourseMembers: Kursteilnehmer
|
||||||
|
MenuCourseCommunication: Kursmitteilung
|
||||||
MenuTermShow: Semester
|
MenuTermShow: Semester
|
||||||
MenuSubmissionDelete: Abgabe löschen
|
MenuSubmissionDelete: Abgabe löschen
|
||||||
MenuUsers: Benutzer
|
MenuUsers: Benutzer
|
||||||
@ -740,7 +743,9 @@ NavigationFavourites: Favoriten
|
|||||||
CommSubject: Betreff
|
CommSubject: Betreff
|
||||||
CommBody: Nachricht
|
CommBody: Nachricht
|
||||||
CommRecipients: Empfänger
|
CommRecipients: Empfänger
|
||||||
|
CommRecipientsTip: Sie können die Liste von Empfängern beliebig bearbeiten, bevor Sie die Nachricht verschicken. Sie selbst erhalten immer eine Kopie der Nachricht.
|
||||||
CommRecipientsSelectBy: Auswahl nach
|
CommRecipientsSelectBy: Auswahl nach
|
||||||
|
CommRecipientsSelectByTip: Mögliche Empfänger sind in verschiedene Gruppen unterteilt; sie können sowohl ganze Empfängergruppen als auch einzelne Mitglieder von Empfängergruppen hinzufügen
|
||||||
CommDuplicateRecipients n@Int: #{tshow n} #{pluralDE n "doppelter" "doppelte"} Empfänger ignoriert
|
CommDuplicateRecipients n@Int: #{tshow n} #{pluralDE n "doppelter" "doppelte"} Empfänger ignoriert
|
||||||
CommSuccess n@Int: Nachricht wurde an #{tshow n} Empfänger versandt
|
CommSuccess n@Int: Nachricht wurde an #{tshow n} Empfänger versandt
|
||||||
|
|
||||||
@ -752,3 +757,6 @@ AddRecipientCustom: Weitere Empfänger
|
|||||||
RGCourseParticipants: Kursteilnehmer
|
RGCourseParticipants: Kursteilnehmer
|
||||||
RGCourseLecturers: Kursverwalter
|
RGCourseLecturers: Kursverwalter
|
||||||
RGCourseCorrectors: Korrektoren
|
RGCourseCorrectors: Korrektoren
|
||||||
|
|
||||||
|
MultiSelectFieldTip: Mehrfach-Auswahl ist möglich (Umschalt bzw. Strg)
|
||||||
|
MultiEmailFieldTip: Je nach Browser sind mehrere komma-separierte E-Mail-Addressen möglich
|
||||||
@ -1172,6 +1172,7 @@ instance YesodBreadcrumbs UniWorX where
|
|||||||
breadcrumb (CourseR tid ssh csh CCorrectionsR) = return ("Abgaben" , Just $ CourseR tid ssh csh CShowR)
|
breadcrumb (CourseR tid ssh csh CCorrectionsR) = return ("Abgaben" , Just $ CourseR tid ssh csh CShowR)
|
||||||
breadcrumb (CourseR tid ssh csh SheetListR) = return ("Übungen" , Just $ CourseR tid ssh csh CShowR)
|
breadcrumb (CourseR tid ssh csh SheetListR) = return ("Übungen" , Just $ CourseR tid ssh csh CShowR)
|
||||||
breadcrumb (CourseR tid ssh csh SheetNewR ) = return ("Neu", Just $ CourseR tid ssh csh SheetListR)
|
breadcrumb (CourseR tid ssh csh SheetNewR ) = return ("Neu", Just $ CourseR tid ssh csh SheetListR)
|
||||||
|
breadcrumb (CourseR tid ssh csh CCommR ) = return ("Kursmitteilung", Just $ CourseR tid ssh csh CShowR)
|
||||||
|
|
||||||
breadcrumb (CSheetR tid ssh csh shn SShowR) = return (CI.original shn, Just $ CourseR tid ssh csh SheetListR)
|
breadcrumb (CSheetR tid ssh csh shn SShowR) = return (CI.original shn, Just $ CourseR tid ssh csh SheetListR)
|
||||||
breadcrumb (CSheetR tid ssh csh shn SEditR) = return ("Edit", Just $ CSheetR tid ssh csh shn SShowR)
|
breadcrumb (CSheetR tid ssh csh shn SEditR) = return ("Edit", Just $ CSheetR tid ssh csh shn SShowR)
|
||||||
@ -1539,6 +1540,14 @@ pageActions (CourseR tid ssh csh CShowR) =
|
|||||||
, menuItemModal = False
|
, menuItemModal = False
|
||||||
, menuItemAccessCallback' = return True
|
, menuItemAccessCallback' = return True
|
||||||
}
|
}
|
||||||
|
, MenuItem
|
||||||
|
{ menuItemType = PageActionSecondary
|
||||||
|
, menuItemLabel = MsgMenuCourseCommunication
|
||||||
|
, menuItemIcon = Nothing
|
||||||
|
, menuItemRoute = SomeRoute $ CourseR tid ssh csh CCommR
|
||||||
|
, menuItemModal = False
|
||||||
|
, menuItemAccessCallback' = return True
|
||||||
|
}
|
||||||
, MenuItem
|
, MenuItem
|
||||||
{ menuItemType = PageActionSecondary
|
{ menuItemType = PageActionSecondary
|
||||||
, menuItemLabel = MsgMenuCourseEdit
|
, menuItemLabel = MsgMenuCourseEdit
|
||||||
@ -2227,11 +2236,15 @@ instance YesodMail UniWorX where
|
|||||||
pool <- maybe (throwM MailNotAvailable) return =<< getsYesod appSmtpPool
|
pool <- maybe (throwM MailNotAvailable) return =<< getsYesod appSmtpPool
|
||||||
withResource pool act
|
withResource pool act
|
||||||
mailT ctx mail = defMailT ctx $ do
|
mailT ctx mail = defMailT ctx $ do
|
||||||
void setMailObjectId
|
void setMailObjectIdRandom
|
||||||
setDateCurrent
|
setDateCurrent
|
||||||
replaceMailHeader "Sender" . Just . addressEmail =<< getsYesod (appMailFrom . appSettings)
|
replaceMailHeader "Sender" . Just . addressEmail =<< getsYesod (appMailFrom . appSettings)
|
||||||
|
|
||||||
mail <* setMailSmtpData
|
(mRes, smtpData) <- listen mail
|
||||||
|
unless (view _MailSmtpDataSet smtpData)
|
||||||
|
setMailSmtpData
|
||||||
|
|
||||||
|
return mRes
|
||||||
|
|
||||||
|
|
||||||
instance (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => MonadCrypto m where
|
instance (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => MonadCrypto m where
|
||||||
|
|||||||
@ -1049,12 +1049,23 @@ postCNotesR = error "CNotesR: Not implemented"
|
|||||||
getCCommR, postCCommR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
getCCommR, postCCommR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||||
getCCommR = postCCommR
|
getCCommR = postCCommR
|
||||||
postCCommR tid ssh csh = do
|
postCCommR tid ssh csh = do
|
||||||
cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh
|
jSender <- requireAuthId
|
||||||
|
cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh
|
||||||
|
|
||||||
commR CommunicationRoute
|
commR CommunicationRoute
|
||||||
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommCourseHeading
|
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommCourseHeading
|
||||||
, crUltDest = SomeRoute $ CourseR tid ssh csh CCommR
|
, crUltDest = SomeRoute $ CourseR tid ssh csh CCommR
|
||||||
, crJob = error "job undefined"
|
, crJobs = \Communication{..} -> do
|
||||||
|
let jSubject = cSubject
|
||||||
|
jMailContent = cBody
|
||||||
|
jCourse = cid
|
||||||
|
allRecipients = Set.toList $ Set.insert (Right jSender) cRecipients
|
||||||
|
jMailObjectUUID <- liftIO getRandom
|
||||||
|
jAllRecipientAddresses <- lift . fmap Set.fromList . forM allRecipients $ \case
|
||||||
|
Left email -> return . Address Nothing $ CI.original email
|
||||||
|
Right rid -> userAddress <$> getJust rid
|
||||||
|
forM_ allRecipients $ \jRecipientEmail ->
|
||||||
|
yield JobSendCourseCommunication{..}
|
||||||
, crRecipients = Map.fromList
|
, crRecipients = Map.fromList
|
||||||
[ ( RGCourseParticipants
|
[ ( RGCourseParticipants
|
||||||
, E.from $ \(user `E.InnerJoin` participant) -> do
|
, E.from $ \(user `E.InnerJoin` participant) -> do
|
||||||
|
|||||||
@ -53,8 +53,8 @@ postHelpR = do
|
|||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
hfReferer' <- traverse toTextUrl hfReferer
|
hfReferer' <- traverse toTextUrl hfReferer
|
||||||
queueJob' JobHelpRequest
|
queueJob' JobHelpRequest
|
||||||
{ jSender = hfUserId
|
{ jHelpSender = hfUserId
|
||||||
, jHelpSubject = hfSubject
|
, jSubject = hfSubject
|
||||||
, jHelpRequest = hfRequest
|
, jHelpRequest = hfRequest
|
||||||
, jRequestTime = now
|
, jRequestTime = now
|
||||||
, jReferer = hfReferer'
|
, jReferer = hfReferer'
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
module Handler.Utils.Communication where
|
module Handler.Utils.Communication
|
||||||
|
( RecipientGroup(..)
|
||||||
|
, CommunicationRoute(..)
|
||||||
|
, Communication(..)
|
||||||
|
, commR
|
||||||
|
-- * Re-Exports
|
||||||
|
, Job(..)
|
||||||
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
@ -56,23 +63,32 @@ instance RenderMessage UniWorX RecipientAddOption where
|
|||||||
|
|
||||||
data CommunicationRoute = CommunicationRoute
|
data CommunicationRoute = CommunicationRoute
|
||||||
{ crRecipients :: Map RecipientGroup (E.SqlQuery (E.SqlExpr (Entity User)))
|
{ crRecipients :: Map RecipientGroup (E.SqlQuery (E.SqlExpr (Entity User)))
|
||||||
, crJob :: Communication -> DB Job
|
, crJobs :: Communication -> Source (YesodDB UniWorX) Job
|
||||||
, crHeading :: SomeMessage UniWorX
|
, crHeading :: SomeMessage UniWorX
|
||||||
, crUltDest :: SomeRoute UniWorX
|
, crUltDest :: SomeRoute UniWorX
|
||||||
}
|
}
|
||||||
-- `Communication` is defined in `Jobs.Types`
|
|
||||||
|
data Communication = Communication
|
||||||
|
{ cRecipients :: Set (Either UserEmail UserId)
|
||||||
|
, cSubject :: Maybe Text
|
||||||
|
, cBody :: Html
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
commR :: CommunicationRoute -> Handler Html
|
commR :: CommunicationRoute -> Handler Html
|
||||||
commR CommunicationRoute{..} = do
|
commR CommunicationRoute{..} = do
|
||||||
|
uid <- maybeAuthId
|
||||||
|
|
||||||
MsgRenderer mr <- getMsgRenderer
|
MsgRenderer mr <- getMsgRenderer
|
||||||
mbCurrentRoute <- getCurrentRoute
|
mbCurrentRoute <- getCurrentRoute
|
||||||
|
|
||||||
suggestedRecipients' <- runDB $ traverse E.select crRecipients
|
suggestedRecipients' <- runDB $ traverse E.select crRecipients
|
||||||
suggestedRecipients <- forM suggestedRecipients' . mapM $ \ent@(Entity rid _) -> (,) <$> (encrypt rid :: Handler CryptoUUIDUser) <*> pure ent
|
suggestedRecipients <- forM suggestedRecipients' . mapM $ \ent@(Entity rid _) -> (,) <$> (encrypt rid :: Handler CryptoUUIDUser) <*> pure ent
|
||||||
|
|
||||||
|
chosenRecipients <- fmap (maybe id cons uid) $ mapM (decrypt :: CryptoUUIDUser -> Handler UserId) =<< lookupGlobalGetParams GetRecipient
|
||||||
|
|
||||||
let recipientAForm :: AForm Handler (Set (Either UserEmail UserId))
|
let recipientAForm :: AForm Handler (Set (Either UserEmail UserId))
|
||||||
recipientAForm = postProcess <$> massInputA MassInput{..} (fslI MsgCommRecipients) True (Nothing {- TODO -})
|
recipientAForm = postProcess <$> massInputA MassInput{..} (fslI MsgCommRecipients & setTooltip MsgCommRecipientsTip) True (Just . Map.fromList . zip [0..] $ map ((, ()) . Right) chosenRecipients)
|
||||||
where
|
where
|
||||||
miAdd _ _ nudge submitView = Just $ \csrf -> do
|
miAdd _ _ nudge submitView = Just $ \csrf -> do
|
||||||
let addOptions :: Map RecipientAddOption (AForm Handler (Set (Either UserEmail UserId)))
|
let addOptions :: Map RecipientAddOption (AForm Handler (Set (Either UserEmail UserId)))
|
||||||
@ -80,19 +96,20 @@ commR CommunicationRoute{..} = do
|
|||||||
[ pure ( AddRecipientGroups
|
[ pure ( AddRecipientGroups
|
||||||
, Set.unions <$> apreq (multiSelectField . return $ mkOptionList
|
, Set.unions <$> apreq (multiSelectField . return $ mkOptionList
|
||||||
[ Option (mr g) (Set.fromList $ map (Right . entityKey . snd) recs) (toPathPiece g) | (g,recs) <- Map.toList suggestedRecipients ]
|
[ Option (mr g) (Set.fromList $ map (Right . entityKey . snd) recs) (toPathPiece g) | (g,recs) <- Map.toList suggestedRecipients ]
|
||||||
) (fslI AddRecipientGroups & addName (nudge . toPathPiece $ AddRecipientGroups)) Nothing
|
) (fslI AddRecipientGroups & addName (nudge . toPathPiece $ AddRecipientGroups) & setTooltip MsgMultiSelectFieldTip) Nothing
|
||||||
)
|
)
|
||||||
, do
|
, do
|
||||||
(g,recs) <- Map.toList suggestedRecipients
|
(g, recs) <- Map.toList suggestedRecipients
|
||||||
|
guard . not $ null recs
|
||||||
return ( AddRecipientGroup g
|
return ( AddRecipientGroup g
|
||||||
, Set.unions <$> apreq (multiSelectField . return $ mkOptionList
|
, Set.unions <$> apreq (multiSelectField . return $ mkOptionList
|
||||||
[ Option userDisplayName (Set.singleton $ Right rid) (toPathPiece cid) | (cid, Entity rid User{..}) <- recs ]
|
[ Option userDisplayName (Set.singleton $ Right rid) (toPathPiece cid) | (cid, Entity rid User{..}) <- recs ]
|
||||||
) (fslI (AddRecipientGroup g) & addName (nudge . toPathPiece $ AddRecipientGroup g)) Nothing
|
) (fslI (AddRecipientGroup g) & addName (nudge . toPathPiece $ AddRecipientGroup g) & setTooltip MsgMultiSelectFieldTip) Nothing
|
||||||
)
|
)
|
||||||
, pure ( AddRecipientCustom
|
, pure ( AddRecipientCustom
|
||||||
, Set.fromList . map (Left . CI.mk) <$> apreq multiEmailField (fslI AddRecipientCustom & addName (nudge $ toPathPiece AddRecipientCustom)) Nothing )
|
, Set.fromList . map (Left . CI.mk) <$> apreq multiEmailField (fslI AddRecipientCustom & addName (nudge $ toPathPiece AddRecipientCustom) & setTooltip MsgMultiEmailFieldTip) Nothing )
|
||||||
]
|
]
|
||||||
(addRes, addWdgt) <- multiActionM addOptions (fslI MsgCommRecipientsSelectBy & addName (nudge "select")) Nothing csrf
|
(addRes, addWdgt) <- multiActionM addOptions (fslI MsgCommRecipientsSelectBy & addName (nudge "select") & setTooltip MsgCommRecipientsSelectByTip) Nothing csrf
|
||||||
let addRes' = addRes <&> \newSet oldMap ->
|
let addRes' = addRes <&> \newSet oldMap ->
|
||||||
let freshSet = newSet `Set.difference` Set.fromList (Map.elems oldMap)
|
let freshSet = newSet `Set.difference` Set.fromList (Map.elems oldMap)
|
||||||
in FormSuccess . Map.fromList . zip [maybe 0 (succ . fst) (Map.lookupMax oldMap)..] $ Set.toList freshSet
|
in FormSuccess . Map.fromList . zip [maybe 0 (succ . fst) (Map.lookupMax oldMap)..] $ Set.toList freshSet
|
||||||
@ -117,7 +134,7 @@ commR CommunicationRoute{..} = do
|
|||||||
<*> aopt textField (fslI MsgCommSubject) Nothing
|
<*> aopt textField (fslI MsgCommSubject) Nothing
|
||||||
<*> areq htmlField (fslI MsgCommBody) Nothing
|
<*> areq htmlField (fslI MsgCommBody) Nothing
|
||||||
formResult commRes $ \comm -> do
|
formResult commRes $ \comm -> do
|
||||||
runDBJobs $ queueDBJob =<< mapReaderT lift (crJob comm)
|
runDBJobs . runConduit $ hoist (mapReaderT lift) (crJobs comm) .| sinkDBJobs
|
||||||
addMessageI Success . MsgCommSuccess . Set.size $ cRecipients comm
|
addMessageI Success . MsgCommSuccess . Set.size $ cRecipients comm
|
||||||
redirect crUltDest
|
redirect crUltDest
|
||||||
|
|
||||||
@ -127,4 +144,6 @@ commR CommunicationRoute{..} = do
|
|||||||
, formAction = SomeRoute <$> mbCurrentRoute
|
, formAction = SomeRoute <$> mbCurrentRoute
|
||||||
, formEncoding = commEncoding
|
, formEncoding = commEncoding
|
||||||
}
|
}
|
||||||
siteLayoutMsg crHeading formWdgt
|
siteLayoutMsg crHeading $ do
|
||||||
|
setTitleI crHeading
|
||||||
|
formWdgt
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import Handler.Utils.Form.Types
|
|||||||
|
|
||||||
import Handler.Utils.DateTime
|
import Handler.Utils.DateTime
|
||||||
|
|
||||||
import Import hiding (cons)
|
import Import
|
||||||
import qualified Data.Char as Char
|
import qualified Data.Char as Char
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
module Handler.Utils.Mail
|
module Handler.Utils.Mail
|
||||||
( addRecipientsDB
|
( addRecipientsDB
|
||||||
|
, userAddress
|
||||||
, userMailT
|
, userMailT
|
||||||
, addFileDB
|
, addFileDB
|
||||||
) where
|
) where
|
||||||
@ -31,22 +32,22 @@ addRecipientsDB uFilter = runConduit $ transPipe (liftHandlerT . runDB) (selectS
|
|||||||
let addr = Address (Just userDisplayName) $ CI.original userEmail
|
let addr = Address (Just userDisplayName) $ CI.original userEmail
|
||||||
_mailTo %= flip snoc addr
|
_mailTo %= flip snoc addr
|
||||||
|
|
||||||
|
userAddress :: User -> Address
|
||||||
|
userAddress User{userEmail, userDisplayName} = Address (Just userDisplayName) $ CI.original userEmail
|
||||||
|
|
||||||
userMailT :: ( MonadHandler m
|
userMailT :: ( MonadHandler m
|
||||||
, HandlerSite m ~ UniWorX
|
, HandlerSite m ~ UniWorX
|
||||||
, MonadBaseControl IO m
|
, MonadBaseControl IO m
|
||||||
, MonadLogger m
|
, MonadLogger m
|
||||||
) => UserId -> MailT m a -> m a
|
) => UserId -> MailT m a -> m a
|
||||||
userMailT uid mAct = do
|
userMailT uid mAct = do
|
||||||
User
|
user@User
|
||||||
{ userEmail
|
{ userMailLanguages
|
||||||
, userDisplayName
|
|
||||||
, userMailLanguages
|
|
||||||
, userDateTimeFormat
|
, userDateTimeFormat
|
||||||
, userDateFormat
|
, userDateFormat
|
||||||
, userTimeFormat
|
, userTimeFormat
|
||||||
} <- liftHandlerT . runDB $ getJust uid
|
} <- liftHandlerT . runDB $ getJust uid
|
||||||
let
|
let
|
||||||
addr = Address (Just userDisplayName) $ CI.original userEmail
|
|
||||||
ctx = MailContext
|
ctx = MailContext
|
||||||
{ mcLanguages = userMailLanguages
|
{ mcLanguages = userMailLanguages
|
||||||
, mcDateTimeFormat = \case
|
, mcDateTimeFormat = \case
|
||||||
@ -55,7 +56,7 @@ userMailT uid mAct = do
|
|||||||
SelFormatTime -> userTimeFormat
|
SelFormatTime -> userTimeFormat
|
||||||
}
|
}
|
||||||
mailT ctx $ do
|
mailT ctx $ do
|
||||||
_mailTo .= pure addr
|
_mailTo .= pure (userAddress user)
|
||||||
mAct
|
mAct
|
||||||
|
|
||||||
addFileDB :: ( MonadMail m
|
addFileDB :: ( MonadMail m
|
||||||
@ -69,4 +70,4 @@ addFileDB fId = do
|
|||||||
_partEncoding .= Base64
|
_partEncoding .= Base64
|
||||||
_partFilename .= Just fileName
|
_partFilename .= Just fileName
|
||||||
_partContent .= LBS.fromStrict fileContent
|
_partContent .= LBS.fromStrict fileContent
|
||||||
setMailObjectId' fId :: StateT Part (HandlerT UniWorX IO) MailObjectId
|
setMailObjectIdCrypto fId :: StateT Part (HandlerT UniWorX IO) MailObjectId
|
||||||
|
|||||||
@ -3,7 +3,7 @@ module Import.NoFoundation
|
|||||||
, MForm
|
, MForm
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod as Import hiding (formatTime, derivePersistFieldJSON, addMessage, addMessageI, (.=), MForm, Proxy, foldlM, static, boolField, identifyForm)
|
import ClassyPrelude.Yesod as Import hiding (formatTime, derivePersistFieldJSON, addMessage, addMessageI, (.=), MForm, Proxy, foldlM, static, boolField, identifyForm, cons)
|
||||||
import Model as Import
|
import Model as Import
|
||||||
import Model.Types.JSON as Import
|
import Model.Types.JSON as Import
|
||||||
import Model.Migration as Import
|
import Model.Migration as Import
|
||||||
|
|||||||
@ -58,6 +58,7 @@ import Jobs.Handler.QueueNotification
|
|||||||
import Jobs.Handler.HelpRequest
|
import Jobs.Handler.HelpRequest
|
||||||
import Jobs.Handler.SetLogSettings
|
import Jobs.Handler.SetLogSettings
|
||||||
import Jobs.Handler.DistributeCorrections
|
import Jobs.Handler.DistributeCorrections
|
||||||
|
import Jobs.Handler.SendCourseCommunication
|
||||||
|
|
||||||
|
|
||||||
data JobQueueException = JInvalid QueuedJobId QueuedJob
|
data JobQueueException = JInvalid QueuedJobId QueuedJob
|
||||||
|
|||||||
@ -23,13 +23,13 @@ dispatchJobHelpRequest :: Either (Maybe Address) UserId
|
|||||||
dispatchJobHelpRequest jSender jRequestTime jHelpSubject jHelpRequest jReferer = do
|
dispatchJobHelpRequest jSender jRequestTime jHelpSubject jHelpRequest jReferer = do
|
||||||
supportAddress <- getsYesod $ appMailSupport . appSettings
|
supportAddress <- getsYesod $ appMailSupport . appSettings
|
||||||
userInfo <- bitraverse return (runDB . getEntity) jSender
|
userInfo <- bitraverse return (runDB . getEntity) jSender
|
||||||
let userAddress = either
|
let senderAddress = either
|
||||||
id
|
id
|
||||||
(fmap $ \(Entity _ User{..}) -> Address (Just userDisplayName) (CI.original userEmail))
|
(fmap $ \(Entity _ User{..}) -> Address (Just userDisplayName) (CI.original userEmail))
|
||||||
userInfo
|
userInfo
|
||||||
mailT def $ do
|
mailT def $ do
|
||||||
_mailTo .= [supportAddress]
|
_mailTo .= [supportAddress]
|
||||||
whenIsJust userAddress (_mailFrom .=)
|
whenIsJust senderAddress (_mailFrom .=)
|
||||||
replaceMailHeader "Auto-Submitted" $ Just "no"
|
replaceMailHeader "Auto-Submitted" $ Just "no"
|
||||||
setSubjectI $ maybe MsgMailSubjectSupport MsgMailSubjectSupportCustom jHelpSubject
|
setSubjectI $ maybe MsgMailSubjectSupport MsgMailSubjectSupportCustom jHelpSubject
|
||||||
setDate jRequestTime
|
setDate jRequestTime
|
||||||
|
|||||||
37
src/Jobs/Handler/SendCourseCommunication.hs
Normal file
37
src/Jobs/Handler/SendCourseCommunication.hs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
module Jobs.Handler.SendCourseCommunication
|
||||||
|
( dispatchJobSendCourseCommunication
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
import Utils.Lens
|
||||||
|
import Handler.Utils
|
||||||
|
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
|
||||||
|
dispatchJobSendCourseCommunication :: Either UserEmail UserId
|
||||||
|
-> Set Address
|
||||||
|
-> CourseId
|
||||||
|
-> UserId
|
||||||
|
-> UUID
|
||||||
|
-> Maybe Text
|
||||||
|
-> Html
|
||||||
|
-> Handler ()
|
||||||
|
dispatchJobSendCourseCommunication jRecipientEmail jAllRecipientAddresses jCourse jSender jMailObjectUUID jSubject jMailContent = do
|
||||||
|
(sender, Course{..}) <- runDB $ (,)
|
||||||
|
<$> getJust jSender
|
||||||
|
<*> getJust jCourse
|
||||||
|
either (\email -> mailT def . (assign _mailTo (pure . Address Nothing $ CI.original email) *>)) userMailT jRecipientEmail $ do
|
||||||
|
void $ setMailObjectUUID jMailObjectUUID
|
||||||
|
_mailFrom .= userAddress sender
|
||||||
|
if -- Use `addMailHeader` instead of `_mailCc` to make `mailT` ignore the additional recipients
|
||||||
|
| jRecipientEmail == Right jSender
|
||||||
|
-> addMailHeader "Cc" . intercalate ", " . map renderAddress $ Set.toAscList (Set.delete (userAddress sender) jAllRecipientAddresses)
|
||||||
|
| otherwise
|
||||||
|
-> addMailHeader "Cc" "Undisclosed Recipients:;"
|
||||||
|
addMailHeader "Auto-Submitted" "no"
|
||||||
|
setSubjectI . prependCourseTitle courseTerm courseSchool courseShorthand $ maybe (SomeMessage MsgCommCourseSubject) SomeMessage jSubject
|
||||||
|
void $ addPart jMailContent
|
||||||
@ -2,7 +2,7 @@ module Jobs.Queue
|
|||||||
( writeJobCtl, writeJobCtlBlock
|
( writeJobCtl, writeJobCtlBlock
|
||||||
, queueJob, queueJob'
|
, queueJob, queueJob'
|
||||||
, YesodJobDB
|
, YesodJobDB
|
||||||
, runDBJobs, queueDBJob
|
, runDBJobs, queueDBJob, sinkDBJobs
|
||||||
, module Jobs.Types
|
, module Jobs.Types
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@ -21,6 +21,8 @@ import qualified Data.HashMap.Strict as HashMap
|
|||||||
|
|
||||||
import Control.Monad.Random (evalRand, mkStdGen, uniform)
|
import Control.Monad.Random (evalRand, mkStdGen, uniform)
|
||||||
|
|
||||||
|
import qualified Data.Conduit.List as C
|
||||||
|
|
||||||
|
|
||||||
data JobQueueException = JobQueuePoolEmpty
|
data JobQueueException = JobQueuePoolEmpty
|
||||||
deriving (Eq, Ord, Enum, Bounded, Show, Read, Generic)
|
deriving (Eq, Ord, Enum, Bounded, Show, Read, Generic)
|
||||||
@ -29,6 +31,10 @@ instance Exception JobQueueException
|
|||||||
|
|
||||||
|
|
||||||
writeJobCtl :: (MonadHandler m, HandlerSite m ~ UniWorX) => JobCtl -> m ()
|
writeJobCtl :: (MonadHandler m, HandlerSite m ~ UniWorX) => JobCtl -> m ()
|
||||||
|
-- | Pass an instruction to the `Job`-Workers
|
||||||
|
--
|
||||||
|
-- Instructions are assigned deterministically and pseudo-randomly to one specific worker.
|
||||||
|
-- While this means that they might be executed later than desireable, rouge threads that queue the same instruction many times do not deny service to others
|
||||||
writeJobCtl cmd = do
|
writeJobCtl cmd = do
|
||||||
tid <- liftIO myThreadId
|
tid <- liftIO myThreadId
|
||||||
wMap <- getsYesod appJobCtl >>= liftIO . readTVarIO
|
wMap <- getsYesod appJobCtl >>= liftIO . readTVarIO
|
||||||
@ -39,6 +45,7 @@ writeJobCtl cmd = do
|
|||||||
liftIO . atomically $ writeTMChan chan cmd
|
liftIO . atomically $ writeTMChan chan cmd
|
||||||
|
|
||||||
writeJobCtlBlock :: (MonadHandler m, HandlerSite m ~ UniWorX) => JobCtl -> ReaderT JobContext m ()
|
writeJobCtlBlock :: (MonadHandler m, HandlerSite m ~ UniWorX) => JobCtl -> ReaderT JobContext m ()
|
||||||
|
-- | Pass an instruction to the `Job`-Workers and block until it was acted upon
|
||||||
writeJobCtlBlock cmd = do
|
writeJobCtlBlock cmd = do
|
||||||
getResVar <- asks jobConfirm
|
getResVar <- asks jobConfirm
|
||||||
resVar <- liftIO . atomically $ do
|
resVar <- liftIO . atomically $ do
|
||||||
@ -67,19 +74,30 @@ queueJobUnsafe job = do
|
|||||||
-- return jId
|
-- return jId
|
||||||
|
|
||||||
queueJob :: (MonadHandler m, HandlerSite m ~ UniWorX) => Job -> m QueuedJobId
|
queueJob :: (MonadHandler m, HandlerSite m ~ UniWorX) => Job -> m QueuedJobId
|
||||||
|
-- ^ Queue a job for later execution
|
||||||
|
--
|
||||||
|
-- Makes no guarantees as to when it will be executed (`queueJob'`) and does not interact with any running database transactions (`runDBJobs`)
|
||||||
queueJob = liftHandlerT . runDB . setSerializable . queueJobUnsafe
|
queueJob = liftHandlerT . runDB . setSerializable . queueJobUnsafe
|
||||||
|
|
||||||
queueJob' :: (MonadHandler m, HandlerSite m ~ UniWorX) => Job -> m ()
|
queueJob' :: (MonadHandler m, HandlerSite m ~ UniWorX) => Job -> m ()
|
||||||
-- ^ `queueJob` followed by `JobCtlPerform`
|
-- ^ `queueJob` followed by `writeJobCtl` `JobCtlPerform` to ensure, that it is executed asap
|
||||||
queueJob' job = queueJob job >>= writeJobCtl . JobCtlPerform
|
queueJob' job = queueJob job >>= writeJobCtl . JobCtlPerform
|
||||||
|
|
||||||
|
-- | Slightly modified Version of `YesodDB` for `runDBJobs`
|
||||||
type YesodJobDB site = ReaderT (YesodPersistBackend site) (WriterT (Set QueuedJobId) (HandlerT site IO))
|
type YesodJobDB site = ReaderT (YesodPersistBackend site) (WriterT (Set QueuedJobId) (HandlerT site IO))
|
||||||
|
|
||||||
queueDBJob :: Job -> ReaderT (YesodPersistBackend UniWorX) (WriterT (Set QueuedJobId) (HandlerT UniWorX IO)) ()
|
queueDBJob :: Job -> YesodJobDB UniWorX ()
|
||||||
|
-- | Queue a job as part of a database transaction and execute it after the transaction succeeds
|
||||||
queueDBJob job = mapReaderT lift (queueJobUnsafe job) >>= tell . Set.singleton
|
queueDBJob job = mapReaderT lift (queueJobUnsafe job) >>= tell . Set.singleton
|
||||||
|
|
||||||
runDBJobs :: (MonadHandler m, HandlerSite m ~ UniWorX)
|
sinkDBJobs :: Sink Job (YesodJobDB UniWorX) ()
|
||||||
=> ReaderT (YesodPersistBackend UniWorX) (WriterT (Set QueuedJobId) (HandlerT UniWorX IO)) a -> m a
|
-- | Queue many jobs as part of a database transaction and execute them after the transaction passes
|
||||||
|
sinkDBJobs = C.mapM_ queueDBJob
|
||||||
|
|
||||||
|
runDBJobs :: (MonadHandler m, HandlerSite m ~ UniWorX) => YesodJobDB UniWorX a -> m a
|
||||||
|
-- | Replacement for/Wrapper around `runDB` when jobs need to be queued as part of a database transaction
|
||||||
|
--
|
||||||
|
-- Jobs get immediately executed if the transaction succeeds
|
||||||
runDBJobs act = do
|
runDBJobs act = do
|
||||||
(ret, jIds) <- liftHandlerT . runDB $ mapReaderT runWriterT act
|
(ret, jIds) <- liftHandlerT . runDB $ mapReaderT runWriterT act
|
||||||
forM_ jIds $ writeJobCtl . JobCtlPerform
|
forM_ jIds $ writeJobCtl . JobCtlPerform
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
module Jobs.Types
|
module Jobs.Types
|
||||||
( Job(..), Notification(..)
|
( Job(..), Notification(..)
|
||||||
, Communication(..)
|
|
||||||
, JobCtl(..)
|
, JobCtl(..)
|
||||||
, JobContext(..)
|
, JobContext(..)
|
||||||
) where
|
) where
|
||||||
@ -16,14 +15,22 @@ import Data.List.NonEmpty (NonEmpty)
|
|||||||
data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notification }
|
data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notification }
|
||||||
| JobSendTestEmail { jEmail :: Email, jMailContext :: MailContext }
|
| JobSendTestEmail { jEmail :: Email, jMailContext :: MailContext }
|
||||||
| JobQueueNotification { jNotification :: Notification }
|
| JobQueueNotification { jNotification :: Notification }
|
||||||
| JobHelpRequest { jSender :: Either (Maybe Address) UserId
|
| JobHelpRequest { jHelpSender :: Either (Maybe Address) UserId
|
||||||
, jRequestTime :: UTCTime
|
, jRequestTime :: UTCTime
|
||||||
, jHelpSubject :: Maybe Text
|
, jSubject :: Maybe Text
|
||||||
, jHelpRequest :: Text
|
, jHelpRequest :: Text
|
||||||
, jReferer :: Maybe Text
|
, jReferer :: Maybe Text
|
||||||
}
|
}
|
||||||
| JobSetLogSettings { jInstance :: InstanceId, jLogSettings :: LogSettings }
|
| JobSetLogSettings { jInstance :: InstanceId, jLogSettings :: LogSettings }
|
||||||
| JobDistributeCorrections { jSheet :: SheetId }
|
| JobDistributeCorrections { jSheet :: SheetId }
|
||||||
|
| JobSendCourseCommunication { jRecipientEmail :: Either UserEmail UserId
|
||||||
|
, jAllRecipientAddresses :: Set Address
|
||||||
|
, jCourse :: CourseId
|
||||||
|
, jSender :: UserId
|
||||||
|
, jMailObjectUUID :: UUID
|
||||||
|
, jSubject :: Maybe Text
|
||||||
|
, jMailContent :: Html
|
||||||
|
}
|
||||||
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 }
|
||||||
@ -52,19 +59,6 @@ deriveJSON defaultOptions
|
|||||||
} ''Notification
|
} ''Notification
|
||||||
|
|
||||||
|
|
||||||
data Communication = Communication
|
|
||||||
{ cRecipients :: Set (Either UserEmail UserId)
|
|
||||||
, cSubject :: Maybe Text
|
|
||||||
, cBody :: Html
|
|
||||||
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
|
||||||
|
|
||||||
instance Hashable Communication
|
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
|
||||||
{ fieldLabelModifier = camelToPathPiece' 1
|
|
||||||
} ''Communication
|
|
||||||
|
|
||||||
|
|
||||||
data JobCtl = JobCtlFlush
|
data JobCtl = JobCtlFlush
|
||||||
| JobCtlPerform QueuedJobId
|
| JobCtlPerform QueuedJobId
|
||||||
| JobCtlDetermineCrontab
|
| JobCtlDetermineCrontab
|
||||||
|
|||||||
64
src/Mail.hs
64
src/Mail.hs
@ -7,7 +7,9 @@ module Mail
|
|||||||
module Network.Mail.Mime
|
module Network.Mail.Mime
|
||||||
-- * MailT
|
-- * MailT
|
||||||
, MailT, defMailT
|
, MailT, defMailT
|
||||||
, MailSmtpData(..), MailContext(..), MailLanguages(..)
|
, MailSmtpData(..)
|
||||||
|
, _MailSmtpDataSet
|
||||||
|
, MailContext(..), MailLanguages(..)
|
||||||
, MonadMail(..)
|
, MonadMail(..)
|
||||||
, getMailMessageRender, getMailMsgRenderer
|
, getMailMessageRender, getMailMsgRenderer
|
||||||
-- * YesodMail
|
-- * YesodMail
|
||||||
@ -24,7 +26,8 @@ module Mail
|
|||||||
, MailObjectId
|
, MailObjectId
|
||||||
, replaceMailHeader, addMailHeader, removeMailHeader
|
, replaceMailHeader, addMailHeader, removeMailHeader
|
||||||
, replaceMailHeaderI, addMailHeaderI
|
, replaceMailHeaderI, addMailHeaderI
|
||||||
, setSubjectI, setMailObjectId, setMailObjectId'
|
, setSubjectI
|
||||||
|
, setMailObjectUUID, setMailObjectIdRandom, setMailObjectIdCrypto, setMailObjectIdPseudorandom
|
||||||
, setDate, setDateCurrent
|
, setDate, setDateCurrent
|
||||||
, setMailSmtpData
|
, setMailSmtpData
|
||||||
, _mailFrom, _mailTo, _mailCc, _mailBcc, _mailHeaders, _mailHeader, _mailParts
|
, _mailFrom, _mailTo, _mailCc, _mailBcc, _mailHeaders, _mailHeader, _mailParts
|
||||||
@ -60,18 +63,19 @@ import qualified Data.Text.Lazy as LT
|
|||||||
import qualified Data.Text.Lazy.Builder as LTB
|
import qualified Data.Text.Lazy.Builder as LTB
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
|
|
||||||
import Utils (MsgRendererS(..))
|
import Utils (MsgRendererS(..), MonadSecretBox(..))
|
||||||
import Utils.Lens.TH
|
import Utils.Lens.TH
|
||||||
import Control.Lens hiding (from)
|
import Control.Lens hiding (from)
|
||||||
|
import Control.Lens.Extras (is)
|
||||||
|
|
||||||
import Text.Blaze.Renderer.Utf8
|
import Text.Blaze.Renderer.Utf8
|
||||||
|
|
||||||
import Data.UUID (UUID)
|
import Data.UUID (UUID)
|
||||||
import qualified Data.UUID as UUID
|
import qualified Data.UUID as UUID
|
||||||
import qualified Data.UUID.V4 as UUID
|
|
||||||
import Data.UUID.Cryptographic.ImplicitNamespace
|
import Data.UUID.Cryptographic.ImplicitNamespace
|
||||||
|
|
||||||
import Data.Binary (Binary)
|
import Data.Binary (Binary)
|
||||||
|
import qualified Data.Binary as Binary
|
||||||
|
|
||||||
import GHC.TypeLits (KnownSymbol)
|
import GHC.TypeLits (KnownSymbol)
|
||||||
|
|
||||||
@ -104,6 +108,12 @@ import Control.Monad.Trans.Maybe (MaybeT(..))
|
|||||||
import Data.CaseInsensitive (CI)
|
import Data.CaseInsensitive (CI)
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
import Control.Monad.Random (MonadRandom(..), evalRand, mkStdGen)
|
||||||
|
import qualified Crypto.Saltine.Class as Saltine (IsEncoding(..))
|
||||||
|
import qualified Data.ByteArray as ByteArray (convert)
|
||||||
|
import Crypto.MAC.HMAC (hmac, HMAC)
|
||||||
|
import Crypto.Hash.Algorithms (SHAKE128)
|
||||||
|
|
||||||
|
|
||||||
makeLenses_ ''Mail
|
makeLenses_ ''Mail
|
||||||
makeLenses_ ''Part
|
makeLenses_ ''Part
|
||||||
@ -131,6 +141,13 @@ instance Monoid (MailSmtpData) where
|
|||||||
mempty = memptydefault
|
mempty = memptydefault
|
||||||
mappend = mappenddefault
|
mappend = mappenddefault
|
||||||
|
|
||||||
|
_MailSmtpDataSet :: Getter MailSmtpData Bool
|
||||||
|
_MailSmtpDataSet = to $ \MailSmtpData{..} -> none id
|
||||||
|
[ is (_Wrapped . _Nothing) smtpEnvelopeFrom
|
||||||
|
, Set.null smtpRecipients
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
newtype MailLanguages = MailLanguages { mailLanguages :: [Lang] }
|
newtype MailLanguages = MailLanguages { mailLanguages :: [Lang] }
|
||||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||||
deriving newtype (FromJSON, ToJSON, IsList)
|
deriving newtype (FromJSON, ToJSON, IsList)
|
||||||
@ -424,20 +441,33 @@ setMailObjectUUID uuid = do
|
|||||||
replaceMailHeader oidHeader . Just $ "<" <> objectId <> ">"
|
replaceMailHeader oidHeader . Just $ "<" <> objectId <> ">"
|
||||||
return objectId
|
return objectId
|
||||||
|
|
||||||
setMailObjectId :: ( MonadHeader m
|
setMailObjectIdRandom :: ( MonadHeader m
|
||||||
, YesodMail (HandlerSite m)
|
, YesodMail (HandlerSite m)
|
||||||
) => m MailObjectId
|
) => m MailObjectId
|
||||||
setMailObjectId = setMailObjectUUID =<< liftIO UUID.nextRandom
|
setMailObjectIdRandom = setMailObjectUUID =<< liftIO getRandom
|
||||||
|
|
||||||
setMailObjectId' :: ( MonadHeader m
|
setMailObjectIdCrypto :: ( MonadHeader m
|
||||||
, YesodMail (HandlerSite m)
|
, YesodMail (HandlerSite m)
|
||||||
, MonadCrypto m
|
, MonadCrypto m
|
||||||
, HasCryptoUUID plain m
|
, HasCryptoUUID plain m
|
||||||
, MonadCryptoKey m ~ CryptoIDKey
|
, MonadCryptoKey m ~ CryptoIDKey
|
||||||
, KnownSymbol (CryptoIDNamespace UUID plain)
|
, KnownSymbol (CryptoIDNamespace UUID plain)
|
||||||
, Binary plain
|
, Binary plain
|
||||||
) => plain -> m MailObjectId
|
) => plain -> m MailObjectId
|
||||||
setMailObjectId' oid = setMailObjectUUID . ciphertext =<< encrypt oid
|
setMailObjectIdCrypto oid = setMailObjectUUID . ciphertext =<< encrypt oid
|
||||||
|
|
||||||
|
setMailObjectIdPseudorandom :: ( MonadHeader m
|
||||||
|
, YesodMail (HandlerSite m)
|
||||||
|
, Binary obj
|
||||||
|
, MonadSecretBox m
|
||||||
|
) => obj -> m MailObjectId
|
||||||
|
-- | Designed to leak no information about the `secretBoxKey` or the given object
|
||||||
|
setMailObjectIdPseudorandom obj = do
|
||||||
|
sbKey <- secretBoxKey
|
||||||
|
let
|
||||||
|
seed :: HMAC (SHAKE128 64)
|
||||||
|
seed = hmac (Saltine.encode sbKey) . toStrict $ Binary.encode obj
|
||||||
|
setMailObjectUUID . evalRand getRandom . mkStdGen $ hash (ByteArray.convert seed :: ByteString)
|
||||||
|
|
||||||
|
|
||||||
setDateCurrent :: (MonadHandler m, YesodMail (HandlerSite m)) => MailT m ()
|
setDateCurrent :: (MonadHandler m, YesodMail (HandlerSite m)) => MailT m ()
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
module Utils.Parameters
|
module Utils.Parameters
|
||||||
( GlobalGetParam(..)
|
( GlobalGetParam(..)
|
||||||
, lookupGlobalGetParam, hasGlobalGetParam
|
, lookupGlobalGetParam, hasGlobalGetParam, lookupGlobalGetParams
|
||||||
, lookupGlobalGetParamForm, hasGlobalGetParamForm
|
, lookupGlobalGetParamForm, hasGlobalGetParamForm
|
||||||
, globalGetParamField
|
, globalGetParamField
|
||||||
, GlobalPostParam(..)
|
, GlobalPostParam(..)
|
||||||
, lookupGlobalPostParam, hasGlobalPostParam
|
, lookupGlobalPostParam, hasGlobalPostParam, lookupGlobalPostParams
|
||||||
, lookupGlobalPostParamForm, hasGlobalPostParamForm
|
, lookupGlobalPostParamForm, hasGlobalPostParamForm
|
||||||
, globalPostParamField
|
, globalPostParamField
|
||||||
) where
|
) where
|
||||||
@ -33,6 +33,9 @@ lookupGlobalGetParam ident = (>>= fromPathPiece) <$> lookupGetParam (toPathPiece
|
|||||||
hasGlobalGetParam :: MonadHandler m => GlobalGetParam -> m Bool
|
hasGlobalGetParam :: MonadHandler m => GlobalGetParam -> m Bool
|
||||||
hasGlobalGetParam ident = isJust <$> lookupGetParam (toPathPiece ident)
|
hasGlobalGetParam ident = isJust <$> lookupGetParam (toPathPiece ident)
|
||||||
|
|
||||||
|
lookupGlobalGetParams :: (MonadHandler m, PathPiece result) => GlobalGetParam -> m [result]
|
||||||
|
lookupGlobalGetParams ident = mapMaybe fromPathPiece <$> lookupGetParams (toPathPiece ident)
|
||||||
|
|
||||||
|
|
||||||
lookupGlobalGetParamForm :: (Monad m, PathPiece result) => GlobalGetParam -> MForm m (Maybe result)
|
lookupGlobalGetParamForm :: (Monad m, PathPiece result) => GlobalGetParam -> MForm m (Maybe result)
|
||||||
lookupGlobalGetParamForm ident = runMaybeT $ do
|
lookupGlobalGetParamForm ident = runMaybeT $ do
|
||||||
@ -42,7 +45,7 @@ lookupGlobalGetParamForm ident = runMaybeT $ do
|
|||||||
hasGlobalGetParamForm :: Monad m => GlobalGetParam -> MForm m Bool
|
hasGlobalGetParamForm :: Monad m => GlobalGetParam -> MForm m Bool
|
||||||
hasGlobalGetParamForm ident = maybe False (Map.member $ toPathPiece ident) <$> askParams
|
hasGlobalGetParamForm ident = maybe False (Map.member $ toPathPiece ident) <$> askParams
|
||||||
|
|
||||||
globalGetParamField :: Monad m => GlobalPostParam -> Field m a -> MForm m (Maybe a)
|
globalGetParamField :: Monad m => GlobalGetParam -> Field m a -> MForm m (Maybe a)
|
||||||
globalGetParamField ident Field{fieldParse} = runMaybeT $ do
|
globalGetParamField ident Field{fieldParse} = runMaybeT $ do
|
||||||
ts <- fromMaybe [] . Map.lookup (toPathPiece ident) <$> MaybeT askParams
|
ts <- fromMaybe [] . Map.lookup (toPathPiece ident) <$> MaybeT askParams
|
||||||
fs <- fromMaybe [] . Map.lookup (toPathPiece ident) <$> MaybeT askFiles
|
fs <- fromMaybe [] . Map.lookup (toPathPiece ident) <$> MaybeT askFiles
|
||||||
@ -62,7 +65,11 @@ lookupGlobalPostParam ident = (>>= fromPathPiece) <$> lookupPostParam (toPathPie
|
|||||||
|
|
||||||
hasGlobalPostParam :: MonadHandler m => GlobalPostParam -> m Bool
|
hasGlobalPostParam :: MonadHandler m => GlobalPostParam -> m Bool
|
||||||
hasGlobalPostParam ident = isJust <$> lookupPostParam (toPathPiece ident)
|
hasGlobalPostParam ident = isJust <$> lookupPostParam (toPathPiece ident)
|
||||||
|
|
||||||
|
lookupGlobalPostParams :: (MonadHandler m, PathPiece result) => GlobalPostParam -> m [result]
|
||||||
|
lookupGlobalPostParams ident = mapMaybe fromPathPiece <$> lookupPostParams (toPathPiece ident)
|
||||||
|
|
||||||
|
|
||||||
lookupGlobalPostParamForm :: (Monad m, PathPiece result) => GlobalPostParam -> MForm m (Maybe result)
|
lookupGlobalPostParamForm :: (Monad m, PathPiece result) => GlobalPostParam -> MForm m (Maybe result)
|
||||||
lookupGlobalPostParamForm ident = runMaybeT $ do
|
lookupGlobalPostParamForm ident = runMaybeT $ do
|
||||||
ps <- MaybeT askParams
|
ps <- MaybeT askParams
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user