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) 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 { type PostBucketsRequestObject struct {
Body *PostBucketsJSONRequestBody Body *PostBucketsJSONRequestBody
} }
@ -541,14 +533,6 @@ func (response DeleteBucketsBucketName404Response) VisitDeleteBucketsBucketNameR
return nil return nil
} }
type DeleteBucketsBucketName500Response struct {
}
func (response DeleteBucketsBucketName500Response) VisitDeleteBucketsBucketNameResponse(w http.ResponseWriter) error {
w.WriteHeader(500)
return nil
}
type GetBucketsBucketNameObjectsRequestObject struct { type GetBucketsBucketNameObjectsRequestObject struct {
BucketName string `json:"bucketName"` BucketName string `json:"bucketName"`
Params GetBucketsBucketNameObjectsParams Params GetBucketsBucketNameObjectsParams
@ -561,12 +545,6 @@ type GetBucketsBucketNameObjectsResponseObject interface {
type GetBucketsBucketNameObjects200JSONResponse struct { type GetBucketsBucketNameObjects200JSONResponse struct {
Items *[]Object `json:"items,omitempty"` 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 Total number of objects available in the bucket
Total *int `json:"total,omitempty"` Total *int `json:"total,omitempty"`
} }
@ -611,14 +589,6 @@ func (response DeleteBucketsBucketNameObjectsObjectKey404Response) VisitDeleteBu
return nil return nil
} }
type DeleteBucketsBucketNameObjectsObjectKey500Response struct {
}
func (response DeleteBucketsBucketNameObjectsObjectKey500Response) VisitDeleteBucketsBucketNameObjectsObjectKeyResponse(w http.ResponseWriter) error {
w.WriteHeader(500)
return nil
}
type GetBucketsBucketNameObjectsObjectKeyRequestObject struct { type GetBucketsBucketNameObjectsObjectKeyRequestObject struct {
BucketName string `json:"bucketName"` BucketName string `json:"bucketName"`
ObjectKey string `json:"objectKey"` ObjectKey string `json:"objectKey"`
@ -681,14 +651,6 @@ func (response PutBucketsBucketNameObjectsObjectKey400Response) VisitPutBucketsB
return nil return nil
} }
type PutBucketsBucketNameObjectsObjectKey500Response struct {
}
func (response PutBucketsBucketNameObjectsObjectKey500Response) VisitPutBucketsBucketNameObjectsObjectKeyResponse(w http.ResponseWriter) error {
w.WriteHeader(500)
return nil
}
// StrictServerInterface represents all server handlers. // StrictServerInterface represents all server handlers.
type StrictServerInterface interface { type StrictServerInterface interface {
// List all buckets // List all buckets
@ -939,26 +901,25 @@ func (sh *strictHandler) PutBucketsBucketNameObjectsObjectKey(w http.ResponseWri
// Base64 encoded, gzipped, json marshaled Swagger object // Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{ var swaggerSpec = []string{
"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",
} }
// GetSwagger returns the content of the embedded swagger specification file // 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/api"
"git.schreifuchs.ch/schreifuchs/warehouse/model" "git.schreifuchs.ch/schreifuchs/warehouse/model"
"git.schreifuchs.ch/schreifuchs/warehouse/utils"
"gorm.io/gorm" "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) buckets, err := c.db.FindApiBuckets(*request.Params.Limit, *request.Params.Offset)
if err != nil { if err != nil {
return api.GetBuckets500Response{}, nil return nil, err
} }
t := len(buckets) 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) { if err := c.db.DeleteBucketByName(request.BucketName); errors.Is(err, gorm.ErrRecordNotFound) {
return api.DeleteBucketsBucketName404Response{}, nil return api.DeleteBucketsBucketName404Response{}, nil
} else if err != nil { } else if err != nil {
return api.DeleteBucketsBucketName500Response{}, err return nil, err
} }
return api.DeleteBucketsBucketName204Response{}, nil 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) {
return api.GetBucketsBucketNameObjects404Response{}, nil
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) { 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) { 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) { 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 { type database interface {
InsertBucket(bucket *model.Bucket) error InsertBucket(bucket *model.Bucket) error
FindApiBuckets(limit, offset int) (buckets []api.Bucket, err 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) FindBucketIdByName(name string) (id uint, err error)
DeleteBucketByName(name string) 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) { 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 err = db.conn.Model(&model.Bucket{}).Limit(limit).Offset(offset).Find(&buckets).Error
return 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) { func (db *DB) FindBucketIdByName(name string) (id uint, err error) {
b := &model.Bucket{} b := &model.Bucket{}
@ -28,3 +31,26 @@ func (db *DB) DeleteBucket(id uint) error {
func (db *DB) DeleteBucketByName(name string) error { func (db *DB) DeleteBucketByName(name string) error {
return db.conn.Delete(&model.Bucket{}).Where("name = ?", name).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 ( require (
git.schreifuchs.ch/schreifuchs/logger v0.1.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.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/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.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/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 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= 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 { type Object struct {
gorm.Model gorm.Model
Name string `gorm:"uniqueIndex"` Key string `gorm:"uniqueIndex"`
Size uint Size uint
BucketId uint BucketId uint
} }

View File

@ -44,8 +44,6 @@ paths:
type: array type: array
items: items:
$ref: '#/components/schemas/Bucket' $ref: '#/components/schemas/Bucket'
'500':
description: Server error
post: post:
summary: Create a new bucket summary: Create a new bucket
@ -85,8 +83,6 @@ paths:
description: Bucket deleted successfully description: Bucket deleted successfully
'404': '404':
description: Bucket not found description: Bucket not found
'500':
description: Server error
/buckets/{bucketName}/objects: /buckets/{bucketName}/objects:
get: get:
@ -122,12 +118,6 @@ paths:
total: total:
type: integer type: integer
description: Total number of objects available in the bucket 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: items:
type: array type: array
items: items:
@ -191,8 +181,6 @@ paths:
description: Object uploaded successfully description: Object uploaded successfully
'400': '400':
description: Invalid object data description: Invalid object data
'500':
description: Server error
delete: delete:
summary: Delete an object summary: Delete an object
@ -215,8 +203,6 @@ paths:
description: Object deleted successfully description: Object deleted successfully
'404': '404':
description: Bucket or object not found description: Bucket or object not found
'500':
description: Server error
components: components:
schemas: 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)
}
})
}
}