routes.go 6.29 KB
Newer Older
1 2 3
package handlers

import (
4
	"context"
5 6 7 8
	"log"
	"net/http"
	"os"

9 10 11 12
	"geeks-accelerator/oss/saas-starter-kit/internal/mid"
	saasSwagger "geeks-accelerator/oss/saas-starter-kit/internal/mid/saas-swagger"
	"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
	"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
13
	"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
14
	"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
15
	_ "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
16
	"geeks-accelerator/oss/saas-starter-kit/internal/project"
17
	_ "geeks-accelerator/oss/saas-starter-kit/internal/signup"
Lee Brown's avatar
Lee Brown committed
18
	"github.com/jmoiron/sqlx"
19
	"github.com/pkg/errors"
Lee Brown's avatar
Lee Brown committed
20
	"gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis"
21 22 23
)

// API returns a handler for a set of routes.
24
func API(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, masterDB *sqlx.DB, redis *redis.Client, authenticator *auth.Authenticator, globalMids ...web.Middleware) http.Handler {
Lee Brown's avatar
Lee Brown committed
25 26 27

	// Define base middlewares applied to all requests.
	middlewares := []web.Middleware{
28
		mid.Trace(), mid.Logger(log), mid.Errors(log, nil), mid.Metrics(), mid.Panics(),
Lee Brown's avatar
Lee Brown committed
29 30 31 32 33 34
	}

	// Append any global middlewares if they were included.
	if len(globalMids) > 0 {
		middlewares = append(middlewares, globalMids...)
	}
35 36

	// Construct the web.App which holds all routes as well as common Middleware.
Lee Brown's avatar
Lee Brown committed
37
	app := web.NewApp(shutdown, log, env, middlewares...)
38 39 40 41

	// Register health check endpoint. This route is not authenticated.
	check := Check{
		MasterDB: masterDB,
Lee Brown's avatar
Lee Brown committed
42
		Redis:    redis,
43 44
	}
	app.Handle("GET", "/v1/health", check.Health)
Lee Brown's avatar
Lee Brown committed
45
	app.Handle("GET", "/ping", check.Ping)
46 47 48 49 50 51

	// Register user management and authentication endpoints.
	u := User{
		MasterDB:       masterDB,
		TokenGenerator: authenticator,
	}
52 53 54 55 56 57 58 59
	app.Handle("GET", "/v1/users", u.Find, mid.AuthenticateHeader(authenticator))
	app.Handle("POST", "/v1/users", u.Create, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
	app.Handle("GET", "/v1/users/:id", u.Read, mid.AuthenticateHeader(authenticator))
	app.Handle("PATCH", "/v1/users", u.Update, mid.AuthenticateHeader(authenticator))
	app.Handle("PATCH", "/v1/users/password", u.UpdatePassword, mid.AuthenticateHeader(authenticator))
	app.Handle("PATCH", "/v1/users/archive", u.Archive, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
	app.Handle("DELETE", "/v1/users/:id", u.Delete, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
	app.Handle("PATCH", "/v1/users/switch-account/:account_id", u.SwitchAccount, mid.AuthenticateHeader(authenticator))
60 61

	// This route is not authenticated
Lee Brown's avatar
Lee Brown committed
62
	app.Handle("POST", "/v1/oauth/token", u.Token)
63

Lee Brown's avatar
Lee Brown committed
64 65
	// Register user account management endpoints.
	ua := UserAccount{
66
		MasterDB: masterDB,
Lee Brown's avatar
Lee Brown committed
67
	}
68 69
	app.Handle("GET", "/v1/user_accounts", ua.Find, mid.AuthenticateHeader(authenticator))
	app.Handle("POST", "/v1/user_accounts", ua.Create, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
70
	app.Handle("GET", "/v1/user_accounts/:user_id/:account_id", ua.Read, mid.AuthenticateHeader(authenticator))
71 72 73
	app.Handle("PATCH", "/v1/user_accounts", ua.Update, mid.AuthenticateHeader(authenticator))
	app.Handle("PATCH", "/v1/user_accounts/archive", ua.Archive, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
	app.Handle("DELETE", "/v1/user_accounts", ua.Delete, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
Lee Brown's avatar
Lee Brown committed
74

Lee Brown's avatar
Lee Brown committed
75 76 77 78
	// Register account endpoints.
	a := Account{
		MasterDB: masterDB,
	}
79 80
	app.Handle("GET", "/v1/accounts/:id", a.Read, mid.AuthenticateHeader(authenticator))
	app.Handle("PATCH", "/v1/accounts", a.Update, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
Lee Brown's avatar
Lee Brown committed
81

82 83 84 85 86 87
	// Register signup endpoints.
	s := Signup{
		MasterDB: masterDB,
	}
	app.Handle("POST", "/v1/signup", s.Signup)

Lee Brown's avatar
Lee Brown committed
88
	// Register project.
Lee Brown's avatar
Lee Brown committed
89
	p := Project{
90 91
		MasterDB: masterDB,
	}
92 93 94 95 96 97
	app.Handle("GET", "/v1/projects", p.Find, mid.AuthenticateHeader(authenticator))
	app.Handle("POST", "/v1/projects", p.Create, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
	app.Handle("GET", "/v1/projects/:id", p.Read, mid.AuthenticateHeader(authenticator))
	app.Handle("PATCH", "/v1/projects", p.Update, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
	app.Handle("PATCH", "/v1/projects/archive", p.Archive, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
	app.Handle("DELETE", "/v1/projects/:id", p.Delete, mid.AuthenticateHeader(authenticator), mid.HasRole(auth.RoleAdmin))
Lee Brown's avatar
Lee Brown committed
98

99 100
	app.Handle("GET", "/v1/examples/error-response", ExampleErrorResponse)

Lee Brown's avatar
Lee Brown committed
101
	// Register swagger documentation.
Lee Brown's avatar
Lee Brown committed
102 103
	// TODO: Add authentication. Current authenticator requires an Authorization header
	// 		 which breaks the browser experience.
Lee Brown's avatar
Lee Brown committed
104 105
	app.Handle("GET", "/docs/", saasSwagger.WrapHandler)
	app.Handle("GET", "/docs/*", saasSwagger.WrapHandler)
106 107 108

	return app
}
109

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
// ExampleErrorResponse returns example error messages.
func ExampleErrorResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
	v, err := webcontext.ContextValues(ctx)
	if err != nil {
		return err
	}

	if qv := r.URL.Query().Get("test-validation-error"); qv != "" {
		_, err := project.Create(ctx, auth.Claims{}, nil, project.ProjectCreateRequest{}, v.Now)
		return web.RespondJsonError(ctx, w, err)

	}

	if qv := r.URL.Query().Get("test-web-error"); qv != "" {
		terr := errors.New("Some random error")
		terr = errors.WithMessage(terr, "Actual error message")
		rerr := weberror.NewError(ctx, terr, http.StatusBadRequest).(*weberror.Error)
		rerr.Message = "Test Web Error Message"
		return web.RespondJsonError(ctx, w, rerr)
	}

	if qv := r.URL.Query().Get("test-error"); qv != "" {
		terr := errors.New("Test error")
		terr = errors.WithMessage(terr, "Error message")
		return web.RespondJsonError(ctx, w, terr)
	}

	return nil
}

140 141
// Types godoc
// @Summary List of types.
142
// @Param data body weberror.FieldError false "Field Error"
143 144
// @Param data body web.TimeResponse false "Time Response"
// @Param data body web.EnumResponse false "Enum Response"
145
// @Param data body web.EnumMultiResponse false "Enum Multi Response"
146
// @Param data body web.EnumOption false "Enum Option"
Lee Brown's avatar
Lee Brown committed
147 148
// @Param data body signup.SignupAccount false "SignupAccount"
// @Param data body signup.SignupUser false "SignupUser"
149 150
// To support nested types not parsed by swag.
func Types() {}