rest-api: endpoint reworkings
Added documentation. Replaced PUT request with POST request. Added inline param support for PATCH request. Replaced unix timestamps handling with RFC-3339 timestampts handling. Added 'Bearer' method requirement for Authorization header. Change-Id: I4faa3864051dd18826c2c583ada53666d4aaec44
This commit is contained in:
parent
894b7b1cf3
commit
96411ba56a
@ -113,7 +113,12 @@ func (a *API) generateGo() ([]byte, error) {
|
||||
}
|
||||
p(")")
|
||||
p("")
|
||||
}
|
||||
|
||||
p("const dateLayout = \"2006-01-02T15:04:05.000Z\"")
|
||||
p("")
|
||||
|
||||
for _, group := range a.EndpointGroups {
|
||||
p("var Err%sAPI = errs.Class(\"%s %s api\")", cases.Title(language.Und).String(group.Prefix), a.PackageName, group.Prefix)
|
||||
p("")
|
||||
|
||||
@ -199,14 +204,12 @@ func (a *API) generateGo() ([]byte, error) {
|
||||
p("")
|
||||
continue
|
||||
case reflect.TypeOf(time.Time{}):
|
||||
p("%sStamp, err := strconv.ParseInt(r.URL.Query().Get(\"%s\"), 10, 64)", param.Name, param.Name)
|
||||
p("%s, err := time.Parse(dateLayout, r.URL.Query().Get(\"%s\"))", param.Name, param.Name)
|
||||
p("if err != nil {")
|
||||
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
p("%s := time.Unix(%sStamp, 0).UTC()", param.Name, param.Name)
|
||||
p("")
|
||||
continue
|
||||
case reflect.TypeOf(""):
|
||||
p("%s := r.URL.Query().Get(\"%s\")", param.Name, param.Name)
|
||||
@ -218,19 +221,17 @@ func (a *API) generateGo() ([]byte, error) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
case http.MethodPut:
|
||||
for _, param := range endpoint.Params {
|
||||
p("%s := &%s{}", param.Name, param.Type)
|
||||
p("if err = json.NewDecoder(r.Body).Decode(&%s); err != nil {", param.Name)
|
||||
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
}
|
||||
case http.MethodPatch:
|
||||
for _, param := range endpoint.Params {
|
||||
if param.Type == reflect.TypeOf(uuid.UUID{}) {
|
||||
p("%s, err := uuid.FromString(r.URL.Query().Get(\"%s\"))", param.Name, param.Name)
|
||||
p("%sParam, ok := mux.Vars(r)[\"%s\"]", param.Name, param.Name)
|
||||
p("if !ok {")
|
||||
p("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"missing %s route param\"))", param.Name)
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
|
||||
p("%s, err := uuid.FromString(%sParam)", param.Name, param.Name)
|
||||
p("if err != nil {")
|
||||
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
|
||||
p("return")
|
||||
@ -245,6 +246,15 @@ func (a *API) generateGo() ([]byte, error) {
|
||||
p("")
|
||||
}
|
||||
}
|
||||
case http.MethodPost:
|
||||
for _, param := range endpoint.Params {
|
||||
p("%s := &%s{}", param.Name, param.Type)
|
||||
p("if err = json.NewDecoder(r.Body).Decode(&%s); err != nil {", param.Name)
|
||||
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
}
|
||||
}
|
||||
|
||||
methodFormat := "retVal, httpErr := h.service.%s(ctx, "
|
||||
@ -253,10 +263,6 @@ func (a *API) generateGo() ([]byte, error) {
|
||||
for _, methodParam := range endpoint.Params {
|
||||
methodFormat += methodParam.Name + ", "
|
||||
}
|
||||
case http.MethodPut:
|
||||
for _, methodParam := range endpoint.Params {
|
||||
methodFormat += "*" + methodParam.Name + ", "
|
||||
}
|
||||
case http.MethodPatch:
|
||||
for _, methodParam := range endpoint.Params {
|
||||
if methodParam.Type == reflect.TypeOf(uuid.UUID{}) {
|
||||
@ -265,6 +271,10 @@ func (a *API) generateGo() ([]byte, error) {
|
||||
methodFormat += "*" + methodParam.Name + ", "
|
||||
}
|
||||
}
|
||||
case http.MethodPost:
|
||||
for _, methodParam := range endpoint.Params {
|
||||
methodFormat += "*" + methodParam.Name + ", "
|
||||
}
|
||||
}
|
||||
|
||||
methodFormat += ")"
|
||||
|
@ -53,9 +53,9 @@ func (eg *EndpointGroup) Patch(path string, endpoint *Endpoint) {
|
||||
eg.addEndpoint(path, http.MethodPatch, endpoint)
|
||||
}
|
||||
|
||||
// Put adds new PUT endpoint to endpoints group.
|
||||
func (eg *EndpointGroup) Put(path string, endpoint *Endpoint) {
|
||||
eg.addEndpoint(path, http.MethodPut, endpoint)
|
||||
// Post adds new POST endpoint to endpoints group.
|
||||
func (eg *EndpointGroup) Post(path string, endpoint *Endpoint) {
|
||||
eg.addEndpoint(path, http.MethodPost, endpoint)
|
||||
}
|
||||
|
||||
// addEndpoint adds new endpoint to endpoints list.
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@ -20,14 +19,16 @@ import (
|
||||
"storj.io/storj/satellite/console"
|
||||
)
|
||||
|
||||
const dateLayout = "2006-01-02T15:04:05.000Z"
|
||||
|
||||
var ErrProjectsAPI = errs.Class("consoleapi projects api")
|
||||
|
||||
type ProjectManagementService interface {
|
||||
GenCreateProject(context.Context, console.ProjectInfo) (*console.Project, api.HTTPError)
|
||||
GenUpdateProject(context.Context, uuid.UUID, console.ProjectInfo) (*console.Project, api.HTTPError)
|
||||
GenGetUsersProjects(context.Context) ([]console.Project, api.HTTPError)
|
||||
GenGetSingleBucketUsageRollup(context.Context, uuid.UUID, string, time.Time, time.Time) (*accounting.BucketUsageRollup, api.HTTPError)
|
||||
GenGetBucketUsageRollups(context.Context, uuid.UUID, time.Time, time.Time) ([]accounting.BucketUsageRollup, api.HTTPError)
|
||||
GenCreateProject(context.Context, console.ProjectInfo) (*console.Project, api.HTTPError)
|
||||
GenUpdateProject(context.Context, uuid.UUID, console.ProjectInfo) (*console.Project, api.HTTPError)
|
||||
}
|
||||
|
||||
// Handler is an api handler that exposes all projects related functionality.
|
||||
@ -48,8 +49,8 @@ func NewProjectManagement(log *zap.Logger, service ProjectManagementService, rou
|
||||
projectsRouter.HandleFunc("/", handler.handleGenGetUsersProjects).Methods("GET")
|
||||
projectsRouter.HandleFunc("/bucket-rollup", handler.handleGenGetSingleBucketUsageRollup).Methods("GET")
|
||||
projectsRouter.HandleFunc("/bucket-rollups", handler.handleGenGetBucketUsageRollups).Methods("GET")
|
||||
projectsRouter.HandleFunc("/create", handler.handleGenCreateProject).Methods("PUT")
|
||||
projectsRouter.HandleFunc("/update", handler.handleGenUpdateProject).Methods("PATCH")
|
||||
projectsRouter.HandleFunc("/create", handler.handleGenCreateProject).Methods("POST")
|
||||
projectsRouter.HandleFunc("/update/{id}", handler.handleGenUpdateProject).Methods("PATCH")
|
||||
|
||||
return handler
|
||||
}
|
||||
@ -73,22 +74,18 @@ func (h *Handler) handleGenGetBucketUsageRollups(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
sinceStamp, err := strconv.ParseInt(r.URL.Query().Get("since"), 10, 64)
|
||||
since, err := time.Parse(dateLayout, r.URL.Query().Get("since"))
|
||||
if err != nil {
|
||||
api.ServeError(h.log, w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
since := time.Unix(sinceStamp, 0).UTC()
|
||||
|
||||
beforeStamp, err := strconv.ParseInt(r.URL.Query().Get("before"), 10, 64)
|
||||
before, err := time.Parse(dateLayout, r.URL.Query().Get("before"))
|
||||
if err != nil {
|
||||
api.ServeError(h.log, w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
before := time.Unix(beforeStamp, 0).UTC()
|
||||
|
||||
retVal, httpErr := h.service.GenGetBucketUsageRollups(ctx, projectID, since, before)
|
||||
if httpErr.Err != nil {
|
||||
api.ServeError(h.log, w, httpErr.Status, httpErr.Err)
|
||||
@ -145,7 +142,13 @@ func (h *Handler) handleGenUpdateProject(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := uuid.FromString(r.URL.Query().Get("id"))
|
||||
idParam, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
api.ServeError(h.log, w, http.StatusBadRequest, errs.New("missing id route param"))
|
||||
return
|
||||
}
|
||||
|
||||
id, err := uuid.FromString(idParam)
|
||||
if err != nil {
|
||||
api.ServeError(h.log, w, http.StatusBadRequest, err)
|
||||
return
|
||||
@ -219,22 +222,18 @@ func (h *Handler) handleGenGetSingleBucketUsageRollup(w http.ResponseWriter, r *
|
||||
return
|
||||
}
|
||||
|
||||
sinceStamp, err := strconv.ParseInt(r.URL.Query().Get("since"), 10, 64)
|
||||
since, err := time.Parse(dateLayout, r.URL.Query().Get("since"))
|
||||
if err != nil {
|
||||
api.ServeError(h.log, w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
since := time.Unix(sinceStamp, 0).UTC()
|
||||
|
||||
beforeStamp, err := strconv.ParseInt(r.URL.Query().Get("before"), 10, 64)
|
||||
before, err := time.Parse(dateLayout, r.URL.Query().Get("before"))
|
||||
if err != nil {
|
||||
api.ServeError(h.log, w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
before := time.Unix(beforeStamp, 0).UTC()
|
||||
|
||||
retVal, httpErr := h.service.GenGetSingleBucketUsageRollup(ctx, projectID, bucket, since, before)
|
||||
if httpErr.Err != nil {
|
||||
api.ServeError(h.log, w, httpErr.Status, httpErr.Err)
|
||||
|
184
satellite/console/consoleweb/consoleapi/gen/README.md
Normal file
184
satellite/console/consoleweb/consoleapi/gen/README.md
Normal file
@ -0,0 +1,184 @@
|
||||
# Generated REST API design
|
||||
|
||||
Requires setting 'Authorization' header for requests.
|
||||
|
||||
## Successful responses
|
||||
All the requests a non-empty body for the resource that we're interacting with.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"project": {
|
||||
"name": "My Awesome Project",
|
||||
"description": "it is perfect"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error responses
|
||||
When an API endpoint returns an error (status code 4XX) it contains a JSON error response with 1 error field:
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "authorization key format is incorrect. Should be 'Bearer <key>'"
|
||||
}
|
||||
```
|
||||
|
||||
### Project Management API Endpoints
|
||||
Example of request
|
||||
```bash
|
||||
curl -i -L \
|
||||
-H "Accept: application/json" \
|
||||
-H 'Authorization: Bearer <key>' \
|
||||
-X GET \
|
||||
"https://satellite.qa.storj.io/api/v0/projects/"
|
||||
```
|
||||
#### GET projects/
|
||||
Gets users projects.
|
||||
|
||||
!!!WARNING!!! Project ID is used as encryption salt. Please don't send it to anyone. We're going to fix it soon.
|
||||
|
||||
A successful response body:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id":"cd8d64bd-7457-4661-b88d-2e257bd0d88a",
|
||||
"name":"My First Project",
|
||||
"description":"",
|
||||
"partnerId":"00000000-0000-0000-0000-000000000000",
|
||||
"userAgent":null,
|
||||
"ownerId":"f0ef7918-c8f0-4a9c-94fe-2260fb2a7877",
|
||||
"rateLimit":null,
|
||||
"burstLimit":null,
|
||||
"maxBuckets":null,
|
||||
"createdAt":"2022-04-15T11:38:36.951306+03:00",
|
||||
"memberCount":0,
|
||||
"storageLimit":"150.00 GB",
|
||||
"bandwidthLimit":"150.00 GB",
|
||||
"segmentLimit":150000
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### GET /bucket-rollup/projectID={uuid string}&bucket={string}&since={Date Timestamp like '2006-01-02T15:00:00Z'}&before={Date Timestamp like '2006-01-02T15:00:00Z'}
|
||||
Gets project's single bucket usage by bucket ID.
|
||||
|
||||
!!!WARNING!!! Project ID is used as encryption salt. Please don't send it to anyone. We're going to fix it soon.
|
||||
|
||||
A successful response body:
|
||||
|
||||
```json
|
||||
{
|
||||
"projectID":"f4f2688e-8dae-4401-8ff1-31d9154ba514",
|
||||
"bucketName":"bucket",
|
||||
"totalStoredData":0.011384611174500622,
|
||||
"totalSegments":0.21667078722222222,
|
||||
"objectCount":0.10833539361111111,
|
||||
"metadataSize":1.2241899478055554e-8,
|
||||
"repairEgress":0,
|
||||
"getEgress":0,
|
||||
"auditEgress":0,
|
||||
"since":"2006-01-02T15:00:00Z",
|
||||
"before":"2022-04-27T23:59:59Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /bucket-rollups/projectID={uuid string}&since={Date Timestamp like '2006-01-02T15:00:00Z'}&before={Date Timestamp like '2006-01-02T15:00:00Z'}
|
||||
Gets project's all buckets usage.
|
||||
|
||||
!!!WARNING!!! Project ID is used as encryption salt. Please don't send it to anyone. We're going to fix it soon.
|
||||
|
||||
A successful response body:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"projectID":"f4f2688e-8dae-4401-8ff1-31d9154ba514",
|
||||
"bucketName":"bucket",
|
||||
"totalStoredData":0.011384611174500622,
|
||||
"totalSegments":0.21667078722222222,
|
||||
"objectCount":0.10833539361111111,
|
||||
"metadataSize":1.2241899478055554e-8,
|
||||
"repairEgress":0,
|
||||
"getEgress":0,
|
||||
"auditEgress":0,
|
||||
"since":"2006-01-02T15:00:00Z",
|
||||
"before":"2022-04-27T23:59:59Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### POST /create
|
||||
Creates new Project with given info.
|
||||
|
||||
!!!WARNING!!! Project ID is used as encryption salt. Please don't send it to anyone. We're going to fix it soon.
|
||||
|
||||
Request body example:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "new project"
|
||||
}
|
||||
```
|
||||
|
||||
A successful response body:
|
||||
|
||||
```json
|
||||
{
|
||||
"id":"f4f2688e-8dae-4401-8ff1-31d9154ba514",
|
||||
"name":"new project",
|
||||
"description":"",
|
||||
"partnerId":"00000000-0000-0000-0000-000000000000",
|
||||
"userAgent":null,
|
||||
"ownerId":"f0ef7918-c8f0-4a9c-94fe-2260fb2a7877",
|
||||
"rateLimit":null,
|
||||
"burstLimit":null,
|
||||
"maxBuckets":null,
|
||||
"createdAt":"2022-04-27T13:23:59.013381+03:00",
|
||||
"memberCount":0,
|
||||
"storageLimit":"15 GB",
|
||||
"bandwidthLimit":"15 GB",
|
||||
"segmentLimit":15000
|
||||
}
|
||||
```
|
||||
|
||||
#### PATCH /update/{uuid string}
|
||||
Updates project with given info.
|
||||
|
||||
!!!WARNING!!! Project ID is used as encryption salt. Please don't send it to anyone. We're going to fix it soon.
|
||||
|
||||
Request body example:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "awesome project",
|
||||
"description": "random stuff",
|
||||
"bandwidthLimit": 1000000000,
|
||||
"storageLimit": 1000000000
|
||||
}
|
||||
```
|
||||
|
||||
A successful response body:
|
||||
|
||||
```json
|
||||
{
|
||||
"id":"f4f2688e-8dae-4401-8ff1-31d9154ba514",
|
||||
"name":"awesome project",
|
||||
"description":"random stuff",
|
||||
"partnerId":"00000000-0000-0000-0000-000000000000",
|
||||
"userAgent":null,
|
||||
"ownerId":"f0ef7918-c8f0-4a9c-94fe-2260fb2a7877",
|
||||
"rateLimit":null,
|
||||
"burstLimit":null,
|
||||
"maxBuckets":null,
|
||||
"createdAt":"2022-04-27T13:23:59.013381+03:00",
|
||||
"memberCount":0,
|
||||
"storageLimit":"1 GB",
|
||||
"bandwidthLimit":"1 GB",
|
||||
"segmentLimit":15000
|
||||
}
|
||||
```
|
@ -57,7 +57,7 @@ func main() {
|
||||
},
|
||||
})
|
||||
|
||||
g.Put("/create", &apigen.Endpoint{
|
||||
g.Post("/create", &apigen.Endpoint{
|
||||
Name: "Create new Project",
|
||||
Description: "Creates new Project with given info",
|
||||
MethodName: "GenCreateProject",
|
||||
@ -67,7 +67,7 @@ func main() {
|
||||
},
|
||||
})
|
||||
|
||||
g.Patch("/update", &apigen.Endpoint{
|
||||
g.Patch("/update/{id}", &apigen.Endpoint{
|
||||
Name: "Update Project",
|
||||
Description: "Updates project with given info",
|
||||
MethodName: "GenUpdateProject",
|
||||
|
@ -104,8 +104,8 @@ type Project struct {
|
||||
type ProjectInfo struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
StorageLimit memory.Size `json:"project specific storage limit"`
|
||||
BandwidthLimit memory.Size `json:"project specific bandwidth limit"`
|
||||
StorageLimit memory.Size `json:"storageLimit"`
|
||||
BandwidthLimit memory.Size `json:"bandwidthLimit"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
@ -2214,11 +2215,14 @@ func (s *Service) cookieAuth(ctx context.Context, r *http.Request) (context.Cont
|
||||
|
||||
// keyAuth checks if request has an authorization api key.
|
||||
func (s *Service) keyAuth(ctx context.Context, r *http.Request) (context.Context, error) {
|
||||
apikey := r.Header.Get("Authorization")
|
||||
if apikey == "" {
|
||||
return nil, errs.New("no authorization key was provided")
|
||||
authToken := r.Header.Get("Authorization")
|
||||
split := strings.Split(authToken, "Bearer ")
|
||||
if len(split) != 2 {
|
||||
return nil, errs.New("authorization key format is incorrect. Should be 'Bearer <key>'")
|
||||
}
|
||||
|
||||
apikey := split[1]
|
||||
|
||||
ctx = consoleauth.WithAPIKey(ctx, []byte(apikey))
|
||||
|
||||
userID, exp, err := s.restKeys.GetUserAndExpirationFromKey(ctx, apikey)
|
||||
|
Loading…
Reference in New Issue
Block a user