Skip to content

Commit 9da933c

Browse files
brandonchinn178ysangkok
authored andcommitted
Break out getNotificationEmails from sendNotifyEmailAndDelay
1 parent 23eae0c commit 9da933c

File tree

1 file changed

+115
-49
lines changed

1 file changed

+115
-49
lines changed

src/Distribution/Server/Features/UserNotify.hs

Lines changed: 115 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
TypeFamilies, TemplateHaskell,
33
RankNTypes, NamedFieldPuns, RecordWildCards, BangPatterns,
44
DefaultSignatures, OverloadedStrings #-}
5+
{-# LANGUAGE LambdaCase #-}
56
{-# LANGUAGE TupleSections #-}
67
{-# LANGUAGE ScopedTypeVariables #-}
78
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
@@ -49,7 +50,9 @@ import Distribution.Server.Features.Users
4950

5051
import Distribution.Server.Util.Email
5152

53+
import Data.Map (Map)
5254
import qualified Data.Map as Map
55+
import Data.Set (Set)
5356
import qualified Data.Set as Set
5457

5558
import Control.Concurrent (threadDelay)
@@ -76,7 +79,6 @@ import Text.XHtml hiding (base, text, (</>))
7679

7780
import qualified Data.Aeson as Aeson
7881
import qualified Data.Aeson.Types as Aeson
79-
import qualified Data.ByteString.Char8 as BSS
8082
import qualified Data.ByteString.Lazy.Char8 as BS
8183
import qualified Data.Text as T
8284
import qualified Data.Vector as Vec
@@ -575,12 +577,12 @@ userNotifyFeature :: ServerEnv
575577
-> StateComponent AcidState NotifyData
576578
-> Templates
577579
-> UserNotifyFeature
578-
userNotifyFeature ServerEnv{serverBaseURI, serverCron}
580+
userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
579581
UserFeature{..}
580582
CoreFeature{..}
581583
UploadFeature{..}
582584
AdminLogFeature{..}
583-
UserDetailsFeature{..}
585+
userDetailsFeature@UserDetailsFeature{..}
584586
ReportsFeature{..}
585587
TagsFeature{..}
586588
ReverseFeature{queryReverseIndex}
@@ -704,20 +706,17 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
704706
dependencyUpdateNotifications <- Map.unionsWith (++) <$> traverse (genDependencyUpdateList idx revIdx . pkgInfoToPkgId) revisionsAndUploads
705707
dependencyEmails <- Map.traverseWithKey describeDependencyUpdate dependencyUpdateNotifications
706708

707-
-- Concat the constituent email parts such that only one email is sent per user
708-
mapM_ (sendNotifyEmailAndDelay users) . Map.toList $
709-
fmap ("Maintainer Notifications",) . foldr1 (Map.unionWith (<>)) $
710-
[ revisionUploadEmails
711-
, groupActionEmails
712-
, docReportEmails
713-
, tagProposalEmails
714-
]
715-
716-
-- Dependency email notifications consist of multiple paragraphs, so it would be confusing if concatenated.
717-
-- So they're sent independently.
718-
mapM_ (sendNotifyEmailAndDelay users) . Map.toList $
719-
Map.mapKeys fst . Map.mapWithKey (\(_, dep) emailContent -> ("Dependency Update: " <> T.pack (display dep), emailContent)) $
720-
dependencyEmails
709+
emails <-
710+
getNotificationEmails serverEnv userDetailsFeature users
711+
( foldr1 (Map.unionWith (<>))
712+
[ revisionUploadEmails
713+
, groupActionEmails
714+
, docReportEmails
715+
, tagProposalEmails
716+
]
717+
, dependencyEmails
718+
)
719+
mapM_ sendNotifyEmailAndDelay emails
721720

722721
updateState notifyState (SetNotifyTime now)
723722

@@ -899,36 +898,103 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
899898
]
900899
<> EmailContentList (map renderPkgLink revDeps)
901900

902-
sendNotifyEmailAndDelay :: Users.Users -> (UserId, (T.Text, EmailContent)) -> IO ()
903-
sendNotifyEmailAndDelay users (uid, (subject, emailContent)) = do
904-
mudetails <- queryUserDetails uid
905-
case mudetails of
906-
Nothing -> return ()
907-
Just (AccountDetails{accountContactEmail=eml, accountName=aname})-> do
908-
let mailFrom = Address (Just (T.pack "Hackage website"))
909-
(T.pack ("noreply@" ++ uriRegName ourHost))
910-
mail = (emptyMail mailFrom) {
911-
mailTo = [Address (Just aname) eml],
912-
mailHeaders = [(BSS.pack "Subject", "[Hackage] " <> subject)],
913-
mailParts =
914-
[ fromEmailContent $ emailContent <> updatePreferencesText
915-
]
916-
}
917-
Just ourHost = uriAuthority serverBaseURI
901+
sendNotifyEmailAndDelay :: Mail -> IO ()
902+
sendNotifyEmailAndDelay email = do
903+
-- TODO: if we need any configuration of sendmail stuff, has to go here
904+
renderSendMail email
918905

919-
renderSendMail mail --TODO: if we need any configuration of
920-
-- sendmail stuff, has to go here
921-
threadDelay 250000
922-
where
923-
updatePreferencesText =
924-
EmailContentParagraph $
925-
"You can adjust your notification preferences at" <> EmailContentSoftBreak
926-
<> emailContentUrl
927-
serverBaseURI
928-
{ uriPath =
929-
concatMap ("/" <>)
930-
[ "user"
931-
, display $ Users.userIdToName users uid
932-
, "notify"
933-
]
934-
}
906+
-- delay sending out emails, because ???
907+
threadDelay 250000
908+
909+
-- | Notifications in the same group are batched in the same email.
910+
--
911+
-- TODO: How often do multiple notifications come in at once? Maybe it's
912+
-- fine to just send one email per notification.
913+
data NotificationGroup
914+
= GeneralNotification
915+
| DependencyNotification PackageId
916+
deriving (Eq, Ord)
917+
918+
-- | Get all the emails to send for the given notifications.
919+
getNotificationEmails
920+
:: ServerEnv
921+
-> UserDetailsFeature
922+
-> Users.Users
923+
-> (Map UserId EmailContent, Map (UserId, PackageId) EmailContent)
924+
-> IO [Mail]
925+
getNotificationEmails
926+
ServerEnv{serverBaseURI}
927+
UserDetailsFeature{queryUserDetails}
928+
allUsers
929+
(generalEmails, dependencyUpdateEmails) = do
930+
let userIds = Set.fromList . Map.keys $ generalEmails <> Map.mapKeys fst dependencyUpdateEmails
931+
userIdToDetails <- Map.mapMaybe id <$> fromSetM queryUserDetails userIds
932+
933+
pure $
934+
let emails =
935+
groupNotifications . concat $
936+
[ Map.toList
937+
. fmap (, GeneralNotification)
938+
$ generalEmails
939+
, Map.toList
940+
. Map.mapKeys fst
941+
. Map.mapWithKey (\(_, pkg) emailContent -> (emailContent, DependencyNotification pkg))
942+
$ dependencyUpdateEmails
943+
]
944+
in flip mapMaybe (Map.toList emails) $ \((uid, group), emailContent) ->
945+
case uid `Map.lookup` userIdToDetails of
946+
Nothing -> Nothing
947+
Just AccountDetails{..} -> Just $
948+
Mail
949+
{ mailFrom =
950+
Address
951+
{ addressName = Just "Hackage website"
952+
, addressEmail = "noreply@" <> hostname
953+
}
954+
, mailTo =
955+
[ Address
956+
{ addressName = Just accountName
957+
, addressEmail = accountContactEmail
958+
}
959+
]
960+
, mailCc = []
961+
, mailBcc = []
962+
, mailHeaders =
963+
[ ("Subject", "[Hackage] " <> getEmailSubject group)
964+
]
965+
, mailParts =
966+
[ fromEmailContent $ emailContent <> updatePreferencesText uid
967+
]
968+
}
969+
where
970+
groupNotifications :: [(UserId, (EmailContent, NotificationGroup))] -> Map (UserId, NotificationGroup) EmailContent
971+
groupNotifications =
972+
Map.fromListWith (<>)
973+
. map (\(uid, (emailContent, group)) -> ((uid, group), emailContent))
974+
975+
getEmailSubject = \case
976+
GeneralNotification -> "Maintainer Notifications"
977+
DependencyNotification pkg -> "Dependency Update: " <> T.pack (display pkg)
978+
979+
hostname =
980+
case uriAuthority serverBaseURI of
981+
Just auth -> T.pack $ uriRegName auth
982+
Nothing -> error $ "Could not get hostname from serverBaseURI: " <> show serverBaseURI
983+
984+
updatePreferencesText uid =
985+
EmailContentParagraph $
986+
"You can adjust your notification preferences at" <> EmailContentSoftBreak
987+
<> emailContentUrl
988+
serverBaseURI
989+
{ uriPath =
990+
concatMap ("/" <>)
991+
[ "user"
992+
, display $ Users.userIdToName allUsers uid
993+
, "notify"
994+
]
995+
}
996+
997+
{----- Utilities -----}
998+
999+
fromSetM :: Monad m => (k -> m v) -> Set k -> m (Map k v)
1000+
fromSetM f = traverse id . Map.fromSet f

0 commit comments

Comments
 (0)