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

klesh 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 fb6c5e2e6 feat: add project to support resource classification (#3679)
fb6c5e2e6 is described below

commit fb6c5e2e6131cd8c868183430b57a84e9c89c827
Author: mappjzc <[email protected]>
AuthorDate: Wed Nov 16 20:51:36 2022 +0800

    feat: add project to support resource classification (#3679)
    
    * feat: add project api and tables
    
    Add GET POST PATCH Project
    Add GET POST PATCH ProjectMetric
    Add tables
    
    Nddtfjiang <[email protected]>
    
    * feat: add plugin metric interface
    
    Add PluginMetric for all plugin witch need it.
    
    Nddtfjiang <[email protected]>
    
    * fix: fix description for review
    
    Nddtfjiang <[email protected]>
    
    * fix: fix error for test
    
    fix some error for testing.
    
    Nddtfjiang <[email protected]>
    
    * feat: fix for review
    
    move out the GetTablesInfo from PluginMetric to PluginModel.
    Changed api ProjectMetric api path.
    add metric data in api plugin.
    
    Nddtfjiang <[email protected]>
    
    * feat: uninstall metric interface implementation
    
    Uninstall PluginMetric interface implementation for unuse plugins.
    
    Nddtfjiang <[email protected]>
    
    * fix: fix for review
    
    fix for review
    
    Nddtfjiang <[email protected]>
    
    * fix: fix for sepll review
    
    fix for sepll review
    
    Nddtfjiang <[email protected]>
---
 api/plugininfo/plugininifo.go                      |  63 +++++-
 api/project/project.go                             | 243 +++++++++++++++++++++
 api/router.go                                      |  16 ++
 .../20221109_add_project_tables.go                 |  69 ++++++
 models/migrationscripts/register.go                |   1 +
 plugins/core/plugin_model.go => models/project.go  |  28 ++-
 plugins/ae/impl/impl.go                            |   2 +
 plugins/azure/impl/impl.go                         |  10 +
 plugins/bitbucket/impl/impl.go                     |   1 +
 plugins/core/{plugin_model.go => plugin_metric.go} |  24 +-
 plugins/core/plugin_model.go                       |   1 +
 plugins/customize/impl/impl.go                     |   5 +
 plugins/dbt/dbt.go                                 |   5 +-
 plugins/dora/impl/impl.go                          |  30 +++
 plugins/feishu/impl/impl.go                        |   1 +
 plugins/gitee/impl/impl.go                         |   1 +
 plugins/gitextractor/gitextractor.go               |   1 +
 plugins/github/impl/impl.go                        |   1 +
 plugins/github_graphql/plugin_main.go              |  12 +-
 plugins/gitlab/impl/impl.go                        |   2 +-
 plugins/icla/plugin_main.go                        |  13 +-
 plugins/jenkins/impl/impl.go                       |   1 +
 plugins/jira/impl/impl.go                          |   5 +-
 plugins/org/impl/impl.go                           |   1 +
 plugins/refdiff/refdiff.go                         |  18 ++
 plugins/starrocks/starrocks.go                     |   5 +
 plugins/tapd/impl/impl.go                          |   1 +
 plugins/webhook/impl/impl.go                       |   5 +
 services/project.go                                | 159 ++++++++++++++
 services/project_helper.go                         | 168 ++++++++++++++
 30 files changed, 868 insertions(+), 24 deletions(-)

diff --git a/api/plugininfo/plugininifo.go b/api/plugininfo/plugininifo.go
index b36803568..4868f883f 100644
--- a/api/plugininfo/plugininifo.go
+++ b/api/plugininfo/plugininifo.go
@@ -18,11 +18,13 @@ limitations under the License.
 package plugininfo
 
 import (
-       "github.com/apache/incubator-devlake/errors"
+       "fmt"
        "net/http"
        "reflect"
        "sync"
 
+       "github.com/apache/incubator-devlake/errors"
+
        "github.com/apache/incubator-devlake/api/shared"
        "github.com/apache/incubator-devlake/models/domainlayer/domaininfo"
        "github.com/apache/incubator-devlake/plugins/core"
@@ -67,6 +69,19 @@ type TableInfos struct {
        Error     *string      `json:"error"`
 }
 
+type PluginMetric struct {
+       RequiredDataEntities []map[string]interface{} 
`json:"requiredDataEntities"`
+       RunAfter             []string                 `json:"runAfter"`
+       IsProjectMetric      bool                     `json:"isProjectMetric"`
+}
+
+type PluginMeta struct {
+       Plugin string       `json:"plugin"`
+       Metric PluginMetric `json:"metric"`
+}
+
+type PluginMetas []PluginMeta
+
 func NewTableInfos(table core.Tabler) *TableInfos {
        tableInfos := &TableInfos{
                TableName: table.TableName(),
@@ -178,3 +193,49 @@ func Get(c *gin.Context) {
 
        shared.ApiOutputSuccess(c, info, http.StatusOK)
 }
+
+// @Get name list of plugins
+// @Description GET /plugins
+// @Description RETURN SAMPLE
+// @Tags framework/plugins
+// @Success 200  {object} PluginMetas
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Router /plugins [get]
+func GetPluginMetas(c *gin.Context) {
+       var metas PluginMetas
+       err := core.TraversalPlugin(func(name string, plugin core.PluginMeta) 
errors.Error {
+               pluginMeta := PluginMeta{
+                       Plugin: name,
+               }
+
+               // if this plugin has the plugin task info
+               if pt, ok := plugin.(core.PluginMetric); ok {
+                       var err1 errors.Error
+                       metric := PluginMetric{}
+
+                       metric.RequiredDataEntities, err1 = 
pt.RequiredDataEntities()
+                       if err1 != nil {
+                               return errors.Default.Wrap(err1, 
fmt.Sprintf("failed to get RequiredDataEntities on plugin %s", name))
+                       }
+
+                       metric.RunAfter, err1 = pt.RunAfter()
+                       if err1 != nil {
+                               return errors.Default.Wrap(err1, 
fmt.Sprintf("failed to get RunAfter on plugin %s", name))
+                       }
+
+                       metric.IsProjectMetric = pt.IsProjectMetric()
+
+                       pluginMeta.Metric = metric
+               }
+
+               metas = append(metas, pluginMeta)
+
+               return nil
+       })
+
+       if err != nil {
+               shared.ApiOutputError(c, errors.Default.Wrap(err, "error 
getting plugin info of plugins"))
+       }
+
+       shared.ApiOutputSuccess(c, metas, http.StatusOK)
+}
diff --git a/api/project/project.go b/api/project/project.go
new file mode 100644
index 000000000..a684b70c4
--- /dev/null
+++ b/api/project/project.go
@@ -0,0 +1,243 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 project
+
+import (
+       "net/http"
+
+       "github.com/apache/incubator-devlake/api/shared"
+       "github.com/apache/incubator-devlake/errors"
+       "github.com/apache/incubator-devlake/models"
+       "github.com/apache/incubator-devlake/services"
+       "github.com/gin-gonic/gin"
+)
+
+type PaginatedProjects struct {
+       Projects []*models.Project
+       Count    int64
+}
+
+// @Summary Create and run a new project
+// @Description Create and run a new project
+// @Tags framework/projects
+// @Accept application/json
+// @Param project body models.Project true "json"
+// @Success 200  {object} models.Project
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /projects/:projectName [get]
+func GetProject(c *gin.Context) {
+       projectName := c.Param("projectName")
+
+       project, err := services.GetProject(projectName)
+       if err != nil {
+               shared.ApiOutputError(c, errors.Default.Wrap(err, "error 
getting project"))
+               return
+       }
+
+       shared.ApiOutputSuccess(c, project, http.StatusOK)
+}
+
+// @Summary Get list of projects
+// @Description GET /projects?page=1&pagesize=10
+// @Tags framework/projects
+// @Param page query int true "query"
+// @Param pagesize query int true "query"
+// @Success 200  {object} PaginatedProjects
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /projects [get]
+func GetProjects(c *gin.Context) {
+       var query services.ProjectQuery
+       err := c.ShouldBindQuery(&query)
+       if err != nil {
+               shared.ApiOutputError(c, errors.BadInput.Wrap(err, 
shared.BadRequestBody))
+               return
+       }
+       projects, count, err := services.GetProjects(&query)
+       if err != nil {
+               shared.ApiOutputAbort(c, errors.Default.Wrap(err, "error 
getting projects"))
+               return
+       }
+       shared.ApiOutputSuccess(c, PaginatedProjects{
+               Projects: projects,
+               Count:    count,
+       }, http.StatusOK)
+}
+
+// @Summary Create a new project
+// @Description Create a new project
+// @Tags framework/projects
+// @Accept application/json
+// @Param project body models.Project true "json"
+// @Success 200  {object} models.Project
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /projects [post]
+func PostProject(c *gin.Context) {
+       project := &models.Project{}
+
+       err := c.ShouldBind(project)
+       if err != nil {
+               shared.ApiOutputError(c, errors.BadInput.Wrap(err, 
shared.BadRequestBody))
+               return
+       }
+
+       err = services.CreateProject(project)
+       if err != nil {
+               shared.ApiOutputError(c, errors.Default.Wrap(err, "error 
creating project"))
+               return
+       }
+
+       shared.ApiOutputSuccess(c, project, http.StatusCreated)
+}
+
+// @Summary Patch a project
+// @Description Patch a project
+// @Tags framework/projects
+// @Accept application/json
+// @Param project body models.Project true "json"
+// @Success 200  {object} models.Project
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /projects/:projectName [patch]
+func PatchProject(c *gin.Context) {
+       projectName := c.Param("projectName")
+
+       var body map[string]interface{}
+       err := c.ShouldBind(&body)
+       if err != nil {
+               shared.ApiOutputError(c, errors.BadInput.Wrap(err, 
shared.BadRequestBody))
+               return
+       }
+
+       project, err := services.PatchProject(projectName, body)
+       if err != nil {
+               shared.ApiOutputError(c, errors.Default.Wrap(err, "error patch 
project"))
+               return
+       }
+
+       shared.ApiOutputSuccess(c, project, http.StatusCreated)
+}
+
+// @Cancel a project
+// @Description Cancel a project
+// @Tags framework/projects
+// @Success 200
+// @Failure 400  {string} er2rcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /projects/:projectName [delete]
+//func DeleteProject(c *gin.Context) {
+//}
+
+// @Summary Get a ProjectMetrics
+// @Description Get a ProjectMetrics
+// @Tags framework/ProjectMetrics
+// @Param page query int true "query"
+// @Param pagesize query int true "query"
+// @Success 200  {object} models.ProjectMetric
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /projects/:projectName/metrics/:pluginName [get]
+func GetProjectMetrics(c *gin.Context) {
+       projectName := c.Param("projectName")
+       pluginName := c.Param("pluginName")
+
+       projectMetric, err := services.GetProjectMetrics(projectName, 
pluginName)
+       if err != nil {
+               shared.ApiOutputError(c, errors.Default.Wrap(err, "error 
getting project metric"))
+               return
+       }
+
+       shared.ApiOutputSuccess(c, projectMetric, http.StatusOK)
+}
+
+// @Summary Create a new ProjectMetrics
+// @Description Create  a new ProjectMetrics
+// @Tags framework/ProjectMetrics
+// @Accept application/json
+// @Param project body models.Project true "json"
+// @Success 200  {object} models.ProjectMetric
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /projects/:projectName/metrics [post]
+func PostProjectMetrics(c *gin.Context) {
+       projectMetric := &models.ProjectMetric{}
+
+       projectName := c.Param("projectName")
+
+       _, err1 := services.GetProject(projectName)
+       if err1 != nil {
+               shared.ApiOutputError(c, errors.BadInput.Wrap(err1, 
shared.BadRequestBody))
+               return
+       }
+
+       err := c.ShouldBind(projectMetric)
+       if err != nil {
+               shared.ApiOutputError(c, errors.BadInput.Wrap(err, 
shared.BadRequestBody))
+               return
+       }
+
+       projectMetric.ProjectName = projectName
+       err = services.CreateProjectMetric(projectMetric)
+       if err != nil {
+               shared.ApiOutputError(c, errors.Default.Wrap(err, "error 
creating project"))
+               return
+       }
+
+       shared.ApiOutputSuccess(c, projectMetric, http.StatusCreated)
+}
+
+// @Summary Patch a ProjectMetrics
+// @Description Patch a ProjectMetrics
+// @Tags framework/ProjectMetrics
+// @Accept application/json
+// @Param ProjectMetrics body models.ProjectMetric true "json"
+// @Success 200  {object} models.ProjectMetric
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /projects/:projectName/metrics/:pluginName  [patch]
+func PatchProjectMetrics(c *gin.Context) {
+       projectName := c.Param("projectName")
+       pluginName := c.Param("pluginName")
+
+       var body map[string]interface{}
+       err := c.ShouldBind(&body)
+       if err != nil {
+               shared.ApiOutputError(c, errors.BadInput.Wrap(err, 
shared.BadRequestBody))
+               return
+       }
+
+       projectMetric, err := services.PatchProjectMetric(projectName, 
pluginName, body)
+       if err != nil {
+               shared.ApiOutputError(c, errors.Default.Wrap(err, "error patch 
project"))
+               return
+       }
+
+       shared.ApiOutputSuccess(c, projectMetric, http.StatusCreated)
+}
+
+// @delete a ProjectMetrics
+// @Description delete a ProjectMetrics
+// @Tags framework/ProjectMetrics
+// @Success 200
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /project_metrics/:projectName/:pluginName [delete]
+//func DeleteProjectMetrics(c *gin.Context) {
+//}
diff --git a/api/router.go b/api/router.go
index 0122a185a..1c50f6236 100644
--- a/api/router.go
+++ b/api/router.go
@@ -27,6 +27,7 @@ import (
        "github.com/apache/incubator-devlake/api/ping"
        "github.com/apache/incubator-devlake/api/pipelines"
        "github.com/apache/incubator-devlake/api/plugininfo"
+       "github.com/apache/incubator-devlake/api/project"
        "github.com/apache/incubator-devlake/api/push"
        "github.com/apache/incubator-devlake/api/shared"
        "github.com/apache/incubator-devlake/api/task"
@@ -59,7 +60,22 @@ func RegisterRouter(r *gin.Engine) {
        r.POST("/push/:tableName", push.Post)
        r.GET("/domainlayer/repos", domainlayer.ReposIndex)
 
+       // plugin api
        r.GET("/plugininfo", plugininfo.Get)
+       r.GET("/plugins", plugininfo.GetPluginMetas)
+
+       // project api
+       r.GET("/projects/:projectName", project.GetProject)
+       r.PATCH("/projects/:projectName", project.PatchProject)
+       //r.DELETE("/projects/:projectName", project.DeleteProject)
+       r.POST("/projects", project.PostProject)
+       r.GET("/projects", project.GetProjects)
+
+       // project metric api
+       r.GET("/projects/:projectName/metrics/:pluginName", 
project.GetProjectMetrics)
+       r.PATCH("/projects/:projectName/metrics/:pluginName", 
project.PatchProjectMetrics)
+       //r.DELETE("/projects/:projectName/metrics/:pluginName", 
project.DeleteProjectMetrics)
+       r.POST("/projects/:projectName/metrics", project.PostProjectMetrics)
 
        // mount all api resources for all plugins
        pluginsApiResources, err := services.GetPluginsApiResources()
diff --git a/models/migrationscripts/20221109_add_project_tables.go 
b/models/migrationscripts/20221109_add_project_tables.go
new file mode 100644
index 000000000..1752ace1a
--- /dev/null
+++ b/models/migrationscripts/20221109_add_project_tables.go
@@ -0,0 +1,69 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 migrationscripts
+
+import (
+       "github.com/apache/incubator-devlake/errors"
+       "github.com/apache/incubator-devlake/helpers/migrationhelper"
+       "github.com/apache/incubator-devlake/models/migrationscripts/archived"
+       "github.com/apache/incubator-devlake/plugins/core"
+)
+
+var _ core.MigrationScript = (*addProjectTables)(nil)
+
+type addProjectTables struct{}
+
+type Project struct {
+       Name        string `gorm:"primaryKey;type:varchar(255)"`
+       Description string `gorm:"type:text"`
+
+       archived.NoPKModel
+}
+
+func (Project) TableName() string {
+       return "projects"
+}
+
+type ProjectMetric struct {
+       ProjectName  string `gorm:"primaryKey;type:varchar(255)"`
+       PluginName   string `gorm:"primaryKey;type:varchar(255)"`
+       PluginOption string `gorm:"type:text"`
+
+       archived.NoPKModel
+}
+
+func (ProjectMetric) TableName() string {
+       return "project_metrics"
+}
+
+func (script *addProjectTables) Up(basicRes core.BasicRes) errors.Error {
+       // To create multiple tables with migrationhelper
+       return migrationhelper.AutoMigrateTables(
+               basicRes,
+               &Project{},
+               &ProjectMetric{},
+       )
+}
+
+func (*addProjectTables) Version() uint64 {
+       return 20221109194734
+}
+
+func (*addProjectTables) Name() string {
+       return "add project tables"
+}
diff --git a/models/migrationscripts/register.go 
b/models/migrationscripts/register.go
index 7acf5e669..78a1f890e 100644
--- a/models/migrationscripts/register.go
+++ b/models/migrationscripts/register.go
@@ -56,5 +56,6 @@ func All() []core.MigrationScript {
                new(addCicdScope),
                new(addSkipOnFail),
                new(modifyCommitsDiffs),
+               new(addProjectTables),
        }
 }
diff --git a/plugins/core/plugin_model.go b/models/project.go
similarity index 56%
copy from plugins/core/plugin_model.go
copy to models/project.go
index 5ddec7f61..a9edfcc54 100644
--- a/plugins/core/plugin_model.go
+++ b/models/project.go
@@ -15,12 +15,30 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package core
+package models
 
-type Tabler interface {
-       TableName() string
+import "github.com/apache/incubator-devlake/models/common"
+
+type Project struct {
+       Name        string `gorm:"primaryKey;type:varchar(255)"`
+       Description string `gorm:"type:text"`
+
+       common.NoPKModel
+}
+
+func (Project) TableName() string {
+       return "projects"
+}
+
+type ProjectMetric struct {
+       ProjectName  string `gorm:"primaryKey;type:varchar(255)"`
+       PluginName   string `gorm:"primaryKey;type:varchar(255)"`
+       PluginOption string `gorm:"type:text"`
+       Enable       bool   `gorm:"type:boolean"`
+
+       common.NoPKModel
 }
 
-type PluginModel interface {
-       GetTablesInfo() []Tabler
+func (ProjectMetric) TableName() string {
+       return "project_metrics"
 }
diff --git a/plugins/ae/impl/impl.go b/plugins/ae/impl/impl.go
index da5c9bfde..3532720e3 100644
--- a/plugins/ae/impl/impl.go
+++ b/plugins/ae/impl/impl.go
@@ -35,6 +35,7 @@ var _ core.PluginMeta = (*AE)(nil)
 var _ core.PluginInit = (*AE)(nil)
 var _ core.PluginTask = (*AE)(nil)
 var _ core.PluginApi = (*AE)(nil)
+var _ core.PluginModel = (*AE)(nil)
 var _ core.PluginMigration = (*AE)(nil)
 var _ core.CloseablePluginTask = (*AE)(nil)
 
@@ -44,6 +45,7 @@ func (plugin AE) Init(config *viper.Viper, logger 
core.Logger, db *gorm.DB) erro
        api.Init(config, logger, db)
        return nil
 }
+
 func (plugin AE) GetTablesInfo() []core.Tabler {
        return []core.Tabler{
                &models.AECommit{},
diff --git a/plugins/azure/impl/impl.go b/plugins/azure/impl/impl.go
index 5b55d4884..29328c3a6 100644
--- a/plugins/azure/impl/impl.go
+++ b/plugins/azure/impl/impl.go
@@ -36,6 +36,7 @@ var _ core.PluginMeta = (*Azure)(nil)
 var _ core.PluginInit = (*Azure)(nil)
 var _ core.PluginTask = (*Azure)(nil)
 var _ core.PluginApi = (*Azure)(nil)
+var _ core.PluginModel = (*Azure)(nil)
 var _ core.CloseablePluginTask = (*Azure)(nil)
 var _ core.PluginMigration = (*Azure)(nil)
 
@@ -53,6 +54,15 @@ func (plugin Azure) Init(config *viper.Viper, logger 
core.Logger, db *gorm.DB) e
        return nil
 }
 
+func (plugin Azure) GetTablesInfo() []core.Tabler {
+       return []core.Tabler{
+               &models.AzureBuild{},
+               &models.AzureBuildDefinition{},
+               &models.AzureConnection{},
+               &models.AzureRepo{},
+       }
+}
+
 func (plugin Azure) SubTaskMetas() []core.SubTaskMeta {
        return []core.SubTaskMeta{
                tasks.CollectApiRepoMeta,
diff --git a/plugins/bitbucket/impl/impl.go b/plugins/bitbucket/impl/impl.go
index cfa6c7fb9..37e53edc9 100644
--- a/plugins/bitbucket/impl/impl.go
+++ b/plugins/bitbucket/impl/impl.go
@@ -35,6 +35,7 @@ var _ core.PluginMeta = (*Bitbucket)(nil)
 var _ core.PluginInit = (*Bitbucket)(nil)
 var _ core.PluginTask = (*Bitbucket)(nil)
 var _ core.PluginApi = (*Bitbucket)(nil)
+var _ core.PluginModel = (*Bitbucket)(nil)
 var _ core.PluginMigration = (*Bitbucket)(nil)
 var _ core.PluginBlueprintV100 = (*Bitbucket)(nil)
 var _ core.CloseablePluginTask = (*Bitbucket)(nil)
diff --git a/plugins/core/plugin_model.go b/plugins/core/plugin_metric.go
similarity index 51%
copy from plugins/core/plugin_model.go
copy to plugins/core/plugin_metric.go
index 5ddec7f61..2297051c8 100644
--- a/plugins/core/plugin_model.go
+++ b/plugins/core/plugin_metric.go
@@ -17,10 +17,24 @@ limitations under the License.
 
 package core
 
-type Tabler interface {
-       TableName() string
-}
+import (
+       "github.com/apache/incubator-devlake/errors"
+)
+
+type PluginMetric interface {
+       // returns a list of required data entities and expected features.
+       // [{ "model": "cicd_tasks", "requiredFields": {"column": "type", 
"execptedValue": "Deployment"}}, ...]
+       RequiredDataEntities() (data []map[string]interface{}, err errors.Error)
+
+       // returns if the metric depends on Project for calculation.
+       // Currently, only dora would return true.
+       IsProjectMetric() bool
+
+       // indicates which plugins must be executed before executing this one.
+       // declare a set of dependencies with this
+       RunAfter() ([]string, errors.Error)
 
-type PluginModel interface {
-       GetTablesInfo() []Tabler
+       // returns an empty pointer of the plugin setting struct.
+       // (no concrete usage at this point)
+       Settings() (p interface{})
 }
diff --git a/plugins/core/plugin_model.go b/plugins/core/plugin_model.go
index 5ddec7f61..5fdb7de06 100644
--- a/plugins/core/plugin_model.go
+++ b/plugins/core/plugin_model.go
@@ -22,5 +22,6 @@ type Tabler interface {
 }
 
 type PluginModel interface {
+       // This method returns all models of the current plugin
        GetTablesInfo() []Tabler
 }
diff --git a/plugins/customize/impl/impl.go b/plugins/customize/impl/impl.go
index edb526725..9f196714a 100644
--- a/plugins/customize/impl/impl.go
+++ b/plugins/customize/impl/impl.go
@@ -32,6 +32,7 @@ import (
 var _ core.PluginMeta = (*Customize)(nil)
 var _ core.PluginInit = (*Customize)(nil)
 var _ core.PluginApi = (*Customize)(nil)
+var _ core.PluginModel = (*Customize)(nil)
 
 type Customize struct {
        handlers *api.Handlers
@@ -43,6 +44,10 @@ func (plugin *Customize) Init(config *viper.Viper, logger 
core.Logger, db *gorm.
        return nil
 }
 
+func (plugin Customize) GetTablesInfo() []core.Tabler {
+       return []core.Tabler{}
+}
+
 func (plugin Customize) SubTaskMetas() []core.SubTaskMeta {
        return []core.SubTaskMeta{
                tasks.ExtractCustomizedFieldsMeta,
diff --git a/plugins/dbt/dbt.go b/plugins/dbt/dbt.go
index 1a5e4b7b3..00784fe78 100644
--- a/plugins/dbt/dbt.go
+++ b/plugins/dbt/dbt.go
@@ -28,8 +28,9 @@ import (
 )
 
 var (
-       _ core.PluginMeta = (*Dbt)(nil)
-       _ core.PluginTask = (*Dbt)(nil)
+       _ core.PluginMeta  = (*Dbt)(nil)
+       _ core.PluginTask  = (*Dbt)(nil)
+       _ core.PluginModel = (*Dbt)(nil)
 )
 
 type Dbt struct{}
diff --git a/plugins/dora/impl/impl.go b/plugins/dora/impl/impl.go
index aa13a6c70..394d4d9cc 100644
--- a/plugins/dora/impl/impl.go
+++ b/plugins/dora/impl/impl.go
@@ -32,6 +32,8 @@ import (
 var _ core.PluginMeta = (*Dora)(nil)
 var _ core.PluginInit = (*Dora)(nil)
 var _ core.PluginTask = (*Dora)(nil)
+var _ core.PluginModel = (*Dora)(nil)
+var _ core.PluginMetric = (*Dora)(nil)
 var _ core.CloseablePluginTask = (*Dora)(nil)
 var _ core.PluginMigration = (*Dora)(nil)
 
@@ -56,6 +58,34 @@ func (plugin Dora) Init(config *viper.Viper, logger 
core.Logger, db *gorm.DB) er
        return nil
 }
 
+func (plugin Dora) RequiredDataEntities() (data []map[string]interface{}, err 
errors.Error) {
+       return []map[string]interface{}{
+               {
+                       "model": "cicd_tasks",
+                       "requiredFields": map[string]string{
+                               "column":        "type",
+                               "execptedValue": "Deployment",
+                       },
+               },
+       }, nil
+}
+
+func (plugin Dora) GetTablesInfo() []core.Tabler {
+       return []core.Tabler{}
+}
+
+func (plugin Dora) IsProjectMetric() bool {
+       return true
+}
+
+func (plugin Dora) RunAfter() ([]string, errors.Error) {
+       return []string{}, nil
+}
+
+func (plugin Dora) Settings() interface{} {
+       return nil
+}
+
 func (plugin Dora) SubTaskMetas() []core.SubTaskMeta {
        // TODO add your sub task here
        return []core.SubTaskMeta{
diff --git a/plugins/feishu/impl/impl.go b/plugins/feishu/impl/impl.go
index 09ab960d3..98d61102d 100644
--- a/plugins/feishu/impl/impl.go
+++ b/plugins/feishu/impl/impl.go
@@ -37,6 +37,7 @@ var _ core.PluginMeta = (*Feishu)(nil)
 var _ core.PluginInit = (*Feishu)(nil)
 var _ core.PluginTask = (*Feishu)(nil)
 var _ core.PluginApi = (*Feishu)(nil)
+var _ core.PluginModel = (*Feishu)(nil)
 var _ core.PluginMigration = (*Feishu)(nil)
 var _ core.CloseablePluginTask = (*Feishu)(nil)
 
diff --git a/plugins/gitee/impl/impl.go b/plugins/gitee/impl/impl.go
index a8f5957a4..38d512788 100644
--- a/plugins/gitee/impl/impl.go
+++ b/plugins/gitee/impl/impl.go
@@ -36,6 +36,7 @@ var _ core.PluginMeta = (*Gitee)(nil)
 var _ core.PluginInit = (*Gitee)(nil)
 var _ core.PluginTask = (*Gitee)(nil)
 var _ core.PluginApi = (*Gitee)(nil)
+var _ core.PluginModel = (*Gitee)(nil)
 var _ core.PluginMigration = (*Gitee)(nil)
 var _ core.CloseablePluginTask = (*Gitee)(nil)
 
diff --git a/plugins/gitextractor/gitextractor.go 
b/plugins/gitextractor/gitextractor.go
index 5a3866e3d..cf73279f6 100644
--- a/plugins/gitextractor/gitextractor.go
+++ b/plugins/gitextractor/gitextractor.go
@@ -31,6 +31,7 @@ import (
 
 var _ core.PluginMeta = (*GitExtractor)(nil)
 var _ core.PluginTask = (*GitExtractor)(nil)
+var _ core.PluginModel = (*GitExtractor)(nil)
 
 type GitExtractor struct{}
 
diff --git a/plugins/github/impl/impl.go b/plugins/github/impl/impl.go
index 3091e085e..84ef2151b 100644
--- a/plugins/github/impl/impl.go
+++ b/plugins/github/impl/impl.go
@@ -36,6 +36,7 @@ var _ core.PluginMeta = (*Github)(nil)
 var _ core.PluginInit = (*Github)(nil)
 var _ core.PluginTask = (*Github)(nil)
 var _ core.PluginApi = (*Github)(nil)
+var _ core.PluginModel = (*Github)(nil)
 var _ core.PluginBlueprintV100 = (*Github)(nil)
 var _ core.CloseablePluginTask = (*Github)(nil)
 
diff --git a/plugins/github_graphql/plugin_main.go 
b/plugins/github_graphql/plugin_main.go
index ddce4e944..12e780e0f 100644
--- a/plugins/github_graphql/plugin_main.go
+++ b/plugins/github_graphql/plugin_main.go
@@ -20,6 +20,10 @@ package main
 import (
        "context"
        "fmt"
+       "reflect"
+       "strings"
+       "time"
+
        "github.com/apache/incubator-devlake/errors"
        "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/github/models"
@@ -32,9 +36,6 @@ import (
        "github.com/spf13/viper"
        "golang.org/x/oauth2"
        "gorm.io/gorm"
-       "reflect"
-       "strings"
-       "time"
 )
 
 // make sure interface is implemented
@@ -42,6 +43,7 @@ var _ core.PluginMeta = (*GithubGraphql)(nil)
 var _ core.PluginInit = (*GithubGraphql)(nil)
 var _ core.PluginTask = (*GithubGraphql)(nil)
 var _ core.PluginApi = (*GithubGraphql)(nil)
+var _ core.PluginModel = (*GithubGraphql)(nil)
 var _ core.CloseablePluginTask = (*GithubGraphql)(nil)
 
 // PluginEntry exports a symbol for Framework to load
@@ -57,6 +59,10 @@ func (plugin GithubGraphql) Init(config *viper.Viper, logger 
core.Logger, db *go
        return nil
 }
 
+func (plugin GithubGraphql) GetTablesInfo() []core.Tabler {
+       return []core.Tabler{}
+}
+
 func (plugin GithubGraphql) SubTaskMetas() []core.SubTaskMeta {
        return []core.SubTaskMeta{
                tasks.CollectRepoMeta,
diff --git a/plugins/gitlab/impl/impl.go b/plugins/gitlab/impl/impl.go
index af16454b1..350656e23 100644
--- a/plugins/gitlab/impl/impl.go
+++ b/plugins/gitlab/impl/impl.go
@@ -34,9 +34,9 @@ import (
 
 var _ core.PluginMeta = (*Gitlab)(nil)
 var _ core.PluginInit = (*Gitlab)(nil)
-var _ core.PluginModel = (*Gitlab)(nil)
 var _ core.PluginTask = (*Gitlab)(nil)
 var _ core.PluginApi = (*Gitlab)(nil)
+var _ core.PluginModel = (*Gitlab)(nil)
 var _ core.PluginMigration = (*Gitlab)(nil)
 var _ core.PluginBlueprintV100 = (*Gitlab)(nil)
 var _ core.CloseablePluginTask = (*Gitlab)(nil)
diff --git a/plugins/icla/plugin_main.go b/plugins/icla/plugin_main.go
index e9125ccff..1298f3e15 100644
--- a/plugins/icla/plugin_main.go
+++ b/plugins/icla/plugin_main.go
@@ -37,6 +37,7 @@ var _ core.PluginMeta = (*Icla)(nil)
 var _ core.PluginInit = (*Icla)(nil)
 var _ core.PluginTask = (*Icla)(nil)
 var _ core.PluginApi = (*Icla)(nil)
+var _ core.PluginModel = (*Icla)(nil)
 var _ core.PluginMigration = (*Icla)(nil)
 var _ core.CloseablePluginTask = (*Icla)(nil)
 
@@ -45,12 +46,6 @@ var PluginEntry Icla //nolint
 
 type Icla struct{}
 
-func (plugin Icla) GetTablesInfo() []core.Tabler {
-       return []core.Tabler{
-               &models.IclaCommitter{},
-       }
-}
-
 func (plugin Icla) Description() string {
        return "collect some Icla data"
 }
@@ -59,6 +54,12 @@ func (plugin Icla) Init(config *viper.Viper, logger 
core.Logger, db *gorm.DB) er
        return nil
 }
 
+func (plugin Icla) GetTablesInfo() []core.Tabler {
+       return []core.Tabler{
+               &models.IclaCommitter{},
+       }
+}
+
 func (plugin Icla) SubTaskMetas() []core.SubTaskMeta {
        return []core.SubTaskMeta{
                tasks.CollectCommitterMeta,
diff --git a/plugins/jenkins/impl/impl.go b/plugins/jenkins/impl/impl.go
index 34b589113..6d1813d14 100644
--- a/plugins/jenkins/impl/impl.go
+++ b/plugins/jenkins/impl/impl.go
@@ -36,6 +36,7 @@ var _ core.PluginMeta = (*Jenkins)(nil)
 var _ core.PluginInit = (*Jenkins)(nil)
 var _ core.PluginTask = (*Jenkins)(nil)
 var _ core.PluginApi = (*Jenkins)(nil)
+var _ core.PluginModel = (*Jenkins)(nil)
 var _ core.PluginMigration = (*Jenkins)(nil)
 var _ core.CloseablePluginTask = (*Jenkins)(nil)
 
diff --git a/plugins/jira/impl/impl.go b/plugins/jira/impl/impl.go
index f54e2d67c..c7705aa5a 100644
--- a/plugins/jira/impl/impl.go
+++ b/plugins/jira/impl/impl.go
@@ -19,10 +19,11 @@ package impl
 
 import (
        "fmt"
-       "github.com/apache/incubator-devlake/errors"
        "net/http"
        "time"
 
+       "github.com/apache/incubator-devlake/errors"
+
        "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/helper"
        "github.com/apache/incubator-devlake/plugins/jira/api"
@@ -37,6 +38,7 @@ var _ core.PluginMeta = (*Jira)(nil)
 var _ core.PluginInit = (*Jira)(nil)
 var _ core.PluginTask = (*Jira)(nil)
 var _ core.PluginApi = (*Jira)(nil)
+var _ core.PluginModel = (*Jira)(nil)
 var _ core.PluginMigration = (*Jira)(nil)
 var _ core.PluginBlueprintV100 = (*Jira)(nil)
 var _ core.CloseablePluginTask = (*Jira)(nil)
@@ -47,6 +49,7 @@ func (plugin Jira) Init(config *viper.Viper, logger 
core.Logger, db *gorm.DB) er
        api.Init(config, logger, db)
        return nil
 }
+
 func (plugin Jira) GetTablesInfo() []core.Tabler {
        return []core.Tabler{
                &models.ApiMyselfResponse{},
diff --git a/plugins/org/impl/impl.go b/plugins/org/impl/impl.go
index b6c8b01c9..76ed1ba9b 100644
--- a/plugins/org/impl/impl.go
+++ b/plugins/org/impl/impl.go
@@ -31,6 +31,7 @@ import (
 var _ core.PluginMeta = (*Org)(nil)
 var _ core.PluginInit = (*Org)(nil)
 var _ core.PluginTask = (*Org)(nil)
+var _ core.PluginModel = (*Org)(nil)
 
 type Org struct {
        handlers *api.Handlers
diff --git a/plugins/refdiff/refdiff.go b/plugins/refdiff/refdiff.go
index 8a4e7fad8..342b9e006 100644
--- a/plugins/refdiff/refdiff.go
+++ b/plugins/refdiff/refdiff.go
@@ -34,6 +34,8 @@ var _ core.PluginMeta = (*RefDiff)(nil)
 var _ core.PluginInit = (*RefDiff)(nil)
 var _ core.PluginTask = (*RefDiff)(nil)
 var _ core.PluginApi = (*RefDiff)(nil)
+var _ core.PluginModel = (*RefDiff)(nil)
+var _ core.PluginMetric = (*RefDiff)(nil)
 
 // PluginEntry is a variable exported for Framework to search and load
 var PluginEntry RefDiff //nolint
@@ -44,10 +46,26 @@ func (plugin RefDiff) Description() string {
        return "Calculate commits diff for specified ref pairs based on 
`commits` and `commit_parents` tables"
 }
 
+func (plugin RefDiff) RequiredDataEntities() (data []map[string]interface{}, 
err errors.Error) {
+       return []map[string]interface{}{}, nil
+}
+
 func (plugin RefDiff) GetTablesInfo() []core.Tabler {
        return []core.Tabler{}
 }
 
+func (plugin RefDiff) IsProjectMetric() bool {
+       return false
+}
+
+func (plugin RefDiff) RunAfter() ([]string, errors.Error) {
+       return []string{}, nil
+}
+
+func (plugin RefDiff) Settings() interface{} {
+       return nil
+}
+
 func (plugin RefDiff) Init(config *viper.Viper, logger core.Logger, db 
*gorm.DB) errors.Error {
        return nil
 }
diff --git a/plugins/starrocks/starrocks.go b/plugins/starrocks/starrocks.go
index 930bc7750..0db79922f 100644
--- a/plugins/starrocks/starrocks.go
+++ b/plugins/starrocks/starrocks.go
@@ -27,6 +27,11 @@ import (
 
 type StarRocks string
 
+// make sure interface is implemented
+var _ core.PluginMeta = (*StarRocks)(nil)
+var _ core.PluginTask = (*StarRocks)(nil)
+var _ core.PluginModel = (*StarRocks)(nil)
+
 func (s StarRocks) SubTaskMetas() []core.SubTaskMeta {
        return []core.SubTaskMeta{
                LoadDataTaskMeta,
diff --git a/plugins/tapd/impl/impl.go b/plugins/tapd/impl/impl.go
index af9d9572f..86f8f1461 100644
--- a/plugins/tapd/impl/impl.go
+++ b/plugins/tapd/impl/impl.go
@@ -37,6 +37,7 @@ var _ core.PluginMeta = (*Tapd)(nil)
 var _ core.PluginInit = (*Tapd)(nil)
 var _ core.PluginTask = (*Tapd)(nil)
 var _ core.PluginApi = (*Tapd)(nil)
+var _ core.PluginModel = (*Tapd)(nil)
 var _ core.PluginMigration = (*Tapd)(nil)
 var _ core.CloseablePluginTask = (*Tapd)(nil)
 
diff --git a/plugins/webhook/impl/impl.go b/plugins/webhook/impl/impl.go
index e555d6f96..372c3453e 100644
--- a/plugins/webhook/impl/impl.go
+++ b/plugins/webhook/impl/impl.go
@@ -30,6 +30,7 @@ import (
 var _ core.PluginMeta = (*Webhook)(nil)
 var _ core.PluginInit = (*Webhook)(nil)
 var _ core.PluginApi = (*Webhook)(nil)
+var _ core.PluginModel = (*Webhook)(nil)
 var _ core.PluginMigration = (*Webhook)(nil)
 
 type Webhook struct{}
@@ -43,6 +44,10 @@ func (plugin Webhook) Init(config *viper.Viper, logger 
core.Logger, db *gorm.DB)
        return nil
 }
 
+func (plugin Webhook) GetTablesInfo() []core.Tabler {
+       return []core.Tabler{}
+}
+
 // PkgPath information lost when compiled as plugin(.so)
 func (plugin Webhook) RootPkgPath() string {
        return "github.com/apache/incubator-devlake/plugins/webhook"
diff --git a/services/project.go b/services/project.go
new file mode 100644
index 000000000..29a7f506a
--- /dev/null
+++ b/services/project.go
@@ -0,0 +1,159 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 services
+
+import (
+       "github.com/apache/incubator-devlake/errors"
+       "github.com/apache/incubator-devlake/models"
+       "github.com/apache/incubator-devlake/plugins/helper"
+)
+
+// ProjectQuery used to query projects as the api project input
+type ProjectQuery struct {
+       Page     int `form:"page"`
+       PageSize int `form:"pageSize"`
+}
+
+// CreateProject accepts a project instance and insert it to database
+func CreateProject(project *models.Project) errors.Error {
+       /*project, err := encryptProject(project)
+       if err != nil {
+               return err
+       }*/
+       err := CreateDbProject(project)
+       if err != nil {
+               return err
+       }
+       return nil
+}
+
+// CreateProjectMetric accepts a ProjectMetric instance and insert it to 
database
+func CreateProjectMetric(projectMetric *models.ProjectMetric) errors.Error {
+       /*enProjectMetric, err := encryptProjectMetric(projectMetric)
+       if err != nil {
+               return err
+       }*/
+       err := CreateDbProjectMetric(projectMetric)
+       if err != nil {
+               return err
+       }
+       return nil
+}
+
+// GetProject returns a Project
+func GetProject(name string) (*models.Project, errors.Error) {
+       project, err := GetDbProject(name)
+       if err != nil {
+               return nil, errors.Convert(err)
+       }
+
+       /*project, err = decryptProject(project)
+       if err != nil {
+               return nil, errors.Convert(err)
+       }*/
+
+       return project, nil
+}
+
+// GetProjectMetrics returns a ProjectMetric
+func GetProjectMetrics(projectName string, pluginName string) 
(*models.ProjectMetric, errors.Error) {
+       projectMetric, err := GetDbProjectMetrics(projectName, pluginName)
+       if err != nil {
+               return nil, errors.Convert(err)
+       }
+
+       /*projectMetric, err = decryptProjectMetric(projectMetric)
+       if err != nil {
+               return nil, errors.Convert(err)
+       }*/
+
+       return projectMetric, nil
+}
+
+// GetProjects returns a paginated list of Projects based on `query`
+func GetProjects(query *ProjectQuery) ([]*models.Project, int64, errors.Error) 
{
+       projects, count, err := GetDbProjects(query)
+       if err != nil {
+               return nil, 0, errors.Convert(err)
+       }
+
+       /*for i, project := range projects {
+               projects[i], err = decryptProject(project)
+               if err != nil {
+                       return nil, 0, err
+               }
+       }*/
+
+       return projects, count, nil
+}
+
+// PatchProject FIXME ...
+func PatchProject(name string, body map[string]interface{}) (*models.Project, 
errors.Error) {
+       // load record from db
+       project, err := GetProject(name)
+       if err != nil {
+               return nil, err
+       }
+
+       err = helper.DecodeMapStruct(body, project)
+       if err != nil {
+               return nil, err
+       }
+
+       /*enProject, err := encryptProject(project)
+       if err != nil {
+               return nil, err
+       }*/
+
+       // save
+       err = SaveDbProject(project)
+       if err != nil {
+               return nil, errors.Internal.Wrap(err, "error saving project")
+       }
+
+       // done
+       return project, nil
+}
+
+// PatchProjectMetric FIXME ...
+func PatchProjectMetric(projectName string, pluginName string, body 
map[string]interface{}) (*models.ProjectMetric, errors.Error) {
+       // load record from db
+       projectMetric, err := GetDbProjectMetrics(projectName, pluginName)
+       if err != nil {
+               return nil, err
+       }
+
+       err = helper.DecodeMapStruct(body, projectMetric)
+       if err != nil {
+               return nil, err
+       }
+
+       /*enProjectMetric, err := encryptProjectMetric(projectMetric)
+       if err != nil {
+               return nil, err
+       }*/
+
+       // save
+       err = SaveDbProjectMetric(projectMetric)
+       if err != nil {
+               return nil, errors.Internal.Wrap(err, "error saving project")
+       }
+
+       // done
+       return projectMetric, nil
+}
diff --git a/services/project_helper.go b/services/project_helper.go
new file mode 100644
index 000000000..bde6b545d
--- /dev/null
+++ b/services/project_helper.go
@@ -0,0 +1,168 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 services
+
+import (
+       goerror "errors"
+       "fmt"
+
+       "github.com/apache/incubator-devlake/errors"
+       "github.com/apache/incubator-devlake/models"
+       "gorm.io/gorm"
+)
+
+// CreateDbProject accepts a project instance and insert it to database
+func CreateDbProject(project *models.Project) errors.Error {
+       err := db.Create(project).Error
+       if err != nil {
+               return errors.Default.Wrap(err, "error creating DB project")
+       }
+       return nil
+}
+
+// CreateDbProjectMetric accepts a project metric instance and insert it to 
database
+func CreateDbProjectMetric(projectMetric *models.ProjectMetric) errors.Error {
+       err := db.Create(projectMetric).Error
+       if err != nil {
+               return errors.Default.Wrap(err, "error creating DB project 
metric")
+       }
+       return nil
+}
+
+// SaveDbProject save a project instance and update it to database
+func SaveDbProject(project *models.Project) errors.Error {
+       err := db.Save(project).Error
+       if err != nil {
+               return errors.Default.Wrap(err, "error saving DB project")
+       }
+       return nil
+}
+
+// SaveDbProjectMetric save a project instance and update it to database
+func SaveDbProjectMetric(projectMetric *models.ProjectMetric) errors.Error {
+       err := db.Save(projectMetric).Error
+       if err != nil {
+               return errors.Default.Wrap(err, "error saving DB project 
metric")
+       }
+       return nil
+}
+
+// GetDbProjects returns a paginated list of Project based on `query`
+func GetDbProjects(query *ProjectQuery) ([]*models.Project, int64, 
errors.Error) {
+       projects := make([]*models.Project, 0)
+       db := db.Model(projects).Order("created_at desc")
+
+       var count int64
+       err := db.Count(&count).Error
+       if err != nil {
+               return nil, 0, errors.Default.Wrap(err, "error getting DB count 
of project")
+       }
+       db = processDbClausesWithPager(db, query.PageSize, query.Page)
+
+       err = db.Find(&projects).Error
+       if err != nil {
+               return nil, 0, errors.Default.Wrap(err, "error finding DB 
project")
+       }
+
+       return projects, count, nil
+}
+
+// GetDbProject returns the detail of a given project name
+func GetDbProject(name string) (*models.Project, errors.Error) {
+       project := &models.Project{}
+       project.Name = name
+
+       err := db.First(project).Error
+       if err != nil {
+               if goerror.Is(err, gorm.ErrRecordNotFound) {
+                       return nil, errors.NotFound.Wrap(err, 
fmt.Sprintf("could not find project [%s] in DB", name))
+               }
+               return nil, errors.Default.Wrap(err, "error getting project 
from DB")
+       }
+
+       return project, nil
+}
+
+// GetDbProjectMetrics returns the detail of a given project name
+func GetDbProjectMetrics(projectName string, pluginName string) 
(*models.ProjectMetric, errors.Error) {
+       projectMetric := &models.ProjectMetric{}
+       projectMetric.ProjectName = projectName
+       projectMetric.PluginName = pluginName
+
+       err := db.First(projectMetric).Error
+       if err != nil {
+               if goerror.Is(err, gorm.ErrRecordNotFound) {
+                       return nil, errors.NotFound.Wrap(err, 
fmt.Sprintf("could not find project metric [%s][%s] in DB", projectName, 
pluginName))
+               }
+               return nil, errors.Default.Wrap(err, "error getting project 
metric from DB")
+       }
+
+       return projectMetric, nil
+}
+
+// encryptProject
+/*func encryptProject(project *models.Project) (*models.Project, errors.Error) 
{
+       encKey := config.GetConfig().GetString(core.EncodeKeyEnvStr)
+
+       describeEncrypt, err := core.Encrypt(encKey, project.Description)
+       if err != nil {
+               return nil, err
+       }
+       project.Description = describeEncrypt
+
+       return project, nil
+}
+
+// encryptProjectMetric
+func encryptProjectMetric(projectMetric *models.ProjectMetric) 
(*models.ProjectMetric, errors.Error) {
+       encKey := config.GetConfig().GetString(core.EncodeKeyEnvStr)
+
+       pluginOption, err := core.Encrypt(encKey, projectMetric.PluginOption)
+       if err != nil {
+               return nil, err
+       }
+       projectMetric.PluginOption = pluginOption
+
+       return projectMetric, nil
+}*/
+
+// decryptProject
+/*func decryptProject(project *models.Project) (*models.Project, errors.Error) 
{
+       encKey := config.GetConfig().GetString(core.EncodeKeyEnvStr)
+
+       describe, err := core.Decrypt(encKey, project.Description)
+       if err != nil {
+               return nil, err
+       }
+       project.Description = describe
+
+       return project, nil
+}
+
+// decryptProjectMetric
+func decryptProjectMetric(projectMetric *models.ProjectMetric) 
(*models.ProjectMetric, errors.Error) {
+       encKey := config.GetConfig().GetString(core.EncodeKeyEnvStr)
+
+       pluginOption, err := core.Decrypt(encKey, projectMetric.PluginOption)
+       if err != nil {
+               return nil, err
+       }
+       projectMetric.PluginOption = pluginOption
+
+       return projectMetric, nil
+}*/


Reply via email to