satellite/console{gen}: GetUsersProjects endpoint
Initial implementation of auto-generated GetUsersProjects endpoint Change-Id: If41bff2ea3ff9cfc87afeda9e5e5b3f586cbab33
This commit is contained in:
parent
cf03209c16
commit
07c71e34c2
11
private/api/authentication.go
Normal file
11
private/api/authentication.go
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package api
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Auth exposes methods to control authentication process for each endpoint.
|
||||
type Auth interface {
|
||||
IsAuthenticated(r *http.Request) error
|
||||
}
|
49
private/api/errorhandling.go
Normal file
49
private/api/errorhandling.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// HTTPError holds http error entity with http status and error itself.
|
||||
type HTTPError struct {
|
||||
Status int
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error returns http error's string representation.
|
||||
func (e HTTPError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// ServeError writes JSON error to response output stream.
|
||||
func ServeError(log *zap.Logger, w http.ResponseWriter, status int, err error) {
|
||||
msg := err.Error()
|
||||
fields := []zap.Field{
|
||||
zap.Int("code", status),
|
||||
zap.String("message", msg),
|
||||
zap.Error(err),
|
||||
}
|
||||
|
||||
if status == http.StatusNoContent {
|
||||
return
|
||||
} else if status/100 == 5 { // Check for 5XX status.
|
||||
log.Error("returning error to client", fields...)
|
||||
} else {
|
||||
log.Debug("returning error to client", fields...)
|
||||
}
|
||||
|
||||
w.WriteHeader(status)
|
||||
|
||||
err = json.NewEncoder(w).Encode(map[string]string{
|
||||
"error": msg,
|
||||
})
|
||||
if err != nil {
|
||||
log.Debug("failed to write json error response", zap.Error(err))
|
||||
}
|
||||
}
|
@ -3,21 +3,27 @@
|
||||
|
||||
package apigen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/format"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/private/api"
|
||||
)
|
||||
|
||||
// API represents specific API's configuration.
|
||||
type API struct {
|
||||
Version string
|
||||
Description string
|
||||
PackageName string
|
||||
Auth api.Auth
|
||||
EndpointGroups []*EndpointGroup
|
||||
}
|
||||
|
||||
// New creates new API with specific configuration.
|
||||
func New(version, description string) *API {
|
||||
return &API{
|
||||
Version: version,
|
||||
Description: description,
|
||||
}
|
||||
}
|
||||
|
||||
// Group adds new endpoints group to API.
|
||||
func (a *API) Group(name, prefix string) *EndpointGroup {
|
||||
group := &EndpointGroup{
|
||||
@ -30,3 +36,169 @@ func (a *API) Group(name, prefix string) *EndpointGroup {
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
// MustWrite writes generated code into a file.
|
||||
func (a *API) MustWrite(path string) {
|
||||
generated, err := a.generateGo()
|
||||
if err != nil {
|
||||
panic(errs.Wrap(err))
|
||||
}
|
||||
|
||||
err = os.WriteFile(path+"api.gen.go", generated, 0755)
|
||||
if err != nil {
|
||||
panic(errs.Wrap(err))
|
||||
}
|
||||
}
|
||||
|
||||
// generateGo generates api code and returns an output.
|
||||
func (a *API) generateGo() ([]byte, error) {
|
||||
var result string
|
||||
|
||||
p := func(format string, a ...interface{}) {
|
||||
result += fmt.Sprintf(format+"\n", a...)
|
||||
}
|
||||
|
||||
getPackageName := func(path string) string {
|
||||
pathPackages := strings.Split(path, "/")
|
||||
return pathPackages[len(pathPackages)-1]
|
||||
}
|
||||
|
||||
p("// AUTOGENERATED BY private/apigen")
|
||||
p("// DO NOT EDIT.")
|
||||
p("")
|
||||
|
||||
p("package %s", a.PackageName)
|
||||
p("")
|
||||
|
||||
p("import (")
|
||||
p(`"context"`)
|
||||
p(`"encoding/json"`)
|
||||
p(`"net/http"`)
|
||||
p("")
|
||||
p(`"github.com/gorilla/mux"`)
|
||||
p(`"github.com/zeebo/errs"`)
|
||||
p(`"go.uber.org/zap"`)
|
||||
p("")
|
||||
|
||||
p(`"storj.io/storj/private/api"`)
|
||||
|
||||
for _, group := range a.EndpointGroups {
|
||||
for _, method := range group.Endpoints {
|
||||
if method.Request != nil {
|
||||
path := reflect.TypeOf(method.Request).Elem().PkgPath()
|
||||
pn := getPackageName(path)
|
||||
if pn == a.PackageName {
|
||||
continue
|
||||
}
|
||||
|
||||
p(`"%s"`, path)
|
||||
}
|
||||
if method.Response != nil {
|
||||
path := reflect.TypeOf(method.Response).Elem().PkgPath()
|
||||
pn := getPackageName(path)
|
||||
if pn == a.PackageName {
|
||||
continue
|
||||
}
|
||||
|
||||
p(`"%s"`, path)
|
||||
}
|
||||
}
|
||||
p(")")
|
||||
p("")
|
||||
|
||||
p("var Err%sAPI = errs.Class(\"%s %s api\")", strings.Title(group.Prefix), a.PackageName, group.Prefix)
|
||||
p("")
|
||||
|
||||
p("type %sService interface {", group.Name)
|
||||
for _, method := range group.Endpoints {
|
||||
responseType := reflect.TypeOf(method.Response)
|
||||
if strings.Contains(responseType.String(), a.PackageName) {
|
||||
p("%s(context.Context) (%s, api.HTTPError)", method.MethodName, responseType.Elem().Name())
|
||||
} else {
|
||||
p("%s(context.Context) (%s, api.HTTPError)", method.MethodName, responseType)
|
||||
}
|
||||
}
|
||||
p("}")
|
||||
p("")
|
||||
|
||||
p("type Handler struct {")
|
||||
p("log *zap.Logger")
|
||||
p("service %sService", group.Name)
|
||||
p("auth api.Auth")
|
||||
p("}")
|
||||
p("")
|
||||
|
||||
p(
|
||||
"func New%s(log *zap.Logger, service %sService, router *mux.Router) *Handler {",
|
||||
group.Name,
|
||||
group.Name,
|
||||
)
|
||||
p("handler := &Handler{")
|
||||
p("log: log,")
|
||||
p("service: service,")
|
||||
p("}")
|
||||
p("")
|
||||
p("%sRouter := router.PathPrefix(\"/api/v0/%s\").Subrouter()", group.Prefix, group.Prefix)
|
||||
for pathMethod, endpoint := range group.Endpoints {
|
||||
handlerName := "handle" + endpoint.MethodName
|
||||
p("%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")", group.Prefix, pathMethod.Path, handlerName, pathMethod.Method)
|
||||
}
|
||||
p("")
|
||||
p("return handler")
|
||||
p("}")
|
||||
|
||||
for _, endpoint := range group.Endpoints {
|
||||
p("")
|
||||
handlerName := "handle" + endpoint.MethodName
|
||||
p("func (h *Handler) %s(w http.ResponseWriter, r *http.Request) {", handlerName)
|
||||
p("ctx := r.Context()")
|
||||
p("var err error")
|
||||
p("defer mon.Task()(&ctx)(&err)")
|
||||
p("")
|
||||
|
||||
p("w.Header().Set(\"Content-Type\", \"application/json\")")
|
||||
p("")
|
||||
|
||||
if !endpoint.NoCookieAuth {
|
||||
p("err = h.auth.IsAuthenticated(r)")
|
||||
p("if err != nil {")
|
||||
p("api.ServeError(h.log, w, http.StatusUnauthorized, err)")
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
}
|
||||
|
||||
methodFormat := "retVal, httpErr := h.service.%s(ctx"
|
||||
args := []string{endpoint.MethodName}
|
||||
|
||||
// TODO to be implemented
|
||||
// if !endpoint.NoAPIAuth {}
|
||||
|
||||
methodFormat += ")"
|
||||
interfaceArgs := make([]interface{}, len(args))
|
||||
for i, v := range args {
|
||||
interfaceArgs[i] = v
|
||||
}
|
||||
p(methodFormat, interfaceArgs...)
|
||||
p("if err != nil {")
|
||||
p("api.ServeError(h.log, w, httpErr.Status, httpErr.Err)")
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
|
||||
p("err = json.NewEncoder(w).Encode(retVal)")
|
||||
p("if err != nil {")
|
||||
p("h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))", endpoint.MethodName, strings.Title(group.Prefix))
|
||||
p("}")
|
||||
p("}")
|
||||
p("")
|
||||
}
|
||||
}
|
||||
|
||||
output, err := format.Source([]byte(result))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
66
satellite/console/consoleweb/consoleapi/api.gen.go
Executable file
66
satellite/console/consoleweb/consoleapi/api.gen.go
Executable file
@ -0,0 +1,66 @@
|
||||
// AUTOGENERATED BY private/apigen
|
||||
// DO NOT EDIT.
|
||||
|
||||
package consoleapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/storj/private/api"
|
||||
"storj.io/storj/satellite/console"
|
||||
)
|
||||
|
||||
var ErrProjectsAPI = errs.Class("consoleapi projects api")
|
||||
|
||||
type ProjectManagementService interface {
|
||||
GetUserProjects(context.Context) ([]console.Project, api.HTTPError)
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
log *zap.Logger
|
||||
service ProjectManagementService
|
||||
auth api.Auth
|
||||
}
|
||||
|
||||
func NewProjectManagement(log *zap.Logger, service ProjectManagementService, router *mux.Router) *Handler {
|
||||
handler := &Handler{
|
||||
log: log,
|
||||
service: service,
|
||||
}
|
||||
|
||||
projectsRouter := router.PathPrefix("/api/v0/projects").Subrouter()
|
||||
projectsRouter.HandleFunc("/", handler.handleGetUserProjects).Methods("GET")
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetUserProjects(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err = h.auth.IsAuthenticated(r)
|
||||
if err != nil {
|
||||
api.ServeError(h.log, w, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
retVal, httpErr := h.service.GetUserProjects(ctx)
|
||||
if err != nil {
|
||||
api.ServeError(h.log, w, httpErr.Status, httpErr.Err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(retVal)
|
||||
if err != nil {
|
||||
h.log.Debug("failed to write json GetUserProjects response", zap.Error(ErrProjectsAPI.Wrap(err)))
|
||||
}
|
||||
}
|
@ -11,16 +11,23 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
api := apigen.New("v1", "")
|
||||
a := &apigen.API{
|
||||
Version: "v1",
|
||||
Description: "",
|
||||
PackageName: "consoleapi",
|
||||
}
|
||||
|
||||
{
|
||||
g := api.Group("Projects", "projects")
|
||||
g := a.Group("ProjectManagement", "projects")
|
||||
|
||||
g.Get("/", &apigen.Endpoint{
|
||||
Name: "List Projects",
|
||||
Description: "Lists all projects user has",
|
||||
MethodName: "ListUserProjects",
|
||||
Name: "Get Projects",
|
||||
Description: "Gets all projects user has",
|
||||
MethodName: "GetUserProjects",
|
||||
Response: []console.Project{},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
a.MustWrite("satellite/console/consoleweb/consoleapi/")
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
|
||||
* Update project name and description.
|
||||
*
|
||||
* @param projectId - project ID
|
||||
* @param name - project name
|
||||
* @param description - project description
|
||||
* @param projectFields - project fields
|
||||
* @param projectLimits - project limits
|
||||
* @returns Project[]
|
||||
* @throws Error
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user