Skip to content

Commit e4af89d

Browse files
committed
Remove divison by zero in query params
1 parent 149d884 commit e4af89d

File tree

4 files changed

+110
-78
lines changed

4 files changed

+110
-78
lines changed

datafiles/static/hackage.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1066,8 +1066,10 @@ a.deprecated[href]:visited {
10661066
border-radius: 2px;
10671067
}
10681068

1069+
/* Targeting of href for a tags higher up makes it more cumbersome to override */
10691070
.paginator a[href]:link, .paginator a[href]:visited {
10701071
color: #333;
1072+
text-decoration: none;
10711073
}
10721074

10731075
.paginator .current,
@@ -1077,7 +1079,7 @@ a.deprecated[href]:visited {
10771079
background: linear-gradient(to bottom, #fff 0%, #dcdcdc 100%);
10781080
}
10791081

1080-
.paginator a:hover {
1082+
.paginator a[href]:hover {
10811083
color: white;
10821084
border: 1px solid #111;
10831085
background: linear-gradient(to bottom, #585858 0%, #111 100%);

src/Distribution/Server/Features/Html.hs

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import qualified Distribution.Server.Pages.Recent as Pages
7979
import qualified Distribution.Server.Util.Paging as Paging
8080
import Distribution.Server.Features.RecentPackages (RecentPackagesFeature (RecentPackagesFeature, getRecentRevisions, getRecentPackages))
8181
import Data.Time (getCurrentTime)
82+
import Text.Read (readMaybe)
8283

8384

8485
-- TODO: move more of the below to Distribution.Server.Pages.*, it's getting
@@ -555,72 +556,60 @@ mkHtmlCore ServerEnv{serverBaseURI, serverBlobStore}
555556
}
556557
]
557558

559+
readParamWithDefaultAnyValid :: (Read a, HasRqData m, Monad m, Functor m, Alternative m) =>
560+
a -> (a -> Bool) -> String -> m a
561+
readParamWithDefaultAnyValid n f queryParam = do
562+
m <- optional (look queryParam)
563+
let parsed = m >>= readMaybe >>= (\x -> if f x then Just x else Nothing)
564+
565+
return $ fromMaybe n parsed
566+
567+
558568
serveRecentPage :: DynamicPath -> ServerPartE Response
559569
serveRecentPage _ = do
560-
-- TODO
561-
-- [x] Change paginate to use custom object to prohibit messing things up
562-
-- [x] Extract out revision to HTML feature
563-
-- [x] Show different pagination options in HTML
564-
-- [x] Remove old HTML from RecentPackages Feature
565-
-- [/] Convert over RSS to use pagination with query params
566-
-- Blocked due to not being able to query for query params
567-
-- Maybe change RecentPackages to only return back cached packages then inject to HTML Feature
568-
-- [/] Convert paginator HTML to look and act like search paginator
569-
-- [] Check that Paging is done correctly
570-
-- [] Make disabled state work on buttons
571-
-- [] Add in Form to set pageSize
572-
573570
recentPackages <- getRecentPackages
574571
users <- queryGetUserDb
575-
page <- readWithDefault 1 <$> optional (look "page")
576-
pageSize <- readWithDefault 25 <$> optional (look "pageSize")
577-
572+
page <- readParamWithDefaultAnyValid 1 (>= 1) "page"
573+
pageSize <- readParamWithDefaultAnyValid 20 (>= 1) "pageSize"
574+
578575
let conf = Paging.createConf page pageSize recentPackages
579576

580577
return . toResponse $ Pages.recentPage conf users recentPackages
581-
where
582-
readWithDefault n = fromMaybe n . fmap (read :: String -> Int)
583578

584579
serveRecentRSS :: DynamicPath -> ServerPartE Response
585580
serveRecentRSS _ = do
586581
recentPackages <- getRecentPackages
587582
users <- queryGetUserDb
588-
page <- readWithDefault 1 <$> optional (look "page")
589-
pageSize <- readWithDefault 25 <$> optional (look "pageSize")
583+
page <- readParamWithDefaultAnyValid 1 (>= 1) "page"
584+
pageSize <- readParamWithDefaultAnyValid 20 (>= 1) "pageSize"
590585
now <- liftIO getCurrentTime
591586

592587
let conf = Paging.createConf page pageSize recentPackages
593588

594589
return . toResponse $ Pages.recentFeed conf users serverBaseURI now recentPackages
595-
where
596-
readWithDefault n = fromMaybe n . fmap (read :: String -> Int)
597590

598591
serveRevisionPage :: DynamicPath -> ServerPartE Response
599592
serveRevisionPage _ = do
600593
revisions <- getRecentRevisions
601594
users <- queryGetUserDb
602-
page <- readWithDefault 1 <$> optional (look "page")
603-
pageSize <- readWithDefault 50 <$> optional (look "pageSize")
595+
page <- readParamWithDefaultAnyValid 1 (>= 1) "page"
596+
pageSize <- readParamWithDefaultAnyValid 40 (>= 1) "pageSize"
604597

605598
let conf = Paging.createConf page pageSize revisions
606599

607-
608600
return . toResponse $ Pages.revisionsPage conf users revisions
609-
where readWithDefault n = fromMaybe n . fmap (read :: String -> Int)
610601

611602
serveRevisionRSS :: DynamicPath -> ServerPartE Response
612603
serveRevisionRSS _ = do
613604
revisions <- getRecentRevisions
614605
users <- queryGetUserDb
615-
page <- readWithDefault 1 <$> optional (look "page")
616-
pageSize <- readWithDefault 40 <$> optional (look "pageSize")
606+
page <- readParamWithDefaultAnyValid 1 (>= 1) "page"
607+
pageSize <- readParamWithDefaultAnyValid 40 (>= 1) "pageSize"
617608
now <- liftIO getCurrentTime
618609

619610
let conf = Paging.createConf page pageSize revisions
620611

621-
622612
return . toResponse $ Pages.recentRevisionsFeed conf users serverBaseURI now revisions
623-
where readWithDefault n = fromMaybe n . fmap (read :: String -> Int)
624613

625614
serveBrowsePage :: DynamicPath -> ServerPartE Response
626615
serveBrowsePage _dpath = do

src/Distribution/Server/Pages/Recent.hs

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,24 @@ import Distribution.Text
2424
import Distribution.Utils.ShortText (fromShortText)
2525

2626
import qualified Text.XHtml.Strict as XHtml
27-
import Text.XHtml
28-
( Html, URL, (<<), (!) )
27+
import Text.XHtml ( Html, URL, (<<), (!) )
2928
import qualified Text.RSS as RSS
3029
import Text.RSS ( RSS(RSS) )
3130
import Network.URI ( URI(..), uriToString )
3231
import Data.Time.Clock ( UTCTime )
3332
import Data.Time.Format ( defaultTimeLocale, formatTime )
34-
import Data.Maybe ( listToMaybe)
35-
import Distribution.Server.Util.Paging (PaginatedConf (..), paginate, allPagedURLS, hasNext, hasPrev, pageIndexRange)
36-
import qualified Text.XHtml as Xhtml
33+
import Data.Maybe ( listToMaybe, fromMaybe)
34+
import Distribution.Server.Util.Paging (PaginatedConfiguration(..), hasNext,
35+
hasPrev, nextURL, pageIndexRange, paginate, prevURL, toURL, allPagedURLs)
3736

3837
-- | Takes a list of package info, in reverse order by timestamp.
3938

40-
recentPage :: PaginatedConf -> Users -> [PkgInfo] -> Html
39+
recentPage :: PaginatedConfiguration -> Users -> [PkgInfo] -> Html
4140
recentPage conf users pkgs =
4241
let log_rows = makeRow users <$> paginate conf pkgs
4342
docBody =
4443
[ XHtml.h2 << "Recent additions",
44+
pageSizeForm recentURL,
4545
XHtml.table ! [XHtml.align "center"] << log_rows,
4646
paginator conf recentURL,
4747
XHtml.anchor ! [XHtml.href recentRevisionsURL] << XHtml.toHtml "Recent revisions"
@@ -51,26 +51,36 @@ recentPage conf users pkgs =
5151
! [ XHtml.rel "alternate",
5252
XHtml.thetype "application/rss+xml",
5353
XHtml.title "Hackage RSS Feed",
54-
XHtml.href rssFeedURL
54+
XHtml.href $ toURL rssFeedURL conf
5555
]
5656
<< XHtml.noHtml
5757
in hackagePageWithHead [rss_link] "recent additions" docBody
5858

59-
paginator :: PaginatedConf -> URL -> Html
60-
paginator pc@(PaginatedConf currPage _ totalAmount) baseUrl =
59+
60+
pageSizeForm :: URL -> Html
61+
pageSizeForm base =
62+
let pageSizeLabel = XHtml.label ! [XHtml.thefor "pageSize"] << "Page Size: "
63+
pageSizeInput = XHtml.input ! [XHtml.thetype "number", XHtml.name "pageSize", XHtml.strAttr "min" "0"]
64+
submitButton = XHtml.button ! [XHtml.thetype "submit"] << "Submit"
65+
theForm = XHtml.form ! [XHtml.action base, XHtml.method "GET"]
66+
in theForm << (pageSizeLabel <> pageSizeInput <> submitButton)
67+
68+
69+
paginator :: PaginatedConfiguration -> URL -> Html
70+
paginator pc@PaginatedConfiguration{currPage,totalAmount} baseUrl =
6171
let
6272
(start, end) = pageIndexRange pc
6373
infoText = "Showing " ++ show start ++ " to " ++ show end ++ " of " ++ show totalAmount ++ " entries"
6474
info = XHtml.thediv << infoText
6575

6676
next = if hasNext pc
67-
then XHtml.anchor ! [XHtml.href (paginateURLNext pc baseUrl)] << "Next"
68-
else Xhtml.noHtml
77+
then XHtml.anchor ! [XHtml.href (fromMaybe "" (nextURL baseUrl pc))] << "Next"
78+
else XHtml.noHtml
6979
prev = if hasPrev pc
70-
then XHtml.anchor ! [XHtml.href (paginateURLPrev pc baseUrl) ] << "Previous"
80+
then XHtml.anchor ! [XHtml.href (fromMaybe "" (prevURL baseUrl pc)) ] << "Previous"
7181
else XHtml.noHtml
7282

73-
pagedURLS = zip [1..] (allPagedURLS baseUrl pc)
83+
pagedURLS = zip [1..] (allPagedURLs baseUrl pc)
7484
pagedLinks = (\(x,y) -> XHtml.anchor ! [XHtml.href y,
7585
if currPage == x then XHtml.theclass "current" else noAttr ] << show x) <$> pagedURLS
7686

@@ -84,14 +94,14 @@ paginator pc@(PaginatedConf currPage _ totalAmount) baseUrl =
8494
noAttr :: XHtml.HtmlAttr
8595
noAttr = XHtml.theclass ""
8696

87-
-- Worst code I've written so far
88-
reducePagedLinks :: PaginatedConf -> [Html] -> Html
89-
reducePagedLinks PaginatedConf{currPage} xs
97+
-- | Generates a list of links of the current possible paging links, recreates the functionality of the paging links on the search page
98+
reducePagedLinks :: PaginatedConfiguration -> [Html] -> Html
99+
reducePagedLinks PaginatedConfiguration{currPage} xs
90100
| currPage >= (length xs - 3) = mconcat . keepLastPages .fillFirst $ xs -- Beginning ellipses
91101
| length xs > 5 && currPage < 5 = mconcat . keepFirstPages . fillLast $ xs -- Ending ellipses
92102
| length xs <= 5 = mconcat xs -- Do Nothing
93103
| otherwise = mconcat . keepMiddlePages . fillLast . fillFirst $ xs -- Begin and End ellipses
94-
where filler = XHtml.anchor << "..."
104+
where filler = XHtml.thespan << "..."
95105
fillFirst x = insertAt 1 filler x
96106
fillLast x = insertAt (pred . length $ x) filler x
97107
keepFirstPages x = case splitAt (length x - 2) x of (hts, hts') -> take 5 hts ++ hts'
@@ -106,16 +116,12 @@ insertAt n a x = case splitAt n x of (hts, hts') -> hts ++ [a] ++ hts'
106116
takeLast :: Int -> [a] -> [a]
107117
takeLast n = reverse . take n . reverse
108118

109-
-- Should actually check if next exists
110-
paginateURLNext,paginateURLPrev :: PaginatedConf -> URL -> URL
111-
paginateURLNext (PaginatedConf cp _ _) url = url <> "?page=" ++ (show . succ) cp
112-
paginateURLPrev (PaginatedConf cp _ _) url = url <> "?page=" ++ (show . pred) cp
113-
114-
revisionsPage :: PaginatedConf -> Users -> [PkgInfo] -> Html
119+
revisionsPage :: PaginatedConfiguration -> Users -> [PkgInfo] -> Html
115120
revisionsPage conf users pkgs =
116121
let log_rows = map (makeRevisionRow users) (paginate conf pkgs)
117122
docBody =
118123
[ XHtml.h2 << "Recent cabal metadata revisions",
124+
pageSizeForm recentRevisionsURL,
119125
XHtml.table ! [XHtml.align "center"] << log_rows,
120126
paginator conf recentRevisionsURL
121127
]
@@ -124,7 +130,7 @@ revisionsPage conf users pkgs =
124130
! [ XHtml.rel "alternate",
125131
XHtml.thetype "application/rss+xml",
126132
XHtml.title "Hackage Revisions RSS Feed",
127-
XHtml.href revisionsRssFeedURL
133+
XHtml.href $ toURL revisionsRssFeedURL conf
128134
]
129135
<< XHtml.noHtml
130136
in hackagePageWithHead [rss_link] "recent revisions" docBody
@@ -190,7 +196,7 @@ recentRevisionsURL :: URL
190196
recentRevisionsURL = "/packages/recent/revisions.html"
191197

192198

193-
recentFeed :: PaginatedConf -> Users -> URI -> UTCTime -> [PkgInfo] -> RSS
199+
recentFeed :: PaginatedConfiguration -> Users -> URI -> UTCTime -> [PkgInfo] -> RSS
194200
recentFeed conf users hostURI now pkgs = RSS
195201
"Recent additions"
196202
(hostURI { uriPath = recentAdditionsURL})
@@ -203,7 +209,7 @@ recentFeed conf users hostURI now pkgs = RSS
203209
pkgList = paginate conf pkgs
204210
updated = maybe now (fst . pkgOriginalUploadInfo) (listToMaybe pkgList)
205211

206-
recentRevisionsFeed :: PaginatedConf -> Users -> URI -> UTCTime -> [PkgInfo] -> RSS
212+
recentRevisionsFeed :: PaginatedConfiguration -> Users -> URI -> UTCTime -> [PkgInfo] -> RSS
207213
recentRevisionsFeed conf users hostURI now pkgs = RSS
208214
"Recent revisions"
209215
(hostURI { uriPath = recentRevisionsURL})
Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,80 @@
11
{-# LANGUAGE NamedFieldPuns #-}
2-
module Distribution.Server.Util.Paging where
2+
3+
module Distribution.Server.Util.Paging
4+
(
5+
totalPages,
6+
createConf,
7+
hasNext,
8+
hasPrev,
9+
paginate,
10+
pageIndexStart,
11+
pageIndexRange,
12+
pageIndexEnd,
13+
allPagedURLs,
14+
nextURL,
15+
prevURL,
16+
toURL,
17+
PaginatedConfiguration(..),
18+
)
19+
where
320
import Text.XHtml (URL)
21+
import Data.List (genericTake, genericDrop, genericLength)
422

5-
-- This could be designed better, perhaps turning PaginatedConf into a function that returns the paging info
23+
-- This could be better designed, perhaps turning PaginatedConfiguration into a function that returns the paging info
624
-- and the paged data
7-
data PaginatedConf = PaginatedConf
25+
data PaginatedConfiguration = PaginatedConfiguration
826
{ currPage :: Int,
927
pageSize :: Int,
1028
totalAmount :: Int
1129
}
1230

13-
totalPages :: PaginatedConf -> Int
14-
totalPages PaginatedConf {pageSize, totalAmount} =
31+
-- Assumes pageSize isn't 0, not the best design
32+
totalPages :: PaginatedConfiguration -> Int
33+
totalPages PaginatedConfiguration {pageSize, totalAmount} =
1534
case totalAmount `quotRem` pageSize of
1635
(x,r)
1736
| r == 0 -> x
1837
| otherwise -> succ x
1938

20-
createConf :: Int -> Int -> [a] -> PaginatedConf
21-
createConf page pageSize xs = PaginatedConf page pageSize (length xs)
39+
createConf :: Int -> Int -> [a] -> PaginatedConfiguration
40+
createConf page pageSize xs = PaginatedConfiguration page pageSize (genericLength xs)
2241

23-
paginate :: PaginatedConf -> [a] -> [a]
24-
paginate PaginatedConf {currPage, pageSize} = take pageSize . drop toDrop
42+
paginate :: PaginatedConfiguration -> [a] -> [a]
43+
paginate PaginatedConfiguration {currPage, pageSize} = genericTake pageSize . genericDrop toDrop
2544
where
26-
toDrop = pageSize * pred currPage
45+
toDrop = pageSize * pred currPage
2746

28-
hasNext,hasPrev :: PaginatedConf -> Bool
29-
hasNext pc@PaginatedConf{currPage} = currPage < totalPages pc
30-
hasPrev PaginatedConf {currPage} = currPage > 1
47+
hasNext,hasPrev :: PaginatedConfiguration -> Bool
48+
hasNext pc@PaginatedConfiguration{currPage} = currPage < totalPages pc
49+
hasPrev PaginatedConfiguration {currPage} = currPage > 1
3150

3251
-- | Returns the index positions that the current PaginatedConfiguration would show (Starts at 1)
33-
pageIndexRange :: PaginatedConf -> (Int, Int)
34-
pageIndexRange conf@PaginatedConf{currPage, pageSize, totalAmount} = (start, end)
35-
where start = succ $ (currPage * pageSize) - pageSize
36-
end = if currPage == totalPages conf then totalAmount else currPage * pageSize
52+
pageIndexRange :: PaginatedConfiguration -> (Int, Int)
53+
pageIndexRange conf@PaginatedConfiguration{currPage, pageSize, totalAmount} = (start, end)
54+
where start = succ $ currPage * pageSize - pageSize
55+
end = if currPage == totalPages conf then totalAmount else currPage * pageSize
3756

38-
pageIndexStart, pageIndexEnd :: PaginatedConf -> Int
57+
pageIndexStart, pageIndexEnd :: PaginatedConfiguration -> Int
3958
pageIndexStart = fst . pageIndexRange
4059
pageIndexEnd = snd . pageIndexRange
4160

4261

43-
allPagedURLS :: URL -> PaginatedConf -> [URL]
44-
allPagedURLS base pc = (\p -> base ++ "?page=" ++ show p ++ "&pageSize=" ++ (show . pageSize) pc)
45-
<$> [1..totalPages pc]
62+
allPagedURLs :: URL -> PaginatedConfiguration -> [URL]
63+
allPagedURLs base pc = toURL base . (\x -> pc{currPage=x}) <$> [1..totalPages pc]
64+
65+
66+
-- | Converts the PaginatedConfiguration to a URL, Assumes no query params in url
67+
toURL :: URL -> PaginatedConfiguration -> URL
68+
toURL base PaginatedConfiguration{currPage, pageSize} = base ++ "?page=" ++ show currPage ++ "&pageSize=" ++ show pageSize
69+
70+
nextURL :: URL -> PaginatedConfiguration -> Maybe URL
71+
nextURL base conf@PaginatedConfiguration {currPage}
72+
| page > totalPages conf = Nothing
73+
| otherwise = Just $ toURL base conf{currPage = page}
74+
where page = succ currPage
75+
76+
prevURL :: URL -> PaginatedConfiguration -> Maybe URL
77+
prevURL base conf@PaginatedConfiguration {currPage}
78+
| page < 1 = Nothing
79+
| otherwise = Just $ toURL base conf{currPage=page}
80+
where page = pred currPage

0 commit comments

Comments
 (0)