Repository: google/exposure-notifications-server
Pull request #347 (2020-05-18 20:57:56)

Combine database and model packages
Towards #35. After this is merged I can begin to separate the db interface without creating dependency cycles.

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

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/347#" title="Author self-approved">crwilcox</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/master/OWNERS)~~ [crwilcox]

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-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/347#" title="Author self-approved">crwilcox</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/347#pullrequestreview-413982045" title="Approved">squee1945</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/master/OWNERS)~~ [crwilcox]

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":[]} -->
mikehelmick: > I'm not sure I understand the need for this change, but I'm not adverse to it either.

this is an intermediate state only
mikehelmick: /lgtm
/approve
google-prow-robot: New changes are detected. LGTM label has been removed.
google-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/347#" title="Author self-approved">crwilcox</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/347#issuecomment-630471074" title="Approved">mikehelmick</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/347#pullrequestreview-413982045" title="Approved">squee1945</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/master/OWNERS)~~ [crwilcox,mikehelmick]

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":[]} -->
mikehelmick: /lgtm
/approve
google-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/347#" title="Author self-approved">crwilcox</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/347#issuecomment-630473236" title="Approved">mikehelmick</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/347#pullrequestreview-413982045" title="Approved">squee1945</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/master/OWNERS)~~ [crwilcox,mikehelmick]

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":[]} -->

Patch:
diff --git a/internal/android/safetynet.go b/internal/android/safetynet.go
index 798d7fa..745ffe5 100644
--- a/internal/android/safetynet.go
+++ b/internal/android/safetynet.go
@@ -25,8 +25,8 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/base64util"
+	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/logging"
-	"github.com/google/exposure-notifications-server/internal/model"
 
 	"github.com/dgrijalva/jwt-go"
 )
@@ -139,7 +139,7 @@ func ValidateAttestation(ctx context.Context, attestation string, opts *VerifyOp
 
 // VerifyOptsFor returns the Android SafetyNet verification options to be used
 // based on the AuthorizedApp configuration, request time, and nonce.
-func VerifyOptsFor(c *model.AuthorizedApp, from time.Time, nonce string) *VerifyOpts {
+func VerifyOptsFor(c *database.AuthorizedApp, from time.Time, nonce string) *VerifyOpts {
 	digests := make([]string, len(c.ApkDigestSHA256))
 	copy(digests, c.ApkDigestSHA256)
 	rtn := &VerifyOpts{
diff --git a/internal/authorizedapp/database_provider.go b/internal/authorizedapp/database_provider.go
index fcd0233..deb42c3 100644
--- a/internal/authorizedapp/database_provider.go
+++ b/internal/authorizedapp/database_provider.go
@@ -22,7 +22,6 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/logging"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/exposure-notifications-server/internal/secrets"
 )
 
@@ -41,7 +40,7 @@ type DatabaseProvider struct {
 }
 
 type cacheItem struct {
-	value    *model.AuthorizedApp
+	value    *database.AuthorizedApp
 	cachedAt time.Time
 }
 
@@ -73,7 +72,7 @@ func NewDatabaseProvider(ctx context.Context, db *database.DB, config *Config, o
 }
 
 // AppConfig returns the config for the given app package name.
-func (p *DatabaseProvider) AppConfig(ctx context.Context, name string) (*model.AuthorizedApp, error) {
+func (p *DatabaseProvider) AppConfig(ctx context.Context, name string) (*database.AuthorizedApp, error) {
 	logger := logging.FromContext(ctx)
 
 	// Acquire a read lock first, which allows concurrent readers, to check if
@@ -129,7 +128,7 @@ func (p *DatabaseProvider) AppConfig(ctx context.Context, name string) (*model.A
 
 // loadAuthorizedAppFromDatabase is a lower-level private API that actually loads and parses
 // a single AuthorizedApp from the database.
-func (p *DatabaseProvider) loadAuthorizedAppFromDatabase(ctx context.Context, name string) (*model.AuthorizedApp, error) {
+func (p *DatabaseProvider) loadAuthorizedAppFromDatabase(ctx context.Context, name string) (*database.AuthorizedApp, error) {
 	logger := logging.FromContext(ctx)
 
 	logger.Infof("authorizedapp: loading %v from database", name)
diff --git a/internal/authorizedapp/memory_provider.go b/internal/authorizedapp/memory_provider.go
index 31749c2..71944d4 100644
--- a/internal/authorizedapp/memory_provider.go
+++ b/internal/authorizedapp/memory_provider.go
@@ -17,7 +17,7 @@ package authorizedapp
 import (
 	"context"
 
-	"github.com/google/exposure-notifications-server/internal/model"
+	"github.com/google/exposure-notifications-server/internal/database"
 )
 
 // Compile-time check to assert implementation.
@@ -26,20 +26,20 @@ var _ Provider = (*MemoryProvider)(nil)
 // MemoryProvider is an Provider that stores values in-memory. It is primarily
 // used for testing.
 type MemoryProvider struct {
-	Data map[string]*model.AuthorizedApp
+	Data map[string]*database.AuthorizedApp
 }
 
 // NewMemoryProvider creates a new Provider that reads from a database.
 func NewMemoryProvider(ctx context.Context, _ *Config) (Provider, error) {
 	provider := &MemoryProvider{
-		Data: make(map[string]*model.AuthorizedApp),
+		Data: make(map[string]*database.AuthorizedApp),
 	}
 
 	return provider, nil
 }
 
 // AppConfig returns the config for the given app package name.
-func (p *MemoryProvider) AppConfig(ctx context.Context, name string) (*model.AuthorizedApp, error) {
+func (p *MemoryProvider) AppConfig(ctx context.Context, name string) (*database.AuthorizedApp, error) {
 	val, ok := p.Data[name]
 	if !ok {
 		return nil, AppNotFound
diff --git a/internal/authorizedapp/provider.go b/internal/authorizedapp/provider.go
index 390c37c..84247be 100644
--- a/internal/authorizedapp/provider.go
+++ b/internal/authorizedapp/provider.go
@@ -18,7 +18,7 @@ import (
 	"context"
 	"errors"
 
-	"github.com/google/exposure-notifications-server/internal/model"
+	"github.com/google/exposure-notifications-server/internal/database"
 )
 
 // AppNotFound is the sentinel error returned when AppConfig fails to find an
@@ -30,5 +30,5 @@ type Provider interface {
 	// AppConfig returns the application-specific configuration for the given
 	// name. An error is returned if the configuration fails to load. An error is
 	// returned if no app with the given name is registered in the system.
-	AppConfig(ctx context.Context, name string) (*model.AuthorizedApp, error)
+	AppConfig(ctx context.Context, name string) (*database.AuthorizedApp, error)
 }
diff --git a/internal/database/authorized_app.go b/internal/database/authorized_app.go
index 6dbb2c5..ff1b54f 100644
--- a/internal/database/authorized_app.go
+++ b/internal/database/authorized_app.go
@@ -21,14 +21,13 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/ios"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/exposure-notifications-server/internal/secrets"
 	pgx "github.com/jackc/pgx/v4"
 )
 
 // GetAuthorizedApp loads a single AuthorizedApp for the given name. If no row
 // exists, this returns nil.
-func (db *DB) GetAuthorizedApp(ctx context.Context, sm secrets.SecretManager, name string) (*model.AuthorizedApp, error) {
+func (db *DB) GetAuthorizedApp(ctx context.Context, sm secrets.SecretManager, name string) (*AuthorizedApp, error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("acquiring connection: %v", err)
@@ -46,7 +45,7 @@ func (db *DB) GetAuthorizedApp(ctx context.Context, sm secrets.SecretManager, na
 
 	row := conn.QueryRow(ctx, query, name)
 
-	config := model.NewAuthorizedApp()
+	config := NewAuthorizedApp()
 	var allowedRegions []string
 	var allowedPastSeconds, allowedFutureSeconds *int
 	var deviceCheckTeamIDSecret, deviceCheckKeyIDSecret, deviceCheckPrivateKeySecret sql.NullString
diff --git a/internal/model/authorized_app.go b/internal/database/authorized_app_model.go
similarity index 99%
rename from internal/model/authorized_app.go
rename to internal/database/authorized_app_model.go
index e91f1af..5d5fed2 100644
--- a/internal/model/authorized_app.go
+++ b/internal/database/authorized_app_model.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package model
+package database
 
 import (
 	"crypto/ecdsa"
diff --git a/internal/database/export.go b/internal/database/export.go
index 29edcb5..d273681 100644
--- a/internal/database/export.go
+++ b/internal/database/export.go
@@ -22,7 +22,6 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/logging"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/exposure-notifications-server/internal/storage"
 
 	pgx "github.com/jackc/pgx/v4"
@@ -34,7 +33,7 @@ const (
 )
 
 // AddExportConfig creates a new ExportConfig record from which batch jobs are created.
-func (db *DB) AddExportConfig(ctx context.Context, ec *model.ExportConfig) error {
+func (db *DB) AddExportConfig(ctx context.Context, ec *ExportConfig) error {
 	if ec.Period > oneDay {
 		return errors.New("maximum period is 24h")
 	}
@@ -70,7 +69,7 @@ func (db *DB) AddExportConfig(ctx context.Context, ec *model.ExportConfig) error
 // IterateExportConfigs applies f to each ExportConfig whose FromTimestamp is
 // before the given time. If f returns a non-nil error, the iteration stops, and
 // the returned error will match f's error with errors.Is.
-func (db *DB) IterateExportConfigs(ctx context.Context, t time.Time, f func(*model.ExportConfig) error) (err error) {
+func (db *DB) IterateExportConfigs(ctx context.Context, t time.Time, f func(*ExportConfig) error) (err error) {
 	defer func() {
 		if err != nil {
 			err = fmt.Errorf("IterateExportConfigs(%s): %w", t, err)
@@ -99,7 +98,7 @@ func (db *DB) IterateExportConfigs(ctx context.Context, t time.Time, f func(*mod
 	defer rows.Close()
 	for rows.Next() {
 		var (
-			m             model.ExportConfig
+			m             ExportConfig
 			periodSeconds int
 			thru          *time.Time
 		)
@@ -117,7 +116,7 @@ func (db *DB) IterateExportConfigs(ctx context.Context, t time.Time, f func(*mod
 	return rows.Err()
 }
 
-func (db *DB) AddSignatureInfo(ctx context.Context, si *model.SignatureInfo) error {
+func (db *DB) AddSignatureInfo(ctx context.Context, si *SignatureInfo) error {
 	if si.SigningKey == "" {
 		return fmt.Errorf("signing key cannot be empty for a signature info")
 	}
@@ -143,7 +142,7 @@ func (db *DB) AddSignatureInfo(ctx context.Context, si *model.SignatureInfo) err
 	})
 }
 
-func (db *DB) LookupSignatureInfos(ctx context.Context, ids []int64, validUntil time.Time) ([]*model.SignatureInfo, error) {
+func (db *DB) LookupSignatureInfos(ctx context.Context, ids []int64, validUntil time.Time) ([]*SignatureInfo, error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("acquiring connection: %w", err)
@@ -162,12 +161,12 @@ func (db *DB) LookupSignatureInfos(ctx context.Context, ids []int64, validUntil
 		return nil, err
 	}
 
-	var sigInfos []*model.SignatureInfo
+	var sigInfos []*SignatureInfo
 	for rows.Next() {
 		if rows.Err() != nil {
 			return nil, rows.Err()
 		}
-		var info model.SignatureInfo
+		var info SignatureInfo
 		var thru *time.Time
 		if err := rows.Scan(&info.ID, &info.SigningKey, &info.AppPackageName, &info.BundleID, &info.SigningKeyVersion, &info.SigningKeyID, &thru); err != nil {
 			return nil, err
@@ -185,7 +184,7 @@ func (db *DB) LookupSignatureInfos(ctx context.Context, ids []int64, validUntil
 // a given ExportConfig. It returns the zero time if no previous ExportBatch
 // exists.
 // TODO(squee1945): This needs a
-func (db *DB) LatestExportBatchEnd(ctx context.Context, ec *model.ExportConfig) (time.Time, error) {
+func (db *DB) LatestExportBatchEnd(ctx context.Context, ec *ExportConfig) (time.Time, error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return time.Time{}, fmt.Errorf("acquiring connection: %w", err)
@@ -215,7 +214,7 @@ func (db *DB) LatestExportBatchEnd(ctx context.Context, ec *model.ExportConfig)
 }
 
 // AddExportBatches inserts new export batches.
-func (db *DB) AddExportBatches(ctx context.Context, batches []*model.ExportBatch) error {
+func (db *DB) AddExportBatches(ctx context.Context, batches []*ExportBatch) error {
 	return db.inTx(ctx, pgx.Serializable, func(tx pgx.Tx) error {
 		const stmtName = "insert export batches"
 		_, err := tx.Prepare(ctx, stmtName, `
@@ -240,7 +239,7 @@ func (db *DB) AddExportBatches(ctx context.Context, batches []*model.ExportBatch
 }
 
 // LeaseBatch returns a leased ExportBatch for the worker to process. If no work to do, nil will be returned.
-func (db *DB) LeaseBatch(ctx context.Context, ttl time.Duration, now time.Time) (*model.ExportBatch, error) {
+func (db *DB) LeaseBatch(ctx context.Context, ttl time.Duration, now time.Time) (*ExportBatch, error) {
 	// Lookup a set of candidate batch IDs.
 	var openBatchIDs []int64
 	err := func() error { // Use a func to allow defer conn.Release() to work.
@@ -266,7 +265,7 @@ func (db *DB) LeaseBatch(ctx context.Context, ttl time.Duration, now time.Time)
 			AND
 				end_timestamp < $3
 			LIMIT 100
-		`, model.ExportBatchOpen, model.ExportBatchPending, now)
+		`, ExportBatchOpen, ExportBatchPending, now)
 		if err != nil {
 			return err
 		}
@@ -315,7 +314,7 @@ func (db *DB) LeaseBatch(ctx context.Context, ttl time.Duration, now time.Time)
 				return err
 			}
 
-			if status == model.ExportBatchComplete || (expires != nil && status == model.ExportBatchPending && now.Before(*expires)) {
+			if status == ExportBatchComplete || (expires != nil && status == ExportBatchPending && now.Before(*expires)) {
 				// Something beat us to this batch, it's no longer available.
 				return nil
 			}
@@ -327,7 +326,7 @@ func (db *DB) LeaseBatch(ctx context.Context, ttl time.Duration, now time.Time)
 					status = $1, lease_expires = $2
 				WHERE
 				    batch_id = $3
-				`, model.ExportBatchPending, now.Add(ttl), bid)
+				`, ExportBatchPending, now.Add(ttl), bid)
 			if err != nil {
 				return err
 			}
@@ -348,7 +347,7 @@ func (db *DB) LeaseBatch(ctx context.Context, ttl time.Duration, now time.Time)
 }
 
 // LookupExportBatch returns an ExportBatch for the given batchID.
-func (db *DB) LookupExportBatch(ctx context.Context, batchID int64) (*model.ExportBatch, error) {
+func (db *DB) LookupExportBatch(ctx context.Context, batchID int64) (*ExportBatch, error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("acquiring connection: %w", err)
@@ -358,7 +357,7 @@ func (db *DB) LookupExportBatch(ctx context.Context, batchID int64) (*model.Expo
 	return lookupExportBatch(ctx, batchID, conn.QueryRow)
 }
 
-func lookupExportBatch(ctx context.Context, batchID int64, queryRow queryRowFn) (*model.ExportBatch, error) {
+func lookupExportBatch(ctx context.Context, batchID int64, queryRow queryRowFn) (*ExportBatch, error) {
 	row := queryRow(ctx, `
 		SELECT
 			batch_id, config_id, bucket_name, filename_root, start_timestamp, end_timestamp, region, status, lease_expires, signature_info_ids
@@ -370,7 +369,7 @@ func lookupExportBatch(ctx context.Context, batchID int64, queryRow queryRowFn)
 		`, batchID)
 
 	var expires *time.Time
-	eb := model.ExportBatch{}
+	eb := ExportBatch{}
 	if err := row.Scan(&eb.BatchID, &eb.ConfigID, &eb.BucketName, &eb.FilenameRoot, &eb.StartTimestamp, &eb.EndTimestamp, &eb.Region, &eb.Status, &expires, &eb.SignatureInfoIDs); err != nil {
 		if err == pgx.ErrNoRows {
 			return nil, ErrNotFound
@@ -384,18 +383,18 @@ func lookupExportBatch(ctx context.Context, batchID int64, queryRow queryRowFn)
 }
 
 // FinalizeBatch writes the ExportFile records and marks the ExportBatch as complete.
-func (db *DB) FinalizeBatch(ctx context.Context, eb *model.ExportBatch, files []string, batchSize int) error {
+func (db *DB) FinalizeBatch(ctx context.Context, eb *ExportBatch, files []string, batchSize int) error {
 	return db.inTx(ctx, pgx.Serializable, func(tx pgx.Tx) error {
 		// Update ExportFile for the files created.
 		for i, file := range files {
-			ef := model.ExportFile{
+			ef := ExportFile{
 				BucketName: eb.BucketName,
 				Filename:   file,
 				BatchID:    eb.BatchID,
 				Region:     eb.Region,
 				BatchNum:   i + 1,
 				BatchSize:  batchSize,
-				Status:     model.ExportBatchComplete,
+				Status:     ExportBatchComplete,
 			}
 			if err := addExportFile(ctx, tx, &ef); err != nil {
 				if err == ErrKeyConflict {
@@ -435,7 +434,7 @@ func (db *DB) LookupExportFiles(ctx context.Context, exportConfigID int64) ([]st
 			eb.status = $2
 		ORDER BY
 			ef.filename
-		`, exportConfigID, model.ExportBatchComplete)
+		`, exportConfigID, ExportBatchComplete)
 	if err != nil {
 		return nil, err
 	}
@@ -464,7 +463,7 @@ type joinedExportBatchFile struct {
 	batchStatus string
 }
 
-func (db *DB) LookupExportFile(ctx context.Context, filename string) (*model.ExportFile, error) {
+func (db *DB) LookupExportFile(ctx context.Context, filename string) (*ExportFile, error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("acquiring connection: %w", err)
@@ -481,7 +480,7 @@ func (db *DB) LookupExportFile(ctx context.Context, filename string) (*model.Exp
 		LIMIT 1
 		`, filename)
 
-	ef := model.ExportFile{}
+	ef := ExportFile{}
 	if err := row.Scan(&ef.BucketName, &ef.Filename, &ef.BatchID, &ef.Region, &ef.BatchNum, &ef.BatchSize, &ef.Status); err != nil {
 		if err == pgx.ErrNoRows {
 			return nil, ErrNotFound
@@ -519,7 +518,7 @@ func (db *DB) DeleteFilesBefore(ctx context.Context, before time.Time, blobstore
 			WHERE
 				eb.end_timestamp < $1
 				AND eb.status != $2`
-		rows, err := conn.Query(ctx, q, before, model.ExportBatchDeleted)
+		rows, err := conn.Query(ctx, q, before, ExportBatchDeleted)
 		if err != nil {
 			return fmt.Errorf("fetching filenames: %w", err)
 		}
@@ -544,7 +543,7 @@ func (db *DB) DeleteFilesBefore(ctx context.Context, before time.Time, blobstore
 	for _, f := range files {
 
 		// If file is already deleted, skip to the next.
-		if f.fileStatus == model.ExportBatchDeleted {
+		if f.fileStatus == ExportBatchDeleted {
 			batchFileDeleteCounter[f.batchID]++
 			continue
 		}
@@ -558,13 +557,13 @@ func (db *DB) DeleteFilesBefore(ctx context.Context, before time.Time, blobstore
 
 		err := db.inTx(ctx, pgx.Serializable, func(tx pgx.Tx) error {
 			// Update Status in ExportFile.
-			if err := updateExportFileStatus(ctx, tx, f.batchID, f.filename, model.ExportBatchDeleted); err != nil {
+			if err := updateExportFileStatus(ctx, tx, f.batchID, f.filename, ExportBatchDeleted); err != nil {
 				return fmt.Errorf("updating ExportFile: %w", err)
 			}
 
 			// If batch completely deleted, update in ExportBatch.
 			if batchFileDeleteCounter[f.batchID] == f.count {
-				if err := updateExportBatchStatus(ctx, tx, f.batchID, model.ExportBatchDeleted); err != nil {
+				if err := updateExportBatchStatus(ctx, tx, f.batchID, ExportBatchDeleted); err != nil {
 					return fmt.Errorf("updating ExportBatch: %w", err)
 				}
 			}
@@ -583,7 +582,7 @@ func (db *DB) DeleteFilesBefore(ctx context.Context, before time.Time, blobstore
 
 // addExportFile adds a row to ExportFile. If the row already exists (based on the primary key),
 // ErrKeyConflict is returned.
-func addExportFile(ctx context.Context, tx pgx.Tx, ef *model.ExportFile) error {
+func addExportFile(ctx context.Context, tx pgx.Tx, ef *ExportFile) error {
 	tag, err := tx.Exec(ctx, `
 		INSERT INTO
 			ExportFile
@@ -639,9 +638,9 @@ func completeBatch(ctx context.Context, tx pgx.Tx, batchID int64) error {
 		return err
 	}
 
-	if batch.Status == model.ExportBatchComplete {
+	if batch.Status == ExportBatchComplete {
 		// Batch is already completed.
-		logger.Warnf("When completing a batch, the status of batch %d was already %s.", batchID, model.ExportBatchComplete)
+		logger.Warnf("When completing a batch, the status of batch %d was already %s.", batchID, ExportBatchComplete)
 		return nil
 	}
 
@@ -652,7 +651,7 @@ func completeBatch(ctx context.Context, tx pgx.Tx, batchID int64) error {
 			status = $1, lease_expires = NULL
 		WHERE
 			batch_id = $2
-		`, model.ExportBatchComplete, batchID)
+		`, ExportBatchComplete, batchID)
 	if err != nil {
 		return err
 	}
diff --git a/internal/model/export.go b/internal/database/export_model.go
similarity index 99%
rename from internal/model/export.go
rename to internal/database/export_model.go
index 79729da..33c066f 100644
--- a/internal/model/export.go
+++ b/internal/database/export_model.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package model
+package database
 
 import (
 	"time"
diff --git a/internal/database/exposure.go b/internal/database/exposure.go
index b2b5219..ec7c132 100644
--- a/internal/database/exposure.go
+++ b/internal/database/exposure.go
@@ -24,7 +24,6 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/base64util"
 	"github.com/google/exposure-notifications-server/internal/logging"
-	"github.com/google/exposure-notifications-server/internal/model"
 
 	pgx "github.com/jackc/pgx/v4"
 )
@@ -55,7 +54,7 @@ type IterateExposuresCriteria struct {
 // criteria.LastCursor in a subsequent call to IterateExposures, will continue
 // the iteration at the failed row. If IterateExposures returns a nil error,
 // the first return value will be the empty string.
-func (db *DB) IterateExposures(ctx context.Context, criteria IterateExposuresCriteria, f func(*model.Exposure) error) (cur string, err error) {
+func (db *DB) IterateExposures(ctx context.Context, criteria IterateExposuresCriteria, f func(*Exposure) error) (cur string, err error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return "", fmt.Errorf("acquiring connection: %v", err)
@@ -95,7 +94,7 @@ func (db *DB) IterateExposures(ctx context.Context, criteria IterateExposuresCri
 			return cursor(), err
 		}
 		var (
-			m          model.Exposure
+			m          Exposure
 			encodedKey string
 			syncID     *int64
 		)
@@ -179,7 +178,7 @@ func generateExposureQuery(criteria IterateExposuresCriteria) (string, []interfa
 }
 
 // InsertExposures inserts a set of exposures.
-func (db *DB) InsertExposures(ctx context.Context, exposures []*model.Exposure) error {
+func (db *DB) InsertExposures(ctx context.Context, exposures []*Exposure) error {
 	return db.inTx(ctx, pgx.ReadCommitted, func(tx pgx.Tx) error {
 		const stmtName = "insert exposures"
 		_, err := tx.Prepare(ctx, stmtName, `
diff --git a/internal/model/exposure.go b/internal/database/exposure_model.go
similarity index 99%
rename from internal/model/exposure.go
rename to internal/database/exposure_model.go
index 3f8ff04..833f996 100644
--- a/internal/model/exposure.go
+++ b/internal/database/exposure_model.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package model
+package database
 
 import (
 	"crypto/sha256"
@@ -42,7 +42,7 @@ const (
 	MaxIntervalCount = 144
 
 	// Self explanatory.
-	oneDay = time.Hour * 24
+	// oneDay = time.Hour * 24
 
 	// interval length
 	intervalLength = 10 * time.Minute
diff --git a/internal/model/federation.go b/internal/database/federation_model.go
similarity index 99%
rename from internal/model/federation.go
rename to internal/database/federation_model.go
index f34d88f..fee9372 100644
--- a/internal/model/federation.go
+++ b/internal/database/federation_model.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package model
+package database
 
 import (
 	"time"
diff --git a/internal/database/federationin.go b/internal/database/federationin.go
index 8a4ce9d..f437a69 100644
--- a/internal/database/federationin.go
+++ b/internal/database/federationin.go
@@ -19,8 +19,6 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/model"
-
 	pgx "github.com/jackc/pgx/v4"
 )
 
@@ -30,7 +28,7 @@ type FinalizeSyncFn func(maxTimestamp time.Time, totalInserted int) error
 type queryRowFn func(ctx context.Context, query string, args ...interface{}) pgx.Row
 
 // GetFederationInQuery returns a query for given queryID. If not found, ErrNotFound will be returned.
-func (db *DB) GetFederationInQuery(ctx context.Context, queryID string) (*model.FederationInQuery, error) {
+func (db *DB) GetFederationInQuery(ctx context.Context, queryID string) (*FederationInQuery, error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("acquiring connection: %w", err)
@@ -40,7 +38,7 @@ func (db *DB) GetFederationInQuery(ctx context.Context, queryID string) (*model.
 	return getFederationInQuery(ctx, queryID, conn.QueryRow)
 }
 
-func getFederationInQuery(ctx context.Context, queryID string, queryRow queryRowFn) (*model.FederationInQuery, error) {
+func getFederationInQuery(ctx context.Context, queryID string, queryRow queryRowFn) (*FederationInQuery, error) {
 	row := queryRow(ctx, `
 		SELECT
 			query_id, server_addr, oidc_audience, include_regions, exclude_regions, last_timestamp
@@ -51,7 +49,7 @@ func getFederationInQuery(ctx context.Context, queryID string, queryRow queryRow
 		`, queryID)
 
 	// See https://www.opsdash.com/blog/postgres-arrays-golang.html for working with Postgres arrays in Go.
-	q := model.FederationInQuery{}
+	q := FederationInQuery{}
 	if err := row.Scan(&q.QueryID, &q.ServerAddr, &q.Audience, &q.IncludeRegions, &q.ExcludeRegions, &q.LastTimestamp); err != nil {
 		if err == pgx.ErrNoRows {
 			return nil, ErrNotFound
@@ -62,7 +60,7 @@ func getFederationInQuery(ctx context.Context, queryID string, queryRow queryRow
 }
 
 // AddFederationInQuery adds a FederationInQuery entity. It will overwrite a query with matching q.queryID if it exists.
-func (db *DB) AddFederationInQuery(ctx context.Context, q *model.FederationInQuery) error {
+func (db *DB) AddFederationInQuery(ctx context.Context, q *FederationInQuery) error {
 	return db.inTx(ctx, pgx.Serializable, func(tx pgx.Tx) error {
 		query := `
 			INSERT INTO
@@ -84,7 +82,7 @@ func (db *DB) AddFederationInQuery(ctx context.Context, q *model.FederationInQue
 }
 
 // GetFederationInSync returns a federation sync record for given syncID. If not found, ErrNotFound will be returned.
-func (db *DB) GetFederationInSync(ctx context.Context, syncID int64) (*model.FederationInSync, error) {
+func (db *DB) GetFederationInSync(ctx context.Context, syncID int64) (*FederationInSync, error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("acquiring connection: %w", err)
@@ -94,7 +92,7 @@ func (db *DB) GetFederationInSync(ctx context.Context, syncID int64) (*model.Fed
 	return getFederationInSync(ctx, syncID, conn.QueryRow)
 }
 
-func getFederationInSync(ctx context.Context, syncID int64, queryRowContext queryRowFn) (*model.FederationInSync, error) {
+func getFederationInSync(ctx context.Context, syncID int64, queryRowContext queryRowFn) (*FederationInSync, error) {
 	row := queryRowContext(ctx, `
 		SELECT
 			sync_id, query_id, started, completed, insertions, max_timestamp
@@ -104,7 +102,7 @@ func getFederationInSync(ctx context.Context, syncID int64, queryRowContext quer
 			sync_id=$1
 		`, syncID)
 
-	s := model.FederationInSync{}
+	s := FederationInSync{}
 	var (
 		completed, max *time.Time
 		insertions     *int
@@ -128,7 +126,7 @@ func getFederationInSync(ctx context.Context, syncID int64, queryRowContext quer
 }
 
 // StartFederationInSync stores a historical record of a query sync starting. It returns a FederationInSync key, and a FinalizeSyncFn that must be invoked to finalize the historical record.
-func (db *DB) StartFederationInSync(ctx context.Context, q *model.FederationInQuery, started time.Time) (int64, FinalizeSyncFn, error) {
+func (db *DB) StartFederationInSync(ctx context.Context, q *FederationInQuery, started time.Time) (int64, FinalizeSyncFn, error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return 0, nil, fmt.Errorf("acquiring connection: %w", err)
diff --git a/internal/database/federationout.go b/internal/database/federationout.go
index cc9e231..96f2913 100644
--- a/internal/database/federationout.go
+++ b/internal/database/federationout.go
@@ -18,12 +18,11 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/google/exposure-notifications-server/internal/model"
 	pgx "github.com/jackc/pgx/v4"
 )
 
 // AddFederationOutAuthorization adds or updates a FederationOutAuthorization record.
-func (db *DB) AddFederationOutAuthorization(ctx context.Context, auth *model.FederationOutAuthorization) error {
+func (db *DB) AddFederationOutAuthorization(ctx context.Context, auth *FederationOutAuthorization) error {
 	return db.inTx(ctx, pgx.Serializable, func(tx pgx.Tx) error {
 		q := `
 			INSERT INTO
@@ -45,7 +44,7 @@ func (db *DB) AddFederationOutAuthorization(ctx context.Context, auth *model.Fed
 }
 
 // GetFederationOutAuthorization returns a FederationOutAuthorization record, or ErrNotFound if not found.
-func (db *DB) GetFederationOutAuthorization(ctx context.Context, issuer, subject string) (*model.FederationOutAuthorization, error) {
+func (db *DB) GetFederationOutAuthorization(ctx context.Context, issuer, subject string) (*FederationOutAuthorization, error) {
 	conn, err := db.pool.Acquire(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("acquiring connection: %w", err)
@@ -63,7 +62,7 @@ func (db *DB) GetFederationOutAuthorization(ctx context.Context, issuer, subject
 			oidc_subject = $2
 		LIMIT 1
 		`, issuer, subject)
-	auth := model.FederationOutAuthorization{}
+	auth := FederationOutAuthorization{}
 	if err := row.Scan(&auth.Issuer, &auth.Subject, &auth.Audience, &auth.Note, &auth.IncludeRegions, &auth.ExcludeRegions); err != nil {
 		if err == pgx.ErrNoRows {
 			return nil, ErrNotFound
diff --git a/internal/export/batcher.go b/internal/export/batcher.go
index b12f568..1bac079 100644
--- a/internal/export/batcher.go
+++ b/internal/export/batcher.go
@@ -24,7 +24,6 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/logging"
-	"github.com/google/exposure-notifications-server/internal/model"
 )
 
 // CreateBatchesHandler is a handler to iterate the rows of ExportConfig and
@@ -60,7 +59,7 @@ func (s *Server) CreateBatchesHandler(w http.ResponseWriter, r *http.Request) {
 	}()
 
 	now := time.Now()
-	err = s.db.IterateExportConfigs(ctx, now, func(ec *model.ExportConfig) error {
+	err = s.db.IterateExportConfigs(ctx, now, func(ec *database.ExportConfig) error {
 		totalConfigs++
 		if batchesCreated, err := s.maybeCreateBatches(ctx, ec, now); err != nil {
 			logger.Errorf("Failed to create batches for config %d: %v, continuing to next config", ec.ConfigID, err)
@@ -89,7 +88,7 @@ func (s *Server) CreateBatchesHandler(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func (s *Server) maybeCreateBatches(ctx context.Context, ec *model.ExportConfig, now time.Time) (int, error) {
+func (s *Server) maybeCreateBatches(ctx context.Context, ec *database.ExportConfig, now time.Time) (int, error) {
 	logger := logging.FromContext(ctx)
 	metrics := s.env.MetricsExporter(ctx)
 
@@ -105,18 +104,18 @@ func (s *Server) maybeCreateBatches(ctx context.Context, ec *model.ExportConfig,
 		return 0, nil
 	}
 
-	var batches []*model.ExportBatch
+	var batches []*database.ExportBatch
 	for _, br := range ranges {
 		infoIds := make([]int64, len(ec.SignatureInfoIDs))
 		copy(infoIds, ec.SignatureInfoIDs)
-		batches = append(batches, &model.ExportBatch{
+		batches = append(batches, &database.ExportBatch{
 			ConfigID:         ec.ConfigID,
 			BucketName:       ec.BucketName,
 			FilenameRoot:     ec.FilenameRoot,
 			StartTimestamp:   br.start,
 			EndTimestamp:     br.end,
 			Region:           ec.Region,
-			Status:           model.ExportBatchOpen,
+			Status:           database.ExportBatchOpen,
 			SignatureInfoIDs: infoIds,
 		})
 	}
@@ -139,7 +138,7 @@ var sanityDate = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
 func makeBatchRanges(period time.Duration, latestEnd, now time.Time, truncateWindow time.Duration) []batchRange {
 
 	// Compute the end of the exposure publish window; we don't want any batches with an end date greater than this time.
-	publishEnd := model.TruncateWindow(now, truncateWindow)
+	publishEnd := database.TruncateWindow(now, truncateWindow)
 
 	// Special case: if there have not been batches before, return only a single one.
 	// We use sanityDate here because the loop below will happily create batch ranges
diff --git a/internal/export/exportfile.go b/internal/export/exportfile.go
index 70a8c55..7b5ec84 100644
--- a/internal/export/exportfile.go
+++ b/internal/export/exportfile.go
@@ -23,7 +23,7 @@ import (
 	"fmt"
 	"sort"
 
-	"github.com/google/exposure-notifications-server/internal/model"
+	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/pb/export"
 
 	"github.com/golang/protobuf/proto"
@@ -39,12 +39,12 @@ const (
 )
 
 type exportSigners struct {
-	signatureInfo *model.SignatureInfo
+	signatureInfo *database.SignatureInfo
 	signer        crypto.Signer
 }
 
 // MarshalExportFile converts the inputs into an encoded byte array.
-func MarshalExportFile(eb *model.ExportBatch, exposures []*model.Exposure, batchNum, batchSize int, signers []exportSigners) ([]byte, error) {
+func MarshalExportFile(eb *database.ExportBatch, exposures []*database.Exposure, batchNum, batchSize int, signers []exportSigners) ([]byte, error) {
 	// create main exposure key export binary
 	expContents, err := marshalContents(eb, exposures, int32(batchNum), int32(batchSize), signers)
 	if err != nil {
@@ -82,7 +82,7 @@ func MarshalExportFile(eb *model.ExportBatch, exposures []*model.Exposure, batch
 	return buf.Bytes(), nil
 }
 
-func marshalContents(eb *model.ExportBatch, exposures []*model.Exposure, batchNum int32, batchSize int32, signers []exportSigners) ([]byte, error) {
+func marshalContents(eb *database.ExportBatch, exposures []*database.Exposure, batchNum int32, batchSize int32, signers []exportSigners) ([]byte, error) {
 	exportBytes := []byte("EK Export v1    ")
 	if len(exportBytes) != fixedHeaderWidth {
 		return nil, fmt.Errorf("incorrect header length: %d", len(exportBytes))
@@ -129,7 +129,7 @@ func marshalContents(eb *model.ExportBatch, exposures []*model.Exposure, batchNu
 	return append(exportBytes, protoBytes...), nil
 }
 
-func createSignatureInfo(si *model.SignatureInfo) *export.SignatureInfo {
+func createSignatureInfo(si *database.SignatureInfo) *export.SignatureInfo {
 	sigInfo := &export.SignatureInfo{SignatureAlgorithm: proto.String(algorithm)}
 	if si.AppPackageName != "" {
 		sigInfo.AndroidPackage = proto.String(si.AppPackageName)
@@ -146,7 +146,7 @@ func createSignatureInfo(si *model.SignatureInfo) *export.SignatureInfo {
 	return sigInfo
 }
 
-func marshalSignature(eb *model.ExportBatch, exportContents []byte, batchNum, batchSize int32, signers []exportSigners) ([]byte, error) {
+func marshalSignature(eb *database.ExportBatch, exportContents []byte, batchNum, batchSize int32, signers []exportSigners) ([]byte, error) {
 
 	var signatures []*export.TEKSignature
 	for _, s := range signers {
diff --git a/internal/export/worker.go b/internal/export/worker.go
index 66ed000..cf09cd3 100644
--- a/internal/export/worker.go
+++ b/internal/export/worker.go
@@ -26,7 +26,6 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/logging"
-	"github.com/google/exposure-notifications-server/internal/model"
 )
 
 const (
@@ -79,7 +78,7 @@ func (s *Server) WorkerHandler(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func (s *Server) exportBatch(ctx context.Context, eb *model.ExportBatch, emitIndexForEmptyBatch bool) error {
+func (s *Server) exportBatch(ctx context.Context, eb *database.ExportBatch, emitIndexForEmptyBatch bool) error {
 	logger := logging.FromContext(ctx)
 	logger.Infof("Processing export batch %d (root: %q, region: %s), max records per file %d", eb.BatchID, eb.FilenameRoot, eb.Region, s.config.MaxRecords)
 
@@ -93,9 +92,9 @@ func (s *Server) exportBatch(ctx context.Context, eb *model.ExportBatch, emitInd
 	// Build up groups of exposures in memory. We need to use memory so we can determine the
 	// total number of groups (which is embedded in each export file). This technique avoids
 	// SELECT COUNT which would lock the database slowing new uploads.
-	var groups [][]*model.Exposure
-	var exposures []*model.Exposure
-	_, err := s.db.IterateExposures(ctx, criteria, func(exp *model.Exposure) error {
+	var groups [][]*database.Exposure
+	var exposures []*database.Exposure
+	_, err := s.db.IterateExposures(ctx, criteria, func(exp *database.Exposure) error {
 		exposures = append(exposures, exp)
 		if len(exposures) == s.config.MaxRecords {
 			groups = append(groups, exposures)
@@ -167,9 +166,9 @@ func (s *Server) exportBatch(ctx context.Context, eb *model.ExportBatch, emitInd
 }
 
 type createFileInfo struct {
-	exposures      []*model.Exposure
-	exportBatch    *model.ExportBatch
-	signatureInfos []*model.SignatureInfo
+	exposures      []*database.Exposure
+	exportBatch    *database.ExportBatch
+	signatureInfos []*database.SignatureInfo
 	batchNum       int
 	batchSize      int
 }
@@ -205,7 +204,7 @@ func (s *Server) createFile(ctx context.Context, cfi createFileInfo) (string, er
 
 // retryingCreateIndex create the index file. The index file includes _all_ batches for an ExportConfig,
 // so multiple workers may be racing to update it. We use a lock to make them line up after one another.
-func (s *Server) retryingCreateIndex(ctx context.Context, eb *model.ExportBatch, objectNames []string) error {
+func (s *Server) retryingCreateIndex(ctx context.Context, eb *database.ExportBatch, objectNames []string) error {
 	logger := logging.FromContext(ctx)
 
 	lockID := fmt.Sprintf("export-batch-%d", eb.BatchID)
@@ -242,7 +241,7 @@ func (s *Server) retryingCreateIndex(ctx context.Context, eb *model.ExportBatch,
 	return nil
 }
 
-func (s *Server) createIndex(ctx context.Context, eb *model.ExportBatch, newObjectNames []string) (string, int, error) {
+func (s *Server) createIndex(ctx context.Context, eb *database.ExportBatch, newObjectNames []string) (string, int, error) {
 	objects, err := s.db.LookupExportFiles(ctx, eb.ConfigID)
 	if err != nil {
 		return "", 0, fmt.Errorf("lookup existing export files for batch %d: %w", eb.BatchID, err)
@@ -273,11 +272,11 @@ func (s *Server) createIndex(ctx context.Context, eb *model.ExportBatch, newObje
 	return indexObjectName, len(objects), nil
 }
 
-func exportFilename(eb *model.ExportBatch, batchNum int) string {
+func exportFilename(eb *database.ExportBatch, batchNum int) string {
 	return fmt.Sprintf("%s/%d-%05d%s", eb.FilenameRoot, eb.StartTimestamp.Unix(), batchNum, filenameSuffix)
 }
 
-func exportIndexFilename(eb *model.ExportBatch) string {
+func exportIndexFilename(eb *database.ExportBatch) string {
 	return fmt.Sprintf("%s/index.txt", eb.FilenameRoot)
 }
 
@@ -290,7 +289,7 @@ func randomInt(min, max int) (int, error) {
 	return int(n.Int64()) + min, nil
 }
 
-func ensureMinNumExposures(exposures []*model.Exposure, region string, minLength, jitter int) ([]*model.Exposure, error) {
+func ensureMinNumExposures(exposures []*database.Exposure, region string, minLength, jitter int) ([]*database.Exposure, error) {
 	if len(exposures) == 0 {
 		return exposures, nil
 	}
@@ -302,14 +301,14 @@ func ensureMinNumExposures(exposures []*model.Exposure, region string, minLength
 		// Pieces needed are
 		// (1) exposure key, (2) interval number, (3) transmission risk
 		// Exposure key is 16 random bytes.
-		eKey := make([]byte, model.KeyLength)
+		eKey := make([]byte, database.KeyLength)
 		_, err := rand.Read(eKey)
 		if err != nil {
 			return nil, fmt.Errorf("rand.Read: %w", err)
 		}
 
 		// Transmission risk is within the bounds.
-		transmissionRisk, err := randomInt(model.MinTransmissionRisk, model.MaxTransmissionRisk)
+		transmissionRisk, err := randomInt(database.MinTransmissionRisk, database.MaxTransmissionRisk)
 		if err != nil {
 			return nil, fmt.Errorf("randomInt: %w", err)
 		}
@@ -328,13 +327,13 @@ func ensureMinNumExposures(exposures []*model.Exposure, region string, minLength
 		}
 		intervalCount := exposures[fromIdx].IntervalCount
 
-		ek := &model.Exposure{
+		ek := &database.Exposure{
 			ExposureKey:      eKey,
 			TransmissionRisk: transmissionRisk,
 			Regions:          []string{region},
 			IntervalNumber:   intervalNumber,
 			IntervalCount:    intervalCount,
-			// The rest of the model.Exposure fields are not used in the export file.
+			// The rest of the database.Exposure fields are not used in the export file.
 		}
 		exposures = append(exposures, ek)
 	}
diff --git a/internal/federationin/federationin.go b/internal/federationin/federationin.go
index 175aceb..0faee37 100644
--- a/internal/federationin/federationin.go
+++ b/internal/federationin/federationin.go
@@ -30,7 +30,6 @@ import (
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/metrics"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/exposure-notifications-server/internal/pb"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 
@@ -50,8 +49,8 @@ var (
 
 type (
 	fetchFn               func(context.Context, *pb.FederationFetchRequest, ...grpc.CallOption) (*pb.FederationFetchResponse, error)
-	insertExposuresFn     func(context.Context, []*model.Exposure) error
-	startFederationSyncFn func(context.Context, *model.FederationInQuery, time.Time) (int64, database.FinalizeSyncFn, error)
+	insertExposuresFn     func(context.Context, []*database.Exposure) error
+	startFederationSyncFn func(context.Context, *database.FederationInQuery, time.Time) (int64, database.FinalizeSyncFn, error)
 )
 
 type pullDependencies struct {
@@ -185,7 +184,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func pull(ctx context.Context, metrics metrics.Exporter, deps pullDependencies, q *model.FederationInQuery, batchStart time.Time, truncateWindow time.Duration) error {
+func pull(ctx context.Context, metrics metrics.Exporter, deps pullDependencies, q *database.FederationInQuery, batchStart time.Time, truncateWindow time.Duration) error {
 	logger := logging.FromContext(ctx)
 	logger.Infof("Processing query %q", q.QueryID)
 
@@ -206,7 +205,7 @@ func pull(ctx context.Context, metrics metrics.Exporter, deps pullDependencies,
 		logger.Infof("Inserted %d keys", total)
 	}()
 
-	createdAt := model.TruncateWindow(batchStart, truncateWindow)
+	createdAt := database.TruncateWindow(batchStart, truncateWindow)
 	partial := true
 	for partial {
 
@@ -223,7 +222,7 @@ func pull(ctx context.Context, metrics metrics.Exporter, deps pullDependencies,
 		}
 
 		// Loop through the result set, storing in database.
-		var exposures []*model.Exposure
+		var exposures []*database.Exposure
 		for _, ctr := range response.Response {
 
 			var upperRegions []string
@@ -235,12 +234,12 @@ func pull(ctx context.Context, metrics metrics.Exporter, deps pullDependencies,
 			for _, cti := range ctr.ContactTracingInfo {
 				for _, key := range cti.ExposureKeys {
 
-					if cti.TransmissionRisk < model.MinTransmissionRisk || cti.TransmissionRisk > model.MaxTransmissionRisk {
+					if cti.TransmissionRisk < database.MinTransmissionRisk || cti.TransmissionRisk > database.MaxTransmissionRisk {
 						logger.Errorf("invalid transmission risk %v - dropping record.", cti.TransmissionRisk)
 						continue
 					}
 
-					exposures = append(exposures, &model.Exposure{
+					exposures = append(exposures, &database.Exposure{
 						TransmissionRisk: int(cti.TransmissionRisk),
 						ExposureKey:      key.ExposureKey,
 						Regions:          upperRegions,
diff --git a/internal/federationout/federationout.go b/internal/federationout/federationout.go
index b2ad28a..2170e74 100644
--- a/internal/federationout/federationout.go
+++ b/internal/federationout/federationout.go
@@ -25,7 +25,6 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/logging"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/exposure-notifications-server/internal/pb"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	"google.golang.org/api/idtoken"
@@ -43,7 +42,7 @@ const (
 // Compile time assert that this server implements the required grpc interface.
 var _ pb.FederationServer = (*Server)(nil)
 
-type iterateExposuresFunc func(context.Context, database.IterateExposuresCriteria, func(*model.Exposure) error) (string, error)
+type iterateExposuresFunc func(context.Context, database.IterateExposuresCriteria, func(*database.Exposure) error) (string, error)
 
 // NewServer builds a new FederationServer.
 func NewServer(env *serverenv.ServerEnv, config *Config) pb.FederationServer {
@@ -67,7 +66,7 @@ func (s Server) Fetch(ctx context.Context, req *pb.FederationFetchRequest) (*pb.
 	ctx, cancel := context.WithTimeout(ctx, s.config.Timeout)
 	defer cancel()
 	logger := logging.FromContext(ctx)
-	response, err := s.fetch(ctx, req, s.db.IterateExposures, model.TruncateWindow(time.Now(), s.config.TruncateWindow)) // Don't fetch the current window, which isn't complete yet. TODO(squee1945): should I double this for safety?
+	response, err := s.fetch(ctx, req, s.db.IterateExposures, database.TruncateWindow(time.Now(), s.config.TruncateWindow)) // Don't fetch the current window, which isn't complete yet. TODO(squee1945): should I double this for safety?
 	if err != nil {
 		s.env.MetricsExporter(ctx).WriteInt("federation-fetch-failed", true, 1)
 		logger.Errorf("Fetch error: %v", err)
@@ -92,7 +91,7 @@ func (s Server) fetch(ctx context.Context, req *pb.FederationFetchRequest, itFun
 	logger.Infof("Processing client request %#v", req)
 
 	// If there is a FederationAuthorization on the context, set the query to operate within its limits.
-	if auth, ok := ctx.Value(authKey{}).(*model.FederationOutAuthorization); ok {
+	if auth, ok := ctx.Value(authKey{}).(*database.FederationOutAuthorization); ok {
 		// For included regions, we INTERSECT the requested included regions with the configured included regions.
 		req.RegionIdentifiers = intersect(req.RegionIdentifiers, auth.IncludeRegions)
 		// For excluded regions, we UNION the the requested excluded regions with the configured excluded regions.
@@ -126,7 +125,7 @@ func (s Server) fetch(ctx context.Context, req *pb.FederationFetchRequest, itFun
 	ctiMap := map[string]*pb.ContactTracingInfo{}     // local index into the response being assembled; keys on unique set of (ctrMap key, transmissionRisk, verificationAuthorityName)
 	response := &pb.FederationFetchResponse{}
 	count := 0
-	cursor, err := itFunc(ctx, criteria, func(inf *model.Exposure) error {
+	cursor, err := itFunc(ctx, criteria, func(inf *database.Exposure) error {
 		// If the diagnosis key is empty, it's malformed, so skip it.
 		if len(inf.ExposureKey) == 0 {
 			logger.Debugf("Exposure %s missing ExposureKey, skipping.", inf.ExposureKey)
diff --git a/internal/publish/publish.go b/internal/publish/publish.go
index d1cd030..be15dd6 100644
--- a/internal/publish/publish.go
+++ b/internal/publish/publish.go
@@ -25,7 +25,6 @@ import (
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/jsonutil"
 	"github.com/google/exposure-notifications-server/internal/logging"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	"github.com/google/exposure-notifications-server/internal/verification"
 )
@@ -41,9 +40,9 @@ 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.MaxKeysOnPublish, config.MaxIntervalAge, config.TruncateWindow)
+	transformer, err := database.NewTransformer(config.MaxKeysOnPublish, config.MaxIntervalAge, config.TruncateWindow)
 	if err != nil {
-		return nil, fmt.Errorf("model.NewTransformer: %w", err)
+		return nil, fmt.Errorf("database.NewTransformer: %w", err)
 	}
 	logger.Infof("max keys per upload: %v", config.MaxKeysOnPublish)
 	logger.Infof("max interval start age: %v", config.MaxIntervalAge)
@@ -61,7 +60,7 @@ func NewHandler(ctx context.Context, config *Config, env *serverenv.ServerEnv) (
 type publishHandler struct {
 	config                *Config
 	serverenv             *serverenv.ServerEnv
-	transformer           *model.Transformer
+	transformer           *database.Transformer
 	database              *database.DB
 	authorizedAppProvider authorizedapp.Provider
 }
@@ -73,7 +72,7 @@ func (h *publishHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	logger := logging.FromContext(ctx)
 	metrics := h.serverenv.MetricsExporter(ctx)
 
-	var data *model.Publish
+	var data *database.Publish
 	code, err := jsonutil.Unmarshal(w, r, &data)
 	if err != nil {
 		// Log the unparsable JSON, but return success to the client.
diff --git a/internal/verification/verify.go b/internal/verification/verify.go
index 9206070..3258830 100644
--- a/internal/verification/verify.go
+++ b/internal/verification/verify.go
@@ -20,8 +20,8 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/android"
+	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/ios"
-	"github.com/google/exposure-notifications-server/internal/model"
 )
 
 var (
@@ -31,7 +31,7 @@ var (
 
 // VerifyRegions checks the request regions against the regions allowed by
 // the configuration for the application.
-func VerifyRegions(cfg *model.AuthorizedApp, data *model.Publish) error {
+func VerifyRegions(cfg *database.AuthorizedApp, data *database.Publish) error {
 	if cfg == nil {
 		return fmt.Errorf("no allowed regions configured")
 	}
@@ -52,7 +52,7 @@ func VerifyRegions(cfg *model.AuthorizedApp, data *model.Publish) error {
 
 // VerifySafetyNet verifies the Android SafetyNet device attestation against the
 // allowed configuration for the application.
-func VerifySafetyNet(ctx context.Context, requestTime time.Time, cfg *model.AuthorizedApp, publish *model.Publish) error {
+func VerifySafetyNet(ctx context.Context, requestTime time.Time, cfg *database.AuthorizedApp, publish *database.Publish) error {
 	if cfg == nil {
 		return fmt.Errorf("cannot enforce SafetyNet, missing config")
 	}
@@ -66,7 +66,7 @@ func VerifySafetyNet(ctx context.Context, requestTime time.Time, cfg *model.Auth
 }
 
 // VerifyDeviceCheck verifies an iOS DeviceCheck token against the Apple API.
-func VerifyDeviceCheck(ctx context.Context, cfg *model.AuthorizedApp, data *model.Publish) error {
+func VerifyDeviceCheck(ctx context.Context, cfg *database.AuthorizedApp, data *database.Publish) error {
 	if cfg == nil {
 		return fmt.Errorf("cannot enforce DeviceCheck, missing config")
 	}
diff --git a/tools/export-config/main.go b/tools/export-config/main.go
index d1c1f11..7e7c225 100644
--- a/tools/export-config/main.go
+++ b/tools/export-config/main.go
@@ -23,7 +23,6 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/database"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/kelseyhightower/envconfig"
 )
 
@@ -90,7 +89,7 @@ func main() {
 	}
 	defer db.Close(ctx)
 
-	si := model.SignatureInfo{
+	si := database.SignatureInfo{
 		SigningKey:        *signingKey,
 		AppPackageName:    *appPkgID,
 		BundleID:          *bundleID,
@@ -101,7 +100,7 @@ func main() {
 		log.Fatalf("AddSignatureInfo: %v", err)
 	}
 
-	ec := model.ExportConfig{
+	ec := database.ExportConfig{
 		BucketName:       *bucketName,
 		FilenameRoot:     *filenameRoot,
 		Period:           *period,
diff --git a/tools/exposure-client/main.go b/tools/exposure-client/main.go
index 467466d..a5b787d 100644
--- a/tools/exposure-client/main.go
+++ b/tools/exposure-client/main.go
@@ -27,7 +27,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/model"
+	"github.com/google/exposure-notifications-server/internal/database"
 )
 
 const (
@@ -83,7 +83,7 @@ func main() {
 		log.Printf("error generating padding: %v", err)
 	}
 
-	data := model.Publish{
+	data := database.Publish{
 		Keys:           exposureKeys,
 		Regions:        region,
 		AppPackageName: *appPackage,
@@ -134,7 +134,7 @@ func randomArrValue(arr []string) string {
 	return arr[randomInt(len(arr))]
 }
 
-func generateExposureKeys(numKeys, tr int) []model.ExposureKey {
+func generateExposureKeys(numKeys, tr int) []database.ExposureKey {
 	keys := make([][]byte, numKeys)
 	for i := 0; i < numKeys; i++ {
 		keys[i] = make([]byte, dkLen)
@@ -147,7 +147,7 @@ func generateExposureKeys(numKeys, tr int) []model.ExposureKey {
 	// When publishing multiple keys - they'll be on different days.
 	intervalCount := randIntervalCount()
 	intervalNumber := int32(time.Now().Unix()/600) - intervalCount
-	exposureKeys := make([]model.ExposureKey, numKeys)
+	exposureKeys := make([]database.ExposureKey, numKeys)
 	for i, rawKey := range keys {
 		transmissionRisk := tr
 		if transmissionRisk < 0 {
diff --git a/tools/federationin-query/main.go b/tools/federationin-query/main.go
index 6175ffc..6bae60e 100644
--- a/tools/federationin-query/main.go
+++ b/tools/federationin-query/main.go
@@ -25,7 +25,6 @@ import (
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/federationin"
 	cflag "github.com/google/exposure-notifications-server/internal/flag"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/kelseyhightower/envconfig"
 )
 
@@ -88,7 +87,7 @@ func main() {
 	}
 	defer db.Close(ctx)
 
-	query := &model.FederationInQuery{
+	query := &database.FederationInQuery{
 		QueryID:        *queryID,
 		ServerAddr:     *serverAddr,
 		Audience:       *audience,
diff --git a/tools/federationout-authorization/main.go b/tools/federationout-authorization/main.go
index d1c024f..9158c1a 100644
--- a/tools/federationout-authorization/main.go
+++ b/tools/federationout-authorization/main.go
@@ -24,7 +24,6 @@ import (
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/federationin"
 	cflag "github.com/google/exposure-notifications-server/internal/flag"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/kelseyhightower/envconfig"
 )
 
@@ -81,7 +80,7 @@ func main() {
 	}
 	defer db.Close(ctx)
 
-	auth := &model.FederationOutAuthorization{
+	auth := &database.FederationOutAuthorization{
 		Issuer:         defaultIssuer, // Authorization interceptor currently only supports defaultIssuer.
 		Subject:        *subject,
 		Audience:       *audience,


Test patch:
diff --git a/internal/android/safetynet_test.go b/internal/android/safetynet_test.go
index 3ffdb70..cec9651 100644
--- a/internal/android/safetynet_test.go
+++ b/internal/android/safetynet_test.go
@@ -20,7 +20,7 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/base64util"
-	"github.com/google/exposure-notifications-server/internal/model"
+	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/go-cmp/cmp"
 )
 
@@ -38,8 +38,8 @@ var (
 	// **This comment applies to all test data in this file.**
 	payload = `eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlGa3pDQ0JIdWdBd0lCQWdJUkFOY1NramRzNW42K0NBQUFBQUFwYTBjd0RRWUpLb1pJaHZjTkFRRUxCUUF3UWpFTE1Ba0dBMVVFQmhNQ1ZWTXhIakFjQmdOVkJBb1RGVWR2YjJkc1pTQlVjblZ6ZENCVFpYSjJhV05sY3pFVE1CRUdBMVVFQXhNS1IxUlRJRU5CSURGUE1UQWVGdzB5TURBeE1UTXhNVFF4TkRsYUZ3MHlNVEF4TVRFeE1UUXhORGxhTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxTmIzVnVkR0ZwYmlCV2FXVjNNUk13RVFZRFZRUUtFd3BIYjI5bmJHVWdURXhETVJzd0dRWURWUVFERXhKaGRIUmxjM1F1WVc1a2NtOXBaQzVqYjIwd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXRXJCUVRHWkdOMWlaYk45ZWhSZ2lmV0J4cWkyUGRneHcwM1A3VHlKWmZNeGpwNUw3ajFHTmVQSzVIemRyVW9JZDF5Q0l5Qk15eHFnYXpxZ3RwWDVXcHNYVzRWZk1oSmJOMVkwOXF6cXA2SkQrMlBaZG9UVTFrRlJBTVdmTC9VdVp0azdwbVJYZ0dtNWpLRHJaOU54ZTA0dk1ZUXI4OE5xd1cva2ZaMWdUT05JVVQwV3NMVC80NTIyQlJXeGZ3eGMzUUUxK1RLV2tMQ3J2ZWs2V2xJcXlhQzUyVzdNRFI4TXBGZWJ5bVNLVHZ3Zk1Sd3lLUUxUMDNVTDR2dDQ4eUVjOHNwN3dUQUhNL1dEZzhRb3RhcmY4T0JIa25vWjkyWGl2aWFWNnRRcWhST0hDZmdtbkNYaXhmVzB3RVhDdnFpTFRiUXRVYkxzUy84SVJ0ZFhrcFFCOUFnTUJBQUdqZ2dKWU1JSUNWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUhBd0V3REFZRFZSMFRBUUgvQkFJd0FEQWRCZ05WSFE0RUZnUVU2REhCd3NBdmI1M2cvQzA3cHJUdnZ3TlFRTFl3SHdZRFZSMGpCQmd3Rm9BVW1OSDRiaERyejV2c1lKOFlrQnVnNjMwSi9Tc3daQVlJS3dZQkJRVUhBUUVFV0RCV01DY0dDQ3NHQVFVRkJ6QUJoaHRvZEhSd09pOHZiMk56Y0M1d2Eya3VaMjl2Wnk5bmRITXhiekV3S3dZSUt3WUJCUVVITUFLR0gyaDBkSEE2THk5d2Eya3VaMjl2Wnk5bmMzSXlMMGRVVXpGUE1TNWpjblF3SFFZRFZSMFJCQll3RklJU1lYUjBaWE4wTG1GdVpISnZhV1F1WTI5dE1DRUdBMVVkSUFRYU1CZ3dDQVlHWjRFTUFRSUNNQXdHQ2lzR0FRUUIxbmtDQlFNd0x3WURWUjBmQkNnd0pqQWtvQ0tnSUlZZWFIUjBjRG92TDJOeWJDNXdhMmt1WjI5dlp5OUhWRk14VHpFdVkzSnNNSUlCQkFZS0t3WUJCQUhXZVFJRUFnU0I5UVNCOGdEd0FIY0E5bHlVTDlGM01DSVVWQmdJTUpSV2p1Tk5FeGt6djk4TUx5QUx6RTd4Wk9NQUFBRnZudXkwWndBQUJBTUFTREJHQWlFQTdlLzBZUnUzd0FGbVdIMjdNMnZiVmNaL21ycCs0cmZZYy81SVBKMjlGNmdDSVFDbktDQ0FhY1ZOZVlaOENDZllkR3BCMkdzSHh1TU9Ia2EvTzQxaldlRit6Z0IxQUVTVVpTNnc3czZ2eEVBSDJLaitLTURhNW9LKzJNc3h0VC9UTTVhMXRvR29BQUFCYjU3c3RKTUFBQVFEQUVZd1JBSWdFWGJpb1BiSnA5cUMwRGoyNThERkdTUk1BVStaQjFFaVZFYmJiLzRVdk5FQ0lCaEhrQnQxOHZSbjl6RHZ5cmZ4eXVkY0hUT1NsM2dUYVlBLzd5VC9CaUg0TUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFESUFjUUJsbWQ4TUVnTGRycnJNYkJUQ3ZwTVhzdDUrd3gyRGxmYWpKTkpVUDRqWUZqWVVROUIzWDRFMnpmNDluWDNBeXVaRnhBcU9SbmJqLzVqa1k3YThxTUowajE5ekZPQitxZXJ4ZWMwbmhtOGdZbExiUW02c0tZN1AwZXhmcjdIdUszTWtQMXBlYzE0d0ZFVWFHcUR3VWJHZ2wvb2l6MzhGWENFK0NXOEUxUUFFVWZ2YlFQVFliS3hZait0Q05sc3MwYlRTb0wyWjJkL2ozQnBMM01GdzB5eFNLL1VUcXlrTHIyQS9NZGhKUW14aStHK01LUlNzUXI2MkFuWmF1OXE2WUZvaSs5QUVIK0E0OFh0SXlzaEx5Q1RVM0h0K2FLb2hHbnhBNXVsMVhSbXFwOEh2Y0F0MzlQOTVGWkdGSmUwdXZseWpPd0F6WHVNdTdNK1BXUmMiLCJNSUlFU2pDQ0F6S2dBd0lCQWdJTkFlTzBtcUdOaXFtQkpXbFF1REFOQmdrcWhraUc5dzBCQVFzRkFEQk1NU0F3SGdZRFZRUUxFeGRIYkc5aVlXeFRhV2R1SUZKdmIzUWdRMEVnTFNCU01qRVRNQkVHQTFVRUNoTUtSMnh2WW1Gc1UybG5iakVUTUJFR0ExVUVBeE1LUjJ4dlltRnNVMmxuYmpBZUZ3MHhOekEyTVRVd01EQXdOREphRncweU1URXlNVFV3TURBd05ESmFNRUl4Q3pBSkJnTlZCQVlUQWxWVE1SNHdIQVlEVlFRS0V4VkhiMjluYkdVZ1ZISjFjM1FnVTJWeWRtbGpaWE14RXpBUkJnTlZCQU1UQ2tkVVV5QkRRU0F4VHpFd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURRR005RjFJdk4wNXprUU85K3ROMXBJUnZKenp5T1RIVzVEekVaaEQyZVBDbnZVQTBRazI4RmdJQ2ZLcUM5RWtzQzRUMmZXQllrL2pDZkMzUjNWWk1kUy9kTjRaS0NFUFpSckF6RHNpS1VEelJybUJCSjV3dWRnem5kSU1ZY0xlL1JHR0ZsNXlPRElLZ2pFdi9TSkgvVUwrZEVhbHROMTFCbXNLK2VRbU1GKytBY3hHTmhyNTlxTS85aWw3MUkyZE44RkdmY2Rkd3VhZWo0YlhocDBMY1FCYmp4TWNJN0pQMGFNM1Q0SStEc2F4bUtGc2JqemFUTkM5dXpwRmxnT0lnN3JSMjV4b3luVXh2OHZObWtxN3pkUEdIWGt4V1k3b0c5aitKa1J5QkFCazdYckpmb3VjQlpFcUZKSlNQazdYQTBMS1cwWTN6NW96MkQwYzF0Skt3SEFnTUJBQUdqZ2dFek1JSUJMekFPQmdOVkhROEJBZjhFQkFNQ0FZWXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFBd0hRWURWUjBPQkJZRUZKalIrRzRRNjgrYjdHQ2ZHSkFib090OUNmMHJNQjhHQTFVZEl3UVlNQmFBRkp2aUIxZG5IQjdBYWdiZVdiU2FMZC9jR1lZdU1EVUdDQ3NHQVFVRkJ3RUJCQ2t3SnpBbEJnZ3JCZ0VGQlFjd0FZWVphSFIwY0RvdkwyOWpjM0F1Y0d0cExtZHZiMmN2WjNOeU1qQXlCZ05WSFI4RUt6QXBNQ2VnSmFBamhpRm9kSFJ3T2k4dlkzSnNMbkJyYVM1bmIyOW5MMmR6Y2pJdlozTnlNaTVqY213d1B3WURWUjBnQkRnd05qQTBCZ1puZ1F3QkFnSXdLakFvQmdnckJnRUZCUWNDQVJZY2FIUjBjSE02THk5d2Eya3VaMjl2Wnk5eVpYQnZjMmwwYjNKNUx6QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFHb0ErTm5uNzh5NnBSamQ5WGxRV05hN0hUZ2laL3IzUk5Ha21VbVlIUFFxNlNjdGk5UEVhanZ3UlQyaVdUSFFyMDJmZXNxT3FCWTJFVFV3Z1pRK2xsdG9ORnZoc085dHZCQ09JYXpwc3dXQzlhSjl4anU0dFdEUUg4TlZVNllaWi9YdGVEU0dVOVl6SnFQalk4cTNNRHhyem1xZXBCQ2Y1bzhtdy93SjRhMkc2eHpVcjZGYjZUOE1jRE8yMlBMUkw2dTNNNFR6czNBMk0xajZieWtKWWk4d1dJUmRBdktMV1p1L2F4QlZielltcW13a201ekxTRFc1bklBSmJFTENRQ1p3TUg1NnQyRHZxb2Z4czZCQmNDRklaVVNweHU2eDZ0ZDBWN1N2SkNDb3NpclNtSWF0ai85ZFNTVkRRaWJldDhxLzdVSzR2NFpVTjgwYXRuWnoxeWc9PSJdfQ.eyJub25jZSI6IlZreEphblUwYW5CcFkydDNObEZFZVM5d2IzRk9iRlprTHpCaFNYTktiRTVqYkVGSmFESnFRbm94VFQwPSIsInRpbWVzdGFtcE1zIjoxNTg5NTg4NDA4MTg1LCJhcGtQYWNrYWdlTmFtZSI6ImNvbS5nb29nbGUuYW5kcm9pZC5hcHBzLmV4cG9zdXJlbm90aWZpY2F0aW9uIiwiYXBrRGlnZXN0U2hhMjU2IjoiYWt2Y3oxdWgyak85NzF5blZ1bXdsSEM4L2xHZG5vYjVGVlNIWVlabCtWcz0iLCJjdHNQcm9maWxlTWF0Y2giOnRydWUsImFwa0NlcnRpZmljYXRlRGlnZXN0U2hhMjU2IjpbImpxbVlFcWk5cVV2cFVlMTFxTWYzdjJvNlZFUU0rNU5EZWUyYnoweGR6V2M9Il0sImJhc2ljSW50ZWdyaXR5Ijp0cnVlLCJldmFsdWF0aW9uVHlwZSI6IkJBU0lDIn0.cU97Z9Fax39ehQvKpa8C0I0hXIh1zclZErn5uDGdtWsVTddpf1qxSVnAwxAGhBmqbOF_YL-75dGojxT8dsGpe7LBXQr9qyRm79FjfIAAqDQrblvOxIatGbR1qkmLJITF7DAc4sAcK8CP2RxIrAkUIAUAdgwsx4JbXeNMfPxR-XODMo93MaMRLV5byA1MDaRw3RA-HTx81LEAuQJfUcoRf-O7WTnBBYDUDlUAY5_VNp7wGRKg9eNNzbbysPqpS-o-sW67dvsalav2ENwwQT9RJwWR4YCI4bDh_qH3Mz8FfChwefS1AW4nOPsOJwkEhLHIO521ZQiwZVct1b6glPc08g`
 
-	publish = &model.Publish{
-		Keys: []model.ExposureKey{
+	publish = &database.Publish{
+		Keys: []database.ExposureKey{
 			{Key: "QLIvVheW9p6JiTx4pslesg==", IntervalNumber: 2649312, IntervalCount: 144, TransmissionRisk: 1},
 			{Key: "UW7pkDKfLbfLs8LveFyY3w==", IntervalNumber: 2649168, IntervalCount: 144, TransmissionRisk: 1},
 		},
@@ -192,11 +192,11 @@ func TestVerifyOptsFor(t *testing.T) {
 	testTime := time.Date(2020, 1, 13, 5, 6, 4, 6, time.Local)
 
 	cases := []struct {
-		cfg  *model.AuthorizedApp
+		cfg  *database.AuthorizedApp
 		opts *VerifyOpts
 	}{
 		{
-			cfg: &model.AuthorizedApp{
+			cfg: &database.AuthorizedApp{
 				AppPackageName:    "foo",
 				CTSProfileMatch:   true,
 				BasicIntegrity:    true,
@@ -213,7 +213,7 @@ func TestVerifyOptsFor(t *testing.T) {
 			},
 		},
 		{
-			cfg: &model.AuthorizedApp{
+			cfg: &database.AuthorizedApp{
 				AppPackageName:    "foo",
 				CTSProfileMatch:   false,
 				BasicIntegrity:    true,
@@ -230,7 +230,7 @@ func TestVerifyOptsFor(t *testing.T) {
 			},
 		},
 		{
-			cfg: &model.AuthorizedApp{
+			cfg: &database.AuthorizedApp{
 				AppPackageName:    "foo",
 				ApkDigestSHA256:   []string{"bar"},
 				CTSProfileMatch:   false,
diff --git a/internal/model/authorized_app_test.go b/internal/database/authorized_app_model_test.go
similarity index 97%
rename from internal/model/authorized_app_test.go
rename to internal/database/authorized_app_model_test.go
index 23aa5e0..a193f29 100644
--- a/internal/model/authorized_app_test.go
+++ b/internal/database/authorized_app_model_test.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package model
+package database
 
 import (
 	"testing"
diff --git a/internal/database/authorized_app_test.go b/internal/database/authorized_app_test.go
index c157673..d6b9c23 100644
--- a/internal/database/authorized_app_test.go
+++ b/internal/database/authorized_app_test.go
@@ -25,7 +25,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 )
@@ -75,7 +74,7 @@ func TestGetAuthorizedApp(t *testing.T) {
 		name string
 		sql  string
 		args []interface{}
-		exp  *model.AuthorizedApp
+		exp  *AuthorizedApp
 		err  bool
 	}{
 		{
@@ -85,7 +84,7 @@ func TestGetAuthorizedApp(t *testing.T) {
 				VALUES ($1, $2, $3)
 			`,
 			args: []interface{}{"myapp", "ios", []string{"US"}},
-			exp: &model.AuthorizedApp{
+			exp: &AuthorizedApp{
 				AppPackageName:  "myapp",
 				Platform:        "ios",
 				AllowedRegions:  map[string]struct{}{"US": {}},
@@ -102,7 +101,7 @@ func TestGetAuthorizedApp(t *testing.T) {
 				) VALUES ($1, $2, $3, $4)
 			`,
 			args: []interface{}{"myapp", "ios", []string{"US"}, 1800},
-			exp: &model.AuthorizedApp{
+			exp: &AuthorizedApp{
 				AppPackageName:  "myapp",
 				Platform:        "ios",
 				AllowedRegions:  map[string]struct{}{"US": {}},
@@ -120,7 +119,7 @@ func TestGetAuthorizedApp(t *testing.T) {
 				) VALUES ($1, $2, $3, $4)
 			`,
 			args: []interface{}{"myapp", "ios", []string{"US"}, 1800},
-			exp: &model.AuthorizedApp{
+			exp: &AuthorizedApp{
 				AppPackageName:    "myapp",
 				Platform:          "ios",
 				AllowedRegions:    map[string]struct{}{"US": {}},
@@ -138,7 +137,7 @@ func TestGetAuthorizedApp(t *testing.T) {
 				) VALUES ($1, $2, $3, $4, $5, $6)
 			`,
 			args: []interface{}{"myapp", "ios", []string{"US"}, "team_id", "key_id", "private_key"},
-			exp: &model.AuthorizedApp{
+			exp: &AuthorizedApp{
 				AppPackageName:        "myapp",
 				Platform:              "ios",
 				AllowedRegions:        map[string]struct{}{"US": {}},
diff --git a/internal/database/export_test.go b/internal/database/export_test.go
index a426ff6..cf45d1a 100644
--- a/internal/database/export_test.go
+++ b/internal/database/export_test.go
@@ -21,7 +21,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/go-cmp/cmp"
 	pgx "github.com/jackc/pgx/v4"
 )
@@ -34,7 +33,7 @@ func TestAddSignatureInfo(t *testing.T) {
 	ctx := context.Background()
 
 	thruTime := time.Now().UTC().Add(6 * time.Hour).Truncate(time.Microsecond)
-	want := &model.SignatureInfo{
+	want := &SignatureInfo{
 		SigningKey:        "/kms/project/key/1",
 		SigningKeyVersion: "1",
 		SigningKeyID:      "310",
@@ -48,7 +47,7 @@ func TestAddSignatureInfo(t *testing.T) {
 		t.Fatal(err)
 	}
 	defer conn.Release()
-	var got model.SignatureInfo
+	var got SignatureInfo
 	err = conn.QueryRow(ctx, `
 		SELECT
 		  id, signing_key, app_package_name, bundle_id, signing_key_version, signing_key_id, thru_timestamp
@@ -74,20 +73,20 @@ func TestLookupSignatureInfos(t *testing.T) {
 	ctx := context.Background()
 
 	testTime := time.Now().UTC()
-	want := []*model.SignatureInfo{
-		&model.SignatureInfo{
+	want := []*SignatureInfo{
+		{
 			SigningKey:        "/kms/project/key/version/1",
 			SigningKeyVersion: "1",
 			SigningKeyID:      "310",
 			EndTimestamp:      testTime.Add(-1 * time.Hour).Truncate(time.Microsecond),
 		},
-		&model.SignatureInfo{
+		{
 			SigningKey:        "/kms/project/key/version/2",
 			SigningKeyVersion: "2",
 			SigningKeyID:      "310",
 			EndTimestamp:      testTime.Add(24 * time.Hour).Truncate(time.Microsecond),
 		},
-		&model.SignatureInfo{
+		{
 			SigningKey:        "/kms/project/key/version/3",
 			SigningKeyVersion: "3",
 			SigningKeyID:      "310",
@@ -120,7 +119,7 @@ func TestAddExportConfig(t *testing.T) {
 
 	fromTime := time.Now()
 	thruTime := fromTime.Add(6 * time.Hour)
-	want := &model.ExportConfig{
+	want := &ExportConfig{
 		BucketName:       "mocked",
 		FilenameRoot:     "root",
 		Period:           3 * time.Hour,
@@ -138,7 +137,7 @@ func TestAddExportConfig(t *testing.T) {
 	}
 	defer conn.Release()
 	var (
-		got   model.ExportConfig
+		got   ExportConfig
 		psecs int
 	)
 	err = conn.QueryRow(ctx, `
@@ -169,7 +168,7 @@ func TestIterateExportConfigs(t *testing.T) {
 	ctx := context.Background()
 
 	now := time.Now().Truncate(time.Microsecond)
-	ecs := []*model.ExportConfig{
+	ecs := []*ExportConfig{
 		{
 			BucketName:   "b 1",
 			FilenameRoot: "active 1",
@@ -202,8 +201,8 @@ func TestIterateExportConfigs(t *testing.T) {
 		}
 	}
 
-	var got []*model.ExportConfig
-	err := testDB.IterateExportConfigs(ctx, now, func(m *model.ExportConfig) error {
+	var got []*ExportConfig
+	err := testDB.IterateExportConfigs(ctx, now, func(m *ExportConfig) error {
 		got = append(got, m)
 		return nil
 	})
@@ -225,7 +224,7 @@ func TestBatches(t *testing.T) {
 	ctx := context.Background()
 
 	now := time.Now().Truncate(time.Microsecond)
-	config := &model.ExportConfig{
+	config := &ExportConfig{
 		BucketName:       "mocked",
 		FilenameRoot:     "root",
 		Period:           time.Hour,
@@ -237,18 +236,18 @@ func TestBatches(t *testing.T) {
 	if err := testDB.AddExportConfig(ctx, config); err != nil {
 		t.Fatal(err)
 	}
-	var batches []*model.ExportBatch
+	var batches []*ExportBatch
 	var wantLatest time.Time
 	for i := 0; i < 4; i++ {
 		start := now.Add(time.Duration(i) * time.Minute)
 		end := start.Add(time.Minute)
 		wantLatest = end
-		batches = append(batches, &model.ExportBatch{
+		batches = append(batches, &ExportBatch{
 			ConfigID:         config.ConfigID,
 			BucketName:       config.BucketName,
 			FilenameRoot:     config.FilenameRoot,
 			Region:           config.Region,
-			Status:           model.ExportBatchOpen,
+			Status:           ExportBatchOpen,
 			StartTimestamp:   start,
 			EndTimestamp:     end,
 			SignatureInfoIDs: []int64{1, 2, 3, 4},
@@ -284,7 +283,7 @@ func TestBatches(t *testing.T) {
 					got.ConfigID, got.BucketName, got.FilenameRoot, got.Region,
 					config.ConfigID, config.BucketName, config.FilenameRoot, config.Region)
 			}
-			if got.Status != model.ExportBatchPending {
+			if got.Status != ExportBatchPending {
 				t.Errorf("LeaseBatch: got status %q, want pending", got.Status)
 			}
 			wantExpires := now.Add(time.Hour)
@@ -322,7 +321,7 @@ func TestBatches(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	if got.Status != model.ExportBatchComplete {
+	if got.Status != ExportBatchComplete {
 		t.Errorf("after completion: got status %q, want complete", got.Status)
 	}
 }
@@ -336,7 +335,7 @@ func TestFinalizeBatch(t *testing.T) {
 	now := time.Now().Truncate(time.Microsecond)
 
 	// Add a config.
-	ec := &model.ExportConfig{
+	ec := &ExportConfig{
 		BucketName:   "some-bucket",
 		FilenameRoot: "filename-root",
 		Period:       time.Minute,
@@ -347,16 +346,16 @@ func TestFinalizeBatch(t *testing.T) {
 	}
 
 	// Add a batch.
-	eb := &model.ExportBatch{
+	eb := &ExportBatch{
 		ConfigID:       ec.ConfigID,
 		BucketName:     ec.BucketName,
 		FilenameRoot:   ec.FilenameRoot,
 		StartTimestamp: now.Add(-2 * time.Hour),
 		EndTimestamp:   now.Add(-time.Hour),
 		Region:         ec.Region,
-		Status:         model.ExportBatchOpen,
+		Status:         ExportBatchOpen,
 	}
-	if err := testDB.AddExportBatches(ctx, []*model.ExportBatch{eb}); err != nil {
+	if err := testDB.AddExportBatches(ctx, []*ExportBatch{eb}); err != nil {
 		t.Fatal(err)
 	}
 
@@ -371,8 +370,8 @@ func TestFinalizeBatch(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	if gotBatch.Status != model.ExportBatchPending {
-		t.Errorf("pre gotBatch.Status=%q, want=%q", gotBatch.Status, model.ExportBatchPending)
+	if gotBatch.Status != ExportBatchPending {
+		t.Errorf("pre gotBatch.Status=%q, want=%q", gotBatch.Status, ExportBatchPending)
 	}
 
 	// Finalize the batch.
@@ -387,8 +386,8 @@ func TestFinalizeBatch(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	if gotBatch.Status != model.ExportBatchComplete {
-		t.Errorf("post gotBatch.Status=%q, want=%q", gotBatch.Status, model.ExportBatchComplete)
+	if gotBatch.Status != ExportBatchComplete {
+		t.Errorf("post gotBatch.Status=%q, want=%q", gotBatch.Status, ExportBatchComplete)
 	}
 
 	// Check that files were written.
@@ -405,14 +404,14 @@ func TestFinalizeBatch(t *testing.T) {
 		if err != nil {
 			t.Fatal(err)
 		}
-		want := &model.ExportFile{
+		want := &ExportFile{
 			BucketName: eb.BucketName,
 			Filename:   filename,
 			BatchID:    eb.BatchID,
 			Region:     eb.Region,
 			BatchNum:   i + 1,
 			BatchSize:  batchSize,
-			Status:     model.ExportBatchComplete,
+			Status:     ExportBatchComplete,
 		}
 		if diff := cmp.Diff(want, got); diff != "" {
 			t.Errorf("mismatch for %q (-want, +got):\n%s", filename, diff)
@@ -430,7 +429,7 @@ func TestKeysInBatch(t *testing.T) {
 	now := time.Now()
 
 	// Add a config.
-	ec := &model.ExportConfig{
+	ec := &ExportConfig{
 		BucketName:   "bucket-name",
 		FilenameRoot: "filename-root",
 		Period:       3600 * time.Second,
@@ -444,35 +443,35 @@ func TestKeysInBatch(t *testing.T) {
 	// Create a batch for two hours ago to one hour ago.
 	startTimestamp := now.Truncate(time.Hour).Add(-2 * time.Hour)
 	endTimestamp := startTimestamp.Add(time.Hour)
-	eb := &model.ExportBatch{
+	eb := &ExportBatch{
 		ConfigID:       ec.ConfigID,
 		BucketName:     ec.BucketName,
 		FilenameRoot:   ec.FilenameRoot,
 		StartTimestamp: startTimestamp,
 		EndTimestamp:   endTimestamp,
 		Region:         ec.Region,
-		Status:         model.ExportBatchOpen,
+		Status:         ExportBatchOpen,
 	}
-	if err := testDB.AddExportBatches(ctx, []*model.ExportBatch{eb}); err != nil {
+	if err := testDB.AddExportBatches(ctx, []*ExportBatch{eb}); err != nil {
 		t.Fatal(err)
 	}
 
 	// Create key aligned with the StartTimestamp
-	sek := &model.Exposure{
+	sek := &Exposure{
 		ExposureKey: []byte("aaa"),
 		Regions:     []string{ec.Region},
 		CreatedAt:   startTimestamp,
 	}
 
 	// Create key aligned with the EndTimestamp
-	eek := &model.Exposure{
+	eek := &Exposure{
 		ExposureKey: []byte("bbb"),
 		Regions:     []string{ec.Region},
 		CreatedAt:   endTimestamp,
 	}
 
 	// Add the keys to the database.
-	if err := testDB.InsertExposures(ctx, []*model.Exposure{sek, eek}); err != nil {
+	if err := testDB.InsertExposures(ctx, []*Exposure{sek, eek}); err != nil {
 		t.Fatal(err)
 	}
 
@@ -499,8 +498,8 @@ func TestKeysInBatch(t *testing.T) {
 		UntilTimestamp: leased.EndTimestamp,
 	}
 
-	var got []*model.Exposure
-	_, err = testDB.IterateExposures(ctx, criteria, func(exp *model.Exposure) error {
+	var got []*Exposure
+	_, err = testDB.IterateExposures(ctx, criteria, func(exp *Exposure) error {
 		got = append(got, exp)
 		return nil
 	})
@@ -526,12 +525,12 @@ func TestAddExportFileSkipsDuplicates(t *testing.T) {
 	ctx := context.Background()
 
 	// Add foreign key records.
-	ec := &model.ExportConfig{Period: time.Hour}
+	ec := &ExportConfig{Period: time.Hour}
 	if err := testDB.AddExportConfig(ctx, ec); err != nil {
 		t.Fatal(err)
 	}
-	eb := &model.ExportBatch{ConfigID: ec.ConfigID, Status: model.ExportBatchOpen}
-	if err := testDB.AddExportBatches(ctx, []*model.ExportBatch{eb}); err != nil {
+	eb := &ExportBatch{ConfigID: ec.ConfigID, Status: ExportBatchOpen}
+	if err := testDB.AddExportBatches(ctx, []*ExportBatch{eb}); err != nil {
 		t.Fatal(err)
 	}
 	// Lease the batch to get the ID.
@@ -541,7 +540,7 @@ func TestAddExportFileSkipsDuplicates(t *testing.T) {
 	}
 
 	wantBucketName := "bucket-1"
-	ef := &model.ExportFile{
+	ef := &ExportFile{
 		Filename:   "file",
 		BucketName: wantBucketName,
 		BatchID:    eb.BatchID,
diff --git a/internal/model/exposure_test.go b/internal/database/exposure_model_test.go
similarity index 99%
rename from internal/model/exposure_test.go
rename to internal/database/exposure_model_test.go
index 35bc3fc..a74f9c6 100644
--- a/internal/model/exposure_test.go
+++ b/internal/database/exposure_model_test.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package model
+package database
 
 import (
 	"crypto/rand"
diff --git a/internal/database/exposure_test.go b/internal/database/exposure_test.go
index a178a7c..79192ac 100644
--- a/internal/database/exposure_test.go
+++ b/internal/database/exposure_test.go
@@ -20,7 +20,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/go-cmp/cmp"
 )
 
@@ -33,7 +32,7 @@ func TestExposures(t *testing.T) {
 
 	// Insert some Exposures.
 	batchTime := time.Date(2020, 5, 1, 0, 0, 0, 0, time.UTC).Truncate(time.Microsecond)
-	exposures := []*model.Exposure{
+	exposures := []*Exposure{
 		{
 			ExposureKey:     []byte("ABC"),
 			Regions:         []string{"US", "CA", "MX"},
@@ -113,7 +112,7 @@ func TestExposures(t *testing.T) {
 		if err != nil {
 			t.Fatalf("%+v: %v", test.criteria, err)
 		}
-		var want []*model.Exposure
+		var want []*Exposure
 		for _, i := range test.want {
 			want = append(want, exposures[i])
 		}
@@ -141,9 +140,9 @@ func TestExposures(t *testing.T) {
 
 }
 
-func listExposures(ctx context.Context, c IterateExposuresCriteria) (_ []*model.Exposure, err error) {
-	var exps []*model.Exposure
-	_, err = testDB.IterateExposures(ctx, c, func(e *model.Exposure) error {
+func listExposures(ctx context.Context, c IterateExposuresCriteria) (_ []*Exposure, err error) {
+	var exps []*Exposure
+	_, err = testDB.IterateExposures(ctx, c, func(e *Exposure) error {
 		exps = append(exps, e)
 		return nil
 	})
@@ -160,7 +159,7 @@ func TestIterateExposuresCursor(t *testing.T) {
 	defer resetTestDB(t)
 	ctx, cancel := context.WithCancel(context.Background())
 	// Insert some Exposures.
-	exposures := []*model.Exposure{
+	exposures := []*Exposure{
 		{
 			ExposureKey:    []byte("ABC"),
 			Regions:        []string{"US", "CA", "MX"},
@@ -181,8 +180,8 @@ func TestIterateExposuresCursor(t *testing.T) {
 		t.Fatal(err)
 	}
 	// Iterate over them, canceling the context in the middle.
-	var seen []*model.Exposure
-	cursor, err := testDB.IterateExposures(ctx, IterateExposuresCriteria{}, func(e *model.Exposure) error {
+	var seen []*Exposure
+	cursor, err := testDB.IterateExposures(ctx, IterateExposuresCriteria{}, func(e *Exposure) error {
 		seen = append(seen, e)
 		if len(seen) == 2 {
 			cancel()
@@ -202,7 +201,7 @@ func TestIterateExposuresCursor(t *testing.T) {
 	ctx = context.Background()
 	seen = nil
 	cursor, err = testDB.IterateExposures(ctx, IterateExposuresCriteria{LastCursor: cursor},
-		func(e *model.Exposure) error { seen = append(seen, e); return nil })
+		func(e *Exposure) error { seen = append(seen, e); return nil })
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/internal/database/federationin_test.go b/internal/database/federationin_test.go
index 0970440..781278a 100644
--- a/internal/database/federationin_test.go
+++ b/internal/database/federationin_test.go
@@ -20,7 +20,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 )
@@ -34,7 +33,7 @@ func TestFederationIn(t *testing.T) {
 	ctx := context.Background()
 
 	ts := time.Date(2020, 5, 6, 0, 0, 0, 0, time.UTC)
-	want := &model.FederationInQuery{
+	want := &FederationInQuery{
 		QueryID:        "qid",
 		ServerAddr:     "addr",
 		IncludeRegions: []string{"MX"},
@@ -86,7 +85,7 @@ func TestFederationIn(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	wantSync := &model.FederationInSync{
+	wantSync := &FederationInSync{
 		SyncID:  syncID,
 		QueryID: want.QueryID,
 		Started: now,
diff --git a/internal/database/federationout_test.go b/internal/database/federationout_test.go
index 5dcd5d8..58fb2bc 100644
--- a/internal/database/federationout_test.go
+++ b/internal/database/federationout_test.go
@@ -19,7 +19,6 @@ import (
 	"errors"
 	"testing"
 
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/go-cmp/cmp"
 )
 
@@ -31,7 +30,7 @@ func TestFederationOutAuthorization(t *testing.T) {
 	defer resetTestDB(t)
 	ctx := context.Background()
 
-	want := &model.FederationOutAuthorization{
+	want := &FederationOutAuthorization{
 		Issuer:         "iss",
 		Subject:        "sub",
 		Audience:       "aud",
diff --git a/internal/export/worker_test.go b/internal/export/worker_test.go
index dc4c298..4407d3b 100644
--- a/internal/export/worker_test.go
+++ b/internal/export/worker_test.go
@@ -19,24 +19,24 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/google/exposure-notifications-server/internal/model"
+	"github.com/google/exposure-notifications-server/internal/database"
 )
 
 func TestRandomInt(t *testing.T) {
 	expected := make(map[int]bool)
-	for i := model.MinTransmissionRisk; i <= model.MaxTransmissionRisk; i++ {
+	for i := database.MinTransmissionRisk; i <= database.MaxTransmissionRisk; i++ {
 		expected[i] = true
 	}
 
 	// Run through 1,000 iterations. To ensure the entire range can be hit.
 	for i := 0; i < 1000; i++ {
-		v, err := randomInt(model.MinTransmissionRisk, model.MaxTransmissionRisk)
+		v, err := randomInt(database.MinTransmissionRisk, database.MaxTransmissionRisk)
 		if err != nil {
 			t.Fatalf("error getting random data")
 		}
-		if v < model.MinTransmissionRisk || v > model.MaxTransmissionRisk {
+		if v < database.MinTransmissionRisk || v > database.MaxTransmissionRisk {
 			t.Fatalf("random data outside expected bounds. %v <= %v <= %v",
-				model.MinTransmissionRisk, v, model.MaxTransmissionRisk)
+				database.MinTransmissionRisk, v, database.MaxTransmissionRisk)
 		}
 		delete(expected, v)
 	}
@@ -46,7 +46,7 @@ func TestRandomInt(t *testing.T) {
 }
 
 func TestDoNotPadZeroLength(t *testing.T) {
-	exposures := make([]*model.Exposure, 0)
+	exposures := make([]*database.Exposure, 0)
 	exposures, err := ensureMinNumExposures(exposures, "US", 1000, 100)
 	if err != nil {
 		t.Fatalf("unepected error: %v", err)
@@ -56,14 +56,14 @@ func TestDoNotPadZeroLength(t *testing.T) {
 	}
 }
 
-func addExposure(t *testing.T, exposures []*model.Exposure, interval, count int32, risk int) []*model.Exposure {
-	key := make([]byte, model.KeyLength)
+func addExposure(t *testing.T, exposures []*database.Exposure, interval, count int32, risk int) []*database.Exposure {
+	key := make([]byte, database.KeyLength)
 	_, err := rand.Read(key)
 	if err != nil {
 		t.Fatalf("error getting random data: %v", err)
 	}
 	return append(exposures,
-		&model.Exposure{
+		&database.Exposure{
 			ExposureKey:      key,
 			TransmissionRisk: risk,
 			IntervalNumber:   interval,
@@ -73,11 +73,11 @@ func addExposure(t *testing.T, exposures []*model.Exposure, interval, count int3
 
 func TestEnsureMinExposures(t *testing.T) {
 	expectedTR := make(map[int]bool)
-	for i := model.MinTransmissionRisk; i <= model.MaxTransmissionRisk; i++ {
+	for i := database.MinTransmissionRisk; i <= database.MaxTransmissionRisk; i++ {
 		expectedTR[i] = true
 	}
 	// Insert a few exposures - that will be used to base the interval information off of.
-	exposures := make([]*model.Exposure, 0)
+	exposures := make([]*database.Exposure, 0)
 	exposures = addExposure(t, exposures, 123456, 144, 0)
 	exposures = addExposure(t, exposures, 789101, 88, 0)
 	// all of these combinations are expected
diff --git a/internal/federationin/federationin_test.go b/internal/federationin/federationin_test.go
index 0cdc7ae..9fd0ffa 100644
--- a/internal/federationin/federationin_test.go
+++ b/internal/federationin/federationin_test.go
@@ -21,7 +21,6 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/metrics"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/exposure-notifications-server/internal/pb"
 
 	"github.com/google/go-cmp/cmp"
@@ -39,7 +38,7 @@ var (
 )
 
 // makeRemoteExposure returns a mock model.Exposure with LocalProvenance=false.
-func makeRemoteExposure(diagKey *pb.ExposureKey, diagStatus int, regions ...string) *model.Exposure {
+func makeRemoteExposure(diagKey *pb.ExposureKey, diagStatus int, regions ...string) *database.Exposure {
 	inf := makeExposure(diagKey, diagStatus, regions...)
 	inf.LocalProvenance = false
 	inf.FederationSyncID = syncID
@@ -65,10 +64,10 @@ func (r *remoteFetchServer) fetch(ctx context.Context, req *pb.FederationFetchRe
 
 // exposureDB mocks the database, recording exposure insertions.
 type exposureDB struct {
-	exposures []*model.Exposure
+	exposures []*database.Exposure
 }
 
-func (idb *exposureDB) insertExposures(ctx context.Context, exposures []*model.Exposure) error {
+func (idb *exposureDB) insertExposures(ctx context.Context, exposures []*database.Exposure) error {
 	idb.exposures = append(idb.exposures, exposures...)
 	return nil
 }
@@ -82,7 +81,7 @@ type syncDB struct {
 	totalInserted int
 }
 
-func (sdb *syncDB) startFederationSync(ctx context.Context, query *model.FederationInQuery, start time.Time) (int64, database.FinalizeSyncFn, error) {
+func (sdb *syncDB) startFederationSync(ctx context.Context, query *database.FederationInQuery, start time.Time) (int64, database.FinalizeSyncFn, error) {
 	sdb.syncStarted = true
 	timerStart := time.Now()
 	return syncID, func(maxTimestamp time.Time, totalInserted int) error {
@@ -100,7 +99,7 @@ func TestFederationPull(t *testing.T) {
 		name             string
 		batchSize        int
 		fetchResponses   []*pb.FederationFetchResponse
-		wantExposures    []*model.Exposure
+		wantExposures    []*database.Exposure
 		wantTokens       []string
 		wantMaxTimestamp time.Time
 	}{
@@ -136,7 +135,7 @@ func TestFederationPull(t *testing.T) {
 					FetchResponseKeyTimestamp: 400,
 				},
 			},
-			wantExposures: []*model.Exposure{
+			wantExposures: []*database.Exposure{
 				makeRemoteExposure(aaa, 1, "US"),
 				makeRemoteExposure(bbb, 1, "US"),
 				makeRemoteExposure(ccc, 2, "CA", "US"),
@@ -172,7 +171,7 @@ func TestFederationPull(t *testing.T) {
 					FetchResponseKeyTimestamp: 400,
 				},
 			},
-			wantExposures: []*model.Exposure{
+			wantExposures: []*database.Exposure{
 				makeRemoteExposure(aaa, 1, "US"),
 				makeRemoteExposure(bbb, 1, "US"),
 			},
@@ -213,7 +212,7 @@ func TestFederationPull(t *testing.T) {
 					FetchResponseKeyTimestamp: 400,
 				},
 			},
-			wantExposures: []*model.Exposure{
+			wantExposures: []*database.Exposure{
 				makeRemoteExposure(aaa, 8, "US"),
 				makeRemoteExposure(bbb, 8, "US"),
 				makeRemoteExposure(ccc, 7, "US"),
@@ -250,7 +249,7 @@ func TestFederationPull(t *testing.T) {
 					FetchResponseKeyTimestamp: 400,
 				},
 			},
-			wantExposures: []*model.Exposure{
+			wantExposures: []*database.Exposure{
 				makeRemoteExposure(aaa, 3, "US"),
 				makeRemoteExposure(bbb, 3, "US"),
 				makeRemoteExposure(ccc, 2, "CA", "US"),
@@ -264,7 +263,7 @@ func TestFederationPull(t *testing.T) {
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 			ctx := context.Background()
-			query := &model.FederationInQuery{}
+			query := &database.FederationInQuery{}
 			remote := remoteFetchServer{responses: tc.fetchResponses}
 			idb := exposureDB{}
 			sdb := syncDB{}
@@ -285,7 +284,7 @@ func TestFederationPull(t *testing.T) {
 				t.Fatalf("pull returned err=%v, want err=nil", err)
 			}
 
-			if diff := cmp.Diff(tc.wantExposures, idb.exposures, cmpopts.IgnoreFields(model.Exposure{}, "CreatedAt")); diff != "" {
+			if diff := cmp.Diff(tc.wantExposures, idb.exposures, cmpopts.IgnoreFields(database.Exposure{}, "CreatedAt")); diff != "" {
 				t.Errorf("exposures mismatch (-want +got):\n%s", diff)
 			}
 			if diff := cmp.Diff(tc.wantTokens, remote.gotTokens); diff != "" {
@@ -310,8 +309,8 @@ func TestFederationPull(t *testing.T) {
 	}
 }
 
-func makeExposure(diagKey *pb.ExposureKey, diagStatus int, regions ...string) *model.Exposure {
-	return &model.Exposure{
+func makeExposure(diagKey *pb.ExposureKey, diagStatus int, regions ...string) *database.Exposure {
+	return &database.Exposure{
 		Regions:          regions,
 		TransmissionRisk: diagStatus,
 		ExposureKey:      diagKey.ExposureKey,
diff --git a/internal/federationout/federationout_test.go b/internal/federationout/federationout_test.go
index cfde8c9..57cea09 100644
--- a/internal/federationout/federationout_test.go
+++ b/internal/federationout/federationout_test.go
@@ -21,7 +21,6 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/database"
-	"github.com/google/exposure-notifications-server/internal/model"
 	"github.com/google/exposure-notifications-server/internal/pb"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	"google.golang.org/grpc/metadata"
@@ -45,9 +44,9 @@ var (
 	ddd = &pb.ExposureKey{ExposureKey: []byte("ddd"), IntervalNumber: 4}
 )
 
-// makeExposure returns a mock model.Exposure.
-func makeExposure(diagKey *pb.ExposureKey, diagStatus int, regions ...string) *model.Exposure {
-	return &model.Exposure{
+// makeExposure returns a mock database.Exposure.
+func makeExposure(diagKey *pb.ExposureKey, diagStatus int, regions ...string) *database.Exposure {
+	return &database.Exposure{
 		Regions:          regions,
 		TransmissionRisk: diagStatus,
 		ExposureKey:      diagKey.ExposureKey,
@@ -61,11 +60,11 @@ func makeExposure(diagKey *pb.ExposureKey, diagStatus int, regions ...string) *m
 type timeout struct{}
 
 func iterFunc(elements []interface{}) iterateExposuresFunc {
-	return func(_ context.Context, _ database.IterateExposuresCriteria, f func(*model.Exposure) error) (string, error) {
+	return func(_ context.Context, _ database.IterateExposuresCriteria, f func(*database.Exposure) error) (string, error) {
 		var cursor string
 		for _, el := range elements {
 			switch v := el.(type) {
-			case *model.Exposure:
+			case *database.Exposure:
 				// Set the cursor to the most recent diagnosis key, suffixed with "_cursor".
 				cursor = string(v.ExposureKey) + "_cursor"
 				if err := f(v); err != nil {
diff --git a/internal/jsonutil/unmarshal_test.go b/internal/jsonutil/unmarshal_test.go
index 81b6b46..1a410ec 100644
--- a/internal/jsonutil/unmarshal_test.go
+++ b/internal/jsonutil/unmarshal_test.go
@@ -23,8 +23,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/model"
-
+	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/go-cmp/cmp"
 )
 
@@ -34,7 +33,7 @@ func TestInvalidHeader(t *testing.T) {
 	r.Header.Set("content-type", "application/text")
 
 	w := httptest.NewRecorder()
-	data := &model.Publish{}
+	data := &database.Publish{}
 	code, err := Unmarshal(w, r, data)
 
 	expCode := http.StatusUnsupportedMediaType
@@ -131,7 +130,7 @@ func TestValidPublishMessage(t *testing.T) {
 
 	w := httptest.NewRecorder()
 
-	got := &model.Publish{}
+	got := &database.Publish{}
 	code, err := Unmarshal(w, r, got)
 	if err != nil {
 		t.Fatalf("unexpected err, %v", err)
@@ -140,8 +139,8 @@ func TestValidPublishMessage(t *testing.T) {
 		t.Errorf("unmarshal wanted %v response code, got %v", http.StatusOK, code)
 	}
 
-	want := &model.Publish{
-		Keys: []model.ExposureKey{
+	want := &database.Publish{
+		Keys: []database.ExposureKey{
 			{Key: "ABC", IntervalNumber: intervalNumber, IntervalCount: 144, TransmissionRisk: 2},
 			{Key: "DEF", IntervalNumber: intervalNumber, IntervalCount: 122, TransmissionRisk: 2},
 			{Key: "123", IntervalNumber: intervalNumber, IntervalCount: 1, TransmissionRisk: 2},
@@ -165,7 +164,7 @@ func unmarshalTestHelper(t *testing.T, payloads []string, errors []string, expCo
 		r.Header.Set("content-type", "application/json")
 
 		w := httptest.NewRecorder()
-		data := &model.Publish{}
+		data := &database.Publish{}
 		code, err := Unmarshal(w, r, data)
 		if code != expCode {
 			t.Errorf("unmarshal wanted %v response code, got %v", expCode, code)
diff --git a/internal/verification/verify_test.go b/internal/verification/verify_test.go
index adefc28..c608e11 100644
--- a/internal/verification/verify_test.go
+++ b/internal/verification/verify_test.go
@@ -22,7 +22,7 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/android"
-	"github.com/google/exposure-notifications-server/internal/model"
+	"github.com/google/exposure-notifications-server/internal/database"
 )
 
 const (
@@ -30,11 +30,11 @@ const (
 )
 
 func TestVerifyRegions(t *testing.T) {
-	allRegions := &model.AuthorizedApp{
+	allRegions := &database.AuthorizedApp{
 		AppPackageName:  appPkgName,
 		AllowAllRegions: true,
 	}
-	usCaRegions := &model.AuthorizedApp{
+	usCaRegions := &database.AuthorizedApp{
 		AppPackageName: appPkgName,
 		AllowedRegions: make(map[string]struct{}),
 	}
@@ -42,32 +42,32 @@ func TestVerifyRegions(t *testing.T) {
 	usCaRegions.AllowedRegions["CA"] = struct{}{}
 
 	cases := []struct {
-		Data *model.Publish
+		Data *database.Publish
 		Msg  string
-		Cfg  *model.AuthorizedApp
+		Cfg  *database.AuthorizedApp
 	}{
 		{
-			&model.Publish{Regions: []string{"US"}},
+			&database.Publish{Regions: []string{"US"}},
 			"no allowed regions configured",
 			nil,
 		},
 		{
-			&model.Publish{Regions: []string{"US"}},
+			&database.Publish{Regions: []string{"US"}},
 			"",
 			allRegions,
 		},
 		{
-			&model.Publish{Regions: []string{"US"}},
+			&database.Publish{Regions: []string{"US"}},
 			"",
 			usCaRegions,
 		},
 		{
-			&model.Publish{Regions: []string{"US", "CA"}},
+			&database.Publish{Regions: []string{"US", "CA"}},
 			"",
 			usCaRegions,
 		},
 		{
-			&model.Publish{Regions: []string{"MX"}},
+			&database.Publish{Regions: []string{"MX"}},
 			fmt.Sprintf("application '%v' tried to write unauthorized region: '%v'", appPkgName, "MX"),
 			usCaRegions,
 		},
@@ -89,33 +89,33 @@ func TestVerifyRegions(t *testing.T) {
 }
 
 func TestVerifySafetyNet(t *testing.T) {
-	allRegions := &model.AuthorizedApp{
+	allRegions := &database.AuthorizedApp{
 		AppPackageName:  appPkgName,
 		AllowAllRegions: true,
 	}
 
 	cases := []struct {
-		Data              *model.Publish
+		Data              *database.Publish
 		Msg               string
-		Cfg               *model.AuthorizedApp
+		Cfg               *database.AuthorizedApp
 		AttestationResult error
 	}{
 		{
 			// With no configuration, return err.
-			&model.Publish{Regions: []string{"US"}},
+			&database.Publish{Regions: []string{"US"}},
 			"cannot enforce SafetyNet",
 			nil,
 			nil,
 		}, {
 			// Verify when Validate Attestation Passes, return nil.
-			&model.Publish{Regions: []string{"US"}},
+			&database.Publish{Regions: []string{"US"}},
 			"",
 			allRegions,
 			nil,
 		}, {
 			// Verify when ValidateAttestation raises err, with safety check
 			// enabled, return err.
-			&model.Publish{Regions: []string{"US"}},
+			&database.Publish{Regions: []string{"US"}},
 			"android.ValidateAttestation: mocked",
 			allRegions,
 			fmt.Errorf("mocked"),