Repository: google/exposure-notifications-server
Pull request #1352 (2021-02-05 21:35:11)

Rename publishHandler -> publishServer
This is just a variable/function rename. I didn't want to mix this up with any refactors.

**Release Note**

<!-- Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". -->

```release-note
NONE
```

google-oss-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/1352#" title="Author self-approved">sethvargo</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/main/OWNERS)~~ [sethvargo]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
google-oss-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/1352#pullrequestreview-584724093" title="Approved">mikehelmick</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/1352#" title="Author self-approved">sethvargo</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/main/OWNERS)~~ [mikehelmick,sethvargo]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
google-oss-robot: @sethvargo: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-en-server-release-unit | 13bb7b04cab0525fdc72a2310601ee4a5740e6ba | [link](https://oss-prow.knative.dev/view/gs/oss-prow/pr-logs/pull/google_exposure-notifications-server/1352/pull-en-server-release-unit/1357804999844827136) | `/test pull-en-server-release-unit`

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sethvargo: /retest

Patch:
diff --git a/cmd/exposure/main.go b/cmd/exposure/main.go
index b6df597..24fc9ea 100644
--- a/cmd/exposure/main.go
+++ b/cmd/exposure/main.go
@@ -68,23 +68,24 @@ func realMain(ctx context.Context) error {
 
 	r := mux.NewRouter()
 	r.Handle("/health", server.HandleHealthz())
-	handler, err := publish.NewHandler(ctx, &config, env)
+
+	publishServer, err := publish.NewServer(ctx, &config, env)
 	if err != nil {
 		return fmt.Errorf("publish.NewHandler: %w", err)
 	}
 
 	// Handle v1 API - this route has to come before the v1alpha route because of
 	// path matching.
-	r.Handle("/v1/publish", handler.Handle())
+	r.Handle("/v1/publish", publishServer.Handle())
 	r.Handle("/v1/publish/", http.NotFoundHandler())
 
 	// Handle stats retrieval API
-	r.Handle("/v1/stats", handler.HandleStats())
+	r.Handle("/v1/stats", publishServer.HandleStats())
 	r.Handle("/v1/stats/", http.NotFoundHandler())
 
 	// Serving of v1alpha1 is on by default, but can be disabled through env var.
 	if config.EnableV1Alpha1API {
-		r.Handle("/", handler.HandleV1Alpha1())
+		r.Handle("/", publishServer.HandleV1Alpha1())
 	}
 
 	srv, err := server.New(config.Port)
diff --git a/internal/integration/helpers.go b/internal/integration/helpers.go
index 322ce65..fe63b35 100644
--- a/internal/integration/helpers.go
+++ b/internal/integration/helpers.go
@@ -231,11 +231,11 @@ func NewTestServer(tb testing.TB) (*serverenv.ServerEnv, *Client) {
 	publishConfig.ReleaseSameDayKeys = true
 	publishConfig.RevisionKeyCacheDuration = time.Second
 
-	publishHandler, err := publish.NewHandler(ctx, &publishConfig, env)
+	publishServer, err := publish.NewServer(ctx, &publishConfig, env)
 	if err != nil {
 		tb.Fatal(err)
 	}
-	r.Handle("/publish", publishHandler.Handle())
+	r.Handle("/publish", publishServer.Handle())
 
 	srv, err := server.New("")
 	if err != nil {
diff --git a/internal/publish/publish.go b/internal/publish/publish.go
index 76a55af..e1131b7 100644
--- a/internal/publish/publish.go
+++ b/internal/publish/publish.go
@@ -47,11 +47,29 @@ const (
 	HeaderAPIVersion = "x-api-version"
 )
 
-// NewHandler creates common API handler for the publish API.
-// This supports all current versions of the API and each defines it's own entry point via
-// an http.HandlerFunc
-func NewHandler(ctx context.Context, config *Config, env *serverenv.ServerEnv) (*PublishHandler, error) {
-	logger := logging.FromContext(ctx)
+type Server struct {
+	config                *Config
+	serverenv             *serverenv.ServerEnv
+	transformer           *model.Transformer
+	database              *database.PublishDB
+	tokenManager          *revision.TokenManager
+	tracker               *chaff.Tracker
+	tokenAAD              []byte
+	authorizedAppProvider authorizedapp.Provider
+	verifier              *verification.Verifier
+}
+
+func NewServer(ctx context.Context, cfg *Config, env *serverenv.ServerEnv) (*Server, error) {
+	logger := logging.FromContext(ctx).Named("publish")
+
+	logger.Debugw("creating server",
+		"max_keys_on_publish", cfg.MaxKeysOnPublish,
+		"max_same_start_interval_keys", cfg.MaxSameStartIntervalKeys,
+		"max_interval_age", cfg.MaxIntervalAge,
+		"truncate_window", cfg.TruncateWindow)
+	if cfg.DebugReleaseSameDayKeys() {
+		logger.Warnw("SERVER IS IN DEBUG MODE - KEYS MAY BE RELEASED EARLY!")
+	}
 
 	if env.Database() == nil {
 		return nil, fmt.Errorf("missing database in server environment")
@@ -60,53 +78,46 @@ func NewHandler(ctx context.Context, config *Config, env *serverenv.ServerEnv) (
 		return nil, fmt.Errorf("missing AuthorizedApp provider in server environment")
 	}
 
-	transformer, err := model.NewTransformer(config)
+	transformer, err := model.NewTransformer(cfg)
 	if err != nil {
 		return nil, fmt.Errorf("model.NewTransformer: %w", err)
 	}
-	logger.Infof("max keys per upload: %v", config.MaxKeysOnPublish)
-	logger.Infof("max same start interval keys: %v", config.MaxSameStartIntervalKeys)
-	logger.Infof("max interval start age: %v", config.MaxIntervalAge)
-	logger.Infof("truncate window: %v", config.TruncateWindow)
-	if config.DebugReleaseSameDayKeys() {
-		logger.Warnf("SERVER IS IN DEBUG MODE. KEYS MAY BE RELEASED EARLY.")
-	}
 
-	verifier, err := verification.New(verifydb.New(env.Database()), &config.Verification)
+	verifier, err := verification.New(verifydb.New(env.Database()), &cfg.Verification)
 	if err != nil {
 		return nil, fmt.Errorf("verification.New: %w", err)
 	}
 
-	aadBytes := config.RevisionToken.AAD
+	aadBytes := cfg.RevisionToken.AAD
 	if len(aadBytes) == 0 {
 		return nil, fmt.Errorf("must provide Additional Authenticated Data (AAD) for revision token encryption in REVISION_TOKEN_AAD env variable")
 	}
 	revisionKeyConfig := revisiondb.KMSConfig{
-		WrapperKeyID: config.RevisionToken.KeyID,
+		WrapperKeyID: cfg.RevisionToken.KeyID,
 		KeyManager:   env.GetKeyManager(),
 	}
 	revisionDB, err := revisiondb.New(env.Database(), &revisionKeyConfig)
 	if err != nil {
 		return nil, fmt.Errorf("revisiondb.New: %w", err)
 	}
-	tm, err := revision.New(ctx, revisionDB, config.RevisionKeyCacheDuration, config.RevisionToken.MinLength)
+	tm, err := revision.New(ctx, revisionDB, cfg.RevisionKeyCacheDuration, cfg.RevisionToken.MinLength)
 	if err != nil {
 		return nil, fmt.Errorf("revision.New: %w", err)
 	}
 
-	if err := config.Validate(); err != nil {
+	if err := cfg.Validate(); err != nil {
 		return nil, fmt.Errorf("config validation: %w", err)
 	}
 
 	chaffer, err := chaff.NewTracker(chaff.NewJSONResponder(chaffPublishResponse), chaff.DefaultCapacity)
 	if err != nil {
-		return nil, fmt.Errorf("error making chaffer: %v", err)
+		return nil, fmt.Errorf("error making chaffer: %w", err)
 	}
 
-	return &PublishHandler{
+	return &Server{
 		serverenv:             env,
 		transformer:           transformer,
-		config:                config,
+		config:                cfg,
 		database:              database.New(env.Database()),
 		tracker:               chaffer,
 		tokenManager:          tm,
@@ -116,18 +127,6 @@ func NewHandler(ctx context.Context, config *Config, env *serverenv.ServerEnv) (
 	}, nil
 }
 
-type PublishHandler struct {
-	config                *Config
-	serverenv             *serverenv.ServerEnv
-	transformer           *model.Transformer
-	database              *database.PublishDB
-	tokenManager          *revision.TokenManager
-	tracker               *chaff.Tracker
-	tokenAAD              []byte
-	authorizedAppProvider authorizedapp.Provider
-	verifier              *verification.Verifier
-}
-
 type response struct {
 	status      int
 	pubResponse *verifyapi.PublishResponse
@@ -179,7 +178,7 @@ func newVersionBridge(regions []string) *versionBridge {
 
 // process runs the publish business logic over a "v1" version of the publish request
 // and knows how to join in data from previous versions (the provided versionBridge)
-func (h *PublishHandler) process(ctx context.Context, data *verifyapi.Publish, platform string, bridge *versionBridge) *response {
+func (s *Server) process(ctx context.Context, data *verifyapi.Publish, platform string, bridge *versionBridge) *response {
 	ctx, span := trace.StartSpan(ctx, "(*publish.PublishHandler).process")
 	defer span.End()
 
@@ -187,7 +186,7 @@ func (h *PublishHandler) process(ctx context.Context, data *verifyapi.Publish, p
 
 	logger.Info("publish API request")
 
-	appConfig, err := h.authorizedAppProvider.AppConfig(ctx, data.HealthAuthorityID)
+	appConfig, err := s.authorizedAppProvider.AppConfig(ctx, data.HealthAuthorityID)
 	if err != nil {
 		// Config loaded, but app with that name isn't registered. This can also
 		// happen if the app was recently registered but the cache hasn't been
@@ -255,8 +254,8 @@ func (h *PublishHandler) process(ctx context.Context, data *verifyapi.Publish, p
 		regions = appConfig.AllAllowedRegions()
 	}
 	// And - worse case, still no regions and server default set.
-	if len(regions) == 0 && h.config.DefaultRegion != "" {
-		regions = append(regions, h.config.DefaultRegion)
+	if len(regions) == 0 && s.config.DefaultRegion != "" {
+		regions = append(regions, s.config.DefaultRegion)
 	}
 
 	// Verify that there is at least one region set by API call or by one of the
@@ -279,14 +278,14 @@ func (h *PublishHandler) process(ctx context.Context, data *verifyapi.Publish, p
 	}
 
 	// Perform health authority certificate verification.
-	verifiedClaims, err := h.verifier.VerifyDiagnosisCertificate(ctx, appConfig, data)
+	verifiedClaims, err := s.verifier.VerifyDiagnosisCertificate(ctx, appConfig, data)
 	if err != nil {
 		if appConfig.BypassHealthAuthorityVerification {
 			logger.Warnf("bypassing health authority certificate verification health authority: %v", appConfig.AppPackageName)
 			stats.Record(ctx, mVerificationBypassed.M(1))
 		} else {
 			message := fmt.Sprintf("unable to validate diagnosis verification: %v", err)
-			if h.config.DebugLogBadCertificates {
+			if s.config.DebugLogBadCertificates {
 				logger.Errorw(message, "error", err, "jwt", data.VerificationPayload)
 			} else {
 				logger.Errorw(message, "error", err)
@@ -313,7 +312,7 @@ func (h *PublishHandler) process(ctx context.Context, data *verifyapi.Publish, p
 		if err != nil {
 			logger.Errorf("unable to decode revision token, proceeding without: %v", err)
 		} else {
-			token, err = h.tokenManager.UnmarshalRevisionToken(ctx, encryptedToken, h.tokenAAD)
+			token, err = s.tokenManager.UnmarshalRevisionToken(ctx, encryptedToken, s.tokenAAD)
 			if err != nil {
 				logger.Errorf("unable to unmarshall / decrypt revision token. Treating as if none was provided: %v", err)
 				token = nil // just in case.
@@ -323,7 +322,7 @@ func (h *PublishHandler) process(ctx context.Context, data *verifyapi.Publish, p
 	}
 
 	batchTime := time.Now()
-	result, transformError := h.transformer.TransformPublish(ctx, data, regions, verifiedClaims, batchTime)
+	result, transformError := s.transformer.TransformPublish(ctx, data, regions, verifiedClaims, batchTime)
 	// Break apart the result object for easier usage below.
 	exposures := result.Exposures
 	publishInfo := result.PublishInfo
@@ -353,13 +352,13 @@ func (h *PublishHandler) process(ctx context.Context, data *verifyapi.Publish, p
 		publishInfo.Platform = platform
 	}
 
-	resp, err := h.database.InsertAndReviseExposures(ctx, &database.InsertAndReviseExposuresRequest{
+	resp, err := s.database.InsertAndReviseExposures(ctx, &database.InsertAndReviseExposuresRequest{
 		Incoming:    exposures,
 		Token:       token,
 		PublishInfo: publishInfo,
 
 		RequireToken:          !appConfig.BypassRevisionToken,
-		AllowPartialRevisions: h.config.AllowPartialRevisions,
+		AllowPartialRevisions: s.config.AllowPartialRevisions,
 	})
 	if err != nil {
 		status := http.StatusBadRequest
@@ -412,14 +411,14 @@ func (h *PublishHandler) process(ctx context.Context, data *verifyapi.Publish, p
 	var keep pb.RevisionTokenData
 	if token != nil {
 		// put existing tokens that aren't too old back in the token.
-		retainInterval := model.IntervalNumber(batchTime.Add(-1 * h.config.MaxIntervalAge))
+		retainInterval := model.IntervalNumber(batchTime.Add(-1 * s.config.MaxIntervalAge))
 		for _, rk := range token.RevisableKeys {
 			if rk.IntervalNumber+rk.IntervalCount >= retainInterval {
 				keep.RevisableKeys = append(keep.RevisableKeys, rk)
 			}
 		}
 	}
-	newToken, err := h.tokenManager.MakeRevisionToken(ctx, &keep, resp.Exposures, h.tokenAAD)
+	newToken, err := s.tokenManager.MakeRevisionToken(ctx, &keep, resp.Exposures, s.tokenAAD)
 	if err != nil {
 		// In this case, an empty revision token is returned. The rest of the request
 		// was handled correctly.
diff --git a/internal/publish/publish_v1.go b/internal/publish/publish_v1.go
index 16efde2..f60e263 100644
--- a/internal/publish/publish_v1.go
+++ b/internal/publish/publish_v1.go
@@ -29,7 +29,7 @@ import (
 	"github.com/mikehelmick/go-chaff"
 )
 
-func (h *PublishHandler) handleRequest(w http.ResponseWriter, r *http.Request) *response {
+func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) *response {
 	ctx, span := trace.StartSpan(r.Context(), "(*publish.PublishHandler).handleRequest")
 	defer span.End()
 
@@ -57,20 +57,20 @@ func (h *PublishHandler) handleRequest(w http.ResponseWriter, r *http.Request) *
 	}
 
 	clientPlatform := platform(r.UserAgent())
-	return h.process(ctx, &data, clientPlatform, newVersionBridge([]string{}))
+	return s.process(ctx, &data, clientPlatform, newVersionBridge([]string{}))
 }
 
 // Handle returns an http.Handler that can process V1 publish requests.
-func (h *PublishHandler) Handle() http.Handler {
-	mResponder := maintenance.New(h.config)
-	return h.tracker.HandleTrack(chaff.HeaderDetector("X-Chaff"),
+func (s *Server) Handle() http.Handler {
+	mResponder := maintenance.New(s.config)
+	return s.tracker.HandleTrack(chaff.HeaderDetector("X-Chaff"),
 		mResponder.Handle(
 			http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-				response := h.handleRequest(w, r)
+				response := s.handleRequest(w, r)
 
 				ctx := r.Context()
 
-				if padding, err := generatePadding(h.config.ResponsePaddingMinBytes, h.config.ResponsePaddingRange); err != nil {
+				if padding, err := generatePadding(s.config.ResponsePaddingMinBytes, s.config.ResponsePaddingRange); err != nil {
 					stats.Record(ctx, mPaddingFailed.M(1))
 					logging.FromContext(ctx).Errorw("failed to pad response", "error", err)
 				} else {
diff --git a/internal/publish/publish_v1alpha1.go b/internal/publish/publish_v1alpha1.go
index 66e5f69..fc45197 100644
--- a/internal/publish/publish_v1alpha1.go
+++ b/internal/publish/publish_v1alpha1.go
@@ -30,7 +30,7 @@ import (
 	"github.com/mikehelmick/go-chaff"
 )
 
-func (h *PublishHandler) handleV1Apha1Request(w http.ResponseWriter, r *http.Request) *response {
+func (s *Server) handleV1Apha1Request(w http.ResponseWriter, r *http.Request) *response {
 	ctx, span := trace.StartSpan(r.Context(), "(*publish.PublishHandler).handleRequest")
 	defer span.End()
 
@@ -75,19 +75,19 @@ func (h *PublishHandler) handleV1Apha1Request(w http.ResponseWriter, r *http.Req
 	bridge := newVersionBridge(data.Regions)
 
 	clientPlatform := platform(r.UserAgent())
-	return h.process(ctx, &publish, clientPlatform, bridge)
+	return s.process(ctx, &publish, clientPlatform, bridge)
 }
 
-func (h *PublishHandler) HandleV1Alpha1() http.Handler {
-	mResponder := maintenance.New(h.config)
-	return h.tracker.HandleTrack(chaff.HeaderDetector("X-Chaff"),
+func (s *Server) HandleV1Alpha1() http.Handler {
+	mResponder := maintenance.New(s.config)
+	return s.tracker.HandleTrack(chaff.HeaderDetector("X-Chaff"),
 		mResponder.Handle(
 			http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-				response := h.handleV1Apha1Request(w, r)
+				response := s.handleV1Apha1Request(w, r)
 
 				ctx := r.Context()
 
-				if padding, err := generatePadding(h.config.ResponsePaddingMinBytes, h.config.ResponsePaddingRange); err != nil {
+				if padding, err := generatePadding(s.config.ResponsePaddingMinBytes, s.config.ResponsePaddingRange); err != nil {
 					stats.Record(ctx, mPaddingFailed.M(1))
 					logging.FromContext(ctx).Errorw("failed to padd response", "error", err)
 				} else {
diff --git a/internal/publish/stats.go b/internal/publish/stats.go
index db7a755..0e15f89 100644
--- a/internal/publish/stats.go
+++ b/internal/publish/stats.go
@@ -31,9 +31,9 @@ import (
 	"github.com/mikehelmick/go-chaff"
 )
 
-func (h *PublishHandler) HandleStats() http.Handler {
-	mResponder := maintenance.New(h.config)
-	return h.tracker.HandleTrack(chaff.HeaderDetector("X-Chaff"), mResponder.Handle(
+func (s *Server) HandleStats() http.Handler {
+	mResponder := maintenance.New(s.config)
+	return s.tracker.HandleTrack(chaff.HeaderDetector("X-Chaff"), mResponder.Handle(
 		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			ctx, span := trace.StartSpan(r.Context(), "(*publish.HandleStats)")
 			defer span.End()
@@ -48,7 +48,7 @@ func (h *PublishHandler) HandleStats() http.Handler {
 				if code == http.StatusInternalServerError {
 					errorCode = verifyapi.ErrorInternalError
 				}
-				h.addMetricsPadding(ctx, response)
+				s.addMetricsPadding(ctx, response)
 				jsonutil.MarshalResponse(w, http.StatusBadRequest, &verifyapi.StatsResponse{
 					ErrorMessage: message,
 					ErrorCode:    errorCode,
@@ -56,22 +56,22 @@ func (h *PublishHandler) HandleStats() http.Handler {
 				return
 			}
 
-			response, status := h.handleMetricsRequest(ctx, r.Header.Get("Authorization"), &request)
-			h.addMetricsPadding(ctx, response)
+			response, status := s.handleMetricsRequest(ctx, r.Header.Get("Authorization"), &request)
+			s.addMetricsPadding(ctx, response)
 
 			jsonutil.MarshalResponse(w, status, response)
 		})))
 }
 
-func (h *PublishHandler) addMetricsPadding(ctx context.Context, response *verifyapi.StatsResponse) {
-	if padding, err := generatePadding(h.config.StatsResponsePaddingMinBytes, h.config.StatsResponsePaddingRange); err != nil {
+func (s *Server) addMetricsPadding(ctx context.Context, response *verifyapi.StatsResponse) {
+	if padding, err := generatePadding(s.config.StatsResponsePaddingMinBytes, s.config.StatsResponsePaddingRange); err != nil {
 		logging.FromContext(ctx).Errorw("failed to pad response", "error", err)
 	} else {
 		response.Padding = padding
 	}
 }
 
-func (h *PublishHandler) handleMetricsRequest(ctx context.Context, bearerToken string, request *verifyapi.StatsRequest) (*verifyapi.StatsResponse, int) {
+func (s *Server) handleMetricsRequest(ctx context.Context, bearerToken string, request *verifyapi.StatsRequest) (*verifyapi.StatsResponse, int) {
 	logger := logging.FromContext(ctx)
 	response := &verifyapi.StatsResponse{}
 
@@ -84,7 +84,7 @@ func (h *PublishHandler) handleMetricsRequest(ctx context.Context, bearerToken s
 	bearerToken = bearerToken[7:]
 
 	// Validate JWT - if valid, the health authority ID (based on issuer) is returned.
-	healthAuthorityID, err := h.verifier.AuthenticateStatsToken(ctx, bearerToken)
+	healthAuthorityID, err := s.verifier.AuthenticateStatsToken(ctx, bearerToken)
 	if err != nil {
 		logger.Infow("stats authorization failure", "error", err)
 		response.ErrorMessage = err.Error()
@@ -93,7 +93,7 @@ func (h *PublishHandler) handleMetricsRequest(ctx context.Context, bearerToken s
 	}
 
 	// retrieve stats
-	stats, err := h.database.ReadStats(ctx, healthAuthorityID)
+	stats, err := s.database.ReadStats(ctx, healthAuthorityID)
 	if err != nil {
 		logger.Errorw("error reading stats", "error", err)
 		response.ErrorMessage = "error reading stats"
@@ -105,7 +105,7 @@ func (h *PublishHandler) handleMetricsRequest(ctx context.Context, bearerToken s
 	onlyBefore := time.Now().UTC().Truncate(time.Hour)
 
 	// Combine days - this also filters things that are "too new" and days that don't meet the threshold.
-	response.Days = model.ReduceStats(stats, onlyBefore, h.config.StatsUploadMinimum)
+	response.Days = model.ReduceStats(stats, onlyBefore, s.config.StatsUploadMinimum)
 
 	// return
 	return response, http.StatusOK


Test patch:
diff --git a/internal/publish/publish_test.go b/internal/publish/publish_test.go
index 33e5ccc..e5b6bb6 100644
--- a/internal/publish/publish_test.go
+++ b/internal/publish/publish_test.go
@@ -510,13 +510,13 @@ func TestPublishWithBypass(t *testing.T) {
 					serverenv.WithKeyManager(kms))
 				// Some config overrides for test.
 
-				pubHandler, err := NewHandler(ctx, &config, env)
+				publishServer, err := NewServer(ctx, &config, env)
 				if err != nil {
 					t.Fatalf("unable to create publish handler: %v", err)
 				}
-				handler := pubHandler.Handle()
+				handler := publishServer.Handle()
 				if ver == useV1Alpha1 {
-					handler = pubHandler.HandleV1Alpha1()
+					handler = publishServer.HandleV1Alpha1()
 				}
 
 				// See if there is a health authority to set up.
@@ -874,11 +874,11 @@ func TestKeyRevision(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	pubHandler, err := NewHandler(ctx, &config, env)
+	publishServer, err := NewServer(ctx, &config, env)
 	if err != nil {
 		t.Fatalf("unable to create publish handler: %v", err)
 	}
-	handler := pubHandler.Handle()
+	handler := publishServer.Handle()
 	pubDB := pubdb.New(testDB)
 
 	// the first key in each publish will be at this time.
diff --git a/internal/publish/stats_test.go b/internal/publish/stats_test.go
index cdedd54..7c8cf13 100644
--- a/internal/publish/stats_test.go
+++ b/internal/publish/stats_test.go
@@ -120,11 +120,11 @@ func TestRetrieveMetrics(t *testing.T) {
 		}
 	}
 
-	pubHandler, err := NewHandler(ctx, &config, env)
+	publishServer, err := NewServer(ctx, &config, env)
 	if err != nil {
 		t.Fatalf("unable to create publish handler: %v", err)
 	}
-	metricsHandler := pubHandler.HandleStats()
+	metricsHandler := publishServer.HandleStats()
 
 	// get the authentication token.
 	jwtConfig := &testutil.StatsJWTConfig{
@@ -239,11 +239,11 @@ func TestRetrieveMetrics_AuthErrors(t *testing.T) {
 	}
 	_ = testutil.InitializeVerificationDB(ctx, t, testDB, healthAuthority, healthAuthorityKey, authKey)
 
-	pubHandler, err := NewHandler(ctx, &config, env)
+	publishServer, err := NewServer(ctx, &config, env)
 	if err != nil {
 		t.Fatalf("unable to create publish handler: %v", err)
 	}
-	metricsHandler := pubHandler.HandleStats()
+	metricsHandler := publishServer.HandleStats()
 
 	// get the authentication token.
 	jwtConfig := &testutil.StatsJWTConfig{