2020-02-07 17:24:58 +00:00
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package admin
import (
2023-06-13 16:58:24 +01:00
"bytes"
2020-08-13 13:40:05 +01:00
"context"
2020-07-06 21:28:49 +01:00
"database/sql"
2020-02-07 17:24:58 +00:00
"encoding/json"
2020-07-06 21:28:49 +01:00
"errors"
2020-02-07 17:24:58 +00:00
"fmt"
2022-10-11 12:39:08 +01:00
"io"
2020-02-07 17:24:58 +00:00
"net/http"
2020-07-06 21:15:55 +01:00
"time"
2020-02-07 17:24:58 +00:00
"github.com/gorilla/mux"
"github.com/gorilla/schema"
2023-06-13 16:58:24 +01:00
"github.com/zeebo/errs/v2"
2020-02-07 17:24:58 +00:00
2020-05-18 18:36:09 +01:00
"storj.io/common/macaroon"
2020-02-07 17:24:58 +00:00
"storj.io/common/memory"
2023-07-25 16:26:35 +01:00
"storj.io/common/storj"
2020-03-30 10:08:50 +01:00
"storj.io/common/uuid"
2023-04-13 13:04:07 +01:00
"storj.io/storj/satellite/buckets"
2020-05-11 17:05:36 +01:00
"storj.io/storj/satellite/console"
2023-04-06 12:41:14 +01:00
"storj.io/storj/satellite/payments/stripe"
2020-02-07 17:24:58 +00:00
)
2020-08-13 13:40:05 +01:00
func ( server * Server ) checkProjectUsage ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
vars := mux . Vars ( r )
projectUUIDString , ok := vars [ "project" ]
if ! ok {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "project-uuid missing" ,
2020-08-13 13:40:05 +01:00
"" , http . StatusBadRequest )
return
}
2023-09-05 20:28:39 +01:00
project , err := server . getProjectByAnyID ( ctx , projectUUIDString )
if errors . Is ( err , sql . ErrNoRows ) {
sendJSONError ( w , "project with specified uuid does not exist" ,
"" , http . StatusNotFound )
return
}
2020-08-13 13:40:05 +01:00
if err != nil {
2023-09-05 20:28:39 +01:00
sendJSONError ( w , "error getting project" ,
err . Error ( ) , http . StatusInternalServerError )
2020-08-13 13:40:05 +01:00
return
}
2023-09-05 20:28:39 +01:00
if ! server . checkUsage ( ctx , w , project . ID ) {
2021-10-01 12:50:21 +01:00
sendJSONData ( w , http . StatusOK , [ ] byte ( ` { "result":"no project usage exist"} ` ) )
2020-08-13 13:40:05 +01:00
}
}
2020-09-05 20:17:18 +01:00
func ( server * Server ) getProject ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
vars := mux . Vars ( r )
projectUUIDString , ok := vars [ "project" ]
if ! ok {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "project-uuid missing" ,
2020-09-05 20:17:18 +01:00
"" , http . StatusBadRequest )
return
}
if err := r . ParseForm ( ) ; err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "invalid form" ,
2020-09-05 20:17:18 +01:00
err . Error ( ) , http . StatusBadRequest )
return
}
2023-09-05 20:28:39 +01:00
project , err := server . getProjectByAnyID ( ctx , projectUUIDString )
if errors . Is ( err , sql . ErrNoRows ) {
sendJSONError ( w , "project with specified uuid does not exist" ,
"" , http . StatusNotFound )
return
}
2020-09-05 20:17:18 +01:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "unable to fetch project details" ,
2020-09-05 20:17:18 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-09-03 22:12:26 +01:00
return
2020-09-05 20:17:18 +01:00
}
data , err := json . Marshal ( project )
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "json encoding failed" ,
2020-09-05 20:17:18 +01:00
err . Error ( ) , http . StatusInternalServerError )
return
}
2021-10-01 12:50:21 +01:00
sendJSONData ( w , http . StatusOK , data )
2020-09-05 20:17:18 +01:00
}
2020-02-07 17:24:58 +00:00
func ( server * Server ) getProjectLimit ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
vars := mux . Vars ( r )
projectUUIDString , ok := vars [ "project" ]
if ! ok {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "project-uuid missing" ,
2020-08-05 14:13:11 +01:00
"" , http . StatusBadRequest )
2020-02-07 17:24:58 +00:00
return
}
2023-09-05 20:28:39 +01:00
project , err := server . getProjectByAnyID ( ctx , projectUUIDString )
2021-10-07 11:42:25 +01:00
if errors . Is ( err , sql . ErrNoRows ) {
sendJSONError ( w , "project with specified uuid does not exist" ,
"" , http . StatusNotFound )
return
}
2020-02-07 17:24:58 +00:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to get project" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-02-07 17:24:58 +00:00
return
}
var output struct {
Usage struct {
Amount memory . Size ` json:"amount" `
Bytes int64 ` json:"bytes" `
} ` json:"usage" `
2020-05-12 14:01:15 +01:00
Bandwidth struct {
Amount memory . Size ` json:"amount" `
Bytes int64 ` json:"bytes" `
} ` json:"bandwidth" `
2020-02-07 17:24:58 +00:00
Rate struct {
RPS int ` json:"rps" `
} ` json:"rate" `
2023-09-27 08:27:28 +01:00
Burst int ` json:"burst" `
2021-12-08 13:18:40 +00:00
Buckets int ` json:"maxBuckets" `
Segments int64 ` json:"maxSegments" `
2020-02-07 17:24:58 +00:00
}
2021-03-22 20:26:59 +00:00
if project . StorageLimit != nil {
output . Usage . Amount = * project . StorageLimit
output . Usage . Bytes = project . StorageLimit . Int64 ( )
2020-09-06 00:02:12 +01:00
}
2021-03-22 20:26:59 +00:00
if project . BandwidthLimit != nil {
output . Bandwidth . Amount = * project . BandwidthLimit
output . Bandwidth . Bytes = project . BandwidthLimit . Int64 ( )
2020-09-06 00:02:12 +01:00
}
if project . MaxBuckets != nil {
output . Buckets = * project . MaxBuckets
}
2020-02-07 17:24:58 +00:00
if project . RateLimit != nil {
output . Rate . RPS = * project . RateLimit
}
2023-09-27 08:27:28 +01:00
if project . BurstLimit != nil {
output . Burst = * project . BurstLimit
}
2021-12-08 13:18:40 +00:00
if project . SegmentLimit != nil {
output . Segments = * project . SegmentLimit
}
2020-02-07 17:24:58 +00:00
data , err := json . Marshal ( output )
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "json encoding failed" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-02-07 17:24:58 +00:00
return
}
2021-10-01 12:50:21 +01:00
sendJSONData ( w , http . StatusOK , data )
2020-02-07 17:24:58 +00:00
}
func ( server * Server ) putProjectLimit ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
vars := mux . Vars ( r )
projectUUIDString , ok := vars [ "project" ]
if ! ok {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "project-uuid missing" ,
2020-08-05 14:13:11 +01:00
"" , http . StatusBadRequest )
2020-02-07 17:24:58 +00:00
return
}
var arguments struct {
2020-05-12 14:01:15 +01:00
Usage * memory . Size ` schema:"usage" `
Bandwidth * memory . Size ` schema:"bandwidth" `
Rate * int ` schema:"rate" `
2021-08-23 22:47:58 +01:00
Burst * int ` schema:"burst" `
2020-07-15 15:14:56 +01:00
Buckets * int ` schema:"buckets" `
2021-12-08 13:18:40 +00:00
Segments * int64 ` schema:"segments" `
2020-02-07 17:24:58 +00:00
}
if err := r . ParseForm ( ) ; err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "invalid form" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusBadRequest )
2020-02-07 17:24:58 +00:00
return
}
decoder := schema . NewDecoder ( )
2023-09-05 20:28:39 +01:00
err := decoder . Decode ( & arguments , r . Form )
2020-02-07 17:24:58 +00:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "invalid arguments" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusBadRequest )
2020-02-07 17:24:58 +00:00
return
}
2021-10-07 11:42:25 +01:00
// check if the project exists.
2023-09-05 20:28:39 +01:00
project , err := server . getProjectByAnyID ( ctx , projectUUIDString )
2021-10-07 11:42:25 +01:00
if errors . Is ( err , sql . ErrNoRows ) {
sendJSONError ( w , "project with specified uuid does not exist" ,
"" , http . StatusNotFound )
return
}
if err != nil {
sendJSONError ( w , "failed to get project" ,
err . Error ( ) , http . StatusInternalServerError )
return
}
2020-02-07 17:24:58 +00:00
if arguments . Usage != nil {
if * arguments . Usage < 0 {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "negative usage" ,
2020-08-05 14:13:11 +01:00
fmt . Sprintf ( "%v" , arguments . Usage ) , http . StatusBadRequest )
2020-02-07 17:24:58 +00:00
return
}
2023-09-05 20:28:39 +01:00
err = server . db . ProjectAccounting ( ) . UpdateProjectUsageLimit ( ctx , project . ID , * arguments . Usage )
2020-02-07 17:24:58 +00:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to update usage" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-02-07 17:24:58 +00:00
return
}
}
2020-05-12 14:01:15 +01:00
if arguments . Bandwidth != nil {
if * arguments . Bandwidth < 0 {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "negative bandwidth" ,
2020-08-05 14:13:11 +01:00
fmt . Sprintf ( "%v" , arguments . Usage ) , http . StatusBadRequest )
2020-05-12 14:01:15 +01:00
return
}
2023-09-05 20:28:39 +01:00
err = server . db . ProjectAccounting ( ) . UpdateProjectBandwidthLimit ( ctx , project . ID , * arguments . Bandwidth )
2020-05-12 14:01:15 +01:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to update bandwidth" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-05-12 14:01:15 +01:00
return
}
}
2020-02-07 17:24:58 +00:00
if arguments . Rate != nil {
if * arguments . Rate < 0 {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "negative rate" ,
2020-08-05 14:13:11 +01:00
fmt . Sprintf ( "%v" , arguments . Rate ) , http . StatusBadRequest )
2020-02-07 17:24:58 +00:00
return
}
2023-09-05 20:28:39 +01:00
err = server . db . Console ( ) . Projects ( ) . UpdateRateLimit ( ctx , project . ID , * arguments . Rate )
2020-02-07 17:24:58 +00:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to update rate" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-02-07 17:24:58 +00:00
return
}
}
2020-07-15 15:14:56 +01:00
2021-08-23 22:47:58 +01:00
if arguments . Burst != nil {
if * arguments . Burst < 0 {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "negative burst rate" ,
2021-08-23 22:47:58 +01:00
fmt . Sprintf ( "%v" , arguments . Burst ) , http . StatusBadRequest )
return
}
2023-09-05 20:28:39 +01:00
err = server . db . Console ( ) . Projects ( ) . UpdateBurstLimit ( ctx , project . ID , * arguments . Burst )
2021-08-23 22:47:58 +01:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to update burst" ,
2021-08-23 22:47:58 +01:00
err . Error ( ) , http . StatusInternalServerError )
return
}
}
2020-07-15 15:14:56 +01:00
if arguments . Buckets != nil {
if * arguments . Buckets < 0 {
2021-12-08 13:18:40 +00:00
sendJSONError ( w , "negative bucket count" ,
2020-08-05 14:13:11 +01:00
fmt . Sprintf ( "t: %v" , arguments . Buckets ) , http . StatusBadRequest )
2020-07-15 15:14:56 +01:00
return
}
2023-09-05 20:28:39 +01:00
err = server . db . Console ( ) . Projects ( ) . UpdateBucketLimit ( ctx , project . ID , * arguments . Buckets )
2020-07-15 15:14:56 +01:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to update bucket limit" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-07-15 15:14:56 +01:00
return
}
}
2021-12-08 13:18:40 +00:00
if arguments . Segments != nil {
if * arguments . Segments < 0 {
sendJSONError ( w , "negative segments count" ,
fmt . Sprintf ( "t: %v" , arguments . Buckets ) , http . StatusBadRequest )
return
}
2023-09-05 20:28:39 +01:00
err = server . db . ProjectAccounting ( ) . UpdateProjectSegmentLimit ( ctx , project . ID , * arguments . Segments )
2021-12-08 13:18:40 +00:00
if err != nil {
sendJSONError ( w , "failed to update segments limit" ,
err . Error ( ) , http . StatusInternalServerError )
return
}
}
2020-02-07 17:24:58 +00:00
}
2020-05-11 17:05:36 +01:00
func ( server * Server ) addProject ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
2022-10-11 12:39:08 +01:00
body , err := io . ReadAll ( r . Body )
2020-05-11 17:05:36 +01:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to read body" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-05-11 17:05:36 +01:00
return
}
var input struct {
OwnerID uuid . UUID ` json:"ownerId" `
ProjectName string ` json:"projectName" `
}
var output struct {
ProjectID uuid . UUID ` json:"projectId" `
}
err = json . Unmarshal ( body , & input )
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to unmarshal request" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusBadRequest )
2020-05-11 17:05:36 +01:00
return
}
if input . OwnerID . IsZero ( ) {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "OwnerID is not set" ,
2020-08-05 14:13:11 +01:00
"" , http . StatusBadRequest )
2020-05-11 17:05:36 +01:00
return
}
if input . ProjectName == "" {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "ProjectName is not set" ,
2020-08-05 14:13:11 +01:00
"" , http . StatusBadRequest )
2020-05-11 17:05:36 +01:00
return
}
project , err := server . db . Console ( ) . Projects ( ) . Insert ( ctx , & console . Project {
Name : input . ProjectName ,
OwnerID : input . OwnerID ,
} )
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to insert project" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-05-11 17:05:36 +01:00
return
}
_ , err = server . db . Console ( ) . ProjectMembers ( ) . Insert ( ctx , project . OwnerID , project . ID )
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to insert project member" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-05-11 17:05:36 +01:00
return
}
output . ProjectID = project . ID
data , err := json . Marshal ( output )
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "json encoding failed" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-05-11 17:05:36 +01:00
return
}
2021-10-01 12:50:21 +01:00
sendJSONData ( w , http . StatusOK , data )
2020-05-11 17:05:36 +01:00
}
2020-05-18 18:36:09 +01:00
2020-07-06 21:28:49 +01:00
func ( server * Server ) renameProject ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
vars := mux . Vars ( r )
projectUUIDString , ok := vars [ "project" ]
if ! ok {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "project-uuid missing" ,
2020-08-05 14:13:11 +01:00
"" , http . StatusBadRequest )
2020-07-06 21:28:49 +01:00
return
}
2023-09-05 20:28:39 +01:00
project , err := server . getProjectByAnyID ( ctx , projectUUIDString )
2020-07-06 21:28:49 +01:00
if errors . Is ( err , sql . ErrNoRows ) {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "project with specified uuid does not exist" ,
2021-10-07 11:42:25 +01:00
"" , http . StatusNotFound )
2020-07-06 21:28:49 +01:00
return
}
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "error getting project" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-07-06 21:28:49 +01:00
return
}
2022-10-11 12:39:08 +01:00
body , err := io . ReadAll ( r . Body )
2020-07-06 21:28:49 +01:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "ailed to read body" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-07-06 21:28:49 +01:00
return
}
var input struct {
ProjectName string ` json:"projectName" `
Description string ` json:"description" `
}
err = json . Unmarshal ( body , & input )
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "failed to unmarshal request" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusBadRequest )
2020-07-06 21:28:49 +01:00
return
}
if input . ProjectName == "" {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "ProjectName is not set" ,
2020-08-05 14:13:11 +01:00
"" , http . StatusBadRequest )
2020-07-06 21:28:49 +01:00
return
}
project . Name = input . ProjectName
2021-08-31 17:41:06 +01:00
if input . Description != "" {
project . Description = input . Description
}
2020-07-06 21:28:49 +01:00
err = server . db . Console ( ) . Projects ( ) . Update ( ctx , project )
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "error renaming project" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-07-06 21:28:49 +01:00
return
}
}
2023-06-13 16:58:24 +01:00
func ( server * Server ) updateProjectsUserAgent ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
vars := mux . Vars ( r )
projectUUIDString , ok := vars [ "project" ]
if ! ok {
sendJSONError ( w , "project-uuid missing" ,
"" , http . StatusBadRequest )
return
}
2023-09-05 20:28:39 +01:00
project , err := server . getProjectByAnyID ( ctx , projectUUIDString )
2023-06-13 16:58:24 +01:00
if errors . Is ( err , sql . ErrNoRows ) {
sendJSONError ( w , "project with specified uuid does not exist" ,
"" , http . StatusNotFound )
return
}
if err != nil {
sendJSONError ( w , "error getting project" ,
err . Error ( ) , http . StatusInternalServerError )
return
}
creationDatePlusMonth := project . CreatedAt . AddDate ( 0 , 1 , 0 )
if time . Now ( ) . After ( creationDatePlusMonth ) {
sendJSONError ( w , "this project was created more than a month ago" ,
"we should update user agent only for recently created projects" , http . StatusBadRequest )
return
}
body , err := io . ReadAll ( r . Body )
if err != nil {
sendJSONError ( w , "failed to read body" ,
err . Error ( ) , http . StatusInternalServerError )
return
}
var input struct {
UserAgent string ` json:"userAgent" `
}
err = json . Unmarshal ( body , & input )
if err != nil {
sendJSONError ( w , "failed to unmarshal request" ,
err . Error ( ) , http . StatusBadRequest )
return
}
if input . UserAgent == "" {
sendJSONError ( w , "UserAgent was not provided" ,
"" , http . StatusBadRequest )
return
}
newUserAgent := [ ] byte ( input . UserAgent )
if bytes . Equal ( project . UserAgent , newUserAgent ) {
sendJSONError ( w , "new UserAgent is equal to existing projects UserAgent" ,
"" , http . StatusBadRequest )
return
}
err = server . _updateProjectsUserAgent ( ctx , project . ID , newUserAgent )
if err != nil {
sendJSONError ( w , "failed to update projects user agent" ,
err . Error ( ) , http . StatusInternalServerError )
}
}
2020-05-18 18:36:09 +01:00
func ( server * Server ) deleteProject ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
vars := mux . Vars ( r )
projectUUIDString , ok := vars [ "project" ]
if ! ok {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "project-uuid missing" ,
2020-08-05 14:13:11 +01:00
"" , http . StatusBadRequest )
2020-05-18 18:36:09 +01:00
return
}
2023-09-05 20:28:39 +01:00
project , err := server . getProjectByAnyID ( ctx , projectUUIDString )
2020-05-18 18:36:09 +01:00
if err != nil {
2023-09-05 20:28:39 +01:00
sendJSONError ( w , "error getting project" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusBadRequest )
2020-05-18 18:36:09 +01:00
return
}
if err := r . ParseForm ( ) ; err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "invalid form" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusBadRequest )
2020-05-18 18:36:09 +01:00
return
}
2023-04-19 13:25:12 +01:00
options := buckets . ListOptions { Limit : 1 , Direction : buckets . DirectionForward }
2023-09-05 20:28:39 +01:00
buckets , err := server . buckets . ListBuckets ( ctx , project . ID , options , macaroon . AllowedBuckets { All : true } )
2020-05-18 18:36:09 +01:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "unable to list buckets" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-05-18 18:36:09 +01:00
return
}
if len ( buckets . Items ) > 0 {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "buckets still exist" ,
2020-08-05 14:13:11 +01:00
fmt . Sprintf ( "%v" , bucketNames ( buckets . Items ) ) , http . StatusConflict )
2020-05-18 18:36:09 +01:00
return
}
2023-09-05 20:28:39 +01:00
keys , err := server . db . Console ( ) . APIKeys ( ) . GetPagedByProjectID ( ctx , project . ID , console . APIKeyCursor { Limit : 1 , Page : 1 } )
2020-05-18 18:36:09 +01:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "unable to list api-keys" ,
2020-08-05 14:13:11 +01:00
err . Error ( ) , http . StatusInternalServerError )
2020-05-18 18:36:09 +01:00
return
}
if keys . TotalCount > 0 {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "api-keys still exist" ,
2020-08-05 14:13:11 +01:00
fmt . Sprintf ( "count %d" , keys . TotalCount ) , http . StatusConflict )
2020-05-18 18:36:09 +01:00
return
}
2020-08-13 13:40:05 +01:00
// if usage exist, return error to client and exit
2023-09-05 20:28:39 +01:00
if server . checkUsage ( ctx , w , project . ID ) {
2020-08-13 13:40:05 +01:00
return
}
2023-09-05 20:28:39 +01:00
err = server . db . Console ( ) . Projects ( ) . Delete ( ctx , project . ID )
2020-08-13 13:40:05 +01:00
if err != nil {
2021-10-01 12:50:21 +01:00
sendJSONError ( w , "unable to delete project" ,
2020-08-13 13:40:05 +01:00
err . Error ( ) , http . StatusInternalServerError )
return
}
}
2023-06-13 16:58:24 +01:00
func ( server * Server ) _updateProjectsUserAgent ( ctx context . Context , projectID uuid . UUID , newUserAgent [ ] byte ) ( err error ) {
err = server . db . Console ( ) . Projects ( ) . UpdateUserAgent ( ctx , projectID , newUserAgent )
if err != nil {
return err
}
listOptions := buckets . ListOptions {
Direction : buckets . DirectionForward ,
}
allowedBuckets := macaroon . AllowedBuckets {
All : true ,
}
projectBuckets , err := server . db . Buckets ( ) . ListBuckets ( ctx , projectID , listOptions , allowedBuckets )
if err != nil {
return err
}
var errList errs . Group
for _ , bucket := range projectBuckets . Items {
err = server . db . Buckets ( ) . UpdateUserAgent ( ctx , projectID , bucket . Name , newUserAgent )
if err != nil {
errList . Append ( err )
}
err = server . db . Attribution ( ) . UpdateUserAgent ( ctx , projectID , bucket . Name , newUserAgent )
if err != nil {
errList . Append ( err )
}
}
if errList . Err ( ) != nil {
return errList . Err ( )
}
return nil
}
2022-02-04 17:31:24 +00:00
func ( server * Server ) checkInvoicing ( ctx context . Context , w http . ResponseWriter , projectID uuid . UUID ) ( openInvoices bool ) {
2020-09-03 22:12:26 +01:00
year , month , _ := server . nowFn ( ) . UTC ( ) . Date ( )
2020-07-06 21:15:55 +01:00
firstOfMonth := time . Date ( year , month , 1 , 0 , 0 , 0 , 0 , time . UTC )
2022-02-04 17:31:24 +00:00
// Check if an invoice project record exists already
err := server . db . StripeCoinPayments ( ) . ProjectRecords ( ) . Check ( ctx , projectID , firstOfMonth . AddDate ( 0 , - 1 , 0 ) , firstOfMonth )
2023-04-06 12:41:14 +01:00
if errors . Is ( err , stripe . ErrProjectRecordExists ) {
2022-02-04 17:31:24 +00:00
record , err := server . db . StripeCoinPayments ( ) . ProjectRecords ( ) . Get ( ctx , projectID , firstOfMonth . AddDate ( 0 , - 1 , 0 ) , firstOfMonth )
if err != nil {
sendJSONError ( w , "unable to get project records" , err . Error ( ) , http . StatusInternalServerError )
return true
}
// state = 0 means unapplied and not invoiced yet.
if record . State == 0 {
sendJSONError ( w , "unapplied project invoice record exist" , "" , http . StatusConflict )
return true
}
// Record has been applied, so project can be deleted.
return false
2020-07-06 21:15:55 +01:00
}
2022-02-04 17:31:24 +00:00
if err != nil {
sendJSONError ( w , "unable to get project records" , err . Error ( ) , http . StatusInternalServerError )
2020-08-13 13:40:05 +01:00
return true
2020-07-06 21:15:55 +01:00
}
2022-02-04 17:31:24 +00:00
return false
}
func ( server * Server ) checkUsage ( ctx context . Context , w http . ResponseWriter , projectID uuid . UUID ) ( hasUsage bool ) {
year , month , _ := server . nowFn ( ) . UTC ( ) . Date ( )
firstOfMonth := time . Date ( year , month , 1 , 0 , 0 , 0 , 0 , time . UTC )
prj , err := server . db . Console ( ) . Projects ( ) . Get ( ctx , projectID )
2020-07-06 21:15:55 +01:00
if err != nil {
2022-02-04 17:31:24 +00:00
sendJSONError ( w , "unable to get project details" ,
err . Error ( ) , http . StatusInternalServerError )
2023-08-09 08:55:24 +01:00
return false
2020-07-06 21:15:55 +01:00
}
2022-02-04 17:31:24 +00:00
// If user is paid tier, check the usage limit, otherwise it is ok to delete it.
paid , err := server . db . Console ( ) . Users ( ) . GetUserPaidTier ( ctx , prj . OwnerID )
if err != nil {
sendJSONError ( w , "unable to project owner tier" ,
err . Error ( ) , http . StatusInternalServerError )
2023-08-09 08:55:24 +01:00
return false
2022-02-04 17:31:24 +00:00
}
if paid {
// check current month usage and do not allow deletion if usage exists
currentUsage , err := server . db . ProjectAccounting ( ) . GetProjectTotal ( ctx , projectID , firstOfMonth , server . nowFn ( ) )
if err != nil {
sendJSONError ( w , "unable to list project usage" , err . Error ( ) , http . StatusInternalServerError )
return true
2021-05-14 16:05:42 +01:00
}
2022-02-04 17:31:24 +00:00
if currentUsage . Storage > 0 || currentUsage . Egress > 0 || currentUsage . SegmentCount > 0 {
sendJSONError ( w , "usage for current month exists" , "" , http . StatusConflict )
return true
}
2022-05-27 22:35:13 +01:00
// check usage for last month, if exists, ensure we have an invoice item created.
2022-02-04 17:31:24 +00:00
lastMonthUsage , err := server . db . ProjectAccounting ( ) . GetProjectTotal ( ctx , projectID , firstOfMonth . AddDate ( 0 , - 1 , 0 ) , firstOfMonth . AddDate ( 0 , 0 , - 1 ) )
2021-05-14 16:05:42 +01:00
if err != nil {
2022-02-04 17:31:24 +00:00
sendJSONError ( w , "error getting project totals" ,
"" , http . StatusInternalServerError )
return true
}
if lastMonthUsage . Storage > 0 || lastMonthUsage . Egress > 0 || lastMonthUsage . SegmentCount > 0 {
2022-05-27 22:35:13 +01:00
err = server . db . StripeCoinPayments ( ) . ProjectRecords ( ) . Check ( ctx , projectID , firstOfMonth . AddDate ( 0 , - 1 , 0 ) , firstOfMonth )
2023-04-06 12:41:14 +01:00
if ! errors . Is ( err , stripe . ErrProjectRecordExists ) {
2022-05-27 22:35:13 +01:00
sendJSONError ( w , "usage for last month exist, but is not billed yet" , "" , http . StatusConflict )
return true
}
2020-07-06 21:15:55 +01:00
}
}
2022-02-04 17:31:24 +00:00
// If we have open invoice items, do not delete the project yet and wait for invoice completion.
return server . checkInvoicing ( ctx , w , projectID )
2020-05-18 18:36:09 +01:00
}
2020-06-05 11:58:00 +01:00
2023-07-25 16:26:35 +01:00
func ( server * Server ) createGeofenceForProject ( w http . ResponseWriter , r * http . Request ) {
placement , err := parsePlacementConstraint ( r . URL . Query ( ) . Get ( "region" ) )
if err != nil {
sendJSONError ( w , err . Error ( ) , "available: EU, EEA, US, DE, NR" , http . StatusBadRequest )
return
}
server . setGeofenceForProject ( w , r , placement )
}
func ( server * Server ) deleteGeofenceForProject ( w http . ResponseWriter , r * http . Request ) {
server . setGeofenceForProject ( w , r , storj . EveryCountry )
}
func ( server * Server ) setGeofenceForProject ( w http . ResponseWriter , r * http . Request , placement storj . PlacementConstraint ) {
ctx := r . Context ( )
vars := mux . Vars ( r )
projectUUIDString , ok := vars [ "project" ]
if ! ok {
sendJSONError ( w , "project-uuid missing" ,
"" , http . StatusBadRequest )
return
}
2023-09-05 20:28:39 +01:00
project , err := server . getProjectByAnyID ( ctx , projectUUIDString )
2023-07-25 16:26:35 +01:00
if errors . Is ( err , sql . ErrNoRows ) {
sendJSONError ( w , "project with specified uuid does not exist" ,
"" , http . StatusNotFound )
return
}
2023-09-05 20:28:39 +01:00
if err != nil {
sendJSONError ( w , "error getting project" ,
"" , http . StatusInternalServerError )
return
}
2023-07-25 16:26:35 +01:00
project . DefaultPlacement = placement
err = server . db . Console ( ) . Projects ( ) . Update ( ctx , project )
if err != nil {
sendJSONError ( w , "unable to set geofence for project" ,
err . Error ( ) , http . StatusInternalServerError )
return
}
}
2023-04-13 13:04:07 +01:00
func bucketNames ( buckets [ ] buckets . Bucket ) [ ] string {
2020-06-05 11:58:00 +01:00
var xs [ ] string
for _ , b := range buckets {
xs = append ( xs , b . Name )
}
return xs
}
2023-09-05 20:28:39 +01:00
// getProjectByAnyID takes a string version of a project public or private ID. If a valid public or private UUID, the associated project will be returned.
func ( server * Server ) getProjectByAnyID ( ctx context . Context , projectUUIDString string ) ( p * console . Project , err error ) {
projectID , err := uuidFromString ( projectUUIDString )
if err != nil {
return nil , Error . Wrap ( err )
}
p , err = server . db . Console ( ) . Projects ( ) . GetByPublicID ( ctx , projectID )
switch {
case errors . Is ( err , sql . ErrNoRows ) :
// if failed to get by public ID, try using provided ID as a private ID
p , err = server . db . Console ( ) . Projects ( ) . Get ( ctx , projectID )
return p , Error . Wrap ( err )
case err != nil :
return nil , Error . Wrap ( err )
default :
return p , nil
}
}