diff --git a/private/apigen/api.go b/private/apigen/api.go index 5ff74b951..7c7c7a108 100644 --- a/private/apigen/api.go +++ b/private/apigen/api.go @@ -227,6 +227,24 @@ func (a *API) generateGo() ([]byte, error) { 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("if err != nil {") + p("api.ServeError(h.log, w, http.StatusBadRequest, err)") + p("return") + p("}") + p("") + } else { + 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, " @@ -239,6 +257,14 @@ func (a *API) generateGo() ([]byte, error) { for _, methodParam := range endpoint.Params { methodFormat += "*" + methodParam.Name + ", " } + case http.MethodPatch: + for _, methodParam := range endpoint.Params { + if methodParam.Type == reflect.TypeOf(uuid.UUID{}) { + methodFormat += methodParam.Name + ", " + } else { + methodFormat += "*" + methodParam.Name + ", " + } + } } methodFormat += ")" diff --git a/private/apigen/endpoint.go b/private/apigen/endpoint.go index db5679d9c..c63ac5cad 100644 --- a/private/apigen/endpoint.go +++ b/private/apigen/endpoint.go @@ -48,6 +48,11 @@ func (eg *EndpointGroup) Get(path string, endpoint *Endpoint) { eg.addEndpoint(path, http.MethodGet, endpoint) } +// Patch adds new PATCH endpoint to endpoints group. +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) diff --git a/satellite/console/consoleweb/consoleapi/api.gen.go b/satellite/console/consoleweb/consoleapi/api.gen.go index 0c8d44383..000f30349 100644 --- a/satellite/console/consoleweb/consoleapi/api.gen.go +++ b/satellite/console/consoleweb/consoleapi/api.gen.go @@ -23,10 +23,11 @@ import ( 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) } // Handler is an api handler that exposes all projects related functionality. @@ -44,14 +45,62 @@ func NewProjectManagement(log *zap.Logger, service ProjectManagementService, rou } projectsRouter := router.PathPrefix("/api/v0/projects").Subrouter() - projectsRouter.HandleFunc("/create", handler.handleGenCreateProject).Methods("PUT") 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") return handler } +func (h *Handler) handleGenGetBucketUsageRollups(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + w.Header().Set("Content-Type", "application/json") + + ctx, err = h.auth.IsAuthenticated(ctx, r, true, true) + if err != nil { + api.ServeError(h.log, w, http.StatusUnauthorized, err) + return + } + + projectID, err := uuid.FromString(r.URL.Query().Get("projectID")) + if err != nil { + api.ServeError(h.log, w, http.StatusBadRequest, err) + return + } + + sinceStamp, err := strconv.ParseInt(r.URL.Query().Get("since"), 10, 64) + 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) + 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) + return + } + + err = json.NewEncoder(w).Encode(retVal) + if err != nil { + h.log.Debug("failed to write json GenGetBucketUsageRollups response", zap.Error(ErrProjectsAPI.Wrap(err))) + } +} + func (h *Handler) handleGenCreateProject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -83,6 +132,43 @@ func (h *Handler) handleGenCreateProject(w http.ResponseWriter, r *http.Request) } } +func (h *Handler) handleGenUpdateProject(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + w.Header().Set("Content-Type", "application/json") + + ctx, err = h.auth.IsAuthenticated(ctx, r, true, true) + if err != nil { + api.ServeError(h.log, w, http.StatusUnauthorized, err) + return + } + + id, err := uuid.FromString(r.URL.Query().Get("id")) + if err != nil { + api.ServeError(h.log, w, http.StatusBadRequest, err) + return + } + + projectInfo := &console.ProjectInfo{} + if err = json.NewDecoder(r.Body).Decode(&projectInfo); err != nil { + api.ServeError(h.log, w, http.StatusBadRequest, err) + return + } + + retVal, httpErr := h.service.GenUpdateProject(ctx, id, *projectInfo) + if httpErr.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 GenUpdateProject response", zap.Error(ErrProjectsAPI.Wrap(err))) + } +} + func (h *Handler) handleGenGetUsersProjects(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -160,50 +246,3 @@ func (h *Handler) handleGenGetSingleBucketUsageRollup(w http.ResponseWriter, r * h.log.Debug("failed to write json GenGetSingleBucketUsageRollup response", zap.Error(ErrProjectsAPI.Wrap(err))) } } - -func (h *Handler) handleGenGetBucketUsageRollups(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var err error - defer mon.Task()(&ctx)(&err) - - w.Header().Set("Content-Type", "application/json") - - ctx, err = h.auth.IsAuthenticated(ctx, r, true, true) - if err != nil { - api.ServeError(h.log, w, http.StatusUnauthorized, err) - return - } - - projectID, err := uuid.FromString(r.URL.Query().Get("projectID")) - if err != nil { - api.ServeError(h.log, w, http.StatusBadRequest, err) - return - } - - sinceStamp, err := strconv.ParseInt(r.URL.Query().Get("since"), 10, 64) - 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) - 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) - return - } - - err = json.NewEncoder(w).Encode(retVal) - if err != nil { - h.log.Debug("failed to write json GenGetBucketUsageRollups response", zap.Error(ErrProjectsAPI.Wrap(err))) - } -} diff --git a/satellite/console/consoleweb/consoleapi/gen/main.go b/satellite/console/consoleweb/consoleapi/gen/main.go index 2f8965d47..20a2cb277 100644 --- a/satellite/console/consoleweb/consoleapi/gen/main.go +++ b/satellite/console/consoleweb/consoleapi/gen/main.go @@ -65,6 +65,17 @@ func main() { apigen.NewParam("projectInfo", console.ProjectInfo{}), }, }) + + g.Patch("/update", &apigen.Endpoint{ + Name: "Update Project", + Description: "Updates project with given info", + MethodName: "GenUpdateProject", + Response: &console.Project{}, + Params: []apigen.Param{ + apigen.NewParam("id", uuid.UUID{}), + apigen.NewParam("projectInfo", console.ProjectInfo{}), + }, + }) } a.MustWrite("satellite/console/consoleweb/consoleapi/api.gen.go") diff --git a/satellite/console/service.go b/satellite/console/service.go index 8bcddb4d1..81e55e82e 100644 --- a/satellite/console/service.go +++ b/satellite/console/service.go @@ -1388,6 +1388,117 @@ func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, projec return project, nil } +// GenUpdateProject is a method for updating project name and description by id for generated api. +func (s *Service) GenUpdateProject(ctx context.Context, projectID uuid.UUID, projectInfo ProjectInfo) (p *Project, httpError api.HTTPError) { + var err error + defer mon.Task()(&ctx)(&err) + + auth, err := s.getAuthAndAuditLog(ctx, "update project name and description", zap.String("projectID", projectID.String())) + if err != nil { + return nil, api.HTTPError{ + Status: http.StatusUnauthorized, + Err: Error.Wrap(err), + } + } + + err = ValidateNameAndDescription(projectInfo.Name, projectInfo.Description) + if err != nil { + return nil, api.HTTPError{ + Status: http.StatusBadRequest, + Err: Error.Wrap(err), + } + } + + isMember, err := s.isProjectMember(ctx, auth.User.ID, projectID) + if err != nil { + return nil, api.HTTPError{ + Status: http.StatusUnauthorized, + Err: Error.Wrap(err), + } + } + project := isMember.project + project.Name = projectInfo.Name + project.Description = projectInfo.Description + + if auth.User.PaidTier { + if project.BandwidthLimit != nil && *project.BandwidthLimit == 0 { + return nil, api.HTTPError{ + Status: http.StatusInternalServerError, + Err: Error.New("current bandwidth limit for project is set to 0 (updating disabled)"), + } + } + if project.StorageLimit != nil && *project.StorageLimit == 0 { + return nil, api.HTTPError{ + Status: http.StatusInternalServerError, + Err: Error.New("current storage limit for project is set to 0 (updating disabled)"), + } + } + if projectInfo.StorageLimit <= 0 || projectInfo.BandwidthLimit <= 0 { + return nil, api.HTTPError{ + Status: http.StatusBadRequest, + Err: Error.New("project limits must be greater than 0"), + } + } + + if projectInfo.StorageLimit > s.config.UsageLimits.Storage.Paid { + return nil, api.HTTPError{ + Status: http.StatusBadRequest, + Err: Error.New("specified storage limit exceeds allowed maximum for current tier"), + } + } + + if projectInfo.BandwidthLimit > s.config.UsageLimits.Bandwidth.Paid { + return nil, api.HTTPError{ + Status: http.StatusBadRequest, + Err: Error.New("specified bandwidth limit exceeds allowed maximum for current tier"), + } + } + + storageUsed, err := s.projectUsage.GetProjectStorageTotals(ctx, projectID) + if err != nil { + return nil, api.HTTPError{ + Status: http.StatusInternalServerError, + Err: Error.Wrap(err), + } + } + if projectInfo.StorageLimit.Int64() < storageUsed { + return nil, api.HTTPError{ + Status: http.StatusBadRequest, + Err: Error.New("cannot set storage limit below current usage"), + } + } + + bandwidthUsed, err := s.projectUsage.GetProjectBandwidthTotals(ctx, projectID) + if err != nil { + return nil, api.HTTPError{ + Status: http.StatusInternalServerError, + Err: Error.Wrap(err), + } + } + if projectInfo.BandwidthLimit.Int64() < bandwidthUsed { + return nil, api.HTTPError{ + Status: http.StatusBadRequest, + Err: Error.New("cannot set bandwidth limit below current usage"), + } + } + + project.StorageLimit = new(memory.Size) + *project.StorageLimit = projectInfo.StorageLimit + project.BandwidthLimit = new(memory.Size) + *project.BandwidthLimit = projectInfo.BandwidthLimit + } + + err = s.store.Projects().Update(ctx, project) + if err != nil { + return nil, api.HTTPError{ + Status: http.StatusInternalServerError, + Err: Error.Wrap(err), + } + } + + return project, httpError +} + // AddProjectMembers adds users by email to given project. func (s *Service) AddProjectMembers(ctx context.Context, projectID uuid.UUID, emails []string) (users []*User, err error) { defer mon.Task()(&ctx)(&err)