2
2
TypeFamilies, TemplateHaskell,
3
3
RankNTypes, NamedFieldPuns, RecordWildCards, BangPatterns,
4
4
DefaultSignatures, OverloadedStrings #-}
5
+ {-# LANGUAGE LambdaCase #-}
5
6
{-# LANGUAGE TupleSections #-}
6
7
{-# LANGUAGE ScopedTypeVariables #-}
7
8
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
@@ -49,7 +50,9 @@ import Distribution.Server.Features.Users
49
50
50
51
import Distribution.Server.Util.Email
51
52
53
+ import Data.Map (Map )
52
54
import qualified Data.Map as Map
55
+ import Data.Set (Set )
53
56
import qualified Data.Set as Set
54
57
55
58
import Control.Concurrent (threadDelay )
@@ -76,7 +79,6 @@ import Text.XHtml hiding (base, text, (</>))
76
79
77
80
import qualified Data.Aeson as Aeson
78
81
import qualified Data.Aeson.Types as Aeson
79
- import qualified Data.ByteString.Char8 as BSS
80
82
import qualified Data.ByteString.Lazy.Char8 as BS
81
83
import qualified Data.Text as T
82
84
import qualified Data.Vector as Vec
@@ -575,12 +577,12 @@ userNotifyFeature :: ServerEnv
575
577
-> StateComponent AcidState NotifyData
576
578
-> Templates
577
579
-> UserNotifyFeature
578
- userNotifyFeature ServerEnv {serverBaseURI, serverCron}
580
+ userNotifyFeature serverEnv @ ServerEnv {serverBaseURI, serverCron}
579
581
UserFeature {.. }
580
582
CoreFeature {.. }
581
583
UploadFeature {.. }
582
584
AdminLogFeature {.. }
583
- UserDetailsFeature {.. }
585
+ userDetailsFeature @ UserDetailsFeature {.. }
584
586
ReportsFeature {.. }
585
587
TagsFeature {.. }
586
588
ReverseFeature {queryReverseIndex}
@@ -704,20 +706,17 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
704
706
dependencyUpdateNotifications <- Map. unionsWith (++) <$> traverse (genDependencyUpdateList idx revIdx . pkgInfoToPkgId) revisionsAndUploads
705
707
dependencyEmails <- Map. traverseWithKey describeDependencyUpdate dependencyUpdateNotifications
706
708
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
721
720
722
721
updateState notifyState (SetNotifyTime now)
723
722
@@ -899,36 +898,103 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
899
898
]
900
899
<> EmailContentList (map renderPkgLink revDeps)
901
900
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
918
905
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