diff --git a/README.md b/README.md index 36b1ece..6bedd15 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,9 @@ simple object storage - Standalone - Head - StorageNode + +## create openapi + +``` go +oapi-codegen -config openapi/server.cfg.yml openapi/openapi.yml +``` diff --git a/api/server.gen.go b/api/server.gen.go index 07368c0..4ddaebc 100644 --- a/api/server.gen.go +++ b/api/server.gen.go @@ -466,12 +466,6 @@ type GetBucketsResponseObject interface { type GetBuckets200JSONResponse struct { Items *[]Bucket `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 buckets available Total *int `json:"total,omitempty"` } @@ -945,26 +939,26 @@ 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/config/env.go b/config/env.go new file mode 100644 index 0000000..263046b --- /dev/null +++ b/config/env.go @@ -0,0 +1,42 @@ +package config + +import ( + "os" + "strconv" + "time" +) + +func (c *Config) ReadEnv() (err error) { + + // General Configuration + + stringVar(&c.BaseUrl, "BASE_URL") + stringVar(&c.SavePath, "SAVE_PATH") + intVar(&c.Port, "PORT") + + return +} + +func stringVar(x *string, key string) { + if v := os.Getenv(key); v != "" { + *x = v + } +} +func float64Var(x *float64, key string) (err error) { + if v := os.Getenv(key); v != "" { + *x, err = strconv.ParseFloat(v, 64) + } + return +} +func intVar(x *int, key string) (err error) { + if v := os.Getenv(key); v != "" { + *x, err = strconv.Atoi(v) + } + return +} +func durationVar(x *time.Duration, key string) (err error) { + if v := os.Getenv(key); v != "" { + *x, err = time.ParseDuration(v) + } + return +} diff --git a/config/env_test.go b/config/env_test.go new file mode 100644 index 0000000..643007e --- /dev/null +++ b/config/env_test.go @@ -0,0 +1,134 @@ +package config + +import ( + "os" + "testing" + "time" +) + +func TestReadEnv(t *testing.T) { + // Set up environment variables + os.Setenv("CONFIG_PATH", "/path/to/config") + os.Setenv("ENV", "dev") + os.Setenv("PORT", "80") + os.Setenv("BASE_URL", "https://example.com") + os.Setenv("ACCESS_ORIGIN", "lou-taylor.ch") + os.Setenv("DB_HOST", "localhost") + os.Setenv("DB_PORT", "5432") + os.Setenv("DB_USER", "testuser") + os.Setenv("DB_PASSWORD", "password") + os.Setenv("DB_NAME", "testdb") + os.Setenv("ADMIN_NAME", "admin") + os.Setenv("ADMIN_PASSWORD", "adminpass") + os.Setenv("JWT_SECRET", "supersecret") + os.Setenv("JWT_VALID_PERIOD", "24h") + os.Setenv("IMAGE_QUALITY", "85.5") + os.Setenv("IMAGE_MAX_WIDTH", "1920") + os.Setenv("IMAGE_SAVE_PATH", "/path/to/images") + + // Initialize an empty Config struct + c := New() + + // Call ReadEnv to populate the config from environment variables + err := c.ReadEnv() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if c.BaseUrl != "https://example.com" { + t.Errorf("expected BaseUrl to be 'https://example.com', got %v", c.BaseUrl) + } + if c.Port != 80 { + t.Errorf("expected Port to be 80, got %d", c.Port) + } + +} + +func TestHelperFunctions(t *testing.T) { + t.Run("stringVar", func(t *testing.T) { + // Set up environment variable + os.Setenv("TEST_STRING", "test_value") + + var result string + stringVar(&result, "TEST_STRING") + + if result != "test_value" { + t.Errorf("expected 'test_value', got '%v'", result) + } + + // Test empty environment variable case + os.Setenv("TEST_STRING_EMPTY", "") + stringVar(&result, "TEST_STRING_EMPTY") + + if result != "test_value" { // result shouldn't change + t.Errorf("expected 'test_value', got '%v'", result) + } + }) + + t.Run("float64Var", func(t *testing.T) { + // Set up valid float environment variable + os.Setenv("TEST_FLOAT", "42.42") + + var result float64 + err := float64Var(&result, "TEST_FLOAT") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != 42.42 { + t.Errorf("expected 42.42, got '%v'", result) + } + + // Test invalid float value + os.Setenv("TEST_FLOAT_INVALID", "invalid_float") + err = float64Var(&result, "TEST_FLOAT_INVALID") + if err == nil { + t.Errorf("expected error for invalid float, but got nil") + } + }) + + t.Run("intVar", func(t *testing.T) { + // Set up valid integer environment variable + os.Setenv("TEST_INT", "123") + + var result int + err := intVar(&result, "TEST_INT") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != 123 { + t.Errorf("expected 123, got '%v'", result) + } + + // Test invalid int value + os.Setenv("TEST_INT_INVALID", "invalid_int") + err = intVar(&result, "TEST_INT_INVALID") + if err == nil { + t.Errorf("expected error for invalid int, but got nil") + } + }) + + t.Run("durationVar", func(t *testing.T) { + // Set up valid duration environment variable + os.Setenv("TEST_DURATION", "2h") + + var result time.Duration + err := durationVar(&result, "TEST_DURATION") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expectedDuration := 2 * time.Hour + if result != expectedDuration { + t.Errorf("expected %v, got '%v'", expectedDuration, result) + } + + // Test invalid duration value + os.Setenv("TEST_DURATION_INVALID", "invalid_duration") + err = durationVar(&result, "TEST_DURATION_INVALID") + if err == nil { + t.Errorf("expected error for invalid duration, but got nil") + } + }) +} diff --git a/config/flag.go b/config/flag.go new file mode 100644 index 0000000..4570f88 --- /dev/null +++ b/config/flag.go @@ -0,0 +1,13 @@ +package config + +import ( + "flag" +) + +func (c *Config) ReadFlags() { + flag.StringVar(&c.ConfigPath, "config", c.ConfigPath, "path of the toml configuration file") + flag.StringVar(&c.BaseUrl, "base-ulr", c.BaseUrl, "base url of the api") + flag.IntVar(&c.Port, "port", c.Port, "port of the api") + + flag.Parse() +} diff --git a/config/resource.go b/config/resource.go new file mode 100644 index 0000000..e5816d0 --- /dev/null +++ b/config/resource.go @@ -0,0 +1,39 @@ +package config + +type Environment string + +const ( + Dev Environment = "dev" + Staging = "staging" + Production = "prod" +) + +// Config holds all configuration values +type Config struct { + ConfigPath string `toml:"-"` + + SavePath string `toml:"savePath"` + + BaseUrl string `toml:"base_url"` + Port int `toml:"port"` +} + +// New returns a pointer to a default configuration with all empty values +func New() *Config { + return &Config{ + ConfigPath: "", + Port: 0, + SavePath: "", + BaseUrl: "", + } +} + +// Default returns a pointer to a default configuration +func Default() *Config { + return &Config{ + ConfigPath: "./config.toml", + BaseUrl: "http://localhost:8080", + Port: 8080, + SavePath: "./storage", + } +} diff --git a/config/toml.go b/config/toml.go new file mode 100644 index 0000000..9183334 --- /dev/null +++ b/config/toml.go @@ -0,0 +1,26 @@ +package config + +import ( + "os" + + "github.com/pelletier/go-toml/v2" +) + +func (c *Config) ReadFile(name string) error { + file, err := os.Open(name) + if err != nil { + return err + } + + err = toml.NewDecoder(file).Decode(c) + + return err +} + +func (c *Config) TOML() string { + b, err := toml.Marshal(c) + if err != nil { + panic(err) + } + return string(b) +} diff --git a/config/toml_test.go b/config/toml_test.go new file mode 100644 index 0000000..3f7dfdb --- /dev/null +++ b/config/toml_test.go @@ -0,0 +1,22 @@ +package config + +import ( + "testing" +) + +func TestReadFile(t *testing.T) { + c := Default() + + err := c.ReadFile("../config.example.toml") + + if err != nil { + t.Fatalf("failed to read config file: %v", err) + } + + // Validate the values are correctly set based on the TOML file + expectedBaseURL := "http://localhost:8080" + if c.BaseUrl != expectedBaseURL { + t.Errorf("expected BaseUrl to be '%v', got '%v'", expectedBaseURL, c.BaseUrl) + } + +} diff --git a/controller/controller.go b/controller/controller.go new file mode 100644 index 0000000..b514d92 --- /dev/null +++ b/controller/controller.go @@ -0,0 +1,60 @@ +package controller + +import ( + "context" + "errors" + + "git.schreifuchs.ch/schreifuchs/warehouse/api" + "git.schreifuchs.ch/schreifuchs/warehouse/model" + "gorm.io/gorm" +) + +func (c *Controller) GetBuckets(ctx context.Context, request api.GetBucketsRequestObject) (api.GetBucketsResponseObject, error) { + + buckets, err := c.db.FindApiBuckets(*request.Params.Limit, *request.Params.Offset) + if err != nil { + return api.GetBuckets500Response{}, nil + } + + t := len(buckets) + + return api.GetBuckets200JSONResponse{ + Items: &buckets, + Total: &t, + }, nil +} + +func (c *Controller) PostBuckets(ctx context.Context, request api.PostBucketsRequestObject) (api.PostBucketsResponseObject, error) { + + b := &model.Bucket{} + b.Name = *request.Body.Name + + c.db.InsertBucket(b) + + return api.PostBuckets409Response{}, nil +} + +func (c *Controller) DeleteBucketsBucketName(ctx context.Context, request api.DeleteBucketsBucketNameRequestObject) (api.DeleteBucketsBucketNameResponseObject, error) { + 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 api.DeleteBucketsBucketName204Response{}, nil +} + +func (c *Controller) GetBucketsBucketNameObjects(ctx context.Context, request api.GetBucketsBucketNameObjectsRequestObject) (api.GetBucketsBucketNameObjectsResponseObject, error) { + return api.GetBucketsBucketNameObjects404Response{}, nil +} + +func (c *Controller) DeleteBucketsBucketNameObjectsObjectKey(ctx context.Context, request api.DeleteBucketsBucketNameObjectsObjectKeyRequestObject) (api.DeleteBucketsBucketNameObjectsObjectKeyResponseObject, error) { + return api.DeleteBucketsBucketNameObjectsObjectKey500Response{}, nil +} + +func (c *Controller) GetBucketsBucketNameObjectsObjectKey(ctx context.Context, request api.GetBucketsBucketNameObjectsObjectKeyRequestObject) (api.GetBucketsBucketNameObjectsObjectKeyResponseObject, error) { + return api.GetBucketsBucketNameObjectsObjectKey404Response{}, nil +} + +func (c *Controller) PutBucketsBucketNameObjectsObjectKey(ctx context.Context, request api.PutBucketsBucketNameObjectsObjectKeyRequestObject) (api.PutBucketsBucketNameObjectsObjectKeyResponseObject, error) { + return api.PutBucketsBucketNameObjectsObjectKey500Response{}, nil +} diff --git a/controller/resource.go b/controller/resource.go new file mode 100644 index 0000000..1808f81 --- /dev/null +++ b/controller/resource.go @@ -0,0 +1,24 @@ +package controller + +import ( + "git.schreifuchs.ch/schreifuchs/warehouse/api" + "git.schreifuchs.ch/schreifuchs/warehouse/model" +) + +// Implement the interface +type Controller struct { + db database +} + +func New(db database) *Controller { + return &Controller{ + db: db, + } +} + +type database interface { + InsertBucket(bucket *model.Bucket) error + FindApiBuckets(limit, offset int) (buckets []api.Bucket, err error) + FindBucketIdByName(name string) (id uint, err error) + DeleteBucketByName(name string) error +} diff --git a/database/controller.go b/database/controller.go new file mode 100644 index 0000000..86e7de1 --- /dev/null +++ b/database/controller.go @@ -0,0 +1,30 @@ +package database + +import ( + "git.schreifuchs.ch/schreifuchs/warehouse/api" + "git.schreifuchs.ch/schreifuchs/warehouse/model" +) + +func (db *DB) InsertBucket(bucket *model.Bucket) error { + return db.conn.Create(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) FindBucketIdByName(name string) (id uint, err error) { + b := &model.Bucket{} + err = db.conn.Select("id").First(b).Error + + return b.ID, err +} + +func (db *DB) DeleteBucket(id uint) error { + return db.conn.Delete(&model.Bucket{}, id).Error +} +func (db *DB) DeleteBucketByName(name string) error { + return db.conn.Delete(&model.Bucket{}).Where("name = ?", name).Error +} diff --git a/database/resource.go b/database/resource.go new file mode 100644 index 0000000..df74ddb --- /dev/null +++ b/database/resource.go @@ -0,0 +1,20 @@ +package database + +import ( + "git.schreifuchs.ch/schreifuchs/warehouse/model" + "gorm.io/driver/sqlite" // Sqlite driver based on CGO + "gorm.io/gorm" +) + +type DB struct { + conn *gorm.DB +} + +func Init(file string) (*DB, error) { + if db, err := gorm.Open(sqlite.Open(file), &gorm.Config{}); err != nil { + return nil, err + } else { + db.AutoMigrate(&model.Bucket{}, &model.Object{}) + return &DB{conn: db}, nil + } +} diff --git a/go.mod b/go.mod index 96fd1a8..90bfe56 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,11 @@ go 1.23.2 require ( github.com/getkin/kin-openapi v0.128.0 github.com/gorilla/mux v1.8.0 + github.com/joho/godotenv v1.5.1 github.com/oapi-codegen/runtime v1.1.1 + github.com/pelletier/go-toml/v2 v2.2.3 + gorm.io/driver/sqlite v1.5.6 + gorm.io/gorm v1.25.12 ) require ( @@ -14,9 +18,13 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/google/uuid v1.5.0 // indirect github.com/invopop/yaml v0.3.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 246c992..2e46334 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,12 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= @@ -28,10 +34,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -45,8 +55,14 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= +gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c076b93 --- /dev/null +++ b/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "net/http" + "os" + + "git.schreifuchs.ch/schreifuchs/warehouse/api" + "git.schreifuchs.ch/schreifuchs/warehouse/config" + "git.schreifuchs.ch/schreifuchs/warehouse/controller" + "git.schreifuchs.ch/schreifuchs/warehouse/database" + "github.com/gorilla/mux" + "github.com/joho/godotenv" +) + +func readCfg() *config.Config { + cfg := config.Default() + + if err := godotenv.Overload(); err != nil { + fmt.Println("no .env file loaded") + } else { + fmt.Println(".env file loaded") + } + cfg.ReadEnv() + + return cfg +} + +func main() { + cfg := readCfg() + + fmt.Println(cfg.TOML()) + + db, err := database.Init("./warehouse.db") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + controller := controller.New(db) + + apiHandler := api.Handler(api.NewStrictHandler(controller, nil)) + + r := mux.NewRouter() + r.PathPrefix("/").Handler(apiHandler) + + if err := http.ListenAndServe(fmt.Sprintf(":%d", cfg.Port), r); err != nil { + fmt.Println(err) + os.Exit(1) + } + +} diff --git a/model/resource.go b/model/resource.go new file mode 100644 index 0000000..afcd52d --- /dev/null +++ b/model/resource.go @@ -0,0 +1,16 @@ +package model + +import "gorm.io/gorm" + +type Bucket struct { + gorm.Model + Name string `gorm:"uniqueIndex"` + Objects []Object +} + +type Object struct { + gorm.Model + Name string `gorm:"uniqueIndex"` + Size uint + BucketId uint +} diff --git a/openapi/openapi.yml b/openapi/openapi.yml index 2d98f96..57052e9 100644 --- a/openapi/openapi.yml +++ b/openapi/openapi.yml @@ -1,5 +1,5 @@ -openapi: 3.0.3 +openapi: 3.0.0 info: title: Object Storage API description: API for managing objects in an object storage service. @@ -21,7 +21,7 @@ paths: in: query schema: type: integer - default: 10 + default: 100 description: The maximum number of items to return - name: offset in: query @@ -40,12 +40,6 @@ paths: total: type: integer description: Total number of buckets available - 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: diff --git a/warehouse.db b/warehouse.db new file mode 100644 index 0000000..134838c Binary files /dev/null and b/warehouse.db differ