diff --git a/satellite/analytics/service.go b/satellite/analytics/service.go index 6013350fd..eda445f8c 100644 --- a/satellite/analytics/service.go +++ b/satellite/analytics/service.go @@ -93,6 +93,7 @@ const ( eventResendInviteClicked = "Resend Invite Clicked" eventCopyInviteLinkClicked = "Copy Invite Link Clicked" eventRemoveProjectMemberCLicked = "Remove Member Clicked" + eventLimitIncreaseRequested = "Limit Increase Requested" ) var ( @@ -128,6 +129,14 @@ type FreezeTracker interface { TrackStorjscanUnpaidInvoice(invID string, userID uuid.UUID, email string) } +// LimitRequestInfo holds data needed to request limit increase. +type LimitRequestInfo struct { + ProjectName string + LimitType string + CurrentLimit string + DesiredLimit string +} + // Service for sending analytics. // // architecture: Service @@ -357,6 +366,27 @@ func (service *Service) TrackAccountFrozen(userID uuid.UUID, email string) { }) } +// TrackRequestLimitIncrease sends a limit increase request to Segment. +func (service *Service) TrackRequestLimitIncrease(userID uuid.UUID, email string, info LimitRequestInfo) { + if !service.config.Enabled { + return + } + + props := segment.NewProperties() + props.Set("email", email) + props.Set("satellite", service.satelliteName) + props.Set("project", info.ProjectName) + props.Set("type", info.LimitType) + props.Set("currentLimit", info.CurrentLimit) + props.Set("desiredLimit", info.DesiredLimit) + + service.enqueueMessage(segment.Track{ + UserId: userID.String(), + Event: service.satelliteName + " " + eventLimitIncreaseRequested, + Properties: props, + }) +} + // TrackAccountUnfrozen sends an account unfrozen event to Segment. func (service *Service) TrackAccountUnfrozen(userID uuid.UUID, email string) { if !service.config.Enabled { diff --git a/satellite/console/consoleweb/consoleapi/projects.go b/satellite/console/consoleweb/consoleapi/projects.go index be3c6d46d..212b8f592 100644 --- a/satellite/console/consoleweb/consoleapi/projects.go +++ b/satellite/console/consoleweb/consoleapi/projects.go @@ -204,6 +204,58 @@ func (p *Projects) UpdateProject(w http.ResponseWriter, r *http.Request) { } } +// RequestLimitIncrease handles requesting limit increase for projects. +func (p *Projects) RequestLimitIncrease(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + var ok bool + var idParam string + + if idParam, ok = mux.Vars(r)["id"]; !ok { + p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing project id route param")) + return + } + + id, err := uuid.FromString(idParam) + if err != nil { + p.serveJSONError(ctx, w, http.StatusBadRequest, err) + return + } + + var payload console.LimitRequestInfo + + err = json.NewDecoder(r.Body).Decode(&payload) + if err != nil { + p.serveJSONError(ctx, w, http.StatusBadRequest, err) + return + } + + if payload.LimitType == "" { + p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing limit type")) + return + } + if payload.DesiredLimit == 0 { + p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing desired limit")) + return + } + if payload.CurrentLimit == 0 { + p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing current limit")) + return + } + + err = p.service.RequestLimitIncrease(ctx, id, payload) + if err != nil { + if console.ErrUnauthorized.Has(err) { + p.serveJSONError(ctx, w, http.StatusUnauthorized, err) + return + } + + p.serveJSONError(ctx, w, http.StatusInternalServerError, err) + } +} + // CreateProject handles creating projects. func (p *Projects) CreateProject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() diff --git a/satellite/console/consoleweb/endpoints_test.go b/satellite/console/consoleweb/endpoints_test.go index 7ccd1fde0..d8938f2e7 100644 --- a/satellite/console/consoleweb/endpoints_test.go +++ b/satellite/console/consoleweb/endpoints_test.go @@ -660,6 +660,15 @@ func TestWrongUser(t *testing.T) { endpoint: getProjectResourceUrl("members"), method: http.MethodGet, }, + { + endpoint: getProjectResourceUrl("limit-increase"), + method: http.MethodPost, + body: map[string]interface{}{ + "limitType": "storage", + "currentLimit": "100000000", + "desiredLimit": "200000000", + }, + }, { endpoint: getProjectResourceUrl("invite"), method: http.MethodPost, diff --git a/satellite/console/consoleweb/server.go b/satellite/console/consoleweb/server.go index adc5331b3..fb90a3db7 100644 --- a/satellite/console/consoleweb/server.go +++ b/satellite/console/consoleweb/server.go @@ -282,6 +282,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc projectsRouter.Handle("", http.HandlerFunc(projectsController.CreateProject)).Methods(http.MethodPost, http.MethodOptions) projectsRouter.Handle("/paged", http.HandlerFunc(projectsController.GetPagedProjects)).Methods(http.MethodGet, http.MethodOptions) projectsRouter.Handle("/{id}", http.HandlerFunc(projectsController.UpdateProject)).Methods(http.MethodPatch, http.MethodOptions) + projectsRouter.Handle("/{id}/limit-increase", http.HandlerFunc(projectsController.RequestLimitIncrease)).Methods(http.MethodPost, http.MethodOptions) projectsRouter.Handle("/{id}/members", http.HandlerFunc(projectsController.DeleteMembersAndInvitations)).Methods(http.MethodDelete, http.MethodOptions) projectsRouter.Handle("/{id}/salt", http.HandlerFunc(projectsController.GetSalt)).Methods(http.MethodGet, http.MethodOptions) projectsRouter.Handle("/{id}/members", http.HandlerFunc(projectsController.GetMembersAndInvitations)).Methods(http.MethodGet, http.MethodOptions) diff --git a/satellite/console/projects.go b/satellite/console/projects.go index 0afc25e87..06735c93a 100644 --- a/satellite/console/projects.go +++ b/satellite/console/projects.go @@ -169,6 +169,13 @@ type ProjectInfoPage struct { TotalCount int64 `json:"totalCount"` } +// LimitRequestInfo holds data needed to request limit increase. +type LimitRequestInfo struct { + LimitType string `json:"limitType"` + CurrentLimit memory.Size `json:"currentLimit"` + DesiredLimit memory.Size `json:"desiredLimit"` +} + // ValidateNameAndDescription validates project name and description strings. // Project name must have more than 0 and less than 21 symbols. // Project description can't have more than hundred symbols. diff --git a/satellite/console/service.go b/satellite/console/service.go index 8707f6f66..1d7dfbb82 100644 --- a/satellite/console/service.go +++ b/satellite/console/service.go @@ -1928,6 +1928,30 @@ func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, update return project, nil } +// RequestLimitIncrease is a method for requesting limit increase for a project. +func (s *Service) RequestLimitIncrease(ctx context.Context, projectID uuid.UUID, info LimitRequestInfo) (err error) { + defer mon.Task()(&ctx)(&err) + + user, err := s.getUserAndAuditLog(ctx, "request limit increase", zap.String("projectID", projectID.String())) + if err != nil { + return Error.Wrap(err) + } + + _, project, err := s.isProjectOwner(ctx, user.ID, projectID) + if err != nil { + return Error.Wrap(err) + } + + s.analytics.TrackRequestLimitIncrease(user.ID, user.Email, analytics.LimitRequestInfo{ + ProjectName: project.Name, + LimitType: info.LimitType, + CurrentLimit: info.CurrentLimit.String(), + DesiredLimit: info.DesiredLimit.String(), + }) + + return 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 UpsertProjectInfo) (p *Project, httpError api.HTTPError) { var err error