internal/worker: Add since form param to /poll.

If the worker's latest timestamp is t, it's hard to get it to back and re-poll
the index for older work. This CL adds a very simple utility that lets you do
so.

Change-Id: If12d385e245b6e64880dd7aa704b749603c7fecc
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/685455
kokoro-CI: kokoro <noreply+kokoro@google.com>
Auto-Submit: Jonathan Amsterdam <jba@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/worker/server.go b/internal/worker/server.go
index 903d0fe..1378318 100644
--- a/internal/worker/server.go
+++ b/internal/worker/server.go
@@ -432,10 +432,14 @@
 	defer derrors.Wrap(&err, "handlePollIndex(%q)", r.URL.Path)
 	ctx := r.Context()
 	limit := parseIntParam(r, "limit", 10)
-	since, err := s.db.LatestIndexTimestamp(ctx)
-	if err != nil {
-		return err
+	since, ok := parseTimeParam(r, "since")
+	if !ok {
+		since, err = s.db.LatestIndexTimestamp(ctx)
+		if err != nil {
+			return err
+		}
 	}
+	log.Infof(ctx, "fetching %d versions since %v from the index", limit, since)
 	modules, err := s.indexClient.GetVersions(ctx, since, limit)
 	if err != nil {
 		return err
@@ -885,12 +889,28 @@
 	}
 	val, err := strconv.Atoi(param)
 	if err != nil {
-		log.Errorf(r.Context(), "parsing query parameter %q: %v", name, err)
+		log.Errorf(r.Context(), "parsing query parameter %q as an int: %v", name, err)
 		return defaultValue
 	}
 	return val
 }
 
+// parseTimeParam parses the named query parameter as a Time, using RFC RFC3339
+// layout. If the parameter is missing or there is a parse error, false is
+// returned.
+func parseTimeParam(r *http.Request, name string) (time.Time, bool) {
+	param := r.FormValue(name)
+	if param == "" {
+		return time.Time{}, false
+	}
+	t, err := time.Parse(time.RFC3339, param)
+	if err != nil {
+		log.Errorf(r.Context(), "parsing query parameter %q as a time.Time: %v", name, err)
+		return time.Time{}, false
+	}
+	return t, true
+}
+
 type serverError struct {
 	status int   // HTTP status code
 	err    error // wrapped error
diff --git a/static/worker/index.tmpl b/static/worker/index.tmpl
index 2d3f775..4321775 100644
--- a/static/worker/index.tmpl
+++ b/static/worker/index.tmpl
@@ -42,6 +42,7 @@
       <button title="Poll the module index for up to 2000 new versions."
         onclick="submitForm('pollForm', false); return false">Poll Module Index</button>
       <input type="number" name="limit" value="10"></input>
+      <input type="text" name="since" value="" placeholder="2006-01-02T15:04:05Z07:00">
       <output name="result"></output>
     </form>
     <form action="/enqueue" method="post" name="enqueueForm">