diff --git a/api/server.gen.go b/api/server.gen.go index 4ddaebc..4288203 100644 --- a/api/server.gen.go +++ b/api/server.gen.go @@ -477,14 +477,6 @@ func (response GetBuckets200JSONResponse) VisitGetBucketsResponse(w http.Respons return json.NewEncoder(w).Encode(response) } -type GetBuckets500Response struct { -} - -func (response GetBuckets500Response) VisitGetBucketsResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - type PostBucketsRequestObject struct { Body *PostBucketsJSONRequestBody } @@ -541,14 +533,6 @@ func (response DeleteBucketsBucketName404Response) VisitDeleteBucketsBucketNameR return nil } -type DeleteBucketsBucketName500Response struct { -} - -func (response DeleteBucketsBucketName500Response) VisitDeleteBucketsBucketNameResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - type GetBucketsBucketNameObjectsRequestObject struct { BucketName string `json:"bucketName"` Params GetBucketsBucketNameObjectsParams @@ -561,12 +545,6 @@ type GetBucketsBucketNameObjectsResponseObject interface { type GetBucketsBucketNameObjects200JSONResponse struct { Items *[]Object `json:"items,omitempty"` - // Limit Maximum number of items returned in the response - Limit *int `json:"limit,omitempty"` - - // Offset Number of items skipped before starting the response - Offset *int `json:"offset,omitempty"` - // Total Total number of objects available in the bucket Total *int `json:"total,omitempty"` } @@ -611,14 +589,6 @@ func (response DeleteBucketsBucketNameObjectsObjectKey404Response) VisitDeleteBu return nil } -type DeleteBucketsBucketNameObjectsObjectKey500Response struct { -} - -func (response DeleteBucketsBucketNameObjectsObjectKey500Response) VisitDeleteBucketsBucketNameObjectsObjectKeyResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - type GetBucketsBucketNameObjectsObjectKeyRequestObject struct { BucketName string `json:"bucketName"` ObjectKey string `json:"objectKey"` @@ -681,14 +651,6 @@ func (response PutBucketsBucketNameObjectsObjectKey400Response) VisitPutBucketsB return nil } -type PutBucketsBucketNameObjectsObjectKey500Response struct { -} - -func (response PutBucketsBucketNameObjectsObjectKey500Response) VisitPutBucketsBucketNameObjectsObjectKeyResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - // StrictServerInterface represents all server handlers. type StrictServerInterface interface { // List all buckets @@ -939,26 +901,25 @@ func (sh *strictHandler) PutBucketsBucketNameObjectsObjectKey(w http.ResponseWri // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/controller/controller.go b/controller/controller.go index b514d92..55426ec 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -6,6 +6,7 @@ import ( "git.schreifuchs.ch/schreifuchs/warehouse/api" "git.schreifuchs.ch/schreifuchs/warehouse/model" + "git.schreifuchs.ch/schreifuchs/warehouse/utils" "gorm.io/gorm" ) @@ -13,7 +14,7 @@ func (c *Controller) GetBuckets(ctx context.Context, request api.GetBucketsReque buckets, err := c.db.FindApiBuckets(*request.Params.Limit, *request.Params.Offset) if err != nil { - return api.GetBuckets500Response{}, nil + return nil, err } t := len(buckets) @@ -38,17 +39,49 @@ func (c *Controller) DeleteBucketsBucketName(ctx context.Context, request api.De if err := c.db.DeleteBucketByName(request.BucketName); errors.Is(err, gorm.ErrRecordNotFound) { return api.DeleteBucketsBucketName404Response{}, nil } else if err != nil { - return api.DeleteBucketsBucketName500Response{}, err + return nil, err } return api.DeleteBucketsBucketName204Response{}, nil } -func (c *Controller) GetBucketsBucketNameObjects(ctx context.Context, request api.GetBucketsBucketNameObjectsRequestObject) (api.GetBucketsBucketNameObjectsResponseObject, error) { - return api.GetBucketsBucketNameObjects404Response{}, nil +func (c *Controller) GetBucketsBucketNameObjects(ctx context.Context, request api.GetBucketsBucketNameObjectsRequestObject) (response api.GetBucketsBucketNameObjectsResponseObject, err error) { + + if bucket, err := c.db.FindBucketByName(request.BucketName, *request.Params.Limit, *request.Params.Offset); err == nil { + objects := make([]api.Object, 0, len(bucket.Objects)) + + for _, o := range utils.Paginate(bucket.Objects, *request.Params.Offset, *request.Params.Limit) { + s := int(o.Size) + + objects = append(objects, api.Object{ + Key: &o.Key, + LastModified: &o.UpdatedAt, + Size: &s, + }) + + } + + total := len(bucket.Objects) + + return api.GetBucketsBucketNameObjects200JSONResponse{ + Items: &objects, + Total: &total, + }, nil + } else if errors.Is(err, gorm.ErrRecordNotFound) { + + return api.GetBucketsBucketNameObjects404Response{}, nil + } + + return } func (c *Controller) DeleteBucketsBucketNameObjectsObjectKey(ctx context.Context, request api.DeleteBucketsBucketNameObjectsObjectKeyRequestObject) (api.DeleteBucketsBucketNameObjectsObjectKeyResponseObject, error) { - return api.DeleteBucketsBucketNameObjectsObjectKey500Response{}, nil + err := c.db.DeleteObjectByKey(request.BucketName, request.ObjectKey) + + if errors.Is(err, gorm.ErrRecordNotFound) { + return api.DeleteBucketsBucketNameObjectsObjectKey404Response{}, nil + } + + return api.DeleteBucketsBucketNameObjectsObjectKey204Response{}, err } func (c *Controller) GetBucketsBucketNameObjectsObjectKey(ctx context.Context, request api.GetBucketsBucketNameObjectsObjectKeyRequestObject) (api.GetBucketsBucketNameObjectsObjectKeyResponseObject, error) { @@ -56,5 +89,5 @@ func (c *Controller) GetBucketsBucketNameObjectsObjectKey(ctx context.Context, r } func (c *Controller) PutBucketsBucketNameObjectsObjectKey(ctx context.Context, request api.PutBucketsBucketNameObjectsObjectKeyRequestObject) (api.PutBucketsBucketNameObjectsObjectKeyResponseObject, error) { - return api.PutBucketsBucketNameObjectsObjectKey500Response{}, nil + return api.PutBucketsBucketNameObjectsObjectKey400Response{}, nil } diff --git a/controller/resource.go b/controller/resource.go index 1808f81..85add0e 100644 --- a/controller/resource.go +++ b/controller/resource.go @@ -19,6 +19,9 @@ func New(db database) *Controller { type database interface { InsertBucket(bucket *model.Bucket) error FindApiBuckets(limit, offset int) (buckets []api.Bucket, err error) + FindBucketByName(name string, limit, offset int) (buckets model.Bucket, err error) FindBucketIdByName(name string) (id uint, err error) DeleteBucketByName(name string) error + FindObjectByKey(bucketName, key string) (object *model.Object, err error) + DeleteObjectByKey(bucketName, key string) error } diff --git a/database/controller.go b/database/controller.go index 86e7de1..2bc2aa2 100644 --- a/database/controller.go +++ b/database/controller.go @@ -10,10 +10,13 @@ func (db *DB) InsertBucket(bucket *model.Bucket) error { } func (db *DB) FindApiBuckets(limit, offset int) (buckets []api.Bucket, err error) { - buckets = make([]api.Bucket, 0) err = db.conn.Model(&model.Bucket{}).Limit(limit).Offset(offset).Find(&buckets).Error return } +func (db *DB) FindBucketByName(name string) (bucket model.Bucket, err error) { + err = db.conn.First(&bucket).Where("name = ?", name).Error + return +} func (db *DB) FindBucketIdByName(name string) (id uint, err error) { b := &model.Bucket{} @@ -28,3 +31,26 @@ func (db *DB) DeleteBucket(id uint) error { func (db *DB) DeleteBucketByName(name string) error { return db.conn.Delete(&model.Bucket{}).Where("name = ?", name).Error } + +func (db *DB) FindObjectByKey(bucketName, key string) (object *model.Object, err error) { + bid, err := db.FindBucketIdByName(bucketName) + if err != nil { + return + } + + err = db.conn.Find(object).Where("key = ? AND bucket_id = ?", key, bid).Error + + return + +} + +func (db *DB) DeleteObjectByKey(bucketName, key string) (err error) { + bid, err := db.FindBucketIdByName(bucketName) + if err != nil { + return + } + + err = db.conn.Delete(&model.Object{}).Where("key = ? AND bucket_id = ?", key, bid).Error + + return +} diff --git a/go.mod b/go.mod index 90bfe56..4fda683 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( ) require ( + git.schreifuchs.ch/schreifuchs/logger v0.1.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect diff --git a/go.sum b/go.sum index 2e46334..f05f392 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +git.schreifuchs.ch/schreifuchs/logger v0.1.0 h1:ChBvtZpVNkYxoQ52jbCyhyLG5ZS+s0863OovBaWQgS4= +git.schreifuchs.ch/schreifuchs/logger v0.1.0/go.mod h1:VRX/HF+FeI/xTk9Guoq+vBzH2WK20zASZEUhMSL3Tws= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= diff --git a/model/resource.go b/model/resource.go index afcd52d..f09336a 100644 --- a/model/resource.go +++ b/model/resource.go @@ -10,7 +10,7 @@ type Bucket struct { type Object struct { gorm.Model - Name string `gorm:"uniqueIndex"` + Key string `gorm:"uniqueIndex"` Size uint BucketId uint } diff --git a/openapi/openapi.yml b/openapi/openapi.yml index 57052e9..7b34ded 100644 --- a/openapi/openapi.yml +++ b/openapi/openapi.yml @@ -44,8 +44,6 @@ paths: type: array items: $ref: '#/components/schemas/Bucket' - '500': - description: Server error post: summary: Create a new bucket @@ -85,8 +83,6 @@ paths: description: Bucket deleted successfully '404': description: Bucket not found - '500': - description: Server error /buckets/{bucketName}/objects: get: @@ -122,12 +118,6 @@ paths: total: type: integer description: Total number of objects available in the bucket - limit: - type: integer - description: Maximum number of items returned in the response - offset: - type: integer - description: Number of items skipped before starting the response items: type: array items: @@ -191,8 +181,6 @@ paths: description: Object uploaded successfully '400': description: Invalid object data - '500': - description: Server error delete: summary: Delete an object @@ -215,8 +203,6 @@ paths: description: Object deleted successfully '404': description: Bucket or object not found - '500': - description: Server error components: schemas: diff --git a/utils/pagination.go b/utils/pagination.go new file mode 100644 index 0000000..7847bf7 --- /dev/null +++ b/utils/pagination.go @@ -0,0 +1,14 @@ +package utils + +// Paginate returns a slicle with offset & limit +func Paginate[T any](items []T, offset, limit int) []T { + if len(items) <= offset { + return []T{} + } + + if limit > len(items)-offset { + limit = len(items) - offset + } + + return items[offset : offset+limit] +} diff --git a/utils/pagination_test.go b/utils/pagination_test.go new file mode 100644 index 0000000..8e51476 --- /dev/null +++ b/utils/pagination_test.go @@ -0,0 +1,37 @@ +package utils + +import ( + "fmt" + "reflect" + "testing" +) + +func TestPagination(t *testing.T) { + + cases := []struct { + Slice []int + Offset int + Limit int + Expected []int + }{ + { + Slice: []int{1, 2, 3, 4, 5}, + Offset: 1, + Limit: 4, + Expected: []int{2, 3, 4, 5}, + }, + } + for _, tc := range cases { + t.Run( + fmt.Sprintf("%v %d %d %v", tc.Slice, tc.Offset, tc.Limit, tc.Expected), + func(t *testing.T) { + got := Paginate(tc.Slice, tc.Offset, tc.Limit) + + if !reflect.DeepEqual(got, tc.Expected) { + t.Errorf("Expected %v, but got %v", tc.Expected, got) + } + + }) + } + +}