Alexander Belanger пре 5 година
родитељ
комит
e88adaed40
44 измењених фајлова са 496 додато и 171 уклоњено
  1. 22 0
      api/server/auth/project.go
  2. 18 0
      api/server/auth/user.go
  3. 17 0
      api/server/server.go
  4. 23 0
      api/types/permissions.go
  5. 37 0
      api/types/project.go
  6. 45 0
      api/types/request.go
  7. 6 0
      api/types/user.go
  8. 1 1
      cli/cmd/auth.go
  9. 1 1
      cli/cmd/connect/actions.go
  10. 1 1
      cli/cmd/create/create.go
  11. 1 1
      cli/cmd/deploy/deploy.go
  12. 1 1
      cli/cmd/docker.go
  13. 1 1
      cli/cmd/docker/auth.go
  14. 2 2
      cli/cmd/docker/porter.go
  15. 1 1
      client/api.go
  16. 1 1
      client/deploy.go
  17. 1 1
      client/git_repo.go
  18. 1 1
      client/github_action.go
  19. 1 1
      client/helm_repo.go
  20. 1 1
      client/helper_test.go
  21. 1 1
      client/integration.go
  22. 1 1
      client/k8s.go
  23. 1 1
      client/project.go
  24. 5 4
      client/project_test.go
  25. 1 1
      client/registry.go
  26. 1 1
      client/user.go
  27. 1 1
      client/user_test.go
  28. 37 37
      internal/helm/agent_test.go
  29. 8 8
      internal/models/cluster.go
  30. 4 4
      internal/registry/registry.go
  31. 1 1
      internal/repository/gorm/cluster_test.go
  32. 2 2
      internal/repository/gorm/project_test.go
  33. 131 0
      internal/repository/memory/release.go
  34. 1 0
      internal/repository/memory/repository.go
  35. 9 9
      server/api/cluster_handler_test.go
  36. 2 2
      server/api/helm_repo_handler_test.go
  37. 7 7
      server/api/integration_handler_test.go
  38. 56 32
      server/api/invite_handler_test.go
  39. 1 1
      server/api/k8s_handler.go
  40. 1 1
      server/api/k8s_handler_test.go
  41. 3 3
      server/api/project_handler_test.go
  42. 3 3
      server/api/registry_handler_test.go
  43. 14 14
      server/api/release_handler_test.go
  44. 23 23
      server/api/user_handler_test.go

+ 22 - 0
api/server/auth/project.go

@@ -0,0 +1,22 @@
+package auth
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/types"
+)
+
+func NewProjectContext(ctx context.Context, project types.Project) context.Context {
+	return context.WithValue(ctx, types.ProjectScope, project)
+}
+
+func ProjectScoped(h http.Handler, w http.ResponseWriter, r *http.Request) {
+	// read the project id from the request
+
+	// find a set of roles for this user and compute a policy document
+
+	// determine if policy document allows for project scope
+
+	// create a new project-scoped context
+}

+ 18 - 0
api/server/auth/user.go

@@ -0,0 +1,18 @@
+package auth
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/types"
+)
+
+func NewUserContext(ctx context.Context, user types.User) context.Context {
+	return context.WithValue(ctx, types.UserScope, user)
+}
+
+func UserScoped(h http.Handler, w http.ResponseWriter, r *http.Request) {
+	// find the user based on the request header
+
+	// create a new user-scoped context
+}

+ 17 - 0
api/server/server.go

@@ -0,0 +1,17 @@
+package server
+
+import (
+	"io"
+
+	"github.com/porter-dev/porter/api/types"
+)
+
+type RequestReader func(r io.Reader, v interface{}) error
+
+type ResponseWriter func(w io.Writer, v interface{}) error
+
+type APIRequest struct {
+	Metadata *types.APIRequestMetadata
+	Reader   RequestReader
+	Writer   ResponseWriter
+}

+ 23 - 0
api/types/permissions.go

@@ -0,0 +1,23 @@
+package types
+
+type PermissionObject string
+
+const (
+	UserScope      PermissionObject = "user"
+	ProjectScope   PermissionObject = "project"
+	ClusterScope   PermissionObject = "cluster"
+	NamespaceScope PermissionObject = "namespace"
+)
+
+type Permission struct {
+	Object PermissionObject
+	Verb   APIVerb
+}
+
+type PolicyObjectReference struct {
+	Resource   PermissionObject `json:"resource"`
+	Verbs      []APIVerb        `json:"verbs"`
+	VerbGroups []APIVerbGroup   `json:"verb_groups"`
+}
+
+type Policy []PolicyObjectReference

+ 37 - 0
api/types/project.go

@@ -0,0 +1,37 @@
+package types
+
+type Project struct {
+	ID    uint          `json:"id"`
+	Name  string        `json:"name"`
+	Roles []ProjectRole `json:"roles"`
+}
+
+type ProjectRole struct {
+	Kind      string `json:"kind"`
+	UserID    uint   `json:"user_id"`
+	ProjectID uint   `json:"project_id"`
+}
+
+type CreateProjectRequest struct {
+	Name string `json:"name" form:"required"`
+}
+
+type CreateProjectResponse Project
+
+type CreateProjectRoleRequest struct {
+	Kind      string `json:"kind" form:"required"`
+	UserID    uint   `json:"user_id" form:"required"`
+	ProjectID uint   `json:"project_id" form:"required"`
+}
+
+type CreateProjectRoleResponse ProjectRole
+
+type ListProjectsRequest struct{}
+
+type ListProjectsResponse []Project
+
+type DeleteProjectRequest struct {
+	Name string `json:"name" form:"required"`
+}
+
+type DeleteProjectResponse Project

+ 45 - 0
api/types/request.go

@@ -0,0 +1,45 @@
+package types
+
+type APIVerb string
+
+const (
+	APIVerbGet    APIVerb = "get"
+	APIVerbCreate APIVerb = "create"
+	APIVerbList   APIVerb = "list"
+	APIVerbUpdate APIVerb = "update"
+	APIVerbDelete APIVerb = "delete"
+)
+
+type APIVerbGroup []APIVerb
+
+func ReadVerbGroup() APIVerbGroup {
+	return []APIVerb{APIVerbGet, APIVerbList}
+}
+
+func WriteVerbGroup() APIVerbGroup {
+	return []APIVerb{APIVerbCreate, APIVerbUpdate, APIVerbDelete}
+}
+
+type HTTPVerb string
+
+const (
+	HTTPVerbGet    HTTPVerb = "GET"
+	HTTPVerbPost   HTTPVerb = "POST"
+	HTTPVerbPut    HTTPVerb = "PUT"
+	HTTPVerbPatch  HTTPVerb = "PUT"
+	HTTPVerbDelete HTTPVerb = "DELETE"
+)
+
+type EndpointPath struct{}
+
+type Endpoint struct {
+	Parent       *Endpoint
+	RelativePath EndpointPath
+	Permissions  []Permission
+}
+
+type APIRequestMetadata struct {
+	Verb     APIVerb
+	Method   HTTPVerb
+	Endpoint *Endpoint
+}

+ 6 - 0
api/types/user.go

@@ -0,0 +1,6 @@
+package types
+
+type User struct {
+	ID    uint   `json:"id"`
+	Email string `json:"email"`
+}

+ 1 - 1
cli/cmd/auth.go

@@ -7,7 +7,7 @@ import (
 
 	"github.com/fatih/color"
 
-	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/porter-dev/porter/api"
 	loginBrowser "github.com/porter-dev/porter/cli/cmd/login"
 	"github.com/porter-dev/porter/cli/cmd/utils"
 	"github.com/spf13/cobra"

+ 1 - 1
cli/cmd/connect/actions.go

@@ -6,7 +6,7 @@ import (
 	"strconv"
 	"time"
 
-	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/porter-dev/porter/api"
 	"github.com/porter-dev/porter/cli/cmd/utils"
 
 	ints "github.com/porter-dev/porter/internal/models/integrations"

+ 1 - 1
cli/cmd/create/create.go

@@ -1,7 +1,7 @@
 package create
 
 import (
-	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/porter-dev/porter/api"
 	"github.com/porter-dev/porter/cli/cmd/docker"
 )
 

+ 1 - 1
cli/cmd/deploy/deploy.go

@@ -9,7 +9,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/porter-dev/porter/api"
 	"github.com/porter-dev/porter/cli/cmd/docker"
 	"github.com/porter-dev/porter/cli/cmd/github"
 	"github.com/porter-dev/porter/cli/cmd/pack"

+ 1 - 1
cli/cmd/docker.go

@@ -144,7 +144,7 @@ func dockerConfig(user *api.AuthCheckResponse, client *api.Client, args []string
 		if strings.Contains(regURL, "index.docker.io") {
 			isAuthenticated := false
 
-			for key, _ := range configFile.AuthConfigs {
+			for key := range configFile.AuthConfigs {
 				if key == "https://index.docker.io/v1/" {
 					isAuthenticated = true
 				}

+ 1 - 1
cli/cmd/docker/auth.go

@@ -12,7 +12,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/porter-dev/porter/api"
 	"k8s.io/client-go/util/homedir"
 )
 

+ 2 - 2
cli/cmd/docker/porter.go

@@ -87,7 +87,7 @@ func StartPorter(opts *PorterStartOpts) (agent *Agent, id string, err error) {
 
 		// pgMount is mount for postgres container
 		pgMount := []mount.Mount{
-			mount.Mount{
+			{
 				Type:        mount.TypeVolume,
 				Source:      vol.Name,
 				Target:      "/var/lib/postgresql/data",
@@ -102,7 +102,7 @@ func StartPorter(opts *PorterStartOpts) (agent *Agent, id string, err error) {
 			Image:  "postgres:latest",
 			Mounts: pgMount,
 			VolumeMap: map[string]struct{}{
-				"porter_postgres": struct{}{},
+				"porter_postgres": {},
 			},
 			NetworkID: netID,
 			Env: []string{

+ 1 - 1
cli/cmd/api/api.go → client/api.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"encoding/base64"

+ 1 - 1
cli/cmd/api/deploy.go → client/deploy.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"context"

+ 1 - 1
cli/cmd/api/git_repo.go → client/git_repo.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"context"

+ 1 - 1
cli/cmd/api/github_action.go → client/github_action.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"context"

+ 1 - 1
cli/cmd/api/helm_repo.go → client/helm_repo.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"context"

+ 1 - 1
cli/cmd/api/helper_test.go → client/helper_test.go

@@ -1,4 +1,4 @@
-package api_test
+package client_test
 
 import (
 	"fmt"

+ 1 - 1
cli/cmd/api/integration.go → client/integration.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"context"

+ 1 - 1
cli/cmd/api/k8s.go → client/k8s.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"context"

+ 1 - 1
cli/cmd/api/project.go → client/project.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"context"

+ 5 - 4
cli/cmd/api/project_test.go → client/project_test.go

@@ -1,18 +1,19 @@
-package api_test
+package client_test
 
 import (
 	"context"
 	"testing"
 
+	"github.com/porter-dev/porter/cli/cmd/api"
 	"github.com/porter-dev/porter/internal/models"
 
-	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/porter-dev/porter/client"
 )
 
-func initProject(name string, client *api.Client, t *testing.T) *api.CreateProjectResponse {
+func initProject(name string, client *client.Client, t *testing.T) *client.CreateProjectResponse {
 	t.Helper()
 
-	resp, err := client.CreateProject(context.Background(), &api.CreateProjectRequest{
+	resp, err := client.CreateProject(context.Background(), &client.CreateProjectRequest{
 		Name: name,
 	})
 

+ 1 - 1
cli/cmd/api/registry.go → client/registry.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"context"

+ 1 - 1
cli/cmd/api/user.go → client/user.go

@@ -1,4 +1,4 @@
-package api
+package client
 
 import (
 	"context"

+ 1 - 1
cli/cmd/api/user_test.go → client/user_test.go

@@ -1,4 +1,4 @@
-package api_test
+package client_test
 
 import (
 	"context"

+ 37 - 37
internal/helm/agent_test.go

@@ -108,7 +108,7 @@ type listReleaseTest struct {
 }
 
 var listReleaseTests = []listReleaseTest{
-	listReleaseTest{
+	{
 		name:      "simple test across namespaces, should sort by name",
 		namespace: "",
 		filter: &helm.ListFilter{
@@ -119,17 +119,17 @@ var listReleaseTests = []listReleaseTest{
 			StatusFilter: []string{"deployed"},
 		},
 		releases: []releaseStub{
-			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
-			releaseStub{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
+			{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
+			{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
 		},
 		expRes: []releaseStub{
-			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-			releaseStub{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
+			{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+			{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
 		},
 	},
-	listReleaseTest{
+	{
 		name:      "simple test only default namespace",
 		namespace: "default",
 		filter: &helm.ListFilter{
@@ -140,16 +140,16 @@ var listReleaseTests = []listReleaseTest{
 			StatusFilter: []string{"deployed"},
 		},
 		releases: []releaseStub{
-			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
-			releaseStub{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
+			{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
+			{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
 		},
 		expRes: []releaseStub{
-			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
+			{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
 		},
 	},
-	listReleaseTest{
+	{
 		name:      "simple test limit",
 		namespace: "",
 		filter: &helm.ListFilter{
@@ -160,13 +160,13 @@ var listReleaseTests = []listReleaseTest{
 			StatusFilter: []string{"deployed"},
 		},
 		releases: []releaseStub{
-			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-			releaseStub{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.2", release.StatusDeployed},
+			{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+			{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.2", release.StatusDeployed},
 		},
 		expRes: []releaseStub{
-			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-			releaseStub{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
+			{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+			{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
 		},
 	},
 }
@@ -200,13 +200,13 @@ type getReleaseTest struct {
 }
 
 var getReleaseTests = []getReleaseTest{
-	getReleaseTest{
+	{
 		name:      "simple get with revision 0 (latest)",
 		namespace: "default",
 		releases: []releaseStub{
-			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
-			releaseStub{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
+			{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
+			{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
 		},
 		getName:    "airwatch",
 		getVersion: 1,
@@ -234,16 +234,16 @@ func TestGetReleases(t *testing.T) {
 }
 
 var listReleaseHistoryTests = []listReleaseTest{
-	listReleaseTest{
+	{
 		name:      "simple history test",
 		namespace: "default",
 		releases: []releaseStub{
-			releaseStub{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
+			{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
 		},
 		expRes: []releaseStub{
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
-			releaseStub{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
+			{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
 		},
 	},
 }
@@ -268,17 +268,17 @@ func TestListReleaseHistory(t *testing.T) {
 }
 
 var upgradeTests = []listReleaseTest{
-	listReleaseTest{
+	{
 		name:      "simple history test",
 		namespace: "default",
 		releases: []releaseStub{
-			releaseStub{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
+			{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
 		},
 		expRes: []releaseStub{
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
-			releaseStub{"wordpress", "default", 2, "1.0.2", release.StatusSuperseded},
-			releaseStub{"wordpress", "default", 3, "1.0.2", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
+			{"wordpress", "default", 2, "1.0.2", release.StatusSuperseded},
+			{"wordpress", "default", 3, "1.0.2", release.StatusDeployed},
 		},
 	},
 }
@@ -305,12 +305,12 @@ var upgradeTests = []listReleaseTest{
 // }
 
 var rollbackReleaseTests = []getReleaseTest{
-	getReleaseTest{
+	{
 		name:      "simple rollback test",
 		namespace: "default",
 		releases: []releaseStub{
-			releaseStub{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
+			{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
+			{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
 		},
 		getName:    "wordpress",
 		getVersion: 3,

+ 8 - 8
internal/models/cluster.go

@@ -227,35 +227,35 @@ type ClusterResolverInfo struct {
 // ClusterResolverInfos is a map of the information for actions to be
 // performed in order to initialize a cluster
 var ClusterResolverInfos = map[ClusterResolverName]ClusterResolverInfo{
-	ClusterCAData: ClusterResolverInfo{
+	ClusterCAData: {
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "cluster_ca_data",
 	},
-	ClusterLocalhost: ClusterResolverInfo{
+	ClusterLocalhost: {
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "cluster_hostname",
 	},
-	ClientCertData: ClusterResolverInfo{
+	ClientCertData: {
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "client_cert_data",
 	},
-	ClientKeyData: ClusterResolverInfo{
+	ClientKeyData: {
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "client_key_data",
 	},
-	OIDCIssuerData: ClusterResolverInfo{
+	OIDCIssuerData: {
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "oidc_idp_issuer_ca_data",
 	},
-	TokenData: ClusterResolverInfo{
+	TokenData: {
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "token_data",
 	},
-	GCPKeyData: ClusterResolverInfo{
+	GCPKeyData: {
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "gcp_key_data",
 	},
-	AWSData: ClusterResolverInfo{
+	AWSData: {
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "aws_access_key_id,aws_secret_access_key,aws_cluster_id",
 	},

+ 4 - 4
internal/registry/registry.go

@@ -806,7 +806,7 @@ func (r *Registry) getECRDockerConfigFile(
 
 	return &configfile.ConfigFile{
 		AuthConfigs: map[string]types.AuthConfig{
-			key: types.AuthConfig{
+			key: {
 				Username: parts[0],
 				Password: parts[1],
 				Auth:     token,
@@ -836,7 +836,7 @@ func (r *Registry) getGCRDockerConfigFile(
 
 	return &configfile.ConfigFile{
 		AuthConfigs: map[string]types.AuthConfig{
-			parsedURL.Host: types.AuthConfig{
+			parsedURL.Host: {
 				Username: "_json_key",
 				Password: string(gcp.GCPKeyData),
 				Auth:     generateAuthToken("_json_key", string(gcp.GCPKeyData)),
@@ -873,7 +873,7 @@ func (r *Registry) getDOCRDockerConfigFile(
 
 	return &configfile.ConfigFile{
 		AuthConfigs: map[string]types.AuthConfig{
-			parsedURL.Host: types.AuthConfig{
+			parsedURL.Host: {
 				Username: tok,
 				Password: tok,
 				Auth:     generateAuthToken(tok, tok),
@@ -909,7 +909,7 @@ func (r *Registry) getPrivateRegistryDockerConfigFile(
 
 	return &configfile.ConfigFile{
 		AuthConfigs: map[string]types.AuthConfig{
-			authConfigKey: types.AuthConfig{
+			authConfigKey: {
 				Username: string(basic.Username),
 				Password: string(basic.Password),
 				Auth:     generateAuthToken(string(basic.Username), string(basic.Password)),

+ 1 - 1
internal/repository/gorm/cluster_test.go

@@ -73,7 +73,7 @@ func TestCreateClusterCandidateWithResolvers(t *testing.T) {
 		ProjectID:        tester.initProjects[0].ID,
 		CreatedClusterID: 0,
 		Resolvers: []models.ClusterResolver{
-			models.ClusterResolver{
+			{
 				Name:     models.ClusterLocalhost,
 				Resolved: false,
 			},

+ 2 - 2
internal/repository/gorm/project_test.go

@@ -88,7 +88,7 @@ func TestCreateProjectRole(t *testing.T) {
 	expProj := &models.Project{
 		Name: "project-test",
 		Roles: []models.Role{
-			models.Role{
+			{
 				Kind:      models.RoleAdmin,
 				UserID:    0,
 				ProjectID: 1,
@@ -148,7 +148,7 @@ func TestListProjectsByUserID(t *testing.T) {
 		expProj := &models.Project{
 			Name: "project-test",
 			Roles: []models.Role{
-				models.Role{
+				{
 					Kind:      models.RoleAdmin,
 					UserID:    tester.initUsers[0].Model.ID,
 					ProjectID: uint(i + 1),

+ 131 - 0
internal/repository/memory/release.go

@@ -0,0 +1,131 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// ReleaseRepository implements repository.ReleaseRepository
+type ReleaseRepository struct {
+	canQuery bool
+	releases []*models.Release
+}
+
+// NewReleaseRepository will return errors if canQuery is false
+func NewReleaseRepository(canQuery bool) repository.ReleaseRepository {
+	return &ReleaseRepository{
+		canQuery,
+		[]*models.Release{},
+	}
+}
+
+// CreateRelease creates a new release
+func (repo *ReleaseRepository) CreateRelease(
+	release *models.Release,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if release == nil {
+		return nil, nil
+	}
+
+	repo.releases = append(repo.releases, release)
+	release.ID = uint(len(repo.releases))
+
+	return release, nil
+}
+
+// ReadRelease finds a release by id
+func (repo *ReleaseRepository) ReadReleaseByWebhookToken(
+	token string,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, release := range repo.releases {
+		if release != nil && release.WebhookToken == token {
+			return release, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// ReadRelease finds a release by id
+func (repo *ReleaseRepository) ReadRelease(
+	clusterID uint, name, namespace string,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, release := range repo.releases {
+		if release != nil && release.ClusterID == clusterID && release.Name == name && release.Namespace == namespace {
+			return release, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
+// ListReleasesByProjectID finds all releases for a given project id
+func (repo *ReleaseRepository) ListReleasesByImageRepoURI(
+	clusterID uint, imageRepoURI string,
+) ([]*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	res := make([]*models.Release, 0)
+
+	for _, release := range repo.releases {
+		if release != nil && release.ClusterID == clusterID && release.ImageRepoURI == imageRepoURI {
+			res = append(res, release)
+		}
+	}
+
+	return res, nil
+}
+
+// UpdateRelease modifies an existing Release in the database
+func (repo *ReleaseRepository) UpdateRelease(
+	release *models.Release,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(release.ID-1) >= len(repo.releases) || repo.releases[release.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(release.ID - 1)
+	repo.releases[index] = release
+
+	return release, nil
+}
+
+// DeleteRelease removes a release from the array by setting it to nil
+func (repo *ReleaseRepository) DeleteRelease(
+	release *models.Release,
+) (*models.Release, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	if int(release.ID-1) >= len(repo.releases) || repo.releases[release.ID-1] == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	index := int(release.ID - 1)
+	copy := repo.releases[index]
+	repo.releases[index] = nil
+
+	return copy, nil
+}

+ 1 - 0
internal/repository/memory/repository.go

@@ -16,6 +16,7 @@ func NewRepository(canQuery bool) *repository.Repository {
 		Registry:         NewRegistryRepository(canQuery),
 		GitRepo:          NewGitRepoRepository(canQuery),
 		Invite:           NewInviteRepository(canQuery),
+		Release:          NewReleaseRepository(canQuery),
 		AuthCode:         NewAuthCodeRepository(canQuery),
 		DNSRecord:        NewDNSRecordRepository(canQuery),
 		PWResetToken:     NewPWResetTokenRepository(canQuery),

+ 9 - 9
server/api/cluster_handler_test.go

@@ -75,7 +75,7 @@ func testClusterRequests(t *testing.T, tests []*clusterTest, canQuery bool) {
 // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
 
 var createClusterTests = []*clusterTest{
-	&clusterTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -99,7 +99,7 @@ func TestHandleCreateCluster(t *testing.T) {
 }
 
 var readProjectClusterTest = []*clusterTest{
-	&clusterTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -123,7 +123,7 @@ func TestHandleReadProjectCluster(t *testing.T) {
 }
 
 var listProjectClustersTest = []*clusterTest{
-	&clusterTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -147,7 +147,7 @@ func TestHandleListProjectClusters(t *testing.T) {
 }
 
 var updateClusterTests = []*clusterTest{
-	&clusterTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -171,7 +171,7 @@ func TestHandleUpdateCluster(t *testing.T) {
 }
 
 var deleteClusterTests = []*clusterTest{
-	&clusterTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -216,7 +216,7 @@ func TestHandleDeleteCluster(t *testing.T) {
 }
 
 var createProjectClusterCandidatesTests = []*clusterTest{
-	&clusterTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -262,7 +262,7 @@ var createProjectClusterCandidatesTests = []*clusterTest{
 			},
 		},
 	},
-	&clusterTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -285,7 +285,7 @@ func TestHandleCreateProjectClusterCandidate(t *testing.T) {
 }
 
 var listProjectClusterCandidatesTests = []*clusterTest{
-	&clusterTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -309,7 +309,7 @@ func TestHandleListProjectClusterCandidates(t *testing.T) {
 }
 
 var resolveProjectClusterCandidatesTests = []*clusterTest{
-	&clusterTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,

+ 2 - 2
server/api/helm_repo_handler_test.go

@@ -69,7 +69,7 @@ func testHelmRepoRequests(t *testing.T, tests []*helmTest, canQuery bool) {
 // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
 
 var createHelmRepoTests = []*helmTest{
-	&helmTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -92,7 +92,7 @@ func TestHandleCreateHelmRepo(t *testing.T) {
 }
 
 var listHelmReposTest = []*helmTest{
-	&helmTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,

+ 7 - 7
server/api/integration_handler_test.go

@@ -70,7 +70,7 @@ func testPublicIntegrationRequests(t *testing.T, tests []*publicIntTest, canQuer
 // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
 
 var listClusterIntegrationsTests = []*publicIntTest{
-	&publicIntTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 		},
@@ -92,7 +92,7 @@ func TestHandleListClusterIntegrations(t *testing.T) {
 }
 
 var listRegistryIntegrationsTests = []*publicIntTest{
-	&publicIntTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 		},
@@ -114,7 +114,7 @@ func TestHandleListRegistryIntegrations(t *testing.T) {
 }
 
 var listHelmRepoIntegrationsTest = []*publicIntTest{
-	&publicIntTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 		},
@@ -136,7 +136,7 @@ func TestHandleListHelmRepoIntegrations(t *testing.T) {
 }
 
 var listRepoIntegrationsTests = []*publicIntTest{
-	&publicIntTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 		},
@@ -158,7 +158,7 @@ func TestHandleListRepoIntegrations(t *testing.T) {
 }
 
 var createGCPIntegrationTests = []*publicIntTest{
-	&publicIntTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -183,7 +183,7 @@ func TestHandleCreateGCPIntegration(t *testing.T) {
 }
 
 var createAWSIntegrationTests = []*publicIntTest{
-	&publicIntTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -210,7 +210,7 @@ func TestHandleCreateAWSIntegration(t *testing.T) {
 }
 
 var createBasicIntegrationTests = []*publicIntTest{
-	&publicIntTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,

+ 56 - 32
server/api/invite_handler_test.go

@@ -70,7 +70,7 @@ func testInviteRequests(t *testing.T, tests []*inviteTest, canQuery bool) {
 // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
 
 var createInviteTests = []*inviteTest{
-	&inviteTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -80,7 +80,7 @@ var createInviteTests = []*inviteTest{
 		endpoint:  "/api/projects/1/invites",
 		body:      `{"email":"test@test.it"}`,
 		expStatus: http.StatusCreated,
-		expBody:   `{"expired":false,"email":"test@test.it","accepted":false}`,
+		expBody:   `{"id":1,"expired":false,"email":"test@test.it","accepted":false}`,
 		useCookie: true,
 		validators: []func(c *inviteTest, tester *tester, t *testing.T){
 			func(c *inviteTest, tester *tester, t *testing.T) {
@@ -109,7 +109,7 @@ func TestHandleCreateInvite(t *testing.T) {
 }
 
 var listInvitesTest = []*inviteTest{
-	&inviteTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -120,7 +120,7 @@ var listInvitesTest = []*inviteTest{
 		endpoint:  "/api/projects/1/invites",
 		body:      ``,
 		expStatus: http.StatusOK,
-		expBody:   `[{"expired":false,"email":"test@test.it","accepted":false}]`,
+		expBody:   `[{"id":1,"expired":false,"email":"test@test.it","accepted":false}]`,
 		useCookie: true,
 		validators: []func(c *inviteTest, tester *tester, t *testing.T){
 			func(c *inviteTest, tester *tester, t *testing.T) {
@@ -149,7 +149,7 @@ func TestHandleListInvites(t *testing.T) {
 }
 
 var acceptInviteTests = []*inviteTest{
-	&inviteTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initUserAlt,
@@ -187,52 +187,76 @@ var acceptInviteTests = []*inviteTest{
 			},
 		},
 	},
-	&inviteTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initUserAlt,
 			initProject,
 			initInvite,
 		},
-		msg:        "Accept invite wrong token",
-		method:     "GET",
-		endpoint:   "/api/projects/1/invites/abcd1",
-		body:       ``,
-		expStatus:  http.StatusForbidden,
-		expBody:    ``,
-		useCookie:  true,
-		validators: []func(c *inviteTest, tester *tester, t *testing.T){},
+		msg:       "Accept invite wrong token",
+		method:    "GET",
+		endpoint:  "/api/projects/1/invites/abcd1",
+		body:      ``,
+		expStatus: http.StatusFound,
+		expBody:   ``,
+		useCookie: true,
+		validators: []func(c *inviteTest, tester *tester, t *testing.T){
+			func(c *inviteTest, tester *tester, t *testing.T) {
+				expRes := "/dashboard?error=Invalid+invite+token"
+
+				if expRes != tester.rr.HeaderMap.Get("Location") {
+					t.Fatalf("Redirect location not correct: expected %v, got %v\n", expRes, tester.rr.HeaderMap.Get("Location"))
+				}
+			},
+		},
 	},
-	&inviteTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
 			initInvite,
 		},
-		msg:        "Accept invite wrong user",
-		method:     "GET",
-		endpoint:   "/api/projects/1/invites/abcd",
-		body:       ``,
-		expStatus:  http.StatusForbidden,
-		expBody:    ``,
-		useCookie:  true,
-		validators: []func(c *inviteTest, tester *tester, t *testing.T){},
+		msg:       "Accept invite wrong user",
+		method:    "GET",
+		endpoint:  "/api/projects/1/invites/abcd",
+		body:      ``,
+		expStatus: http.StatusFound,
+		expBody:   ``,
+		useCookie: true,
+		validators: []func(c *inviteTest, tester *tester, t *testing.T){
+			func(c *inviteTest, tester *tester, t *testing.T) {
+				expRes := "/dashboard?error=Wrong+email+for+invite"
+
+				if expRes != tester.rr.HeaderMap.Get("Location") {
+					t.Fatalf("Redirect location not correct: expected %v, got %v\n", expRes, tester.rr.HeaderMap.Get("Location"))
+				}
+			},
+		},
 	},
-	&inviteTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initUserAlt,
 			initProject,
 			initInviteExpiredToken,
 		},
-		msg:        "Accept invite expired token",
-		method:     "GET",
-		endpoint:   "/api/projects/1/invites/abcd",
-		body:       ``,
-		expStatus:  http.StatusForbidden,
-		expBody:    ``,
-		useCookie:  true,
-		validators: []func(c *inviteTest, tester *tester, t *testing.T){},
+		msg:       "Accept invite expired token",
+		method:    "GET",
+		endpoint:  "/api/projects/1/invites/abcd",
+		body:      ``,
+		expStatus: http.StatusFound,
+		expBody:   ``,
+		useCookie: true,
+		validators: []func(c *inviteTest, tester *tester, t *testing.T){
+			func(c *inviteTest, tester *tester, t *testing.T) {
+				expRes := "/dashboard?error=Invite+has+expired"
+
+				if expRes != tester.rr.HeaderMap.Get("Location") {
+					t.Fatalf("Redirect location not correct: expected %v, got %v\n", expRes, tester.rr.HeaderMap.Get("Location"))
+				}
+			},
+		},
 	},
 }
 

+ 1 - 1
server/api/k8s_handler.go

@@ -188,7 +188,7 @@ func (app *App) HandleCreateConfigMap(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
-	for key, _ := range configMap.SecretEnvVariables {
+	for key := range configMap.SecretEnvVariables {
 		configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
 	}
 

+ 1 - 1
server/api/k8s_handler_test.go

@@ -73,7 +73,7 @@ func testK8sRequests(t *testing.T, tests []*k8sTest, canQuery bool) {
 // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
 
 var listNamespacesTests = []*k8sTest{
-	&k8sTest{
+	{
 		initializers: []func(tester *tester){
 			initDefaultK8s,
 		},

+ 3 - 3
server/api/project_handler_test.go

@@ -69,7 +69,7 @@ func testProjRequests(t *testing.T, tests []*projTest, canQuery bool) {
 // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
 
 var createProjectTests = []*projTest{
-	&projTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 		},
@@ -93,7 +93,7 @@ func TestHandleCreateProject(t *testing.T) {
 }
 
 var readProjectTests = []*projTest{
-	&projTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -116,7 +116,7 @@ func TestHandleReadProject(t *testing.T) {
 }
 
 var deleteProjectTests = []*projTest{
-	&projTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,

+ 3 - 3
server/api/registry_handler_test.go

@@ -149,7 +149,7 @@ func testImagesRequests(t *testing.T, tests []*imagesTest, canQuery bool) {
 // }
 
 var listRegistryTests = []*regTest{
-	&regTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -173,7 +173,7 @@ func TestHandleListRegistries(t *testing.T) {
 }
 
 var updateRegistryTests = []*regTest{
-	&regTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,
@@ -197,7 +197,7 @@ func TestHandleUpdateRegistry(t *testing.T) {
 }
 
 var deleteRegTests = []*regTest{
-	&regTest{
+	{
 		initializers: []func(t *tester){
 			initUserDefault,
 			initProject,

+ 14 - 14
server/api/release_handler_test.go

@@ -86,7 +86,7 @@ func testReleaseRequests(t *testing.T, tests []*releaseTest, canQuery bool) {
 // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
 
 var listReleasesTests = []*releaseTest{
-	&releaseTest{
+	{
 		initializers: []func(tester *tester){
 			initDefaultReleases,
 		},
@@ -109,7 +109,7 @@ var listReleasesTests = []*releaseTest{
 			releaseReleaseArrBodyValidator,
 		},
 	},
-	&releaseTest{
+	{
 		initializers: []func(tester *tester){
 			initDefaultReleases,
 		},
@@ -143,7 +143,7 @@ func TestHandleListReleases(t *testing.T) {
 }
 
 var getReleaseTests = []*releaseTest{
-	&releaseTest{
+	{
 		initializers: []func(tester *tester){
 			initDefaultReleases,
 		},
@@ -151,7 +151,7 @@ var getReleaseTests = []*releaseTest{
 		method:    "GET",
 		namespace: "default",
 		endpoint: "/api/projects/1/releases/airwatch/1?" + url.Values{
-			"namespace":  []string{""},
+			"namespace":  []string{"default"},
 			"cluster_id": []string{"1"},
 			"storage":    []string{"memory"},
 		}.Encode(),
@@ -163,7 +163,7 @@ var getReleaseTests = []*releaseTest{
 			releaseReleaseBodyValidator,
 		},
 	},
-	&releaseTest{
+	{
 		initializers: []func(tester *tester){
 			initDefaultReleases,
 		},
@@ -190,7 +190,7 @@ func TestHandleGetRelease(t *testing.T) {
 }
 
 var listReleaseHistoryTests = []*releaseTest{
-	&releaseTest{
+	{
 		initializers: []func(tester *tester){
 			initHistoryReleases,
 		},
@@ -210,7 +210,7 @@ var listReleaseHistoryTests = []*releaseTest{
 			releaseReleaseArrBodyValidator,
 		},
 	},
-	&releaseTest{
+	{
 		initializers: []func(tester *tester){
 			initDefaultReleases,
 		},
@@ -237,7 +237,7 @@ func TestHandleListReleaseHistory(t *testing.T) {
 }
 
 var upgradeReleaseTests = []*releaseTest{
-	&releaseTest{
+	{
 		initializers: []func(tester *tester){
 			initHistoryReleases,
 		},
@@ -315,7 +315,7 @@ func TestUpgradeRelease(t *testing.T) {
 }
 
 var rollbackReleaseTests = []*releaseTest{
-	&releaseTest{
+	{
 		initializers: []func(tester *tester){
 			initHistoryReleases,
 		},
@@ -414,14 +414,14 @@ func initHistoryReleases(tester *tester) {
 }
 
 var sampleReleaseStubs = []releaseStub{
-	releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-	releaseStub{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
-	releaseStub{"wordpress", "default", 1, "1.0.2", release.StatusDeployed},
+	{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+	{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
+	{"wordpress", "default", 1, "1.0.2", release.StatusDeployed},
 }
 
 var historyReleaseStubs = []releaseStub{
-	releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
-	releaseStub{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
+	{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
+	{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
 }
 
 func releaseStubsToReleaseJSON(rels []releaseStub) string {

+ 23 - 23
server/api/user_handler_test.go

@@ -73,7 +73,7 @@ func testUserRequests(t *testing.T, tests []*userTest, canQuery bool) {
 // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
 
 var authCheckTests = []*userTest{
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -82,13 +82,13 @@ var authCheckTests = []*userTest{
 		endpoint:  "/api/auth/check",
 		expStatus: http.StatusOK,
 		body:      "",
-		expBody:   `{"id":1,"email":"belanger@getporter.dev"}`,
+		expBody:   `{"id":1,"email":"belanger@getporter.dev","email_verified":false}`,
 		useCookie: true,
 		validators: []func(c *userTest, tester *tester, t *testing.T){
 			userBasicBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -156,7 +156,7 @@ func TestHandleAuthCheckToken(t *testing.T) {
 }
 
 var createUserTests = []*userTest{
-	&userTest{
+	{
 		msg:      "Create user",
 		method:   "POST",
 		endpoint: "/api/users",
@@ -170,7 +170,7 @@ var createUserTests = []*userTest{
 			userModelBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		msg:      "Create user invalid email",
 		method:   "POST",
 		endpoint: "/api/users",
@@ -184,7 +184,7 @@ var createUserTests = []*userTest{
 			userBasicBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		msg:      "Create user missing field",
 		method:   "POST",
 		endpoint: "/api/users",
@@ -197,7 +197,7 @@ var createUserTests = []*userTest{
 			userBasicBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -214,7 +214,7 @@ var createUserTests = []*userTest{
 			userBasicBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		msg:      "Create user invalid field type",
 		method:   "POST",
 		endpoint: "/api/users",
@@ -235,7 +235,7 @@ func TestHandleCreateUser(t *testing.T) {
 }
 
 var createUserTestsWriteFail = []*userTest{
-	&userTest{
+	{
 		msg:      "Create user db connection down",
 		method:   "POST",
 		endpoint: "/api/users",
@@ -256,7 +256,7 @@ func TestHandleCreateUserWriteFail(t *testing.T) {
 }
 
 var loginUserTests = []*userTest{
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -268,12 +268,12 @@ var loginUserTests = []*userTest{
 			"password": "hello"
 		}`,
 		expStatus: http.StatusOK,
-		expBody:   `{"id":1,"email":"belanger@getporter.dev"}`,
+		expBody:   `{"id":1,"email":"belanger@getporter.dev","email_verified":false}`,
 		validators: []func(c *userTest, tester *tester, t *testing.T){
 			userBasicBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -285,13 +285,13 @@ var loginUserTests = []*userTest{
 			"password": "hello"
 		}`,
 		expStatus: http.StatusOK,
-		expBody:   `{"id":1,"email":"belanger@getporter.dev"}`,
+		expBody:   `{"id":1,"email":"belanger@getporter.dev","email_verified":false}`,
 		useCookie: true,
 		validators: []func(c *userTest, tester *tester, t *testing.T){
 			userBasicBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		msg:      "Login user unregistered email",
 		method:   "POST",
 		endpoint: "/api/login",
@@ -305,7 +305,7 @@ var loginUserTests = []*userTest{
 			userBasicBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -330,7 +330,7 @@ func TestHandleLoginUser(t *testing.T) {
 }
 
 var logoutUserTests = []*userTest{
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -375,7 +375,7 @@ func TestHandleLogoutUser(t *testing.T) {
 }
 
 var readUserTests = []*userTest{
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -390,7 +390,7 @@ var readUserTests = []*userTest{
 			userModelBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -411,7 +411,7 @@ func TestHandleReadUser(t *testing.T) {
 }
 
 var listUserProjectsTests = []*userTest{
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 			initProject,
@@ -427,7 +427,7 @@ var listUserProjectsTests = []*userTest{
 			userProjectsListValidator,
 		},
 	},
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -448,7 +448,7 @@ func TestHandleListUserProjects(t *testing.T) {
 }
 
 var deleteUserTests = []*userTest{
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -495,7 +495,7 @@ var deleteUserTests = []*userTest{
 			},
 		},
 	},
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
@@ -509,7 +509,7 @@ var deleteUserTests = []*userTest{
 			userBasicBodyValidator,
 		},
 	},
-	&userTest{
+	{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},