bucket crud

This commit is contained in:
schreifuchs 2024-11-17 22:08:06 +01:00
parent abd805b9cd
commit 8cf61c68bf
10 changed files with 143 additions and 80 deletions

View File

@ -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

View File

@ -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) {
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
}

View File

@ -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
}

View File

@ -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
}

1
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -10,7 +10,7 @@ type Bucket struct {
type Object struct {
gorm.Model
Name string `gorm:"uniqueIndex"`
Key string `gorm:"uniqueIndex"`
Size uint
BucketId uint
}

View File

@ -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:

14
utils/pagination.go Normal file
View File

@ -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]
}

37
utils/pagination_test.go Normal file
View File

@ -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)
}
})
}
}