This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 615a88f48 refactor(bitbucket): refactor scope (#4661)
615a88f48 is described below

commit 615a88f4865fb149666facde25fcac9ea8a6d78e
Author: Warren Chen <[email protected]>
AuthorDate: Tue Mar 14 21:43:26 2023 +0800

    refactor(bitbucket): refactor scope (#4661)
---
 backend/helpers/pluginhelper/api/scope_helper.go   |  46 +-----
 .../helpers/pluginhelper/api/scope_helper_test.go  |  41 -----
 backend/plugins/bamboo/api/init.go                 |  13 +-
 backend/plugins/bamboo/api/remote.go               |   5 +
 backend/plugins/bamboo/api/scope.go                | 171 ++-------------------
 backend/plugins/bamboo/impl/impl.go                |   2 +-
 backend/plugins/bamboo/models/project.go           |   4 +-
 backend/plugins/bitbucket/api/init.go              |   7 +
 backend/plugins/bitbucket/api/remote.go            |   5 +
 backend/plugins/bitbucket/api/scope.go             | 147 ++----------------
 backend/plugins/bitbucket/impl/impl.go             |   2 +-
 backend/plugins/bitbucket/models/repo.go           |   4 +-
 backend/plugins/github/api/scope.go                |   5 +-
 backend/plugins/github/models/repo.go              |   2 +-
 backend/plugins/gitlab/api/scope.go                |  13 +-
 backend/plugins/gitlab/impl/impl.go                |   2 +-
 backend/plugins/gitlab/models/project.go           |   2 +-
 17 files changed, 78 insertions(+), 393 deletions(-)

diff --git a/backend/helpers/pluginhelper/api/scope_helper.go 
b/backend/helpers/pluginhelper/api/scope_helper.go
index 04244290e..b153dfe27 100644
--- a/backend/helpers/pluginhelper/api/scope_helper.go
+++ b/backend/helpers/pluginhelper/api/scope_helper.go
@@ -66,7 +66,7 @@ func NewScopeHelper[Conn any, Scope any, Tr any](
 
 type ScopeRes[T any] struct {
        Scope                  T      `mapstructure:",squash"`
-       TransformationRuleName string `json:"transformationRuleName,omitempty"`
+       TransformationRuleName string 
`mapstructure:"transformationRuleName,omitempty"`
 }
 
 type ScopeReq[T any] struct {
@@ -331,9 +331,9 @@ func VerifyScope(scope interface{}, vld 
*validator.Validate) errors.Error {
 }
 
 // Implement MarshalJSON method to flatten all fields
-func (sr ScopeRes[T]) MarshalJSON() ([]byte, error) {
-       // Create an empty map to store flattened fields and values
-       flatMap, err := flattenStruct(sr)
+func (sr *ScopeRes[T]) MarshalJSON() ([]byte, error) {
+       var flatMap map[string]interface{}
+       err := mapstructure.Decode(sr, &flatMap)
        if err != nil {
                return nil, err
        }
@@ -345,41 +345,3 @@ func (sr ScopeRes[T]) MarshalJSON() ([]byte, error) {
 
        return result, nil
 }
-
-// A helper function to flatten nested structs
-func flattenStruct(s interface{}) (map[string]interface{}, error) {
-       flatMap := make(map[string]interface{})
-
-       // Use reflection to get all fields of the nested struct type
-       fields := reflect.TypeOf(s).NumField()
-
-       // Traverse all fields of the nested struct and add them to flatMap
-       for i := 0; i < fields; i++ {
-               field := reflect.TypeOf(s).Field(i)
-               fieldValue := reflect.ValueOf(s).Field(i)
-               if strings.Contains(field.Tag.Get("swaggerignore"), "true") {
-                       continue
-               }
-               if fieldValue.IsZero() && 
strings.Contains(field.Tag.Get("json"), "omitempty") {
-                       continue
-               }
-               // If the field is a nested struct, recursively flatten its 
fields
-               if field.Type.Kind() == reflect.Struct && 
strings.Contains(field.Tag.Get("mapstructure"), "squash") {
-                       nestedFields, err := 
flattenStruct(fieldValue.Interface())
-                       if err != nil {
-                               return nil, err
-                       }
-                       for k, v := range nestedFields {
-                               flatMap[lowerCaseFirst(k)] = v
-                       }
-               } else {
-                       // If the field is not a nested struct, add its name 
and value to flatMap
-                       flatMap[lowerCaseFirst(field.Name)] = 
fieldValue.Interface()
-               }
-       }
-       return flatMap, nil
-}
-
-func lowerCaseFirst(name string) string {
-       return strings.ToLower(string(name[0])) + name[1:]
-}
diff --git a/backend/helpers/pluginhelper/api/scope_helper_test.go 
b/backend/helpers/pluginhelper/api/scope_helper_test.go
index cc0657eea..c90afffa4 100644
--- a/backend/helpers/pluginhelper/api/scope_helper_test.go
+++ b/backend/helpers/pluginhelper/api/scope_helper_test.go
@@ -291,44 +291,3 @@ func TestScopeApiHelper_Put(t *testing.T) {
        _, err := apiHelper.Put(input)
        assert.NoError(t, err)
 }
-
-func TestFlattenStruct(t *testing.T) {
-       type InnerStruct struct {
-               Foo int
-               Bar string
-       }
-
-       type OuterStruct struct {
-               Baz       bool
-               Qux       float64
-               Inner     InnerStruct `mapstructure:",squash"`
-               OtherProp string
-       }
-
-       input := OuterStruct{
-               Baz: true,
-               Qux: 3.14,
-               Inner: InnerStruct{
-                       Foo: 42,
-                       Bar: "hello",
-               },
-               OtherProp: "world",
-       }
-
-       expectedOutput := map[string]interface{}{
-               "baz":       true,
-               "qux":       3.14,
-               "foo":       42,
-               "bar":       "hello",
-               "otherProp": "world",
-       }
-
-       output, err := flattenStruct(input)
-       if err != nil {
-               t.Errorf("flattenStruct returned an error: %v", err)
-       }
-
-       if !reflect.DeepEqual(output, expectedOutput) {
-               t.Errorf("flattenStruct returned incorrect output.\nExpected: 
%v\nActual:   %v", expectedOutput, output)
-       }
-}
diff --git a/backend/plugins/bamboo/api/init.go 
b/backend/plugins/bamboo/api/init.go
index cd019a36f..3044a56ba 100644
--- a/backend/plugins/bamboo/api/init.go
+++ b/backend/plugins/bamboo/api/init.go
@@ -19,19 +19,26 @@ package api
 
 import (
        "github.com/apache/incubator-devlake/core/context"
-       helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/plugins/github/models"
        "github.com/go-playground/validator/v10"
 )
 
 var vld *validator.Validate
-var connectionHelper *helper.ConnectionApiHelper
+var connectionHelper *api.ConnectionApiHelper
+var scopeHelper *api.ScopeApiHelper[models.GithubConnection, 
models.GithubRepo, models.GithubTransformationRule]
 var basicRes context.BasicRes
 
 func Init(br context.BasicRes) {
        basicRes = br
        vld = validator.New()
-       connectionHelper = helper.NewConnectionHelper(
+       connectionHelper = api.NewConnectionHelper(
                basicRes,
                vld,
        )
+       scopeHelper = api.NewScopeHelper[models.GithubConnection, 
models.GithubRepo, models.GithubTransformationRule](
+               basicRes,
+               vld,
+               connectionHelper,
+       )
 }
diff --git a/backend/plugins/bamboo/api/remote.go 
b/backend/plugins/bamboo/api/remote.go
index 250571390..91551001c 100644
--- a/backend/plugins/bamboo/api/remote.go
+++ b/backend/plugins/bamboo/api/remote.go
@@ -300,3 +300,8 @@ func GetQueryForSearchProject(search string, page int, 
perPage int) url.Values {
 
        return query
 }
+func extractParam(params map[string]string) (uint64, string) {
+       connectionId, _ := strconv.ParseUint(params["connectionId"], 10, 64)
+       projectKey := params["projectKey"]
+       return connectionId, projectKey
+}
diff --git a/backend/plugins/bamboo/api/scope.go 
b/backend/plugins/bamboo/api/scope.go
index 703f20a39..3b318da8f 100644
--- a/backend/plugins/bamboo/api/scope.go
+++ b/backend/plugins/bamboo/api/scope.go
@@ -18,26 +18,18 @@ limitations under the License.
 package api
 
 import (
-       "context"
-       "net/http"
-       "strconv"
-
-       "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
        "github.com/apache/incubator-devlake/plugins/bamboo/models"
-       "github.com/mitchellh/mapstructure"
 )
 
-type apiProject struct {
+type ScopeRes struct {
        models.BambooProject
        TransformationRuleName string `json:"transformationRuleName,omitempty"`
 }
 
-type req struct {
-       Data []*models.BambooProject `json:"data"`
-}
+type ScopeReq api.ScopeReq[models.BambooProject]
 
 // PutScope create or update bamboo project
 // @Summary create or update bamboo project
@@ -45,40 +37,13 @@ type req struct {
 // @Tags plugins/bamboo
 // @Accept application/json
 // @Param connectionId path int false "connection ID"
-// @Param scope body req true "json"
+// @Param scope body ScopeReq true "json"
 // @Success 200  {object} []models.BambooProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scopes [PUT]
 func PutScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       connectionId, _ := extractParam(input.Params)
-
-       if connectionId == 0 {
-               return nil, errors.BadInput.New("invalid connectionId")
-       }
-       var projects req
-       err := errors.Convert(mapstructure.Decode(input.Body, &projects))
-       if err != nil {
-               return nil, errors.BadInput.Wrap(err, "decoding Bamboo project 
error")
-       }
-       keeper := make(map[string]struct{})
-       for _, project := range projects.Data {
-               if _, ok := keeper[project.ProjectKey]; ok {
-                       return nil, errors.BadInput.New("duplicated item")
-               } else {
-                       keeper[project.ProjectKey] = struct{}{}
-               }
-               project.ConnectionId = connectionId
-               err = verifyProject(project)
-               if err != nil {
-                       return nil, err
-               }
-       }
-       err = basicRes.GetDal().CreateOrUpdate(projects.Data)
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "error on saving 
BambooProject")
-       }
-       return &plugin.ApiResourceOutput{Body: projects.Data, Status: 
http.StatusOK}, nil
+       return scopeHelper.Put(input)
 }
 
 // UpdateScope patch to bamboo project
@@ -87,35 +52,14 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Tags plugins/bamboo
 // @Accept application/json
 // @Param connectionId path int false "connection ID"
-// @Param projectKey path int false "project ID"
+// @Param scopeId path int false "project ID"
 // @Param scope body models.BambooProject true "json"
 // @Success 200  {object} models.BambooProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bamboo/connections/{connectionId}/scopes/{projectKey} 
[PATCH]
+// @Router /plugins/bamboo/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       connectionId, projectKey := extractParam(input.Params)
-       if connectionId == 0 || projectKey == "" {
-               return nil, errors.BadInput.New("invalid path params")
-       }
-       var project models.BambooProject
-       err := basicRes.GetDal().First(&project, dal.Where("connection_id = ? 
AND project_key = ?", connectionId, projectKey))
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "getting BambooProject 
error")
-       }
-       err = api.DecodeMapStruct(input.Body, &project)
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "patch bamboo project 
error")
-       }
-       err = verifyProject(&project)
-       if err != nil {
-               return nil, err
-       }
-       err = basicRes.GetDal().Update(project)
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "error on saving 
BambooProject")
-       }
-       return &plugin.ApiResourceOutput{Body: project, Status: http.StatusOK}, 
nil
+       return scopeHelper.Update(input, "project_key")
 }
 
 // GetScopeList get Bamboo projects
@@ -123,43 +67,12 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Description get Bamboo projects
 // @Tags plugins/bamboo
 // @Param connectionId path int false "connection ID"
-// @Success 200  {object} []apiProject
+// @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scopes/ [GET]
 func GetScopeList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var projects []models.BambooProject
-       connectionId, _ := extractParam(input.Params)
-       if connectionId == 0 {
-               return nil, errors.BadInput.New("invalid path params")
-       }
-       limit, offset := api.GetLimitOffset(input.Query, "pageSize", "page")
-       err := basicRes.GetDal().All(&projects, dal.Where("connection_id = ?", 
connectionId), dal.Limit(limit), dal.Offset(offset))
-       if err != nil {
-               return nil, err
-       }
-       var ruleIds []uint64
-       for _, proj := range projects {
-               if proj.TransformationRuleId > 0 {
-                       ruleIds = append(ruleIds, proj.TransformationRuleId)
-               }
-       }
-       var rules []models.BambooTransformationRule
-       if len(ruleIds) > 0 {
-               err = basicRes.GetDal().All(&rules, dal.Where("id IN (?)", 
ruleIds))
-               if err != nil {
-                       return nil, err
-               }
-       }
-       names := make(map[uint64]string)
-       for _, rule := range rules {
-               names[rule.ID] = rule.Name
-       }
-       var apiProjects []apiProject
-       for _, proj := range projects {
-               apiProjects = append(apiProjects, apiProject{proj, 
names[proj.TransformationRuleId]})
-       }
-       return &plugin.ApiResourceOutput{Body: apiProjects, Status: 
http.StatusOK}, nil
+       return scopeHelper.GetScopeList(input)
 }
 
 // GetScope get one Bamboo project
@@ -167,71 +80,13 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Description get one Bamboo project
 // @Tags plugins/bamboo
 // @Param connectionId path int false "connection ID"
-// @Param projectKey path int false "project ID"
+// @Param scopeId path int false "project ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
-// @Success 200  {object} apiProject
+// @Success 200  {object} ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bamboo/connections/{connectionId}/scopes/{projectKey} [GET]
+// @Router /plugins/bamboo/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var project models.BambooProject
-       connectionId, projectKey := extractParam(input.Params)
-       if connectionId == 0 || projectKey == "" {
-               return nil, errors.BadInput.New("invalid path params")
-       }
-       db := basicRes.GetDal()
-       err := db.First(&project, dal.Where("connection_id = ? AND project_key 
= ?", connectionId, projectKey))
-       if err != nil && db.IsErrorNotFound(err) {
-               var scope models.BambooProject
-               connection := &models.BambooConnection{}
-               err = connectionHelper.First(connection, input.Params)
-               if err != nil {
-                       return nil, err
-               }
-               apiClient, err := 
api.NewApiClientFromConnection(context.TODO(), basicRes, connection)
-               if err != nil {
-                       return nil, err
-               }
-
-               apiProject, err := GetApiProject(projectKey, apiClient)
-               if err != nil {
-                       return nil, err
-               }
-
-               scope.Convert(apiProject)
-               scope.ConnectionId = connectionId
-               err = db.CreateIfNotExist(&scope)
-               if err != nil {
-                       return nil, err
-               }
-               return nil, errors.NotFound.New("record not found")
-       } else if err != nil {
-               return nil, err
-       }
-
-       var rule models.BambooTransformationRule
-       if project.TransformationRuleId > 0 {
-               err = basicRes.GetDal().First(&rule, dal.Where("id = ?", 
project.TransformationRuleId))
-               if err != nil {
-                       return nil, err
-               }
-       }
-       return &plugin.ApiResourceOutput{Body: apiProject{project, rule.Name}, 
Status: http.StatusOK}, nil
-}
-
-func extractParam(params map[string]string) (uint64, string) {
-       connectionId, _ := strconv.ParseUint(params["connectionId"], 10, 64)
-       projectKey := params["projectKey"]
-       return connectionId, projectKey
-}
-
-func verifyProject(project *models.BambooProject) errors.Error {
-       if project.ConnectionId == 0 {
-               return errors.BadInput.New("invalid connectionId")
-       }
-       if project.ProjectKey == "" {
-               return errors.BadInput.New("invalid projectKey")
-       }
-       return nil
+       return scopeHelper.GetScope(input, "project_key")
 }
diff --git a/backend/plugins/bamboo/impl/impl.go 
b/backend/plugins/bamboo/impl/impl.go
index 50de2945b..f5612f6cd 100644
--- a/backend/plugins/bamboo/impl/impl.go
+++ b/backend/plugins/bamboo/impl/impl.go
@@ -213,7 +213,7 @@ func (p Bamboo) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                "connections/:connectionId/search-remote-scopes": {
                        "GET": api.SearchRemoteScopes,
                },
-               "connections/:connectionId/scopes/:projectKey": {
+               "connections/:connectionId/scopes/:scopeId": {
                        "GET":   api.GetScope,
                        "PATCH": api.UpdateScope,
                },
diff --git a/backend/plugins/bamboo/models/project.go 
b/backend/plugins/bamboo/models/project.go
index 89e7caa4b..279b996e3 100644
--- a/backend/plugins/bamboo/models/project.go
+++ b/backend/plugins/bamboo/models/project.go
@@ -24,8 +24,8 @@ import (
 )
 
 type BambooProject struct {
-       ConnectionId         uint64 `json:"connectionId" 
mapstructure:"connectionId" gorm:"primaryKey"`
-       ProjectKey           string `json:"projectKey" 
gorm:"primaryKey;type:varchar(256)"`
+       ConnectionId         uint64 `json:"connectionId" 
mapstructure:"connectionId" validate:"required" gorm:"primaryKey"`
+       ProjectKey           string `json:"projectKey" 
gorm:"primaryKey;type:varchar(256)" validate:"required"`
        TransformationRuleId uint64 `json:"transformationRuleId,omitempty" 
mapstructure:"transformationRuleId"`
        Name                 string `json:"name" gorm:"index;type:varchar(256)"`
        Description          string `json:"description"`
diff --git a/backend/plugins/bitbucket/api/init.go 
b/backend/plugins/bitbucket/api/init.go
index d92c2b334..a91525bd4 100644
--- a/backend/plugins/bitbucket/api/init.go
+++ b/backend/plugins/bitbucket/api/init.go
@@ -20,11 +20,13 @@ package api
 import (
        "github.com/apache/incubator-devlake/core/context"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/plugins/bitbucket/models"
        "github.com/go-playground/validator/v10"
 )
 
 var vld *validator.Validate
 var connectionHelper *api.ConnectionApiHelper
+var scopeHelper *api.ScopeApiHelper[models.BitbucketConnection, 
models.BitbucketRepo, models.BitbucketTransformationRule]
 var basicRes context.BasicRes
 
 func Init(br context.BasicRes) {
@@ -34,4 +36,9 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
+       scopeHelper = api.NewScopeHelper[models.BitbucketConnection, 
models.BitbucketRepo, models.BitbucketTransformationRule](
+               basicRes,
+               vld,
+               connectionHelper,
+       )
 }
diff --git a/backend/plugins/bitbucket/api/remote.go 
b/backend/plugins/bitbucket/api/remote.go
index 9fc116842..49f85b8a7 100644
--- a/backend/plugins/bitbucket/api/remote.go
+++ b/backend/plugins/bitbucket/api/remote.go
@@ -361,3 +361,8 @@ func GetQueryFromPageData(pageData *PageData) (url.Values, 
errors.Error) {
        query.Set("pagelen", fmt.Sprintf("%v", pageData.PerPage))
        return query, nil
 }
+func extractParam(params map[string]string) (uint64, string) {
+       connectionId, _ := strconv.ParseUint(params["connectionId"], 10, 64)
+       fullName := strings.TrimLeft(params["repoId"], "/")
+       return connectionId, fullName
+}
diff --git a/backend/plugins/bitbucket/api/scope.go 
b/backend/plugins/bitbucket/api/scope.go
index e9053e56c..496def2e9 100644
--- a/backend/plugins/bitbucket/api/scope.go
+++ b/backend/plugins/bitbucket/api/scope.go
@@ -18,25 +18,18 @@ limitations under the License.
 package api
 
 import (
-       "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
        "github.com/apache/incubator-devlake/plugins/bitbucket/models"
-       "github.com/mitchellh/mapstructure"
-       "net/http"
-       "strconv"
-       "strings"
 )
 
-type apiRepo struct {
+type ScopeRes struct {
        models.BitbucketRepo
        TransformationRuleName string `json:"transformationRuleName,omitempty"`
 }
 
-type req struct {
-       Data []*models.BitbucketRepo `json:"data"`
-}
+type ScopeReq api.ScopeReq[models.BitbucketRepo]
 
 // PutScope create or update repo
 // @Summary create or update repo
@@ -44,39 +37,13 @@ type req struct {
 // @Tags plugins/bitbucket
 // @Accept application/json
 // @Param connectionId path int true "connection ID"
-// @Param scope body req true "json"
+// @Param scope body ScopeReq true "json"
 // @Success 200  {object} []models.BitbucketRepo
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bitbucket/connections/{connectionId}/scopes [PUT]
 func PutScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       connectionId, _ := extractParam(input.Params)
-       if connectionId == 0 {
-               return nil, errors.BadInput.New("invalid connectionId")
-       }
-       var repos req
-       err := errors.Convert(mapstructure.Decode(input.Body, &repos))
-       if err != nil {
-               return nil, errors.BadInput.Wrap(err, "decoding repo error")
-       }
-       keeper := make(map[string]struct{})
-       for _, repo := range repos.Data {
-               if _, ok := keeper[repo.BitbucketId]; ok {
-                       return nil, errors.BadInput.New("duplicated item")
-               } else {
-                       keeper[repo.BitbucketId] = struct{}{}
-               }
-               repo.ConnectionId = connectionId
-               err = verifyRepo(repo)
-               if err != nil {
-                       return nil, err
-               }
-       }
-       err = basicRes.GetDal().CreateOrUpdate(repos.Data)
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "error on saving 
BitbucketRepo")
-       }
-       return &plugin.ApiResourceOutput{Body: repos.Data, Status: 
http.StatusOK}, nil
+       return scopeHelper.Put(input)
 }
 
 // UpdateScope patch to repo
@@ -85,35 +52,14 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Tags plugins/bitbucket
 // @Accept application/json
 // @Param connectionId path int true "connection ID"
-// @Param repoId path string true "repo ID"
+// @Param scopeId path string true "repo ID"
 // @Param scope body models.BitbucketRepo true "json"
 // @Success 200  {object} models.BitbucketRepo
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bitbucket/connections/{connectionId}/scopes/{repoId} 
[PATCH]
+// @Router /plugins/bitbucket/connections/{connectionId}/scopes/{scopeId} 
[PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       connectionId, repoId := extractParam(input.Params)
-       if connectionId == 0 || repoId == "" {
-               return nil, errors.BadInput.New("invalid connectionId or 
repoId")
-       }
-       var repo models.BitbucketRepo
-       err := basicRes.GetDal().First(&repo, dal.Where("connection_id = ? AND 
bitbucket_id = ?", connectionId, repoId))
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "getting Repo error")
-       }
-       err = api.DecodeMapStruct(input.Body, &repo)
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "patch repo error")
-       }
-       err = verifyRepo(&repo)
-       if err != nil {
-               return nil, err
-       }
-       err = basicRes.GetDal().Update(repo)
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "error on saving 
BitbucketRepo")
-       }
-       return &plugin.ApiResourceOutput{Body: repo, Status: http.StatusOK}, nil
+       return scopeHelper.Update(input, "bitbucket_id")
 }
 
 // GetScopeList get repos
@@ -123,43 +69,12 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Param connectionId path int true "connection ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
-// @Success 200  {object} []apiRepo
+// @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bitbucket/connections/{connectionId}/scopes/ [GET]
 func GetScopeList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var repos []models.BitbucketRepo
-       connectionId, _ := extractParam(input.Params)
-       if connectionId == 0 {
-               return nil, errors.BadInput.New("invalid path params")
-       }
-       limit, offset := api.GetLimitOffset(input.Query, "pageSize", "page")
-       err := basicRes.GetDal().All(&repos, dal.Where("connection_id = ?", 
connectionId), dal.Limit(limit), dal.Offset(offset))
-       if err != nil {
-               return nil, err
-       }
-       var ruleIds []uint64
-       for _, repo := range repos {
-               if repo.TransformationRuleId > 0 {
-                       ruleIds = append(ruleIds, repo.TransformationRuleId)
-               }
-       }
-       var rules []models.BitbucketTransformationRule
-       if len(ruleIds) > 0 {
-               err = basicRes.GetDal().All(&rules, dal.Where("id IN (?)", 
ruleIds))
-               if err != nil {
-                       return nil, err
-               }
-       }
-       names := make(map[uint64]string)
-       for _, rule := range rules {
-               names[rule.ID] = rule.Name
-       }
-       var apiRepos []apiRepo
-       for _, repo := range repos {
-               apiRepos = append(apiRepos, apiRepo{repo, 
names[repo.TransformationRuleId]})
-       }
-       return &plugin.ApiResourceOutput{Body: apiRepos, Status: 
http.StatusOK}, nil
+       return scopeHelper.GetScopeList(input)
 }
 
 // GetScope get one repo
@@ -167,47 +82,11 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Description get one repo
 // @Tags plugins/bitbucket
 // @Param connectionId path int true "connection ID"
-// @Param repoId path string true "repo ID"
-// @Success 200  {object} apiRepo
+// @Param scopeId path string true "repo ID"
+// @Success 200  {object} ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bitbucket/connections/{connectionId}/scopes/{repoId} [GET]
+// @Router /plugins/bitbucket/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var repo models.BitbucketRepo
-       connectionId, repoId := extractParam(input.Params)
-       if connectionId == 0 || repoId == "" {
-               return nil, errors.BadInput.New("invalid connectionId or 
repoId")
-       }
-       db := basicRes.GetDal()
-       err := db.First(&repo, dal.Where("connection_id = ? AND bitbucket_id = 
?", connectionId, repoId))
-       if db.IsErrorNotFound(err) {
-               return nil, errors.NotFound.New("record not found")
-       }
-       if err != nil {
-               return nil, err
-       }
-       var rule models.BitbucketTransformationRule
-       if repo.TransformationRuleId > 0 {
-               err = basicRes.GetDal().First(&rule, dal.Where("id = ?", 
repo.TransformationRuleId))
-               if err != nil {
-                       return nil, err
-               }
-       }
-       return &plugin.ApiResourceOutput{Body: apiRepo{repo, rule.Name}, 
Status: http.StatusOK}, nil
-}
-
-func extractParam(params map[string]string) (uint64, string) {
-       connectionId, _ := strconv.ParseUint(params["connectionId"], 10, 64)
-       fullName := strings.TrimLeft(params["repoId"], "/")
-       return connectionId, fullName
-}
-
-func verifyRepo(repo *models.BitbucketRepo) errors.Error {
-       if repo.ConnectionId == 0 {
-               return errors.BadInput.New("invalid connectionId")
-       }
-       if repo.BitbucketId == `` {
-               return errors.BadInput.New("invalid bitbucket ID or full name")
-       }
-       return nil
+       return scopeHelper.GetScope(input, "bitbucket_id")
 }
diff --git a/backend/plugins/bitbucket/impl/impl.go 
b/backend/plugins/bitbucket/impl/impl.go
index 927667108..50b2d19ca 100644
--- a/backend/plugins/bitbucket/impl/impl.go
+++ b/backend/plugins/bitbucket/impl/impl.go
@@ -193,7 +193,7 @@ func (p Bitbucket) ApiResources() 
map[string]map[string]plugin.ApiResourceHandle
                        "DELETE": api.DeleteConnection,
                        "GET":    api.GetConnection,
                },
-               "connections/:connectionId/scopes/*repoId": {
+               "connections/:connectionId/scopes/*scopeId": {
                        "GET":   api.GetScope,
                        "PATCH": api.UpdateScope,
                },
diff --git a/backend/plugins/bitbucket/models/repo.go 
b/backend/plugins/bitbucket/models/repo.go
index 249651a7b..417ebe064 100644
--- a/backend/plugins/bitbucket/models/repo.go
+++ b/backend/plugins/bitbucket/models/repo.go
@@ -23,8 +23,8 @@ import (
 )
 
 type BitbucketRepo struct {
-       ConnectionId         uint64     `json:"connectionId" gorm:"primaryKey" 
mapstructure:"connectionId,omitempty"`
-       BitbucketId          string     `json:"bitbucketId" 
gorm:"primaryKey;type:varchar(255)" mapstructure:"bitbucketId"`
+       ConnectionId         uint64     `json:"connectionId" gorm:"primaryKey" 
validate:"required" mapstructure:"connectionId,omitempty"`
+       BitbucketId          string     `json:"bitbucketId" 
gorm:"primaryKey;type:varchar(255)" validate:"required" 
mapstructure:"bitbucketId"`
        Name                 string     `json:"name" gorm:"type:varchar(255)" 
mapstructure:"name,omitempty"`
        HTMLUrl              string     `json:"HTMLUrl" 
gorm:"type:varchar(255)" mapstructure:"HTMLUrl,omitempty"`
        Description          string     `json:"description" 
mapstructure:"description,omitempty"`
diff --git a/backend/plugins/github/api/scope.go 
b/backend/plugins/github/api/scope.go
index d9ba2499a..1225e9edd 100644
--- a/backend/plugins/github/api/scope.go
+++ b/backend/plugins/github/api/scope.go
@@ -20,6 +20,7 @@ package api
 import (
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
+       "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
        "github.com/apache/incubator-devlake/plugins/github/models"
 )
 
@@ -28,13 +29,15 @@ type ScopeRes struct {
        TransformationRuleName string `json:"transformationRuleName,omitempty"`
 }
 
+type ScopeReq api.ScopeReq[models.GithubRepo]
+
 // PutScope create or update github repo
 // @Summary create or update github repo
 // @Description Create or update github repo
 // @Tags plugins/github
 // @Accept application/json
 // @Param connectionId path int true "connection ID"
-// @Param scope body req true "json"
+// @Param scope body ScopeReq true "json"
 // @Success 200  {object} []models.GithubRepo
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
diff --git a/backend/plugins/github/models/repo.go 
b/backend/plugins/github/models/repo.go
index 045437180..9eda5e118 100644
--- a/backend/plugins/github/models/repo.go
+++ b/backend/plugins/github/models/repo.go
@@ -36,7 +36,7 @@ type GithubRepo struct {
        CloneUrl             string     `json:"cloneUrl" 
gorm:"type:varchar(255)" mapstructure:"cloneUrl,omitempty"`
        CreatedDate          *time.Time `json:"createdDate" mapstructure:"-"`
        UpdatedDate          *time.Time `json:"updatedDate" mapstructure:"-"`
-       common.NoPKModel     `json:"-" mapstructure:",squash"`
+       common.NoPKModel     `json:"-" mapstructure:"-"`
 }
 
 func (GithubRepo) TableName() string {
diff --git a/backend/plugins/gitlab/api/scope.go 
b/backend/plugins/gitlab/api/scope.go
index 9f3da5b67..a40ccf6f0 100644
--- a/backend/plugins/gitlab/api/scope.go
+++ b/backend/plugins/gitlab/api/scope.go
@@ -20,6 +20,7 @@ package api
 import (
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
+       "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
        "github.com/apache/incubator-devlake/plugins/gitlab/models"
 )
 
@@ -28,13 +29,15 @@ type ScopeRes struct {
        TransformationRuleName string `json:"transformationRuleName,omitempty"`
 }
 
+type ScopeReq api.ScopeReq[models.GitlabProject]
+
 // PutScope create or update gitlab project
 // @Summary create or update gitlab project
 // @Description Create or update gitlab project
 // @Tags plugins/gitlab
 // @Accept application/json
 // @Param connectionId path int false "connection ID"
-// @Param scope body req true "json"
+// @Param scope body ScopeReq true "json"
 // @Success 200  {object} []models.GitlabProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
@@ -49,12 +52,12 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Tags plugins/gitlab
 // @Accept application/json
 // @Param connectionId path int false "connection ID"
-// @Param projectId path int false "project ID"
+// @Param scopeId path int false "project ID"
 // @Param scope body models.GitlabProject true "json"
 // @Success 200  {object} models.GitlabProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/gitlab/connections/{connectionId}/scopes/{projectId} 
[PATCH]
+// @Router /plugins/gitlab/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
        return scopeHelper.Update(input, "gitlab_id")
 }
@@ -77,13 +80,13 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Description get one Gitlab project
 // @Tags plugins/gitlab
 // @Param connectionId path int false "connection ID"
-// @Param projectId path int false "project ID"
+// @Param scopeId path int false "project ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
 // @Success 200  {object} ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/gitlab/connections/{connectionId}/scopes/{projectId} [GET]
+// @Router /plugins/gitlab/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
        return scopeHelper.GetScope(input, "gitlab_id")
 }
diff --git a/backend/plugins/gitlab/impl/impl.go 
b/backend/plugins/gitlab/impl/impl.go
index a5949aa49..99c2dd968 100644
--- a/backend/plugins/gitlab/impl/impl.go
+++ b/backend/plugins/gitlab/impl/impl.go
@@ -246,7 +246,7 @@ func (p Gitlab) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "DELETE": api.DeleteConnection,
                        "GET":    api.GetConnection,
                },
-               "connections/:connectionId/scopes/:projectId": {
+               "connections/:connectionId/scopes/:scopeId": {
                        "GET":   api.GetScope,
                        "PATCH": api.UpdateScope,
                },
diff --git a/backend/plugins/gitlab/models/project.go 
b/backend/plugins/gitlab/models/project.go
index 91db090ff..41c6855f5 100644
--- a/backend/plugins/gitlab/models/project.go
+++ b/backend/plugins/gitlab/models/project.go
@@ -42,7 +42,7 @@ type GitlabProject struct {
 
        CreatedDate      time.Time  `json:"createdDate" mapstructure:"-"`
        UpdatedDate      *time.Time `json:"updatedDate" mapstructure:"-"`
-       common.NoPKModel `json:"-" mapstructure:",squash"`
+       common.NoPKModel `json:"-" mapstructure:"-"`
 }
 
 func (GitlabProject) TableName() string {

Reply via email to