From abe21276a66c86930ed8e4119c64e0a7905ecd80 Mon Sep 17 00:00:00 2001
From: Janus Troelsen
Date: Sun, 21 May 2023 11:40:36 -0500
Subject: [PATCH 01/71] Email notification: Disjunction across bounds for same
package
---
.../Server/Features/UserNotify.hs | 26 ++++++++++++++++++-
tests/RevDepCommon.hs | 9 ++++++-
tests/ReverseDependenciesTest.hs | 18 ++++++++++++-
3 files changed, 50 insertions(+), 3 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 88941ec94..34c066371 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -494,7 +494,14 @@ dependencyReleaseEmails userSetIdForPackage index (ReverseIndex revs nodemap dep
(userId, Just NotifyPref{..}) <- zip ids mPrefs
guard $ not notifyOptOut
guard notifyDependencyForMaintained
- Just depList <- [mDepList]
+
+ Just depListWithCollisions <- [mDepList]
+ -- Remove collisions on the same PackageName, amassed e.g. across
+ -- multiple conditional branches. The branches could be from either
+ -- side of an 'if' block conditioned on a flag. If either of them
+ -- permits the newly released version, avoid sending the notification.
+ let depList = unionSamePackageName depListWithCollisions
+
case notifyDependencyTriggerBounds of
NewIncompatibility -> do
let allNewUploadPkgInfos = PackageIndex.lookupPackageName index (pkgName pkgId)
@@ -532,6 +539,23 @@ dependencyReleaseEmails userSetIdForPackage index (ReverseIndex revs nodemap dep
| otherwise = True
newestVersion = pkgVersion pkgId
+-- | Boolean OR on ranges across dependencies on the same PackageName
+unionSamePackageName :: [Dependency] -> [Dependency]
+unionSamePackageName collisions =
+ let
+ maps = [Map.singleton depName dep | dep@(Dependency depName _ _) <- collisions]
+ disjunct :: Dependency -> Dependency -> Dependency
+ disjunct
+ (Dependency fName fRange fLibraries)
+ (Dependency _ gRange gLibraries) =
+ mkDependency
+ fName
+ (unionVersionRanges fRange gRange)
+ (fLibraries <> gLibraries)
+ disjunctions = Map.unionsWith disjunct maps
+ in
+ Map.elems disjunctions
+
pkgInfoToPkgId :: PkgInfo -> PackageIdentifier
pkgInfoToPkgId pkgInfo =
PackageIdentifier (packageName pkgInfo) (packageVersion pkgInfo)
diff --git a/tests/RevDepCommon.hs b/tests/RevDepCommon.hs
index a7f79ff9c..9a9ac3330 100644
--- a/tests/RevDepCommon.hs
+++ b/tests/RevDepCommon.hs
@@ -31,6 +31,13 @@ packToPkgInfo Package {pName, pVersion, pDeps} =
mkPackage :: PackageName -> [Int] -> [BSL.ByteString] -> PkgInfo
mkPackage name intVersion depends =
+ mkPackageWithCabalFileSuffix name intVersion $
+ if depends /= []
+ then "library\n build-depends: " <> BSL.intercalate "," depends
+ else ""
+
+mkPackageWithCabalFileSuffix :: PackageName -> [Int] -> BSL.ByteString -> PkgInfo
+mkPackageWithCabalFileSuffix name intVersion cabalFileSuffix =
let
version = mkVersion intVersion
-- e.g. "2.3" for [2,3]
@@ -41,7 +48,7 @@ mkPackage name intVersion depends =
\name: " <> BSL.fromStrict (Char8.pack $ unPackageName name) <> "\n\
\version: " <> dotVersion <> "\n"
cabalFile :: CabalFileText
- cabalFile = CabalFileText $ cabalFilePrefix <> if depends /= [] then "library\n build-depends: " <> BSL.intercalate "," depends else ""
+ cabalFile = CabalFileText $ cabalFilePrefix <> cabalFileSuffix
in
PkgInfo
(PackageIdentifier name version)
diff --git a/tests/ReverseDependenciesTest.hs b/tests/ReverseDependenciesTest.hs
index 0e74a8d8e..030eb8d08 100644
--- a/tests/ReverseDependenciesTest.hs
+++ b/tests/ReverseDependenciesTest.hs
@@ -31,7 +31,7 @@ import qualified Hedgehog.Range as Range
import qualified Hedgehog.Gen as Gen
import Hedgehog ((===), Group(Group), MonadGen, Property, PropertyT, checkSequential, forAll, property)
-import RevDepCommon (Package(..), TestPackage(..), mkPackage, packToPkgInfo)
+import RevDepCommon (Package(..), TestPackage(..), mkPackage, mkPackageWithCabalFileSuffix, packToPkgInfo)
mtlBeelineLens :: [PkgInfo]
mtlBeelineLens =
@@ -214,6 +214,22 @@ allTests = testGroup "ReverseDependenciesTest"
"dependencyReleaseEmails(trigger=BoundsOutOfRange) shouldn't generate a notification when the new package is for an old release series"
mempty
(runWithPref (pref BoundsOutOfRange) (PackageIndex.fromList newVersionOfOldBase) base4_14_1)
+ assertEqual
+ "dependencyReleaseEmails(trigger=BoundsOutOfRange) should only generate a notification when the new version is forbidden across all branches"
+ mempty -- The two branches below should get OR'd and therefore the dependency is not out of bounds
+ (runWithPref
+ (pref BoundsOutOfRange)
+ (PackageIndex.fromList
+ [ mkPackage "base" [4,14] []
+ , mkPackage "base" [4,15] []
+ , mkPackageWithCabalFileSuffix "mtl" [2,3]
+ "library\n\
+ \ if arch(arm)\n\
+ \ build-depends: base >= 4.14 && < 4.15\n\
+ \ else\n\
+ \ build-depends: base >= 4.15 && < 4.16"
+ ])
+ base4_15)
, testCase "hedgehogTests" $ do
res <- hedgehogTests
assertEqual "hedgehog test pass" True res
From 6569675d6e8509b9287431e6e55faa49d5087c18 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Sun, 21 May 2023 14:48:41 -0700
Subject: [PATCH 02/71] Hackage CSS theme takes precedence over
`datatables.min.css`
---
datafiles/templates/Html/candidate-index.html.st | 2 +-
datafiles/templates/Html/table-interface.html.st | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/datafiles/templates/Html/candidate-index.html.st b/datafiles/templates/Html/candidate-index.html.st
index 60c56a70c..96c85f2cf 100644
--- a/datafiles/templates/Html/candidate-index.html.st
+++ b/datafiles/templates/Html/candidate-index.html.st
@@ -2,9 +2,9 @@
- $hackageCssTheme()$
+ $hackageCssTheme()$
Package candidates | Hackage
diff --git a/datafiles/templates/Html/table-interface.html.st b/datafiles/templates/Html/table-interface.html.st
index f08fb67eb..72accfad2 100644
--- a/datafiles/templates/Html/table-interface.html.st
+++ b/datafiles/templates/Html/table-interface.html.st
@@ -2,13 +2,13 @@
- $hackageCssTheme()$
$if(!noDatatable)$
$endif$
+ $hackageCssTheme()$
All packages by name | Hackage
From cc6ecceba685b009b162cd876cd6196805c719e1 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Sun, 28 May 2023 12:45:53 -0700
Subject: [PATCH 03/71] colors for paginate buttons and dataTables length,
filter text
---
datafiles/static/hackage.css | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/datafiles/static/hackage.css b/datafiles/static/hackage.css
index 1e0523b82..a640d3d31 100644
--- a/datafiles/static/hackage.css
+++ b/datafiles/static/hackage.css
@@ -1079,6 +1079,12 @@ table.fancy th {
table.dataTable.compact.fancy tbody td {
background: #333;
}
+ div#table_length.dataTables_length {
+ color: #fff
+ }
+ div#table_filter.dataTables_filter {
+ color: #fff
+ }
}
table.fancy td, table.properties td,
@@ -1163,6 +1169,9 @@ a.deprecated[href]:visited {
.paginator a {
color: #474747;
}
+ .paginate_button {
+ background-color: #979797;
+ }
}
@media (prefers-color-scheme: light) {
From eef83438545904205eac9f2db4d7dc380a9f817c Mon Sep 17 00:00:00 2001
From: Tristan de Cacqueray
Date: Thu, 1 Jun 2023 22:16:00 +0000
Subject: [PATCH 04/71] Remove trailing whitespace on package links (#1204)
This change fixes the link as described in this issue:
https://stackoverflow.com/a/73074209
---
datafiles/templates/Html/package-page.html.st | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/datafiles/templates/Html/package-page.html.st b/datafiles/templates/Html/package-page.html.st
index 72faee82d..cfa131b8d 100644
--- a/datafiles/templates/Html/package-page.html.st
+++ b/datafiles/templates/Html/package-page.html.st
@@ -31,7 +31,7 @@
$package.name$ $if(package.optional.hasSynopsis)$: $package.optional.synopsis$ $endif$
$if(isDeprecated)$
@@ -187,9 +187,7 @@
Home page
-
- $package.optional.homepage$
-
+ $package.optional.homepage$
$endif$
@@ -198,9 +196,7 @@
Bug tracker
-
- $package.optional.bugTracker$
-
+ $package.optional.bugTracker$
$endif$
From b9aca8922b97cb5c3a6f9dd69cb5bed1d1ef0103 Mon Sep 17 00:00:00 2001
From: gbaz
Date: Thu, 1 Jun 2023 19:42:03 -0400
Subject: [PATCH 05/71] relax upper bounds revision check (#1214)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* relax upper bounds revision check
* Update src/Distribution/Server/Util/CabalRevisions.hs
Co-authored-by: ˌbodʲɪˈɡrʲim
---------
Co-authored-by: ˌbodʲɪˈɡrʲim
---
src/Distribution/Server/Util/CabalRevisions.hs | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/Distribution/Server/Util/CabalRevisions.hs b/src/Distribution/Server/Util/CabalRevisions.hs
index 7a38d1d75..9036cf9c8 100644
--- a/src/Distribution/Server/Util/CabalRevisions.hs
+++ b/src/Distribution/Server/Util/CabalRevisions.hs
@@ -179,7 +179,12 @@ checkCabalFileRevision checkXRevision old new = do
checkPackageChecks :: Check GenericPackageDescription
checkPackageChecks pkg pkg' =
let checks = checkPackage pkg Nothing
- checks' = checkPackage pkg' Nothing
+ checks' = filter notUpperBounds $ checkPackage pkg' Nothing
+ -- if multiple upper bounds are missing, then the simple set subtraction might detect a change to
+ -- just one, and fail. Ideally we'd perform a set subtraction directly on just the missing bounds
+ -- warning contents. A simple second best is to discard this check for now.
+ notUpperBounds (PackageDistSuspiciousWarn (MissingUpperBounds _)) = False
+ notUpperBounds _ = True
in case checks' \\ checks of
[] -> return ()
newchecks -> fail $ unlines (map ppPackageCheck newchecks)
From 1d09c1807f2f6cf33c7e15dfdf9471481b6fac80 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 6 Jun 2023 14:46:54 -0400
Subject: [PATCH 06/71] Bump cachix/install-nix-action from 20 to 21 (#1212)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 20 to 21.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v20...v21)
---
updated-dependencies:
- dependency-name: cachix/install-nix-action
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/nix-flake.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml
index 83dff3376..ebb68cb58 100644
--- a/.github/workflows/nix-flake.yml
+++ b/.github/workflows/nix-flake.yml
@@ -18,7 +18,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3.1.0
- - uses: cachix/install-nix-action@v20
+ - uses: cachix/install-nix-action@v21
with:
extra_nix_config: |
trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hackage-server.cachix.org-1:iw0iRh6+gsFIrxROFaAt5gKNgIHejKjIfyRdbpPYevY=
From 7eaca159ef77a4b0bb4637f1f2009584b09bae36 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Sun, 18 Jun 2023 21:16:16 -0700
Subject: [PATCH 07/71] enable `nix flake check` and Mac build in GitHub Action
https://github.com/haskell/hackage-server/issues/1193
---
.github/workflows/nix-flake.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml
index ebb68cb58..19c94a373 100644
--- a/.github/workflows/nix-flake.yml
+++ b/.github/workflows/nix-flake.yml
@@ -13,7 +13,7 @@ jobs:
matrix:
os:
- ubuntu-latest
- # - macos-latest
+ - macos-latest
name: Nix on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
@@ -29,4 +29,4 @@ jobs:
name: hackage-server
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix build
- # - run: nix flake check
+ - run: nix flake check
From 8a6154732a5f55e6e33babe3d41125ba2d71a896 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Sun, 18 Jun 2023 21:26:49 -0700
Subject: [PATCH 08/71] update GitHub Actions versions
---
.github/workflows/nix-flake.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml
index 19c94a373..00bad8b82 100644
--- a/.github/workflows/nix-flake.yml
+++ b/.github/workflows/nix-flake.yml
@@ -17,8 +17,8 @@ jobs:
name: Nix on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v3.1.0
- - uses: cachix/install-nix-action@v21
+ - uses: actions/checkout@v3.5.3
+ - uses: cachix/install-nix-action@v22
with:
extra_nix_config: |
trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hackage-server.cachix.org-1:iw0iRh6+gsFIrxROFaAt5gKNgIHejKjIfyRdbpPYevY=
From 5222ef74408b600922b7697f9fd2b8e413899bbc Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Sun, 18 Jun 2023 23:33:53 -0700
Subject: [PATCH 09/71] leave `nix flake check` disabled until
https://github.com/haskell/hackage-server/pull/1219#issuecomment-1596576063
solved
---
.github/workflows/nix-flake.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml
index 00bad8b82..7324c67b6 100644
--- a/.github/workflows/nix-flake.yml
+++ b/.github/workflows/nix-flake.yml
@@ -29,4 +29,4 @@ jobs:
name: hackage-server
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix build
- - run: nix flake check
+ # - run: nix flake check
From 7c2b32b91028c84d2843ed7da5309094e89ea088 Mon Sep 17 00:00:00 2001
From: ffaf1
Date: Tue, 27 Jun 2023 01:18:30 +0200
Subject: [PATCH 10/71] Update max cabal-version check (#1224)
Also add a more informative comment.
---
src/Distribution/Server/Packages/Unpack.hs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Distribution/Server/Packages/Unpack.hs b/src/Distribution/Server/Packages/Unpack.hs
index 79a69570b..f3881bb2c 100644
--- a/src/Distribution/Server/Packages/Unpack.hs
+++ b/src/Distribution/Server/Packages/Unpack.hs
@@ -216,9 +216,9 @@ specVersionChecks specVerOk specVer = do
when (specVer < CabalSpecV1_2) $
throwError "'cabal-version' must be at least 1.2"
- -- Safeguard; should already be caught by parser
- unless (specVer <= CabalSpecV3_0) $
- throwError "'cabal-version' must be at most 3.0"
+ -- To keep people from uploading packages most users cannot use.
+ unless (specVer <= CabalSpecV3_6) $
+ throwError "'cabal-version' must be at most 3.6"
-- | The issue is that browsers can upload the file name using either unix
-- or windows convention, so we need to take the basename using either
From b48fa5f2aeb33d704cec9d5551c4d72725374b49 Mon Sep 17 00:00:00 2001
From: Raoul Hidalgo Charman
Date: Wed, 28 Jun 2023 07:37:08 -0400
Subject: [PATCH 11/71] Filter out metadata revisions before mirroring
These are still there from the index being mirrored, but we avoid mirroring
tarballs for each metadata revision. This seems to also improve ram usage.
---
exes/MirrorClient.hs | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/exes/MirrorClient.hs b/exes/MirrorClient.hs
index 5cf0cc548..7008ea0e3 100644
--- a/exes/MirrorClient.hs
+++ b/exes/MirrorClient.hs
@@ -4,7 +4,9 @@ module Main (main) where
import Control.Exception
import Control.Monad
import Control.Monad.Trans
+import Data.Function (on)
import Data.List
+import qualified Data.List.NonEmpty as NonEmpty
import Data.Version
import Network.Browser
import System.Directory
@@ -118,10 +120,14 @@ mirrorOnce verbosity opts
| null (selectedPkgs opts) = pkgsMissingFromDest
| otherwise = subsetIndex (selectedPkgs opts)
pkgsMissingFromDest
- pkgsToMirror' = filter (\(PkgIndexInfo pkg _ _ _) ->
- pkg `Set.notMember` missingPkgs
- && pkg `Set.notMember` unmirrorablePkgs )
- pkgsToMirror
+ byPkgId cmp = on cmp (\(PkgIndexInfo pkg _ _ _) -> pkg)
+ pkgsToMirror'
+ -- Remove any duplicates in the index from metadata revisions
+ = map NonEmpty.head . NonEmpty.groupBy (byPkgId (==)) . sortBy (byPkgId compare)
+ $ filter (\(PkgIndexInfo pkg _ _ _) ->
+ pkg `Set.notMember` missingPkgs
+ && pkg `Set.notMember` unmirrorablePkgs)
+ pkgsToMirror
mirrorCount = length pkgsToMirror'
ignoreCount = length pkgsToMirror - mirrorCount
From 9279b9a243fedaf044121ac92887c884030f1ad1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=CB=8Cbod=CA=B2=C9=AA=CB=88=C9=A1r=CA=B2im?=
Date: Thu, 6 Jul 2023 04:47:52 +0100
Subject: [PATCH 12/71] Enable MathJAX on candidate pages (#1232)
---
datafiles/templates/Html/candidate-page.html.st | 2 ++
1 file changed, 2 insertions(+)
diff --git a/datafiles/templates/Html/candidate-page.html.st b/datafiles/templates/Html/candidate-page.html.st
index 412591585..c7006d9c0 100644
--- a/datafiles/templates/Html/candidate-page.html.st
+++ b/datafiles/templates/Html/candidate-page.html.st
@@ -14,6 +14,8 @@
+
+
From 2a7d751630e8341554a520aa0c545fdcc1f423df Mon Sep 17 00:00:00 2001
From: Janus Troelsen
Date: Sat, 8 Jul 2023 17:25:44 +0200
Subject: [PATCH 13/71] Fix package list when user has multiple packages
---
.../Server/Features/UserNotify.hs | 2 +-
tests/ReverseDependenciesTest.hs | 37 +++++++++++++++++++
2 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 34c066371..eabc2b49c 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -473,7 +473,7 @@ dependencyReleaseEmails userSetIdForPackage index (ReverseIndex revs nodemap dep
revDepNames = mapMaybe (`lookupR` nodemap) (Set.toList vertices)
toNotify <- traverse maintainersToNotify revDepNames
pure $
- Map.fromList
+ Map.fromListWith (++)
[ ( (maintainerId, pkgId), [ packageId latestRevDep ] )
| (ids, latestRevDep) <- toNotify
, maintainerId <- ids
diff --git a/tests/ReverseDependenciesTest.hs b/tests/ReverseDependenciesTest.hs
index 030eb8d08..d72ebcf1a 100644
--- a/tests/ReverseDependenciesTest.hs
+++ b/tests/ReverseDependenciesTest.hs
@@ -56,6 +56,14 @@ newBaseReleased =
, mkPackage "mtl" [2,3] ["base < 4.15"]
]
+newBaseReleasedMultiple :: [PkgInfo]
+newBaseReleasedMultiple =
+ [ mkPackage "base" [4,14] []
+ , mkPackage "base" [4,15] []
+ , mkPackage "mtl" [2,3] ["base < 4.15"]
+ , mkPackage "mtl2" [2,3] ["base < 4.15"]
+ ]
+
newVersionOfOldBase :: [PkgInfo]
newVersionOfOldBase =
[ mkPackage "base" [4,14] []
@@ -104,6 +112,17 @@ allTests = testGroup "ReverseDependenciesTest"
res <- revPackageName "mtl"
let ref = Map.fromList [("beeline", (version0, Just NormalVersion))]
assertEqual "reverse dependencies must be [beeline]" ref res
+ , testCase "with set [beeline->mtl, beeline2->mtl] and querying for mtl, we get [beeline, beeline2]" $ do
+ let pkgs =
+ [ mkPackage "base" [4,15] []
+ , mkPackage "mtl" [2,3] ["base"]
+ , mkPackage "beeline" [0] ["mtl"]
+ , mkPackage "beeline2" [0] ["mtl"]
+ ]
+ ReverseFeature{revPackageName} <- mkRevFeat pkgs
+ res <- revPackageName "mtl"
+ let ref = Map.fromList [("beeline", (version0, Just NormalVersion)), ("beeline2", (version0, Just NormalVersion))]
+ assertEqual "reverse dependencies must be [beeline, beeline2]" ref res
, testCase "revPackageName selects only the version with an actual dependency, even if it is not the newest" $ do
let pkgs =
[ mkPackage "base" [4,15] []
@@ -190,6 +209,12 @@ allTests = testGroup "ReverseDependenciesTest"
base4_16 = PackageIdentifier "base" (mkVersion [4,16])
runWithPref preferences index pkg = runIdentity $
dependencyReleaseEmails userSetIdForPackage index (constructReverseIndex index) preferences pkg
+ runWithPrefAlsoMtl2 preferences index pkg = runIdentity $
+ dependencyReleaseEmails userSet index (constructReverseIndex index) preferences pkg
+ where
+ userSet arg | arg == mkPackageName "mtl" = Identity (UserIdSet.fromList [UserId 0])
+ | arg == mkPackageName "mtl2" = Identity (UserIdSet.fromList [UserId 0])
+ | otherwise = error "should only get user ids for mtl and mtl2"
assertEqual
"dependencyReleaseEmails(trigger=NewIncompatibility) shouldn't generate a notification when there are packages, but none are behind"
mempty
@@ -198,6 +223,18 @@ allTests = testGroup "ReverseDependenciesTest"
"dependencyReleaseEmails(trigger=NewIncompatibility) should generate a notification when package is a single base version behind"
(refNotification base4_15)
(runWithPref (pref NewIncompatibility) (PackageIndex.fromList newBaseReleased) base4_15)
+ assertEqual
+ "dependencyReleaseEmails(trigger=NewIncompatibility) should generate a notification for two packages that are a single base version behind"
+ (Just $
+ Set.fromList
+ [ PackageIdentifier (mkPackageName "mtl") (mkVersion [2,3])
+ , PackageIdentifier (mkPackageName "mtl2") (mkVersion [2,3])
+ ]
+ )
+ ( fmap Set.fromList
+ . Map.lookup (UserId 0, base4_15)
+ $ runWithPrefAlsoMtl2 (pref NewIncompatibility) (PackageIndex.fromList newBaseReleasedMultiple) base4_15
+ )
assertEqual
"dependencyReleaseEmails(trigger=BoundsOutOfRange) should generate a notification when package is a single base version behind"
(refNotification base4_15)
From eabf54a2e1ed56fa8cee13bea6d71680a4535267 Mon Sep 17 00:00:00 2001
From: Jessica Hamilton
Date: Wed, 7 Jun 2023 11:19:44 +1200
Subject: [PATCH 14/71] haiku: add as known platform
This combines via CPP two PRs by @jessicah:
- https://github.com/haskell/hackage-server/pull/1217 (for Cabal >= 3.11)
- https://github.com/haskell/hackage-server/pull/1221 (for Cabal <= 3.10)
---
hackage-server.cabal | 4 ++--
.../Server/Framework/Instances.hs | 20 ++++++++++++++++++-
2 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/hackage-server.cabal b/hackage-server.cabal
index 237636d7c..6873dca29 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -130,8 +130,8 @@ common defaults
-- other dependencies shared by most components
build-depends:
, aeson ^>= 2.0.3.0 || ^>= 2.1.0.0
- , Cabal ^>= 3.10.1.0
- , Cabal-syntax ^>= 3.10.1.0
+ , Cabal >= 3.10.1.0 && < 3.12
+ , Cabal-syntax >= 3.10.1.0 && < 3.12
-- Cabal-syntax needs to be bound to constrain hackage-security
-- see https://github.com/haskell/hackage-server/issues/1130
, fail ^>= 4.9.0
diff --git a/src/Distribution/Server/Framework/Instances.hs b/src/Distribution/Server/Framework/Instances.hs
index d2a68cf57..273278664 100644
--- a/src/Distribution/Server/Framework/Instances.hs
+++ b/src/Distribution/Server/Framework/Instances.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts, BangPatterns, TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
@@ -119,6 +120,9 @@ instance SafeCopy VersionRange where
instance SafeCopy OS where
errorTypeName _ = "OS"
+#if !MIN_VERSION_Cabal_syntax(3,11,0)
+ putCopy (OtherOS "haiku") = contain $ putWord8 18
+#endif
putCopy (OtherOS s) = contain $ putWord8 0 >> safePut s
putCopy Linux = contain $ putWord8 1
putCopy Windows = contain $ putWord8 2
@@ -137,6 +141,9 @@ instance SafeCopy OS where
putCopy Hurd = contain $ putWord8 15
putCopy Android = contain $ putWord8 16
putCopy Wasi = contain $ putWord8 17
+#if MIN_VERSION_Cabal_syntax(3,11,0)
+ putCopy Haiku = contain $ putWord8 18
+#endif
getCopy = contain $ do
tag <- getWord8
@@ -159,6 +166,11 @@ instance SafeCopy OS where
15 -> return Hurd
16 -> return Android
17 -> return Wasi
+#if MIN_VERSION_Cabal_syntax(3,11,0)
+ 18 -> return Haiku
+#else
+ 18 -> return $ OtherOS "haiku"
+#endif
_ -> fail "SafeCopy OS getCopy: unexpected tag"
instance SafeCopy Arch where
@@ -382,7 +394,13 @@ instance Arbitrary OS where
arbitrary = oneof [ pure OtherOS <*> vectorOf 3 (choose ('A', 'Z'))
, pure Linux, pure Windows, pure OSX, pure FreeBSD
, pure OpenBSD, pure NetBSD, pure Solaris, pure AIX
- , pure HPUX, pure IRIX, pure HaLVM, pure IOS ]
+ , pure HPUX, pure IRIX, pure HaLVM, pure IOS
+#if MIN_VERSION_Cabal_syntax(3,11,0)
+ , pure Haiku
+#else
+ , pure $ OtherOS "haiku"
+#endif
+ ]
instance Arbitrary FlagName where
arbitrary = mkFlagName <$> vectorOf 4 (choose ('a', 'z'))
From 7d9af0dffdb7504a95dcd0c5d842e334a8ffb863 Mon Sep 17 00:00:00 2001
From: Andreas Abel
Date: Thu, 3 Aug 2023 16:39:50 +0200
Subject: [PATCH 15/71] Bump aeson to ^>= 2.2.0.0, use attoparsec-aeson
---
hackage-server.cabal | 3 ++-
tests/HttpUtils.hs | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/hackage-server.cabal b/hackage-server.cabal
index 6873dca29..a0b8310c8 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -129,7 +129,7 @@ common defaults
, scientific
-- other dependencies shared by most components
build-depends:
- , aeson ^>= 2.0.3.0 || ^>= 2.1.0.0
+ , aeson ^>= 2.2.0.0
, Cabal >= 3.10.1.0 && < 3.12
, Cabal-syntax >= 3.10.1.0 && < 3.12
-- Cabal-syntax needs to be bound to constrain hackage-security
@@ -547,6 +547,7 @@ test-suite HighLevelTest
build-depends:
-- version constraints inherited from lib-server
, HTTP
+ , attoparsec-aeson ^>= 2.2.0.0
, base64-bytestring
, random
-- component-specific dependencies
diff --git a/tests/HttpUtils.hs b/tests/HttpUtils.hs
index a688efa7d..5a6e714db 100644
--- a/tests/HttpUtils.hs
+++ b/tests/HttpUtils.hs
@@ -27,7 +27,8 @@ import Control.Monad
import Data.Maybe
import Network.HTTP hiding (user)
import Network.HTTP.Auth
-import Data.Aeson (Result(..), Value(..), FromJSON(..), (.:), fromJSON, json')
+import Data.Aeson (Result(..), Value(..), FromJSON(..), (.:), fromJSON)
+import Data.Aeson.Parser (json')
import System.Exit (die)
import qualified Network.Http.Client as HC
From 73465709ac5e34d9d58b9046ec15dd664e0ff60e Mon Sep 17 00:00:00 2001
From: Andreas Abel
Date: Thu, 3 Aug 2023 16:41:14 +0200
Subject: [PATCH 16/71] Bumps, to allow building with GHC 9.8.1-alpha1
Tested locally.
---
hackage-server.cabal | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/hackage-server.cabal b/hackage-server.cabal
index a0b8310c8..f99a971c9 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -111,11 +111,11 @@ common defaults
-- see `cabal.project.local-ghc-${VERSION}` files
build-depends:
, array >= 0.5 && < 0.6
- , base >= 4.13 && < 4.19
+ , base >= 4.13 && < 4.20
, binary >= 0.8 && < 0.9
, bytestring >= 0.10 && < 0.12
, containers ^>= 0.6.0
- , deepseq >= 1.4 && < 1.5
+ , deepseq >= 1.4 && < 1.6
, directory >= 1.3 && < 1.4
, filepath >= 1.4 && < 1.5
, mtl >= 2.2.1 && < 2.4
@@ -564,7 +564,7 @@ test-suite ReverseDependenciesTest
, tasty ^>= 1.4
, tasty-hunit ^>= 0.10
, HUnit ^>= 1.6
- , hedgehog ^>= 1.2
+ , hedgehog ^>= 1.3
, exceptions
, bimap
other-modules: RevDepCommon
From 310942f23e3962cac81770a687f3c2dc4f3aa9e0 Mon Sep 17 00:00:00 2001
From: Andreas Abel
Date: Thu, 3 Aug 2023 16:47:51 +0200
Subject: [PATCH 17/71] CI: bump haskell-ci.yml to GHCs 9.6.2 9.4.5 9.2.8
---
.github/workflows/haskell-ci.yml | 22 +++++++++++-----------
hackage-server.cabal | 8 +++++++-
2 files changed, 18 insertions(+), 12 deletions(-)
diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml
index 726be0826..155cec0ac 100644
--- a/.github/workflows/haskell-ci.yml
+++ b/.github/workflows/haskell-ci.yml
@@ -8,9 +8,9 @@
#
# For more information, see https://github.com/haskell-CI/haskell-ci
#
-# version: 0.15.20230321
+# version: 0.16.6.20230729
#
-# REGENDATA ("0.15.20230321",["github","hackage-server.cabal"])
+# REGENDATA ("0.16.6.20230729",["github","hackage-server.cabal"])
#
name: Haskell-CI
on:
@@ -34,19 +34,19 @@ jobs:
strategy:
matrix:
include:
- - compiler: ghc-9.6.1
+ - compiler: ghc-9.6.2
compilerKind: ghc
- compilerVersion: 9.6.1
+ compilerVersion: 9.6.2
setup-method: ghcup
allow-failure: false
- - compiler: ghc-9.4.4
+ - compiler: ghc-9.4.5
compilerKind: ghc
- compilerVersion: 9.4.4
+ compilerVersion: 9.4.5
setup-method: ghcup
allow-failure: false
- - compiler: ghc-9.2.7
+ - compiler: ghc-9.2.8
compilerKind: ghc
- compilerVersion: 9.2.7
+ compilerVersion: 9.2.8
setup-method: ghcup
allow-failure: false
- compiler: ghc-9.0.2
@@ -71,7 +71,7 @@ jobs:
apt-get update
apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
mkdir -p "$HOME/.ghcup/bin"
- curl -sL https://downloads.haskell.org/ghcup/0.1.19.2/x86_64-linux-ghcup-0.1.19.2 > "$HOME/.ghcup/bin/ghcup"
+ curl -sL https://downloads.haskell.org/ghcup/0.1.19.5/x86_64-linux-ghcup-0.1.19.5 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
"$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
@@ -142,8 +142,8 @@ jobs:
- name: install cabal-plan
run: |
mkdir -p $HOME/.cabal/bin
- curl -sL https://github.com/haskell-hvr/cabal-plan/releases/download/v0.6.2.0/cabal-plan-0.6.2.0-x86_64-linux.xz > cabal-plan.xz
- echo 'de73600b1836d3f55e32d80385acc055fd97f60eaa0ab68a755302685f5d81bc cabal-plan.xz' | sha256sum -c -
+ curl -sL https://github.com/haskell-hvr/cabal-plan/releases/download/v0.7.3.0/cabal-plan-0.7.3.0-x86_64-linux.xz > cabal-plan.xz
+ echo 'f62ccb2971567a5f638f2005ad3173dba14693a45154c1508645c52289714cb2 cabal-plan.xz' | sha256sum -c -
xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan
rm -f cabal-plan.xz
chmod a+x $HOME/.cabal/bin/cabal-plan
diff --git a/hackage-server.cabal b/hackage-server.cabal
index f99a971c9..e0479f091 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -27,7 +27,13 @@ copyright: 2008-2015 Duncan Coutts,
license: BSD-3-Clause
license-file: LICENSE
-tested-with: GHC == { 9.6.1, 9.4.4, 9.2.7, 9.0.2, 8.10.7, 8.8.4 }
+tested-with:
+ GHC == 9.6.2
+ GHC == 9.4.5
+ GHC == 9.2.8
+ GHC == 9.0.2
+ GHC == 8.10.7
+ GHC == 8.8.4
data-dir: datafiles
data-files:
From e5dd400eeab60c1d0b64d2b967f1eb05b4a3901f Mon Sep 17 00:00:00 2001
From: Andreas Abel
Date: Thu, 3 Aug 2023 16:48:34 +0200
Subject: [PATCH 18/71] CI: bump cabal.yml to GHC 9.6.2 and cabal 3.10.1.0
---
.github/workflows/cabal.yml | 57 +++++++++++++++++++------------------
1 file changed, 30 insertions(+), 27 deletions(-)
diff --git a/.github/workflows/cabal.yml b/.github/workflows/cabal.yml
index 2f49c5b5b..e4c404a13 100644
--- a/.github/workflows/cabal.yml
+++ b/.github/workflows/cabal.yml
@@ -14,14 +14,14 @@ jobs:
strategy:
fail-fast: false
matrix:
- ghc: ['9.4.4', '9.2.7', '9.0.2']
- cabal: ['3.8.1.0']
+ ghc: ['9.6.2', '9.4.5', '9.2.8', '9.0.2']
+ cabal: ['3.10.1.0']
os: [ubuntu-latest]
name: Cabal with GHC ${{ matrix.ghc }}
steps:
- uses: actions/checkout@v3
- name: Setup Haskell
- uses: haskell/actions/setup@v2
+ uses: haskell-actions/setup@v2
with:
ghc-version: ${{ matrix.ghc }}
cabal-version: ${{ matrix.cabal }}
@@ -33,27 +33,30 @@ jobs:
run: cabal test all --enable-tests
- name: Haddock
run: cabal haddock all
- build_with_mtl_2_3:
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- ghc: ['9.4.4']
- cabal: ['3.8.1.0']
- os: [ubuntu-latest]
- name: Cabal with GHC ${{ matrix.ghc }} and mtl >= 2.3.1
- steps:
- - uses: actions/checkout@v3
- - name: Setup Haskell
- uses: haskell/actions/setup@v2
- with:
- ghc-version: ${{ matrix.ghc }}
- cabal-version: ${{ matrix.cabal }}
- - name: Install dependencies
- run: sudo apt install -y libbrotli-dev libgd-dev
- - name: Build dependencies with mtl >= 2.3.1
- # 2022-12-30: 'transformers >= 0.6' is needed because of happstack-server
- run: cabal build all --disable-tests --dependencies-only -O0 --constraint 'mtl >= 2.3.1' --constraint 'transformers >= 0.6' --allow-newer='Cabal:mtl' --allow-newer='Cabal:transformers'
- - name: Build with mtl >= 2.3.1
- # 2022-12-30: 'transformers >= 0.6' is needed because of happstack-server
- run: cabal build all --disable-tests -O0 --constraint 'mtl >= 2.3.1' --constraint 'transformers >= 0.6' --allow-newer='Cabal:mtl' --allow-newer='Cabal:transformers'
+
+ ## Andreas, 2023-08-03: mtl-2.3 is covered by GHC 9.6
+ #
+ # build_with_mtl_2_3:
+ # runs-on: ${{ matrix.os }}
+ # strategy:
+ # fail-fast: false
+ # matrix:
+ # ghc: ['9.4.4']
+ # cabal: ['3.8.1.0']
+ # os: [ubuntu-latest]
+ # name: Cabal with GHC ${{ matrix.ghc }} and mtl >= 2.3.1
+ # steps:
+ # - uses: actions/checkout@v3
+ # - name: Setup Haskell
+ # uses: haskell/actions/setup@v2
+ # with:
+ # ghc-version: ${{ matrix.ghc }}
+ # cabal-version: ${{ matrix.cabal }}
+ # - name: Install dependencies
+ # run: sudo apt install -y libbrotli-dev libgd-dev
+ # - name: Build dependencies with mtl >= 2.3.1
+ # # 2022-12-30: 'transformers >= 0.6' is needed because of happstack-server
+ # run: cabal build all --disable-tests --dependencies-only -O0 --constraint 'mtl >= 2.3.1' --constraint 'transformers >= 0.6' --allow-newer='Cabal:mtl' --allow-newer='Cabal:transformers'
+ # - name: Build with mtl >= 2.3.1
+ # # 2022-12-30: 'transformers >= 0.6' is needed because of happstack-server
+ # run: cabal build all --disable-tests -O0 --constraint 'mtl >= 2.3.1' --constraint 'transformers >= 0.6' --allow-newer='Cabal:mtl' --allow-newer='Cabal:transformers'
From e7f3a553afd18d70bb96d291e477934f661a6434 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Sat, 12 Aug 2023 18:25:48 -0700
Subject: [PATCH 19/71] fix `flake.nix`
https://github.com/haskell/hackage-server/issues/1237
---
flake.lock | 135 ++-----------------------------------------
flake.nix | 70 +---------------------
hackage-server.cabal | 4 +-
3 files changed, 8 insertions(+), 201 deletions(-)
diff --git a/flake.lock b/flake.lock
index 113d672e4..06592f6eb 100644
--- a/flake.lock
+++ b/flake.lock
@@ -48,94 +48,6 @@
"type": "github"
}
},
- "hls-floskell-plugin": {
- "flake": false,
- "locked": {
- "dir": "plugins/hls-floskell-plugin",
- "lastModified": 1682176345,
- "narHash": "sha256-WmkHsjI0HgdAK+EY45pHsPUAOAoyDSTj+bpB3v3T+/g=",
- "owner": "haskell",
- "repo": "haskell-language-server",
- "rev": "6640ebf33eeb8a62ccd094e2f47f69106acfd200",
- "type": "github"
- },
- "original": {
- "dir": "plugins/hls-floskell-plugin",
- "owner": "haskell",
- "repo": "haskell-language-server",
- "type": "github"
- }
- },
- "hls-graph": {
- "flake": false,
- "locked": {
- "dir": "hls-graph",
- "lastModified": 1682176345,
- "narHash": "sha256-WmkHsjI0HgdAK+EY45pHsPUAOAoyDSTj+bpB3v3T+/g=",
- "owner": "haskell",
- "repo": "haskell-language-server",
- "rev": "6640ebf33eeb8a62ccd094e2f47f69106acfd200",
- "type": "github"
- },
- "original": {
- "dir": "hls-graph",
- "owner": "haskell",
- "repo": "haskell-language-server",
- "type": "github"
- }
- },
- "hls-hlint-plugin": {
- "flake": false,
- "locked": {
- "dir": "plugins/hls-hlint-plugin",
- "lastModified": 1682176345,
- "narHash": "sha256-WmkHsjI0HgdAK+EY45pHsPUAOAoyDSTj+bpB3v3T+/g=",
- "owner": "haskell",
- "repo": "haskell-language-server",
- "rev": "6640ebf33eeb8a62ccd094e2f47f69106acfd200",
- "type": "github"
- },
- "original": {
- "dir": "plugins/hls-hlint-plugin",
- "owner": "haskell",
- "repo": "haskell-language-server",
- "type": "github"
- }
- },
- "hls-ormolu-plugin": {
- "flake": false,
- "locked": {
- "dir": "plugins/hls-ormolu-plugin",
- "lastModified": 1682176345,
- "narHash": "sha256-WmkHsjI0HgdAK+EY45pHsPUAOAoyDSTj+bpB3v3T+/g=",
- "owner": "haskell",
- "repo": "haskell-language-server",
- "rev": "6640ebf33eeb8a62ccd094e2f47f69106acfd200",
- "type": "github"
- },
- "original": {
- "dir": "plugins/hls-ormolu-plugin",
- "owner": "haskell",
- "repo": "haskell-language-server",
- "type": "github"
- }
- },
- "linear-generics": {
- "flake": false,
- "locked": {
- "lastModified": 1679794303,
- "narHash": "sha256-2PtZRHVvS0aP2xKV68VE5NmwjK7g/xVIW7gZ0jiulgg=",
- "owner": "linear-generics",
- "repo": "linear-generics",
- "rev": "b3ed84a5c2438f6c000496322a4aa707363c4c7d",
- "type": "github"
- },
- "original": {
- "owner": "linear-generics",
- "repo": "linear-generics",
- "type": "github"
- }
- },
"mission-control": {
"locked": {
"lastModified": 1682001320,
@@ -153,16 +65,16 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1682109806,
- "narHash": "sha256-d9g7RKNShMLboTWwukM+RObDWWpHKaqTYXB48clBWXI=",
+ "lastModified": 1691885509,
+ "narHash": "sha256-MCKEstJdWdlUnh3V34SpOZTQj4bOeada+xhDh9U/0y8=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "2362848adf8def2866fabbffc50462e929d7fffb",
+ "rev": "163a5a5675d7e95d3856cf6ae2a26227f25a96f8",
"type": "github"
},
"original": {
"owner": "nixos",
- "ref": "nixpkgs-unstable",
+ "ref": "haskell-updates",
"repo": "nixpkgs",
"type": "github"
}
@@ -216,56 +128,17 @@
"type": "github"
}
},
- "nothunks": {
- "flake": false,
- "locked": {
- "lastModified": 1680263759,
- "narHash": "sha256-LhEmrkKcUk84PoKmeS4HhZjwTbRbKKEZB0O2hAxLkEQ=",
- "owner": "input-output-hk",
- "repo": "nothunks",
- "rev": "006536813c1bfbd1db8f2755734540e654baca05",
- "type": "github"
- },
- "original": {
- "owner": "input-output-hk",
- "repo": "nothunks",
- "type": "github"
- }
- },
"root": {
"inputs": {
"flake-parts": "flake-parts",
"flake-root": "flake-root",
"haskell-flake": "haskell-flake",
- "hls-floskell-plugin": "hls-floskell-plugin",
- "hls-graph": "hls-graph",
- "hls-hlint-plugin": "hls-hlint-plugin",
- "hls-ormolu-plugin": "hls-ormolu-plugin",
- "linear-generics": "linear-generics",
"mission-control": "mission-control",
"nixpkgs": "nixpkgs",
"nixpkgs-140774-workaround": "nixpkgs-140774-workaround",
- "nothunks": "nothunks",
- "stylish-haskell": "stylish-haskell",
"treefmt-nix": "treefmt-nix"
}
},
- "stylish-haskell": {
- "flake": false,
- "locked": {
- "lastModified": 1678437708,
- "narHash": "sha256-XO4HCG1hWwEVr765GjfJwvLOCwICOZ/MkCj1xP2b/w8=",
- "owner": "haskell",
- "repo": "stylish-haskell",
- "rev": "13f1db77f28b62ac6da6abb3c82244d5e740503b",
- "type": "github"
- },
- "original": {
- "owner": "haskell",
- "repo": "stylish-haskell",
- "type": "github"
- }
- },
"treefmt-nix": {
"inputs": {
"nixpkgs": "nixpkgs_2"
diff --git a/flake.nix b/flake.nix
index cbac13ae7..8e81f1b39 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,43 +1,12 @@
{
inputs = {
- nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
+ nixpkgs.url = "github:nixos/nixpkgs/haskell-updates";
flake-parts.url = "github:hercules-ci/flake-parts";
haskell-flake.url = "github:srid/haskell-flake";
treefmt-nix.url = "github:numtide/treefmt-nix";
flake-root.url = "github:srid/flake-root";
mission-control.url = "github:Platonic-Systems/mission-control";
- linear-generics = {
- url = "github:linear-generics/linear-generics";
- flake = false;
- };
-
- stylish-haskell = {
- url = "github:haskell/stylish-haskell";
- flake = false;
- };
-
- nothunks = {
- url = "github:input-output-hk/nothunks";
- flake = false;
- };
-
- hls-hlint-plugin = {
- url = "github:haskell/haskell-language-server?dir=plugins/hls-hlint-plugin";
- flake = false;
- };
- hls-floskell-plugin = {
- url = "github:haskell/haskell-language-server?dir=plugins/hls-floskell-plugin";
- flake = false;
- };
- hls-ormolu-plugin = {
- url = "github:haskell/haskell-language-server?dir=plugins/hls-ormolu-plugin";
- flake = false;
- };
- hls-graph = {
- url = "github:haskell/haskell-language-server?dir=hls-graph";
- flake = false;
- };
nixpkgs-140774-workaround.url = "github:srid/nixpkgs-140774-workaround";
};
@@ -59,45 +28,10 @@
inputs.nixpkgs-140774-workaround.haskellFlakeProjectModules.default
];
packages.hackage-server.root = ./.; # Auto-discovered by haskell-flake
+
overrides = self: super: {
Cabal = super.Cabal_3_10_1_0;
Cabal-syntax = super.Cabal-syntax_3_10_1_0;
- doctest-parallel = super.doctest-parallel_0_3_0_1;
-
- ghc-lib-parser = super.ghc-lib-parser_9_4_4_20221225;
- ghc-lib-parser-ex = super.ghc-lib-parser-ex_9_4_0_0;
- text = super.text_2_0_2;
- parsec = self.callHackage "parsec" "3.1.16.1" {};
-
- chell = pkgs.haskell.lib.doJailbreak (self.callHackage "chell" "0.5.0.1" {});
- ghc-boot-th = self.callHackage "ghc-boot-th" "9.2.1" {};
- hedgehog = self.callHackage "hedgehog" "1.2" {};
- tasty-hedgehog = self.callHackage "tasty-hedgehog" "1.4.0.0" {};
- optparse-applicative = pkgs.haskell.lib.doJailbreak (super.optparse-applicative_0_15_1_0);
- haddock-library = pkgs.haskell.lib.doJailbreak (self.callHackage "haddock-library" "1.11.0" {});
-
- th-abstraction = self.callHackage "th-abstraction" "0.4.5.0" {};
- stylish-haskell = super.callCabal2nix "stylish-haskell" inputs.stylish-haskell {};
-
- nothunks = super.callCabal2nix "nothunks" inputs.nothunks {};
-
- # requirements of HLS
- # TODO: fix HLS https://github.com/haskell/haskell-language-server/issues/3518
- ormolu = self.callHackage "ormolu" "0.5.3.0" {};
- fourmolu = pkgs.haskell.lib.dontCheck (self.callHackage "fourmolu" "0.10.1.0" {});
- # hls-floskell-plugin = pkgs.haskell.lib.dontCheck (self.callHackage "hls-floskell-plugin" "1.0.2.0" {});
- # hls-floskell-plugin = self.callCabal2nix "hls-floskell-plugin" inputs.hls-floskell-plugin {};
- # hls-graph = self.callCabal2nix "hls-graph" inputs.hls-graph {};
- # hls-graph = self.callHackage "hls-graph" "1.9.0.0" {};
- # hls-hlint-plugin = self.callCabal2nix "hls-hlint-plugin" inputs.hls-hlint-plugin {};
- # hls-hlint-plugin = self.callHackage "hls-hlint-plugin" "1.1.2.0" {};
- # hls-ormolu-plugin = self.callCabal2nix "hls-ormolu-plugin" inputs.hls-ormolu-plugin {};
- # hls-ormolu-plugin = self.callHackage "hls-ormolu-plugin" "1.0.2.2" {};
- # hls-ormolu-plugin = self.callHackage "hls-ormolu-plugin" "1.0.3.0" {};
- # hls-plugin-api = self.callHackage "hls-plugin-api" "1.6.0.0" {};
- # hls-test-utils = self.callHackage "hls-test-utils" "1.5.0.0" {};
-
- hlint = self.callHackage "hlint" "3.5" {};
ghcide = pkgs.haskell.lib.dontCheck (self.callHackage "ghcide" "1.9.0.0" {});
};
diff --git a/hackage-server.cabal b/hackage-server.cabal
index e0479f091..08a90264b 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -135,7 +135,7 @@ common defaults
, scientific
-- other dependencies shared by most components
build-depends:
- , aeson ^>= 2.2.0.0
+ , aeson >= 2.1.0.0
, Cabal >= 3.10.1.0 && < 3.12
, Cabal-syntax >= 3.10.1.0 && < 3.12
-- Cabal-syntax needs to be bound to constrain hackage-security
@@ -553,7 +553,7 @@ test-suite HighLevelTest
build-depends:
-- version constraints inherited from lib-server
, HTTP
- , attoparsec-aeson ^>= 2.2.0.0
+ , attoparsec-aeson >= 2.1.0.0
, base64-bytestring
, random
-- component-specific dependencies
From 1d82ded8b583592b8dd1e75a93f5314fbb712548 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Sat, 12 Aug 2023 18:34:28 -0700
Subject: [PATCH 20/71] disable `fail-fast` for Flake GitHub Action
---
.github/workflows/nix-flake.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml
index 7324c67b6..040cdf5f2 100644
--- a/.github/workflows/nix-flake.yml
+++ b/.github/workflows/nix-flake.yml
@@ -9,7 +9,7 @@ on:
jobs:
nix:
strategy:
- fail-fast: true
+ fail-fast: false
matrix:
os:
- ubuntu-latest
From 6bc9d71c0f7a39bda6254128c603f05a28753ebe Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Sat, 12 Aug 2023 18:52:06 -0700
Subject: [PATCH 21/71] attempt to fix `nix build` on Mac
---
flake.nix | 3 +++
1 file changed, 3 insertions(+)
diff --git a/flake.nix b/flake.nix
index 8e81f1b39..77f64c5e3 100644
--- a/flake.nix
+++ b/flake.nix
@@ -34,6 +34,9 @@
Cabal-syntax = super.Cabal-syntax_3_10_1_0;
ghcide = pkgs.haskell.lib.dontCheck (self.callHackage "ghcide" "1.9.0.0" {});
+
+ streamly = self.callHackage "streamly" "0.9.0" {};
+ streamly_0_9_0 = self.callHackage "streamly" "0.9.0" {};
};
devShell = {
hlsCheck.enable = false;
From 99ad0125720873bdb452ea63f83d6cd7a0323980 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Thu, 17 Aug 2023 18:19:56 -0700
Subject: [PATCH 22/71] upper bound on `aeson`
---
hackage-server.cabal | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hackage-server.cabal b/hackage-server.cabal
index 08a90264b..8c3f2e6a2 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -135,7 +135,7 @@ common defaults
, scientific
-- other dependencies shared by most components
build-depends:
- , aeson >= 2.1.0.0
+ , aeson >= 2.1.0.0 && < 2.3
, Cabal >= 3.10.1.0 && < 3.12
, Cabal-syntax >= 3.10.1.0 && < 3.12
-- Cabal-syntax needs to be bound to constrain hackage-security
@@ -553,7 +553,7 @@ test-suite HighLevelTest
build-depends:
-- version constraints inherited from lib-server
, HTTP
- , attoparsec-aeson >= 2.1.0.0
+ , attoparsec-aeson >= 2.1.0.0 && < 2.3
, base64-bytestring
, random
-- component-specific dependencies
From f01e00e872f95d88d891efcb3663d373e34bceba Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Wed, 16 Aug 2023 12:04:12 -0700
Subject: [PATCH 23/71] Update README for Mac
---
README.md | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/README.md b/README.md
index 6e7995d78..ba9979ac3 100644
--- a/README.md
+++ b/README.md
@@ -89,6 +89,33 @@ You'll need to do the following to get `hackage-server`'s dependency `hs-captcha
nix-shell --packages zlib
+#### Mac OS X
+
+In addition to the above commands, you'll need to run
+
+```bash
+brew install pkg-config
+```
+
+After running the above `brew install` commands, you also need to update `cabal.project.local` with the following:
+
+```bash
+cat >> cabal.project.local <
Date: Wed, 16 Aug 2023 14:00:09 -0700
Subject: [PATCH 24/71] Minor spelling improvements
---
src/Distribution/Server/Features/UserNotify.hs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index eabc2b49c..6e4383050 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -846,11 +846,11 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
(Admin_GroupAddUser tn (MaintainerGroup pkg)) -> Just $
"Group modified by " ++ formatTimeUser users time uid ++ ":\n" ++
display (Users.userIdToName users tn) ++ " added to maintainers for " ++ BS.unpack pkg ++
- "\n" ++ "reason: " ++ BS.unpack descr
+ "\n" ++ "Reason: " ++ BS.unpack descr
(Admin_GroupDelUser tn (MaintainerGroup pkg)) -> Just $
"Group modified by " ++ formatTimeUser users time uid ++ ":\n" ++
display (Users.userIdToName users tn) ++ " removed from maintainers for " ++ BS.unpack pkg ++
- "\n" ++ "reason: " ++ BS.unpack descr
+ "\n" ++ "Reason: " ++ BS.unpack descr
_ -> Nothing
describeDocReport (pkg, doc) =
@@ -860,9 +860,9 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
else "Build failed."
describeTagProposal (pkgName, (addTags, delTags)) =
- "Pending tag propasal for " ++ display pkgName ++ ":\n" ++
- "Addition: " ++ showTags addTags ++ "\n" ++
- "Deletion: " ++ showTags delTags
+ "Pending tag proposal for " ++ display pkgName ++ ":\n" ++
+ "Additions: " ++ showTags addTags ++ "\n" ++
+ "Deletions: " ++ showTags delTags
where
showTags = intercalate ", " . map display . Set.toList
From 53ef87d492fee494a4b243b56de529ce8f5ce068 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 09:51:13 -0700
Subject: [PATCH 25/71] Use sortOn instead of sortBy
---
src/Distribution/Server/Features/UserNotify.hs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 6e4383050..011550da5 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -58,10 +58,10 @@ import Data.Bifunctor (Bifunctor(second))
import Data.Bimap (lookup, lookupR)
import Data.Graph (Vertex)
import Data.Hashable (Hashable(..))
-import Data.List (maximumBy, sortBy)
+import Data.List (maximumBy, sortOn)
import Data.List (intercalate)
import Data.Maybe (fromJust, fromMaybe, listToMaybe, mapMaybe, maybeToList)
-import Data.Ord (comparing)
+import Data.Ord (Down(..), comparing)
import Data.SafeCopy (Migrate(migrate), MigrateFrom, base, deriveSafeCopy, extension)
import Data.Time (UTCTime(..), addUTCTime, defaultTimeLocale, diffUTCTime, formatTime, getCurrentTime)
import Data.Time.Format.Internal (buildTime)
@@ -505,7 +505,7 @@ dependencyReleaseEmails userSetIdForPackage index (ReverseIndex revs nodemap dep
case notifyDependencyTriggerBounds of
NewIncompatibility -> do
let allNewUploadPkgInfos = PackageIndex.lookupPackageName index (pkgName pkgId)
- sortedByVersionDesc = sortBy (flip $ comparing packageVersion) allNewUploadPkgInfos
+ sortedByVersionDesc = sortOn (Down . packageVersion) allNewUploadPkgInfos
mSecondHighest =
case sortedByVersionDesc of
_:b:_ -> Just b
From 58a197329b58a6002e63f30e37ec25de7b7f65a2 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Wed, 16 Aug 2023 14:06:02 -0700
Subject: [PATCH 26/71] Consolidate dependencyEmailMap => dependencyEmailMaps
---
src/Distribution/Server/Features/UserNotify.hs | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 011550da5..85bc197bc 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -702,10 +702,8 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
genEmails :: PackageIdentifier -> IO (Map.Map (UserId, PackageId) [PackageId])
genEmails =
dependencyReleaseEmails (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref
- dependencyEmailMaps <- traverse (genEmails . pkgInfoToPkgId) revisionsAndUploads
+ dependencyEmailMap <- Map.unionsWith (++) <$> traverse (genEmails . pkgInfoToPkgId) revisionsAndUploads
let
- dependencyEmailMap :: Map.Map (UserId, PackageId) [PackageId]
- dependencyEmailMap = Map.unionsWith (++) dependencyEmailMaps
emailText :: MonadIO m => (UserId, PackageId) -> [PackageId] -> m [String]
emailText (uId, dep) revDeps = do
mPrefs <- queryGetUserNotifyPref uId
From 0f21fe94f73ef9b605061e107a3773d46ed012d2 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Wed, 16 Aug 2023 14:07:28 -0700
Subject: [PATCH 27/71] Move + rename emailText => describeDependencyUpdate
---
.../Server/Features/UserNotify.hs | 63 +++++++++----------
1 file changed, 31 insertions(+), 32 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 85bc197bc..2bd8dba1e 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -703,44 +703,14 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
genEmails =
dependencyReleaseEmails (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref
dependencyEmailMap <- Map.unionsWith (++) <$> traverse (genEmails . pkgInfoToPkgId) revisionsAndUploads
- let
- emailText :: MonadIO m => (UserId, PackageId) -> [PackageId] -> m [String]
- emailText (uId, dep) revDeps = do
- mPrefs <- queryGetUserNotifyPref uId
- pure $
- case mPrefs of
- Nothing -> []
- Just NotifyPref{notifyDependencyTriggerBounds} ->
- [ "The dependency " <> display dep <> " has been updated."
- ] ++
- case notifyDependencyTriggerBounds of
- Always ->
- [ "You have requested to be notified for each upload/revision of a dependency. \
- \These are your packages that depend on " <> display dep <> ":"
- ]
- outOfRangeOption ->
- [ "You have requested to be notified when a dependency isn't accepted by any of \
- \your maintained packages."
- ] ++
- case outOfRangeOption of
- NewIncompatibility ->
- [ "The following packages did accept the second highest version of "
- <> display (packageName dep) <> "."
- ]
- _ ->
- []
- ++
- [ "These are your packages that require " <> display (packageName dep) <> " but don't accept " <> display (packageVersion dep) <> ":"
- ]
- ++ map display revDeps
- dependencyEmailTextMaps <- Map.mapKeys fst <$> Map.traverseWithKey emailText dependencyEmailMap
+ dependencyEmails <- Map.mapKeys fst <$> Map.traverseWithKey describeDependencyUpdate dependencyEmailMap
-- Concat the constituent email parts such that only one email is sent per user
mapM_ (sendNotifyEmailAndDelay users) . Map.toList $ foldr1 (Map.unionWith (++)) $ [revisionUploadEmails, groupActionEmails, docReportEmails, tagProposalEmails]
-- Dependency email notifications consist of multiple paragraphs, so it would be confusing if concatenated.
-- So they're sent independently.
- mapM_ (sendNotifyEmailAndDelay users) . Map.toList $ dependencyEmailTextMaps
+ mapM_ (sendNotifyEmailAndDelay users) . Map.toList $ dependencyEmails
updateState notifyState (SetNotifyTime now)
@@ -864,6 +834,35 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
where
showTags = intercalate ", " . map display . Set.toList
+ describeDependencyUpdate (uId, dep) revDeps = do
+ mPrefs <- queryGetUserNotifyPref uId
+ pure $
+ case mPrefs of
+ Nothing -> []
+ Just NotifyPref{notifyDependencyTriggerBounds} ->
+ [ "The dependency " <> display dep <> " has been updated."
+ ] ++
+ case notifyDependencyTriggerBounds of
+ Always ->
+ [ "You have requested to be notified for each upload/revision of a dependency. \
+ \These are your packages that depend on " <> display dep <> ":"
+ ]
+ outOfRangeOption ->
+ [ "You have requested to be notified when a dependency isn't accepted by any of \
+ \your maintained packages."
+ ] ++
+ case outOfRangeOption of
+ NewIncompatibility ->
+ [ "The following packages did accept the second highest version of "
+ <> display (packageName dep) <> "."
+ ]
+ _ ->
+ []
+ ++
+ [ "These are your packages that require " <> display (packageName dep) <> " but don't accept " <> display (packageVersion dep) <> ":"
+ ]
+ ++ map display revDeps
+
sendNotifyEmailAndDelay :: Users.Users -> (UserId, [String]) -> IO ()
sendNotifyEmailAndDelay users (uid, ebody) = do
mudetails <- queryUserDetails uid
From 7a92fc1c9baafdc29c413c0340361c62f70f757d Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 10:04:11 -0700
Subject: [PATCH 28/71] Move + rename genEmails => genDependencyUpdateList
---
src/Distribution/Server/Features/UserNotify.hs | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 2bd8dba1e..9daa94d32 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -698,12 +698,8 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
idx <- queryGetPackageIndex
revIdx <- liftIO queryReverseIndex
- let
- genEmails :: PackageIdentifier -> IO (Map.Map (UserId, PackageId) [PackageId])
- genEmails =
- dependencyReleaseEmails (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref
- dependencyEmailMap <- Map.unionsWith (++) <$> traverse (genEmails . pkgInfoToPkgId) revisionsAndUploads
- dependencyEmails <- Map.mapKeys fst <$> Map.traverseWithKey describeDependencyUpdate dependencyEmailMap
+ dependencyUpdateNotifications <- Map.unionsWith (++) <$> traverse (genDependencyUpdateList idx revIdx . pkgInfoToPkgId) revisionsAndUploads
+ dependencyEmails <- Map.mapKeys fst <$> Map.traverseWithKey describeDependencyUpdate dependencyUpdateNotifications
-- Concat the constituent email parts such that only one email is sent per user
mapM_ (sendNotifyEmailAndDelay users) . Map.toList $ foldr1 (Map.unionWith (++)) $ [revisionUploadEmails, groupActionEmails, docReportEmails, tagProposalEmails]
@@ -799,6 +795,9 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
maintainers <- queryUserGroup $ maintainersGroup (fst pkgTags)
return $ foldr addNotification mp (toList maintainers)
+ genDependencyUpdateList idx revIdx =
+ dependencyReleaseEmails (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref
+
describeRevision users earlier now pkg =
if pkgNumRevisions pkg <= 1
then "Package upload, " ++ display (packageName pkg) ++ ", by " ++
From e652a607f3b74f440f6475172d8eae08d4fc3335 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Wed, 16 Aug 2023 14:17:17 -0700
Subject: [PATCH 29/71] Improve email subject for dep update emails
---
.../Server/Features/UserNotify.hs | 21 ++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 9daa94d32..56565bb6f 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -699,14 +699,22 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
idx <- queryGetPackageIndex
revIdx <- liftIO queryReverseIndex
dependencyUpdateNotifications <- Map.unionsWith (++) <$> traverse (genDependencyUpdateList idx revIdx . pkgInfoToPkgId) revisionsAndUploads
- dependencyEmails <- Map.mapKeys fst <$> Map.traverseWithKey describeDependencyUpdate dependencyUpdateNotifications
+ dependencyEmails <- Map.traverseWithKey describeDependencyUpdate dependencyUpdateNotifications
-- Concat the constituent email parts such that only one email is sent per user
- mapM_ (sendNotifyEmailAndDelay users) . Map.toList $ foldr1 (Map.unionWith (++)) $ [revisionUploadEmails, groupActionEmails, docReportEmails, tagProposalEmails]
+ mapM_ (sendNotifyEmailAndDelay users) . Map.toList $
+ fmap ("Maintainer Notifications",) . foldr1 (Map.unionWith (++)) $
+ [ revisionUploadEmails
+ , groupActionEmails
+ , docReportEmails
+ , tagProposalEmails
+ ]
-- Dependency email notifications consist of multiple paragraphs, so it would be confusing if concatenated.
-- So they're sent independently.
- mapM_ (sendNotifyEmailAndDelay users) . Map.toList $ dependencyEmails
+ mapM_ (sendNotifyEmailAndDelay users) . Map.toList $
+ Map.mapKeys fst . Map.mapWithKey (\(_, dep) ebody -> ("Dependency Update: " <> T.pack (display dep), ebody)) $
+ dependencyEmails
updateState notifyState (SetNotifyTime now)
@@ -862,8 +870,8 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
]
++ map display revDeps
- sendNotifyEmailAndDelay :: Users.Users -> (UserId, [String]) -> IO ()
- sendNotifyEmailAndDelay users (uid, ebody) = do
+ sendNotifyEmailAndDelay :: Users.Users -> (UserId, (T.Text, [String])) -> IO ()
+ sendNotifyEmailAndDelay users (uid, (subject, ebody)) = do
mudetails <- queryUserDetails uid
case mudetails of
Nothing -> return ()
@@ -872,8 +880,7 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
(T.pack ("noreply@" ++ uriRegName ourHost))
mail = (emptyMail mailFrom) {
mailTo = [Address (Just aname) eml],
- mailHeaders = [(BSS.pack "Subject",
- T.pack "[Hackage] Maintainer Notifications")],
+ mailHeaders = [(BSS.pack "Subject", "[Hackage] " <> subject)],
mailParts = [[Part (T.pack "text/plain; charset=utf-8")
None DefaultDisposition []
(PartContent $ BS.pack $
From fb09c8b8f3787027dc3cf1d3c5c7d79f619fb881 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 15:58:34 -0700
Subject: [PATCH 30/71] Simplify describeDependencyUpdate
---
.../Server/Features/UserNotify.hs | 43 ++++++++++---------
1 file changed, 22 insertions(+), 21 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 56565bb6f..5d8de66a0 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -847,27 +847,28 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
case mPrefs of
Nothing -> []
Just NotifyPref{notifyDependencyTriggerBounds} ->
- [ "The dependency " <> display dep <> " has been updated."
- ] ++
- case notifyDependencyTriggerBounds of
- Always ->
- [ "You have requested to be notified for each upload/revision of a dependency. \
- \These are your packages that depend on " <> display dep <> ":"
- ]
- outOfRangeOption ->
- [ "You have requested to be notified when a dependency isn't accepted by any of \
- \your maintained packages."
- ] ++
- case outOfRangeOption of
- NewIncompatibility ->
- [ "The following packages did accept the second highest version of "
- <> display (packageName dep) <> "."
- ]
- _ ->
- []
- ++
- [ "These are your packages that require " <> display (packageName dep) <> " but don't accept " <> display (packageVersion dep) <> ":"
- ]
+ let depName = display (packageName dep)
+ depVersion = display (packageVersion dep)
+ in
+ [ "The dependency " <> display dep <> " has been uploaded or revised."
+ , case notifyDependencyTriggerBounds of
+ Always ->
+ "You have requested to be notified for each upload or revision \
+ \of a dependency."
+ _ ->
+ "You have requested to be notified when a dependency isn't \
+ \accepted by any of your maintained packages."
+ , case notifyDependencyTriggerBounds of
+ Always ->
+ "These are your packages that depend on " <> depName <> ":"
+ BoundsOutOfRange ->
+ "These are your packages that require " <> depName
+ <> " but don't accept " <> depVersion <> ":"
+ NewIncompatibility ->
+ "The following packages require " <> depName
+ <> " but don't accept " <> depVersion
+ <> " (they do accept the second-highest version):"
+ ]
++ map display revDeps
sendNotifyEmailAndDelay :: Users.Users -> (UserId, (T.Text, [String])) -> IO ()
From 5e48f72ff224b8f7fddaa195e2d090b8ac100afe Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 16:02:11 -0700
Subject: [PATCH 31/71] Refactor message generation
---
.../Server/Features/UserNotify.hs | 49 +++++++++++--------
1 file changed, 28 insertions(+), 21 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 5d8de66a0..4a913ed90 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -806,31 +806,40 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
genDependencyUpdateList idx revIdx =
dependencyReleaseEmails (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref
- describeRevision users earlier now pkg =
- if pkgNumRevisions pkg <= 1
- then "Package upload, " ++ display (packageName pkg) ++ ", by " ++
- formatTimeUser users (pkgLatestUploadTime pkg) (pkgLatestUploadUser pkg)
- else "Package metadata revision(s), " ++ display (packageName pkg) ++ ":\n" ++
- unlines (map (uncurry (formatTimeUser users) . snd) recentRevs)
+ describeRevision users earlier now pkg
+ | pkgNumRevisions pkg <= 1 =
+ "Package upload, " ++ display (packageName pkg) ++ ", by " ++
+ formatTimeUser users (pkgLatestUploadTime pkg) (pkgLatestUploadUser pkg)
+ | otherwise =
+ "Package metadata revision(s), " ++ display (packageName pkg) ++ ":\n" ++
+ unlines (map (uncurry (formatTimeUser users) . snd) recentRevs)
where
revs = reverse $ Vec.toList (pkgMetadataRevisions pkg)
recentRevs = filter ((\x -> x > earlier && x <= now) . fst . snd) revs
- describeGroupAction users (time, uid, act, descr) =
- case act of
- (Admin_GroupAddUser tn (MaintainerGroup pkg)) -> Just $
- "Group modified by " ++ formatTimeUser users time uid ++ ":\n" ++
- display (Users.userIdToName users tn) ++ " added to maintainers for " ++ BS.unpack pkg ++
- "\n" ++ "Reason: " ++ BS.unpack descr
- (Admin_GroupDelUser tn (MaintainerGroup pkg)) -> Just $
- "Group modified by " ++ formatTimeUser users time uid ++ ":\n" ++
- display (Users.userIdToName users tn) ++ " removed from maintainers for " ++ BS.unpack pkg ++
- "\n" ++ "Reason: " ++ BS.unpack descr
+ describeGroupAction users (time, uid, act, reason) =
+ fmap
+ ( \message ->
+ "Group modified by " ++ formatTimeUser users time uid ++ ":\n"
+ ++ message ++ "\n"
+ ++ "Reason: " ++ BS.unpack reason
+ )
+ $ case act of
+ (Admin_GroupAddUser tn (MaintainerGroup pkg)) ->
+ Just $
+ display (Users.userIdToName users tn)
+ <> " added to maintainers for "
+ <> BS.unpack pkg
+ (Admin_GroupDelUser tn (MaintainerGroup pkg)) ->
+ Just $
+ display (Users.userIdToName users tn)
+ <> " removed from maintainers for "
+ <> BS.unpack pkg
_ -> Nothing
- describeDocReport (pkg, doc) =
+ describeDocReport (pkg, success) =
"Package doc build for " ++ display (packageName pkg) ++ ":\n" ++
- if doc
+ if success
then "Build successful."
else "Build failed."
@@ -885,9 +894,7 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
mailParts = [[Part (T.pack "text/plain; charset=utf-8")
None DefaultDisposition []
(PartContent $ BS.pack $
- intercalate "\n\n" ebody
- <> "\n\n"
- <> adjustmentLinkParagraph
+ intercalate "\n\n" (ebody <> [adjustmentLinkParagraph])
)
]]
}
From 8d62bbe9c7e1845958dacd031a6359c746e2737d Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 15:36:51 -0700
Subject: [PATCH 32/71] Add email markup implementation
---
hackage-server.cabal | 1 +
src/Distribution/Server/Util/Email.hs | 148 ++++++++++++++++++++++++++
2 files changed, 149 insertions(+)
create mode 100644 src/Distribution/Server/Util/Email.hs
diff --git a/hackage-server.cabal b/hackage-server.cabal
index 8c3f2e6a2..98e4731b6 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -265,6 +265,7 @@ library lib-server
Distribution.Server.Util.CountingMap
Distribution.Server.Util.CabalRevisions
Distribution.Server.Util.DocMeta
+ Distribution.Server.Util.Email
Distribution.Server.Util.Parse
Distribution.Server.Util.ServeTarball
Distribution.Server.Util.Validators
diff --git a/src/Distribution/Server/Util/Email.hs b/src/Distribution/Server/Util/Email.hs
new file mode 100644
index 000000000..30fd8bb9c
--- /dev/null
+++ b/src/Distribution/Server/Util/Email.hs
@@ -0,0 +1,148 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Distribution.Server.Util.Email
+ ( EmailContent(..)
+ , emailContentStr
+ , emailContentLBS
+ , emailContentDisplay
+ , emailContentIntercalate
+ , emailContentUrl
+
+ -- * Rendering email content
+ , fromEmailContent
+ , toPlainContent
+ , toHtmlContent
+ ) where
+
+import qualified Data.ByteString.Lazy as Lazy (ByteString)
+import Data.List (intersperse)
+import Data.String (IsString(..))
+import Data.Text (Text)
+import qualified Data.Text as Text
+import qualified Data.Text.Lazy as TextL
+import qualified Data.Text.Lazy.Encoding as TextL
+import Distribution.Pretty (Pretty)
+import Distribution.Text (display)
+import Network.Mail.Mime
+import Network.URI (URI, uriToString)
+
+{- $setup
+>>> :set -XOverloadedStrings
+>>> import qualified Data.Text.IO as Text
+>>> import Network.URI (parseURI)
+-}
+
+data EmailContent
+ = EmailContentText Text
+ | EmailContentLink Text URI
+ | EmailContentSoftBreak
+ | EmailContentParagraph EmailContent
+ | EmailContentList [EmailContent]
+ | EmailContentConcat EmailContent EmailContent
+ deriving (Show)
+
+instance IsString EmailContent where
+ fromString = EmailContentText . Text.pack
+
+instance Semigroup EmailContent where
+ (<>) = EmailContentConcat
+
+instance Monoid EmailContent where
+ mempty = EmailContentText ""
+
+emailContentStr :: String -> EmailContent
+emailContentStr = EmailContentText . Text.pack
+
+emailContentLBS :: Lazy.ByteString -> EmailContent
+emailContentLBS = EmailContentText . TextL.toStrict . TextL.decodeUtf8
+
+emailContentDisplay :: Pretty a => a -> EmailContent
+emailContentDisplay = EmailContentText . Text.pack . display
+
+emailContentIntercalate :: EmailContent -> [EmailContent] -> EmailContent
+emailContentIntercalate x = mconcat . intersperse x
+
+emailContentUrl :: URI -> EmailContent
+emailContentUrl uri = EmailContentLink (uriToText uri) uri
+
+fromEmailContent :: EmailContent -> Alternatives
+fromEmailContent emailContent =
+ [ Part
+ { partType = contentType <> "; charset=utf-8"
+ , partEncoding = None
+ , partDisposition = DefaultDisposition
+ , partHeaders = []
+ , partContent = PartContent $ TextL.encodeUtf8 $ TextL.fromStrict content
+ }
+ | (contentType, content) <- contents
+ ]
+ where
+ contents =
+ [ ("text/plain", toPlainContent emailContent)
+ , ("text/html", toHtmlContent emailContent)
+ ]
+
+-- | Convert an 'EmailContent' to plain text.
+--
+-- >>> let Just haskellURI = parseURI "https://haskell.org"
+-- >>> let Just hackageURI = parseURI "https://hackage.haskell.org"
+-- >>> :{
+-- Text.putStr . toPlainContent . mconcat $
+-- [ EmailContentParagraph "Haskell is fun!"
+-- , EmailContentList
+-- [ "Website: " <> EmailContentLink "haskell.org" haskellURI
+-- , EmailContentLink "Hackage" hackageURI
+-- ]
+-- ]
+-- :}
+-- Haskell is fun!
+--
+-- * Website: haskell.org (https://haskell.org)
+-- * Hackage (https://hackage.haskell.org)
+--
+toPlainContent :: EmailContent -> Text
+toPlainContent = \case
+ EmailContentText s -> s
+ EmailContentLink s uri -> s <> " (" <> uriToText uri <> ")"
+ EmailContentSoftBreak -> "\n"
+ EmailContentParagraph content -> toPlainContent content <> "\n\n"
+ EmailContentList items ->
+ let renderListItem item = "* " <> toPlainContent item
+ in Text.intercalate "\n" (map renderListItem items) <> "\n\n"
+ EmailContentConcat a b -> toPlainContent a <> toPlainContent b
+
+-- | Convert an 'EmailContent' to HTML.
+--
+-- >>> let Just haskellURI = parseURI "https://haskell.org"
+-- >>> let Just hackageURI = parseURI "https://hackage.haskell.org"
+-- >>> :{
+-- Text.putStr . toHtmlContent . mconcat $
+-- [ EmailContentParagraph "Haskell is fun!"
+-- , EmailContentList
+-- [ "Website: " <> EmailContentLink "haskell.org" haskellURI
+-- , EmailContentLink "Hackage" hackageURI
+-- ]
+-- ]
+-- :}
+--
+--
+-- Haskell is fun!
+--
+--
+toHtmlContent :: EmailContent -> Text
+toHtmlContent = \case
+ EmailContentText s -> s
+ EmailContentLink s uri -> " uriToText uri <> "\">" <> s <> " "
+ EmailContentSoftBreak -> "\n "
+ EmailContentParagraph content -> "\n\n" <> toHtmlContent content <> "\n
"
+ EmailContentList items ->
+ let renderListItem item = " " <> toHtmlContent item <> " "
+ in "\n\n" <> Text.unlines (map renderListItem items) <> " "
+ EmailContentConcat a b -> toHtmlContent a <> toHtmlContent b
+
+uriToText :: URI -> Text
+uriToText uri = Text.pack $ uriToString id uri ""
From f39dd8a195cf342f6382b33566f4c0ae5f6ceea6 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 16:08:49 -0700
Subject: [PATCH 33/71] Direct translation from plain text to EmailContent
---
.../Server/Features/UserNotify.hs | 75 ++++++++++---------
1 file changed, 39 insertions(+), 36 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 4a913ed90..af2738416 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -47,6 +47,8 @@ import Distribution.Server.Features.Upload
import Distribution.Server.Features.UserDetails
import Distribution.Server.Features.Users
+import Distribution.Server.Util.Email
+
import qualified Data.Map as Map
import qualified Data.Set as Set
@@ -59,7 +61,6 @@ import Data.Bimap (lookup, lookupR)
import Data.Graph (Vertex)
import Data.Hashable (Hashable(..))
import Data.List (maximumBy, sortOn)
-import Data.List (intercalate)
import Data.Maybe (fromJust, fromMaybe, listToMaybe, mapMaybe, maybeToList)
import Data.Ord (Down(..), comparing)
import Data.SafeCopy (Migrate(migrate), MigrateFrom, base, deriveSafeCopy, extension)
@@ -713,12 +714,13 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
-- Dependency email notifications consist of multiple paragraphs, so it would be confusing if concatenated.
-- So they're sent independently.
mapM_ (sendNotifyEmailAndDelay users) . Map.toList $
- Map.mapKeys fst . Map.mapWithKey (\(_, dep) ebody -> ("Dependency Update: " <> T.pack (display dep), ebody)) $
+ Map.mapKeys fst . Map.mapWithKey (\(_, dep) emailContent -> ("Dependency Update: " <> T.pack (display dep), emailContent)) $
dependencyEmails
updateState notifyState (SetNotifyTime now)
formatTimeUser users t u =
+ EmailContentText . T.pack $
display (Users.userIdToName users u) ++ " [" ++
(formatTime defaultTimeLocale "%c" t) ++ "]"
@@ -808,11 +810,11 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
describeRevision users earlier now pkg
| pkgNumRevisions pkg <= 1 =
- "Package upload, " ++ display (packageName pkg) ++ ", by " ++
- formatTimeUser users (pkgLatestUploadTime pkg) (pkgLatestUploadUser pkg)
+ "Package upload, " <> emailContentDisplay (packageName pkg) <> ", by " <>
+ formatTimeUser users (pkgLatestUploadTime pkg) (pkgLatestUploadUser pkg)
| otherwise =
- "Package metadata revision(s), " ++ display (packageName pkg) ++ ":\n" ++
- unlines (map (uncurry (formatTimeUser users) . snd) recentRevs)
+ "Package metadata revision(s), " <> emailContentDisplay (packageName pkg) <> ":" <> EmailContentSoftBreak
+ <> foldMap (<> EmailContentSoftBreak) (map (uncurry (formatTimeUser users) . snd) recentRevs)
where
revs = reverse $ Vec.toList (pkgMetadataRevisions pkg)
recentRevs = filter ((\x -> x > earlier && x <= now) . fst . snd) revs
@@ -820,35 +822,35 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
describeGroupAction users (time, uid, act, reason) =
fmap
( \message ->
- "Group modified by " ++ formatTimeUser users time uid ++ ":\n"
- ++ message ++ "\n"
- ++ "Reason: " ++ BS.unpack reason
+ "Group modified by " <> formatTimeUser users time uid <> ":" <> EmailContentSoftBreak
+ <> message <> EmailContentSoftBreak
+ <> "Reason: " <> emailContentLBS reason
)
$ case act of
(Admin_GroupAddUser tn (MaintainerGroup pkg)) ->
Just $
- display (Users.userIdToName users tn)
+ emailContentDisplay (Users.userIdToName users tn)
<> " added to maintainers for "
- <> BS.unpack pkg
+ <> emailContentLBS pkg
(Admin_GroupDelUser tn (MaintainerGroup pkg)) ->
Just $
- display (Users.userIdToName users tn)
+ emailContentDisplay (Users.userIdToName users tn)
<> " removed from maintainers for "
- <> BS.unpack pkg
+ <> emailContentLBS pkg
_ -> Nothing
describeDocReport (pkg, success) =
- "Package doc build for " ++ display (packageName pkg) ++ ":\n" ++
+ "Package doc build for " <> emailContentDisplay (packageName pkg) <> ":" <> EmailContentSoftBreak <>
if success
then "Build successful."
else "Build failed."
describeTagProposal (pkgName, (addTags, delTags)) =
- "Pending tag proposal for " ++ display pkgName ++ ":\n" ++
- "Additions: " ++ showTags addTags ++ "\n" ++
- "Deletions: " ++ showTags delTags
+ "Pending tag proposal for " <> emailContentDisplay pkgName <> ":" <> EmailContentSoftBreak
+ <> "Additions: " <> showTags addTags <> EmailContentSoftBreak
+ <> "Deletions: " <> showTags delTags
where
- showTags = intercalate ", " . map display . Set.toList
+ showTags = emailContentIntercalate ", " . map emailContentDisplay . Set.toList
describeDependencyUpdate (uId, dep) revDeps = do
mPrefs <- queryGetUserNotifyPref uId
@@ -856,10 +858,10 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
case mPrefs of
Nothing -> []
Just NotifyPref{notifyDependencyTriggerBounds} ->
- let depName = display (packageName dep)
- depVersion = display (packageVersion dep)
+ let depName = emailContentDisplay (packageName dep)
+ depVersion = emailContentDisplay (packageVersion dep)
in
- [ "The dependency " <> display dep <> " has been uploaded or revised."
+ [ "The dependency " <> emailContentDisplay dep <> " has been uploaded or revised."
, case notifyDependencyTriggerBounds of
Always ->
"You have requested to be notified for each upload or revision \
@@ -878,10 +880,10 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
<> " but don't accept " <> depVersion
<> " (they do accept the second-highest version):"
]
- ++ map display revDeps
+ ++ map emailContentDisplay revDeps
- sendNotifyEmailAndDelay :: Users.Users -> (UserId, (T.Text, [String])) -> IO ()
- sendNotifyEmailAndDelay users (uid, (subject, ebody)) = do
+ sendNotifyEmailAndDelay :: Users.Users -> (UserId, (T.Text, [EmailContent])) -> IO ()
+ sendNotifyEmailAndDelay users (uid, (subject, emailContent)) = do
mudetails <- queryUserDetails uid
case mudetails of
Nothing -> return ()
@@ -891,12 +893,11 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
mail = (emptyMail mailFrom) {
mailTo = [Address (Just aname) eml],
mailHeaders = [(BSS.pack "Subject", "[Hackage] " <> subject)],
- mailParts = [[Part (T.pack "text/plain; charset=utf-8")
- None DefaultDisposition []
- (PartContent $ BS.pack $
- intercalate "\n\n" (ebody <> [adjustmentLinkParagraph])
- )
- ]]
+ mailParts =
+ [ fromEmailContent $
+ foldMap EmailContentParagraph $
+ emailContent <> [updatePreferencesText]
+ ]
}
Just ourHost = uriAuthority serverBaseURI
@@ -904,9 +905,11 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
-- sendmail stuff, has to go here
threadDelay 250000
where
- adjustmentLinkParagraph =
- "You can adjust your notification preferences at\n"
- <> uriToString id serverBaseURI ""
- <> "/user/"
- <> display (Users.userIdToName users uid)
- <> "/notify"
+ updatePreferencesText =
+ "You can adjust your notification preferences at" <> EmailContentSoftBreak
+ <> (EmailContentText . T.pack)
+ ( uriToString id serverBaseURI ""
+ <> "/user/"
+ <> display (Users.userIdToName users uid)
+ <> "/notify"
+ )
From cbca2d69d5911058f1388968487f2df043347ae0 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 16:33:17 -0700
Subject: [PATCH 34/71] Add markup to emails
---
.../Server/Features/UserNotify.hs | 80 +++++++++++--------
1 file changed, 48 insertions(+), 32 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index af2738416..33169dd3f 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -69,7 +69,7 @@ import Data.Time.Format.Internal (buildTime)
import Data.Typeable (Typeable)
import Distribution.Text (display)
import Network.Mail.Mime
-import Network.URI(uriAuthority, uriRegName, uriToString)
+import Network.URI (uriAuthority, uriPath, uriRegName)
import Text.CSV (CSV, Record)
import Text.PrettyPrint hiding ((<>))
import Text.XHtml hiding (base, text, (>))
@@ -683,19 +683,19 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
revisionsAndUploads <- collectRevisionsAndUploads trimLastTime now
revisionUploadNotifications <- foldM (genRevUploadList notifyPrefs) Map.empty revisionsAndUploads
- let revisionUploadEmails = map (describeRevision users trimLastTime now) <$> revisionUploadNotifications
+ let revisionUploadEmails = foldMap (describeRevision users trimLastTime now) <$> revisionUploadNotifications
groupActions <- collectAdminActions trimLastTime now
groupActionNotifications <- foldM (genGroupUploadList notifyPrefs) Map.empty groupActions
- let groupActionEmails = mapMaybe (describeGroupAction users) <$> groupActionNotifications
+ let groupActionEmails = mconcat . mapMaybe (describeGroupAction users) <$> groupActionNotifications
docReports <- collectDocReport trimLastTime now
docReportNotifications <- foldM (genDocReportList notifyPrefs) Map.empty docReports
- let docReportEmails = map describeDocReport <$> docReportNotifications
+ let docReportEmails = foldMap describeDocReport <$> docReportNotifications
tagProposals <- collectTagProposals
tagProposalNotifications <- foldM (genTagProposalList notifyPrefs) Map.empty tagProposals
- let tagProposalEmails = map describeTagProposal <$> tagProposalNotifications
+ let tagProposalEmails = foldMap describeTagProposal <$> tagProposalNotifications
idx <- queryGetPackageIndex
revIdx <- liftIO queryReverseIndex
@@ -704,7 +704,7 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
-- Concat the constituent email parts such that only one email is sent per user
mapM_ (sendNotifyEmailAndDelay users) . Map.toList $
- fmap ("Maintainer Notifications",) . foldr1 (Map.unionWith (++)) $
+ fmap ("Maintainer Notifications",) . foldr1 (Map.unionWith (<>)) $
[ revisionUploadEmails
, groupActionEmails
, docReportEmails
@@ -719,6 +719,13 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
updateState notifyState (SetNotifyTime now)
+ renderPkgLink pkg =
+ EmailContentLink
+ (T.pack $ display pkg)
+ serverBaseURI
+ { uriPath = "/package/" <> display (packageName pkg) <> "-" <> display (packageVersion pkg)
+ }
+
formatTimeUser users t u =
EmailContentText . T.pack $
display (Users.userIdToName users u) ++ " [" ++
@@ -810,11 +817,12 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
describeRevision users earlier now pkg
| pkgNumRevisions pkg <= 1 =
- "Package upload, " <> emailContentDisplay (packageName pkg) <> ", by " <>
+ EmailContentParagraph $
+ "Package upload, " <> renderPkgLink (pkgInfoId pkg) <> ", by " <>
formatTimeUser users (pkgLatestUploadTime pkg) (pkgLatestUploadUser pkg)
| otherwise =
- "Package metadata revision(s), " <> emailContentDisplay (packageName pkg) <> ":" <> EmailContentSoftBreak
- <> foldMap (<> EmailContentSoftBreak) (map (uncurry (formatTimeUser users) . snd) recentRevs)
+ EmailContentParagraph ("Package metadata revision(s), " <> renderPkgLink (pkgInfoId pkg) <> ":")
+ <> EmailContentList (map (uncurry (formatTimeUser users) . snd) recentRevs)
where
revs = reverse $ Vec.toList (pkgMetadataRevisions pkg)
recentRevs = filter ((\x -> x > earlier && x <= now) . fst . snd) revs
@@ -822,9 +830,11 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
describeGroupAction users (time, uid, act, reason) =
fmap
( \message ->
- "Group modified by " <> formatTimeUser users time uid <> ":" <> EmailContentSoftBreak
- <> message <> EmailContentSoftBreak
- <> "Reason: " <> emailContentLBS reason
+ EmailContentParagraph ("Group modified by " <> formatTimeUser users time uid <> ":")
+ <> EmailContentList
+ [ message
+ , "Reason: " <> emailContentLBS reason
+ ]
)
$ case act of
(Admin_GroupAddUser tn (MaintainerGroup pkg)) ->
@@ -840,15 +850,18 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
_ -> Nothing
describeDocReport (pkg, success) =
- "Package doc build for " <> emailContentDisplay (packageName pkg) <> ":" <> EmailContentSoftBreak <>
- if success
- then "Build successful."
- else "Build failed."
+ EmailContentParagraph $
+ "Package doc build for " <> emailContentDisplay (packageName pkg) <> ":" <> EmailContentSoftBreak <>
+ if success
+ then "Build successful."
+ else "Build failed."
describeTagProposal (pkgName, (addTags, delTags)) =
- "Pending tag proposal for " <> emailContentDisplay pkgName <> ":" <> EmailContentSoftBreak
- <> "Additions: " <> showTags addTags <> EmailContentSoftBreak
- <> "Deletions: " <> showTags delTags
+ EmailContentParagraph ("Pending tag proposal for " <> emailContentDisplay pkgName <> ":")
+ <> EmailContentList
+ [ "Additions: " <> showTags addTags
+ , "Deletions: " <> showTags delTags
+ ]
where
showTags = emailContentIntercalate ", " . map emailContentDisplay . Set.toList
@@ -856,12 +869,13 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
mPrefs <- queryGetUserNotifyPref uId
pure $
case mPrefs of
- Nothing -> []
+ Nothing -> mempty
Just NotifyPref{notifyDependencyTriggerBounds} ->
let depName = emailContentDisplay (packageName dep)
depVersion = emailContentDisplay (packageVersion dep)
in
- [ "The dependency " <> emailContentDisplay dep <> " has been uploaded or revised."
+ foldMap EmailContentParagraph
+ [ "The dependency " <> renderPkgLink dep <> " has been uploaded or revised."
, case notifyDependencyTriggerBounds of
Always ->
"You have requested to be notified for each upload or revision \
@@ -880,9 +894,9 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
<> " but don't accept " <> depVersion
<> " (they do accept the second-highest version):"
]
- ++ map emailContentDisplay revDeps
+ <> EmailContentList (map renderPkgLink revDeps)
- sendNotifyEmailAndDelay :: Users.Users -> (UserId, (T.Text, [EmailContent])) -> IO ()
+ sendNotifyEmailAndDelay :: Users.Users -> (UserId, (T.Text, EmailContent)) -> IO ()
sendNotifyEmailAndDelay users (uid, (subject, emailContent)) = do
mudetails <- queryUserDetails uid
case mudetails of
@@ -894,9 +908,7 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
mailTo = [Address (Just aname) eml],
mailHeaders = [(BSS.pack "Subject", "[Hackage] " <> subject)],
mailParts =
- [ fromEmailContent $
- foldMap EmailContentParagraph $
- emailContent <> [updatePreferencesText]
+ [ fromEmailContent $ emailContent <> updatePreferencesText
]
}
Just ourHost = uriAuthority serverBaseURI
@@ -906,10 +918,14 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
threadDelay 250000
where
updatePreferencesText =
+ EmailContentParagraph $
"You can adjust your notification preferences at" <> EmailContentSoftBreak
- <> (EmailContentText . T.pack)
- ( uriToString id serverBaseURI ""
- <> "/user/"
- <> display (Users.userIdToName users uid)
- <> "/notify"
- )
+ <> emailContentUrl
+ serverBaseURI
+ { uriPath =
+ concatMap ("/" <>)
+ [ "user"
+ , display $ Users.userIdToName users uid
+ , "notify"
+ ]
+ }
From 0ded9fc5744e48b0a118293a93e856c01931faf8 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 16:33:20 -0700
Subject: [PATCH 35/71] Reformat
---
.../Server/Features/UserNotify.hs | 62 +++++++++----------
1 file changed, 31 insertions(+), 31 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 33169dd3f..cfb4aaf4c 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -818,8 +818,8 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
describeRevision users earlier now pkg
| pkgNumRevisions pkg <= 1 =
EmailContentParagraph $
- "Package upload, " <> renderPkgLink (pkgInfoId pkg) <> ", by " <>
- formatTimeUser users (pkgLatestUploadTime pkg) (pkgLatestUploadUser pkg)
+ "Package upload, " <> renderPkgLink (pkgInfoId pkg) <> ", by " <>
+ formatTimeUser users (pkgLatestUploadTime pkg) (pkgLatestUploadUser pkg)
| otherwise =
EmailContentParagraph ("Package metadata revision(s), " <> renderPkgLink (pkgInfoId pkg) <> ":")
<> EmailContentList (map (uncurry (formatTimeUser users) . snd) recentRevs)
@@ -875,25 +875,25 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
depVersion = emailContentDisplay (packageVersion dep)
in
foldMap EmailContentParagraph
- [ "The dependency " <> renderPkgLink dep <> " has been uploaded or revised."
- , case notifyDependencyTriggerBounds of
- Always ->
- "You have requested to be notified for each upload or revision \
- \of a dependency."
- _ ->
- "You have requested to be notified when a dependency isn't \
- \accepted by any of your maintained packages."
- , case notifyDependencyTriggerBounds of
- Always ->
- "These are your packages that depend on " <> depName <> ":"
- BoundsOutOfRange ->
- "These are your packages that require " <> depName
- <> " but don't accept " <> depVersion <> ":"
- NewIncompatibility ->
- "The following packages require " <> depName
- <> " but don't accept " <> depVersion
- <> " (they do accept the second-highest version):"
- ]
+ [ "The dependency " <> renderPkgLink dep <> " has been uploaded or revised."
+ , case notifyDependencyTriggerBounds of
+ Always ->
+ "You have requested to be notified for each upload or revision \
+ \of a dependency."
+ _ ->
+ "You have requested to be notified when a dependency isn't \
+ \accepted by any of your maintained packages."
+ , case notifyDependencyTriggerBounds of
+ Always ->
+ "These are your packages that depend on " <> depName <> ":"
+ BoundsOutOfRange ->
+ "These are your packages that require " <> depName
+ <> " but don't accept " <> depVersion <> ":"
+ NewIncompatibility ->
+ "The following packages require " <> depName
+ <> " but don't accept " <> depVersion
+ <> " (they do accept the second-highest version):"
+ ]
<> EmailContentList (map renderPkgLink revDeps)
sendNotifyEmailAndDelay :: Users.Users -> (UserId, (T.Text, EmailContent)) -> IO ()
@@ -919,13 +919,13 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
where
updatePreferencesText =
EmailContentParagraph $
- "You can adjust your notification preferences at" <> EmailContentSoftBreak
- <> emailContentUrl
- serverBaseURI
- { uriPath =
- concatMap ("/" <>)
- [ "user"
- , display $ Users.userIdToName users uid
- , "notify"
- ]
- }
+ "You can adjust your notification preferences at" <> EmailContentSoftBreak
+ <> emailContentUrl
+ serverBaseURI
+ { uriPath =
+ concatMap ("/" <>)
+ [ "user"
+ , display $ Users.userIdToName users uid
+ , "notify"
+ ]
+ }
From 23eae0c9f301c16e77b1b4b2f1991ffdffa405ea Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Wed, 16 Aug 2023 16:47:56 -0700
Subject: [PATCH 36/71] Give dependencyReleaseEmails more accurate name + type
---
.../Server/Features/UserNotify.hs | 27 ++++++-----
tests/ReverseDependenciesTest.hs | 45 +++++++++++--------
2 files changed, 42 insertions(+), 30 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index cfb4aaf4c..ba864a1e5 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -12,7 +12,7 @@ module Distribution.Server.Features.UserNotify (
NotifyTriggerBounds(..),
UserNotifyFeature(..),
defaultNotifyPrefs,
- dependencyReleaseEmails,
+ getUserNotificationsOnRelease,
importNotifyPref,
initUserNotifyFeature,
notifyDataToCSV,
@@ -446,24 +446,26 @@ initUserNotifyFeature env@ServerEnv{ serverStateDir, serverTemplatesDir,
data InRange = InRange | OutOfRange
--- | Get the release notification emails when a new package has been released.
--- The new package (PackageIdentifier) must already be in the indexes.
--- The keys in the returned map are the new packages. The values are the revDeps.
-dependencyReleaseEmails
+-- | Get the users to notify when a new package has been released.
+-- The new package (PackageId) must already be in the indexes.
+-- The keys in the returned map are the user to notify, and the values are
+-- the packages the user maintains that depend on the new package (i.e. the
+-- reverse dependencies of the new package).
+getUserNotificationsOnRelease
:: forall m. Monad m
=> (PackageName -> m UserIdSet)
-> PackageIndex.PackageIndex PkgInfo
-> ReverseIndex
-> (UserId -> m (Maybe NotifyPref))
- -> PackageIdentifier
- -> m (Map.Map (UserId, PackageId) [PackageId])
-dependencyReleaseEmails _ index _ _ pkgId
+ -> PackageId
+ -> m (Map.Map UserId [PackageId])
+getUserNotificationsOnRelease _ index _ _ pkgId
| let versionsForNewRelease = packageVersion <$> PackageIndex.lookupPackageName index (pkgName pkgId)
, pkgVersion pkgId /= maximum versionsForNewRelease
-- If e.g. a minor bugfix release is made for an old release series, never notify maintainers.
-- Only start checking if the new version is the highest.
= pure mempty
-dependencyReleaseEmails userSetIdForPackage index (ReverseIndex revs nodemap dependencies) queryGetUserNotifyPref pkgId =
+getUserNotificationsOnRelease userSetIdForPackage index (ReverseIndex revs nodemap dependencies) queryGetUserNotifyPref pkgId =
case lookup (pkgName pkgId) nodemap :: Maybe NodeId of
Nothing -> pure mempty
Just foundPackage -> do
@@ -475,7 +477,7 @@ dependencyReleaseEmails userSetIdForPackage index (ReverseIndex revs nodemap dep
toNotify <- traverse maintainersToNotify revDepNames
pure $
Map.fromListWith (++)
- [ ( (maintainerId, pkgId), [ packageId latestRevDep ] )
+ [ (maintainerId, [packageId latestRevDep])
| (ids, latestRevDep) <- toNotify
, maintainerId <- ids
]
@@ -812,8 +814,9 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
maintainers <- queryUserGroup $ maintainersGroup (fst pkgTags)
return $ foldr addNotification mp (toList maintainers)
- genDependencyUpdateList idx revIdx =
- dependencyReleaseEmails (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref
+ genDependencyUpdateList idx revIdx pid =
+ Map.mapKeys (, pid) <$>
+ getUserNotificationsOnRelease (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref pid
describeRevision users earlier now pkg
| pkgNumRevisions pkg <= 1 =
diff --git a/tests/ReverseDependenciesTest.hs b/tests/ReverseDependenciesTest.hs
index d72ebcf1a..2626f7a34 100644
--- a/tests/ReverseDependenciesTest.hs
+++ b/tests/ReverseDependenciesTest.hs
@@ -14,7 +14,16 @@ import Distribution.Package (PackageIdentifier(..), mkPackageName, packageId, pa
import Distribution.Server.Features.PreferredVersions.State (PreferredVersions(..), VersionStatus(NormalVersion), PreferredInfo(..))
import Distribution.Server.Features.ReverseDependencies (ReverseFeature(..), ReverseCount(..), reverseFeature)
import Distribution.Server.Features.ReverseDependencies.State (ReverseIndex(..), addPackage, constructReverseIndex, emptyReverseIndex, getDependenciesFlat, getDependencies, getDependenciesFlatRaw, getDependenciesRaw)
-import Distribution.Server.Features.UserNotify (NotifyData(..), NotifyPref(..), NotifyRevisionRange, NotifyTriggerBounds(..), defaultNotifyPrefs, dependencyReleaseEmails, importNotifyPref, notifyDataToCSV)
+import Distribution.Server.Features.UserNotify
+ ( NotifyData(..)
+ , NotifyPref(..)
+ , NotifyRevisionRange
+ , NotifyTriggerBounds(..)
+ , defaultNotifyPrefs
+ , getUserNotificationsOnRelease
+ , importNotifyPref
+ , notifyDataToCSV
+ )
import Distribution.Server.Framework.BackupRestore (runRestore)
import Distribution.Server.Framework.Hook (newHook)
import Distribution.Server.Framework.MemState (newMemStateWHNF)
@@ -186,7 +195,7 @@ allTests = testGroup "ReverseDependenciesTest"
ReverseFeature{revDisplayInfo} <- mkRevFeat mtlBeelineLens
res <- revDisplayInfo
assertEqual "beeline preferred is old" (PreferredInfo [] [] Nothing, [mkVersion [0]]) (res "beeline")
- , testCase "dependencyReleaseEmails sends notification" $ do
+ , testCase "getUserNotificationsOnRelease sends notification" $ do
let userSetIdForPackage arg | arg == mkPackageName "mtl" = Identity (UserIdSet.fromList [UserId 0])
| otherwise = error "should only get user ids for mtl"
notifyPref triggerBounds =
@@ -197,9 +206,9 @@ allTests = testGroup "ReverseDependenciesTest"
}
pref triggerBounds (UserId 0) = Identity (Just $ notifyPref triggerBounds)
pref _ _ = error "should only get preferences for UserId 0"
- refNotification base = Map.fromList
+ userNotification = Map.fromList
[
- ( (UserId 0, base)
+ ( UserId 0
, [PackageIdentifier (mkPackageName "mtl") (mkVersion [2,3])]
)
]
@@ -208,23 +217,23 @@ allTests = testGroup "ReverseDependenciesTest"
base4_15 = PackageIdentifier "base" (mkVersion [4,15])
base4_16 = PackageIdentifier "base" (mkVersion [4,16])
runWithPref preferences index pkg = runIdentity $
- dependencyReleaseEmails userSetIdForPackage index (constructReverseIndex index) preferences pkg
+ getUserNotificationsOnRelease userSetIdForPackage index (constructReverseIndex index) preferences pkg
runWithPrefAlsoMtl2 preferences index pkg = runIdentity $
- dependencyReleaseEmails userSet index (constructReverseIndex index) preferences pkg
+ getUserNotificationsOnRelease userSet index (constructReverseIndex index) preferences pkg
where
userSet arg | arg == mkPackageName "mtl" = Identity (UserIdSet.fromList [UserId 0])
| arg == mkPackageName "mtl2" = Identity (UserIdSet.fromList [UserId 0])
| otherwise = error "should only get user ids for mtl and mtl2"
assertEqual
- "dependencyReleaseEmails(trigger=NewIncompatibility) shouldn't generate a notification when there are packages, but none are behind"
+ "getUserNotificationsOnRelease(trigger=NewIncompatibility) shouldn't generate a notification when there are packages, but none are behind"
mempty
(runWithPref (pref NewIncompatibility) (PackageIndex.fromList twoPackagesWithNoDepsOutOfRange) base4_14)
assertEqual
- "dependencyReleaseEmails(trigger=NewIncompatibility) should generate a notification when package is a single base version behind"
- (refNotification base4_15)
+ "getUserNotificationsOnRelease(trigger=NewIncompatibility) should generate a notification when package is a single base version behind"
+ userNotification
(runWithPref (pref NewIncompatibility) (PackageIndex.fromList newBaseReleased) base4_15)
assertEqual
- "dependencyReleaseEmails(trigger=NewIncompatibility) should generate a notification for two packages that are a single base version behind"
+ "getUserNotificationsOnRelease(trigger=NewIncompatibility) should generate a notification for two packages that are a single base version behind"
(Just $
Set.fromList
[ PackageIdentifier (mkPackageName "mtl") (mkVersion [2,3])
@@ -232,27 +241,27 @@ allTests = testGroup "ReverseDependenciesTest"
]
)
( fmap Set.fromList
- . Map.lookup (UserId 0, base4_15)
+ . Map.lookup (UserId 0)
$ runWithPrefAlsoMtl2 (pref NewIncompatibility) (PackageIndex.fromList newBaseReleasedMultiple) base4_15
)
assertEqual
- "dependencyReleaseEmails(trigger=BoundsOutOfRange) should generate a notification when package is a single base version behind"
- (refNotification base4_15)
+ "getUserNotificationsOnRelease(trigger=BoundsOutOfRange) should generate a notification when package is a single base version behind"
+ userNotification
(runWithPref (pref BoundsOutOfRange) (PackageIndex.fromList newBaseReleased) base4_15)
assertEqual
- "dependencyReleaseEmails(trigger=NewIncompatibility) shouldn't generate a notification when package is two base versions behind"
+ "getUserNotificationsOnRelease(trigger=NewIncompatibility) shouldn't generate a notification when package is two base versions behind"
mempty
(runWithPref (pref NewIncompatibility) (PackageIndex.fromList twoNewBasesReleased) base4_16)
assertEqual
- "dependencyReleaseEmails(trigger=BoundsOutOfRange) should generate a notification when package is two base versions behind"
- (refNotification base4_16)
+ "getUserNotificationsOnRelease(trigger=BoundsOutOfRange) should generate a notification when package is two base versions behind"
+ userNotification
(runWithPref (pref BoundsOutOfRange) (PackageIndex.fromList twoNewBasesReleased) base4_16)
assertEqual
- "dependencyReleaseEmails(trigger=BoundsOutOfRange) shouldn't generate a notification when the new package is for an old release series"
+ "getUserNotificationsOnRelease(trigger=BoundsOutOfRange) shouldn't generate a notification when the new package is for an old release series"
mempty
(runWithPref (pref BoundsOutOfRange) (PackageIndex.fromList newVersionOfOldBase) base4_14_1)
assertEqual
- "dependencyReleaseEmails(trigger=BoundsOutOfRange) should only generate a notification when the new version is forbidden across all branches"
+ "getUserNotificationsOnRelease(trigger=BoundsOutOfRange) should only generate a notification when the new version is forbidden across all branches"
mempty -- The two branches below should get OR'd and therefore the dependency is not out of bounds
(runWithPref
(pref BoundsOutOfRange)
From 9da933c27e7f6cde33a75ef2a2cfc40ef5ae92fb Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 11:11:33 -0700
Subject: [PATCH 37/71] Break out getNotificationEmails from
sendNotifyEmailAndDelay
---
.../Server/Features/UserNotify.hs | 164 ++++++++++++------
1 file changed, 115 insertions(+), 49 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index ba864a1e5..bdabe79a0 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -2,6 +2,7 @@
TypeFamilies, TemplateHaskell,
RankNTypes, NamedFieldPuns, RecordWildCards, BangPatterns,
DefaultSignatures, OverloadedStrings #-}
+{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
@@ -49,7 +50,9 @@ import Distribution.Server.Features.Users
import Distribution.Server.Util.Email
+import Data.Map (Map)
import qualified Data.Map as Map
+import Data.Set (Set)
import qualified Data.Set as Set
import Control.Concurrent (threadDelay)
@@ -76,7 +79,6 @@ import Text.XHtml hiding (base, text, (>))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
-import qualified Data.ByteString.Char8 as BSS
import qualified Data.ByteString.Lazy.Char8 as BS
import qualified Data.Text as T
import qualified Data.Vector as Vec
@@ -575,12 +577,12 @@ userNotifyFeature :: ServerEnv
-> StateComponent AcidState NotifyData
-> Templates
-> UserNotifyFeature
-userNotifyFeature ServerEnv{serverBaseURI, serverCron}
+userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
UserFeature{..}
CoreFeature{..}
UploadFeature{..}
AdminLogFeature{..}
- UserDetailsFeature{..}
+ userDetailsFeature@UserDetailsFeature{..}
ReportsFeature{..}
TagsFeature{..}
ReverseFeature{queryReverseIndex}
@@ -704,20 +706,17 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
dependencyUpdateNotifications <- Map.unionsWith (++) <$> traverse (genDependencyUpdateList idx revIdx . pkgInfoToPkgId) revisionsAndUploads
dependencyEmails <- Map.traverseWithKey describeDependencyUpdate dependencyUpdateNotifications
- -- Concat the constituent email parts such that only one email is sent per user
- mapM_ (sendNotifyEmailAndDelay users) . Map.toList $
- fmap ("Maintainer Notifications",) . foldr1 (Map.unionWith (<>)) $
- [ revisionUploadEmails
- , groupActionEmails
- , docReportEmails
- , tagProposalEmails
- ]
-
- -- Dependency email notifications consist of multiple paragraphs, so it would be confusing if concatenated.
- -- So they're sent independently.
- mapM_ (sendNotifyEmailAndDelay users) . Map.toList $
- Map.mapKeys fst . Map.mapWithKey (\(_, dep) emailContent -> ("Dependency Update: " <> T.pack (display dep), emailContent)) $
- dependencyEmails
+ emails <-
+ getNotificationEmails serverEnv userDetailsFeature users
+ ( foldr1 (Map.unionWith (<>))
+ [ revisionUploadEmails
+ , groupActionEmails
+ , docReportEmails
+ , tagProposalEmails
+ ]
+ , dependencyEmails
+ )
+ mapM_ sendNotifyEmailAndDelay emails
updateState notifyState (SetNotifyTime now)
@@ -899,36 +898,103 @@ userNotifyFeature ServerEnv{serverBaseURI, serverCron}
]
<> EmailContentList (map renderPkgLink revDeps)
- sendNotifyEmailAndDelay :: Users.Users -> (UserId, (T.Text, EmailContent)) -> IO ()
- sendNotifyEmailAndDelay users (uid, (subject, emailContent)) = do
- mudetails <- queryUserDetails uid
- case mudetails of
- Nothing -> return ()
- Just (AccountDetails{accountContactEmail=eml, accountName=aname})-> do
- let mailFrom = Address (Just (T.pack "Hackage website"))
- (T.pack ("noreply@" ++ uriRegName ourHost))
- mail = (emptyMail mailFrom) {
- mailTo = [Address (Just aname) eml],
- mailHeaders = [(BSS.pack "Subject", "[Hackage] " <> subject)],
- mailParts =
- [ fromEmailContent $ emailContent <> updatePreferencesText
- ]
- }
- Just ourHost = uriAuthority serverBaseURI
+ sendNotifyEmailAndDelay :: Mail -> IO ()
+ sendNotifyEmailAndDelay email = do
+ -- TODO: if we need any configuration of sendmail stuff, has to go here
+ renderSendMail email
- renderSendMail mail --TODO: if we need any configuration of
- -- sendmail stuff, has to go here
- threadDelay 250000
- where
- updatePreferencesText =
- EmailContentParagraph $
- "You can adjust your notification preferences at" <> EmailContentSoftBreak
- <> emailContentUrl
- serverBaseURI
- { uriPath =
- concatMap ("/" <>)
- [ "user"
- , display $ Users.userIdToName users uid
- , "notify"
- ]
- }
+ -- delay sending out emails, because ???
+ threadDelay 250000
+
+-- | Notifications in the same group are batched in the same email.
+--
+-- TODO: How often do multiple notifications come in at once? Maybe it's
+-- fine to just send one email per notification.
+data NotificationGroup
+ = GeneralNotification
+ | DependencyNotification PackageId
+ deriving (Eq, Ord)
+
+-- | Get all the emails to send for the given notifications.
+getNotificationEmails
+ :: ServerEnv
+ -> UserDetailsFeature
+ -> Users.Users
+ -> (Map UserId EmailContent, Map (UserId, PackageId) EmailContent)
+ -> IO [Mail]
+getNotificationEmails
+ ServerEnv{serverBaseURI}
+ UserDetailsFeature{queryUserDetails}
+ allUsers
+ (generalEmails, dependencyUpdateEmails) = do
+ let userIds = Set.fromList . Map.keys $ generalEmails <> Map.mapKeys fst dependencyUpdateEmails
+ userIdToDetails <- Map.mapMaybe id <$> fromSetM queryUserDetails userIds
+
+ pure $
+ let emails =
+ groupNotifications . concat $
+ [ Map.toList
+ . fmap (, GeneralNotification)
+ $ generalEmails
+ , Map.toList
+ . Map.mapKeys fst
+ . Map.mapWithKey (\(_, pkg) emailContent -> (emailContent, DependencyNotification pkg))
+ $ dependencyUpdateEmails
+ ]
+ in flip mapMaybe (Map.toList emails) $ \((uid, group), emailContent) ->
+ case uid `Map.lookup` userIdToDetails of
+ Nothing -> Nothing
+ Just AccountDetails{..} -> Just $
+ Mail
+ { mailFrom =
+ Address
+ { addressName = Just "Hackage website"
+ , addressEmail = "noreply@" <> hostname
+ }
+ , mailTo =
+ [ Address
+ { addressName = Just accountName
+ , addressEmail = accountContactEmail
+ }
+ ]
+ , mailCc = []
+ , mailBcc = []
+ , mailHeaders =
+ [ ("Subject", "[Hackage] " <> getEmailSubject group)
+ ]
+ , mailParts =
+ [ fromEmailContent $ emailContent <> updatePreferencesText uid
+ ]
+ }
+ where
+ groupNotifications :: [(UserId, (EmailContent, NotificationGroup))] -> Map (UserId, NotificationGroup) EmailContent
+ groupNotifications =
+ Map.fromListWith (<>)
+ . map (\(uid, (emailContent, group)) -> ((uid, group), emailContent))
+
+ getEmailSubject = \case
+ GeneralNotification -> "Maintainer Notifications"
+ DependencyNotification pkg -> "Dependency Update: " <> T.pack (display pkg)
+
+ hostname =
+ case uriAuthority serverBaseURI of
+ Just auth -> T.pack $ uriRegName auth
+ Nothing -> error $ "Could not get hostname from serverBaseURI: " <> show serverBaseURI
+
+ updatePreferencesText uid =
+ EmailContentParagraph $
+ "You can adjust your notification preferences at" <> EmailContentSoftBreak
+ <> emailContentUrl
+ serverBaseURI
+ { uriPath =
+ concatMap ("/" <>)
+ [ "user"
+ , display $ Users.userIdToName allUsers uid
+ , "notify"
+ ]
+ }
+
+{----- Utilities -----}
+
+fromSetM :: Monad m => (k -> m v) -> Set k -> m (Map k v)
+fromSetM f = traverse id . Map.fromSet f
From b896c75b551c4e3a8bb41a5292eed21306be66de Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 17:26:08 -0700
Subject: [PATCH 38/71] Move describeRevision into getNotificationEmails
---
.../Server/Features/UserNotify.hs | 124 +++++++++++++-----
1 file changed, 92 insertions(+), 32 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index bdabe79a0..afe638a1d 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -686,8 +686,7 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
users <- queryGetUserDb
revisionsAndUploads <- collectRevisionsAndUploads trimLastTime now
- revisionUploadNotifications <- foldM (genRevUploadList notifyPrefs) Map.empty revisionsAndUploads
- let revisionUploadEmails = foldMap (describeRevision users trimLastTime now) <$> revisionUploadNotifications
+ revisionUploadNotifications <- concatMapM (genRevUploadList notifyPrefs trimLastTime now) revisionsAndUploads
groupActions <- collectAdminActions trimLastTime now
groupActionNotifications <- foldM (genGroupUploadList notifyPrefs) Map.empty groupActions
@@ -709,13 +708,15 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
emails <-
getNotificationEmails serverEnv userDetailsFeature users
( foldr1 (Map.unionWith (<>))
- [ revisionUploadEmails
- , groupActionEmails
+ [ groupActionEmails
, docReportEmails
, tagProposalEmails
]
, dependencyEmails
- )
+ ) $
+ concat
+ [ revisionUploadNotifications
+ ]
mapM_ sendNotifyEmailAndDelay emails
updateState notifyState (SetNotifyTime now)
@@ -762,25 +763,39 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
writeMemState tagProposalLog Map.empty
pure $ Map.toList logs
- genRevUploadList notifyPrefs mp pkg = do
+ genRevUploadList notifyPrefs earlier now pkg = do
pkgIndex <- queryGetPackageIndex
let actor = pkgLatestUploadUser pkg
isRevision = pkgNumRevisions pkg > 1
pkgName = packageName . pkgInfoId $ pkg
mbLatest = listToMaybe . take 1 . reverse $ PackageIndex.lookupPackageName pkgIndex pkgName
isLatestVersion = maybe False (\x -> pkgInfoId pkg == pkgInfoId x) mbLatest
- addNotification uid m =
- if not (notifyOptOut npref) &&
- (isRevision &&
- ( notifyRevisionRange npref == NotifyAllVersions ||
- ((notifyRevisionRange npref == NotifyNewestVersion) && isLatestVersion))
- ||
- not isRevision && notifyUpload npref)
- then Map.insertWith (++) uid [pkg] m
- else m
- where npref = fromMaybe defaultNotifyPrefs (Map.lookup uid notifyPrefs)
maintainers <- queryUserGroup $ maintainersGroup (packageName . pkgInfoId $ pkg)
- return $ foldr addNotification mp (toList (delete actor maintainers))
+ pure . flip mapMaybe (toList maintainers) $ \uid ->
+ fmap (uid,) $ do
+ let NotifyPref{..} = fromMaybe defaultNotifyPrefs (Map.lookup uid notifyPrefs)
+ guard $ uid /= actor
+ guard $ not notifyOptOut
+ if isRevision
+ then do
+ guard $
+ notifyRevisionRange == NotifyAllVersions ||
+ (notifyRevisionRange == NotifyNewestVersion && isLatestVersion)
+ Just
+ NotifyNewRevision
+ { notifyPackageId = pkgInfoId pkg
+ , notifyRevisions =
+ filter (\(t, _) -> earlier < t && t <= now)
+ . map snd
+ . Vec.toList
+ $ pkgMetadataRevisions pkg
+ }
+ else do
+ guard notifyUpload
+ Just
+ NotifyNewVersion
+ { notifyPackageInfo = pkg
+ }
genGroupUploadList notifyPrefs mp ga =
let (actor,gdesc) = case ga of (_,uid,Admin_GroupAddUser _ gd,_) -> (uid, gd)
@@ -817,18 +832,6 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
Map.mapKeys (, pid) <$>
getUserNotificationsOnRelease (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref pid
- describeRevision users earlier now pkg
- | pkgNumRevisions pkg <= 1 =
- EmailContentParagraph $
- "Package upload, " <> renderPkgLink (pkgInfoId pkg) <> ", by " <>
- formatTimeUser users (pkgLatestUploadTime pkg) (pkgLatestUploadUser pkg)
- | otherwise =
- EmailContentParagraph ("Package metadata revision(s), " <> renderPkgLink (pkgInfoId pkg) <> ":")
- <> EmailContentList (map (uncurry (formatTimeUser users) . snd) recentRevs)
- where
- revs = reverse $ Vec.toList (pkgMetadataRevisions pkg)
- recentRevs = filter ((\x -> x > earlier && x <= now) . fst . snd) revs
-
describeGroupAction users (time, uid, act, reason) =
fmap
( \message ->
@@ -906,6 +909,15 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
-- delay sending out emails, because ???
threadDelay 250000
+data Notification
+ = NotifyNewVersion
+ { notifyPackageInfo :: PkgInfo
+ }
+ | NotifyNewRevision
+ { notifyPackageId :: PackageId
+ , notifyRevisions :: [UploadInfo]
+ }
+
-- | Notifications in the same group are batched in the same email.
--
-- TODO: How often do multiple notifications come in at once? Maybe it's
@@ -921,14 +933,17 @@ getNotificationEmails
-> UserDetailsFeature
-> Users.Users
-> (Map UserId EmailContent, Map (UserId, PackageId) EmailContent)
+ -> [(UserId, Notification)]
-> IO [Mail]
getNotificationEmails
ServerEnv{serverBaseURI}
UserDetailsFeature{queryUserDetails}
allUsers
- (generalEmails, dependencyUpdateEmails) = do
- let userIds = Set.fromList . Map.keys $ generalEmails <> Map.mapKeys fst dependencyUpdateEmails
- userIdToDetails <- Map.mapMaybe id <$> fromSetM queryUserDetails userIds
+ (generalEmails, dependencyUpdateEmails)
+ notifications = do
+ let userIds = Set.fromList $ map fst notifications
+ let userIds' = Set.fromList . Map.keys $ generalEmails <> Map.mapKeys fst dependencyUpdateEmails
+ userIdToDetails <- Map.mapMaybe id <$> fromSetM queryUserDetails (userIds <> userIds')
pure $
let emails =
@@ -940,6 +955,8 @@ getNotificationEmails
. Map.mapKeys fst
. Map.mapWithKey (\(_, pkg) emailContent -> (emailContent, DependencyNotification pkg))
$ dependencyUpdateEmails
+ , flip mapMaybe notifications $ \(uid, notif) ->
+ fmap (uid,) $ renderNotification notif
]
in flip mapMaybe (Map.toList emails) $ \((uid, group), emailContent) ->
case uid `Map.lookup` userIdToDetails of
@@ -994,7 +1011,50 @@ getNotificationEmails
]
}
+ {----- Render notifications -----}
+
+ renderNotification :: Notification -> Maybe (EmailContent, NotificationGroup)
+ renderNotification = \case
+ NotifyNewVersion{..} ->
+ generalNotification $
+ renderNotifyNewVersion
+ notifyPackageInfo
+ NotifyNewRevision{..} ->
+ generalNotification $
+ renderNotifyNewRevision
+ notifyPackageId
+ notifyRevisions
+ where
+ generalNotification emailContent = Just (emailContent, GeneralNotification)
+
+ renderNotifyNewVersion pkg =
+ EmailContentParagraph $
+ "Package upload, " <> renderPkgLink (pkgInfoId pkg) <> ", by " <>
+ renderUserTime (pkgLatestUploadUser pkg) (pkgLatestUploadTime pkg)
+
+ renderNotifyNewRevision pkg revs =
+ EmailContentParagraph ("Package metadata revision(s), " <> renderPkgLink pkg <> ":")
+ <> EmailContentList (map (uncurry $ flip renderUserTime) $ sortOn (Down . fst) revs)
+
+ {----- Rendering helpers -----}
+
+ renderPkgLink pkg =
+ EmailContentLink
+ (T.pack $ display pkg)
+ serverBaseURI
+ { uriPath = "/package/" <> display (packageName pkg) <> "-" <> display (packageVersion pkg)
+ }
+
+ renderUser = emailContentDisplay . Users.userIdToName allUsers
+
+ renderTime = emailContentStr . formatTime defaultTimeLocale "%c"
+
+ renderUserTime u t = renderUser u <> " [" <> renderTime t <> "]"
+
{----- Utilities -----}
fromSetM :: Monad m => (k -> m v) -> Set k -> m (Map k v)
fromSetM f = traverse id . Map.fromSet f
+
+concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b]
+concatMapM f = fmap concat . mapM f
From d6d0d94eb0a618f9cd73f2aae8064ebc2f260fca Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 17:31:56 -0700
Subject: [PATCH 39/71] Move describeGroupAction into getNotificationEmails
---
.../Server/Features/UserNotify.hs | 112 +++++++++++-------
1 file changed, 69 insertions(+), 43 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index afe638a1d..37ce441eb 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -80,7 +80,10 @@ import Text.XHtml hiding (base, text, (>))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import qualified Data.ByteString.Lazy.Char8 as BS
+import Data.Text (Text)
import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TL
import qualified Data.Vector as Vec
-- A feature to manage notifications to users when package metadata, etc is updated.
@@ -689,8 +692,7 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
revisionUploadNotifications <- concatMapM (genRevUploadList notifyPrefs trimLastTime now) revisionsAndUploads
groupActions <- collectAdminActions trimLastTime now
- groupActionNotifications <- foldM (genGroupUploadList notifyPrefs) Map.empty groupActions
- let groupActionEmails = mconcat . mapMaybe (describeGroupAction users) <$> groupActionNotifications
+ groupActionNotifications <- concatMapM (genGroupUploadList notifyPrefs) groupActions
docReports <- collectDocReport trimLastTime now
docReportNotifications <- foldM (genDocReportList notifyPrefs) Map.empty docReports
@@ -708,14 +710,14 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
emails <-
getNotificationEmails serverEnv userDetailsFeature users
( foldr1 (Map.unionWith (<>))
- [ groupActionEmails
- , docReportEmails
+ [ docReportEmails
, tagProposalEmails
]
, dependencyEmails
) $
concat
[ revisionUploadNotifications
+ , groupActionNotifications
]
mapM_ sendNotifyEmailAndDelay emails
@@ -728,11 +730,6 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
{ uriPath = "/package/" <> display (packageName pkg) <> "-" <> display (packageVersion pkg)
}
- formatTimeUser users t u =
- EmailContentText . T.pack $
- display (Users.userIdToName users u) ++ " [" ++
- (formatTime defaultTimeLocale "%c" t) ++ "]"
-
collectRevisionsAndUploads earlier now = do
pkgIndex <- queryGetPackageIndex
let isRecent pkgInfo =
@@ -797,18 +794,36 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
{ notifyPackageInfo = pkg
}
- genGroupUploadList notifyPrefs mp ga =
- let (actor,gdesc) = case ga of (_,uid,Admin_GroupAddUser _ gd,_) -> (uid, gd)
- (_,uid,Admin_GroupDelUser _ gd,_) -> (uid, gd)
- addNotification uid m = if not (notifyOptOut npref) && notifyMaintainerGroup npref
- then Map.insertWith (++) uid [ga] m
- else m
- where npref = fromMaybe defaultNotifyPrefs (Map.lookup uid notifyPrefs)
- in case gdesc of
- (MaintainerGroup pkg) -> do
- maintainers <- queryUserGroup $ maintainersGroup (mkPackageName $ BS.unpack pkg)
- return $ foldr addNotification mp (toList (delete actor maintainers))
- _ -> return mp
+ genGroupUploadList notifyPrefs groupAction =
+ let notifyAllMaintainers actor pkg notif = do
+ maintainers <- queryUserGroup $ maintainersGroup (mkPackageName $ BS.unpack pkg)
+ pure . flip mapMaybe (toList maintainers) $ \uid -> do
+ let NotifyPref{..} = fromMaybe defaultNotifyPrefs (Map.lookup uid notifyPrefs)
+ guard $ uid /= actor
+ guard $ not notifyOptOut
+ Just (uid, notif)
+ in case groupAction of
+ (time, userActor, Admin_GroupAddUser userSubject (MaintainerGroup pkg), reason) ->
+ notifyAllMaintainers userActor pkg $
+ NotifyMaintainerUpdate
+ { notifyMaintainerUpdateType = MaintainerAdded
+ , notifyUserActor = userActor
+ , notifyUserSubject = userSubject
+ , notifyPackageName = mkPackageName $ BS.unpack pkg
+ , notifyReason = TL.toStrict $ TL.decodeUtf8 reason
+ , notifyUpdatedAt = time
+ }
+ (time, userActor, Admin_GroupDelUser userSubject (MaintainerGroup pkg), reason) ->
+ notifyAllMaintainers userActor pkg $
+ NotifyMaintainerUpdate
+ { notifyMaintainerUpdateType = MaintainerRemoved
+ , notifyUserActor = userActor
+ , notifyUserSubject = userSubject
+ , notifyPackageName = mkPackageName $ BS.unpack pkg
+ , notifyReason = TL.toStrict $ TL.decodeUtf8 reason
+ , notifyUpdatedAt = time
+ }
+ _ -> pure []
genDocReportList notifyPrefs mp pkgDoc = do
let addNotification uid m =
@@ -832,28 +847,6 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
Map.mapKeys (, pid) <$>
getUserNotificationsOnRelease (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref pid
- describeGroupAction users (time, uid, act, reason) =
- fmap
- ( \message ->
- EmailContentParagraph ("Group modified by " <> formatTimeUser users time uid <> ":")
- <> EmailContentList
- [ message
- , "Reason: " <> emailContentLBS reason
- ]
- )
- $ case act of
- (Admin_GroupAddUser tn (MaintainerGroup pkg)) ->
- Just $
- emailContentDisplay (Users.userIdToName users tn)
- <> " added to maintainers for "
- <> emailContentLBS pkg
- (Admin_GroupDelUser tn (MaintainerGroup pkg)) ->
- Just $
- emailContentDisplay (Users.userIdToName users tn)
- <> " removed from maintainers for "
- <> emailContentLBS pkg
- _ -> Nothing
-
describeDocReport (pkg, success) =
EmailContentParagraph $
"Package doc build for " <> emailContentDisplay (packageName pkg) <> ":" <> EmailContentSoftBreak <>
@@ -917,6 +910,16 @@ data Notification
{ notifyPackageId :: PackageId
, notifyRevisions :: [UploadInfo]
}
+ | NotifyMaintainerUpdate
+ { notifyMaintainerUpdateType :: NotifyMaintainerUpdateType
+ , notifyUserActor :: UserId
+ , notifyUserSubject :: UserId
+ , notifyPackageName :: PackageName
+ , notifyReason :: Text
+ , notifyUpdatedAt :: UTCTime
+ }
+
+data NotifyMaintainerUpdateType = MaintainerAdded | MaintainerRemoved
-- | Notifications in the same group are batched in the same email.
--
@@ -1024,6 +1027,15 @@ getNotificationEmails
renderNotifyNewRevision
notifyPackageId
notifyRevisions
+ NotifyMaintainerUpdate{..} ->
+ generalNotification $
+ renderNotifyMaintainerUpdate
+ notifyMaintainerUpdateType
+ notifyUserActor
+ notifyUserSubject
+ notifyPackageName
+ notifyReason
+ notifyUpdatedAt
where
generalNotification emailContent = Just (emailContent, GeneralNotification)
@@ -1036,8 +1048,22 @@ getNotificationEmails
EmailContentParagraph ("Package metadata revision(s), " <> renderPkgLink pkg <> ":")
<> EmailContentList (map (uncurry $ flip renderUserTime) $ sortOn (Down . fst) revs)
+ renderNotifyMaintainerUpdate updateType userActor userSubject pkg reason time =
+ EmailContentParagraph ("Group modified by " <> renderUserTime userActor time <> ":")
+ <> EmailContentList
+ [ case updateType of
+ MaintainerAdded ->
+ renderUser userSubject <> " added to maintainers for " <> renderPackageName pkg
+ MaintainerRemoved ->
+ renderUser userSubject <> " removed from maintainers for " <> renderPackageName pkg
+ , "Reason: " <> EmailContentText reason
+ ]
+
+
{----- Rendering helpers -----}
+ renderPackageName = emailContentStr . unPackageName
+
renderPkgLink pkg =
EmailContentLink
(T.pack $ display pkg)
From 8f7c98ea93496af91acc41cf728696ae0c7e7db0 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 17:35:02 -0700
Subject: [PATCH 40/71] Move describeTagProposal into getNotificationEmails
---
.../Server/Features/UserNotify.hs | 54 ++++++++++++-------
1 file changed, 34 insertions(+), 20 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 37ce441eb..a99217cc2 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -699,8 +699,7 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
let docReportEmails = foldMap describeDocReport <$> docReportNotifications
tagProposals <- collectTagProposals
- tagProposalNotifications <- foldM (genTagProposalList notifyPrefs) Map.empty tagProposals
- let tagProposalEmails = foldMap describeTagProposal <$> tagProposalNotifications
+ tagProposalNotifications <- concatMapM (genTagProposalList notifyPrefs) tagProposals
idx <- queryGetPackageIndex
revIdx <- liftIO queryReverseIndex
@@ -711,13 +710,13 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
getNotificationEmails serverEnv userDetailsFeature users
( foldr1 (Map.unionWith (<>))
[ docReportEmails
- , tagProposalEmails
]
, dependencyEmails
) $
concat
[ revisionUploadNotifications
, groupActionNotifications
+ , tagProposalNotifications
]
mapM_ sendNotifyEmailAndDelay emails
@@ -834,14 +833,19 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
maintainers <- queryUserGroup $ maintainersGroup (packageName . pkgInfoId . fst $ pkgDoc)
return $ foldr addNotification mp (toList maintainers)
- genTagProposalList notifyPrefs mp pkgTags = do
- let addNotification uid m =
- if not (notifyOptOut npref) && notifyPendingTags npref
- then Map.insertWith (++) uid [pkgTags] m
- else m
- where npref = fromMaybe defaultNotifyPrefs (Map.lookup uid notifyPrefs)
- maintainers <- queryUserGroup $ maintainersGroup (fst pkgTags)
- return $ foldr addNotification mp (toList maintainers)
+ genTagProposalList notifyPrefs (pkg, (addedTags, deletedTags)) = do
+ maintainers <- queryUserGroup $ maintainersGroup pkg
+ pure . flip mapMaybe (toList maintainers) $ \uid ->
+ fmap (uid,) $ do
+ let NotifyPref{..} = fromMaybe defaultNotifyPrefs (Map.lookup uid notifyPrefs)
+ guard $ not notifyOptOut
+ guard notifyPendingTags
+ Just
+ NotifyUpdateTags
+ { notifyPackageName = pkg
+ , notifyAddedTags = addedTags
+ , notifyDeletedTags = deletedTags
+ }
genDependencyUpdateList idx revIdx pid =
Map.mapKeys (, pid) <$>
@@ -854,15 +858,6 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
then "Build successful."
else "Build failed."
- describeTagProposal (pkgName, (addTags, delTags)) =
- EmailContentParagraph ("Pending tag proposal for " <> emailContentDisplay pkgName <> ":")
- <> EmailContentList
- [ "Additions: " <> showTags addTags
- , "Deletions: " <> showTags delTags
- ]
- where
- showTags = emailContentIntercalate ", " . map emailContentDisplay . Set.toList
-
describeDependencyUpdate (uId, dep) revDeps = do
mPrefs <- queryGetUserNotifyPref uId
pure $
@@ -918,6 +913,11 @@ data Notification
, notifyReason :: Text
, notifyUpdatedAt :: UTCTime
}
+ | NotifyUpdateTags
+ { notifyPackageName :: PackageName
+ , notifyAddedTags :: Set Tag
+ , notifyDeletedTags :: Set Tag
+ }
data NotifyMaintainerUpdateType = MaintainerAdded | MaintainerRemoved
@@ -1036,6 +1036,12 @@ getNotificationEmails
notifyPackageName
notifyReason
notifyUpdatedAt
+ NotifyUpdateTags{..} ->
+ generalNotification $
+ renderNotifyUpdateTags
+ notifyPackageName
+ notifyAddedTags
+ notifyDeletedTags
where
generalNotification emailContent = Just (emailContent, GeneralNotification)
@@ -1059,6 +1065,14 @@ getNotificationEmails
, "Reason: " <> EmailContentText reason
]
+ renderNotifyUpdateTags pkg addedTags deletedTags =
+ EmailContentParagraph ("Pending tag proposal for " <> emailContentDisplay pkg <> ":")
+ <> EmailContentList
+ [ "Additions: " <> showTags addedTags
+ , "Deletions: " <> showTags deletedTags
+ ]
+ where
+ showTags = emailContentIntercalate ", " . map emailContentDisplay . Set.toList
{----- Rendering helpers -----}
From b60a9c2df9ae9611d89b20b45b320285ad3f4f4f Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 17:38:50 -0700
Subject: [PATCH 41/71] Move describeDependencyUpdate into
getNotificationEmails
---
.../Server/Features/UserNotify.hs | 111 ++++++++++--------
1 file changed, 62 insertions(+), 49 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index a99217cc2..31dfc10c1 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -580,7 +580,7 @@ userNotifyFeature :: ServerEnv
-> StateComponent AcidState NotifyData
-> Templates
-> UserNotifyFeature
-userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
+userNotifyFeature serverEnv@ServerEnv{serverCron}
UserFeature{..}
CoreFeature{..}
UploadFeature{..}
@@ -703,32 +703,25 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
idx <- queryGetPackageIndex
revIdx <- liftIO queryReverseIndex
- dependencyUpdateNotifications <- Map.unionsWith (++) <$> traverse (genDependencyUpdateList idx revIdx . pkgInfoToPkgId) revisionsAndUploads
- dependencyEmails <- Map.traverseWithKey describeDependencyUpdate dependencyUpdateNotifications
+ dependencyUpdateNotifications <- concatMapM (genDependencyUpdateList idx revIdx . pkgInfoToPkgId) revisionsAndUploads
emails <-
- getNotificationEmails serverEnv userDetailsFeature users
+ getNotificationEmails serverEnv userDetailsFeature queryGetUserNotifyPref users
( foldr1 (Map.unionWith (<>))
[ docReportEmails
]
- , dependencyEmails
+ , mempty
) $
concat
[ revisionUploadNotifications
, groupActionNotifications
, tagProposalNotifications
+ , dependencyUpdateNotifications
]
mapM_ sendNotifyEmailAndDelay emails
updateState notifyState (SetNotifyTime now)
- renderPkgLink pkg =
- EmailContentLink
- (T.pack $ display pkg)
- serverBaseURI
- { uriPath = "/package/" <> display (packageName pkg) <> "-" <> display (packageVersion pkg)
- }
-
collectRevisionsAndUploads earlier now = do
pkgIndex <- queryGetPackageIndex
let isRecent pkgInfo =
@@ -847,9 +840,14 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
, notifyDeletedTags = deletedTags
}
- genDependencyUpdateList idx revIdx pid =
- Map.mapKeys (, pid) <$>
- getUserNotificationsOnRelease (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref pid
+ genDependencyUpdateList idx revIdx pkg = do
+ let toNotif watchedPkgs =
+ NotifyDependencyUpdate
+ { notifyPackageId = pkg
+ , notifyWatchedPackages = watchedPkgs
+ }
+ Map.toList . fmap toNotif
+ <$> getUserNotificationsOnRelease (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref pkg
describeDocReport (pkg, success) =
EmailContentParagraph $
@@ -858,37 +856,6 @@ userNotifyFeature serverEnv@ServerEnv{serverBaseURI, serverCron}
then "Build successful."
else "Build failed."
- describeDependencyUpdate (uId, dep) revDeps = do
- mPrefs <- queryGetUserNotifyPref uId
- pure $
- case mPrefs of
- Nothing -> mempty
- Just NotifyPref{notifyDependencyTriggerBounds} ->
- let depName = emailContentDisplay (packageName dep)
- depVersion = emailContentDisplay (packageVersion dep)
- in
- foldMap EmailContentParagraph
- [ "The dependency " <> renderPkgLink dep <> " has been uploaded or revised."
- , case notifyDependencyTriggerBounds of
- Always ->
- "You have requested to be notified for each upload or revision \
- \of a dependency."
- _ ->
- "You have requested to be notified when a dependency isn't \
- \accepted by any of your maintained packages."
- , case notifyDependencyTriggerBounds of
- Always ->
- "These are your packages that depend on " <> depName <> ":"
- BoundsOutOfRange ->
- "These are your packages that require " <> depName
- <> " but don't accept " <> depVersion <> ":"
- NewIncompatibility ->
- "The following packages require " <> depName
- <> " but don't accept " <> depVersion
- <> " (they do accept the second-highest version):"
- ]
- <> EmailContentList (map renderPkgLink revDeps)
-
sendNotifyEmailAndDelay :: Mail -> IO ()
sendNotifyEmailAndDelay email = do
-- TODO: if we need any configuration of sendmail stuff, has to go here
@@ -918,6 +885,12 @@ data Notification
, notifyAddedTags :: Set Tag
, notifyDeletedTags :: Set Tag
}
+ | NotifyDependencyUpdate
+ { notifyPackageId :: PackageId
+ -- ^ Dependency that was updated
+ , notifyWatchedPackages :: [PackageId]
+ -- ^ Packages maintained by user that depend on updated dep
+ }
data NotifyMaintainerUpdateType = MaintainerAdded | MaintainerRemoved
@@ -934,6 +907,7 @@ data NotificationGroup
getNotificationEmails
:: ServerEnv
-> UserDetailsFeature
+ -> (UserId -> IO (Maybe NotifyPref))
-> Users.Users
-> (Map UserId EmailContent, Map (UserId, PackageId) EmailContent)
-> [(UserId, Notification)]
@@ -941,12 +915,14 @@ getNotificationEmails
getNotificationEmails
ServerEnv{serverBaseURI}
UserDetailsFeature{queryUserDetails}
+ queryGetUserNotifyPref
allUsers
(generalEmails, dependencyUpdateEmails)
notifications = do
let userIds = Set.fromList $ map fst notifications
let userIds' = Set.fromList . Map.keys $ generalEmails <> Map.mapKeys fst dependencyUpdateEmails
userIdToDetails <- Map.mapMaybe id <$> fromSetM queryUserDetails (userIds <> userIds')
+ userIdToNotifyPref <- Map.mapMaybe id <$> fromSetM queryGetUserNotifyPref userIds
pure $
let emails =
@@ -959,7 +935,7 @@ getNotificationEmails
. Map.mapWithKey (\(_, pkg) emailContent -> (emailContent, DependencyNotification pkg))
$ dependencyUpdateEmails
, flip mapMaybe notifications $ \(uid, notif) ->
- fmap (uid,) $ renderNotification notif
+ fmap (uid,) $ renderNotification userIdToNotifyPref uid notif
]
in flip mapMaybe (Map.toList emails) $ \((uid, group), emailContent) ->
case uid `Map.lookup` userIdToDetails of
@@ -1016,8 +992,8 @@ getNotificationEmails
{----- Render notifications -----}
- renderNotification :: Notification -> Maybe (EmailContent, NotificationGroup)
- renderNotification = \case
+ renderNotification :: Map UserId NotifyPref -> UserId -> Notification -> Maybe (EmailContent, NotificationGroup)
+ renderNotification userIdToNotifyPref uid = \case
NotifyNewVersion{..} ->
generalNotification $
renderNotifyNewVersion
@@ -1042,6 +1018,17 @@ getNotificationEmails
notifyPackageName
notifyAddedTags
notifyDeletedTags
+ NotifyDependencyUpdate{..} ->
+ case uid `Map.lookup` userIdToNotifyPref of
+ Nothing -> Nothing
+ Just notifyPref ->
+ Just
+ ( renderNotifyDependencyUpdate
+ notifyPref
+ notifyPackageId
+ notifyWatchedPackages
+ , DependencyNotification notifyPackageId
+ )
where
generalNotification emailContent = Just (emailContent, GeneralNotification)
@@ -1074,6 +1061,32 @@ getNotificationEmails
where
showTags = emailContentIntercalate ", " . map emailContentDisplay . Set.toList
+ renderNotifyDependencyUpdate NotifyPref{..} dep revDeps =
+ let depName = emailContentDisplay (packageName dep)
+ depVersion = emailContentDisplay (packageVersion dep)
+ in
+ foldMap EmailContentParagraph
+ [ "The dependency " <> renderPkgLink dep <> " has been uploaded or revised."
+ , case notifyDependencyTriggerBounds of
+ Always ->
+ "You have requested to be notified for each upload or revision \
+ \of a dependency."
+ _ ->
+ "You have requested to be notified when a dependency isn't \
+ \accepted by any of your maintained packages."
+ , case notifyDependencyTriggerBounds of
+ Always ->
+ "These are your packages that depend on " <> depName <> ":"
+ BoundsOutOfRange ->
+ "These are your packages that require " <> depName
+ <> " but don't accept " <> depVersion <> ":"
+ NewIncompatibility ->
+ "The following packages require " <> depName
+ <> " but don't accept " <> depVersion
+ <> " (they do accept the second-highest version):"
+ ]
+ <> EmailContentList (map renderPkgLink revDeps)
+
{----- Rendering helpers -----}
renderPackageName = emailContentStr . unPackageName
From cab758ef502b6abd285470e2f4137c977e578f60 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 17:41:25 -0700
Subject: [PATCH 42/71] Move describeDocReport into getNotificationEmails
---
.../Server/Features/UserNotify.hs | 49 ++++++++++++-------
1 file changed, 31 insertions(+), 18 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 31dfc10c1..496f92ca2 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -695,8 +695,7 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
groupActionNotifications <- concatMapM (genGroupUploadList notifyPrefs) groupActions
docReports <- collectDocReport trimLastTime now
- docReportNotifications <- foldM (genDocReportList notifyPrefs) Map.empty docReports
- let docReportEmails = foldMap describeDocReport <$> docReportNotifications
+ docReportNotifications <- concatMapM (genDocReportList notifyPrefs) docReports
tagProposals <- collectTagProposals
tagProposalNotifications <- concatMapM (genTagProposalList notifyPrefs) tagProposals
@@ -708,13 +707,14 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
emails <-
getNotificationEmails serverEnv userDetailsFeature queryGetUserNotifyPref users
( foldr1 (Map.unionWith (<>))
- [ docReportEmails
+ [
]
, mempty
) $
concat
[ revisionUploadNotifications
, groupActionNotifications
+ , docReportNotifications
, tagProposalNotifications
, dependencyUpdateNotifications
]
@@ -817,14 +817,18 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
}
_ -> pure []
- genDocReportList notifyPrefs mp pkgDoc = do
- let addNotification uid m =
- if not (notifyOptOut npref) && notifyDocBuilderReport npref
- then Map.insertWith (++) uid [pkgDoc] m
- else m
- where npref = fromMaybe defaultNotifyPrefs (Map.lookup uid notifyPrefs)
- maintainers <- queryUserGroup $ maintainersGroup (packageName . pkgInfoId . fst $ pkgDoc)
- return $ foldr addNotification mp (toList maintainers)
+ genDocReportList notifyPrefs (pkg, success) = do
+ maintainers <- queryUserGroup $ maintainersGroup (packageName $ pkgInfoId pkg)
+ pure . flip mapMaybe (toList maintainers) $ \uid ->
+ fmap (uid,) $ do
+ let NotifyPref{..} = fromMaybe defaultNotifyPrefs (Map.lookup uid notifyPrefs)
+ guard $ not notifyOptOut
+ guard notifyDocBuilderReport
+ Just
+ NotifyDocsBuild
+ { notifyPackageId = pkgInfoId pkg
+ , notifyBuildSuccess = success
+ }
genTagProposalList notifyPrefs (pkg, (addedTags, deletedTags)) = do
maintainers <- queryUserGroup $ maintainersGroup pkg
@@ -849,13 +853,6 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
Map.toList . fmap toNotif
<$> getUserNotificationsOnRelease (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref pkg
- describeDocReport (pkg, success) =
- EmailContentParagraph $
- "Package doc build for " <> emailContentDisplay (packageName pkg) <> ":" <> EmailContentSoftBreak <>
- if success
- then "Build successful."
- else "Build failed."
-
sendNotifyEmailAndDelay :: Mail -> IO ()
sendNotifyEmailAndDelay email = do
-- TODO: if we need any configuration of sendmail stuff, has to go here
@@ -880,6 +877,10 @@ data Notification
, notifyReason :: Text
, notifyUpdatedAt :: UTCTime
}
+ | NotifyDocsBuild
+ { notifyPackageId :: PackageId
+ , notifyBuildSuccess :: Bool
+ }
| NotifyUpdateTags
{ notifyPackageName :: PackageName
, notifyAddedTags :: Set Tag
@@ -1012,6 +1013,11 @@ getNotificationEmails
notifyPackageName
notifyReason
notifyUpdatedAt
+ NotifyDocsBuild{..} ->
+ generalNotification $
+ renderNotifyDocsBuild
+ notifyPackageId
+ notifyBuildSuccess
NotifyUpdateTags{..} ->
generalNotification $
renderNotifyUpdateTags
@@ -1052,6 +1058,13 @@ getNotificationEmails
, "Reason: " <> EmailContentText reason
]
+ renderNotifyDocsBuild pkg success =
+ EmailContentParagraph $
+ "Package doc build for " <> renderPkgLink pkg <> ":" <> EmailContentSoftBreak
+ <> if success
+ then "Build successful."
+ else "Build failed."
+
renderNotifyUpdateTags pkg addedTags deletedTags =
EmailContentParagraph ("Pending tag proposal for " <> emailContentDisplay pkg <> ":")
<> EmailContentList
From 3a0fad28eaec9ad6f9412fd6c2cd8c5833a6909c Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 17:42:03 -0700
Subject: [PATCH 43/71] Remove migration workarounds
---
.../Server/Features/UserNotify.hs | 25 +++----------------
1 file changed, 4 insertions(+), 21 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 496f92ca2..7b21ceaa9 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -705,12 +705,7 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
dependencyUpdateNotifications <- concatMapM (genDependencyUpdateList idx revIdx . pkgInfoToPkgId) revisionsAndUploads
emails <-
- getNotificationEmails serverEnv userDetailsFeature queryGetUserNotifyPref users
- ( foldr1 (Map.unionWith (<>))
- [
- ]
- , mempty
- ) $
+ getNotificationEmails serverEnv userDetailsFeature queryGetUserNotifyPref users $
concat
[ revisionUploadNotifications
, groupActionNotifications
@@ -910,7 +905,6 @@ getNotificationEmails
-> UserDetailsFeature
-> (UserId -> IO (Maybe NotifyPref))
-> Users.Users
- -> (Map UserId EmailContent, Map (UserId, PackageId) EmailContent)
-> [(UserId, Notification)]
-> IO [Mail]
getNotificationEmails
@@ -918,26 +912,15 @@ getNotificationEmails
UserDetailsFeature{queryUserDetails}
queryGetUserNotifyPref
allUsers
- (generalEmails, dependencyUpdateEmails)
notifications = do
let userIds = Set.fromList $ map fst notifications
- let userIds' = Set.fromList . Map.keys $ generalEmails <> Map.mapKeys fst dependencyUpdateEmails
- userIdToDetails <- Map.mapMaybe id <$> fromSetM queryUserDetails (userIds <> userIds')
+ userIdToDetails <- Map.mapMaybe id <$> fromSetM queryUserDetails userIds
userIdToNotifyPref <- Map.mapMaybe id <$> fromSetM queryGetUserNotifyPref userIds
pure $
let emails =
- groupNotifications . concat $
- [ Map.toList
- . fmap (, GeneralNotification)
- $ generalEmails
- , Map.toList
- . Map.mapKeys fst
- . Map.mapWithKey (\(_, pkg) emailContent -> (emailContent, DependencyNotification pkg))
- $ dependencyUpdateEmails
- , flip mapMaybe notifications $ \(uid, notif) ->
- fmap (uid,) $ renderNotification userIdToNotifyPref uid notif
- ]
+ groupNotifications . flip mapMaybe notifications $ \(uid, notif) ->
+ fmap (uid,) $ renderNotification userIdToNotifyPref uid notif
in flip mapMaybe (Map.toList emails) $ \((uid, group), emailContent) ->
case uid `Map.lookup` userIdToDetails of
Nothing -> Nothing
From 3d3f48a72b6f0cb2918eb137ef74da8a90052755 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Thu, 17 Aug 2023 13:34:50 -0700
Subject: [PATCH 44/71] Add tests for getNotificationEmails
---
hackage-server.cabal | 16 +
.../Server/Features/UserNotify.hs | 9 +-
tests/ReverseDependenciesTest.hs | 321 +++++++++++++++++-
...mails-NotifyDependencyUpdate-Always.golden | 42 +++
...fyDependencyUpdate-BoundsOutOfRange.golden | 42 +++
...DependencyUpdate-NewIncompatibility.golden | 42 +++
...ationEmails-NotifyDocsBuild-failure.golden | 29 ++
...ationEmails-NotifyDocsBuild-success.golden | 29 ++
...ifyMaintainerUpdate-MaintainerAdded.golden | 34 ++
...yMaintainerUpdate-MaintainerRemoved.golden | 34 ++
...otificationEmails-NotifyNewRevision.golden | 34 ++
...NotificationEmails-NotifyNewVersion.golden | 27 ++
...NotificationEmails-NotifyUpdateTags.golden | 34 ++
.../getNotificationEmails-batched.golden | 51 +++
14 files changed, 737 insertions(+), 7 deletions(-)
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-Always.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-failure.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-success.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewRevision.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewVersion.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyUpdateTags.golden
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-batched.golden
diff --git a/hackage-server.cabal b/hackage-server.cabal
index 98e4731b6..fc0f51c88 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -52,6 +52,17 @@ data-files:
TUF/timestamp.private
extra-source-files:
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewVersion.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewRevision.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-success.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-failure.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyUpdateTags.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-Always.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden
+ tests/golden/ReverseDependenciesTest/getNotificationEmails-batched.golden
tests/permissions-tarballs/*.tar.gz
tests/unpack-checks/correct-package-0.1.0.0/LICENSE
tests/unpack-checks/correct-package-0.1.0.0/Main.hs
@@ -569,11 +580,16 @@ test-suite ReverseDependenciesTest
build-tool-depends: hackage-server:hackage-server
build-depends:
, tasty ^>= 1.4
+ , tasty-golden ^>= 2.3
+ , tasty-hedgehog ^>= 1.4
, tasty-hunit ^>= 0.10
, HUnit ^>= 1.6
, hedgehog ^>= 1.3
, exceptions
, bimap
+ , mime-mail
+ , random
+ , transformers
other-modules: RevDepCommon
benchmark RevDeps
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 7b21ceaa9..47fa29b3d 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -9,7 +9,7 @@
module Distribution.Server.Features.UserNotify (
NotifyData(..),
NotifyPref(..),
- NotifyRevisionRange,
+ NotifyRevisionRange(..),
NotifyTriggerBounds(..),
UserNotifyFeature(..),
defaultNotifyPrefs,
@@ -17,6 +17,11 @@ module Distribution.Server.Features.UserNotify (
importNotifyPref,
initUserNotifyFeature,
notifyDataToCSV,
+
+ -- * getNotificationEmails
+ Notification(..),
+ NotifyMaintainerUpdateType(..),
+ getNotificationEmails,
) where
import Prelude hiding (lookup)
@@ -887,8 +892,10 @@ data Notification
, notifyWatchedPackages :: [PackageId]
-- ^ Packages maintained by user that depend on updated dep
}
+ deriving (Show)
data NotifyMaintainerUpdateType = MaintainerAdded | MaintainerRemoved
+ deriving (Show)
-- | Notifications in the same group are batched in the same email.
--
diff --git a/tests/ReverseDependenciesTest.hs b/tests/ReverseDependenciesTest.hs
index 2626f7a34..6910066c3 100644
--- a/tests/ReverseDependenciesTest.hs
+++ b/tests/ReverseDependenciesTest.hs
@@ -1,25 +1,43 @@
{-# LANGUAGE OverloadedStrings, NamedFieldPuns, TypeApplications, ScopedTypeVariables #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE TupleSections #-}
module Main where
+import Control.Monad (guard)
import Control.Monad.IO.Class (liftIO)
+import qualified Control.Monad.Trans.State as State
import qualified Data.Array as Arr
import qualified Data.Bimap as Bimap
+import qualified Data.ByteString.Lazy as Lazy (ByteString)
+import qualified Data.ByteString.Lazy as ByteStringL
import Data.Foldable (for_)
import Data.Functor.Identity (Identity(..))
import Data.List (partition, foldl')
import qualified Data.Map as Map
+import Data.Maybe (fromJust)
import qualified Data.Set as Set
+import qualified Data.Time as Time
+import Data.Vector (Vector)
+import qualified Data.Vector as Vector
+import qualified Network.Mail.Mime as Mail
+import Network.URI (parseURI)
+import System.Random (mkStdGen)
import Distribution.Package (PackageIdentifier(..), mkPackageName, packageId, packageName)
import Distribution.Server.Features.PreferredVersions.State (PreferredVersions(..), VersionStatus(NormalVersion), PreferredInfo(..))
import Distribution.Server.Features.ReverseDependencies (ReverseFeature(..), ReverseCount(..), reverseFeature)
import Distribution.Server.Features.ReverseDependencies.State (ReverseIndex(..), addPackage, constructReverseIndex, emptyReverseIndex, getDependenciesFlat, getDependencies, getDependenciesFlatRaw, getDependenciesRaw)
+import Distribution.Server.Features.Tags (Tag(..))
+import Distribution.Server.Features.UserDetails (AccountDetails(..), UserDetailsFeature(..))
import Distribution.Server.Features.UserNotify
- ( NotifyData(..)
+ ( Notification(..)
+ , NotifyMaintainerUpdateType(..)
+ , NotifyData(..)
, NotifyPref(..)
- , NotifyRevisionRange
+ , NotifyRevisionRange(..)
, NotifyTriggerBounds(..)
, defaultNotifyPrefs
+ , getNotificationEmails
, getUserNotificationsOnRelease
, importNotifyPref
, notifyDataToCSV
@@ -27,18 +45,38 @@ import Distribution.Server.Features.UserNotify
import Distribution.Server.Framework.BackupRestore (runRestore)
import Distribution.Server.Framework.Hook (newHook)
import Distribution.Server.Framework.MemState (newMemStateWHNF)
+import Distribution.Server.Framework.ServerEnv (ServerEnv(..))
import Distribution.Server.Packages.PackageIndex as PackageIndex
-import Distribution.Server.Packages.Types (PkgInfo(..))
-import Distribution.Server.Users.Types (UserId(..))
+import Distribution.Server.Packages.Types (CabalFileText(..), PkgInfo(..))
+import Distribution.Server.Users.Types
+ ( PasswdHash(..)
+ , UserAuth(..)
+ , UserId(..)
+ , UserName(..)
+ )
import Distribution.Server.Users.UserIdSet as UserIdSet
+import qualified Distribution.Server.Users.Users as Users
import Distribution.Version (mkVersion, version0)
-import Test.Tasty (TestTree, defaultMain, testGroup)
+import Test.Tasty (TestName, TestTree, defaultMain, testGroup)
+import Test.Tasty.Golden (goldenVsString)
+import Test.Tasty.Hedgehog (testProperty)
import Test.Tasty.HUnit
import qualified Hedgehog.Range as Range
import qualified Hedgehog.Gen as Gen
-import Hedgehog ((===), Group(Group), MonadGen, Property, PropertyT, checkSequential, forAll, property)
+import Hedgehog
+ ( (===)
+ , Group(Group)
+ , MonadGen
+ , Property
+ , PropertyT
+ , Range
+ , checkSequential
+ , forAll
+ , property
+ , withTests
+ )
import RevDepCommon (Package(..), TestPackage(..), mkPackage, mkPackageWithCabalFileSuffix, packToPkgInfo)
@@ -276,11 +314,261 @@ allTests = testGroup "ReverseDependenciesTest"
\ build-depends: base >= 4.15 && < 4.16"
])
base4_15)
+ , getNotificationEmailsTests
, testCase "hedgehogTests" $ do
res <- hedgehogTests
assertEqual "hedgehog test pass" True res
]
+getNotificationEmailsTests :: TestTree
+getNotificationEmailsTests =
+ testGroup "getNotificationEmails"
+ [ testProperty "All general notifications batched in one email" . withTests 30 . property $ do
+ notifs <- forAll $ Gen.list (Range.linear 1 10) $ Gen.filterT isGeneral genNotification
+ emails <- liftIO $ getNotificationEmailsMocked $ map (userWatcher,) notifs
+ length emails === 1
+ , testGolden "Render NotifyNewVersion" "getNotificationEmails-NotifyNewVersion.golden" $
+ fmap renderMail . getNotificationEmailMocked userWatcher $
+ NotifyNewVersion
+ { notifyPackageInfo =
+ PkgInfo
+ { pkgInfoId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
+ , pkgMetadataRevisions = Vector.singleton (CabalFileText "", (timestamp, userActor))
+ , pkgTarballRevisions = mempty
+ }
+ }
+ , testGolden "Render NotifyNewRevision" "getNotificationEmails-NotifyNewRevision.golden" $ do
+ let mkRev rev = (CabalFileText "", (rev, userActor))
+ rev0 = (0 * Time.nominalDay) `Time.addUTCTime` timestamp
+ rev1 = (1 * Time.nominalDay) `Time.addUTCTime` timestamp
+ rev2 = (2 * Time.nominalDay) `Time.addUTCTime` timestamp
+ fmap renderMail . getNotificationEmailMocked userWatcher $
+ NotifyNewRevision
+ { notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
+ , notifyRevisions = map (, userActor) [rev1, rev2]
+ }
+ , testGolden "Render NotifyMaintainerUpdate-MaintainerAdded" "getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden" $
+ fmap renderMail . getNotificationEmailMocked userWatcher $
+ NotifyMaintainerUpdate
+ { notifyMaintainerUpdateType = MaintainerAdded
+ , notifyUserActor = userActor
+ , notifyUserSubject = userSubject
+ , notifyPackageName = "base"
+ , notifyReason = "User is cool"
+ , notifyUpdatedAt = timestamp
+ }
+ , testGolden "Render NotifyMaintainerUpdate-MaintainerRemoved" "getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden" $
+ fmap renderMail . getNotificationEmailMocked userWatcher $
+ NotifyMaintainerUpdate
+ { notifyMaintainerUpdateType = MaintainerRemoved
+ , notifyUserActor = userActor
+ , notifyUserSubject = userSubject
+ , notifyPackageName = "base"
+ , notifyReason = "User is no longer cool"
+ , notifyUpdatedAt = timestamp
+ }
+ , testGolden "Render NotifyDocsBuild-success" "getNotificationEmails-NotifyDocsBuild-success.golden" $
+ fmap renderMail . getNotificationEmailMocked userWatcher $
+ NotifyDocsBuild
+ { notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
+ , notifyBuildSuccess = True
+ }
+ , testGolden "Render NotifyDocsBuild-failure" "getNotificationEmails-NotifyDocsBuild-failure.golden" $
+ fmap renderMail . getNotificationEmailMocked userWatcher $
+ NotifyDocsBuild
+ { notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
+ , notifyBuildSuccess = False
+ }
+ , testGolden "Render NotifyUpdateTags" "getNotificationEmails-NotifyUpdateTags.golden" $
+ fmap renderMail . getNotificationEmailMocked userWatcher $
+ NotifyUpdateTags
+ { notifyPackageName = "base"
+ , notifyAddedTags = Set.fromList . map Tag $ ["bsd3", "library", "prelude"]
+ , notifyDeletedTags = Set.fromList . map Tag $ ["example", "bad", "foo"]
+ }
+ , testGolden "Render NotifyDependencyUpdate-Always" "getNotificationEmails-NotifyDependencyUpdate-Always.golden" $
+ fmap renderMail
+ . getNotificationEmail
+ testServerEnv
+ testUserDetailsFeature
+ (\_ -> pure $ Just notifyEverything{notifyDependencyTriggerBounds = Always})
+ allUsers
+ userWatcher
+ $ NotifyDependencyUpdate
+ { notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
+ , notifyWatchedPackages = [PackageIdentifier "mtl" (mkVersion [2, 3])]
+ }
+ , testGolden "Render NotifyDependencyUpdate-NewIncompatibility" "getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden" $
+ fmap renderMail
+ . getNotificationEmail
+ testServerEnv
+ testUserDetailsFeature
+ (\_ -> pure $ Just notifyEverything{notifyDependencyTriggerBounds = NewIncompatibility})
+ allUsers
+ userWatcher
+ $ NotifyDependencyUpdate
+ { notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
+ , notifyWatchedPackages = [PackageIdentifier "mtl" (mkVersion [2, 3])]
+ }
+ , testGolden "Render NotifyDependencyUpdate-BoundsOutOfRange" "getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden" $
+ fmap renderMail
+ . getNotificationEmail
+ testServerEnv
+ testUserDetailsFeature
+ (\_ -> pure $ Just notifyEverything{notifyDependencyTriggerBounds = BoundsOutOfRange})
+ allUsers
+ userWatcher
+ $ NotifyDependencyUpdate
+ { notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
+ , notifyWatchedPackages = [PackageIdentifier "mtl" (mkVersion [2, 3])]
+ }
+ , testGolden "Render general notifications in single batched email" "getNotificationEmails-batched.golden" $ do
+ emails <-
+ getNotificationEmailsMocked . map (userWatcher,) $
+ [ NotifyNewRevision
+ { notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
+ , notifyRevisions = [(timestamp, userActor)]
+ }
+ , NotifyDocsBuild
+ { notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
+ , notifyBuildSuccess = True
+ }
+ , NotifyUpdateTags
+ { notifyPackageName = "base"
+ , notifyAddedTags = Set.fromList [Tag "newtag"]
+ , notifyDeletedTags = Set.fromList [Tag "oldtag"]
+ }
+ ]
+ case emails of
+ [email] -> pure $ renderMail email
+ _ -> error $ "Emails were not batched: " ++ show emails
+ ]
+ where
+ -- If adding a new constructor here, make sure to do the following:
+ -- * update genNotification
+ -- * add a golden test above
+ -- * add the golden file to hackage-server.cabal
+ _allNotificationTypes = \case
+ NotifyNewVersion{} -> ()
+ NotifyNewRevision{} -> ()
+ NotifyMaintainerUpdate{} -> ()
+ NotifyDocsBuild{} -> ()
+ NotifyUpdateTags{} -> ()
+ NotifyDependencyUpdate{} -> ()
+
+ isGeneral = \case
+ NotifyNewVersion{} -> True
+ NotifyNewRevision{} -> True
+ NotifyMaintainerUpdate{} -> True
+ NotifyDocsBuild{} -> True
+ NotifyUpdateTags{} -> True
+ NotifyDependencyUpdate{} -> False
+
+ -- userWatcher = user getting the notification
+ -- userActor = user that did the action
+ -- userSubject = user the action is about
+ ((userWatcher, userActor, userSubject), allUsers) =
+ (`State.runState` Users.emptyUsers) $ do
+ let addUser name = State.StateT $ \users0 ->
+ case Users.addUserEnabled (UserName name) (UserAuth $ PasswdHash "") users0 of
+ Right (users1, uid) -> pure (uid, users1)
+ Left _ -> error $ "Got duplicate username: " <> name
+ (,,)
+ <$> addUser "user-watcher"
+ <*> addUser "user-actor"
+ <*> addUser "user-subject"
+
+ getNotificationEmail env details pref users uid notif =
+ getNotificationEmails env details pref users [(uid, notif)] >>= \case
+ [email] -> pure email
+ _ -> error "Did not get exactly one email"
+
+ testServerEnv =
+ ServerEnv
+ { serverBaseURI = fromJust $ parseURI "https://hackage.haskell.org"
+ }
+ testUserDetailsFeature =
+ UserDetailsFeature
+ { queryUserDetails = \uid ->
+ pure $ do
+ guard $ uid == userWatcher
+ Just
+ AccountDetails
+ { accountName = "user-watcher"
+ , accountContactEmail = "user-watcher@example.com"
+ , accountKind = Nothing
+ , accountAdminNotes = ""
+ }
+ }
+ notifyEverything =
+ NotifyPref
+ { notifyOptOut = False
+ , notifyRevisionRange = NotifyAllVersions
+ , notifyUpload = True
+ , notifyMaintainerGroup = True
+ , notifyDocBuilderReport = True
+ , notifyPendingTags = True
+ , notifyDependencyForMaintained = True
+ , notifyDependencyTriggerBounds = Always
+ }
+ testGetUserNotifyPref uid = pure $ do
+ guard $ uid == userWatcher
+ Just notifyEverything
+ getNotificationEmailsMocked =
+ getNotificationEmails
+ testServerEnv
+ testUserDetailsFeature
+ testGetUserNotifyPref
+ allUsers
+ getNotificationEmailMocked =
+ getNotificationEmail
+ testServerEnv
+ testUserDetailsFeature
+ testGetUserNotifyPref
+ allUsers
+
+ renderMail = fst . Mail.renderMail (mkStdGen 0)
+ timestamp = Time.UTCTime (Time.fromGregorian 2020 1 1) 0
+
+ genNotification =
+ Gen.choice
+ [ NotifyNewVersion
+ <$> genPkgInfo
+ , NotifyNewRevision
+ <$> genPackageId
+ <*> Gen.list (Range.linear 1 5) genUploadInfo
+ , NotifyMaintainerUpdate
+ <$> Gen.element [MaintainerAdded, MaintainerRemoved]
+ <*> genNonExistentUserId
+ <*> genNonExistentUserId
+ <*> genPackageName
+ <*> Gen.text (Range.linear 1 20) Gen.unicode
+ <*> genUTCTime
+ , NotifyDocsBuild
+ <$> genPackageId
+ <*> Gen.bool
+ , NotifyUpdateTags
+ <$> genPackageName
+ <*> Gen.set (Range.linear 1 5) genTag
+ <*> Gen.set (Range.linear 1 5) genTag
+ , NotifyDependencyUpdate
+ <$> genPackageId
+ <*> Gen.list (Range.linear 1 10) genPackageId
+ ]
+
+ genPackageName = mkPackageName <$> Gen.string (Range.linear 1 30) Gen.unicode
+ genVersion = mkVersion <$> Gen.list (Range.linear 1 4) (Gen.int $ Range.linear 0 50)
+ genPackageId = PackageIdentifier <$> genPackageName <*> genVersion
+ genCabalFileText = CabalFileText . ByteStringL.fromStrict <$> Gen.utf8 (Range.linear 0 50000) Gen.unicode
+ genNonExistentUserId = UserId <$> Gen.int (Range.linear (-1000) (-1))
+ genUploadInfo = (,) <$> genUTCTime <*> genNonExistentUserId
+ genTag = Tag <$> Gen.string (Range.linear 1 10) Gen.unicode
+ genPkgInfo =
+ PkgInfo
+ <$> genPackageId
+ <*> genVec (Range.linear 1 5) ((,) <$> genCabalFileText <*> genUploadInfo)
+ <*> pure Vector.empty -- ignoring pkgTarballRevisions for now
+
genPacks :: PropertyT IO [Package TestPackage]
genPacks = do
numPacks <- forAll $ Gen.int (Range.linear 1 10)
@@ -385,5 +673,26 @@ hedgehogTests =
, ("prop_csvBackupRoundtrips", prop_csvBackupRoundtrips)
]
+testGolden :: TestName -> FilePath -> IO Lazy.ByteString -> TestTree
+testGolden name fp = goldenVsString name ("tests/golden/ReverseDependenciesTest/" <> fp)
+
main :: IO ()
main = defaultMain allTests
+
+{----- Utilities -----}
+
+genUTCTime :: MonadGen m => m Time.UTCTime
+genUTCTime =
+ Time.UTCTime
+ <$> genDay
+ <*> genDiffTime
+ where
+ genDay =
+ Time.fromGregorian
+ <$> Gen.integral (Range.linear 0 3000)
+ <*> Gen.int (Range.linear 1 12)
+ <*> Gen.int (Range.linear 1 31)
+ genDiffTime = realToFrac <$> Gen.double (Range.linearFrac 0 86400)
+
+genVec :: MonadGen m => Range Int -> m a -> m (Vector a)
+genVec r = fmap Vector.fromList . Gen.list r
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-Always.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-Always.golden
new file mode 100644
index 000000000..1be08910f
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-Always.golden
@@ -0,0 +1,42 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Dependency Update: base-4.18.0.0
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+The dependency base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0) has been uploaded or revised.
+
+You have requested to be notified for each upload or revision of a dependency.
+
+These are your packages that depend on base:
+
+* mtl-2.3 (https://hackage.haskell.org/package/mtl-2.3)
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+The dependency base-4.18.0.0 has been uploaded or revised.
+
+
+You have requested to be notified for each upload or revision of a dependency.
+
+
+These are your packages that depend on base:
+
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden
new file mode 100644
index 000000000..f0af6524b
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden
@@ -0,0 +1,42 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Dependency Update: base-4.18.0.0
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+The dependency base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0) has been uploaded or revised.
+
+You have requested to be notified when a dependency isn't accepted by any of your maintained packages.
+
+These are your packages that require base but don't accept 4.18.0.0:
+
+* mtl-2.3 (https://hackage.haskell.org/package/mtl-2.3)
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+The dependency base-4.18.0.0 has been uploaded or revised.
+
+
+You have requested to be notified when a dependency isn't accepted by any of your maintained packages.
+
+
+These are your packages that require base but don't accept 4.18.0.0:
+
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden
new file mode 100644
index 000000000..e14720d55
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden
@@ -0,0 +1,42 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Dependency Update: base-4.18.0.0
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+The dependency base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0) has been uploaded or revised.
+
+You have requested to be notified when a dependency isn't accepted by any of your maintained packages.
+
+The following packages require base but don't accept 4.18.0.0 (they do accept the second-highest version):
+
+* mtl-2.3 (https://hackage.haskell.org/package/mtl-2.3)
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+The dependency base-4.18.0.0 has been uploaded or revised.
+
+
+You have requested to be notified when a dependency isn't accepted by any of your maintained packages.
+
+
+The following packages require base but don't accept 4.18.0.0 (they do accept the second-highest version):
+
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-failure.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-failure.golden
new file mode 100644
index 000000000..5932132ed
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-failure.golden
@@ -0,0 +1,29 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Maintainer Notifications
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+Package doc build for base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
+Build failed.
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+Package doc build for base-4.18.0.0 :
+ Build failed.
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-success.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-success.golden
new file mode 100644
index 000000000..f60d93d7f
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-success.golden
@@ -0,0 +1,29 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Maintainer Notifications
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+Package doc build for base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
+Build successful.
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+Package doc build for base-4.18.0.0 :
+ Build successful.
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden
new file mode 100644
index 000000000..07d465d03
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden
@@ -0,0 +1,34 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Maintainer Notifications
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
+
+* user-subject added to maintainers for base
+* Reason: User is cool
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
+
+
+ user-subject added to maintainers for base
+ Reason: User is cool
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden
new file mode 100644
index 000000000..73dccf5c7
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden
@@ -0,0 +1,34 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Maintainer Notifications
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
+
+* user-subject removed from maintainers for base
+* Reason: User is no longer cool
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
+
+
+ user-subject removed from maintainers for base
+ Reason: User is no longer cool
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewRevision.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewRevision.golden
new file mode 100644
index 000000000..afd5fd08a
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewRevision.golden
@@ -0,0 +1,34 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Maintainer Notifications
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+Package metadata revision(s), base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
+
+* user-actor [Fri Jan 3 00:00:00 UTC 2020]
+* user-actor [Thu Jan 2 00:00:00 UTC 2020]
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+Package metadata revision(s), base-4.18.0.0 :
+
+
+ user-actor [Fri Jan 3 00:00:00 UTC 2020]
+ user-actor [Thu Jan 2 00:00:00 UTC 2020]
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewVersion.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewVersion.golden
new file mode 100644
index 000000000..303ffed7f
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewVersion.golden
@@ -0,0 +1,27 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Maintainer Notifications
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+Package upload, base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0), by user-actor [Wed Jan 1 00:00:00 UTC 2020]
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+Package upload, base-4.18.0.0 , by user-actor [Wed Jan 1 00:00:00 UTC 2020]
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyUpdateTags.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyUpdateTags.golden
new file mode 100644
index 000000000..718f2c2c3
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyUpdateTags.golden
@@ -0,0 +1,34 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Maintainer Notifications
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+Pending tag proposal for base:
+
+* Additions: bsd3, library, prelude
+* Deletions: bad, example, foo
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+Pending tag proposal for base:
+
+
+ Additions: bsd3, library, prelude
+ Deletions: bad, example, foo
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-batched.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-batched.golden
new file mode 100644
index 000000000..abf6f7167
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-batched.golden
@@ -0,0 +1,51 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Maintainer Notifications
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+
+Pending tag proposal for base:
+
+* Additions: newtag
+* Deletions: oldtag
+
+Package doc build for base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
+Build successful.
+
+Package metadata revision(s), base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
+
+* user-actor [Wed Jan 1 00:00:00 UTC 2020]
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+
+
+
+Pending tag proposal for base:
+
+
+ Additions: newtag
+ Deletions: oldtag
+
+
+Package doc build for base-4.18.0.0 :
+ Build successful.
+
+
+Package metadata revision(s), base-4.18.0.0 :
+
+
+ user-actor [Wed Jan 1 00:00:00 UTC 2020]
+
+
+You can adjust your notification preferences at
+https://hackage.haskell.org/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
From 364ffe90cb76848ca07ba9553fb30b6c769a3246 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Fri, 18 Aug 2023 09:53:09 -0700
Subject: [PATCH 45/71] Update threadDelay comment, use htmlPart/plainPart
---
.../Server/Features/UserNotify.hs | 3 +-
src/Distribution/Server/Util/Email.hs | 15 +---
...mails-NotifyDependencyUpdate-Always.golden | 67 ++++++++-------
...fyDependencyUpdate-BoundsOutOfRange.golden | 67 ++++++++-------
...DependencyUpdate-NewIncompatibility.golden | 69 ++++++++-------
...ationEmails-NotifyDocsBuild-failure.golden | 34 ++++----
...ationEmails-NotifyDocsBuild-success.golden | 34 ++++----
...ifyMaintainerUpdate-MaintainerAdded.golden | 46 +++++-----
...yMaintainerUpdate-MaintainerRemoved.golden | 46 +++++-----
...otificationEmails-NotifyNewRevision.golden | 48 ++++++-----
...NotificationEmails-NotifyNewVersion.golden | 31 ++++---
...NotificationEmails-NotifyUpdateTags.golden | 46 +++++-----
.../getNotificationEmails-batched.golden | 84 ++++++++++---------
13 files changed, 327 insertions(+), 263 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index 47fa29b3d..cb6b96c39 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -858,7 +858,8 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
-- TODO: if we need any configuration of sendmail stuff, has to go here
renderSendMail email
- -- delay sending out emails, because ???
+ -- delay sending out emails, to avoid spamming people if we accidentally
+ -- send out too many emails
threadDelay 250000
data Notification
diff --git a/src/Distribution/Server/Util/Email.hs b/src/Distribution/Server/Util/Email.hs
index 30fd8bb9c..daabdfd62 100644
--- a/src/Distribution/Server/Util/Email.hs
+++ b/src/Distribution/Server/Util/Email.hs
@@ -68,20 +68,9 @@ emailContentUrl uri = EmailContentLink (uriToText uri) uri
fromEmailContent :: EmailContent -> Alternatives
fromEmailContent emailContent =
- [ Part
- { partType = contentType <> "; charset=utf-8"
- , partEncoding = None
- , partDisposition = DefaultDisposition
- , partHeaders = []
- , partContent = PartContent $ TextL.encodeUtf8 $ TextL.fromStrict content
- }
- | (contentType, content) <- contents
+ [ plainPart $ TextL.fromStrict $ toPlainContent emailContent
+ , htmlPart $ TextL.fromStrict $ toHtmlContent emailContent
]
- where
- contents =
- [ ("text/plain", toPlainContent emailContent)
- , ("text/html", toHtmlContent emailContent)
- ]
-- | Convert an 'EmailContent' to plain text.
--
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-Always.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-Always.golden
index 1be08910f..03900ecaf 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-Always.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-Always.golden
@@ -6,37 +6,46 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
-
-The dependency base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0) has been uploaded or revised.
-
-You have requested to be notified for each upload or revision of a dependency.
-
-These are your packages that depend on base:
-
-* mtl-2.3 (https://hackage.haskell.org/package/mtl-2.3)
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Content-Transfer-Encoding: quoted-printable
+
+The dependency base-4=2E18=2E0=2E0 (https://hackage=2Ehaskell=2Eorg/package=
+/base-4=2E18=2E0=2E0) has been uploaded or revised=2E
+
+You have requested to be notified for each upload or revision of a dependen=
+cy=2E
+
+These are your packages that depend on base:
+
+* mtl-2=2E3 (https://hackage=2Ehaskell=2Eorg/package/mtl-2=2E3)
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
-
-
-
-The dependency base-4.18.0.0 has been uploaded or revised.
-
-
-You have requested to be notified for each upload or revision of a dependency.
-
-
-These are your packages that depend on base:
-
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+Content-Transfer-Encoding: quoted-printable
+
+
+
+The dependency base-4=2E18=2E0=2E0 has been uploaded or revised=2E
+
+
+You have requested to be notified for each upload or revision of a dependen=
+cy=2E
+
+
+These are your packages that depend on base:
+
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden
index f0af6524b..f02b34387 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden
@@ -6,37 +6,46 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
-
-The dependency base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0) has been uploaded or revised.
-
-You have requested to be notified when a dependency isn't accepted by any of your maintained packages.
-
-These are your packages that require base but don't accept 4.18.0.0:
-
-* mtl-2.3 (https://hackage.haskell.org/package/mtl-2.3)
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Content-Transfer-Encoding: quoted-printable
+
+The dependency base-4=2E18=2E0=2E0 (https://hackage=2Ehaskell=2Eorg/package=
+/base-4=2E18=2E0=2E0) has been uploaded or revised=2E
+
+You have requested to be notified when a dependency isn't accepted by any o=
+f your maintained packages=2E
+
+These are your packages that require base but don't accept 4=2E18=2E0=2E0:
+
+* mtl-2=2E3 (https://hackage=2Ehaskell=2Eorg/package/mtl-2=2E3)
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
-
-
-
-The dependency base-4.18.0.0 has been uploaded or revised.
-
-
-You have requested to be notified when a dependency isn't accepted by any of your maintained packages.
-
-
-These are your packages that require base but don't accept 4.18.0.0:
-
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+Content-Transfer-Encoding: quoted-printable
+
+
+
+The dependency base-4=2E18=2E0=2E0 has been uploaded or revised=2E
+
+
+You have requested to be notified when a dependency isn't accepted by any o=
+f your maintained packages=2E
+
+
+These are your packages that require base but don't accept 4=2E18=2E0=2E0:
+
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden
index e14720d55..8c05de87a 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden
@@ -6,37 +6,48 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
-
-The dependency base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0) has been uploaded or revised.
-
-You have requested to be notified when a dependency isn't accepted by any of your maintained packages.
-
-The following packages require base but don't accept 4.18.0.0 (they do accept the second-highest version):
-
-* mtl-2.3 (https://hackage.haskell.org/package/mtl-2.3)
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Content-Transfer-Encoding: quoted-printable
+
+The dependency base-4=2E18=2E0=2E0 (https://hackage=2Ehaskell=2Eorg/package=
+/base-4=2E18=2E0=2E0) has been uploaded or revised=2E
+
+You have requested to be notified when a dependency isn't accepted by any o=
+f your maintained packages=2E
+
+The following packages require base but don't accept 4=2E18=2E0=2E0 (they d=
+o accept the second-highest version):
+
+* mtl-2=2E3 (https://hackage=2Ehaskell=2Eorg/package/mtl-2=2E3)
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
-
-
-
-The dependency base-4.18.0.0 has been uploaded or revised.
-
-
-You have requested to be notified when a dependency isn't accepted by any of your maintained packages.
-
-
-The following packages require base but don't accept 4.18.0.0 (they do accept the second-highest version):
-
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+Content-Transfer-Encoding: quoted-printable
+
+
+
+The dependency base-4=2E18=2E0=2E0 has been uploaded or revised=2E
+
+
+You have requested to be notified when a dependency isn't accepted by any o=
+f your maintained packages=2E
+
+
+The following packages require base but don't accept 4=2E18=2E0=2E0 (they d=
+o accept the second-highest version):
+
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-failure.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-failure.golden
index 5932132ed..a328aca46 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-failure.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-failure.golden
@@ -6,24 +6,30 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
-Package doc build for base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
-Build failed.
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Package doc build for base-4=2E18=2E0=2E0 (https://hackage=2Ehaskell=2Eorg/=
+package/base-4=2E18=2E0=2E0):
+Build failed=2E
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
-
-
-Package doc build for base-4.18.0.0 :
- Build failed.
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+
+
+Package doc build for base-4=2E18=2E0=2E0 :
+ Build failed=2E
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-success.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-success.golden
index f60d93d7f..aa6392fff 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-success.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyDocsBuild-success.golden
@@ -6,24 +6,30 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
-Package doc build for base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
-Build successful.
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Package doc build for base-4=2E18=2E0=2E0 (https://hackage=2Ehaskell=2Eorg/=
+package/base-4=2E18=2E0=2E0):
+Build successful=2E
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
-
-
-Package doc build for base-4.18.0.0 :
- Build successful.
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+
+
+Package doc build for base-4=2E18=2E0=2E0 :
+ Build successful=2E
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden
index 07d465d03..e3777cc93 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerAdded.golden
@@ -6,29 +6,33 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
-
-Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
-
-* user-subject added to maintainers for base
-* Reason: User is cool
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Content-Transfer-Encoding: quoted-printable
+
+Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
+
+* user-subject added to maintainers for base
+* Reason: User is cool
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
-
-
-
-Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
-
-
- user-subject added to maintainers for base
- Reason: User is cool
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+Content-Transfer-Encoding: quoted-printable
+
+
+
+Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
+
+
+ user-subject added to maintainers for base
+ Reason: User is cool
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden
index 73dccf5c7..a5a4c91f7 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyMaintainerUpdate-MaintainerRemoved.golden
@@ -6,29 +6,33 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
-
-Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
-
-* user-subject removed from maintainers for base
-* Reason: User is no longer cool
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Content-Transfer-Encoding: quoted-printable
+
+Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
+
+* user-subject removed from maintainers for base
+* Reason: User is no longer cool
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
-
-
-
-Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
-
-
- user-subject removed from maintainers for base
- Reason: User is no longer cool
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+Content-Transfer-Encoding: quoted-printable
+
+
+
+Group modified by user-actor [Wed Jan 1 00:00:00 UTC 2020]:
+
+
+ user-subject removed from maintainers for base
+ Reason: User is no longer cool
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewRevision.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewRevision.golden
index afd5fd08a..02e1aca6f 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewRevision.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewRevision.golden
@@ -6,29 +6,35 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
-
-Package metadata revision(s), base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
-
-* user-actor [Fri Jan 3 00:00:00 UTC 2020]
-* user-actor [Thu Jan 2 00:00:00 UTC 2020]
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Content-Transfer-Encoding: quoted-printable
+
+Package metadata revision(s), base-4=2E18=2E0=2E0 (https://hackage=2Ehaskel=
+l=2Eorg/package/base-4=2E18=2E0=2E0):
+
+* user-actor [Fri Jan 3 00:00:00 UTC 2020]
+* user-actor [Thu Jan 2 00:00:00 UTC 2020]
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
-
-
-
-Package metadata revision(s), base-4.18.0.0 :
-
-
- user-actor [Fri Jan 3 00:00:00 UTC 2020]
- user-actor [Thu Jan 2 00:00:00 UTC 2020]
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+Content-Transfer-Encoding: quoted-printable
+
+
+
+Package metadata revision(s), base-4=2E18=2E0=2E0 :
+
+
+ user-actor [Fri Jan 3 00:00:00 UTC 2020]
+ user-actor [Thu Jan 2 00:00:00 UTC 2020]
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewVersion.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewVersion.golden
index 303ffed7f..9a9cf7cfa 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewVersion.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyNewVersion.golden
@@ -6,22 +6,29 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
-Package upload, base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0), by user-actor [Wed Jan 1 00:00:00 UTC 2020]
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Package upload, base-4=2E18=2E0=2E0 (https://hackage=2Ehaskell=2Eorg/packag=
+e/base-4=2E18=2E0=2E0), by user-actor [Wed Jan 1 00:00:00 UTC 2020]
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
-
-
-Package upload, base-4.18.0.0 , by user-actor [Wed Jan 1 00:00:00 UTC 2020]
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+
+
+Package upload, base-4=2E18=2E0=2E0 , by user-actor [Wed Jan 1 00:00:00 =
+UTC 2020]
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyUpdateTags.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyUpdateTags.golden
index 718f2c2c3..b9e473041 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyUpdateTags.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyUpdateTags.golden
@@ -6,29 +6,33 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
-
-Pending tag proposal for base:
-
-* Additions: bsd3, library, prelude
-* Deletions: bad, example, foo
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Content-Transfer-Encoding: quoted-printable
+
+Pending tag proposal for base:
+
+* Additions: bsd3, library, prelude
+* Deletions: bad, example, foo
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
-
-
-
-Pending tag proposal for base:
-
-
- Additions: bsd3, library, prelude
- Deletions: bad, example, foo
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+Content-Transfer-Encoding: quoted-printable
+
+
+
+Pending tag proposal for base:
+
+
+ Additions: bsd3, library, prelude
+ Deletions: bad, example, foo
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-batched.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-batched.golden
index abf6f7167..d3d6811de 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-batched.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-batched.golden
@@ -6,46 +6,54 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
--YIYrWcf3to
Content-Type: text/plain; charset=utf-8
-
-Pending tag proposal for base:
-
-* Additions: newtag
-* Deletions: oldtag
-
-Package doc build for base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
-Build successful.
-
-Package metadata revision(s), base-4.18.0.0 (https://hackage.haskell.org/package/base-4.18.0.0):
-
-* user-actor [Wed Jan 1 00:00:00 UTC 2020]
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify (https://hackage.haskell.org/user/user-watcher/notify)
-
+Content-Transfer-Encoding: quoted-printable
+
+Pending tag proposal for base:
+
+* Additions: newtag
+* Deletions: oldtag
+
+Package doc build for base-4=2E18=2E0=2E0 (https://hackage=2Ehaskell=2Eorg/=
+package/base-4=2E18=2E0=2E0):
+Build successful=2E
+
+Package metadata revision(s), base-4=2E18=2E0=2E0 (https://hackage=2Ehaskel=
+l=2Eorg/package/base-4=2E18=2E0=2E0):
+
+* user-actor [Wed Jan 1 00:00:00 UTC 2020]
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
--YIYrWcf3to
Content-Type: text/html; charset=utf-8
-
-
-
-Pending tag proposal for base:
-
-
- Additions: newtag
- Deletions: oldtag
-
-
-Package doc build for base-4.18.0.0 :
- Build successful.
-
-
-Package metadata revision(s), base-4.18.0.0 :
-
-
- user-actor [Wed Jan 1 00:00:00 UTC 2020]
-
-
-You can adjust your notification preferences at
-https://hackage.haskell.org/user/user-watcher/notify
+Content-Transfer-Encoding: quoted-printable
+
+
+
+Pending tag proposal for base:
+
+
+ Additions: newtag
+ Deletions: oldtag
+
+
+Package doc build for base-4=2E18=2E0=2E0 :
+ Build successful=2E
+
+
+Package metadata revision(s), base-4=2E18=2E0=2E0 :
+
+
+ user-actor [Wed Jan 1 00:00:00 UTC 2020]
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
--YIYrWcf3to--
\ No newline at end of file
From 6d820545f2fdbdd96285e715f3d43b971faa7740 Mon Sep 17 00:00:00 2001
From: Brandon Chinn
Date: Fri, 18 Aug 2023 11:15:13 -0700
Subject: [PATCH 46/71] Move NotifyTriggerBounds into NotifyDependencyUpdate
---
.../Server/Features/UserNotify.hs | 49 +++++++++----------
tests/ReverseDependenciesTest.hs | 27 +++-------
2 files changed, 28 insertions(+), 48 deletions(-)
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index cb6b96c39..fb11fa5ef 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -707,10 +707,10 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
idx <- queryGetPackageIndex
revIdx <- liftIO queryReverseIndex
- dependencyUpdateNotifications <- concatMapM (genDependencyUpdateList idx revIdx . pkgInfoToPkgId) revisionsAndUploads
+ dependencyUpdateNotifications <- concatMapM (genDependencyUpdateList notifyPrefs idx revIdx . pkgInfoToPkgId) revisionsAndUploads
emails <-
- getNotificationEmails serverEnv userDetailsFeature queryGetUserNotifyPref users $
+ getNotificationEmails serverEnv userDetailsFeature users $
concat
[ revisionUploadNotifications
, groupActionNotifications
@@ -844,13 +844,16 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
, notifyDeletedTags = deletedTags
}
- genDependencyUpdateList idx revIdx pkg = do
- let toNotif watchedPkgs =
+ genDependencyUpdateList notifyPrefs idx revIdx pkg = do
+ let toNotif uid watchedPkgs =
NotifyDependencyUpdate
{ notifyPackageId = pkg
, notifyWatchedPackages = watchedPkgs
+ , notifyTriggerBounds =
+ notifyDependencyTriggerBounds $
+ fromMaybe defaultNotifyPrefs (Map.lookup uid notifyPrefs)
}
- Map.toList . fmap toNotif
+ Map.toList . Map.mapWithKey toNotif
<$> getUserNotificationsOnRelease (queryUserGroup . maintainersGroup) idx revIdx queryGetUserNotifyPref pkg
sendNotifyEmailAndDelay :: Mail -> IO ()
@@ -892,6 +895,7 @@ data Notification
-- ^ Dependency that was updated
, notifyWatchedPackages :: [PackageId]
-- ^ Packages maintained by user that depend on updated dep
+ , notifyTriggerBounds :: NotifyTriggerBounds
}
deriving (Show)
@@ -911,24 +915,19 @@ data NotificationGroup
getNotificationEmails
:: ServerEnv
-> UserDetailsFeature
- -> (UserId -> IO (Maybe NotifyPref))
-> Users.Users
-> [(UserId, Notification)]
-> IO [Mail]
getNotificationEmails
ServerEnv{serverBaseURI}
UserDetailsFeature{queryUserDetails}
- queryGetUserNotifyPref
allUsers
notifications = do
let userIds = Set.fromList $ map fst notifications
userIdToDetails <- Map.mapMaybe id <$> fromSetM queryUserDetails userIds
- userIdToNotifyPref <- Map.mapMaybe id <$> fromSetM queryGetUserNotifyPref userIds
pure $
- let emails =
- groupNotifications . flip mapMaybe notifications $ \(uid, notif) ->
- fmap (uid,) $ renderNotification userIdToNotifyPref uid notif
+ let emails = groupNotifications $ map (fmap renderNotification) notifications
in flip mapMaybe (Map.toList emails) $ \((uid, group), emailContent) ->
case uid `Map.lookup` userIdToDetails of
Nothing -> Nothing
@@ -984,8 +983,8 @@ getNotificationEmails
{----- Render notifications -----}
- renderNotification :: Map UserId NotifyPref -> UserId -> Notification -> Maybe (EmailContent, NotificationGroup)
- renderNotification userIdToNotifyPref uid = \case
+ renderNotification :: Notification -> (EmailContent, NotificationGroup)
+ renderNotification = \case
NotifyNewVersion{..} ->
generalNotification $
renderNotifyNewVersion
@@ -1016,18 +1015,14 @@ getNotificationEmails
notifyAddedTags
notifyDeletedTags
NotifyDependencyUpdate{..} ->
- case uid `Map.lookup` userIdToNotifyPref of
- Nothing -> Nothing
- Just notifyPref ->
- Just
- ( renderNotifyDependencyUpdate
- notifyPref
- notifyPackageId
- notifyWatchedPackages
- , DependencyNotification notifyPackageId
- )
+ ( renderNotifyDependencyUpdate
+ notifyTriggerBounds
+ notifyPackageId
+ notifyWatchedPackages
+ , DependencyNotification notifyPackageId
+ )
where
- generalNotification emailContent = Just (emailContent, GeneralNotification)
+ generalNotification = (, GeneralNotification)
renderNotifyNewVersion pkg =
EmailContentParagraph $
@@ -1065,20 +1060,20 @@ getNotificationEmails
where
showTags = emailContentIntercalate ", " . map emailContentDisplay . Set.toList
- renderNotifyDependencyUpdate NotifyPref{..} dep revDeps =
+ renderNotifyDependencyUpdate triggerBounds dep revDeps =
let depName = emailContentDisplay (packageName dep)
depVersion = emailContentDisplay (packageVersion dep)
in
foldMap EmailContentParagraph
[ "The dependency " <> renderPkgLink dep <> " has been uploaded or revised."
- , case notifyDependencyTriggerBounds of
+ , case triggerBounds of
Always ->
"You have requested to be notified for each upload or revision \
\of a dependency."
_ ->
"You have requested to be notified when a dependency isn't \
\accepted by any of your maintained packages."
- , case notifyDependencyTriggerBounds of
+ , case triggerBounds of
Always ->
"These are your packages that depend on " <> depName <> ":"
BoundsOutOfRange ->
diff --git a/tests/ReverseDependenciesTest.hs b/tests/ReverseDependenciesTest.hs
index 6910066c3..5b0333a04 100644
--- a/tests/ReverseDependenciesTest.hs
+++ b/tests/ReverseDependenciesTest.hs
@@ -391,36 +391,36 @@ getNotificationEmailsTests =
. getNotificationEmail
testServerEnv
testUserDetailsFeature
- (\_ -> pure $ Just notifyEverything{notifyDependencyTriggerBounds = Always})
allUsers
userWatcher
$ NotifyDependencyUpdate
{ notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
, notifyWatchedPackages = [PackageIdentifier "mtl" (mkVersion [2, 3])]
+ , notifyTriggerBounds = Always
}
, testGolden "Render NotifyDependencyUpdate-NewIncompatibility" "getNotificationEmails-NotifyDependencyUpdate-NewIncompatibility.golden" $
fmap renderMail
. getNotificationEmail
testServerEnv
testUserDetailsFeature
- (\_ -> pure $ Just notifyEverything{notifyDependencyTriggerBounds = NewIncompatibility})
allUsers
userWatcher
$ NotifyDependencyUpdate
{ notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
, notifyWatchedPackages = [PackageIdentifier "mtl" (mkVersion [2, 3])]
+ , notifyTriggerBounds = NewIncompatibility
}
, testGolden "Render NotifyDependencyUpdate-BoundsOutOfRange" "getNotificationEmails-NotifyDependencyUpdate-BoundsOutOfRange.golden" $
fmap renderMail
. getNotificationEmail
testServerEnv
testUserDetailsFeature
- (\_ -> pure $ Just notifyEverything{notifyDependencyTriggerBounds = BoundsOutOfRange})
allUsers
userWatcher
$ NotifyDependencyUpdate
{ notifyPackageId = PackageIdentifier "base" (mkVersion [4, 18, 0, 0])
, notifyWatchedPackages = [PackageIdentifier "mtl" (mkVersion [2, 3])]
+ , notifyTriggerBounds = BoundsOutOfRange
}
, testGolden "Render general notifications in single batched email" "getNotificationEmails-batched.golden" $ do
emails <-
@@ -478,8 +478,8 @@ getNotificationEmailsTests =
<*> addUser "user-actor"
<*> addUser "user-subject"
- getNotificationEmail env details pref users uid notif =
- getNotificationEmails env details pref users [(uid, notif)] >>= \case
+ getNotificationEmail env details users uid notif =
+ getNotificationEmails env details users [(uid, notif)] >>= \case
[email] -> pure email
_ -> error "Did not get exactly one email"
@@ -500,31 +500,15 @@ getNotificationEmailsTests =
, accountAdminNotes = ""
}
}
- notifyEverything =
- NotifyPref
- { notifyOptOut = False
- , notifyRevisionRange = NotifyAllVersions
- , notifyUpload = True
- , notifyMaintainerGroup = True
- , notifyDocBuilderReport = True
- , notifyPendingTags = True
- , notifyDependencyForMaintained = True
- , notifyDependencyTriggerBounds = Always
- }
- testGetUserNotifyPref uid = pure $ do
- guard $ uid == userWatcher
- Just notifyEverything
getNotificationEmailsMocked =
getNotificationEmails
testServerEnv
testUserDetailsFeature
- testGetUserNotifyPref
allUsers
getNotificationEmailMocked =
getNotificationEmail
testServerEnv
testUserDetailsFeature
- testGetUserNotifyPref
allUsers
renderMail = fst . Mail.renderMail (mkStdGen 0)
@@ -554,6 +538,7 @@ getNotificationEmailsTests =
, NotifyDependencyUpdate
<$> genPackageId
<*> Gen.list (Range.linear 1 10) genPackageId
+ <*> Gen.element [Always, NewIncompatibility, BoundsOutOfRange]
]
genPackageName = mkPackageName <$> Gen.string (Range.linear 1 30) Gen.unicode
From 06f831ef7a701967f22128087b3817cdff185cbc Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Sun, 9 Jul 2023 12:18:26 -0700
Subject: [PATCH 47/71] update `flake.nix` for recent `haskell-flake`
and
`nix flake update`
---
README.md | 70 ++++++++++++++++++++++++++++----
cabal.project | 9 ++++-
flake.lock | 109 ++++++--------------------------------------------
flake.nix | 88 +++++++++++++++++-----------------------
4 files changed, 120 insertions(+), 156 deletions(-)
diff --git a/README.md b/README.md
index 6e7995d78..5753f2c07 100644
--- a/README.md
+++ b/README.md
@@ -7,25 +7,81 @@ This is the `hackage-server` code. This is what powers
Date: Mon, 21 Aug 2023 18:04:04 -0700
Subject: [PATCH 48/71] Flake `cabal.project` parser succeeds with `.` here
---
cabal.project | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cabal.project b/cabal.project
index 8b8fc0c18..292cfa64c 100644
--- a/cabal.project
+++ b/cabal.project
@@ -1,5 +1,5 @@
packages:
- .
+ .
-- This comment moved below "packages" to fix parsing of this file
-- by flake.nix
From 1b2d9c6f5b80dfa49492aa372fd29b6f3fd8b894 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Mon, 21 Aug 2023 18:28:00 -0700
Subject: [PATCH 49/71] attempt to fix Flake on Mac
---
flake.nix | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/flake.nix b/flake.nix
index 8cc759e1e..747ac2c97 100644
--- a/flake.nix
+++ b/flake.nix
@@ -34,14 +34,14 @@
# Setting to null should remove this tool from defaults.
ghcid = null;
haskell-language-server = null;
-
inherit (pkgs)
cabal-install
ghc
- glibc
- icu67
- zlib
+ # https://github.com/haskell/hackage-server/pull/1219#issuecomment-1597140858
+ # glibc
+ # icu67
+ # zlib
openssl
cryptodev
pkg-config
From 4dbfbf73cfae9c9ad653ea2a6e941ddcef970eef Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Mon, 21 Aug 2023 20:15:10 -0700
Subject: [PATCH 50/71] fix `ghcid` in `nix develop`
---
flake.lock | 6 +++---
flake.nix | 1 -
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/flake.lock b/flake.lock
index 9a2ad973d..6b4c0e051 100644
--- a/flake.lock
+++ b/flake.lock
@@ -52,11 +52,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1692490321,
- "narHash": "sha256-5GAMLkvPQroGsLv6gmCct+GLFCOOL0hKQFv7UWpUMsc=",
+ "lastModified": 1692663074,
+ "narHash": "sha256-RfwM4yauPOS5zOKtjIKULrbe64LEpXTtZkz2S9J70WE=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "b8a3b5d228949ae9cc782ef0e9cf2b4518403dad",
+ "rev": "fe2033d9c47e1aaa4e0458d5e86c91d474032f47",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 747ac2c97..d73c0b212 100644
--- a/flake.nix
+++ b/flake.nix
@@ -32,7 +32,6 @@
devShell = {
tools = hp: {
# Setting to null should remove this tool from defaults.
- ghcid = null;
haskell-language-server = null;
inherit (pkgs)
cabal-install
From a32611dc171765561301d780f3b6516aebf64ef8 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Mon, 21 Aug 2023 20:26:36 -0700
Subject: [PATCH 51/71] fix `nix build`
---
flake.nix | 1 +
1 file changed, 1 insertion(+)
diff --git a/flake.nix b/flake.nix
index d73c0b212..728138d77 100644
--- a/flake.nix
+++ b/flake.nix
@@ -22,6 +22,7 @@
settings = {
hackage-server.check = false;
heist.check = false;
+ threads.check = false;
};
packages = {
Cabal.source = "3.10.1.0";
From cb3e0b64d5a3c42aa68a1477012d80acdf37edde Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Mon, 19 Jun 2023 10:37:06 -0700
Subject: [PATCH 52/71] enable `nix flake check` in GitHub Action
---
.github/workflows/nix-flake.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml
index 040cdf5f2..257dbf61d 100644
--- a/.github/workflows/nix-flake.yml
+++ b/.github/workflows/nix-flake.yml
@@ -29,4 +29,7 @@ jobs:
name: hackage-server
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix build
- # - run: nix flake check
+
+ # allow error until Flake check is improved
+ - continue-on-error: true
+ run: nix flake check
From f74bedfcd475e5f1b7c4e2b59253f5b3c1515d69 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Fri, 25 Aug 2023 23:27:25 -0700
Subject: [PATCH 53/71] remove Mac-incompatible Nix Flake dependency
Squashed commit of the following:
commit ab42f6c05bbe85caf1aac7d49772aa105d4bb5bb
Author: Peter Becich
Date: Fri Aug 25 23:25:03 2023 -0700
`nix flake check` should succeed
commit 112b5d931decc1c1c75bc936f334814efdc26883
Author: Peter Becich
Date: Fri Aug 25 23:12:08 2023 -0700
remove other dev shell dependencies
commit c24534ce6bd89120f4bc27851a822ffabc563e72
Author: Peter Becich
Date: Fri Aug 25 23:00:51 2023 -0700
remove pkg-config
commit bf4c4000bcc8da1cb28fd5a70c57ca69de2efcee
Author: Peter Becich
Date: Fri Aug 25 22:50:44 2023 -0700
try to fix `nix develop` on Mac
https://github.com/haskell/hackage-server/issues/1247
---
.github/workflows/nix-flake.yml | 3 +--
flake.nix | 6 +++---
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml
index 257dbf61d..39fe13b5e 100644
--- a/.github/workflows/nix-flake.yml
+++ b/.github/workflows/nix-flake.yml
@@ -30,6 +30,5 @@ jobs:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix build
- # allow error until Flake check is improved
- - continue-on-error: true
+ - continue-on-error: false
run: nix flake check
diff --git a/flake.nix b/flake.nix
index 728138d77..27d24584c 100644
--- a/flake.nix
+++ b/flake.nix
@@ -40,10 +40,10 @@
# https://github.com/haskell/hackage-server/pull/1219#issuecomment-1597140858
# glibc
- # icu67
- # zlib
+ icu67
+ zlib
openssl
- cryptodev
+ # cryptodev
pkg-config
brotli
From 20587a803d4ef203e5853eb602cc3de6210c5adf Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Sep 2023 16:12:59 +0000
Subject: [PATCH 54/71] Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.github/workflows/cabal.yml | 4 ++--
.github/workflows/haskell-ci.yml | 2 +-
.github/workflows/nix-flake.yml | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/cabal.yml b/.github/workflows/cabal.yml
index e4c404a13..f92a9585b 100644
--- a/.github/workflows/cabal.yml
+++ b/.github/workflows/cabal.yml
@@ -19,7 +19,7 @@ jobs:
os: [ubuntu-latest]
name: Cabal with GHC ${{ matrix.ghc }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup Haskell
uses: haskell-actions/setup@v2
with:
@@ -46,7 +46,7 @@ jobs:
# os: [ubuntu-latest]
# name: Cabal with GHC ${{ matrix.ghc }} and mtl >= 2.3.1
# steps:
- # - uses: actions/checkout@v3
+ # - uses: actions/checkout@v4
# - name: Setup Haskell
# uses: haskell/actions/setup@v2
# with:
diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml
index 155cec0ac..9e17c7011 100644
--- a/.github/workflows/haskell-ci.yml
+++ b/.github/workflows/haskell-ci.yml
@@ -149,7 +149,7 @@ jobs:
chmod a+x $HOME/.cabal/bin/cabal-plan
cabal-plan --version
- name: checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
path: source
- name: initial cabal.project for sdist
diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml
index 39fe13b5e..1d8813854 100644
--- a/.github/workflows/nix-flake.yml
+++ b/.github/workflows/nix-flake.yml
@@ -17,7 +17,7 @@ jobs:
name: Nix on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v3.5.3
+ - uses: actions/checkout@v4
- uses: cachix/install-nix-action@v22
with:
extra_nix_config: |
From 9c91563cf6b352fff7a61a6c74f337432c4d8f42 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Fri, 15 Sep 2023 19:01:46 -0700
Subject: [PATCH 55/71] `nix flake update`
HLS provided in `nix develop` shell
---
flake.lock | 24 ++++++++++++------------
flake.nix | 17 ++++++++++++++---
2 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/flake.lock b/flake.lock
index 6b4c0e051..7903bf046 100644
--- a/flake.lock
+++ b/flake.lock
@@ -7,11 +7,11 @@
]
},
"locked": {
- "lastModified": 1690933134,
- "narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=",
+ "lastModified": 1693611461,
+ "narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=",
"owner": "hercules-ci",
"repo": "flake-parts",
- "rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
+ "rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca",
"type": "github"
},
"original": {
@@ -22,11 +22,11 @@
},
"flake-root": {
"locked": {
- "lastModified": 1680964220,
- "narHash": "sha256-dIdTYcf+KW9a4pKHsEbddvLVSfR1yiAJynzg2x0nfWg=",
+ "lastModified": 1692742795,
+ "narHash": "sha256-f+Y0YhVCIJ06LemO+3Xx00lIcqQxSKJHXT/yk1RTKxw=",
"owner": "srid",
"repo": "flake-root",
- "rev": "f1c0b93d05bdbea6c011136ba1a135c80c5b326c",
+ "rev": "d9a70d9c7a5fd7f3258ccf48da9335e9b47c3937",
"type": "github"
},
"original": {
@@ -37,11 +37,11 @@
},
"haskell-flake": {
"locked": {
- "lastModified": 1691763544,
- "narHash": "sha256-QQsSI5VXm0bBijeGSXXNf4fyw76/XmN67NGbmTCx71s=",
+ "lastModified": 1694478711,
+ "narHash": "sha256-zW/saV4diypxwP56b8l93Nw8fR7tXLbOFku2I+xYCxU=",
"owner": "srid",
"repo": "haskell-flake",
- "rev": "f16e7ac05b1f22b66ef05b7fcc8a96281bb2b749",
+ "rev": "ddc704f3f62d3d3569ced794b534e8fd065c379c",
"type": "github"
},
"original": {
@@ -52,11 +52,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1692663074,
- "narHash": "sha256-RfwM4yauPOS5zOKtjIKULrbe64LEpXTtZkz2S9J70WE=",
+ "lastModified": 1694736714,
+ "narHash": "sha256-5xqXf2CfPiIHg2W7f+6odQ9c09L+jTVqGmxLB6qxPLc=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "fe2033d9c47e1aaa4e0458d5e86c91d474032f47",
+ "rev": "8b1c1ca2feb87ae8b7d9455d8dfe5361f249e4cf",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 27d24584c..f266814d7 100644
--- a/flake.nix
+++ b/flake.nix
@@ -22,18 +22,29 @@
settings = {
hackage-server.check = false;
heist.check = false;
+ fourmolu.check = false;
threads.check = false;
+ hls-cabal-plugin.check = false;
+ hls-fourmolu-plugin.check = false;
+ hw-prim.jailbreak = true;
+ hw-hspec-hedgehog.jailbreak = true;
+ hw-fingertree.jailbreak = true;
};
packages = {
Cabal.source = "3.10.1.0";
Cabal-syntax.source = "3.10.1.0";
attoparsec-aeson.source = "2.1.0.0";
- hedgehog.source = "1.3";
+ hedgehog.source = "1.4";
+ ormolu.source = "0.7.2.0";
+ fourmolu.source = "0.13.1.0";
+ tasty-hedgehog.source = "1.4.0.2";
+ ghc-lib-parser.source = "9.6.2.20230523";
+ ghc-lib-parser-ex.source = "9.6.0.2";
+ hlint.source = "3.6.1";
+ stylish-haskell.source = "0.14.5.0";
};
devShell = {
tools = hp: {
- # Setting to null should remove this tool from defaults.
- haskell-language-server = null;
inherit (pkgs)
cabal-install
ghc
From d170f8f938f02689994493c38e36f9fe6ff8aa13 Mon Sep 17 00:00:00 2001
From: Peter Becich
Date: Fri, 15 Sep 2023 23:04:04 -0700
Subject: [PATCH 56/71] remove unnecessary flake settings
---
flake.nix | 3 ---
1 file changed, 3 deletions(-)
diff --git a/flake.nix b/flake.nix
index f266814d7..3d79ffd0d 100644
--- a/flake.nix
+++ b/flake.nix
@@ -23,9 +23,6 @@
hackage-server.check = false;
heist.check = false;
fourmolu.check = false;
- threads.check = false;
- hls-cabal-plugin.check = false;
- hls-fourmolu-plugin.check = false;
hw-prim.jailbreak = true;
hw-hspec-hedgehog.jailbreak = true;
hw-fingertree.jailbreak = true;
From c3f997e935d7d8cd7b5e54381620f8012b0b9201 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 16 Sep 2023 06:08:53 +0000
Subject: [PATCH 57/71] Bump cachix/install-nix-action from 22 to 23
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 22 to 23.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v22...v23)
---
updated-dependencies:
- dependency-name: cachix/install-nix-action
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.github/workflows/nix-flake.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml
index 1d8813854..e036875cc 100644
--- a/.github/workflows/nix-flake.yml
+++ b/.github/workflows/nix-flake.yml
@@ -18,7 +18,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- - uses: cachix/install-nix-action@v22
+ - uses: cachix/install-nix-action@v23
with:
extra_nix_config: |
trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hackage-server.cachix.org-1:iw0iRh6+gsFIrxROFaAt5gKNgIHejKjIfyRdbpPYevY=
From b69b5d6a3a649b03ae186cbb1f8192c3b9a40d8b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9cate=20Moonlight?=
Date: Sat, 23 Sep 2023 14:16:47 +0200
Subject: [PATCH 58/71] Close tags in the revisions RSS feed
---
src/Distribution/Server/Pages/Recent.hs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Distribution/Server/Pages/Recent.hs b/src/Distribution/Server/Pages/Recent.hs
index 19e7e75b0..ac60f89af 100644
--- a/src/Distribution/Server/Pages/Recent.hs
+++ b/src/Distribution/Server/Pages/Recent.hs
@@ -241,7 +241,7 @@ releaseItem users hostURI pkgInfo =
title = display (packageName pkgId) ++ " " ++ display (packageVersion pkgId)
body = fromShortText $ synopsis (packageDescription (pkgDesc pkgInfo))
desc = "Added by " ++ display user ++ ", " ++ showTime time ++ ". "
- ++ if null body then "" else "
" ++ body
+ ++ if null body then "" else "
" ++ body ++ "
"
user = Users.userIdToName users userId
(time, userId) = pkgOriginalUploadInfo pkgInfo
@@ -261,7 +261,7 @@ revisionItem users hostURI pkgInfo =
title = display (packageName pkgId) ++ " " ++ display (packageVersion pkgId)
body = "Revision #" ++ show revision
desc = "Revised by " ++ display user ++ ", " ++ showTime time ++ ". "
- ++ if null body then "" else "" ++ body
+ ++ if null body then "" else "
" ++ body ++ "
"
user = Users.userIdToName users userId
revision = pkgNumRevisions pkgInfo - 1
From bf8ee3abbd454a3b629a4cd80971b5a0832dd7f0 Mon Sep 17 00:00:00 2001
From: Janus Troelsen
Date: Sat, 3 Sep 2022 09:43:51 -0500
Subject: [PATCH 59/71] Add vouching
Vouching allows new users to get added to the uploaders group by way of
other people "vouching" for then. This should alleviate privileged
Hackage users, since they were previously the only people that could add
people to the uploaders group.
---
datafiles/templates/Html/vouch.html.st | 29 +++
hackage-server.cabal | 3 +-
src/Distribution/Server/Features.hs | 8 +
src/Distribution/Server/Features/Vouch.hs | 218 ++++++++++++++++++++++
4 files changed, 257 insertions(+), 1 deletion(-)
create mode 100644 datafiles/templates/Html/vouch.html.st
create mode 100644 src/Distribution/Server/Features/Vouch.hs
diff --git a/datafiles/templates/Html/vouch.html.st b/datafiles/templates/Html/vouch.html.st
new file mode 100644
index 000000000..89ae9c238
--- /dev/null
+++ b/datafiles/templates/Html/vouch.html.st
@@ -0,0 +1,29 @@
+
+
+
+$hackageCssTheme()$
+Vouch for user | Hackage
+
+
+
+$hackagePageHeader()$
+
+
+
Vouch for user
+
+
$msg$
+
+
+
+
Vouching cannot be undone! When the user has three vouches, the user
+can upload packages. Note that users are, to a certain degree, held accountable
+for the actions of the users they vouch for. Only vouch for people you know.
+
+
+
+
+
diff --git a/hackage-server.cabal b/hackage-server.cabal
index fc0f51c88..e5749d161 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -373,8 +373,9 @@ library lib-server
Distribution.Server.Features.Search.TermBag
Distribution.Server.Features.Sitemap.Functions
Distribution.Server.Features.Votes
- Distribution.Server.Features.Votes.State
Distribution.Server.Features.Votes.Render
+ Distribution.Server.Features.Votes.State
+ Distribution.Server.Features.Vouch
Distribution.Server.Features.RecentPackages
Distribution.Server.Features.PreferredVersions
Distribution.Server.Features.PreferredVersions.State
diff --git a/src/Distribution/Server/Features.hs b/src/Distribution/Server/Features.hs
index be89e4175..0a9b52011 100644
--- a/src/Distribution/Server/Features.hs
+++ b/src/Distribution/Server/Features.hs
@@ -51,6 +51,7 @@ import Distribution.Server.Features.Votes (initVotesFeature)
import Distribution.Server.Features.Sitemap (initSitemapFeature)
import Distribution.Server.Features.UserNotify (initUserNotifyFeature)
import Distribution.Server.Features.PackageFeed (initPackageFeedFeature)
+import Distribution.Server.Features.Vouch (initVouchFeature)
#endif
import Distribution.Server.Features.ServerIntrospect (serverIntrospectFeature)
@@ -159,6 +160,8 @@ initHackageFeatures env@ServerEnv{serverVerbosity = verbosity} = do
initUserNotifyFeature env
mkPackageFeedFeature <- logStartup "package feed" $
initPackageFeedFeature env
+ mkVouchFeature <- logStartup "vouch" $
+ initVouchFeature env
mkBrowseFeature <- logStartup "browse" $
initBrowseFeature env
mkPackageJSONFeature <- logStartup "package info JSON" $
@@ -359,6 +362,10 @@ initHackageFeatures env@ServerEnv{serverVerbosity = verbosity} = do
usersFeature
tarIndexCacheFeature
+ vouchFeature <- mkVouchFeature
+ usersFeature
+ uploadFeature
+
browseFeature <- mkBrowseFeature
coreFeature
usersFeature
@@ -415,6 +422,7 @@ initHackageFeatures env@ServerEnv{serverVerbosity = verbosity} = do
, getFeatureInterface userNotifyFeature
, getFeatureInterface packageFeedFeature
, getFeatureInterface packageInfoJSONFeature
+ , getFeatureInterface vouchFeature
#endif
, staticFilesFeature
, serverIntrospectFeature allFeatures
diff --git a/src/Distribution/Server/Features/Vouch.hs b/src/Distribution/Server/Features/Vouch.hs
new file mode 100644
index 000000000..df846d0b8
--- /dev/null
+++ b/src/Distribution/Server/Features/Vouch.hs
@@ -0,0 +1,218 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE DerivingStrategies #-}
+module Distribution.Server.Features.Vouch where
+
+import Control.Monad (when, join)
+import Control.Monad.Except (runExceptT, throwError)
+import Control.Monad.Reader (ask)
+import Control.Monad.State (get, put)
+import qualified Data.ByteString.Lazy.Char8 as LBS
+import qualified Data.Map.Strict as Map
+import Data.Maybe (fromMaybe)
+import Data.Time (UTCTime(..), addUTCTime, getCurrentTime, nominalDay, secondsToDiffTime)
+import Data.Time.Format.ISO8601 (formatShow, iso8601Format)
+import Text.XHtml.Strict (prettyHtmlFragment, stringToHtml, li)
+
+import Data.SafeCopy (base, deriveSafeCopy)
+import Distribution.Server.Framework ((>), AcidState, DynamicPath, HackageFeature, IsHackageFeature, IsHackageFeature(..), MemSize)
+import Distribution.Server.Framework (MessageSpan(MText), Method(..), Query, Response, ServerEnv(..), ServerPartE, StateComponent(..), Update)
+import Distribution.Server.Framework (abstractAcidStateComponent, emptyHackageFeature, errBadRequest)
+import Distribution.Server.Framework (featureDesc, featureReloadFiles, featureResources, featureState)
+import Distribution.Server.Framework (liftIO, makeAcidic, openLocalStateFrom, query, queryState, resourceAt, resourceDesc, resourceGet)
+import Distribution.Server.Framework (resourcePost, toResponse, update, updateState)
+import Distribution.Server.Framework.BackupRestore (RestoreBackup(..))
+import Distribution.Server.Framework.Templating (($=), TemplateAttr, getTemplate, loadTemplates, reloadTemplates, templateUnescaped)
+import qualified Distribution.Server.Users.Group as Group
+import Distribution.Server.Users.Types (UserId(..), UserInfo, UserName(..), userName)
+import Distribution.Server.Features.Upload(UploadFeature(..))
+import Distribution.Server.Features.Users (UserFeature(..))
+import Distribution.Simple.Utils (toUTF8LBS)
+
+newtype VouchData = VouchData (Map.Map UserId [(UserId, UTCTime)])
+ deriving (Show, Eq)
+ deriving newtype MemSize
+
+putVouch :: UserId -> (UserId, UTCTime) -> Update VouchData ()
+putVouch vouchee (voucher, now) = do
+ VouchData tbl <- get
+ let oldMap = fromMaybe [] (Map.lookup vouchee tbl)
+ newMap = (voucher, now) : oldMap
+ put $ VouchData (Map.insert vouchee newMap tbl)
+
+getVouchesFor :: UserId -> Query VouchData [(UserId, UTCTime)]
+getVouchesFor needle = do
+ VouchData tbl <- ask
+ pure . fromMaybe [] $ Map.lookup needle tbl
+
+getVouchesData :: Query VouchData VouchData
+getVouchesData = ask
+
+replaceVouchesData :: VouchData -> Update VouchData ()
+replaceVouchesData = put
+
+$(deriveSafeCopy 0 'base ''VouchData)
+
+makeAcidic ''VouchData
+ [ 'putVouch
+ , 'getVouchesFor
+ -- Stock
+ , 'getVouchesData
+ , 'replaceVouchesData
+ ]
+
+vouchStateComponent :: FilePath -> IO (StateComponent AcidState VouchData)
+vouchStateComponent stateDir = do
+ st <- openLocalStateFrom (stateDir > "db" > "Vouch") (VouchData mempty)
+ let initialVouchData = VouchData mempty
+ restore =
+ RestoreBackup
+ { restoreEntry = error "Unexpected backup entry"
+ , restoreFinalize = return initialVouchData
+ }
+ pure StateComponent
+ { stateDesc = "Keeps track of vouches"
+ , stateHandle = st
+ , getState = query st GetVouchesData
+ , putState = update st . ReplaceVouchesData
+ , backupState = \_ _ -> []
+ , restoreState = restore
+ , resetState = vouchStateComponent
+ }
+
+data VouchFeature =
+ VouchFeature
+ { vouchFeatureInterface :: HackageFeature
+ }
+
+instance IsHackageFeature VouchFeature where
+ getFeatureInterface = vouchFeatureInterface
+
+requiredCountOfVouches :: Int
+requiredCountOfVouches = 3
+
+isWithinLastMonth :: UTCTime -> (UserId, UTCTime) -> Bool
+isWithinLastMonth now (_, vouchTime) =
+ addUTCTime (30 * nominalDay) vouchTime < now
+
+data Err
+ = NotAnUploader
+ | You'reTooNew
+ | VoucheeAlreadyUploader
+ | AlreadySufficientlyVouched
+ | YouAlreadyVouched
+
+data Success = AddVouchComplete | AddVouchIncomplete
+
+judge :: Group.UserIdSet -> UTCTime -> UserId -> [(UserId, UTCTime)] -> [(UserId, UTCTime)] -> UserId -> Either Err (Either Err Success)
+judge ugroup now vouchee vouchersForVoucher existingVouchers voucher = runExceptT $ do
+ when (not (voucher `Group.member` ugroup)) $
+ throwError NotAnUploader
+ -- You can only vouch for non-uploaders, so if this list has items, the user is uploader because of these vouches.
+ -- Make sure none of them are too recent.
+ when (length vouchersForVoucher >= requiredCountOfVouches && any (isWithinLastMonth now) vouchersForVoucher) $
+ throwError You'reTooNew
+ when (vouchee `Group.member` ugroup) $
+ throwError VoucheeAlreadyUploader
+ when (length existingVouchers >= 3) $
+ throwError AlreadySufficientlyVouched
+ when (voucher `elem` map fst existingVouchers) $
+ throwError YouAlreadyVouched
+ pure $
+ if length existingVouchers == requiredCountOfVouches - 1
+ then AddVouchComplete
+ else AddVouchIncomplete
+
+renderToLBS :: (UserId -> ServerPartE UserInfo) -> [(UserId, UTCTime)] -> ServerPartE TemplateAttr
+renderToLBS lookupUserInfo vouches = do
+ rendered <- traverse renderVouchers vouches
+ pure $
+ templateUnescaped "vouches" $
+ if null rendered
+ then LBS.pack "Nobody has vouched yet."
+ else LBS.intercalate mempty rendered
+ where
+ renderVouchers :: (UserId, UTCTime) -> ServerPartE LBS.ByteString
+ renderVouchers (uid, timestamp) = do
+ info <- lookupUserInfo uid
+ let UserName name = userName info
+ -- We don't need to show millisecond precision
+ -- So we truncate it off here
+ truncated = truncate $ utctDayTime timestamp
+ newUTCTime = timestamp {utctDayTime = secondsToDiffTime truncated}
+ pure . toUTF8LBS . prettyHtmlFragment . li . stringToHtml $ name <> " vouched on " <> formatShow iso8601Format newUTCTime
+
+initVouchFeature :: ServerEnv -> IO (UserFeature -> UploadFeature -> IO VouchFeature)
+initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMode} = do
+ vouchState <- vouchStateComponent serverStateDir
+ templates <- loadTemplates serverTemplatesMode [ serverTemplatesDir, serverTemplatesDir > "Html"]
+ ["vouch.html"]
+ vouchTemplate <- getTemplate templates "vouch.html"
+ return $ \UserFeature{userNameInPath, lookupUserName, lookupUserInfo, guardAuthenticated}
+ UploadFeature{uploadersGroup} -> do
+ let
+ handleGetVouches :: DynamicPath -> ServerPartE Response
+ handleGetVouches dpath = do
+ uid <- lookupUserName =<< userNameInPath dpath
+ userIds <- queryState vouchState $ GetVouchesFor uid
+ param <- renderToLBS lookupUserInfo userIds
+ pure . toResponse $ vouchTemplate
+ [ "msg" $= ""
+ , param
+ ]
+ handlePostVouch :: DynamicPath -> ServerPartE Response
+ handlePostVouch dpath = do
+ voucher <- guardAuthenticated
+ ugroup <- liftIO $ Group.queryUserGroup uploadersGroup
+ now <- liftIO getCurrentTime
+ vouchee <- lookupUserName =<< userNameInPath dpath
+ vouchersForVoucher <- queryState vouchState $ GetVouchesFor voucher
+ existingVouchers <- queryState vouchState $ GetVouchesFor vouchee
+ case join $ judge ugroup now vouchee vouchersForVoucher existingVouchers voucher of
+ Left NotAnUploader ->
+ errBadRequest "Not an uploader" [MText "You must be an uploader yourself to vouch for other users."]
+ Left You'reTooNew ->
+ errBadRequest "You're too new" [MText "The latest of the vouches for your user must be at least 30 days old."]
+ Left VoucheeAlreadyUploader ->
+ errBadRequest "Vouchee already uploader" [MText "You can't vouch for this user, since they are already an uploader."]
+ Left AlreadySufficientlyVouched ->
+ errBadRequest "Already sufficiently vouched" [MText "There are already a sufficient number of vouches for this user."]
+ Left YouAlreadyVouched ->
+ errBadRequest "Already vouched" [MText "You have already vouched for this user."]
+ Right result -> do
+ updateState vouchState $ PutVouch vouchee (voucher, now)
+ param <- renderToLBS lookupUserInfo $ existingVouchers ++ [(voucher, now)]
+ case result of
+ AddVouchComplete -> do
+ liftIO $ Group.addUserToGroup uploadersGroup vouchee
+ pure . toResponse $ vouchTemplate
+ [ "msg" $= "Added vouch. User is now an uploader!"
+ , param
+ ]
+ AddVouchIncomplete -> do
+ let stillRequired = requiredCountOfVouches - length existingVouchers - 1
+ pure . toResponse $ vouchTemplate
+ [ "msg" $=
+ "Added vouch. User still needs "
+ <> show stillRequired
+ <> if stillRequired == 1 then " vouch" else " vouches"
+ <> " to become uploader."
+ , param
+ ]
+ return $ VouchFeature $
+ (emptyHackageFeature "vouch")
+ { featureDesc = "Vouching for users getting upload permission."
+ , featureResources =
+ [(resourceAt "/user/:username/vouch")
+ { resourceDesc = [(GET, "list people vouching")
+ ,(POST, "vouch for user")
+ ]
+ , resourceGet = [("html", handleGetVouches)]
+ , resourcePost = [("html", handlePostVouch)]
+ }
+ ]
+ , featureState = [ abstractAcidStateComponent vouchState ]
+ , featureReloadFiles = reloadTemplates templates
+ }
From 3c82d01dec38a974d8d998233804a42ad5dccd1a Mon Sep 17 00:00:00 2001
From: Janus Troelsen
Date: Mon, 16 Oct 2023 16:53:21 -0600
Subject: [PATCH 60/71] Vouching: Address review comments, add tests
---
hackage-server.cabal | 8 ++
src/Distribution/Server/Features/Vouch.hs | 60 ++++++++------
tests/VouchTest.hs | 96 +++++++++++++++++++++++
3 files changed, 139 insertions(+), 25 deletions(-)
create mode 100644 tests/VouchTest.hs
diff --git a/hackage-server.cabal b/hackage-server.cabal
index e5749d161..a4bde6531 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -574,6 +574,14 @@ test-suite HighLevelTest
, io-streams ^>= 1.5.0.1
, http-io-streams ^>= 0.1.6.1
+test-suite VouchTest
+ import: test-defaults
+ type: exitcode-stdio-1.0
+ main-is: VouchTest.hs
+ build-depends:
+ , tasty ^>= 1.4
+ , tasty-hunit ^>= 0.10
+
test-suite ReverseDependenciesTest
import: test-defaults
type: exitcode-stdio-1.0
diff --git a/src/Distribution/Server/Features/Vouch.hs b/src/Distribution/Server/Features/Vouch.hs
index df846d0b8..88a0f3494 100644
--- a/src/Distribution/Server/Features/Vouch.hs
+++ b/src/Distribution/Server/Features/Vouch.hs
@@ -3,7 +3,7 @@
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DerivingStrategies #-}
-module Distribution.Server.Features.Vouch where
+module Distribution.Server.Features.Vouch (VouchError(..), VouchSuccess(..), initVouchFeature, judgeVouch) where
import Control.Monad (when, join)
import Control.Monad.Except (runExceptT, throwError)
@@ -91,23 +91,32 @@ instance IsHackageFeature VouchFeature where
getFeatureInterface = vouchFeatureInterface
requiredCountOfVouches :: Int
-requiredCountOfVouches = 3
+requiredCountOfVouches = 2
isWithinLastMonth :: UTCTime -> (UserId, UTCTime) -> Bool
isWithinLastMonth now (_, vouchTime) =
- addUTCTime (30 * nominalDay) vouchTime < now
+ addUTCTime (30 * nominalDay) vouchTime >= now
-data Err
+data VouchError
= NotAnUploader
| You'reTooNew
| VoucheeAlreadyUploader
| AlreadySufficientlyVouched
| YouAlreadyVouched
-
-data Success = AddVouchComplete | AddVouchIncomplete
-
-judge :: Group.UserIdSet -> UTCTime -> UserId -> [(UserId, UTCTime)] -> [(UserId, UTCTime)] -> UserId -> Either Err (Either Err Success)
-judge ugroup now vouchee vouchersForVoucher existingVouchers voucher = runExceptT $ do
+ deriving stock (Show, Eq)
+
+data VouchSuccess = AddVouchComplete | AddVouchIncomplete Int
+ deriving stock (Show, Eq)
+
+judgeVouch
+ :: Group.UserIdSet
+ -> UTCTime
+ -> UserId
+ -> [(UserId, UTCTime)]
+ -> [(UserId, UTCTime)]
+ -> UserId
+ -> Either VouchError VouchSuccess
+judgeVouch ugroup now vouchee vouchersForVoucher existingVouchers voucher = join . runExceptT $ do
when (not (voucher `Group.member` ugroup)) $
throwError NotAnUploader
-- You can only vouch for non-uploaders, so if this list has items, the user is uploader because of these vouches.
@@ -116,33 +125,35 @@ judge ugroup now vouchee vouchersForVoucher existingVouchers voucher = runExcept
throwError You'reTooNew
when (vouchee `Group.member` ugroup) $
throwError VoucheeAlreadyUploader
- when (length existingVouchers >= 3) $
+ when (length existingVouchers >= requiredCountOfVouches) $
throwError AlreadySufficientlyVouched
when (voucher `elem` map fst existingVouchers) $
throwError YouAlreadyVouched
pure $
if length existingVouchers == requiredCountOfVouches - 1
then AddVouchComplete
- else AddVouchIncomplete
+ else
+ let stillRequired = requiredCountOfVouches - length existingVouchers - 1
+ in AddVouchIncomplete stillRequired
renderToLBS :: (UserId -> ServerPartE UserInfo) -> [(UserId, UTCTime)] -> ServerPartE TemplateAttr
renderToLBS lookupUserInfo vouches = do
- rendered <- traverse renderVouchers vouches
+ rendered <- traverse (renderVouchers lookupUserInfo) vouches
pure $
templateUnescaped "vouches" $
if null rendered
then LBS.pack "Nobody has vouched yet."
else LBS.intercalate mempty rendered
- where
- renderVouchers :: (UserId, UTCTime) -> ServerPartE LBS.ByteString
- renderVouchers (uid, timestamp) = do
- info <- lookupUserInfo uid
- let UserName name = userName info
- -- We don't need to show millisecond precision
- -- So we truncate it off here
- truncated = truncate $ utctDayTime timestamp
- newUTCTime = timestamp {utctDayTime = secondsToDiffTime truncated}
- pure . toUTF8LBS . prettyHtmlFragment . li . stringToHtml $ name <> " vouched on " <> formatShow iso8601Format newUTCTime
+
+renderVouchers :: (UserId -> ServerPartE UserInfo) -> (UserId, UTCTime) -> ServerPartE LBS.ByteString
+renderVouchers lookupUserInfo (uid, timestamp) = do
+ info <- lookupUserInfo uid
+ let UserName name = userName info
+ -- We don't need to show millisecond precision
+ -- So we truncate it off here
+ truncated = truncate $ utctDayTime timestamp
+ newUTCTime = timestamp {utctDayTime = secondsToDiffTime truncated}
+ pure . toUTF8LBS . prettyHtmlFragment . li . stringToHtml $ name <> " vouched on " <> formatShow iso8601Format newUTCTime
initVouchFeature :: ServerEnv -> IO (UserFeature -> UploadFeature -> IO VouchFeature)
initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMode} = do
@@ -170,7 +181,7 @@ initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMo
vouchee <- lookupUserName =<< userNameInPath dpath
vouchersForVoucher <- queryState vouchState $ GetVouchesFor voucher
existingVouchers <- queryState vouchState $ GetVouchesFor vouchee
- case join $ judge ugroup now vouchee vouchersForVoucher existingVouchers voucher of
+ case judgeVouch ugroup now vouchee vouchersForVoucher existingVouchers voucher of
Left NotAnUploader ->
errBadRequest "Not an uploader" [MText "You must be an uploader yourself to vouch for other users."]
Left You'reTooNew ->
@@ -191,8 +202,7 @@ initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMo
[ "msg" $= "Added vouch. User is now an uploader!"
, param
]
- AddVouchIncomplete -> do
- let stillRequired = requiredCountOfVouches - length existingVouchers - 1
+ AddVouchIncomplete stillRequired ->
pure . toResponse $ vouchTemplate
[ "msg" $=
"Added vouch. User still needs "
diff --git a/tests/VouchTest.hs b/tests/VouchTest.hs
new file mode 100644
index 000000000..ece72d578
--- /dev/null
+++ b/tests/VouchTest.hs
@@ -0,0 +1,96 @@
+module Main where
+
+import Data.Time (UTCTime(UTCTime), fromGregorian)
+
+import Distribution.Server.Features.Vouch (VouchError(..), VouchSuccess(..), judgeVouch)
+import Distribution.Server.Users.UserIdSet (fromList)
+import Distribution.Server.Users.Types (UserId(UserId))
+
+import Test.Tasty (TestTree, defaultMain, testGroup)
+import Test.Tasty.HUnit (assertEqual, testCase)
+
+allTests :: TestTree
+allTests = testGroup "VouchTest"
+ [ testCase "happy path, vouch added, but more vouches needed" $ do
+ let ref = Right (AddVouchIncomplete 1)
+ voucher = UserId 1
+ vouchee = UserId 2
+ assertEqual "must match" ref $
+ judgeVouch
+ (fromList [voucher]) -- uploaders. Can't vouch if user is not a voucher
+ (UTCTime (fromGregorian 2020 1 1) 0)
+ vouchee
+ [] -- vouchers for voucher. If this short enough, voucher is assumed to be old enough to vouch themselves.
+ [] -- no existing vouchers
+ voucher
+ , testCase "happy path, vouch added, no more vouches needed" $ do
+ let ref = Right AddVouchComplete
+ voucher = UserId 1
+ vouchee = UserId 2
+ otherVoucherForVouchee = UserId 4
+ assertEqual "must match" ref $
+ judgeVouch
+ (fromList [voucher])
+ (UTCTime (fromGregorian 2020 1 1) 0)
+ vouchee
+ []
+ [(otherVoucherForVouchee, UTCTime (fromGregorian 2020 1 1) 0)]
+ voucher
+ , testCase "non-uploader tried to vouch" $ do
+ let ref = Left NotAnUploader
+ voucher = UserId 1
+ vouchee = UserId 2
+ assertEqual "must match" ref $
+ judgeVouch
+ (fromList []) -- empty. Should contain voucher for operation to proceed.
+ (UTCTime (fromGregorian 2020 1 1) 0)
+ vouchee
+ []
+ []
+ voucher
+ , testCase "voucher too new" $ do
+ let ref = Left You'reTooNew
+ voucher = UserId 1
+ vouchee = UserId 2
+ fstVoucherForVoucher = UserId 3
+ sndVoucherForVoucher = UserId 4
+ now = UTCTime (fromGregorian 2020 1 1) 0
+ assertEqual "must match" ref $
+ judgeVouch
+ (fromList [voucher])
+ now
+ vouchee
+ [ (fstVoucherForVoucher, now) -- These two timestamps are too new
+ , (sndVoucherForVoucher, now)
+ ]
+ []
+ voucher
+ , testCase "vouchee already uploader" $ do
+ let ref = Left VoucheeAlreadyUploader
+ voucher = UserId 1
+ vouchee = UserId 2
+ now = UTCTime (fromGregorian 2020 1 1) 0
+ assertEqual "must match" ref $
+ judgeVouch
+ (fromList [voucher, vouchee]) -- vouchee is here. So they're already an uploader.
+ now
+ vouchee
+ []
+ []
+ voucher
+ , testCase "already vouched" $ do
+ let ref = Left YouAlreadyVouched
+ voucher = UserId 1
+ vouchee = UserId 2
+ assertEqual "must match" ref $
+ judgeVouch
+ (fromList [voucher])
+ (UTCTime (fromGregorian 2020 1 1) 0)
+ vouchee
+ []
+ [(voucher, UTCTime (fromGregorian 2020 1 1) 0)] -- voucher is here. So they already vouched
+ voucher
+ ]
+
+main :: IO ()
+main = defaultMain allTests
From 113d4dfb432c63bc829e27d417058c9f86ddcf64 Mon Sep 17 00:00:00 2001
From: Vladimir Nikishkin <>
Date: Thu, 26 Oct 2023 13:11:23 +0800
Subject: [PATCH 61/71] Allow serving cabal files with package name in the file
name.
Allow serving url like this:
https://hackage.haskell.org/package/pkg-2.6/revision/pkg-2.6-1.cabal
---
datafiles/templates/Html/revisions.html.st | 2 +-
src/Distribution/Server/Features/Core.hs | 22 ++++++++++++++++++++++
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/datafiles/templates/Html/revisions.html.st b/datafiles/templates/Html/revisions.html.st
index 69cf2b75a..98c875c29 100644
--- a/datafiles/templates/Html/revisions.html.st
+++ b/datafiles/templates/Html/revisions.html.st
@@ -25,7 +25,7 @@ stored separately.
$revisions:{revision|
- -r$revision.number$
+ -r$revision.number$ ($pkgid$-r$revision.number$ )
$revision.htmltime$
$revision.user$
$revision.sha256$
diff --git a/src/Distribution/Server/Features/Core.hs b/src/Distribution/Server/Features/Core.hs
index 6aec793ab..e32e247e2 100644
--- a/src/Distribution/Server/Features/Core.hs
+++ b/src/Distribution/Server/Features/Core.hs
@@ -225,6 +225,7 @@ data CoreResource = CoreResource {
corePackageTarball :: Resource,
-- | A Cabal file metatada revision.
coreCabalFileRev :: Resource,
+ coreCabalFileRevName :: Resource,
-- Rendering resources.
-- | URI for `corePackagesPage`, given a format (blank for none).
@@ -403,6 +404,7 @@ coreFeature ServerEnv{serverBlobStore = store} UserFeature{..}
, coreCabalFile
, coreCabalFileRevs
, coreCabalFileRev
+ , coreCabalFileRevName
, coreUserDeauth
, coreAdminDeauth
, corePackUserDeauth
@@ -456,6 +458,11 @@ coreFeature ServerEnv{serverBlobStore = store} UserFeature{..}
resourceDesc = [(GET, "Get package .cabal file revision")]
, resourceGet = [("cabal", serveCabalFileRevision)]
}
+ coreCabalFileRevName = (resourceAt "/package/:package/revision/:tarball-:revision.:format") {
+ resourceDesc = [(GET, "Get package .cabal file revision with name")]
+ , resourceGet = [("cabal", serveCabalFileRevisionName)]
+ }
+
coreUserDeauth = (resourceAt "/packages/deauth") {
resourceDesc = [(GET, "Deauth Package user")]
@@ -750,6 +757,21 @@ coreFeature ServerEnv{serverBlobStore = store} UserFeature{..}
Nothing -> errNotFound "Package revision not found"
[MText "Cannot parse revision, or revision out of range."]
+ serveCabalFileRevisionName :: DynamicPath -> ServerPartE Response
+ serveCabalFileRevisionName dpath = do
+ pkgid1 <- packageTarballInPath dpath
+ pkgid2 <- packageInPath dpath
+ guard (pkgVersion pkgid2 == pkgVersion pkgid2)
+ pkginfo <- packageInPath dpath >>= lookupPackageId
+ let mrev = lookup "revision" dpath >>= fromReqURI
+ revisions = pkgMetadataRevisions pkginfo
+ case mrev >>= \rev -> revisions Vec.!? rev of
+ Just (fileRev, (utime, _uid)) -> return $ toResponse cabalfile
+ where
+ cabalfile = Resource.CabalFile (cabalFileByteString fileRev) utime
+ Nothing -> errNotFound "Package revision not found"
+ [MText "Cannot parse revision, or revision out of range."]
+
deauth :: DynamicPath -> ServerPartE Response
deauth _ = do
From 4ce6377701fb1d0cf6c52f7c3c71c4823cc61c61 Mon Sep 17 00:00:00 2001
From: Andreas Abel
Date: Fri, 13 Oct 2023 21:11:18 +0200
Subject: [PATCH 62/71] Bump dependencies and CI for GHC 9.8.1
Building with GHC 9.8.1 blocked on:
- [ ] happstack-server: https://github.com/Happstack/happstack-server/pull/80
---
.github/workflows/cabal.yml | 6 ++----
.github/workflows/haskell-ci.yml | 30 ++++++++++++++++++------------
cabal.haskell-ci | 2 +-
hackage-server.cabal | 21 +++++++++++----------
4 files changed, 32 insertions(+), 27 deletions(-)
diff --git a/.github/workflows/cabal.yml b/.github/workflows/cabal.yml
index f92a9585b..491a24ad7 100644
--- a/.github/workflows/cabal.yml
+++ b/.github/workflows/cabal.yml
@@ -2,11 +2,9 @@ on:
push:
branches:
- master
- - ci*
pull_request:
branches:
- master
- - ci*
name: Cabal
jobs:
build:
@@ -14,8 +12,8 @@ jobs:
strategy:
fail-fast: false
matrix:
- ghc: ['9.6.2', '9.4.5', '9.2.8', '9.0.2']
- cabal: ['3.10.1.0']
+ ghc: ['9.8', '9.6', '9.4', '9.2']
+ cabal: ['latest']
os: [ubuntu-latest]
name: Cabal with GHC ${{ matrix.ghc }}
steps:
diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml
index 9e17c7011..79a87d2c1 100644
--- a/.github/workflows/haskell-ci.yml
+++ b/.github/workflows/haskell-ci.yml
@@ -6,22 +6,20 @@
#
# haskell-ci regenerate
#
-# For more information, see https://github.com/haskell-CI/haskell-ci
+# For more information, see https://github.com/andreasabel/haskell-ci
#
-# version: 0.16.6.20230729
+# version: 0.17.20231012
#
-# REGENDATA ("0.16.6.20230729",["github","hackage-server.cabal"])
+# REGENDATA ("0.17.20231012",["github","hackage-server.cabal"])
#
name: Haskell-CI
on:
push:
branches:
- master
- - ci*
pull_request:
branches:
- master
- - ci*
jobs:
linux:
name: Haskell-CI - Linux - ${{ matrix.compiler }}
@@ -34,14 +32,19 @@ jobs:
strategy:
matrix:
include:
- - compiler: ghc-9.6.2
+ - compiler: ghc-9.8.1
compilerKind: ghc
- compilerVersion: 9.6.2
+ compilerVersion: 9.8.1
setup-method: ghcup
allow-failure: false
- - compiler: ghc-9.4.5
+ - compiler: ghc-9.6.3
compilerKind: ghc
- compilerVersion: 9.4.5
+ compilerVersion: 9.6.3
+ setup-method: ghcup
+ allow-failure: false
+ - compiler: ghc-9.4.7
+ compilerKind: ghc
+ compilerVersion: 9.4.7
setup-method: ghcup
allow-failure: false
- compiler: ghc-9.2.8
@@ -73,6 +76,7 @@ jobs:
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.19.5/x86_64-linux-ghcup-0.1.19.5 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
+ "$HOME/.ghcup/bin/ghcup" config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml;
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
"$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
apt-get update
@@ -88,10 +92,12 @@ jobs:
echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
HCDIR=/opt/$HCKIND/$HCVER
- HC=$HOME/.ghcup/bin/$HCKIND-$HCVER
+ HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER")
+ HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#')
+ HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#')
echo "HC=$HC" >> "$GITHUB_ENV"
- echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
- echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
+ echo "HCPKG=$HCPKG" >> "$GITHUB_ENV"
+ echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV"
echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.1.0 -vnormal+nowrap" >> "$GITHUB_ENV"
HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
diff --git a/cabal.haskell-ci b/cabal.haskell-ci
index 8f39d5cf4..391b2ebbf 100644
--- a/cabal.haskell-ci
+++ b/cabal.haskell-ci
@@ -1,4 +1,4 @@
-branches: master ci*
+branches: master
installed: +all -Cabal -Cabal-syntax -text -parsec -process
diff --git a/hackage-server.cabal b/hackage-server.cabal
index fc0f51c88..a09731d01 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -28,8 +28,9 @@ license: BSD-3-Clause
license-file: LICENSE
tested-with:
- GHC == 9.6.2
- GHC == 9.4.5
+ GHC == 9.8.1
+ GHC == 9.6.3
+ GHC == 9.4.7
GHC == 9.2.8
GHC == 9.0.2
GHC == 8.10.7
@@ -130,8 +131,8 @@ common defaults
, array >= 0.5 && < 0.6
, base >= 4.13 && < 4.20
, binary >= 0.8 && < 0.9
- , bytestring >= 0.10 && < 0.12
- , containers ^>= 0.6.0
+ , bytestring >= 0.10 && < 0.13
+ , containers >= 0.6.0 && < 0.8
, deepseq >= 1.4 && < 1.6
, directory >= 1.3 && < 1.4
, filepath >= 1.4 && < 1.5
@@ -139,7 +140,7 @@ common defaults
-- we use Control.Monad.Except, introduced in mtl-2.2.1
, pretty >= 1.1 && < 1.2
, process >= 1.6 && < 1.7
- , text ^>= 1.2.5.0 || ^>= 2.0
+ , text ^>= 1.2.5.0 || >= 2.0 && < 2.2
, time >= 1.9 && < 1.13
, transformers >= 0.5 && < 0.7
, unix >= 2.7 && < 2.9
@@ -450,7 +451,7 @@ library lib-server
, stm ^>= 2.5.0
, stringsearch ^>= 0.3.6.6
, tagged ^>= 0.8.5
- , xhtml ^>= 3000.2
+ , xhtml ^>= 3000.2.0.0
, xmlgen ^>= 0.6
, xss-sanitize ^>= 0.3.6
@@ -579,12 +580,12 @@ test-suite ReverseDependenciesTest
main-is: ReverseDependenciesTest.hs
build-tool-depends: hackage-server:hackage-server
build-depends:
- , tasty ^>= 1.4
+ , tasty ^>= 1.5
, tasty-golden ^>= 2.3
, tasty-hedgehog ^>= 1.4
, tasty-hunit ^>= 0.10
, HUnit ^>= 1.6
- , hedgehog ^>= 1.3
+ , hedgehog ^>= 1.4
, exceptions
, bimap
, mime-mail
@@ -649,7 +650,7 @@ test-suite PackageTests
build-depends:
-- version constraints inherited from lib-server
-- component-specific dependencies
- , tasty ^>= 1.4
+ , tasty ^>= 1.5
, tasty-hunit ^>= 0.10
, HUnit ^>= 1.6
@@ -667,7 +668,7 @@ test-suite HashTests
, cryptohash-sha256
, safecopy
-- component-specific dependencies
- , tasty ^>= 1.4
+ , tasty ^>= 1.5
, tasty-hunit ^>= 0.10
test-suite DocTests
From 1cba0440ea7f83b5d676c2b7b4058ff3da4bd067 Mon Sep 17 00:00:00 2001
From: Andreas Abel
Date: Tue, 24 Oct 2023 20:21:26 +0200
Subject: [PATCH 63/71] CI: disable redundant workflow `cabal.yml`
- does not add anything over `haskell-ci.yml`
- not quite up to the state of the art:
* does not do any caching
* `cabal haddock` should be invoked with `--disable-documentation`;
currently, it rebuilds everything
---
.github/workflows/{cabal.yml => cabal.off} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename .github/workflows/{cabal.yml => cabal.off} (100%)
diff --git a/.github/workflows/cabal.yml b/.github/workflows/cabal.off
similarity index 100%
rename from .github/workflows/cabal.yml
rename to .github/workflows/cabal.off
From ada92d091073ed0aa97b3e65854f619f21cfdac7 Mon Sep 17 00:00:00 2001
From: Janus Troelsen
Date: Fri, 1 Dec 2023 23:13:57 -0600
Subject: [PATCH 64/71] Add e-mail notification when all vouches received
---
src/Distribution/Server/Features.hs | 9 ++-
.../Server/Features/UserNotify.hs | 23 +++++-
src/Distribution/Server/Features/Vouch.hs | 77 ++++++++++++-------
tests/ReverseDependenciesTest.hs | 5 ++
...ationEmails-NotifyVouchingCompleted.golden | 35 +++++++++
5 files changed, 117 insertions(+), 32 deletions(-)
create mode 100644 tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyVouchingCompleted.golden
diff --git a/src/Distribution/Server/Features.hs b/src/Distribution/Server/Features.hs
index 0a9b52011..b4e2d96d5 100644
--- a/src/Distribution/Server/Features.hs
+++ b/src/Distribution/Server/Features.hs
@@ -347,6 +347,10 @@ initHackageFeatures env@ServerEnv{serverVerbosity = verbosity} = do
tagsFeature
tarIndexCacheFeature
+ vouchFeature <- mkVouchFeature
+ usersFeature
+ uploadFeature
+
userNotifyFeature <- mkUserNotifyFeature
usersFeature
coreFeature
@@ -356,16 +360,13 @@ initHackageFeatures env@ServerEnv{serverVerbosity = verbosity} = do
reportsCoreFeature
tagsFeature
reverseFeature
+ vouchFeature
packageFeedFeature <- mkPackageFeedFeature
coreFeature
usersFeature
tarIndexCacheFeature
- vouchFeature <- mkVouchFeature
- usersFeature
- uploadFeature
-
browseFeature <- mkBrowseFeature
coreFeature
usersFeature
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index fb11fa5ef..ce6a9f9ea 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -52,6 +52,7 @@ import Distribution.Server.Features.Tags
import Distribution.Server.Features.Upload
import Distribution.Server.Features.UserDetails
import Distribution.Server.Features.Users
+import Distribution.Server.Features.Vouch
import Distribution.Server.Util.Email
@@ -437,6 +438,7 @@ initUserNotifyFeature :: ServerEnv
-> ReportsFeature
-> TagsFeature
-> ReverseFeature
+ -> VouchFeature
-> IO UserNotifyFeature)
initUserNotifyFeature env@ServerEnv{ serverStateDir, serverTemplatesDir,
serverTemplatesMode } = do
@@ -448,10 +450,10 @@ initUserNotifyFeature env@ServerEnv{ serverStateDir, serverTemplatesDir,
[serverTemplatesDir, serverTemplatesDir > "UserNotify"]
[ "user-notify-form.html" ]
- return $ \users core uploadfeature adminlog userdetails reports tags revers -> do
+ return $ \users core uploadfeature adminlog userdetails reports tags revers vouch -> do
let feature = userNotifyFeature env
users core uploadfeature adminlog userdetails reports tags
- revers notifyState templates
+ revers vouch notifyState templates
return feature
data InRange = InRange | OutOfRange
@@ -582,6 +584,7 @@ userNotifyFeature :: ServerEnv
-> ReportsFeature
-> TagsFeature
-> ReverseFeature
+ -> VouchFeature
-> StateComponent AcidState NotifyData
-> Templates
-> UserNotifyFeature
@@ -594,6 +597,7 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
ReportsFeature{..}
TagsFeature{..}
ReverseFeature{queryReverseIndex}
+ VouchFeature{drainQueuedNotifications}
notifyState templates
= UserNotifyFeature {..}
@@ -709,6 +713,8 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
revIdx <- liftIO queryReverseIndex
dependencyUpdateNotifications <- concatMapM (genDependencyUpdateList notifyPrefs idx revIdx . pkgInfoToPkgId) revisionsAndUploads
+ vouchNotifications <- fmap (, NotifyVouchingCompleted) <$> drainQueuedNotifications
+
emails <-
getNotificationEmails serverEnv userDetailsFeature users $
concat
@@ -717,6 +723,7 @@ userNotifyFeature serverEnv@ServerEnv{serverCron}
, docReportNotifications
, tagProposalNotifications
, dependencyUpdateNotifications
+ , vouchNotifications
]
mapM_ sendNotifyEmailAndDelay emails
@@ -897,6 +904,7 @@ data Notification
-- ^ Packages maintained by user that depend on updated dep
, notifyTriggerBounds :: NotifyTriggerBounds
}
+ | NotifyVouchingCompleted
deriving (Show)
data NotifyMaintainerUpdateType = MaintainerAdded | MaintainerRemoved
@@ -1021,6 +1029,10 @@ getNotificationEmails
notifyWatchedPackages
, DependencyNotification notifyPackageId
)
+ NotifyVouchingCompleted ->
+ generalNotification
+ renderNotifyVouchingCompleted
+
where
generalNotification = (, GeneralNotification)
@@ -1086,6 +1098,13 @@ getNotificationEmails
]
<> EmailContentList (map renderPkgLink revDeps)
+ renderNotifyVouchingCompleted =
+ EmailContentParagraph
+ "You have received all necessary vouches. \
+ \You have been added the the 'uploaders' group. \
+ \You can now upload packages to Hackage. \
+ \Note that packages cannot be deleted, so be careful."
+
{----- Rendering helpers -----}
renderPackageName = emailContentStr . unPackageName
diff --git a/src/Distribution/Server/Features/Vouch.hs b/src/Distribution/Server/Features/Vouch.hs
index 88a0f3494..d6444e966 100644
--- a/src/Distribution/Server/Features/Vouch.hs
+++ b/src/Distribution/Server/Features/Vouch.hs
@@ -3,21 +3,24 @@
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DerivingStrategies #-}
-module Distribution.Server.Features.Vouch (VouchError(..), VouchSuccess(..), initVouchFeature, judgeVouch) where
+{-# LANGUAGE RankNTypes #-}
+module Distribution.Server.Features.Vouch (VouchFeature(..), VouchData(..), VouchError(..), VouchSuccess(..), initVouchFeature, judgeVouch) where
import Control.Monad (when, join)
import Control.Monad.Except (runExceptT, throwError)
import Control.Monad.Reader (ask)
import Control.Monad.State (get, put)
+import Control.Monad.IO.Class (MonadIO)
import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.Map.Strict as Map
+import qualified Data.Set as Set
import Data.Maybe (fromMaybe)
import Data.Time (UTCTime(..), addUTCTime, getCurrentTime, nominalDay, secondsToDiffTime)
import Data.Time.Format.ISO8601 (formatShow, iso8601Format)
import Text.XHtml.Strict (prettyHtmlFragment, stringToHtml, li)
import Data.SafeCopy (base, deriveSafeCopy)
-import Distribution.Server.Framework ((>), AcidState, DynamicPath, HackageFeature, IsHackageFeature, IsHackageFeature(..), MemSize)
+import Distribution.Server.Framework ((>), AcidState, DynamicPath, HackageFeature, IsHackageFeature, IsHackageFeature(..), MemSize(..), memSize2)
import Distribution.Server.Framework (MessageSpan(MText), Method(..), Query, Response, ServerEnv(..), ServerPartE, StateComponent(..), Update)
import Distribution.Server.Framework (abstractAcidStateComponent, emptyHackageFeature, errBadRequest)
import Distribution.Server.Framework (featureDesc, featureReloadFiles, featureResources, featureState)
@@ -31,20 +34,26 @@ import Distribution.Server.Features.Upload(UploadFeature(..))
import Distribution.Server.Features.Users (UserFeature(..))
import Distribution.Simple.Utils (toUTF8LBS)
-newtype VouchData = VouchData (Map.Map UserId [(UserId, UTCTime)])
+data VouchData =
+ VouchData
+ { vouches :: Map.Map UserId [(UserId, UTCTime)]
+ , notNotified :: Set.Set UserId
+ }
deriving (Show, Eq)
- deriving newtype MemSize
+
+instance MemSize VouchData where
+ memSize (VouchData vouches notified) = memSize2 vouches notified
putVouch :: UserId -> (UserId, UTCTime) -> Update VouchData ()
putVouch vouchee (voucher, now) = do
- VouchData tbl <- get
+ VouchData tbl notNotified <- get
let oldMap = fromMaybe [] (Map.lookup vouchee tbl)
newMap = (voucher, now) : oldMap
- put $ VouchData (Map.insert vouchee newMap tbl)
+ put $ VouchData (Map.insert vouchee newMap tbl) notNotified
getVouchesFor :: UserId -> Query VouchData [(UserId, UTCTime)]
getVouchesFor needle = do
- VouchData tbl <- ask
+ VouchData tbl _notNotified <- ask
pure . fromMaybe [] $ Map.lookup needle tbl
getVouchesData :: Query VouchData VouchData
@@ -65,8 +74,8 @@ makeAcidic ''VouchData
vouchStateComponent :: FilePath -> IO (StateComponent AcidState VouchData)
vouchStateComponent stateDir = do
- st <- openLocalStateFrom (stateDir > "db" > "Vouch") (VouchData mempty)
- let initialVouchData = VouchData mempty
+ st <- openLocalStateFrom (stateDir > "db" > "Vouch") (VouchData mempty mempty)
+ let initialVouchData = VouchData mempty mempty
restore =
RestoreBackup
{ restoreEntry = error "Unexpected backup entry"
@@ -85,6 +94,7 @@ vouchStateComponent stateDir = do
data VouchFeature =
VouchFeature
{ vouchFeatureInterface :: HackageFeature
+ , drainQueuedNotifications :: forall m. MonadIO m => m [UserId]
}
instance IsHackageFeature VouchFeature where
@@ -167,8 +177,8 @@ initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMo
handleGetVouches :: DynamicPath -> ServerPartE Response
handleGetVouches dpath = do
uid <- lookupUserName =<< userNameInPath dpath
- userIds <- queryState vouchState $ GetVouchesFor uid
- param <- renderToLBS lookupUserInfo userIds
+ vouches <- queryState vouchState $ GetVouchesFor uid
+ param <- renderToLBS lookupUserInfo vouches
pure . toResponse $ vouchTemplate
[ "msg" $= ""
, param
@@ -197,6 +207,13 @@ initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMo
param <- renderToLBS lookupUserInfo $ existingVouchers ++ [(voucher, now)]
case result of
AddVouchComplete -> do
+ -- enqueue vouching completed notification
+ -- which will be read using drainQueuedNotifications
+ VouchData vouches notNotified <-
+ queryState vouchState GetVouchesData
+ let newState = VouchData vouches (Set.insert vouchee notNotified)
+ updateState vouchState $ ReplaceVouchesData newState
+
liftIO $ Group.addUserToGroup uploadersGroup vouchee
pure . toResponse $ vouchTemplate
[ "msg" $= "Added vouch. User is now an uploader!"
@@ -211,18 +228,26 @@ initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMo
<> " to become uploader."
, param
]
- return $ VouchFeature $
- (emptyHackageFeature "vouch")
- { featureDesc = "Vouching for users getting upload permission."
- , featureResources =
- [(resourceAt "/user/:username/vouch")
- { resourceDesc = [(GET, "list people vouching")
- ,(POST, "vouch for user")
- ]
- , resourceGet = [("html", handleGetVouches)]
- , resourcePost = [("html", handlePostVouch)]
- }
- ]
- , featureState = [ abstractAcidStateComponent vouchState ]
- , featureReloadFiles = reloadTemplates templates
- }
+ return $ VouchFeature {
+ vouchFeatureInterface =
+ (emptyHackageFeature "vouch")
+ { featureDesc = "Vouching for users getting upload permission."
+ , featureResources =
+ [(resourceAt "/user/:username/vouch")
+ { resourceDesc = [(GET, "list people vouching")
+ ,(POST, "vouch for user")
+ ]
+ , resourceGet = [("html", handleGetVouches)]
+ , resourcePost = [("html", handlePostVouch)]
+ }
+ ]
+ , featureState = [ abstractAcidStateComponent vouchState ]
+ , featureReloadFiles = reloadTemplates templates
+ },
+ drainQueuedNotifications = do
+ VouchData vouches notNotified <-
+ queryState vouchState GetVouchesData
+ let newState = VouchData vouches mempty
+ updateState vouchState $ ReplaceVouchesData newState
+ pure $ Set.toList notNotified
+ }
diff --git a/tests/ReverseDependenciesTest.hs b/tests/ReverseDependenciesTest.hs
index 5b0333a04..fa78807d9 100644
--- a/tests/ReverseDependenciesTest.hs
+++ b/tests/ReverseDependenciesTest.hs
@@ -422,6 +422,8 @@ getNotificationEmailsTests =
, notifyWatchedPackages = [PackageIdentifier "mtl" (mkVersion [2, 3])]
, notifyTriggerBounds = BoundsOutOfRange
}
+ , testGolden "Render NotifyVouchingCompleted" "getNotificationEmails-NotifyVouchingCompleted.golden" $
+ fmap renderMail $ getNotificationEmailMocked userWatcher NotifyVouchingCompleted
, testGolden "Render general notifications in single batched email" "getNotificationEmails-batched.golden" $ do
emails <-
getNotificationEmailsMocked . map (userWatcher,) $
@@ -455,6 +457,7 @@ getNotificationEmailsTests =
NotifyDocsBuild{} -> ()
NotifyUpdateTags{} -> ()
NotifyDependencyUpdate{} -> ()
+ NotifyVouchingCompleted{} -> ()
isGeneral = \case
NotifyNewVersion{} -> True
@@ -463,6 +466,7 @@ getNotificationEmailsTests =
NotifyDocsBuild{} -> True
NotifyUpdateTags{} -> True
NotifyDependencyUpdate{} -> False
+ NotifyVouchingCompleted{} -> True
-- userWatcher = user getting the notification
-- userActor = user that did the action
@@ -539,6 +543,7 @@ getNotificationEmailsTests =
<$> genPackageId
<*> Gen.list (Range.linear 1 10) genPackageId
<*> Gen.element [Always, NewIncompatibility, BoundsOutOfRange]
+ , pure NotifyVouchingCompleted
]
genPackageName = mkPackageName <$> Gen.string (Range.linear 1 30) Gen.unicode
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyVouchingCompleted.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyVouchingCompleted.golden
new file mode 100644
index 000000000..d0d1d301f
--- /dev/null
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyVouchingCompleted.golden
@@ -0,0 +1,35 @@
+From: =?utf-8?Q?Hackage_website?=
+To: =?utf-8?Q?user-watcher?=
+Subject: [Hackage] Maintainer Notifications
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="YIYrWcf3to"
+
+--YIYrWcf3to
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+You have received all necessary vouches=2E You have been added the the 'upl=
+oaders' group=2E You can now upload packages to Hackage=2E Note that packag=
+es cannot be deleted, so be careful=2E
+
+You can adjust your notification preferences at
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
+=2Ehaskell=2Eorg/user/user-watcher/notify)
+
+
+--YIYrWcf3to
+Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+
+
+You have received all necessary vouches=2E You have been added the the 'upl=
+oaders' group=2E You can now upload packages to Hackage=2E Note that packag=
+es cannot be deleted, so be careful=2E
+
+
+You can adjust your notification preferences at
+=
+https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify
+
+--YIYrWcf3to--
\ No newline at end of file
From d9fd27a6b0e25d60f830c05db51704c9594f20d7 Mon Sep 17 00:00:00 2001
From: Janus Troelsen
Date: Fri, 1 Dec 2023 23:24:43 -0600
Subject: [PATCH 65/71] Rename user-facing strings to 'endorsements', add
threshold template param
---
datafiles/templates/Html/vouch.html.st | 10 +++----
.../Server/Features/UserNotify.hs | 2 +-
src/Distribution/Server/Features/Vouch.hs | 29 ++++++++++---------
...ationEmails-NotifyVouchingCompleted.golden | 12 ++++----
4 files changed, 27 insertions(+), 26 deletions(-)
diff --git a/datafiles/templates/Html/vouch.html.st b/datafiles/templates/Html/vouch.html.st
index 89ae9c238..36d0e7945 100644
--- a/datafiles/templates/Html/vouch.html.st
+++ b/datafiles/templates/Html/vouch.html.st
@@ -2,24 +2,24 @@
$hackageCssTheme()$
-Vouch for user | Hackage
+Endorse user | Hackage
$hackagePageHeader()$
-
Vouch for user
+
Endorse user
$msg$
-
Vouching cannot be undone! When the user has three vouches, the user
+
Endorsing cannot be undone! When the user has $requiredNumber$ endorsements, the user
can upload packages. Note that users are, to a certain degree, held accountable
-for the actions of the users they vouch for. Only vouch for people you know.
+for the actions of the users they endorse. Only endorse people you know.
$vouches$
diff --git a/src/Distribution/Server/Features/UserNotify.hs b/src/Distribution/Server/Features/UserNotify.hs
index ce6a9f9ea..2d0289a20 100644
--- a/src/Distribution/Server/Features/UserNotify.hs
+++ b/src/Distribution/Server/Features/UserNotify.hs
@@ -1100,7 +1100,7 @@ getNotificationEmails
renderNotifyVouchingCompleted =
EmailContentParagraph
- "You have received all necessary vouches. \
+ "You have received all necessary endorsements. \
\You have been added the the 'uploaders' group. \
\You can now upload packages to Hackage. \
\Note that packages cannot be deleted, so be careful."
diff --git a/src/Distribution/Server/Features/Vouch.hs b/src/Distribution/Server/Features/Vouch.hs
index d6444e966..39795f0b5 100644
--- a/src/Distribution/Server/Features/Vouch.hs
+++ b/src/Distribution/Server/Features/Vouch.hs
@@ -152,7 +152,7 @@ renderToLBS lookupUserInfo vouches = do
pure $
templateUnescaped "vouches" $
if null rendered
- then LBS.pack "Nobody has vouched yet."
+ then LBS.pack "Nobody has endorsed yet."
else LBS.intercalate mempty rendered
renderVouchers :: (UserId -> ServerPartE UserInfo) -> (UserId, UTCTime) -> ServerPartE LBS.ByteString
@@ -181,6 +181,7 @@ initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMo
param <- renderToLBS lookupUserInfo vouches
pure . toResponse $ vouchTemplate
[ "msg" $= ""
+ , "requiredNumber" $= show requiredCountOfVouches
, param
]
handlePostVouch :: DynamicPath -> ServerPartE Response
@@ -193,15 +194,15 @@ initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMo
existingVouchers <- queryState vouchState $ GetVouchesFor vouchee
case judgeVouch ugroup now vouchee vouchersForVoucher existingVouchers voucher of
Left NotAnUploader ->
- errBadRequest "Not an uploader" [MText "You must be an uploader yourself to vouch for other users."]
+ errBadRequest "Not an uploader" [MText "You must be an uploader yourself to endorse other users."]
Left You'reTooNew ->
- errBadRequest "You're too new" [MText "The latest of the vouches for your user must be at least 30 days old."]
+ errBadRequest "You're too new" [MText "The latest of the endorsements for your user must be at least 30 days old."]
Left VoucheeAlreadyUploader ->
- errBadRequest "Vouchee already uploader" [MText "You can't vouch for this user, since they are already an uploader."]
+ errBadRequest "Endorsee already uploader" [MText "You can't endorse this user, since they are already an uploader."]
Left AlreadySufficientlyVouched ->
- errBadRequest "Already sufficiently vouched" [MText "There are already a sufficient number of vouches for this user."]
+ errBadRequest "Already sufficiently endorsed" [MText "There are already a sufficient number of endorsements for this user."]
Left YouAlreadyVouched ->
- errBadRequest "Already vouched" [MText "You have already vouched for this user."]
+ errBadRequest "Already endorsed" [MText "You have already endorsed this user."]
Right result -> do
updateState vouchState $ PutVouch vouchee (voucher, now)
param <- renderToLBS lookupUserInfo $ existingVouchers ++ [(voucher, now)]
@@ -216,26 +217,26 @@ initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMo
liftIO $ Group.addUserToGroup uploadersGroup vouchee
pure . toResponse $ vouchTemplate
- [ "msg" $= "Added vouch. User is now an uploader!"
+ [ "msg" $= "Added endorsement. User is now an uploader!"
, param
]
AddVouchIncomplete stillRequired ->
pure . toResponse $ vouchTemplate
[ "msg" $=
- "Added vouch. User still needs "
+ "Added endorsement. User still needs "
<> show stillRequired
- <> if stillRequired == 1 then " vouch" else " vouches"
+ <> if stillRequired == 1 then " endorsement" else " endorsements"
<> " to become uploader."
, param
]
return $ VouchFeature {
vouchFeatureInterface =
- (emptyHackageFeature "vouch")
- { featureDesc = "Vouching for users getting upload permission."
+ (emptyHackageFeature "endorse")
+ { featureDesc = "Endorsing users such that they get upload permission."
, featureResources =
- [(resourceAt "/user/:username/vouch")
- { resourceDesc = [(GET, "list people vouching")
- ,(POST, "vouch for user")
+ [(resourceAt "/user/:username/endorse")
+ { resourceDesc = [(GET, "list people endorsing")
+ ,(POST, "endorse for user")
]
, resourceGet = [("html", handleGetVouches)]
, resourcePost = [("html", handlePostVouch)]
diff --git a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyVouchingCompleted.golden b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyVouchingCompleted.golden
index d0d1d301f..b45e64f28 100644
--- a/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyVouchingCompleted.golden
+++ b/tests/golden/ReverseDependenciesTest/getNotificationEmails-NotifyVouchingCompleted.golden
@@ -8,9 +8,9 @@ Content-Type: multipart/alternative; boundary="YIYrWcf3to"
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
-You have received all necessary vouches=2E You have been added the the 'upl=
-oaders' group=2E You can now upload packages to Hackage=2E Note that packag=
-es cannot be deleted, so be careful=2E
+You have received all necessary endorsements=2E You have been added the the=
+ 'uploaders' group=2E You can now upload packages to Hackage=2E Note that p=
+ackages cannot be deleted, so be careful=2E
You can adjust your notification preferences at
https://hackage=2Ehaskell=2Eorg/user/user-watcher/notify (https://hackage=
@@ -23,9 +23,9 @@ Content-Transfer-Encoding: quoted-printable
-You have received all necessary vouches=2E You have been added the the 'upl=
-oaders' group=2E You can now upload packages to Hackage=2E Note that packag=
-es cannot be deleted, so be careful=2E
+You have received all necessary endorsements=2E You have been added the the=
+ 'uploaders' group=2E You can now upload packages to Hackage=2E Note that p=
+ackages cannot be deleted, so be careful=2E
You can adjust your notification preferences at
From 7bad6f8308f09c84f65e1e1b4850457555bd7244 Mon Sep 17 00:00:00 2001
From: Gautier DI FOLCO
Date: Thu, 30 Nov 2023 23:22:12 +0100
Subject: [PATCH 66/71] fix: replace list lastVersion by referenceVersion
(#1264)
---
datafiles/static/browse.js | 2 +-
datafiles/templates/Html/browse.html.st | 2 +-
src/Distribution/Server/Features/Browse.hs | 4 +-
.../Server/Features/Browse/ApplyFilter.hs | 2 +-
.../Server/Features/Browse/Options.hs | 6 +-
.../Server/Features/Html/HtmlUtilities.hs | 2 +-
.../Server/Features/PackageList.hs | 57 +++++++++++++++----
.../Server/Features/PreferredVersions.hs | 6 ++
8 files changed, 62 insertions(+), 19 deletions(-)
diff --git a/datafiles/static/browse.js b/datafiles/static/browse.js
index 868768a44..4c79adcbe 100644
--- a/datafiles/static/browse.js
+++ b/datafiles/static/browse.js
@@ -133,7 +133,7 @@ const replaceRows = (response) => {
tr.appendChild(createSimpleText(row.description));
tr.appendChild(createTags(row.tags));
tr.appendChild(createLastUpload(row.lastUpload));
- tr.appendChild(createSimpleText(row.lastVersion));
+ tr.appendChild(createSimpleText(row.referenceVersion));
tr.appendChild(createMaintainers(row.maintainers));
l.appendChild(tr);
}
diff --git a/datafiles/templates/Html/browse.html.st b/datafiles/templates/Html/browse.html.st
index 8f79ce634..ddc240e75 100644
--- a/datafiles/templates/Html/browse.html.st
+++ b/datafiles/templates/Html/browse.html.st
@@ -212,7 +212,7 @@
Description
Tags
Last U/L
- Last Version
+ Reference Version
Maintainers
diff --git a/src/Distribution/Server/Features/Browse.hs b/src/Distribution/Server/Features/Browse.hs
index 0ff354f3b..9b53e01b3 100644
--- a/src/Distribution/Server/Features/Browse.hs
+++ b/src/Distribution/Server/Features/Browse.hs
@@ -139,7 +139,7 @@ packageIndexInfoToValue :: CoreResource -> TagsResource -> UserResource -> Packa
packageIndexInfoToValue
coreResource tagsResource userResource
PackageItem{itemName, itemDownloads, itemVotes,
- itemDesc, itemTags, itemLastUpload, itemLastVersion, itemMaintainer} =
+ itemDesc, itemTags, itemLastUpload, itemReferenceVersion, itemMaintainer} =
object
[ Key.fromString "name" .= renderPackage itemName
, Key.fromString "downloads" .= itemDownloads
@@ -147,7 +147,7 @@ packageIndexInfoToValue
, Key.fromString "description" .= itemDesc
, Key.fromString "tags" .= map renderTag (S.toAscList itemTags)
, Key.fromString "lastUpload" .= iso8601Show itemLastUpload
- , Key.fromString "lastVersion" .= itemLastVersion
+ , Key.fromString "referenceVersion" .= itemReferenceVersion
, Key.fromString "maintainers" .= map renderUser itemMaintainer
]
where
diff --git a/src/Distribution/Server/Features/Browse/ApplyFilter.hs b/src/Distribution/Server/Features/Browse/ApplyFilter.hs
index c86082fc8..f129109fb 100644
--- a/src/Distribution/Server/Features/Browse/ApplyFilter.hs
+++ b/src/Distribution/Server/Features/Browse/ApplyFilter.hs
@@ -63,7 +63,7 @@ sort isSearch sortColumn sortDirection =
Description -> comparing itemDesc
Tags -> comparing (S.toAscList . itemTags)
LastUpload -> comparing itemLastUpload
- LastVersion -> comparing itemLastVersion
+ ReferenceVersion -> comparing itemReferenceVersion
Maintainers -> comparing itemMaintainer
in sortBy (maybeReverse comparer)
where
diff --git a/src/Distribution/Server/Features/Browse/Options.hs b/src/Distribution/Server/Features/Browse/Options.hs
index dd93401ef..64416b355 100644
--- a/src/Distribution/Server/Features/Browse/Options.hs
+++ b/src/Distribution/Server/Features/Browse/Options.hs
@@ -9,7 +9,7 @@ import Distribution.Server.Features.Browse.Parsers (Filter, conditions, condsToF
data IsSearch = IsSearch | IsNotSearch
-data NormalColumn = Name | Downloads | Rating | Description | Tags | LastUpload | LastVersion | Maintainers
+data NormalColumn = Name | Downloads | Rating | Description | Tags | LastUpload | ReferenceVersion | Maintainers
deriving (Show, Eq)
data Column = DefaultColumn | NormalColumn NormalColumn
@@ -36,7 +36,7 @@ instance FromJSON Column where
"description" -> pure $ NormalColumn Description
"tags" -> pure $ NormalColumn Tags
"lastUpload" -> pure $ NormalColumn LastUpload
- "lastVersion" -> pure $ NormalColumn LastVersion
+ "referenceVersion" -> pure $ NormalColumn ReferenceVersion
"maintainers" -> pure $ NormalColumn Maintainers
t -> fail $ "Column invalid: " ++ T.unpack t
@@ -49,7 +49,7 @@ columnToTemplateName = \case
NormalColumn Description -> "description"
NormalColumn Tags -> "tags"
NormalColumn LastUpload -> "lastUpload"
- NormalColumn LastVersion -> "lastVersion"
+ NormalColumn ReferenceVersion -> "referenceVersion"
NormalColumn Maintainers -> "maintainers"
instance FromJSON Direction where
diff --git a/src/Distribution/Server/Features/Html/HtmlUtilities.hs b/src/Distribution/Server/Features/Html/HtmlUtilities.hs
index d0fd2f44f..298810ff0 100644
--- a/src/Distribution/Server/Features/Html/HtmlUtilities.hs
+++ b/src/Distribution/Server/Features/Html/HtmlUtilities.hs
@@ -52,7 +52,7 @@ htmlUtilities CoreFeature{coreResource}
, td $ toHtml $ itemDesc item
, td $ " (" +++ renderTags (itemTags item) +++ ")"
, td $ toHtml $ formatTime defaultTimeLocale "%F" (itemLastUpload item)
- , td $ toHtml $ itemLastVersion item
+ , td $ toHtml $ itemReferenceVersion item
, td $ "" +++ intersperse (toHtml ", ") (map renderUser (itemMaintainer item))
]
where
diff --git a/src/Distribution/Server/Features/PackageList.hs b/src/Distribution/Server/Features/PackageList.hs
index 57b1c1b8a..d2b063c29 100644
--- a/src/Distribution/Server/Features/PackageList.hs
+++ b/src/Distribution/Server/Features/PackageList.hs
@@ -29,6 +29,7 @@ import Distribution.Package
import Distribution.PackageDescription
import Distribution.PackageDescription.Configuration
import Distribution.Pretty (prettyShow)
+import Distribution.Types.Version (Version)
import Distribution.Utils.ShortText (fromShortText)
import Control.Concurrent
@@ -89,8 +90,8 @@ data PackageItem = PackageItem {
itemLastUpload :: !UTCTime,
-- Hotness = recent downloads + stars + 2 * no rev deps
itemHotness :: !Float,
- -- Last version
- itemLastVersion :: !String
+ -- Reference version (non-deprecated highest numbered version)
+ itemReferenceVersion :: !String
}
instance MemSize PackageItem where
@@ -98,8 +99,24 @@ instance MemSize PackageItem where
emptyPackageItem :: PackageName -> PackageItem
-emptyPackageItem pkg = PackageItem pkg Set.empty Nothing "" []
- 0 0 0 False 0 0 0 (UTCTime (toEnum 0) 0) 0 ""
+emptyPackageItem pkg =
+ PackageItem {
+ itemName = pkg,
+ itemTags = Set.empty,
+ itemDeprecated = Nothing,
+ itemDesc = "",
+ itemMaintainer = [],
+ itemVotes = 0,
+ itemDownloads = 0,
+ itemRevDepsCount = 0,
+ itemHasLibrary = False,
+ itemNumExecutables = 0,
+ itemNumTests = 0,
+ itemNumBenchmarks = 0,
+ itemLastUpload = UTCTime (toEnum 0) 0,
+ itemHotness = 0,
+ itemReferenceVersion = ""
+ }
initListFeature :: ServerEnv
@@ -134,10 +151,14 @@ initListFeature _env = do
registerHookJust packageChangeHook isPackageAdd $ \pkg -> do
let pkgname = packageName . packageId $ pkg
- modifyItem pkgname $ \x -> x
- {itemLastUpload = fst (pkgOriginalUploadInfo pkg)
- ,itemLastVersion = prettyShow $ pkgVersion $ pkgInfoId pkg
- }
+ prefsinfo <- queryGetPreferredInfo pkgname
+ index <- queryGetPackageIndex
+ let allVersions = packageVersion <$> PackageIndex.lookupPackageName index pkgname
+ modifyItem pkgname $ \x ->
+ updateReferenceVersion prefsinfo allVersions $
+ x
+ { itemLastUpload = fst (pkgOriginalUploadInfo pkg)
+ }
runHook_ itemUpdate (Set.singleton pkgname)
registerHook groupChangedHook $ \(gd,_,_,_,_) ->
@@ -174,6 +195,11 @@ initListFeature _env = do
modifyItem pkgname (updateDeprecation mpkgs)
runHook_ itemUpdate (Set.singleton pkgname)
+ registerHook updatePreferredHook $ \(pkgname, prefsinfo) -> do
+ index <- queryGetPackageIndex
+ let allVersions = packageVersion <$> PackageIndex.lookupPackageName index pkgname
+ modifyItem pkgname $ updateReferenceVersion prefsinfo allVersions
+
return feature
@@ -265,8 +291,9 @@ listFeature CoreFeature{..}
votes <- pkgNumScore pkgname
deprs <- queryGetDeprecatedFor pkgname
maintainers <- queryUserGroup (maintainersGroup pkgname)
+ prefsinfo <- queryGetPreferredInfo pkgname
- return $ (,) pkgname $ (updateDescriptionItem desc $ emptyPackageItem pkgname) {
+ return $ (,) pkgname . updateReferenceVersion prefsinfo [pkgVersion (pkgInfoId pkg)] $ (updateDescriptionItem desc $ emptyPackageItem pkgname) {
itemTags = tags
, itemMaintainer = map (userIdToName users) (UserIdSet.toList maintainers)
, itemDeprecated = deprs
@@ -275,7 +302,6 @@ listFeature CoreFeature{..}
, itemLastUpload = fst (pkgOriginalUploadInfo pkg)
, itemRevDepsCount = intRevDirectCount
, itemHotness = votes + fromIntegral (cmFind pkgname downs) + fromIntegral intRevDirectCount * 2
- , itemLastVersion = prettyShow $ pkgVersion $ pkgInfoId pkg
}
------------------------------
@@ -329,6 +355,17 @@ updateDeprecation pkgs item =
itemDeprecated = pkgs
}
+updateReferenceVersion :: PreferredInfo -> [Version] -> PackageItem -> PackageItem
+updateReferenceVersion prefsinfo allVersions item =
+ item {
+ itemReferenceVersion =
+ case nonDeprecatedVersion of
+ [] -> ""
+ xs -> prettyShow $ maximum xs
+ }
+ where
+ nonDeprecatedVersion = filter (`notElem` deprecatedVersions prefsinfo) allVersions
+
updateReverseItem :: Int -> PackageItem -> PackageItem
updateReverseItem revDirectCount item =
item {
diff --git a/src/Distribution/Server/Features/PreferredVersions.hs b/src/Distribution/Server/Features/PreferredVersions.hs
index 78c5ddeac..6097afa0c 100644
--- a/src/Distribution/Server/Features/PreferredVersions.hs
+++ b/src/Distribution/Server/Features/PreferredVersions.hs
@@ -55,6 +55,7 @@ data VersionsFeature = VersionsFeature {
versionsResource :: VersionsResource,
deprecatedHook :: Hook (PackageName, Maybe [PackageName]) (),
putDeprecated :: PackageName -> ServerPartE Bool,
+ updatePreferredHook :: Hook (PackageName, PreferredInfo) (),
putPreferred :: PackageName -> ServerPartE (),
updateDeprecatedTags :: IO (),
@@ -101,12 +102,14 @@ initVersionsFeature :: ServerEnv
initVersionsFeature env@ServerEnv{serverStateDir} = do
preferredState <- preferredStateComponent False serverStateDir
deprecatedHook <- newHook
+ updatePreferredHook <- newHook
return $ \core upload tags user -> do
let feature = versionsFeature env
core upload tags user
preferredState deprecatedHook
+ updatePreferredHook
return feature
preferredStateComponent :: Bool -> FilePath -> IO (StateComponent AcidState PreferredVersions)
@@ -130,6 +133,7 @@ versionsFeature :: ServerEnv
-> UserFeature
-> StateComponent AcidState PreferredVersions
-> Hook (PackageName, Maybe [PackageName]) ()
+ -> Hook (PackageName, PreferredInfo) ()
-> VersionsFeature
versionsFeature ServerEnv{ serverVerbosity = verbosity }
CoreFeature{..}
@@ -138,6 +142,7 @@ versionsFeature ServerEnv{ serverVerbosity = verbosity }
UserFeature{ guardAuthorised_ }
preferredState
deprecatedHook
+ updatePreferredHook
= VersionsFeature{..}
where
versionsFeatureInterface = (emptyHackageFeature "versions") {
@@ -315,6 +320,7 @@ versionsFeature ServerEnv{ serverVerbosity = verbosity }
(prefs, deprs) <- lookPrefRangeDeprecatedVersions pkgs
prefinfo <- updateState preferredState (SetPreferredInfo pkgname prefs deprs)
+ runHook_ updatePreferredHook (pkgname, prefinfo { deprecatedVersions = deprs }) -- It seems they are not set
updateIndexPackagePreferredVersions pkgname prefinfo
where
lookPrefRangeDeprecatedVersions pkgs = do
From eb00f2fc0780deaedd80a1b7b1f635e3049fa323 Mon Sep 17 00:00:00 2001
From: Andrea Bedini
Date: Wed, 29 Nov 2023 22:19:41 +0800
Subject: [PATCH 67/71] Add hashes to the list of revisions in JSON format
The list of metadata revisions for a package is available at
the url /package/:package/revisions/
When rendered in HTML this list includes the revision number, timestamp,
uploader, and sha256; but when rendered in JSON the sha256 is missing.
This change adds revisions sha256 to the JSON rendering.
---
src/Distribution/Server/Features/Core.hs | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/Distribution/Server/Features/Core.hs b/src/Distribution/Server/Features/Core.hs
index 6aec793ab..91fc83f62 100644
--- a/src/Distribution/Server/Features/Core.hs
+++ b/src/Distribution/Server/Features/Core.hs
@@ -24,7 +24,7 @@ module Distribution.Server.Features.Core (
-- stdlib
import qualified Codec.Compression.GZip as GZip
-import Data.Aeson (Value (..))
+import Data.Aeson (Value (..), toJSON)
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import Data.ByteString.Lazy (ByteString)
@@ -40,6 +40,7 @@ import Distribution.Server.Prelude
import Distribution.Server.Features.Core.Backup
import Distribution.Server.Features.Core.State
import Distribution.Server.Features.Security.Migration
+import Distribution.Server.Features.Security.SHA256 (sha256)
import Distribution.Server.Features.Users
import Distribution.Server.Framework
import qualified Distribution.Server.Framework.BlobStorage as BlobStorage
@@ -728,12 +729,15 @@ coreFeature ServerEnv{serverBlobStore = store} UserFeature{..}
pkginfo <- packageInPath dpath >>= lookupPackageId
users <- queryGetUserDb
let revisions = pkgMetadataRevisions pkginfo
- revisionToObj rev (_, (utime, uid)) =
- let uname = userIdToName users uid in
+ revisionToObj rev (cabalFileText, (utime, uid)) =
+ let uname = userIdToName users uid
+ hash = sha256 (cabalFileByteString cabalFileText)
+ in
Object $ KeyMap.fromList
[ (Key.fromString "number", Number (fromIntegral rev))
, (Key.fromString "user", String (Text.pack (display uname)))
, (Key.fromString "time", String (Text.pack (formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%SZ" utime)))
+ , (Key.fromString "sha256", toJSON hash)
]
revisionsJson = Array $ Vec.imap revisionToObj revisions
return (toResponse revisionsJson)
From f763229b614e479fba0b3678ca1b8a95f9334a1d Mon Sep 17 00:00:00 2001
From: gbaz
Date: Mon, 4 Dec 2023 18:10:43 -0500
Subject: [PATCH 68/71] Update Vouch.hs
---
src/Distribution/Server/Features/Vouch.hs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Distribution/Server/Features/Vouch.hs b/src/Distribution/Server/Features/Vouch.hs
index 39795f0b5..ba08ecc4a 100644
--- a/src/Distribution/Server/Features/Vouch.hs
+++ b/src/Distribution/Server/Features/Vouch.hs
@@ -218,6 +218,7 @@ initVouchFeature ServerEnv{serverStateDir, serverTemplatesDir, serverTemplatesMo
liftIO $ Group.addUserToGroup uploadersGroup vouchee
pure . toResponse $ vouchTemplate
[ "msg" $= "Added endorsement. User is now an uploader!"
+ , "requiredNumber" $= show requiredCountOfVouches
, param
]
AddVouchIncomplete stillRequired ->
From 36ab220e84bb86ff97f5d12e93a67713f5ddc5fd Mon Sep 17 00:00:00 2001
From: gbaz
Date: Mon, 4 Dec 2023 18:12:17 -0500
Subject: [PATCH 69/71] Update vouch.html.st
---
datafiles/templates/Html/vouch.html.st | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/datafiles/templates/Html/vouch.html.st b/datafiles/templates/Html/vouch.html.st
index 36d0e7945..cb34cd855 100644
--- a/datafiles/templates/Html/vouch.html.st
+++ b/datafiles/templates/Html/vouch.html.st
@@ -18,9 +18,7 @@ $hackagePageHeader()$
Endorsing cannot be undone! When the user has $requiredNumber$ endorsements, the user
-can upload packages. Note that users are, to a certain degree, held accountable
-for the actions of the users they endorse. Only endorse people you know.
-
+will be added to the uploaders group, and allowed to upload packages. Only endorse people who you trust to upload packages responsibly.
From fcc5c5117b6b4f7faa508dbf826a872442e55b53 Mon Sep 17 00:00:00 2001
From: Gershom Bazerman
Date: Mon, 4 Dec 2023 18:30:49 -0500
Subject: [PATCH 70/71] fix tasty dep
---
hackage-server.cabal | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hackage-server.cabal b/hackage-server.cabal
index a4f4f92c5..6bea36a21 100644
--- a/hackage-server.cabal
+++ b/hackage-server.cabal
@@ -580,7 +580,7 @@ test-suite VouchTest
type: exitcode-stdio-1.0
main-is: VouchTest.hs
build-depends:
- , tasty ^>= 1.4
+ , tasty ^>= 1.5
, tasty-hunit ^>= 0.10
test-suite ReverseDependenciesTest
From 44a3b1c0a2e56dce5fbbdd0e318cc9af6fced6d9 Mon Sep 17 00:00:00 2001
From: Gershom Bazerman
Date: Mon, 4 Dec 2023 19:40:31 -0500
Subject: [PATCH 71/71] update central-server text for upload ensorsement
---
.../UserSignupReset/SignupConfirm.html.st | 5 +++--
.../SignupConfirmation.email.st | 18 ++++++++++++-----
.../UserSignupReset/SignupEmailSent.html.st | 5 ++++-
.../UserSignupReset/SignupRequest.html.st | 20 ++++++++++++-------
.../Server/Features/UserSignup.hs | 1 +
5 files changed, 34 insertions(+), 15 deletions(-)
diff --git a/datafiles/templates/UserSignupReset/SignupConfirm.html.st b/datafiles/templates/UserSignupReset/SignupConfirm.html.st
index bc3fff397..d3a4d1a52 100644
--- a/datafiles/templates/UserSignupReset/SignupConfirm.html.st
+++ b/datafiles/templates/UserSignupReset/SignupConfirm.html.st
@@ -13,7 +13,9 @@ $hackagePageHeader()$
Email confirmation done!
-
Now you can set your password and create the account.
+
Now you can set your password and use the account. If you wish to
+ upload a package, please request to be added to the uploader group
+ by one of the mechanisms described in the email you were sent.