diff --git a/go.mod b/go.mod index 9acf7e137..84826a0ba 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,9 @@ module d7y.io/dragonfly/v2 go 1.15 require ( - github.com/HuKeping/rbtree v0.0.0-20210106022122-8ad34838eb2b github.com/RichardKnop/machinery v1.10.6 github.com/VividCortex/mysqlerr v1.0.0 github.com/agiledragon/gomonkey v2.0.2+incompatible - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible github.com/appleboy/gin-jwt/v2 v2.6.5-0.20210827121450-79689222c755 github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect @@ -49,7 +47,6 @@ require ( github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.14.0 github.com/opencontainers/go-digest v1.0.0 - github.com/pborman/uuid v1.2.1 github.com/pelletier/go-toml v1.8.1 // indirect github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 7bc6cff38..2651987d2 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,6 @@ github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/HuKeping/rbtree v0.0.0-20210106022122-8ad34838eb2b h1:zDhQxG7rm8RLgLgi6NpfaVFsop+zxw5hwhbzHr624us= -github.com/HuKeping/rbtree v0.0.0-20210106022122-8ad34838eb2b/go.mod h1:bODsl3NElqKlgf1UkBLj67fYmY5DsqkKrrYm/kMT/6Y= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= @@ -70,7 +68,6 @@ github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3 github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -382,7 +379,6 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= @@ -667,8 +663,6 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= -github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= -github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= diff --git a/manager/database/database.go b/manager/database/database.go index 8c0072271..698e3de4d 100644 --- a/manager/database/database.go +++ b/manager/database/database.go @@ -117,6 +117,7 @@ func migrate(db *gorm.DB) error { &model.User{}, &model.Oauth{}, &model.Config{}, + &model.Application{}, ) } diff --git a/manager/handlers/application.go b/manager/handlers/application.go new file mode 100644 index 000000000..6ddff1b76 --- /dev/null +++ b/manager/handlers/application.go @@ -0,0 +1,279 @@ +/* + * Copyright 2020 The Dragonfly Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package handlers + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + // nolint + _ "d7y.io/dragonfly/v2/manager/model" + "d7y.io/dragonfly/v2/manager/types" +) + +// @Summary Create Application +// @Description create by json config +// @Tags Application +// @Accept json +// @Produce json +// @Param Application body types.CreateApplicationRequest true "Application" +// @Success 200 {object} model.Application +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Router /applications [post] +func (h *Handlers) CreateApplication(ctx *gin.Context) { + var json types.CreateApplicationRequest + if err := ctx.ShouldBindJSON(&json); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + application, err := h.service.CreateApplication(ctx.Request.Context(), json) + if err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + ctx.JSON(http.StatusOK, application) +} + +// @Summary Destroy Application +// @Description Destroy by id +// @Tags Application +// @Accept json +// @Produce json +// @Param id path string true "id" +// @Success 200 +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Router /applications/{id} [delete] +func (h *Handlers) DestroyApplication(ctx *gin.Context) { + var params types.ApplicationParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if err := h.service.DestroyApplication(ctx.Request.Context(), params.ID); err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + ctx.Status(http.StatusOK) +} + +// @Summary Update Application +// @Description Update by json config +// @Tags Application +// @Accept json +// @Produce json +// @Param id path string true "id" +// @Param Application body types.UpdateApplicationRequest true "Application" +// @Success 200 {object} model.Application +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Router /applications/{id} [patch] +func (h *Handlers) UpdateApplication(ctx *gin.Context) { + var params types.ApplicationParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + var json types.UpdateApplicationRequest + if err := ctx.ShouldBindJSON(&json); err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + application, err := h.service.UpdateApplication(ctx.Request.Context(), params.ID, json) + if err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + ctx.JSON(http.StatusOK, application) +} + +// @Summary Get Application +// @Description Get Application by id +// @Tags Application +// @Accept json +// @Produce json +// @Param id path string true "id" +// @Success 200 {object} model.Application +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Router /applications/{id} [get] +func (h *Handlers) GetApplication(ctx *gin.Context) { + var params types.ApplicationParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + application, err := h.service.GetApplication(ctx.Request.Context(), params.ID) + if err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + ctx.JSON(http.StatusOK, application) +} + +// @Summary Get Applications +// @Description Get Applications +// @Tags Application +// @Accept json +// @Produce json +// @Param page query int true "current page" default(0) +// @Param per_page query int true "return max item count, default 10, max 50" default(10) minimum(2) maximum(50) +// @Success 200 {object} []model.Application +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Router /applications [get] +func (h *Handlers) GetApplications(ctx *gin.Context) { + var query types.GetApplicationsQuery + if err := ctx.ShouldBindQuery(&query); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + h.setPaginationDefault(&query.Page, &query.PerPage) + applications, count, err := h.service.GetApplications(ctx.Request.Context(), query) + if err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + h.setPaginationLinkHeader(ctx, query.Page, query.PerPage, int(count)) + ctx.JSON(http.StatusOK, applications) +} + +// @Summary Add Scheduler to Application +// @Description Add Scheduler to Application +// @Tags Application +// @Accept json +// @Produce json +// @Param id path string true "id" +// @Param scheduler_cluster_id path string true "scheduler cluster id" +// @Success 200 +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Router /applications/{id}/scheduler-clusters/{scheduler_cluster_id} [put] +func (h *Handlers) AddSchedulerClusterToApplication(ctx *gin.Context) { + var params types.AddSchedulerClusterToApplicationParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if err := h.service.AddSchedulerClusterToApplication(ctx.Request.Context(), params.ID, params.SchedulerClusterID); err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + ctx.Status(http.StatusOK) +} + +// @Summary Delete Scheduler to Application +// @Description Delete Scheduler to Application +// @Tags Application +// @Accept json +// @Produce json +// @Param id path string true "id" +// @Param scheduler_cluster_id path string true "scheduler cluster id" +// @Success 200 +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Router /applications/{id}/scheduler-clusters/{scheduler_cluster_id} [delete] +func (h *Handlers) DeleteSchedulerClusterToApplication(ctx *gin.Context) { + var params types.DeleteSchedulerClusterToApplicationParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if err := h.service.DeleteSchedulerClusterToApplication(ctx.Request.Context(), params.ID, params.SchedulerClusterID); err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + ctx.Status(http.StatusOK) +} + +// @Summary Add CDN to Application +// @Description Add CDN to Application +// @Tags Application +// @Accept json +// @Produce json +// @Param id path string true "id" +// @Param cdn_cluster_id path string true "cdn cluster id" +// @Success 200 +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Router /applications/{id}/cdn-clusters/{cdn_cluster_id} [put] +func (h *Handlers) AddCDNClusterToApplication(ctx *gin.Context) { + var params types.AddCDNClusterToApplicationParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if err := h.service.AddCDNClusterToApplication(ctx.Request.Context(), params.ID, params.CDNClusterID); err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + ctx.Status(http.StatusOK) +} + +// @Summary Delete CDN to Application +// @Description Delete CDN to Application +// @Tags Application +// @Accept json +// @Produce json +// @Param id path string true "id" +// @Param cdn_cluster_id path string true "cdn cluster id" +// @Success 200 +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Router /applications/{id}/cdn-clusters/{cdn_cluster_id} [delete] +func (h *Handlers) DeleteCDNClusterToApplication(ctx *gin.Context) { + var params types.DeleteCDNClusterToApplicationParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if err := h.service.DeleteCDNClusterToApplication(ctx.Request.Context(), params.ID, params.CDNClusterID); err != nil { + ctx.Error(err) // nolint: errcheck + return + } + + ctx.Status(http.StatusOK) +} diff --git a/manager/model/application.go b/manager/model/application.go new file mode 100644 index 000000000..f288643d9 --- /dev/null +++ b/manager/model/application.go @@ -0,0 +1,29 @@ +/* + * Copyright 2020 The Dragonfly Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package model + +type Application struct { + Model + Name string `gorm:"column:name;type:varchar(256);index:uk_application_name,unique;not null;comment:name" json:"name"` + DownloadRateLimit string `gorm:"column:download_rate_limit;type:varchar(1024);comment:download_rate_limit" json:"download_rate_limit"` + URL string `gorm:"column:url;not null;comment:url" json:"url"` + State string `gorm:"column:state;type:varchar(256);default:'enable';comment:state" json:"state"` + BIO string `gorm:"column:bio;type:varchar(1024);comment:biography" json:"bio"` + UserID uint `gorm:"comment:user id" json:"user_id"` + SchedulerClusters []SchedulerCluster `json:"-"` + CDNClusters []CDNCluster `json:"-"` +} diff --git a/manager/model/cdn_cluster.go b/manager/model/cdn_cluster.go index ebc516ca1..187640ccf 100644 --- a/manager/model/cdn_cluster.go +++ b/manager/model/cdn_cluster.go @@ -25,6 +25,7 @@ type CDNCluster struct { IsDefault bool `gorm:"column:is_default;not null;default:false;comment:default cdn cluster" json:"is_default"` CDNs []CDN `json:"-"` SecurityGroupID uint `gorm:"comment:security group id" json:"security_group_id"` + ApplicationID uint `gorm:"comment:application id" json:"application_id"` SecurityGroup SecurityGroup `json:"-"` Jobs []Job `gorm:"many2many:job_cdn_cluster;" json:"jobs"` } diff --git a/manager/model/models.go b/manager/model/models.go index 98d242cfa..09eae5267 100644 --- a/manager/model/models.go +++ b/manager/model/models.go @@ -42,7 +42,10 @@ func Paginate(page, perPage int) func(db *gorm.DB) *gorm.DB { } } -type JSONMap map[string]interface{} +type ( + JSONMap map[string]interface{} + Array []string +) func (m JSONMap) Value() (driver.Value, error) { if m == nil { @@ -52,6 +55,14 @@ func (m JSONMap) Value() (driver.Value, error) { return string(ba), err } +func (a Array) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + ba, err := a.MarshalJSON() + return string(ba), err +} + func (m *JSONMap) Scan(val interface{}) error { var ba []byte switch v := val.(type) { @@ -68,6 +79,22 @@ func (m *JSONMap) Scan(val interface{}) error { return err } +func (a *Array) Scan(val interface{}) error { + var ba []byte + switch v := val.(type) { + case []byte: + ba = v + case string: + ba = []byte(v) + default: + return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", val)) + } + t := []string{} + err := json.Unmarshal(ba, &t) + *a = Array(t) + return err +} + func (m JSONMap) MarshalJSON() ([]byte, error) { if m == nil { return []byte("null"), nil @@ -76,6 +103,14 @@ func (m JSONMap) MarshalJSON() ([]byte, error) { return json.Marshal(t) } +func (a Array) MarshalJSON() ([]byte, error) { + if a == nil { + return []byte("null"), nil + } + t := ([]string)(a) + return json.Marshal(t) +} + func (m *JSONMap) UnmarshalJSON(b []byte) error { t := map[string]interface{}{} err := json.Unmarshal(b, &t) @@ -83,6 +118,13 @@ func (m *JSONMap) UnmarshalJSON(b []byte) error { return err } +func (a *Array) UnmarshalJSON(b []byte) error { + t := []string{} + err := json.Unmarshal(b, &t) + *a = Array(t) + return err +} + func (m JSONMap) GormDataType() string { return "jsonmap" } @@ -90,3 +132,11 @@ func (m JSONMap) GormDataType() string { func (JSONMap) GormDBDataType(db *gorm.DB, field *schema.Field) string { return "text" } + +func (Array) GormDataType() string { + return "array" +} + +func (Array) GormDBDataType(db *gorm.DB, field *schema.Field) string { + return "text" +} diff --git a/manager/model/scheduler_cluster.go b/manager/model/scheduler_cluster.go index c0f7f5122..d9fb0be19 100644 --- a/manager/model/scheduler_cluster.go +++ b/manager/model/scheduler_cluster.go @@ -29,4 +29,5 @@ type SchedulerCluster struct { SecurityGroupID uint `gorm:"comment:security group id" json:"security_group_id"` SecurityGroup SecurityGroup `json:"-"` Jobs []Job `gorm:"many2many:job_scheduler_cluster;" json:"jobs"` + ApplicationID uint `gorm:"comment:application id" json:"application_id"` } diff --git a/manager/router/router.go b/manager/router/router.go index 7f273aea6..267a697cf 100644 --- a/manager/router/router.go +++ b/manager/router/router.go @@ -42,9 +42,7 @@ const ( OtelServiceName = "dragonfly-manager" ) -var ( - GinLogFileName = "gin.log" -) +var GinLogFileName = "gin.log" func Init(cfg *config.Config, service service.REST, enforcer *casbin.Enforcer) (*gin.Engine, error) { // Set mode @@ -147,6 +145,18 @@ func Init(cfg *config.Config, service service.REST, enforcer *casbin.Enforcer) ( s.GET(":id", h.GetScheduler) s.GET("", h.GetSchedulers) + // Application + cs := apiv1.Group("/applications", jwt.MiddlewareFunc(), rbac) + cs.POST("", h.CreateApplication) + cs.DELETE(":id", h.DestroyApplication) + cs.PATCH(":id", h.UpdateApplication) + cs.GET(":id", h.GetApplication) + cs.GET("", h.GetApplications) + cs.PUT(":id/scheduler-clusters/:scheduler_cluster_id", h.AddSchedulerClusterToApplication) + cs.DELETE(":id/scheduler-clusters/:scheduler_cluster_id", h.DeleteSchedulerClusterToApplication) + cs.PUT(":id/cdn-clusters/:cdn_cluster_id", h.AddCDNClusterToApplication) + cs.DELETE(":id/cdn-clusters/:cdn_cluster_id", h.DeleteCDNClusterToApplication) + // CDN Cluster cc := apiv1.Group("/cdn-clusters", jwt.MiddlewareFunc(), rbac) cc.POST("", h.CreateCDNCluster) diff --git a/manager/service/application.go b/manager/service/application.go new file mode 100644 index 000000000..de91e36dc --- /dev/null +++ b/manager/service/application.go @@ -0,0 +1,161 @@ +/* + * Copyright 2020 The Dragonfly Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package service + +import ( + "context" + + "d7y.io/dragonfly/v2/manager/model" + "d7y.io/dragonfly/v2/manager/types" +) + +func (s *rest) CreateApplication(ctx context.Context, json types.CreateApplicationRequest) (*model.Application, error) { + application := model.Application{ + Name: json.Name, + DownloadRateLimit: json.DownloadRateLimit, + URL: json.URL, + UserID: json.UserID, + BIO: json.BIO, + State: json.State, + } + + if err := s.db.WithContext(ctx).Create(&application).Error; err != nil { + return nil, err + } + + return &application, nil +} + +func (s *rest) DestroyApplication(ctx context.Context, id uint) error { + application := model.Application{} + if err := s.db.WithContext(ctx).First(&application, id).Error; err != nil { + return err + } + + if err := s.db.WithContext(ctx).Unscoped().Delete(&model.Application{}, id).Error; err != nil { + return err + } + + return nil +} + +func (s *rest) UpdateApplication(ctx context.Context, id uint, json types.UpdateApplicationRequest) (*model.Application, error) { + application := model.Application{} + if err := s.db.WithContext(ctx).First(&application, id).Updates(model.Application{ + Name: json.Name, + DownloadRateLimit: json.DownloadRateLimit, + URL: json.URL, + State: json.State, + BIO: json.BIO, + UserID: json.UserID, + }).Error; err != nil { + return nil, err + } + + return &application, nil +} + +func (s *rest) GetApplication(ctx context.Context, id uint) (*model.Application, error) { + application := model.Application{} + if err := s.db.WithContext(ctx).Preload("SchedulerClusters").First(&application, id).Error; err != nil { + return nil, err + } + + return &application, nil +} + +func (s *rest) GetApplications(ctx context.Context, q types.GetApplicationsQuery) (*[]model.Application, int64, error) { + var count int64 + applications := []model.Application{} + if err := s.db.WithContext(ctx).Scopes(model.Paginate(q.Page, q.PerPage)).Preload("SchedulerClusters").Find(&applications).Count(&count).Error; err != nil { + return nil, 0, err + } + + return &applications, count, nil +} + +func (s *rest) AddSchedulerClusterToApplication(ctx context.Context, id, schedulerClusterID uint) error { + application := model.Application{} + if err := s.db.WithContext(ctx).First(&application, id).Error; err != nil { + return err + } + + schedulerCluster := model.SchedulerCluster{} + if err := s.db.WithContext(ctx).First(&schedulerCluster, schedulerClusterID).Error; err != nil { + return err + } + + if err := s.db.WithContext(ctx).Model(&application).Association("SchedulerClusters").Append(&schedulerCluster); err != nil { + return err + } + + return nil +} + +func (s *rest) DeleteSchedulerClusterToApplication(ctx context.Context, id, schedulerClusterID uint) error { + application := model.Application{} + if err := s.db.WithContext(ctx).First(&application, id).Error; err != nil { + return err + } + + schedulerCluster := model.SchedulerCluster{} + if err := s.db.WithContext(ctx).First(&schedulerCluster, schedulerClusterID).Error; err != nil { + return err + } + + if err := s.db.Model(&application).Association("SchedulerClusters").Delete(&schedulerCluster); err != nil { + return err + } + + return nil +} + +func (s *rest) AddCDNClusterToApplication(ctx context.Context, id, cdnClusterID uint) error { + application := model.Application{} + if err := s.db.WithContext(ctx).First(&application, id).Error; err != nil { + return err + } + + cdnCluster := model.CDNCluster{} + if err := s.db.WithContext(ctx).First(&cdnCluster, cdnClusterID).Error; err != nil { + return err + } + + if err := s.db.WithContext(ctx).Model(&application).Association("CDNClusters").Append(&cdnCluster); err != nil { + return err + } + + return nil +} + +func (s *rest) DeleteCDNClusterToApplication(ctx context.Context, id, cdnClusterID uint) error { + application := model.Application{} + if err := s.db.WithContext(ctx).First(&application, id).Error; err != nil { + return err + } + + cdnCluster := model.CDNCluster{} + if err := s.db.WithContext(ctx).First(&cdnCluster, cdnClusterID).Error; err != nil { + return err + } + + if err := s.db.Model(&application).Association("CDNClusters").Delete(&cdnCluster); err != nil { + return err + } + + return nil +} diff --git a/manager/service/service.go b/manager/service/service.go index 63b8c1904..8a773e7b5 100644 --- a/manager/service/service.go +++ b/manager/service/service.go @@ -116,6 +116,16 @@ type REST interface { CreateV1Preheat(context.Context, types.CreateV1PreheatRequest) (*types.CreateV1PreheatResponse, error) GetV1Preheat(context.Context, string) (*types.GetV1PreheatResponse, error) + + CreateApplication(context.Context, types.CreateApplicationRequest) (*model.Application, error) + DestroyApplication(context.Context, uint) error + UpdateApplication(context.Context, uint, types.UpdateApplicationRequest) (*model.Application, error) + GetApplication(context.Context, uint) (*model.Application, error) + GetApplications(context.Context, types.GetApplicationsQuery) (*[]model.Application, int64, error) + AddSchedulerClusterToApplication(context.Context, uint, uint) error + DeleteSchedulerClusterToApplication(context.Context, uint, uint) error + AddCDNClusterToApplication(context.Context, uint, uint) error + DeleteCDNClusterToApplication(context.Context, uint, uint) error } type rest struct { diff --git a/manager/types/application.go b/manager/types/application.go new file mode 100644 index 000000000..3f1f7f574 --- /dev/null +++ b/manager/types/application.go @@ -0,0 +1,64 @@ +/* + * Copyright 2020 The Dragonfly Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package types + +type ApplicationParams struct { + ID uint `uri:"id" binding:"required"` +} + +type AddSchedulerClusterToApplicationParams struct { + ID uint `uri:"id" binding:"required"` + SchedulerClusterID uint `uri:"scheduler_cluster_id" binding:"required"` +} + +type DeleteSchedulerClusterToApplicationParams struct { + ID uint `uri:"id" binding:"required"` + SchedulerClusterID uint `uri:"scheduler_cluster_id" binding:"required"` +} + +type AddCDNClusterToApplicationParams struct { + ID uint `uri:"id" binding:"required"` + CDNClusterID uint `uri:"cdn_cluster_id" binding:"required"` +} + +type DeleteCDNClusterToApplicationParams struct { + ID uint `uri:"id" binding:"required"` + CDNClusterID uint `uri:"cdn_cluster_id" binding:"required"` +} + +type CreateApplicationRequest struct { + Name string `json:"name" binding:"required"` + DownloadRateLimit string `json:"download_rate_limit" binding:"omitempty"` + URL string `json:"url" binding:"omitempty"` + BIO string `json:"bio" binding:"omitempty"` + UserID uint `json:"user_id" binding:"omitempty"` + State string `json:"state" binding:"required,oneof=enable disable"` +} + +type UpdateApplicationRequest struct { + Name string `json:"name" binding:"required"` + DownloadRateLimit string `json:"download_rate_limit" binding:"required"` + URL string `json:"url" binding:"required"` + State string `json:"state" binding:"required,oneof=enable disable"` + BIO string `json:"bio" binding:"omitempty"` + UserID uint `json:"user_id" binding:"omitempty"` +} + +type GetApplicationsQuery struct { + Page int `form:"page" binding:"omitempty,gte=1"` + PerPage int `form:"per_page" binding:"omitempty,gte=1,lte=50"` +}